debug.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. # -*- coding: utf-8 -*-
  2. """
  3. celery.utils.debug
  4. ~~~~~~~~~~~~~~~~~~
  5. Utilities for debugging memory usage.
  6. """
  7. from __future__ import absolute_import, print_function
  8. import os
  9. from contextlib import contextmanager
  10. from functools import partial
  11. from celery.five import range
  12. from celery.platforms import signals
  13. try:
  14. from psutil import Process
  15. except ImportError:
  16. Process = None # noqa
  17. __all__ = [
  18. 'blockdetection', 'sample_mem', 'memdump', 'sample',
  19. 'humanbytes', 'mem_rss', 'ps',
  20. ]
  21. UNITS = (
  22. (2 ** 40.0, 'TB'),
  23. (2 ** 30.0, 'GB'),
  24. (2 ** 20.0, 'MB'),
  25. (2 ** 10.0, 'kB'),
  26. (0.0, '{0!d}b'),
  27. )
  28. _process = None
  29. _mem_sample = []
  30. def _on_blocking(signum, frame):
  31. import inspect
  32. raise RuntimeError(
  33. 'Blocking detection timed-out at: %s' % (
  34. inspect.getframeinfo(frame), ))
  35. @contextmanager
  36. def blockdetection(timeout):
  37. """A timeout context using ``SIGALRM`` that can be used to detect blocking
  38. functions."""
  39. if not timeout:
  40. yield
  41. else:
  42. old_handler = signals['ALRM']
  43. old_handler = None if old_handler == _on_blocking else old_handler
  44. signals['ALRM'] = _on_blocking
  45. try:
  46. yield signals.arm_alarm(timeout)
  47. finally:
  48. if old_handler:
  49. signals['ALRM'] = old_handler
  50. signals.reset_alarm()
  51. def sample_mem():
  52. """Sample RSS memory usage.
  53. Statistics can then be output by calling :func:`memdump`.
  54. """
  55. current_rss = mem_rss()
  56. _mem_sample.append(current_rss)
  57. return current_rss
  58. def _memdump(samples=10):
  59. S = _mem_sample
  60. prev = list(S) if len(S) <= samples else sample(S, samples)
  61. _mem_sample[:] = []
  62. import gc
  63. gc.collect()
  64. after_collect = mem_rss()
  65. return prev, after_collect
  66. def memdump(samples=10, file=None):
  67. """Dump memory statistics.
  68. Will print a sample of all RSS memory samples added by
  69. calling :func:`sample_mem`, and in addition print
  70. used RSS memory after :func:`gc.collect`.
  71. """
  72. say = partial(print, file=file)
  73. if ps() is None:
  74. say('- rss: (psutil not installed).')
  75. return
  76. prev, after_collect = _memdump(samples)
  77. if prev:
  78. say('- rss (sample):')
  79. for mem in prev:
  80. say('- > {0},'.format(mem))
  81. say('- rss (end): {0}.'.format(after_collect))
  82. def sample(x, n, k=0):
  83. """Given a list `x` a sample of length ``n`` of that list is returned.
  84. E.g. if `n` is 10, and `x` has 100 items, a list of every 10th
  85. item is returned.
  86. ``k`` can be used as offset.
  87. """
  88. j = len(x) // n
  89. for _ in range(n):
  90. try:
  91. yield x[k]
  92. except IndexError:
  93. break
  94. k += j
  95. def hfloat(f, p=5):
  96. """Convert float to value suitable for humans.
  97. :keyword p: Float precision.
  98. """
  99. i = int(f)
  100. return i if i == f else '{0:.{p}}'.format(f, p=p)
  101. def humanbytes(s):
  102. """Convert bytes to human-readable form (e.g. kB, MB)."""
  103. return next(
  104. '{0}{1}'.format(hfloat(s / div if div else s), unit)
  105. for div, unit in UNITS if s >= div
  106. )
  107. def mem_rss():
  108. """Return RSS memory usage as a humanized string."""
  109. p = ps()
  110. if p is not None:
  111. return humanbytes(p.get_memory_info().rss)
  112. def ps():
  113. """Return the global :class:`psutil.Process` instance,
  114. or :const:`None` if :mod:`psutil` is not installed."""
  115. global _process
  116. if _process is None and Process is not None:
  117. _process = Process(os.getpid())
  118. return _process