cProfile.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  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. else:
  97. raise TypeError('runcall expected at least 1 positional argument, '
  98. 'got %d' % (len(args)-1))
  99. self.enable()
  100. try:
  101. return func(*args, **kw)
  102. finally:
  103. self.disable()
  104. # ____________________________________________________________
  105. def label(code):
  106. if isinstance(code, str):
  107. return ('~', 0, code) # built-in functions ('~' sorts at the end)
  108. else:
  109. return (code.co_filename, code.co_firstlineno, code.co_name)
  110. # ____________________________________________________________
  111. def main():
  112. import os
  113. import sys
  114. import runpy
  115. import pstats
  116. from optparse import OptionParser
  117. usage = "cProfile.py [-o output_file_path] [-s sort] [-m module | scriptfile] [arg] ..."
  118. parser = OptionParser(usage=usage)
  119. parser.allow_interspersed_args = False
  120. parser.add_option('-o', '--outfile', dest="outfile",
  121. help="Save stats to <outfile>", default=None)
  122. parser.add_option('-s', '--sort', dest="sort",
  123. help="Sort order when printing to stdout, based on pstats.Stats class",
  124. default=-1,
  125. choices=sorted(pstats.Stats.sort_arg_dict_default))
  126. parser.add_option('-m', dest="module", action="store_true",
  127. help="Profile a library module", default=False)
  128. if not sys.argv[1:]:
  129. parser.print_usage()
  130. sys.exit(2)
  131. (options, args) = parser.parse_args()
  132. sys.argv[:] = args
  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 open(progname, 'rb') as fp:
  144. code = compile(fp.read(), progname, 'exec')
  145. globs = {
  146. '__file__': progname,
  147. '__name__': '__main__',
  148. '__package__': None,
  149. '__cached__': None,
  150. }
  151. runctx(code, globs, None, options.outfile, options.sort)
  152. else:
  153. parser.print_usage()
  154. return parser
  155. # When invoked as main program, invoke the profiler on a script
  156. if __name__ == '__main__':
  157. main()