profilehooks.py

Go to the documentation of this file.
00001 ##
00002 # 
00003 # Profiling hooks
00004 # 
00005 # This module contains a couple of decorators (`profile` and `coverage`) that
00006 # can be used to wrap functions and/or methods to produce profiles and line
00007 # coverage reports.
00008 # 
00009 # Usage example (Python 2.4 or newer)::
00010 # 
00011 #     from profilehooks import profile, coverage
00012 # 
00013 #     @profile    # or @coverage
00014 #     def fn(n):
00015 #         if n < 2: return 1
00016 #         else: return n * fn(n-1)
00017 # 
00018 #     print fn(42)
00019 # 
00020 # Usage example (Python 2.3 or older)::
00021 # 
00022 #     from profilehooks import profile, coverage
00023 # 
00024 #     def fn(n):
00025 #         if n < 2: return 1
00026 #         else: return n * fn(n-1)
00027 # 
00028 #     # Now wrap that function in a decorator
00029 #     fn = profile(fn) # or coverage(fn)
00030 # 
00031 #     print fn(42)
00032 # 
00033 # Reports for all thusly decorated functions will be printed to sys.stdout
00034 # on program termination.  You can alternatively request for immediate
00035 # reports for each call by passing immediate=True to the profile decorator.
00036 # 
00037 # There's also a @timecall decorator for printing the time to sys.stderr
00038 # every time a function is called, when you just want to get a rough measure
00039 # instead of a detailed (but costly) profile.
00040 # 
00041 # Caveats
00042 # 
00043 #   A thread on python-dev convinced me that hotshot produces bogus numbers.
00044 #   See http://mail.python.org/pipermail/python-dev/2005-November/058264.html
00045 # 
00046 #   I don't know what will happen if a decorated function will try to call
00047 #   another decorated function.  All decorators probably need to explicitly
00048 #   support nested profiling (currently TraceFuncCoverage is the only one
00049 #   that supports this, while HotShotFuncProfile has support for recursive
00050 #   functions.)
00051 # 
00052 #   Profiling with hotshot creates temporary files (*.prof for profiling,
00053 #   *.cprof for coverage) in the current directory.  These files are not
00054 #   cleaned up.  Exception: when you specify a filename to the profile
00055 #   decorator (to store the pstats.Stats object for later inspection),
00056 #   the temporary file will be the filename you specified with '.raw'
00057 #   appended at the end.
00058 # 
00059 #   Coverage analysis with hotshot seems to miss some executions resulting
00060 #   in lower line counts and some lines errorneously marked as never
00061 #   executed.  For this reason coverage analysis now uses trace.py which is
00062 #   slower, but more accurate.
00063 # 
00064 # Copyright (c) 2004--2008 Marius Gedminas <marius@pov.lt>
00065 # Copyright (c) 2007 Hanno Schlichting
00066 # Copyright (c) 2008 Florian Schulze
00067 # 
00068 # Released under the MIT licence since December 2006:
00069 # 
00070 #     Permission is hereby granted, free of charge, to any person obtaining a
00071 #     copy of this software and associated documentation files (the "Software"),
00072 #     to deal in the Software without restriction, including without limitation
00073 #     the rights to use, copy, modify, merge, publish, distribute, sublicense,
00074 #     and/or sell copies of the Software, and to permit persons to whom the
00075 #     Software is furnished to do so, subject to the following conditions:
00076 # 
00077 #     The above copyright notice and this permission notice shall be included in
00078 #     all copies or substantial portions of the Software.
00079 # 
00080 #     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
00081 #     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
00082 #     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
00083 #     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
00084 #     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
00085 #     FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
00086 #     DEALINGS IN THE SOFTWARE.
00087 # 
00088 # (Previously it was distributed under the GNU General Public Licence.)
00089 # 
00090 # $Id$
00091 
00092 __author__ = "Marius Gedminas (marius@gedmin.as)"
00093 __copyright__ = "Copyright 2004-2008, Marius Gedminas"
00094 __license__ = "MIT"
00095 __version__ = "1.3"
00096 __date__ = "2008-06-10"
00097 
00098 
00099 import atexit
00100 import inspect
00101 import sys
00102 import re
00103 
00104 # For profiling
00105 from profile import Profile
00106 import pstats
00107 
00108 # For hotshot profiling (inaccurate!)
00109 import hotshot
00110 import hotshot.stats
00111 
00112 # For trace.py coverage
00113 import trace
00114 
00115 # For hotshot coverage (inaccurate!; uses undocumented APIs; might break)
00116 import _hotshot
00117 import hotshot.log
00118 
00119 # For timecall
00120 import time
00121 
00122 
00123 ##
00124 # Mark `fn` for profiling.
00125 # 
00126 #     If `skip` is > 0, first `skip` calls to `fn` will not be profiled.
00127 # 
00128 #     If `immediate` is False, profiling results will be printed to
00129 #     sys.stdout on program termination.  Otherwise results will be printed
00130 #     after each call.
00131 # 
00132 #     If `dirs` is False only the name of the file will be printed.
00133 #     Otherwise the full path is used.
00134 # 
00135 #     `sort` can be a list of sort keys (defaulting to ['cumulative',
00136 #     'time', 'calls']).  The following ones are recognized::
00137 # 
00138 #         'calls'      -- call count
00139 #         'cumulative' -- cumulative time
00140 #         'file'       -- file name
00141 #         'line'       -- line number
00142 #         'module'     -- file name
00143 #         'name'       -- function name
00144 #         'nfl'        -- name/file/line
00145 #         'pcalls'     -- call count
00146 #         'stdname'    -- standard name
00147 #         'time'       -- internal time
00148 # 
00149 #     `entries` limits the output to the first N entries.
00150 # 
00151 #     If `filename` is specified, the profile stats will be stored in the
00152 #     named file.  You can load them pstats.Stats(filename).
00153 # 
00154 #     Usage::
00155 # 
00156 #         def fn(...):
00157 #             ...
00158 #         fn = profile(fn, skip=1)
00159 # 
00160 #     If you are using Python 2.4, you should be able to use the decorator
00161 #     syntax::
00162 # 
00163 #         @profile(skip=3)
00164 #         def fn(...):
00165 #             ...
00166 # 
00167 #     or just ::
00168 # 
00169 #         @profile
00170 #         def fn(...):
00171 #             ...
00172 # 
00173 #     
00174 def profile(fn=None, skip=0, filename=None, immediate=False, dirs=False,
00175             sort=None, entries=40):
00176     if fn is None: # @profile() syntax -- we are a decorator maker
00177         def decorator(fn):
00178             return profile(fn, skip=skip, filename=filename,
00179                            immediate=immediate, dirs=dirs,
00180                            sort=sort, entries=entries)
00181         return decorator
00182     # @profile syntax -- we are a decorator.
00183     fp = FuncProfile(fn, skip=skip, filename=filename,
00184                      immediate=immediate, dirs=dirs,
00185                      sort=sort, entries=entries)
00186     # fp = HotShotFuncProfile(fn, skip=skip, filename=filename, ...)
00187          # or HotShotFuncProfile
00188     # We cannot return fp or fp.__call__ directly as that would break method
00189     # definitions, instead we need to return a plain function.
00190     def new_fn(*args, **kw):
00191         return fp(*args, **kw)
00192     new_fn.__doc__ = fn.__doc__
00193     new_fn.__name__ = fn.__name__
00194     new_fn.__dict__ = fn.__dict__
00195     new_fn.__module__ = fn.__module__
00196     return new_fn
00197 
00198 
00199 ##
00200 # Mark `fn` for line coverage analysis.
00201 # 
00202 #     Results will be printed to sys.stdout on program termination.
00203 # 
00204 #     Usage::
00205 # 
00206 #         def fn(...):
00207 #             ...
00208 #         fn = coverage(fn)
00209 # 
00210 #     If you are using Python 2.4, you should be able to use the decorator
00211 #     syntax::
00212 # 
00213 #         @coverage
00214 #         def fn(...):
00215 #             ...
00216 # 
00217 #     
00218 def coverage(fn):
00219     fp = TraceFuncCoverage(fn) # or HotShotFuncCoverage
00220     # We cannot return fp or fp.__call__ directly as that would break method
00221     # definitions, instead we need to return a plain function.
00222     def new_fn(*args, **kw):
00223         return fp(*args, **kw)
00224     new_fn.__doc__ = fn.__doc__
00225     new_fn.__name__ = fn.__name__
00226     new_fn.__dict__ = fn.__dict__
00227     new_fn.__module__ = fn.__module__
00228     return new_fn
00229 
00230 
00231 ##
00232 # Mark `fn` for line coverage analysis.
00233 # 
00234 #     Uses the 'hotshot' module for fast coverage analysis.
00235 # 
00236 #     BUG: Produces inaccurate results.
00237 # 
00238 #     See the docstring of `coverage` for usage examples.
00239 #     
00240 def coverage_with_hotshot(fn):
00241     fp = HotShotFuncCoverage(fn)
00242     # We cannot return fp or fp.__call__ directly as that would break method
00243     # definitions, instead we need to return a plain function.
00244     def new_fn(*args, **kw):
00245         return fp(*args, **kw)
00246     new_fn.__doc__ = fn.__doc__
00247     new_fn.__name__ = fn.__name__
00248     new_fn.__dict__ = fn.__dict__
00249     new_fn.__module__ = fn.__module__
00250     return new_fn
00251 
00252 
00253 ##
00254 # Profiler for a function (uses profile).
00255 class FuncProfile:
00256 
00257     # This flag is shared between all instances
00258     in_profiler = False
00259 
00260     ##
00261     # Creates a profiler for a function.
00262     # 
00263     #         Every profiler has its own log file (the name of which is derived
00264     #         from the function name).
00265     # 
00266     #         FuncProfile registers an atexit handler that prints profiling
00267     #         information to sys.stderr when the program terminates.
00268     #         
00269     def __init__(self, fn, skip=0, filename=None, immediate=False, dirs=False,
00270                  sort=None, entries=40):
00271         self.fn = fn
00272         self.filename = filename
00273         self.immediate = immediate
00274         self.dirs = dirs
00275         self.sort = sort or ('cumulative', 'time', 'calls')
00276         if isinstance(self.sort, str):
00277             self.sort = (self.sort, )
00278         self.entries = entries
00279         self.stats = pstats.Stats(Profile())
00280         self.ncalls = 0
00281         self.skip = skip
00282         self.skipped = 0
00283         atexit.register(self.atexit)
00284 
00285     ##
00286     # Profile a singe call to the function.
00287     def __call__(self, *args, **kw):
00288         self.ncalls += 1
00289         if self.skip > 0:
00290             self.skip -= 1
00291             self.skipped += 1
00292             return self.fn(*args, **kw)
00293         if FuncProfile.in_profiler:
00294             # handle recursive calls
00295             return self.fn(*args, **kw)
00296         # You cannot reuse the same profiler for many calls and accumulate
00297         # stats that way.  :-/
00298         profiler = Profile()
00299         try:
00300             FuncProfile.in_profiler = True
00301             return profiler.runcall(self.fn, *args, **kw)
00302         finally:
00303             FuncProfile.in_profiler = False
00304             self.stats.add(profiler)
00305             if self.immediate:
00306                 self.print_stats()
00307                 self.reset_stats()
00308 
00309     ##
00310     # Print profile information to sys.stdout.
00311     def print_stats(self):
00312         funcname = self.fn.__name__
00313         filename = self.fn.func_code.co_filename
00314         lineno = self.fn.func_code.co_firstlineno
00315         print
00316         print "*** PROFILER RESULTS ***"
00317         print "%s (%s:%s)" % (funcname, filename, lineno)
00318         print "function called %d times" % self.ncalls,
00319         if self.skipped:
00320             print "(%d calls not profiled)" % self.skipped
00321         else:
00322             print
00323         print
00324         stats = self.stats
00325         if self.filename:
00326             stats.dump_stats(self.filename)
00327         if not self.dirs:
00328             stats.strip_dirs()
00329         stats.sort_stats(*self.sort)
00330         stats.print_stats(self.entries)
00331 
00332     ##
00333     # Reset accumulated profiler statistics.
00334     def reset_stats(self):
00335         self.stats = pstats.Stats(Profile())
00336         self.ncalls = 0
00337         self.skipped = 0
00338 
00339     ##
00340     # Stop profiling and print profile information to sys.stdout.
00341     # 
00342     #         This function is registered as an atexit hook.
00343     #         
00344     def atexit(self):
00345         if not self.immediate:
00346             self.print_stats()
00347 
00348 
00349 ##
00350 # Profiler for a function (uses hotshot).
00351 class HotShotFuncProfile:
00352 
00353     # This flag is shared between all instances
00354     in_profiler = False
00355 
00356     ##
00357     # Creates a profiler for a function.
00358     # 
00359     #         Every profiler has its own log file (the name of which is derived
00360     #         from the function name).
00361     # 
00362     #         HotShotFuncProfile registers an atexit handler that prints
00363     #         profiling information to sys.stderr when the program terminates.
00364     # 
00365     #         The log file is not removed and remains there to clutter the
00366     #         current working directory.
00367     #         
00368     def __init__(self, fn, skip=0, filename=None):
00369         self.fn = fn
00370         self.filename = filename
00371         if self.filename:
00372             self.logfilename = filename + ".raw"
00373         else:
00374             self.logfilename = fn.__name__ + ".prof"
00375         self.profiler = hotshot.Profile(self.logfilename)
00376         self.ncalls = 0
00377         self.skip = skip
00378         self.skipped = 0
00379         atexit.register(self.atexit)
00380 
00381     ##
00382     # Profile a singe call to the function.
00383     def __call__(self, *args, **kw):
00384         self.ncalls += 1
00385         if self.skip > 0:
00386             self.skip -= 1
00387             self.skipped += 1
00388             return self.fn(*args, **kw)
00389         if HotShotFuncProfile.in_profiler:
00390             # handle recursive calls
00391             return self.fn(*args, **kw)
00392         try:
00393             HotShotFuncProfile.in_profiler = True
00394             return self.profiler.runcall(self.fn, *args, **kw)
00395         finally:
00396             HotShotFuncProfile.in_profiler = False
00397 
00398     ##
00399     # Stop profiling and print profile information to sys.stderr.
00400     # 
00401     #         This function is registered as an atexit hook.
00402     #         
00403     def atexit(self):
00404         self.profiler.close()
00405         funcname = self.fn.__name__
00406         filename = self.fn.func_code.co_filename
00407         lineno = self.fn.func_code.co_firstlineno
00408         print
00409         print "*** PROFILER RESULTS ***"
00410         print "%s (%s:%s)" % (funcname, filename, lineno)
00411         print "function called %d times" % self.ncalls,
00412         if self.skipped:
00413             print "(%d calls not profiled)" % self.skipped
00414         else:
00415             print
00416         print
00417         stats = hotshot.stats.load(self.logfilename)
00418         # hotshot.stats.load takes ages, and the .prof file eats megabytes, but
00419         # a saved stats object is small and fast
00420         if self.filename:
00421             stats.dump_stats(self.filename)
00422         # it is best to save before strip_dirs
00423         stats.strip_dirs()
00424         stats.sort_stats('cumulative', 'time', 'calls')
00425         stats.print_stats(40)
00426 
00427 
00428 ##
00429 # Coverage analysis for a function (uses _hotshot).
00430 # 
00431 #     HotShot coverage is reportedly faster than trace.py, but it appears to
00432 #     have problems with exceptions; also line counts in coverage reports
00433 #     are generally lower from line counts produced by TraceFuncCoverage.
00434 #     Is this my bug, or is it a problem with _hotshot?
00435 #     
00436 class HotShotFuncCoverage:
00437 
00438     ##
00439     # Creates a profiler for a function.
00440     # 
00441     #         Every profiler has its own log file (the name of which is derived
00442     #         from the function name).
00443     # 
00444     #         HotShotFuncCoverage registers an atexit handler that prints
00445     #         profiling information to sys.stderr when the program terminates.
00446     # 
00447     #         The log file is not removed and remains there to clutter the
00448     #         current working directory.
00449     #         
00450     def __init__(self, fn):
00451         self.fn = fn
00452         self.logfilename = fn.__name__ + ".cprof"
00453         self.profiler = _hotshot.coverage(self.logfilename)
00454         self.ncalls = 0
00455         atexit.register(self.atexit)
00456 
00457     ##
00458     # Profile a singe call to the function.
00459     def __call__(self, *args, **kw):
00460         self.ncalls += 1
00461         return self.profiler.runcall(self.fn, args, kw)
00462 
00463     ##
00464     # Stop profiling and print profile information to sys.stderr.
00465     # 
00466     #         This function is registered as an atexit hook.
00467     #         
00468     def atexit(self):
00469         self.profiler.close()
00470         funcname = self.fn.__name__
00471         filename = self.fn.func_code.co_filename
00472         lineno = self.fn.func_code.co_firstlineno
00473         print
00474         print "*** COVERAGE RESULTS ***"
00475         print "%s (%s:%s)" % (funcname, filename, lineno)
00476         print "function called %d times" % self.ncalls
00477         print
00478         fs = FuncSource(self.fn)
00479         reader = hotshot.log.LogReader(self.logfilename)
00480         for what, (filename, lineno, funcname), tdelta in reader:
00481             if filename != fs.filename:
00482                 continue
00483             if what == hotshot.log.LINE:
00484                 fs.mark(lineno)
00485             if what == hotshot.log.ENTER:
00486                 # hotshot gives us the line number of the function definition
00487                 # and never gives us a LINE event for the first statement in
00488                 # a function, so if we didn't perform this mapping, the first
00489                 # statement would be marked as never executed
00490                 if lineno == fs.firstlineno:
00491                     lineno = fs.firstcodelineno
00492                 fs.mark(lineno)
00493         reader.close()
00494         print fs
00495 
00496 
00497 ##
00498 # Coverage analysis for a function (uses trace module).
00499 # 
00500 #     HotShot coverage analysis is reportedly faster, but it appears to have
00501 #     problems with exceptions.
00502 #     
00503 class TraceFuncCoverage:
00504 
00505     # Shared between all instances so that nested calls work
00506     tracer = trace.Trace(count=True, trace=False,
00507                          ignoredirs=[sys.prefix, sys.exec_prefix])
00508 
00509     # This flag is also shared between all instances
00510     tracing = False
00511 
00512     ##
00513     # Creates a profiler for a function.
00514     # 
00515     #         Every profiler has its own log file (the name of which is derived
00516     #         from the function name).
00517     # 
00518     #         TraceFuncCoverage registers an atexit handler that prints
00519     #         profiling information to sys.stderr when the program terminates.
00520     # 
00521     #         The log file is not removed and remains there to clutter the
00522     #         current working directory.
00523     #         
00524     def __init__(self, fn):
00525         self.fn = fn
00526         self.logfilename = fn.__name__ + ".cprof"
00527         self.ncalls = 0
00528         atexit.register(self.atexit)
00529 
00530     ##
00531     # Profile a singe call to the function.
00532     def __call__(self, *args, **kw):
00533         self.ncalls += 1
00534         if TraceFuncCoverage.tracing:
00535             return self.fn(*args, **kw)
00536         try:
00537             TraceFuncCoverage.tracing = True
00538             return self.tracer.runfunc(self.fn, *args, **kw)
00539         finally:
00540             TraceFuncCoverage.tracing = False
00541 
00542     ##
00543     # Stop profiling and print profile information to sys.stderr.
00544     # 
00545     #         This function is registered as an atexit hook.
00546     #         
00547     def atexit(self):
00548         funcname = self.fn.__name__
00549         filename = self.fn.func_code.co_filename
00550         lineno = self.fn.func_code.co_firstlineno
00551         print
00552         print "*** COVERAGE RESULTS ***"
00553         print "%s (%s:%s)" % (funcname, filename, lineno)
00554         print "function called %d times" % self.ncalls
00555         print
00556         fs = FuncSource(self.fn)
00557         for (filename, lineno), count in self.tracer.counts.items():
00558             if filename != fs.filename:
00559                 continue
00560             fs.mark(lineno, count)
00561         print fs
00562         never_executed = fs.count_never_executed()
00563         if never_executed:
00564             print "%d lines were not executed." % never_executed
00565 
00566 
00567 ##
00568 # Source code annotator for a function.
00569 class FuncSource:
00570 
00571     blank_rx = re.compile(r"^\s*finally:\s*(#.*)?$")
00572 
00573     def __init__(self, fn):
00574         self.fn = fn
00575         self.filename = inspect.getsourcefile(fn)
00576         self.source, self.firstlineno = inspect.getsourcelines(fn)
00577         self.sourcelines = {}
00578         self.firstcodelineno = self.firstlineno
00579         self.find_source_lines()
00580 
00581     ##
00582     # Mark all executable source lines in fn as executed 0 times.
00583     def find_source_lines(self):
00584         strs = trace.find_strings(self.filename)
00585         lines = trace.find_lines_from_code(self.fn.func_code, strs)
00586         self.firstcodelineno = sys.maxint
00587         for lineno in lines:
00588             self.firstcodelineno = min(self.firstcodelineno, lineno)
00589             self.sourcelines.setdefault(lineno, 0)
00590         if self.firstcodelineno == sys.maxint:
00591             self.firstcodelineno = self.firstlineno
00592 
00593     ##
00594     # Mark a given source line as executed count times.
00595     # 
00596     #         Multiple calls to mark for the same lineno add up.
00597     #         
00598     def mark(self, lineno, count=1):
00599         self.sourcelines[lineno] = self.sourcelines.get(lineno, 0) + count
00600 
00601     ##
00602     # Count statements that were never executed.
00603     def count_never_executed(self):
00604         lineno = self.firstlineno
00605         counter = 0
00606         for line in self.source:
00607             if self.sourcelines.get(lineno) == 0:
00608                 if not self.blank_rx.match(line):
00609                     counter += 1
00610             lineno += 1
00611         return counter
00612 
00613     ##
00614     # Return annotated source code for the function.
00615     def __str__(self):
00616         lines = []
00617         lineno = self.firstlineno
00618         for line in self.source:
00619             counter = self.sourcelines.get(lineno)
00620             if counter is None:
00621                 prefix = ' ' * 7
00622             elif counter == 0:
00623                 if self.blank_rx.match(line):
00624                     prefix = ' ' * 7
00625                 else:
00626                     prefix = '>' * 6 + ' '
00627             else:
00628                 prefix = '%5d: ' % counter
00629             lines.append(prefix + line)
00630             lineno += 1
00631         return ''.join(lines)
00632 
00633 
00634 ##
00635 # Wrap `fn` and print its execution time.
00636 # 
00637 #     Example::
00638 # 
00639 #         @timecall
00640 #         def somefunc(x, y):
00641 #             time.sleep(x * y)
00642 # 
00643 #         somefunc(2, 3)
00644 # 
00645 #     will print the time taken by somefunc on every call.  If you want just
00646 #     a summary at program termination, use
00647 # 
00648 #         @timecall(immediate=False)
00649 # 
00650 #     
00651 def timecall(fn=None, immediate=True):
00652     if fn is None: # @timecall() syntax -- we are a decorator maker
00653         def decorator(fn):
00654             return timecall(fn, immediate=immediate)
00655         return decorator
00656     # @timecall syntax -- we are a decorator.
00657     fp = FuncTimer(fn, immediate=immediate)
00658     # We cannot return fp or fp.__call__ directly as that would break method
00659     # definitions, instead we need to return a plain function.
00660     def new_fn(*args, **kw):
00661         return fp(*args, **kw)
00662     new_fn.__doc__ = fn.__doc__
00663     new_fn.__name__ = fn.__name__
00664     new_fn.__dict__ = fn.__dict__
00665     new_fn.__module__ = fn.__module__
00666     return new_fn
00667 
00668 
00669 class FuncTimer(object):
00670 
00671     def __init__(self, fn, immediate):
00672         self.fn = fn
00673         self.ncalls = 0
00674         self.totaltime = 0
00675         self.immediate = immediate
00676         if not immediate:
00677             atexit.register(self.atexit)
00678 
00679     ##
00680     # Profile a singe call to the function.
00681     def __call__(self, *args, **kw):
00682         fn = self.fn
00683         self.ncalls += 1
00684         try:
00685             start = time.time()
00686             return fn(*args, **kw)
00687         finally:
00688             duration = time.time() - start
00689             self.totaltime += duration
00690             if self.immediate:
00691                 funcname = fn.__name__
00692                 filename = fn.func_code.co_filename
00693                 lineno = fn.func_code.co_firstlineno
00694                 print >> sys.stderr, "\n  %s (%s:%s):\n    %.3f seconds\n" % (
00695                                         funcname, filename, lineno, duration)
00696     def atexit(self):
00697         if not self.ncalls:
00698             return
00699         funcname = self.fn.__name__
00700         filename = self.fn.func_code.co_filename
00701         lineno = self.fn.func_code.co_firstlineno
00702         print ("\n  %s (%s:%s):\n"
00703                "    %d calls, %.3f seconds (%.3f seconds per call)\n" % (
00704                                 funcname, filename, lineno, self.ncalls,
00705                                 self.totaltime, self.totaltime / self.ncalls))
00706 
00707 
00708 

© Copyright 2008-2009 Vyper Logix Corp., All Right Reserved; If you reference this document or any part of this document you must use the citation verbatim (including the link) "© Copyright 2008-2009 Vyper Logix Corp., All Right Reserved."

Notice: This source code contained in this document is NOT open source and is NOT being distributed as open source.

122,241 lines of code and growing...