cProfile.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  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(*args, **kw):
  88. if len(args) >= 2:
  89. self, func, *args = args
  90. elif not args:
  91. raise TypeError("descriptor 'runcall' of 'Profile' object "
  92. "needs an argument")
  93. elif 'func' in kw:
  94. func = kw.pop('func')
  95. self, *args = args
  96. import warnings
  97. warnings.warn("Passing 'func' as keyword argument is deprecated",
  98. DeprecationWarning, stacklevel=2)
  99. else:
  100. raise TypeError('runcall expected at least 1 positional argument, '
  101. 'got %d' % (len(args)-1))
  102. self.enable()
  103. try:
  104. return func(*args, **kw)
  105. finally:
  106. self.disable()
  107. runcall.__text_signature__ = '($self, func, /, *args, **kw)'
  108. def __enter__(self):
  109. self.enable()
  110. return self
  111. def __exit__(self, *exc_info):
  112. self.disable()
  113. # ____________________________________________________________
  114. def label(code):
  115. if isinstance(code, str):
  116. return ('~', 0, code) # built-in functions ('~' sorts at the end)
  117. else:
  118. return (code.co_filename, code.co_firstlineno, code.co_name)
  119. # ____________________________________________________________
  120. def main():
  121. import os
  122. import sys
  123. import runpy
  124. import pstats
  125. from optparse import OptionParser
  126. usage = "cProfile.py [-o output_file_path] [-s sort] [-m module | scriptfile] [arg] ..."
  127. parser = OptionParser(usage=usage)
  128. parser.allow_interspersed_args = False
  129. parser.add_option('-o', '--outfile', dest="outfile",
  130. help="Save stats to <outfile>", default=None)
  131. parser.add_option('-s', '--sort', dest="sort",
  132. help="Sort order when printing to stdout, based on pstats.Stats class",
  133. default=-1,
  134. choices=sorted(pstats.Stats.sort_arg_dict_default))
  135. parser.add_option('-m', dest="module", action="store_true",
  136. help="Profile a library module", default=False)
  137. if not sys.argv[1:]:
  138. parser.print_usage()
  139. sys.exit(2)
  140. (options, args) = parser.parse_args()
  141. sys.argv[:] = args
  142. # The script that we're profiling may chdir, so capture the absolute path
  143. # to the output file at startup.
  144. if options.outfile is not None:
  145. options.outfile = os.path.abspath(options.outfile)
  146. if len(args) > 0:
  147. if options.module:
  148. code = "run_module(modname, run_name='__main__')"
  149. globs = {
  150. 'run_module': runpy.run_module,
  151. 'modname': args[0]
  152. }
  153. else:
  154. progname = args[0]
  155. sys.path.insert(0, os.path.dirname(progname))
  156. with open(progname, 'rb') as fp:
  157. code = compile(fp.read(), progname, 'exec')
  158. globs = {
  159. '__file__': progname,
  160. '__name__': '__main__',
  161. '__package__': None,
  162. '__cached__': None,
  163. }
  164. try:
  165. runctx(code, globs, None, options.outfile, options.sort)
  166. except BrokenPipeError as exc:
  167. # Prevent "Exception ignored" during interpreter shutdown.
  168. sys.stdout = None
  169. sys.exit(exc.errno)
  170. else:
  171. parser.print_usage()
  172. return parser
  173. # When invoked as main program, invoke the profiler on a script
  174. if __name__ == '__main__':
  175. main()