stack_tracer.py 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. """Stack tracer for multi-threaded applications.
  2. Usage:
  3. import stacktracer
  4. stacktracer.start_trace("trace.html",interval=5,auto=True) # Set auto flag to always update file!
  5. ....
  6. stacktracer.stop_trace()
  7. """
  8. import sys
  9. import traceback
  10. from pygments import highlight
  11. from pygments.lexers import PythonLexer
  12. from pygments.formatters import HtmlFormatter
  13. # Taken from http://bzimmer.ziclix.com/2008/12/17/python-thread-dumps/
  14. def stacktraces():
  15. code = []
  16. for threadId, stack in sys._current_frames().items():
  17. code.append("\n# ThreadID: %s" % threadId)
  18. for filename, lineno, name, line in traceback.extract_stack(stack):
  19. code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
  20. if line:
  21. code.append(" %s" % (line.strip()))
  22. return highlight("\n".join(code), PythonLexer(), HtmlFormatter(
  23. full=False,
  24. # style="native",
  25. noclasses=True,
  26. ))
  27. # This part was made by nagylzs
  28. import os
  29. import time
  30. import threading
  31. class TraceDumper(threading.Thread):
  32. """Dump stack traces into a given file periodically."""
  33. def __init__(self, fpath, interval, auto):
  34. """
  35. @param fpath: File path to output HTML (stack trace file)
  36. @param auto: Set flag (True) to update trace continuously.
  37. Clear flag (False) to update only if file not exists.
  38. (Then delete the file to force update.)
  39. @param interval: In seconds: how often to update the trace file.
  40. """
  41. assert (interval > 0.1)
  42. self.auto = auto
  43. self.interval = interval
  44. self.fpath = os.path.abspath(fpath)
  45. self.stop_requested = threading.Event()
  46. threading.Thread.__init__(self)
  47. def run(self):
  48. while not self.stop_requested.isSet():
  49. time.sleep(self.interval)
  50. if self.auto or not os.path.isfile(self.fpath):
  51. self.stacktraces()
  52. def stop(self):
  53. self.stop_requested.set()
  54. self.join()
  55. try:
  56. if os.path.isfile(self.fpath):
  57. os.unlink(self.fpath)
  58. except:
  59. pass
  60. def stacktraces(self):
  61. fout = open(self.fpath, "w+")
  62. try:
  63. fout.write(stacktraces())
  64. finally:
  65. fout.close()
  66. _tracer = None
  67. def trace_start(fpath, interval=5, auto=True):
  68. """Start tracing into the given file."""
  69. global _tracer
  70. if _tracer is None:
  71. _tracer = TraceDumper(fpath, interval, auto)
  72. _tracer.setDaemon(True)
  73. _tracer.start()
  74. else:
  75. raise Exception("Already tracing to %s" % _tracer.fpath)
  76. def trace_stop():
  77. """Stop tracing."""
  78. global _tracer
  79. if _tracer is None:
  80. raise Exception("Not tracing, cannot stop.")
  81. else:
  82. _tracer.stop()
  83. _tracer = None