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