codekingpro/portable-devtools
114k
1# Copyright (c) 2010, CCP Games2# All rights reserved.3#4# Redistribution and use in source and binary forms, with or without5# modification, are permitted provided that the following conditions are met:6# * Redistributions of source code must retain the above copyright7# notice, this list of conditions and the following disclaimer.8# * Redistributions in binary form must reproduce the above copyright9# notice, this list of conditions and the following disclaimer in the10# documentation and/or other materials provided with the distribution.11# * Neither the name of CCP Games nor the12# names of its contributors may be used to endorse or promote products13# derived from this software without specific prior written permission.14#15# THIS SOFTWARE IS PROVIDED BY CCP GAMES ``AS IS'' AND ANY16# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED17# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE18# DISCLAIMED. IN NO EVENT SHALL CCP GAMES BE LIABLE FOR ANY19# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES20# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;21# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND22# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT23# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS24# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.25 26"""This module is API-equivalent to the standard library :mod:`profile` module27lbut it is greenthread-aware as well as thread-aware. Use this module28to profile Eventlet-based applications in preference to either :mod:`profile` or :mod:`cProfile`.29FIXME: No testcases for this module.30"""31 32profile_orig = __import__('profile')33__all__ = profile_orig.__all__34 35from eventlet.patcher import slurp_properties36slurp_properties(profile_orig, globals(), srckeys=dir(profile_orig))37 38import sys39import functools40 41from eventlet import greenthread42from eventlet import patcher43import six44 45thread = patcher.original(six.moves._thread.__name__) # non-monkeypatched module needed46 47 48# This class provides the start() and stop() functions49class Profile(profile_orig.Profile):50 base = profile_orig.Profile51 52 def __init__(self, timer=None, bias=None):53 self.current_tasklet = greenthread.getcurrent()54 self.thread_id = thread.get_ident()55 self.base.__init__(self, timer, bias)56 self.sleeping = {}57 58 def __call__(self, *args):59 """make callable, allowing an instance to be the profiler"""60 self.dispatcher(*args)61 62 def _setup(self):63 self._has_setup = True64 self.cur = None65 self.timings = {}66 self.current_tasklet = greenthread.getcurrent()67 self.thread_id = thread.get_ident()68 self.simulate_call("profiler")69 70 def start(self, name="start"):71 if getattr(self, "running", False):72 return73 self._setup()74 self.simulate_call("start")75 self.running = True76 sys.setprofile(self.dispatcher)77 78 def stop(self):79 sys.setprofile(None)80 self.running = False81 self.TallyTimings()82 83 # special cases for the original run commands, makin sure to84 # clear the timer context.85 def runctx(self, cmd, globals, locals):86 if not getattr(self, "_has_setup", False):87 self._setup()88 try:89 return profile_orig.Profile.runctx(self, cmd, globals, locals)90 finally:91 self.TallyTimings()92 93 def runcall(self, func, *args, **kw):94 if not getattr(self, "_has_setup", False):95 self._setup()96 try:97 return profile_orig.Profile.runcall(self, func, *args, **kw)98 finally:99 self.TallyTimings()100 101 def trace_dispatch_return_extend_back(self, frame, t):102 """A hack function to override error checking in parent class. It103 allows invalid returns (where frames weren't preveiously entered into104 the profiler) which can happen for all the tasklets that suddenly start105 to get monitored. This means that the time will eventually be attributed106 to a call high in the chain, when there is a tasklet switch107 """108 if isinstance(self.cur[-2], Profile.fake_frame):109 return False110 self.trace_dispatch_call(frame, 0)111 return self.trace_dispatch_return(frame, t)112 113 def trace_dispatch_c_return_extend_back(self, frame, t):114 # same for c return115 if isinstance(self.cur[-2], Profile.fake_frame):116 return False # ignore bogus returns117 self.trace_dispatch_c_call(frame, 0)118 return self.trace_dispatch_return(frame, t)119 120 def SwitchTasklet(self, t0, t1, t):121 # tally the time spent in the old tasklet122 pt, it, et, fn, frame, rcur = self.cur123 cur = (pt, it + t, et, fn, frame, rcur)124 125 # we are switching to a new tasklet, store the old126 self.sleeping[t0] = cur, self.timings127 self.current_tasklet = t1128 129 # find the new one130 try:131 self.cur, self.timings = self.sleeping.pop(t1)132 except KeyError:133 self.cur, self.timings = None, {}134 self.simulate_call("profiler")135 self.simulate_call("new_tasklet")136 137 def TallyTimings(self):138 oldtimings = self.sleeping139 self.sleeping = {}140 141 # first, unwind the main "cur"142 self.cur = self.Unwind(self.cur, self.timings)143 144 # we must keep the timings dicts separate for each tasklet, since it contains145 # the 'ns' item, recursion count of each function in that tasklet. This is146 # used in the Unwind dude.147 for tasklet, (cur, timings) in six.iteritems(oldtimings):148 self.Unwind(cur, timings)149 150 for k, v in six.iteritems(timings):151 if k not in self.timings:152 self.timings[k] = v153 else:154 # accumulate all to the self.timings155 cc, ns, tt, ct, callers = self.timings[k]156 # ns should be 0 after unwinding157 cc += v[0]158 tt += v[2]159 ct += v[3]160 for k1, v1 in six.iteritems(v[4]):161 callers[k1] = callers.get(k1, 0) + v1162 self.timings[k] = cc, ns, tt, ct, callers163 164 def Unwind(self, cur, timings):165 "A function to unwind a 'cur' frame and tally the results"166 "see profile.trace_dispatch_return() for details"167 # also see simulate_cmd_complete()168 while(cur[-1]):169 rpt, rit, ret, rfn, frame, rcur = cur170 frame_total = rit + ret171 172 if rfn in timings:173 cc, ns, tt, ct, callers = timings[rfn]174 else:175 cc, ns, tt, ct, callers = 0, 0, 0, 0, {}176 177 if not ns:178 ct = ct + frame_total179 cc = cc + 1180 181 if rcur:182 ppt, pit, pet, pfn, pframe, pcur = rcur183 else:184 pfn = None185 186 if pfn in callers:187 callers[pfn] = callers[pfn] + 1 # hack: gather more188 elif pfn:189 callers[pfn] = 1190 191 timings[rfn] = cc, ns - 1, tt + rit, ct, callers192 193 ppt, pit, pet, pfn, pframe, pcur = rcur194 rcur = ppt, pit + rpt, pet + frame_total, pfn, pframe, pcur195 cur = rcur196 return cur197 198 199def ContextWrap(f):200 @functools.wraps(f)201 def ContextWrapper(self, arg, t):202 current = greenthread.getcurrent()203 if current != self.current_tasklet:204 self.SwitchTasklet(self.current_tasklet, current, t)205 t = 0.0 # the time was billed to the previous tasklet206 return f(self, arg, t)207 return ContextWrapper208 209 210# Add "return safety" to the dispatchers211Profile.dispatch = dict(profile_orig.Profile.dispatch, **{212 'return': Profile.trace_dispatch_return_extend_back,213 'c_return': Profile.trace_dispatch_c_return_extend_back,214})215# Add automatic tasklet detection to the callbacks.216Profile.dispatch = dict((k, ContextWrap(v)) for k, v in six.viewitems(Profile.dispatch))217 218 219# run statements shamelessly stolen from profile.py220def run(statement, filename=None, sort=-1):221 """Run statement under profiler optionally saving results in filename222 223 This function takes a single argument that can be passed to the224 "exec" statement, and an optional file name. In all cases this225 routine attempts to "exec" its first argument and gather profiling226 statistics from the execution. If no file name is present, then this227 function automatically prints a simple profiling report, sorted by the228 standard name string (file/line/function-name) that is presented in229 each line.230 """231 prof = Profile()232 try:233 prof = prof.run(statement)234 except SystemExit:235 pass236 if filename is not None:237 prof.dump_stats(filename)238 else:239 return prof.print_stats(sort)240 241 242def runctx(statement, globals, locals, filename=None):243 """Run statement under profiler, supplying your own globals and locals,244 optionally saving results in filename.245 246 statement and filename have the same semantics as profile.run247 """248 prof = Profile()249 try:250 prof = prof.runctx(statement, globals, locals)251 except SystemExit:252 pass253 254 if filename is not None:255 prof.dump_stats(filename)256 else:257 return prof.print_stats()258 