cProfile.py 6.4 KB

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