cProfile.py 6.2 KB

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