123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566 |
- import os
- import socket
- import fnmatch
- import multiprocessing
- import json
- import docker
- import time
- import re
- import traceback
- from threading import Thread
- from colorama import Fore, Style
- from toolset.utils.output_helper import log
- from toolset.utils.metadata_helper import gather_tests
- from toolset.utils.ordered_set import OrderedSet
- from toolset.utils.database_helper import test_database
- def clean(benchmarker_config):
- '''
- Cleans all the docker images from the system
- '''
- # Clean the app server images
- client = docker.DockerClient(
- base_url=benchmarker_config.server_docker_host)
- client.images.prune()
- for image in client.images.list():
- if len(image.tags) > 0:
- # 'techempower/tfb.test.gemini:0.1' -> 'techempower/tfb.test.gemini'
- image_tag = image.tags[0].split(':')[0]
- if image_tag != 'techempower/tfb':
- client.images.remove(image.id, force=True)
- client.images.prune()
- # Clean the database server images
- client = docker.DockerClient(
- base_url=benchmarker_config.database_docker_host)
- client.images.prune()
- for image in client.images.list():
- if len(image.tags) > 0:
- # 'techempower/tfb.test.gemini:0.1' -> 'techempower/tfb.test.gemini'
- image_tag = image.tags[0].split(':')[0]
- if image_tag != 'techempower/tfb':
- client.images.remove(image.id, force=True)
- client.images.prune()
- def publish(benchmarker_config):
- '''
- Builds fresh versions of all the docker container images known in the
- system and attempts to publish them to dockerhub.
- Note: this requires `docker login` to have been performed prior to the
- call.
- '''
- start_time = time.time()
- docker_buildargs = {
- 'CPU_COUNT': str(multiprocessing.cpu_count()),
- 'MAX_CONCURRENCY': str(max(benchmarker_config.concurrency_levels)),
- 'TFB_DATABASE': str(benchmarker_config.database_host)
- }
- # Force building instead of pulling
- benchmarker_config.build = True
- tests = gather_tests(benchmarker_config=benchmarker_config)
- for test in tests:
- __build_dependencies(benchmarker_config, test, docker_buildargs)
- client = docker.DockerClient(
- base_url=benchmarker_config.server_docker_host)
- client.login("USERNAME", "PASSWORD")
- for image in client.images.list():
- has_tags = len(image.tags) > 0
- if has_tags and 'techempower' in image.tags[0] and 'tfb.test' not in image.tags[0]:
- log("Pushing docker image: %s" % image.tags[0].split(':')[0])
- client.images.push(image.tags[0].split(':')[0])
- log("Publish took: %s seconds" % (time.time() - start_time))
- def build(benchmarker_config, test_names, build_log_dir=os.devnull):
- '''
- Builds the dependency chain as well as the test implementation docker images
- for the given tests.
- '''
- docker_buildargs = {
- 'MAX_CONCURRENCY': str(max(benchmarker_config.concurrency_levels)),
- 'TFB_DATABASE': str(benchmarker_config.database_host)
- }
- tests = gather_tests(
- include=test_names, benchmarker_config=benchmarker_config)
- for test in tests:
- log_prefix = "%s: " % test.name
- if __build_dependencies(benchmarker_config, test, docker_buildargs,
- build_log_dir) > 0:
- return 1
- # Build the test image
- test_docker_file = "%s.dockerfile" % test.name
- build_log_file = build_log_dir
- if build_log_dir is not os.devnull:
- build_log_file = os.path.join(
- build_log_dir,
- "%s.log" % test_docker_file.replace(".dockerfile", "").lower())
- with open(build_log_file, 'w') as build_log:
- try:
- for line in docker.APIClient(
- base_url=benchmarker_config.server_docker_host).build(
- path=test.directory,
- dockerfile=test_docker_file,
- tag="techempower/tfb.test.%s" %
- test_docker_file.replace(".dockerfile", ""),
- buildargs=docker_buildargs,
- forcerm=True):
- if line.startswith('{"stream":'):
- line = json.loads(line)
- line = line[line.keys()[0]].encode('utf-8')
- log(line,
- prefix=log_prefix,
- file=build_log,
- color=Fore.WHITE + Style.BRIGHT \
- if re.match(r'^Step \d+\/\d+', line) else '')
- except Exception:
- tb = traceback.format_exc()
- log("Docker build failed; terminating",
- prefix=log_prefix,
- file=build_log,
- color=Fore.RED)
- log(tb, prefix=log_prefix, file=build_log)
- return 1
- return 0
- def run(benchmarker_config, test, run_log_dir):
- '''
- Run the given Docker container(s)
- '''
- client = docker.DockerClient(
- base_url=benchmarker_config.server_docker_host)
- containers = []
- log_prefix = "%s: " % test.name
- try:
- def watch_container(container, docker_file):
- with open(
- os.path.join(run_log_dir, "%s.log" % docker_file.replace(
- ".dockerfile", "").lower()), 'w') as run_log:
- for line in container.logs(stream=True):
- log(line, prefix=log_prefix, file=run_log)
- extra_hosts = None
- name = "tfb-server"
- if benchmarker_config.network is None:
- extra_hosts = {
- socket.gethostname(): str(benchmarker_config.server_host),
- 'tfb-server': str(benchmarker_config.server_host),
- 'tfb-database': str(benchmarker_config.database_host)
- }
- name = None
- sysctl = {'net.core.somaxconn': 65535}
- ulimit = [{
- 'name': 'nofile',
- 'hard': 200000,
- 'soft': 200000
- }, {
- 'name': 'rtprio',
- 'hard': 99,
- 'soft': 99
- }]
- container = client.containers.run(
- "techempower/tfb.test.%s" % test.name,
- name=name,
- network=benchmarker_config.network,
- network_mode=benchmarker_config.network_mode,
- stderr=True,
- detach=True,
- init=True,
- extra_hosts=extra_hosts,
- privileged=True,
- ulimits=ulimit,
- sysctls=sysctl)
- containers.append(container)
- watch_thread = Thread(
- target=watch_container,
- args=(
- container,
- "%s.dockerfile" % test.name,
- ))
- watch_thread.daemon = True
- watch_thread.start()
- except Exception:
- with open(
- os.path.join(run_log_dir, "%s.log" % test.name.lower()),
- 'w') as run_log:
- tb = traceback.format_exc()
- log("Running docker cointainer: %s.dockerfile failed" % test.name,
- prefix=log_prefix,
- file=run_log)
- log(tb, prefix=log_prefix, file=run_log)
- return containers
- def successfully_running_containers(benchmarker_config, test, out):
- '''
- Returns whether all the expected containers for the given docker_files are
- running.
- '''
- client = docker.DockerClient(
- base_url=benchmarker_config.server_docker_host)
- running_container_images = []
- for container in client.containers.list():
- # 'techempower/tfb.test.gemini:0.1' -> 'gemini'
- image_tag = container.image.tags[0].split(':')[0][21:]
- running_container_images.append(image_tag)
- if test.name not in running_container_images:
- log_prefix = "%s: " % test.name
- log("ERROR: Expected techempower/tfb.test.%s to be running container" %
- test.name,
- prefix=log_prefix,
- file=out)
- return False
- return True
- def stop(benchmarker_config=None,
- containers=None,
- database_container=None,
- test=None):
- '''
- Attempts to stop the running test container.
- '''
- client = docker.DockerClient(
- base_url=benchmarker_config.server_docker_host)
- if containers is None:
- for container in client.containers.list():
- if len(
- container.image.tags
- ) > 0 and 'techempower' in container.image.tags[0] and 'tfb:latest' not in container.image.tags[0]:
- container.stop()
- # 'techempower/tfb.test.gemini:0.1' -> 'techempower/tfb.test.gemini'
- client.images.remove(
- container.image.tags[0].split(':')[0], force=True)
- else:
- # Stop all our running containers
- for container in containers:
- container.stop()
- # 'techempower/tfb.test.gemini:0.1' -> 'techempower/tfb.test.gemini'
- client.images.remove(
- container.image.tags[0].split(':')[0], force=True)
- database_client = docker.DockerClient(
- base_url=benchmarker_config.database_docker_host)
- # Stop the database container
- if database_container is None:
- for container in database_client.containers.list():
- if len(
- container.image.tags
- ) > 0 and 'techempower' in container.image.tags[0] and 'tfb:latest' not in container.image.tags[0]:
- container.stop()
- # 'techempower/tfb.test.gemini:0.1' -> 'techempower/tfb.test.gemini'
- client.images.remove(
- container.image.tags[0].split(':')[0], force=True)
- else:
- database_container.stop()
- client.images.prune()
- client.containers.prune()
- client.volumes.prune()
- if benchmarker_config.server_docker_host != benchmarker_config.database_docker_host:
- database_client.images.prune()
- database_client.containers.prune()
- database_client.volumes.prune()
- def find(path, pattern):
- '''
- Finds and returns all the the files matching the given pattern recursively in
- the given path.
- '''
- for root, dirs, files in os.walk(path):
- for name in files:
- if fnmatch.fnmatch(name, pattern):
- return os.path.join(root, name)
- def start_database(benchmarker_config, test, database):
- '''
- Sets up a container for the given database and port, and starts said docker
- container.
- '''
- image_name = "techempower/%s:latest" % database
- log_prefix = image_name + ": "
- database_dir = os.path.join(benchmarker_config.fwroot, "toolset", "setup",
- "docker", "databases", database)
- docker_file = "%s.dockerfile" % database
- pulled = False
- client = docker.DockerClient(
- base_url=benchmarker_config.database_docker_host)
- try:
- # Don't pull if we have it
- client.images.get(image_name)
- pulled = True
- log("Found published image; skipping build", prefix=log_prefix)
- except:
- # Pull the dependency image
- try:
- log("Attempting docker pull for image (this can take some time)",
- prefix=log_prefix)
- client.images.pull(image_name)
- pulled = True
- log("Found published image; skipping build", prefix=log_prefix)
- except:
- pass
- if not pulled:
- for line in docker.APIClient(
- base_url=benchmarker_config.database_docker_host).build(
- path=database_dir,
- dockerfile=docker_file,
- tag="techempower/%s" % database):
- if line.startswith('{"stream":'):
- line = json.loads(line)
- line = line[line.keys()[0]].encode('utf-8')
- log(line,
- prefix=log_prefix,
- color=Fore.WHITE + Style.BRIGHT \
- if re.match(r'^Step \d+\/\d+', line) else '')
- client = docker.DockerClient(
- base_url=benchmarker_config.database_docker_host)
- sysctl = {'net.core.somaxconn': 65535, 'kernel.sem': "250 32000 256 512"}
- ulimit = [{'name': 'nofile', 'hard': 65535, 'soft': 65535}]
- container = client.containers.run(
- "techempower/%s" % database,
- name="tfb-database",
- network=benchmarker_config.network,
- network_mode=benchmarker_config.network_mode,
- detach=True,
- ulimits=ulimit,
- sysctls=sysctl)
- # Sleep until the database accepts connections
- slept = 0
- max_sleep = 60
- database_ready = False
- while not database_ready and slept < max_sleep:
- time.sleep(1)
- slept += 1
- database_ready = test_database(benchmarker_config, database)
- if not database_ready:
- log("Database was not ready after startup", prefix=log_prefix)
- return container
- def test_client_connection(benchmarker_config, url):
- '''
- Tests that the app server at the given url responds successfully to a
- request.
- '''
- client = docker.DockerClient(
- base_url=benchmarker_config.client_docker_host)
- try:
- client.images.get('techempower/tfb.wrk:latest')
- except:
- log("Attempting docker pull for image (this can take some time)",
- prefix="techempower/tfb.wrk:latest: ")
- client.images.pull('techempower/tfb.wrk:latest')
- try:
- client.containers.run(
- 'techempower/tfb.wrk',
- 'curl %s' % url,
- network=benchmarker_config.network,
- network_mode=benchmarker_config.network_mode)
- except:
- return False
- return True
- def benchmark(benchmarker_config, script, variables, raw_file):
- '''
- Runs the given remote_script on the wrk container on the client machine.
- '''
- def watch_container(container, raw_file):
- with open(raw_file, 'w') as benchmark_file:
- for line in container.logs(stream=True):
- log(line, file=benchmark_file)
- client = docker.DockerClient(
- base_url=benchmarker_config.client_docker_host)
- sysctl = {'net.core.somaxconn': 65535}
- ulimit = [{'name': 'nofile', 'hard': 65535, 'soft': 65535}]
- watch_container(
- client.containers.run(
- "techempower/tfb.wrk",
- "/bin/bash /%s" % script,
- environment=variables,
- network=benchmarker_config.network,
- network_mode=benchmarker_config.network_mode,
- detach=True,
- stderr=True,
- ulimits=ulimit,
- sysctls=sysctl), raw_file)
- def __gather_dependencies(benchmarker_config, docker_file):
- '''
- Gathers all the known docker dependencies for the given docker image.
- '''
- deps = []
- docker_dir = os.path.join(benchmarker_config.fwroot, "toolset", "setup",
- "docker")
- if os.path.exists(docker_file):
- with open(docker_file) as fp:
- for line in fp.readlines():
- tokens = line.strip().split(' ')
- if tokens[0] == "FROM":
- # This is magic that our base image points to
- if tokens[1].startswith('techempower/'):
- dep_ref = tokens[1].strip().split(':')[0].strip()
- if '/' not in dep_ref:
- raise AttributeError(
- "Could not find docker FROM dependency: %s" %
- dep_ref)
- depToken = dep_ref.split('/')[1]
- deps.append(tokens[1])
- dep_docker_file = os.path.join(
- os.path.dirname(docker_file),
- depToken + ".dockerfile")
- if not os.path.exists(dep_docker_file):
- dep_docker_file = find(docker_dir,
- depToken + ".dockerfile")
- deps.extend(
- __gather_dependencies(benchmarker_config,
- dep_docker_file))
- return deps
- def __build_dependencies(benchmarker_config,
- test,
- docker_buildargs,
- build_log_dir=os.devnull):
- '''
- Builds all the dependency docker images for the given test.
- Does not build the test docker image.
- '''
- dependencies = OrderedSet(
- list(
- reversed(
- __gather_dependencies(
- benchmarker_config,
- os.path.join(test.directory,
- "%s.dockerfile" % test.name)))))
- docker_dir = os.path.join(benchmarker_config.fwroot, "toolset", "setup",
- "docker")
- for dep in dependencies:
- log_prefix = dep + ": "
- pulled = False
- # Do not pull techempower/ images if we are building specifically
- if not benchmarker_config.build and 'techempower/' not in dep:
- client = docker.DockerClient(
- base_url=benchmarker_config.server_docker_host)
- try:
- # If we have it, use it
- client.images.get(dep)
- pulled = True
- log("Found published image; skipping build", prefix=log_prefix)
- except:
- # Pull the dependency image
- try:
- log("Attempting docker pull for image (this can take some time)",
- prefix=log_prefix)
- client.images.pull(dep)
- pulled = True
- log("Found published image; skipping build",
- prefix=log_prefix)
- except:
- log("Docker pull failed; %s could not be found; terminating"
- % dep,
- prefix=log_prefix,
- color=Fore.RED)
- return 1
- if not pulled:
- dep_ref = dep.strip().split(':')[0].strip()
- dependency = dep_ref.split('/')[1]
- build_log_file = build_log_dir
- if build_log_dir is not os.devnull:
- build_log_file = os.path.join(build_log_dir,
- "%s.log" % dependency.lower())
- with open(build_log_file, 'w') as build_log:
- docker_file = os.path.join(test.directory,
- dependency + ".dockerfile")
- if not docker_file or not os.path.exists(docker_file):
- docker_file = find(docker_dir, dependency + ".dockerfile")
- if not docker_file:
- log("Docker build failed; %s could not be found; terminating"
- % (dependency + ".dockerfile"),
- prefix=log_prefix,
- file=build_log,
- color=Fore.RED)
- return 1
- # Build the dependency image
- try:
- for line in docker.APIClient(
- base_url=benchmarker_config.server_docker_host
- ).build(
- path=os.path.dirname(docker_file),
- dockerfile="%s.dockerfile" % dependency,
- tag=dep,
- buildargs=docker_buildargs,
- forcerm=True):
- if line.startswith('{"stream":'):
- line = json.loads(line)
- line = line[line.keys()[0]].encode('utf-8')
- log(line,
- prefix=log_prefix,
- file=build_log,
- color=Fore.WHITE + Style.BRIGHT \
- if re.match(r'^Step \d+\/\d+', line) else '')
- except Exception:
- tb = traceback.format_exc()
- log("Docker dependency build failed; terminating",
- prefix=log_prefix,
- file=build_log,
- color=Fore.RED)
- log(tb, prefix=log_prefix, file=build_log)
- return 1
|