stress.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. #!/usr/bin/env python
  2. from __future__ import absolute_import, print_function
  3. import os
  4. import platform
  5. import random
  6. import signal
  7. import sys
  8. from itertools import count
  9. from time import time, sleep
  10. from kombu import Exchange, Queue
  11. from kombu.utils.compat import OrderedDict
  12. from celery import Celery, group, VERSION_BANNER
  13. from celery.bin.base import Command, Option
  14. from celery.exceptions import TimeoutError, SoftTimeLimitExceeded
  15. from celery.five import range, values
  16. from celery.utils.debug import blockdetection
  17. from celery.utils.text import pluralize
  18. # Should be run with workers running using these options:
  19. #
  20. # 1) celery -A stress worker -c 1 --maxtasksperchild=1
  21. # 2) celery -A stress worker -c 8 --maxtasksperchild=1
  22. #
  23. # 3) celery -A stress worker -c 1
  24. # 4) celery -A stress worker -c 8
  25. #
  26. # 5) celery -A stress worker --autoscale=8,0
  27. #
  28. # 6) celery -A stress worker --time-limit=1
  29. #
  30. # 7) celery -A stress worker -c1 --maxtasksperchild=1 -- celery.acks_late=1
  31. BIG = 'x' * 2 ** 20 * 8
  32. SMALL = 'e' * 1024
  33. BANNER = """\
  34. Celery stress-suite v{version}
  35. {platform}
  36. [config]
  37. .> broker: {conninfo}
  38. [toc: {total} {TESTS} total]
  39. {toc}
  40. """
  41. CSTRESS_QUEUE = os.environ.get('CSTRESS_QUEUE_NAME', 'c.stress')
  42. CSTRESS_BACKEND = os.environ.get('CSTRESS_BACKEND', 'redis://')
  43. app = Celery(
  44. 'stress', broker='amqp://', backend=CSTRESS_BACKEND,
  45. set_as_current=False,
  46. )
  47. app.conf.update(
  48. CELERYD_PREFETCH_MULTIPLIER=10,
  49. CELERY_DEFAULT_QUEUE=CSTRESS_QUEUE,
  50. CELERY_QUEUES=(
  51. Queue(CSTRESS_QUEUE,
  52. exchange=Exchange(CSTRESS_QUEUE, durable=False),
  53. routing_key=CSTRESS_QUEUE,
  54. durable=False, auto_delete=True),
  55. ),
  56. )
  57. @app.task
  58. def _marker(s, sep='-'):
  59. print('{0} {1} {2}'.format(sep * 3, s, sep * 3))
  60. @app.task
  61. def add(x, y):
  62. return x + y
  63. @app.task
  64. def any_(*args, **kwargs):
  65. wait = kwargs.get('sleep')
  66. if wait:
  67. sleep(wait)
  68. @app.task
  69. def exiting(status=0):
  70. sys.exit(status)
  71. @app.task
  72. def kill(sig=signal.SIGKILL):
  73. os.kill(os.getpid(), sig)
  74. @app.task
  75. def sleeping(i):
  76. sleep(i)
  77. @app.task
  78. def sleeping_ignore_limits(i):
  79. try:
  80. sleep(i)
  81. except SoftTimeLimitExceeded:
  82. sleep(i)
  83. @app.task
  84. def segfault():
  85. import ctypes
  86. ctypes.memset(0, 0, 1)
  87. assert False, 'should not get here'
  88. def marker(s, sep='-'):
  89. print('{0}{1}'.format(sep, s))
  90. _marker.delay(s, sep)
  91. class Stress(Command):
  92. def run(self, *names, **options):
  93. try:
  94. return Suite(
  95. self.app,
  96. block_timeout=options.get('block_timeout'),
  97. ).run(names, **options)
  98. except KeyboardInterrupt:
  99. pass
  100. def get_options(self):
  101. return (
  102. Option('-i', '--iterations', type='int', default=50,
  103. help='Number of iterations for each test'),
  104. Option('-n', '--numtests', type='int', default=None,
  105. help='Number of tests to execute'),
  106. Option('-o', '--offset', type='int', default=0,
  107. help='Start at custom offset'),
  108. Option('--block-timeout', type='int', default=30 * 60),
  109. Option('-l', '--list', action='store_true', dest='list_all',
  110. help='List all tests'),
  111. Option('-r', '--repeat', type='float', default=0,
  112. help='Number of times to repeat the test suite'),
  113. )
  114. class Suite(object):
  115. def __init__(self, app, block_timeout=30 * 60):
  116. self.app = app
  117. self.connerrors = self.app.connection().recoverable_connection_errors
  118. self.block_timeout = block_timeout
  119. self.tests = OrderedDict(
  120. (fun.__name__, fun) for fun in [
  121. self.manyshort,
  122. self.termbysig,
  123. self.bigtasks,
  124. self.smalltasks,
  125. self.timelimits,
  126. self.timelimits_soft,
  127. self.revoketermfast,
  128. self.revoketermslow,
  129. self.alwayskilled,
  130. ]
  131. )
  132. def run(self, names=None, iterations=50, offset=0,
  133. numtests=None, list_all=False, repeat=0, **kw):
  134. tests = self.filtertests(names)[offset:numtests or None]
  135. if list_all:
  136. return print(self.testlist(tests))
  137. print(self.banner(tests))
  138. it = count() if repeat == float('Inf') else range(int(repeat) + 1)
  139. for i in it:
  140. marker(
  141. 'Stresstest suite start (repetition {0})'.format(i + 1),
  142. '+',
  143. )
  144. for j, test in enumerate(tests):
  145. self.runtest(test, iterations, j + 1)
  146. marker(
  147. 'Stresstest suite end (repetition {0})'.format(i + 1),
  148. '+',
  149. )
  150. def filtertests(self, names):
  151. try:
  152. return ([self.tests[n] for n in names] if names
  153. else list(values(self.tests)))
  154. except KeyError as exc:
  155. raise KeyError('Unknown test name: {0}'.format(exc))
  156. def testlist(self, tests):
  157. return ',\n'.join(
  158. '.> {0}) {1}'.format(i + 1, t.__name__)
  159. for i, t in enumerate(tests)
  160. )
  161. def banner(self, tests):
  162. app = self.app
  163. return BANNER.format(
  164. app='{0}:0x{1:x}'.format(app.main or '__main__', id(app)),
  165. version=VERSION_BANNER,
  166. conninfo=app.connection().as_uri(),
  167. platform=platform.platform(),
  168. toc=self.testlist(tests),
  169. TESTS=pluralize(len(tests), 'test'),
  170. total=len(tests),
  171. )
  172. def manyshort(self):
  173. self.join(group(add.s(i, i) for i in xrange(1000))(), propagate=True)
  174. def runtest(self, fun, n=50, index=0):
  175. with blockdetection(self.block_timeout):
  176. t = time()
  177. i = 0
  178. failed = False
  179. marker('{0}: {1}({2})'.format(index, fun.__name__, n))
  180. try:
  181. for i in range(n):
  182. print('{0} ({1})'.format(i, fun.__name__))
  183. fun()
  184. except Exception:
  185. failed = True
  186. raise
  187. finally:
  188. print('{0} {1} iterations in {2}s'.format(
  189. 'failed after' if failed else 'completed',
  190. i + 1, time() - t,
  191. ))
  192. def termbysig(self):
  193. self._evil_groupmember(kill)
  194. def termbysegfault(self):
  195. self._evil_groupmember(segfault)
  196. def timelimits(self):
  197. self._evil_groupmember(sleeping, 2, timeout=1)
  198. def timelimits_soft(self):
  199. self._evil_groupmember(sleeping_ignore_limits, 2,
  200. soft_timeout=1, timeout=1.1)
  201. def alwayskilled(self):
  202. g = group(kill.s() for _ in range(10))
  203. self.join(g(), timeout=10)
  204. def _evil_groupmember(self, evil_t, *eargs, **opts):
  205. g1 = group(add.s(2, 2).set(**opts), evil_t.s(*eargs).set(**opts),
  206. add.s(4, 4).set(**opts), add.s(8, 8).set(**opts))
  207. g2 = group(add.s(3, 3).set(**opts), add.s(5, 5).set(**opts),
  208. evil_t.s(*eargs).set(**opts), add.s(7, 7).set(**opts))
  209. self.join(g1(), timeout=10)
  210. self.join(g2(), timeout=10)
  211. def bigtasks(self, wait=None):
  212. self._revoketerm(wait, False, False, BIG)
  213. def smalltasks(self, wait=None):
  214. self._revoketerm(wait, False, False, SMALL)
  215. def revoketermfast(self, wait=None):
  216. self._revoketerm(wait, True, False, SMALL)
  217. def revoketermslow(self, wait=5):
  218. self._revoketerm(wait, True, True, BIG)
  219. def _revoketerm(self, wait=None, terminate=True,
  220. joindelay=True, data=BIG):
  221. g = group(any_.s(data, sleep=wait) for i in range(8))
  222. r = g()
  223. if terminate:
  224. if joindelay:
  225. sleep(random.choice(range(4)))
  226. r.revoke(terminate=True)
  227. self.join(r, timeout=100)
  228. def join(self, r, propagate=False, **kwargs):
  229. while 1:
  230. try:
  231. return r.get(propagate=propagate, **kwargs)
  232. except TimeoutError as exc:
  233. marker('join timed out: {0!r}'.format(exc), '!')
  234. except self.connerrors as exc:
  235. marker('join: connection lost: {0!r}'.format(exc), '!')
  236. if __name__ == '__main__':
  237. Stress(app=app).execute_from_commandline()