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...