docker_helper.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. import os
  2. import socket
  3. import fnmatch
  4. import json
  5. import docker
  6. import time
  7. import re
  8. import traceback
  9. from threading import Thread
  10. from colorama import Fore, Style
  11. from toolset.utils.output_helper import log
  12. from toolset.utils.metadata_helper import gather_tests
  13. from toolset.utils.database_helper import test_database
  14. def clean(benchmarker_config):
  15. '''
  16. Cleans all the docker images from the system
  17. '''
  18. # Clean the app server images
  19. client = docker.DockerClient(
  20. base_url=benchmarker_config.server_docker_host)
  21. client.images.prune()
  22. for image in client.images.list():
  23. if len(image.tags) > 0:
  24. # 'techempower/tfb.test.gemini:0.1' -> 'techempower/tfb.test.gemini'
  25. image_tag = image.tags[0].split(':')[0]
  26. if image_tag != 'techempower/tfb' and 'techempower' in image_tag:
  27. client.images.remove(image.id, force=True)
  28. client.images.prune()
  29. # Clean the database server images
  30. client = docker.DockerClient(
  31. base_url=benchmarker_config.database_docker_host)
  32. client.images.prune()
  33. for image in client.images.list():
  34. if len(image.tags) > 0:
  35. # 'techempower/tfb.test.gemini:0.1' -> 'techempower/tfb.test.gemini'
  36. image_tag = image.tags[0].split(':')[0]
  37. if image_tag != 'techempower/tfb':
  38. client.images.remove(image.id, force=True)
  39. client.images.prune()
  40. def build(benchmarker_config, test_names, build_log_dir=os.devnull):
  41. '''
  42. Builds the dependency chain as well as the test implementation docker images
  43. for the given tests.
  44. '''
  45. tests = gather_tests(
  46. include=test_names, benchmarker_config=benchmarker_config)
  47. for test in tests:
  48. log_prefix = "%s: " % test.name
  49. # Build the test image
  50. test_docker_file = "%s.dockerfile" % test.name
  51. build_log_file = build_log_dir
  52. if build_log_dir is not os.devnull:
  53. build_log_file = os.path.join(
  54. build_log_dir,
  55. "%s.log" % test_docker_file.replace(".dockerfile", "").lower())
  56. with open(build_log_file, 'w') as build_log:
  57. try:
  58. for line in docker.APIClient(
  59. base_url=benchmarker_config.server_docker_host).build(
  60. path=test.directory,
  61. dockerfile=test_docker_file,
  62. tag="techempower/tfb.test.%s" %
  63. test_docker_file.replace(".dockerfile", ""),
  64. forcerm=True):
  65. if line.startswith('{"stream":'):
  66. line = json.loads(line)
  67. line = line[line.keys()[0]].encode('utf-8')
  68. log(line,
  69. prefix=log_prefix,
  70. file=build_log,
  71. color=Fore.WHITE + Style.BRIGHT \
  72. if re.match(r'^Step \d+\/\d+', line) else '')
  73. except Exception:
  74. tb = traceback.format_exc()
  75. log("Docker build failed; terminating",
  76. prefix=log_prefix,
  77. file=build_log,
  78. color=Fore.RED)
  79. log(tb, prefix=log_prefix, file=build_log)
  80. return 1
  81. return 0
  82. def run(benchmarker_config, test, run_log_dir):
  83. '''
  84. Run the given Docker container(s)
  85. '''
  86. client = docker.DockerClient(
  87. base_url=benchmarker_config.server_docker_host)
  88. log_prefix = "%s: " % test.name
  89. container = None
  90. try:
  91. def watch_container(docker_container, docker_file):
  92. with open(
  93. os.path.join(run_log_dir, "%s.log" % docker_file.replace(
  94. ".dockerfile", "").lower()), 'w') as run_log:
  95. for line in docker_container.logs(stream=True):
  96. log(line, prefix=log_prefix, file=run_log)
  97. extra_hosts = None
  98. name = "tfb-server"
  99. if benchmarker_config.network is None:
  100. extra_hosts = {
  101. socket.gethostname(): str(benchmarker_config.server_host),
  102. 'tfb-server': str(benchmarker_config.server_host),
  103. 'tfb-database': str(benchmarker_config.database_host)
  104. }
  105. name = None
  106. sysctl = {'net.core.somaxconn': 65535}
  107. ulimit = [{
  108. 'name': 'nofile',
  109. 'hard': 200000,
  110. 'soft': 200000
  111. }, {
  112. 'name': 'rtprio',
  113. 'hard': 99,
  114. 'soft': 99
  115. }]
  116. container = client.containers.run(
  117. "techempower/tfb.test.%s" % test.name,
  118. name=name,
  119. network=benchmarker_config.network,
  120. network_mode=benchmarker_config.network_mode,
  121. stderr=True,
  122. detach=True,
  123. init=True,
  124. extra_hosts=extra_hosts,
  125. privileged=True,
  126. ulimits=ulimit,
  127. sysctls=sysctl)
  128. watch_thread = Thread(
  129. target=watch_container,
  130. args=(
  131. container,
  132. "%s.dockerfile" % test.name,
  133. ))
  134. watch_thread.daemon = True
  135. watch_thread.start()
  136. except Exception:
  137. with open(
  138. os.path.join(run_log_dir, "%s.log" % test.name.lower()),
  139. 'w') as run_log:
  140. tb = traceback.format_exc()
  141. log("Running docker cointainer: %s.dockerfile failed" % test.name,
  142. prefix=log_prefix,
  143. file=run_log)
  144. log(tb, prefix=log_prefix, file=run_log)
  145. return container
  146. def stop(benchmarker_config=None,
  147. container=None,
  148. database_container=None,
  149. test=None):
  150. '''
  151. Attempts to stop the running test container.
  152. '''
  153. client = docker.DockerClient(
  154. base_url=benchmarker_config.server_docker_host)
  155. if container is None:
  156. for container in client.containers.list():
  157. if len(
  158. container.image.tags
  159. ) > 0 and 'techempower' in container.image.tags[0] and 'tfb:latest' not in container.image.tags[0]:
  160. container.stop()
  161. else:
  162. # Stop the running container
  163. container.stop()
  164. database_client = docker.DockerClient(
  165. base_url=benchmarker_config.database_docker_host)
  166. # Stop the database container
  167. if database_container is None:
  168. for container in database_client.containers.list():
  169. if len(
  170. container.image.tags
  171. ) > 0 and 'techempower' in container.image.tags[0] and 'tfb:latest' not in container.image.tags[0]:
  172. container.stop()
  173. else:
  174. database_container.stop()
  175. client.containers.prune()
  176. if benchmarker_config.server_docker_host != benchmarker_config.database_docker_host:
  177. database_client.containers.prune()
  178. def find(path, pattern):
  179. '''
  180. Finds and returns all the the files matching the given pattern recursively in
  181. the given path.
  182. '''
  183. for root, dirs, files in os.walk(path):
  184. for name in files:
  185. if fnmatch.fnmatch(name, pattern):
  186. return os.path.join(root, name)
  187. def start_database(benchmarker_config, test, database):
  188. '''
  189. Sets up a container for the given database and port, and starts said docker
  190. container.
  191. '''
  192. image_name = "techempower/%s:latest" % database
  193. log_prefix = image_name + ": "
  194. database_dir = os.path.join(benchmarker_config.fwroot, "toolset",
  195. "databases", database)
  196. docker_file = "%s.dockerfile" % database
  197. client = docker.DockerClient(
  198. base_url=benchmarker_config.database_docker_host)
  199. try:
  200. # Don't pull if we have it
  201. client.images.get(image_name)
  202. log("Found published image; skipping build", prefix=log_prefix)
  203. except:
  204. # Build the database image
  205. for line in docker.APIClient(
  206. base_url=benchmarker_config.database_docker_host).build(
  207. path=database_dir,
  208. dockerfile=docker_file,
  209. tag="techempower/%s" % database):
  210. if line.startswith('{"stream":'):
  211. line = json.loads(line)
  212. line = line[line.keys()[0]].encode('utf-8')
  213. log(line,
  214. prefix=log_prefix,
  215. color=Fore.WHITE + Style.BRIGHT \
  216. if re.match(r'^Step \d+\/\d+', line) else '')
  217. client = docker.DockerClient(
  218. base_url=benchmarker_config.database_docker_host)
  219. sysctl = {'net.core.somaxconn': 65535, 'kernel.sem': "250 32000 256 512"}
  220. ulimit = [{'name': 'nofile', 'hard': 65535, 'soft': 65535}]
  221. container = client.containers.run(
  222. "techempower/%s" % database,
  223. name="tfb-database",
  224. network=benchmarker_config.network,
  225. network_mode=benchmarker_config.network_mode,
  226. detach=True,
  227. ulimits=ulimit,
  228. sysctls=sysctl)
  229. # Sleep until the database accepts connections
  230. slept = 0
  231. max_sleep = 60
  232. database_ready = False
  233. while not database_ready and slept < max_sleep:
  234. time.sleep(1)
  235. slept += 1
  236. database_ready = test_database(benchmarker_config, database)
  237. if not database_ready:
  238. log("Database was not ready after startup", prefix=log_prefix)
  239. return container
  240. def test_client_connection(benchmarker_config, url):
  241. '''
  242. Tests that the app server at the given url responds successfully to a
  243. request.
  244. '''
  245. client = docker.DockerClient(
  246. base_url=benchmarker_config.client_docker_host)
  247. try:
  248. client.images.get('techempower/tfb.wrk:latest')
  249. except:
  250. log("Attempting docker pull for image (this can take some time)",
  251. prefix="techempower/tfb.wrk:latest: ")
  252. client.images.pull('techempower/tfb.wrk:latest')
  253. try:
  254. client.containers.run(
  255. 'techempower/tfb.wrk',
  256. 'curl %s' % url,
  257. network=benchmarker_config.network,
  258. network_mode=benchmarker_config.network_mode)
  259. except:
  260. return False
  261. return True
  262. def benchmark(benchmarker_config, script, variables, raw_file):
  263. '''
  264. Runs the given remote_script on the wrk container on the client machine.
  265. '''
  266. def watch_container(container, raw_file):
  267. with open(raw_file, 'w') as benchmark_file:
  268. for line in container.logs(stream=True):
  269. log(line, file=benchmark_file)
  270. client = docker.DockerClient(
  271. base_url=benchmarker_config.client_docker_host)
  272. sysctl = {'net.core.somaxconn': 65535}
  273. ulimit = [{'name': 'nofile', 'hard': 65535, 'soft': 65535}]
  274. watch_container(
  275. client.containers.run(
  276. "techempower/tfb.wrk",
  277. "/bin/bash /%s" % script,
  278. environment=variables,
  279. network=benchmarker_config.network,
  280. network_mode=benchmarker_config.network_mode,
  281. detach=True,
  282. stderr=True,
  283. ulimits=ulimit,
  284. sysctls=sysctl), raw_file)