Skip to content

Commit

Permalink
Fix @Profile(profiler='hotshot'), and add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mgedmin committed Dec 2, 2014
1 parent bd8264b commit 0894371
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 36 deletions.
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Changelog
1.7.1 (unreleased)
------------------

- Make ``@profile(profiler='hotshot')`` work again. This was probably broken
in 1.0 or 1.1, but nobody complained.


1.7 (2013-10-16)
Expand Down
54 changes: 18 additions & 36 deletions profilehooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,13 +397,14 @@ class CProfileFuncProfile(FuncProfile):

if hotshot is not None:

class HotShotFuncProfile(object):
class HotShotFuncProfile(FuncProfile):
"""Profiler for a function (uses hotshot)."""

# This flag is shared between all instances
in_profiler = False

def __init__(self, fn, skip=0, filename=None):
def __init__(self, fn, skip=0, filename=None, immediate=False,
dirs=False, sort=None, entries=40):
"""Creates a profiler for a function.
Every profiler has its own log file (the name of which is derived
Expand All @@ -415,17 +416,13 @@ def __init__(self, fn, skip=0, filename=None):
The log file is not removed and remains there to clutter the
current working directory.
"""
self.fn = fn
self.filename = filename
if self.filename:
if filename:
self.logfilename = filename + ".raw"
else:
self.logfilename = fn.__name__ + ".prof"
self.profiler = hotshot.Profile(self.logfilename)
self.ncalls = 0
self.skip = skip
self.skipped = 0
atexit.register(self.atexit)
super(HotShotFuncProfile, self).__init__(
fn, skip=skip, filename=filename, immediate=immediate,
dirs=dirs, sort=sort, entries=entries)

def __call__(self, *args, **kw):
"""Profile a singe call to the function."""
Expand All @@ -442,34 +439,19 @@ def __call__(self, *args, **kw):
return self.profiler.runcall(self.fn, *args, **kw)
finally:
HotShotFuncProfile.in_profiler = False
if self.immediate:
self.print_stats()
self.reset_stats()

def atexit(self):
"""Stop profiling and print profile information to sys.stderr.
This function is registered as an atexit hook.
"""
def print_stats(self):
self.profiler.close()
funcname = self.fn.__name__
filename = self.fn.__code__.co_filename
lineno = self.fn.__code__.co_firstlineno
print("")
print("*** PROFILER RESULTS ***")
print("%s (%s:%s)" % (funcname, filename, lineno))
if self.skipped:
skipped = "(%d calls not profiled)" % self.skipped
else:
skipped = ""
print("function called %d times%s" % (self.ncalls, skipped))
print("")
stats = hotshot.stats.load(self.logfilename)
# hotshot.stats.load takes ages, and the .prof file eats megabytes, but
# a saved stats object is small and fast
if self.filename:
stats.dump_stats(self.filename)
# it is best to save before strip_dirs
stats.strip_dirs()
stats.sort_stats('cumulative', 'time', 'calls')
stats.print_stats(40)
self.stats = hotshot.stats.load(self.logfilename)
super(HotShotFuncProfile, self).print_stats()

def reset_stats(self):
self.profiler = hotshot.Profile(self.logfilename)
self.ncalls = 0
self.skipped = 0

AVAILABLE_PROFILERS['hotshot'] = HotShotFuncProfile

Expand Down
27 changes: 27 additions & 0 deletions test_profilehooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
Run it with python setup.py test
"""

import os
import sys
import doctest
import unittest
Expand Down Expand Up @@ -216,6 +217,32 @@ def doctest_profile_recursive_function():
"""


def doctest_profile_with_hotshot():
"""Test for profile
>>> @profilehooks.profile(immediate=True, profiler='hotshot', skip=2)
... def fac(n):
... if n < 1: return 1
... return n * fac(n-1)
>>> fac(1)
1
>>> fac(3)
<BLANKLINE>
*** PROFILER RESULTS ***
fac (<doctest test_profilehooks.doctest_profile_with_hotshot[0]>:1)
function called 6 times (2 calls not profiled)
...
6
Hotshot leaves temporary files behind
>>> os.unlink('fac.prof')
"""


def doctest_timecall():
"""Test for timecall.
Expand Down

0 comments on commit 0894371

Please sign in to comment.