cProfile.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. #! /usr/bin/env python3
  2. """Python interface for the 'lsprof' profiler.
  3. Compatible with the 'profile' module.
  4. """
  5. __all__ = ["run", "runctx", "Profile"]
  6. import _lsprof
  7. import profile as _pyprofile
  8. # ____________________________________________________________
  9. # Simple interface
  10. def run(statement, filename=None, sort=-1):
  11. return _pyprofile._Utils(Profile).run(statement, filename, sort)
  12. def runctx(statement, globals, locals, filename=None, sort=-1):
  13. return _pyprofile._Utils(Profile).runctx(statement, globals, locals,
  14. filename, sort)
  15. run.__doc__ = _pyprofile.run.__doc__
  16. runctx.__doc__ = _pyprofile.runctx.__doc__
  17. # ____________________________________________________________
  18. class Profile(_lsprof.Profiler):
  19. """Profile(timer=None, timeunit=None, subcalls=True, builtins=True)
  20. Builds a profiler object using the specified timer function.
  21. The default timer is a fast built-in one based on real time.
  22. For custom timer functions returning integers, timeunit can
  23. be a float specifying a scale (i.e. how long each integer unit
  24. is, in seconds).
  25. """
  26. # Most of the functionality is in the base class.
  27. # This subclass only adds convenient and backward-compatible methods.
  28. def print_stats(self, sort=-1):
  29. import pstats
  30. pstats.Stats(self).strip_dirs().sort_stats(sort).print_stats()
  31. def dump_stats(self, file):
  32. import marshal
  33. with open(file, 'wb') as f:
  34. self.create_stats()
  35. marshal.dump(self.stats, f)
  36. def create_stats(self):
  37. self.disable()
  38. self.snapshot_stats()
  39. def snapshot_stats(self):
  40. entries = self.getstats()
  41. self.stats = {}
  42. callersdicts = {}
  43. # call information
  44. for entry in entries:
  45. func = label(entry.code)
  46. nc = entry.callcount # ncalls column of pstats (before '/')
  47. cc = nc - entry.reccallcount # ncalls column of pstats (after '/')
  48. tt = entry.inlinetime # tottime column of pstats
  49. ct = entry.totaltime # cumtime column of pstats
  50. callers = {}
  51. callersdicts[id(entry.code)] = callers
  52. self.stats[func] = cc, nc, tt, ct, callers
  53. # subcall information
  54. for entry in entries:
  55. if entry.calls:
  56. func = label(entry.code)
  57. for subentry in entry.calls:
  58. try:
  59. callers = callersdicts[id(subentry.code)]
  60. except KeyError:
  61. continue
  62. nc = subentry.callcount
  63. cc = nc - subentry.reccallcount
  64. tt = subentry.inlinetime
  65. ct = subentry.totaltime
  66. if func in callers:
  67. prev = callers[func]
  68. nc += prev[0]
  69. cc += prev[1]
  70. tt += prev[2]
  71. ct += prev[3]
  72. callers[func] = nc, cc, tt, ct
  73. # The following two methods can be called by clients to use
  74. # a profiler to profile a statement, given as a string.
  75. def run(self, cmd):
  76. import __main__
  77. dict = __main__.__dict__
  78. return self.runctx(cmd, dict, dict)
  79. def runctx(self, cmd, globals, locals):
  80. self.enable()
  81. try:
  82. exec(cmd, globals, locals)
  83. finally:
  84. self.disable()
  85. return self
  86. # This method is more useful to profile a single function call.
  87. def runcall(self, func, /, *args, **kw):
  88. self.enable()
  89. try:
  90. return func(*args, **kw)
  91. finally:
  92. self.disable()
  93. def __enter__(self):
  94. self.enable()
  95. return self
  96. def __exit__(self, *exc_info):
  97. self.disable()
  98. # ____________________________________________________________
  99. def label(code):
  100. if isinstance(code, str):
  101. return ('~', 0, code) # built-in functions ('~' sorts at the end)
  102. else:
  103. return (code.co_filename, code.co_firstlineno, code.co_name)
  104. # ____________________________________________________________
  105. def main():
  106. import os
  107. import sys
  108. import runpy
  109. import pstats
  110. from optparse import OptionParser
  111. usage = "cProfile.py [-o output_file_path] [-s sort] [-m module | scriptfile] [arg] ..."
  112. parser = OptionParser(usage=usage)
  113. parser.allow_interspersed_args = False
  114. parser.add_option('-o', '--outfile', dest="outfile",
  115. help="Save stats to <outfile>", default=None)
  116. parser.add_option('-s', '--sort', dest="sort",
  117. help="Sort order when printing to stdout, based on pstats.Stats class",
  118. default=-1,
  119. choices=sorted(pstats.Stats.sort_arg_dict_default))
  120. parser.add_option('-m', dest="module", action="store_true",
  121. help="Profile a library module", default=False)
  122. if not sys.argv[1:]:
  123. parser.print_usage()
  124. sys.exit(2)
  125. (options, args) = parser.parse_args()
  126. sys.argv[:] = args
  127. # The script that we're profiling may chdir, so capture the absolute path
  128. # to the output file at startup.
  129. if options.outfile is not None:
  130. options.outfile = os.path.abspath(options.outfile)
  131. if len(args) > 0:
  132. if options.module:
  133. code = "run_module(modname, run_name='__main__')"
  134. globs = {
  135. 'run_module': runpy.run_module,
  136. 'modname': args[0]
  137. }
  138. else:
  139. progname = args[0]
  140. sys.path.insert(0, os.path.dirname(progname))
  141. with open(progname, 'rb') as fp:
  142. code = compile(fp.read(), progname, 'exec')
  143. globs = {
  144. '__file__': progname,
  145. '__name__': '__main__',
  146. '__package__': None,
  147. '__cached__': None,
  148. }
  149. try:
  150. runctx(code, globs, None, options.outfile, options.sort)
  151. except BrokenPipeError as exc:
  152. # Prevent "Exception ignored" during interpreter shutdown.
  153. sys.stdout = None
  154. sys.exit(exc.errno)
  155. else:
  156. parser.print_usage()
  157. return parser
  158. # When invoked as main program, invoke the profiler on a script
  159. if __name__ == '__main__':
  160. main()