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