Skip to content
This repository was archived by the owner on Jun 9, 2020. It is now read-only.

Commit eb54217

Browse files
committed
Initial commit.
0 parents  commit eb54217

File tree

10 files changed

+1381
-0
lines changed

10 files changed

+1381
-0
lines changed

_line_profiler.pyx

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
from python25 cimport PyFrameObject, PyObject, PyStringObject
2+
3+
from cProfile import label
4+
5+
cdef extern from "frameobject.h":
6+
ctypedef int (*Py_tracefunc)(object self, PyFrameObject *py_frame, int what, object arg)
7+
8+
cdef extern from "Python.h":
9+
ctypedef long long PY_LONG_LONG
10+
cdef bint PyCFunction_Check(object obj)
11+
12+
cdef void PyEval_SetProfile(Py_tracefunc func, object arg)
13+
cdef void PyEval_SetTrace(Py_tracefunc func, object arg)
14+
15+
ctypedef object (*PyCFunction)(object self, object args)
16+
17+
ctypedef struct PyMethodDef:
18+
char *ml_name
19+
PyCFunction ml_meth
20+
int ml_flags
21+
char *ml_doc
22+
23+
ctypedef struct PyCFunctionObject:
24+
PyMethodDef *m_ml
25+
PyObject *m_self
26+
PyObject *m_module
27+
28+
# They're actually #defines, but whatever.
29+
cdef int PyTrace_CALL
30+
cdef int PyTrace_EXCEPTION
31+
cdef int PyTrace_LINE
32+
cdef int PyTrace_RETURN
33+
cdef int PyTrace_C_CALL
34+
cdef int PyTrace_C_EXCEPTION
35+
cdef int PyTrace_C_RETURN
36+
37+
cdef extern from "timers.h":
38+
PY_LONG_LONG hpTimer()
39+
double hpTimerUnit()
40+
41+
42+
cdef class LineTiming:
43+
""" The timing for a single line.
44+
"""
45+
cdef public object code
46+
cdef public int lineno
47+
cdef public PY_LONG_LONG total_time
48+
cdef public long nhits
49+
50+
def __init__(self, object code, int lineno):
51+
self.code = code
52+
self.lineno = lineno
53+
self.total_time = 0
54+
self.nhits = 0
55+
56+
def hit(self, PY_LONG_LONG dt):
57+
""" Record a line timing.
58+
"""
59+
self.nhits += 1
60+
self.total_time += dt
61+
62+
def astuple(self):
63+
""" Convert to a tuple of (lineno, nhits, total_time).
64+
"""
65+
return (self.lineno, self.nhits, <long>self.total_time)
66+
67+
def __repr__(self):
68+
return '<LineTiming for %r\n lineno: %r\n nhits: %r\n total_time: %r>' % (self.code, self.lineno, self.nhits, <long>self.total_time)
69+
70+
71+
cdef class LineProfiler:
72+
""" Time the execution of lines of Python code.
73+
"""
74+
cdef public object functions
75+
cdef public object code_map
76+
cdef public object last_time
77+
cdef public double timer_unit
78+
cdef public long enable_count
79+
80+
def __init__(self, *functions):
81+
self.functions = []
82+
self.code_map = {}
83+
self.last_time = {}
84+
self.timer_unit = hpTimerUnit()
85+
self.enable_count = 0
86+
for func in functions:
87+
self.add_function(func)
88+
89+
def add_function(self, func):
90+
code = func.func_code
91+
if code not in self.code_map:
92+
self.code_map[code] = {}
93+
self.functions.append(func)
94+
95+
def enable_by_count(self):
96+
""" Enable the profiler if it hasn't been enabled before.
97+
"""
98+
if self.enable_count == 0:
99+
self.enable()
100+
self.enable_count += 1
101+
102+
def disable_by_count(self):
103+
""" Disable the profiler if the number of disable requests matches the
104+
number of enable requests.
105+
"""
106+
if self.enable_count > 0:
107+
self.enable_count -= 1
108+
if self.enable_count == 0:
109+
self.disable()
110+
111+
def __enter__(self):
112+
self.enable_by_count()
113+
114+
def __exit__(self, exc_type, exc_val, exc_tb):
115+
self.disable_by_count()
116+
117+
def enable(self):
118+
PyEval_SetTrace(python_trace_callback, self)
119+
120+
def disable(self):
121+
self.last_time = {}
122+
PyEval_SetTrace(NULL, <object>NULL)
123+
124+
def get_stats(self):
125+
""" Return a serializable dictionary of the profiling data along with
126+
the timer unit.
127+
128+
Returns
129+
-------
130+
stats : dict
131+
Mapping from (filename, first_lineno, function_name) of the profiled
132+
function to a list of (lineno, nhits, total_time) tuples for each
133+
profiled line. total_time is an integer in the native units of the
134+
timer.
135+
timer_unit : float
136+
The number of seconds per timer unit.
137+
"""
138+
stats = {}
139+
for code in self.code_map:
140+
entries = self.code_map[code].values()
141+
key = label(code)
142+
stats[key] = [e.astuple() for e in entries]
143+
stats[key].sort()
144+
return stats, self.timer_unit
145+
146+
147+
cdef class LastTime:
148+
""" Record the last callback call for a given line.
149+
"""
150+
cdef int f_lineno
151+
cdef PY_LONG_LONG time
152+
153+
def __cinit__(self, int f_lineno, PY_LONG_LONG time):
154+
self.f_lineno = f_lineno
155+
self.time = time
156+
157+
158+
cdef int python_trace_callback(object self, PyFrameObject *py_frame, int what,
159+
object arg):
160+
""" The PyEval_SetTrace() callback.
161+
"""
162+
cdef object code, line_entries, key
163+
cdef LineTiming entry
164+
cdef LastTime old
165+
cdef PY_LONG_LONG time
166+
167+
if what == PyTrace_LINE or what == PyTrace_RETURN:
168+
code = <object>py_frame.f_code
169+
if code in self.code_map:
170+
time = hpTimer()
171+
if code in self.last_time:
172+
old = self.last_time[code]
173+
line_entries = self.code_map[code]
174+
key = old.f_lineno
175+
if key not in line_entries:
176+
entry = LineTiming(code, old.f_lineno)
177+
line_entries[key] = entry
178+
else:
179+
entry = line_entries[key]
180+
entry.hit(time - old.time)
181+
if what == PyTrace_LINE:
182+
# Get the time again. This way, we don't record much time wasted
183+
# in this function.
184+
self.last_time[code] = LastTime(py_frame.f_lineno, hpTimer())
185+
else:
186+
# We are returning from a function, not executing a line. Delete
187+
# the last_time record.
188+
del self.last_time[code]
189+
190+
return 0
191+
192+

