Permalink
Browse files

Initial commit.

  • Loading branch information...
rkern committed Sep 16, 2008
0 parents commit eb54217e4bc0bb8e64b09e7578465c8b5a5f5215
Showing with 1,381 additions and 0 deletions.
  1. +192 −0 _line_profiler.pyx
  2. +39 −0 line_profiler.py
  3. +11 −0 linestone.py
  4. +147 −0 lsprof.py
  5. +12 −0 mystone.py
  6. +267 −0 pystone.py
  7. +625 −0 python25.pxd
  8. +16 −0 setup.py
  9. +68 −0 timers.c
  10. +4 −0 timers.h
@@ -0,0 +1,192 @@
+from python25 cimport PyFrameObject, PyObject, PyStringObject
+
+from cProfile import label
+
+cdef extern from "frameobject.h":
+ ctypedef int (*Py_tracefunc)(object self, PyFrameObject *py_frame, int what, object arg)
+
+cdef extern from "Python.h":
+ ctypedef long long PY_LONG_LONG
+ cdef bint PyCFunction_Check(object obj)
+
+ cdef void PyEval_SetProfile(Py_tracefunc func, object arg)
+ cdef void PyEval_SetTrace(Py_tracefunc func, object arg)
+
+ ctypedef object (*PyCFunction)(object self, object args)
+
+ ctypedef struct PyMethodDef:
+ char *ml_name
+ PyCFunction ml_meth
+ int ml_flags
+ char *ml_doc
+
+ ctypedef struct PyCFunctionObject:
+ PyMethodDef *m_ml
+ PyObject *m_self
+ PyObject *m_module
+
+ # They're actually #defines, but whatever.
+ cdef int PyTrace_CALL
+ cdef int PyTrace_EXCEPTION
+ cdef int PyTrace_LINE
+ cdef int PyTrace_RETURN
+ cdef int PyTrace_C_CALL
+ cdef int PyTrace_C_EXCEPTION
+ cdef int PyTrace_C_RETURN
+
+cdef extern from "timers.h":
+ PY_LONG_LONG hpTimer()
+ double hpTimerUnit()
+
+
+cdef class LineTiming:
+ """ The timing for a single line.
+ """
+ cdef public object code
+ cdef public int lineno
+ cdef public PY_LONG_LONG total_time
+ cdef public long nhits
+
+ def __init__(self, object code, int lineno):
+ self.code = code
+ self.lineno = lineno
+ self.total_time = 0
+ self.nhits = 0
+
+ def hit(self, PY_LONG_LONG dt):
+ """ Record a line timing.
+ """
+ self.nhits += 1
+ self.total_time += dt
+
+ def astuple(self):
+ """ Convert to a tuple of (lineno, nhits, total_time).
+ """
+ return (self.lineno, self.nhits, <long>self.total_time)
+
+ def __repr__(self):
+ return '<LineTiming for %r\n lineno: %r\n nhits: %r\n total_time: %r>' % (self.code, self.lineno, self.nhits, <long>self.total_time)
+
+
+cdef class LineProfiler:
+ """ Time the execution of lines of Python code.
+ """
+ cdef public object functions
+ cdef public object code_map
+ cdef public object last_time
+ cdef public double timer_unit
+ cdef public long enable_count
+
+ def __init__(self, *functions):
+ self.functions = []
+ self.code_map = {}
+ self.last_time = {}
+ self.timer_unit = hpTimerUnit()
+ self.enable_count = 0
+ for func in functions:
+ self.add_function(func)
+
+ def add_function(self, func):
+ code = func.func_code
+ if code not in self.code_map:
+ self.code_map[code] = {}
+ self.functions.append(func)
+
+ def enable_by_count(self):
+ """ Enable the profiler if it hasn't been enabled before.
+ """
+ if self.enable_count == 0:
+ self.enable()
+ self.enable_count += 1
+
+ def disable_by_count(self):
+ """ Disable the profiler if the number of disable requests matches the
+ number of enable requests.
+ """
+ if self.enable_count > 0:
+ self.enable_count -= 1
+ if self.enable_count == 0:
+ self.disable()
+
+ def __enter__(self):
+ self.enable_by_count()
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self.disable_by_count()
+
+ def enable(self):
+ PyEval_SetTrace(python_trace_callback, self)
+
+ def disable(self):
+ self.last_time = {}
+ PyEval_SetTrace(NULL, <object>NULL)
+
+ def get_stats(self):
+ """ Return a serializable dictionary of the profiling data along with
+ the timer unit.
+
+ Returns
+ -------
+ stats : dict
+ Mapping from (filename, first_lineno, function_name) of the profiled
+ function to a list of (lineno, nhits, total_time) tuples for each
+ profiled line. total_time is an integer in the native units of the
+ timer.
+ timer_unit : float
+ The number of seconds per timer unit.
+ """
+ stats = {}
+ for code in self.code_map:
+ entries = self.code_map[code].values()
+ key = label(code)
+ stats[key] = [e.astuple() for e in entries]
+ stats[key].sort()
+ return stats, self.timer_unit
+
+
+cdef class LastTime:
+ """ Record the last callback call for a given line.
+ """
+ cdef int f_lineno
+ cdef PY_LONG_LONG time
+
+ def __cinit__(self, int f_lineno, PY_LONG_LONG time):
+ self.f_lineno = f_lineno
+ self.time = time
+
+
+cdef int python_trace_callback(object self, PyFrameObject *py_frame, int what,
+ object arg):
+ """ The PyEval_SetTrace() callback.
+ """
+ cdef object code, line_entries, key
+ cdef LineTiming entry
+ cdef LastTime old
+ cdef PY_LONG_LONG time
+
+ if what == PyTrace_LINE or what == PyTrace_RETURN:
+ code = <object>py_frame.f_code
+ if code in self.code_map:
+ time = hpTimer()
+ if code in self.last_time:
+ old = self.last_time[code]
+ line_entries = self.code_map[code]
+ key = old.f_lineno
+ if key not in line_entries:
+ entry = LineTiming(code, old.f_lineno)
+ line_entries[key] = entry
+ else:
+ entry = line_entries[key]
+ entry.hit(time - old.time)
+ if what == PyTrace_LINE:
+ # Get the time again. This way, we don't record much time wasted
+ # in this function.
+ self.last_time[code] = LastTime(py_frame.f_lineno, hpTimer())
+ else:
+ # We are returning from a function, not executing a line. Delete
+ # the last_time record.
+ del self.last_time[code]
+
+ return 0
+
+
@@ -0,0 +1,39 @@
+from cProfile import label
+import marshal
+
+from _line_profiler import LineProfiler as CLineProfiler
+
+
+class LineProfiler(CLineProfiler):
+ """ A subclass of the C version solely to provide a decorator since Cython
+ does not have closures.
+ """
+
+ def __call__(self, func):
+ """ Decorate a function to start the profiler on function entry and stop
+ it on function exit.
+ """
+ def f(*args, **kwds):
+ self.add_function(func)
+ self.enable_by_count()
+ try:
+ result = func(*args, **kwds)
+ finally:
+ self.disable_by_count()
+ return result
+ f.__name__ = func.__name__
+ f.__doc__ = func.__doc__
+ f.__dict__.update(func.__dict__)
+ return f
+
+ def dump_stats(self, filename):
+ """ Dump a representation of the data to a file as a marshalled
+ dictionary from `get_stats()`.
+ """
+ stats = self.get_stats()
+ f = open(filename, 'wb')
+ try:
+ marshal.dump(stats, f)
+ finally:
+ f.close()
+
@@ -0,0 +1,11 @@
+import pystone
+
+from line_profiler import LineProfiler
+
+
+lp = LineProfiler(pystone.Proc0)
+lp.enable()
+pystone.pystones()
+lp.disable()
+code = lp.code_map.keys()[0]
+
147 lsprof.py
@@ -0,0 +1,147 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+""" Script to conveniently run the profiler on code in a variety of
+circumstances.
+"""
+
+import cProfile
+import optparse
+import os
+import sys
+
+def find_script(script_name):
+ """ Find the script.
+
+ If the input is not a file, then $PATH will be searched.
+ """
+ if os.path.isfile(script_name):
+ return script_name
+ path = os.getenv('PATH', os.defpath).split(os.pathsep)
+ for dir in path:
+ if dir == '':
+ continue
+ fn = os.path.join(dir, script_name)
+ if os.path.isfile(fn):
+ return fn
+
+ print >>sys.stderr, 'Could not find script %s' % script_name
+ raise SystemExit(1)
+
+class ContextualProfile(cProfile.Profile):
+ """ A subclass of cProfile.Profile that adds a context manager for Python
+ 2.5 with: statements and a decorator.
+ """
+
+ def __init__(self, *args, **kwds):
+ super(ContextualProfile, self).__init__(*args, **kwds)
+ self.enable_count = 0
+
+ def enable_by_count(self, subcalls=True, builtins=True):
+ """ Enable the profiler if it hasn't been enabled before.
+ """
+ if self.enable_count == 0:
+ self.enable(subcalls=subcalls, builtins=builtins)
+ self.enable_count += 1
+
+ def disable_by_count(self):
+ """ Disable the profiler if the number of disable requests matches the
+ number of enable requests.
+ """
+ if self.enable_count > 0:
+ self.enable_count -= 1
+ if self.enable_count == 0:
+ self.disable()
+
+ def __call__(self, func):
+ """ Decorate a function to start the profiler on function entry and stop
+ it on function exit.
+ """
+ def f(*args, **kwds):
+ self.enable_by_count()
+ try:
+ result = func(*args, **kwds)
+ finally:
+ self.disable_by_count()
+ return result
+ f.__name__ = func.__name__
+ f.__doc__ = func.__doc__
+ f.__dict__.update(func.__dict__)
+ return f
+
+ def __enter__(self):
+ self.enable_by_count()
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self.disable_by_count()
+
+
+def main(args):
+ usage = "%s [-s setupfile] [-o output_file_path] scriptfile [arg] ..."
+ parser = optparse.OptionParser(usage=usage % sys.argv[0])
+ parser.allow_interspersed_args = False
+ parser.add_option('-l', '--line-by-line', action='store_true',
+ help="Use the line-by-line profiler from the line_profiler module "
+ "instead of cProfile. Implies --builtin.")
+ parser.add_option('-b', '--builtin', action='store_true',
+ help="Put 'profile' in the builtins. Use 'profile.enable()' and "
+ "'profile.disable()' in your code to turn it on and off, or "
+ "'@profile' to decorate a single function, or 'with profile:' "
+ "to profile a single section of code.")
+ parser.add_option('-o', '--outfile', default=None,
+ help="Save stats to <outfile>")
+ parser.add_option('-s', '--setup', default=None,
+ help="Code to execute before the code to profile")
+
+ if not sys.argv[1:]:
+ parser.print_usage()
+ sys.exit(2)
+
+ options, args = parser.parse_args()
+
+ if not options.outfile:
+ options.outfile = '%s.prof' % os.path.basename(args[0])
+
+ sys.argv[:] = args
+ if options.setup is not None:
+ # Run some setup code outside of the profiler. This is good for large
+ # imports.
+ setup_file = find_script(options.setup)
+ __file__ = setup_file
+ __name__ = '__main__'
+ # Make sure the script's directory is on sys.path instead of just
+ # lsprof.py's.
+ sys.path.insert(0, os.path.dirname(setup_file))
+ execfile(setup_file)
+
+ script_file = find_script(sys.argv[0])
+ __file__ = script_file
+ __name__ = '__main__'
+ # Make sure the script's directory is on sys.path instead of just
+ # lsprof.py's.
+ sys.path.insert(0, os.path.dirname(script_file))
+
+ if options.line_by_line:
+ import line_profiler
+ prof = line_profiler.LineProfiler()
+ options.builtin = True
+ else:
+ prof = ContextualProfile()
+ if options.builtin:
+ import __builtin__
+ __builtin__.__dict__['profile'] = prof
+
+ try:
+ try:
+ if options.builtin:
+ execfile(script_file)
+ else:
+ prof.run('execfile(%r)' % (script_file,))
+ except (KeyboardInterrupt, SystemExit):
+ pass
+ finally:
+ prof.dump_stats(options.outfile)
+ print 'Wrote profile results to %s' % options.outfile
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
+
Oops, something went wrong.

0 comments on commit eb54217

Please sign in to comment.