1#! /usr/bin/env python
2
3"""Python interface for the 'lsprof' profiler.
4   Compatible with the 'profile' module.
5"""
6
7__all__ = ["run", "runctx", "help", "Profile"]
8
9import _lsprof
10
11# ____________________________________________________________
12# Simple interface
13
14def run(statement, filename=None, sort=-1):
15    """Run statement under profiler optionally saving results in filename
16
17    This function takes a single argument that can be passed to the
18    "exec" statement, and an optional file name.  In all cases this
19    routine attempts to "exec" its first argument and gather profiling
20    statistics from the execution. If no file name is present, then this
21    function automatically prints a simple profiling report, sorted by the
22    standard name string (file/line/function-name) that is presented in
23    each line.
24    """
25    prof = Profile()
26    result = None
27    try:
28        try:
29            prof = prof.run(statement)
30        except SystemExit:
31            pass
32    finally:
33        if filename is not None:
34            prof.dump_stats(filename)
35        else:
36            result = prof.print_stats(sort)
37    return result
38
39def runctx(statement, globals, locals, filename=None, sort=-1):
40    """Run statement under profiler, supplying your own globals and locals,
41    optionally saving results in filename.
42
43    statement and filename have the same semantics as profile.run
44    """
45    prof = Profile()
46    result = None
47    try:
48        try:
49            prof = prof.runctx(statement, globals, locals)
50        except SystemExit:
51            pass
52    finally:
53        if filename is not None:
54            prof.dump_stats(filename)
55        else:
56            result = prof.print_stats(sort)
57    return result
58
59# Backwards compatibility.
60def help():
61    print "Documentation for the profile/cProfile modules can be found "
62    print "in the Python Library Reference, section 'The Python Profiler'."
63
64# ____________________________________________________________
65
66class Profile(_lsprof.Profiler):
67    """Profile(custom_timer=None, time_unit=None, subcalls=True, builtins=True)
68
69    Builds a profiler object using the specified timer function.
70    The default timer is a fast built-in one based on real time.
71    For custom timer functions returning integers, time_unit can
72    be a float specifying a scale (i.e. how long each integer unit
73    is, in seconds).
74    """
75
76    # Most of the functionality is in the base class.
77    # This subclass only adds convenient and backward-compatible methods.
78
79    def print_stats(self, sort=-1):
80        import pstats
81        pstats.Stats(self).strip_dirs().sort_stats(sort).print_stats()
82
83    def dump_stats(self, file):
84        import marshal
85        f = open(file, 'wb')
86        self.create_stats()
87        marshal.dump(self.stats, f)
88        f.close()
89
90    def create_stats(self):
91        self.disable()
92        self.snapshot_stats()
93
94    def snapshot_stats(self):
95        entries = self.getstats()
96        self.stats = {}
97        callersdicts = {}
98        # call information
99        for entry in entries:
100            func = label(entry.code)
101            nc = entry.callcount         # ncalls column of pstats (before '/')
102            cc = nc - entry.reccallcount # ncalls column of pstats (after '/')
103            tt = entry.inlinetime        # tottime column of pstats
104            ct = entry.totaltime         # cumtime column of pstats
105            callers = {}
106            callersdicts[id(entry.code)] = callers
107            self.stats[func] = cc, nc, tt, ct, callers
108        # subcall information
109        for entry in entries:
110            if entry.calls:
111                func = label(entry.code)
112                for subentry in entry.calls:
113                    try:
114                        callers = callersdicts[id(subentry.code)]
115                    except KeyError:
116                        continue
117                    nc = subentry.callcount
118                    cc = nc - subentry.reccallcount
119                    tt = subentry.inlinetime
120                    ct = subentry.totaltime
121                    if func in callers:
122                        prev = callers[func]
123                        nc += prev[0]
124                        cc += prev[1]
125                        tt += prev[2]
126                        ct += prev[3]
127                    callers[func] = nc, cc, tt, ct
128
129    # The following two methods can be called by clients to use
130    # a profiler to profile a statement, given as a string.
131
132    def run(self, cmd):
133        import __main__
134        dict = __main__.__dict__
135        return self.runctx(cmd, dict, dict)
136
137    def runctx(self, cmd, globals, locals):
138        self.enable()
139        try:
140            exec cmd in globals, locals
141        finally:
142            self.disable()
143        return self
144
145    # This method is more useful to profile a single function call.
146    def runcall(self, func, *args, **kw):
147        self.enable()
148        try:
149            return func(*args, **kw)
150        finally:
151            self.disable()
152
153# ____________________________________________________________
154
155def label(code):
156    if isinstance(code, str):
157        return ('~', 0, code)    # built-in functions ('~' sorts at the end)
158    else:
159        return (code.co_filename, code.co_firstlineno, code.co_name)
160
161# ____________________________________________________________
162
163def main():
164    import os, sys
165    from optparse import OptionParser
166    usage = "cProfile.py [-o output_file_path] [-s sort] scriptfile [arg] ..."
167    parser = OptionParser(usage=usage)
168    parser.allow_interspersed_args = False
169    parser.add_option('-o', '--outfile', dest="outfile",
170        help="Save stats to <outfile>", default=None)
171    parser.add_option('-s', '--sort', dest="sort",
172        help="Sort order when printing to stdout, based on pstats.Stats class",
173        default=-1)
174
175    if not sys.argv[1:]:
176        parser.print_usage()
177        sys.exit(2)
178
179    (options, args) = parser.parse_args()
180    sys.argv[:] = args
181
182    if len(args) > 0:
183        progname = args[0]
184        sys.path.insert(0, os.path.dirname(progname))
185        with open(progname, 'rb') as fp:
186            code = compile(fp.read(), progname, 'exec')
187        globs = {
188            '__file__': progname,
189            '__name__': '__main__',
190            '__package__': None,
191        }
192        runctx(code, globs, None, options.outfile, options.sort)
193    else:
194        parser.print_usage()
195    return parser
196
197# When invoked as main program, invoke the profiler on a script
198if __name__ == '__main__':
199    main()
200