line_profiler.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from cProfile import label
2+
import marshal
3+
4+
from _line_profiler import LineProfiler as CLineProfiler
5+
6+
7+
class LineProfiler(CLineProfiler):
8+
""" A subclass of the C version solely to provide a decorator since Cython
9+
does not have closures.
10+
"""
11+
12+
def __call__(self, func):
13+
""" Decorate a function to start the profiler on function entry and stop
14+
it on function exit.
15+
"""
16+
def f(*args, **kwds):
17+
self.add_function(func)
18+
self.enable_by_count()
19+
try:
20+
result = func(*args, **kwds)
21+
finally:
22+
self.disable_by_count()
23+
return result
24+
f.__name__ = func.__name__
25+
f.__doc__ = func.__doc__
26+
f.__dict__.update(func.__dict__)
27+
return f
28+
29+
def dump_stats(self, filename):
30+
""" Dump a representation of the data to a file as a marshalled
31+
dictionary from `get_stats()`.
32+
"""
33+
stats = self.get_stats()
34+
f = open(filename, 'wb')
35+
try:
36+
marshal.dump(stats, f)
37+
finally:
38+
f.close()
39+

linestone.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import pystone
2+
3+
from line_profiler import LineProfiler
4+
5+
6+
lp = LineProfiler(pystone.Proc0)
7+
lp.enable()
8+
pystone.pystones()
9+
lp.disable()
10+
code = lp.code_map.keys()[0]
11+

