docker_helper.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  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(base_url, path, build_log_file, log_prefix, dockerfile, tag):
  41. '''
  42. Builds docker containers using docker-py low-level api
  43. '''
  44. with open(build_log_file, 'w') as build_log:
  45. try:
  46. client = docker.APIClient(base_url=base_url)
  47. output = client.build(
  48. path=path,
  49. dockerfile=dockerfile,
  50. tag=tag,
  51. forcerm=True,
  52. pull=True)
  53. buffer = ""
  54. for token in output:
  55. if token.startswith('{"stream":'):
  56. token = json.loads(token)
  57. token = token[token.keys()[0]].encode('utf-8')
  58. buffer += token
  59. elif token.startswith('{"errorDetail":'):
  60. token = json.loads(token)
  61. raise Exception(token['errorDetail']['message'])
  62. while "\n" in buffer:
  63. index = buffer.index("\n")
  64. line = buffer[:index]
  65. buffer = buffer[index + 1:]
  66. log(line,
  67. prefix=log_prefix,
  68. file=build_log,
  69. color=Fore.WHITE + Style.BRIGHT \
  70. if re.match(r'^Step \d+\/\d+', line) else '')
  71. if buffer:
  72. log(buffer,
  73. prefix=log_prefix,
  74. file=build_log,
  75. color=Fore.WHITE + Style.BRIGHT \
  76. if re.match(r'^Step \d+\/\d+', buffer) else '')
  77. except Exception:
  78. tb = traceback.format_exc()
  79. log("Docker build failed; terminating",
  80. prefix=log_prefix,
  81. file=build_log,
  82. color=Fore.RED)
  83. log(tb, prefix=log_prefix, file=build_log)
  84. raise
  85. def build(benchmarker_config, test_names, build_log_dir=os.devnull):
  86. '''
  87. Builds the test docker containers
  88. '''
  89. tests = gather_tests(
  90. include=test_names, benchmarker_config=benchmarker_config)
  91. for test in tests:
  92. log_prefix = "%s: " % test.name
  93. # Build the test image
  94. test_docker_file = "%s.dockerfile" % test.name
  95. build_log_file = build_log_dir
  96. if build_log_dir is not os.devnull:
  97. build_log_file = os.path.join(
  98. build_log_dir,
  99. "%s.log" % test_docker_file.replace(".dockerfile", "").lower())
  100. try:
  101. __build(
  102. base_url=benchmarker_config.server_docker_host,
  103. build_log_file=build_log_file,
  104. log_prefix=log_prefix,
  105. path=test.directory,
  106. dockerfile=test_docker_file,
  107. tag="techempower/tfb.test.%s" % test_docker_file.replace(
  108. ".dockerfile", ""))
  109. except Exception:
  110. return 1
  111. return 0
  112. def run(benchmarker_config, test, run_log_dir):
  113. '''
  114. Run the given Docker container(s)
  115. '''
  116. client = docker.DockerClient(
  117. base_url=benchmarker_config.server_docker_host)
  118. log_prefix = "%s: " % test.name
  119. container = None
  120. try:
  121. def watch_container(docker_container, docker_file):
  122. with open(
  123. os.path.join(run_log_dir, "%s.log" % docker_file.replace(
  124. ".dockerfile", "").lower()), 'w') as run_log:
  125. for line in docker_container.logs(stream=True):
  126. log(line, prefix=log_prefix, file=run_log)
  127. extra_hosts = None
  128. name = "tfb-server"
  129. if benchmarker_config.network is None:
  130. extra_hosts = {
  131. socket.gethostname(): str(benchmarker_config.server_host),
  132. 'tfb-server': str(benchmarker_config.server_host),
  133. 'tfb-database': str(benchmarker_config.database_host)
  134. }
  135. name = None
  136. sysctl = {'net.core.somaxconn': 65535}
  137. ulimit = [{
  138. 'name': 'nofile',
  139. 'hard': 200000,
  140. 'soft': 200000
  141. }, {
  142. 'name': 'rtprio',
  143. 'hard': 99,
  144. 'soft': 99
  145. }]
  146. container = client.containers.run(
  147. "techempower/tfb.test.%s" % test.name,
  148. name=name,
  149. network=benchmarker_config.network,
  150. network_mode=benchmarker_config.network_mode,
  151. stderr=True,
  152. detach=True,
  153. init=True,
  154. extra_hosts=extra_hosts,
  155. privileged=True,
  156. ulimits=ulimit,
  157. sysctls=sysctl,
  158. remove=True,
  159. log_config={'type': None})
  160. watch_thread = Thread(
  161. target=watch_container,
  162. args=(
  163. container,
  164. "%s.dockerfile" % test.name,
  165. ))
  166. watch_thread.daemon = True
  167. watch_thread.start()
  168. except Exception:
  169. with open(
  170. os.path.join(run_log_dir, "%s.log" % test.name.lower()),
  171. 'w') as run_log:
  172. tb = traceback.format_exc()
  173. log("Running docker cointainer: %s.dockerfile failed" % test.name,
  174. prefix=log_prefix,
  175. file=run_log)
  176. log(tb, prefix=log_prefix, file=run_log)
  177. return container
  178. def stop(benchmarker_config=None,
  179. container=None,
  180. database_container=None,
  181. test=None):
  182. '''
  183. Attempts to stop the running test container.
  184. '''
  185. client = docker.DockerClient(
  186. base_url=benchmarker_config.server_docker_host)
  187. if container is None:
  188. for container in client.containers.list():
  189. if len(
  190. container.image.tags
  191. ) > 0 and 'techempower' in container.image.tags[0] and 'tfb:latest' not in container.image.tags[0]:
  192. container.stop()
  193. else:
  194. # Stop the running container
  195. try:
  196. container.stop()
  197. except Exception:
  198. # Suppress "No such container" errors
  199. pass
  200. database_client = docker.DockerClient(
  201. base_url=benchmarker_config.database_docker_host)
  202. # Stop the database container
  203. if database_container is None:
  204. for container in database_client.containers.list():
  205. if len(
  206. container.image.tags
  207. ) > 0 and 'techempower' in container.image.tags[0] and 'tfb:latest' not in container.image.tags[0]:
  208. container.stop()
  209. else:
  210. try:
  211. database_container.stop()
  212. except Exception:
  213. # Suppress "No such container" errors
  214. pass
  215. client.containers.prune()
  216. if benchmarker_config.server_docker_host != benchmarker_config.database_docker_host:
  217. database_client.containers.prune()
  218. def find(path, pattern):
  219. '''
  220. Finds and returns all the the files matching the given pattern recursively in
  221. the given path.
  222. '''
  223. for root, dirs, files in os.walk(path):
  224. for name in files:
  225. if fnmatch.fnmatch(name, pattern):
  226. return os.path.join(root, name)
  227. def start_database(benchmarker_config, database):
  228. '''
  229. Sets up a container for the given database and port, and starts said docker
  230. container.
  231. '''
  232. image_name = "techempower/%s:latest" % database
  233. log_prefix = image_name + ": "
  234. database_dir = os.path.join(benchmarker_config.fwroot, "toolset",
  235. "databases", database)
  236. docker_file = "%s.dockerfile" % database
  237. __build(
  238. base_url=benchmarker_config.database_docker_host,
  239. path=database_dir,
  240. dockerfile=docker_file,
  241. log_prefix=log_prefix,
  242. build_log_file=os.devnull,
  243. tag="techempower/%s" % database)
  244. client = docker.DockerClient(
  245. base_url=benchmarker_config.database_docker_host)
  246. sysctl = {'net.core.somaxconn': 65535, 'kernel.sem': "250 32000 256 512"}
  247. ulimit = [{'name': 'nofile', 'hard': 65535, 'soft': 65535}]
  248. container = client.containers.run(
  249. "techempower/%s" % database,
  250. name="tfb-database",
  251. network=benchmarker_config.network,
  252. network_mode=benchmarker_config.network_mode,
  253. detach=True,
  254. ulimits=ulimit,
  255. sysctls=sysctl,
  256. remove=True,
  257. log_config={'type': None})
  258. # Sleep until the database accepts connections
  259. slept = 0
  260. max_sleep = 60
  261. database_ready = False
  262. while not database_ready and slept < max_sleep:
  263. time.sleep(1)
  264. slept += 1
  265. database_ready = test_database(benchmarker_config, database)
  266. if not database_ready:
  267. log("Database was not ready after startup", prefix=log_prefix)
  268. return container
  269. def build_wrk(benchmarker_config):
  270. '''
  271. Builds the techempower/tfb.wrk container
  272. '''
  273. __build(
  274. base_url=benchmarker_config.client_docker_host,
  275. path=os.path.join(benchmarker_config.fwroot, "toolset", "wrk"),
  276. dockerfile="wrk.dockerfile",
  277. log_prefix="wrk: ",
  278. build_log_file=os.devnull,
  279. tag="techempower/tfb.wrk")
  280. def test_client_connection(benchmarker_config, url):
  281. '''
  282. Tests that the app server at the given url responds successfully to a
  283. request.
  284. '''
  285. client = docker.DockerClient(
  286. base_url=benchmarker_config.client_docker_host)
  287. try:
  288. client.containers.run(
  289. 'techempower/tfb.wrk',
  290. 'curl %s' % url,
  291. remove=True,
  292. log_config={'type': None},
  293. network=benchmarker_config.network,
  294. network_mode=benchmarker_config.network_mode)
  295. except:
  296. return False
  297. return True
  298. def server_container_exists(benchmarker_config, container_id_or_name):
  299. '''
  300. Returns True if the container still exists on the server.
  301. '''
  302. client = docker.DockerClient(
  303. base_url=benchmarker_config.server_docker_host)
  304. try:
  305. client.containers.get(container_id_or_name)
  306. return True
  307. except:
  308. return False
  309. def benchmark(benchmarker_config, script, variables, raw_file):
  310. '''
  311. Runs the given remote_script on the wrk container on the client machine.
  312. '''
  313. def watch_container(container, raw_file):
  314. with open(raw_file, 'w') as benchmark_file:
  315. for line in container.logs(stream=True):
  316. log(line, file=benchmark_file)
  317. client = docker.DockerClient(
  318. base_url=benchmarker_config.client_docker_host)
  319. sysctl = {'net.core.somaxconn': 65535}
  320. ulimit = [{'name': 'nofile', 'hard': 65535, 'soft': 65535}]
  321. watch_container(
  322. client.containers.run(
  323. "techempower/tfb.wrk",
  324. "/bin/bash /%s" % script,
  325. environment=variables,
  326. network=benchmarker_config.network,
  327. network_mode=benchmarker_config.network_mode,
  328. detach=True,
  329. stderr=True,
  330. ulimits=ulimit,
  331. sysctls=sysctl,
  332. remove=True,
  333. log_config={'type': None}), raw_file)