lsprof.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
#!/usr/bin/env python
2+
# -*- coding: UTF-8 -*-
3+
""" Script to conveniently run the profiler on code in a variety of
4+
circumstances.
5+
"""
6+
7+
import cProfile
8+
import optparse
9+
import os
10+
import sys
11+
12+
def find_script(script_name):
13+
""" Find the script.
14+
15+
If the input is not a file, then $PATH will be searched.
16+
"""
17+
if os.path.isfile(script_name):
18+
return script_name
19+
path = os.getenv('PATH', os.defpath).split(os.pathsep)
20+
for dir in path:
21+
if dir == '':
22+
continue
23+
fn = os.path.join(dir, script_name)
24+
if os.path.isfile(fn):
25+
return fn
26+
27+
print >>sys.stderr, 'Could not find script %s' % script_name
28+
raise SystemExit(1)
29+
30+
class ContextualProfile(cProfile.Profile):
31+
""" A subclass of cProfile.Profile that adds a context manager for Python
32+
2.5 with: statements and a decorator.
33+
"""
34+
35+
def __init__(self, *args, **kwds):
36+
super(ContextualProfile, self).__init__(*args, **kwds)
37+
self.enable_count = 0
38+
39+
def enable_by_count(self, subcalls=True, builtins=True):
40+
""" Enable the profiler if it hasn't been enabled before.
41+
"""
42+
if self.enable_count == 0:
43+
self.enable(subcalls=subcalls, builtins=builtins)
44+
self.enable_count += 1
45+
46+
def disable_by_count(self):
47+
""" Disable the profiler if the number of disable requests matches the
48+
number of enable requests.
49+
"""
50+
if self.enable_count > 0:
51+
self.enable_count -= 1
52+
if self.enable_count == 0:
53+
self.disable()
54+
55+
def __call__(self, func):
56+
""" Decorate a function to start the profiler on function entry and stop
57+
it on function exit.
58+
"""
59+
def f(*args, **kwds):
60+
self.enable_by_count()
61+
try:
62+
result = func(*args, **kwds)
63+
finally:
64+
self.disable_by_count()
65+
return result
66+
f.__name__ = func.__name__
67+
f.__doc__ = func.__doc__
68+
f.__dict__.update(func.__dict__)
69+
return f
70+
71+
def __enter__(self):
72+
self.enable_by_count()
73+
74+
def __exit__(self, exc_type, exc_val, exc_tb):
75+
self.disable_by_count()
76+
77+
78+
def main(args):
79+
usage = "%s [-s setupfile] [-o output_file_path] scriptfile [arg] ..."
80+
parser = optparse.OptionParser(usage=usage % sys.argv[0])
81+
parser.allow_interspersed_args = False
82+
parser.add_option('-l', '--line-by-line', action='store_true',
83+
help="Use the line-by-line profiler from the line_profiler module "
84+
"instead of cProfile. Implies --builtin.")
85+
parser.add_option('-b', '--builtin', action='store_true',
86+
help="Put 'profile' in the builtins. Use 'profile.enable()' and "
87+
"'profile.disable()' in your code to turn it on and off, or "
88+
"'@profile' to decorate a single function, or 'with profile:' "
89+
"to profile a single section of code.")
90+
parser.add_option('-o', '--outfile', default=None,
91+
help="Save stats to <outfile>")
92+
parser.add_option('-s', '--setup', default=None,
93+
help="Code to execute before the code to profile")
94+
95+
if not sys.argv[1:]:
96+
parser.print_usage()
97+
sys.exit(2)
98+
99+
options, args = parser.parse_args()
100+
101+
if not options.outfile:
102+
options.outfile = '%s.prof' % os.path.basename(args[0])
103+
104+
sys.argv[:] = args
105+
if options.setup is not None:
106+
# Run some setup code outside of the profiler. This is good for large
107+
# imports.
108+
setup_file = find_script(options.setup)
109+
__file__ = setup_file
110+
__name__ = '__main__'
111+
# Make sure the script's directory is on sys.path instead of just
112+
# lsprof.py's.
113+
sys.path.insert(0, os.path.dirname(setup_file))
114+
execfile(setup_file)
115+
116+
script_file = find_script(sys.argv[0])
117+
__file__ = script_file
118+
__name__ = '__main__'
119+
# Make sure the script's directory is on sys.path instead of just
120+
# lsprof.py's.
121+
sys.path.insert(0, os.path.dirname(script_file))
122+
123+
if options.line_by_line:
124+
import line_profiler
125+
prof = line_profiler.LineProfiler()
126+
options.builtin = True
127+
else:
128+
prof = ContextualProfile()
129+
if options.builtin:
130+
import __builtin__
131+
__builtin__.__dict__['profile'] = prof
132+
133+
try:
134+
try:
135+
if options.builtin:
136+
execfile(script_file)
137+
else:
138+
prof.run('execfile(%r)' % (script_file,))
139+
except (KeyboardInterrupt, SystemExit):
140+
pass
141+
finally:
142+
prof.dump_stats(options.outfile)
143+
print 'Wrote profile results to %s' % options.outfile
144+
145+
if __name__ == '__main__':
146+
sys.exit(main(sys.argv))
147+

0 commit comments

Comments
 (0)