X-Git-Url: https://review.fuel-infra.org/gitweb?a=blobdiff_plain;f=eventlet%2Feventlet%2Fgreen%2Fprofile.py;fp=eventlet%2Feventlet%2Fgreen%2Fprofile.py;h=e323adc23a671e0340dc5ef963a7263b7b5242e9;hb=376ff3bfe7071cc0793184a378c4e74508fb0d97;hp=0000000000000000000000000000000000000000;hpb=70992db4bef26426213a8eae488be377cdd655ae;p=packages%2Ftrusty%2Fpython-eventlet.git diff --git a/eventlet/eventlet/green/profile.py b/eventlet/eventlet/green/profile.py new file mode 100644 index 0000000..e323adc --- /dev/null +++ b/eventlet/eventlet/green/profile.py @@ -0,0 +1,257 @@ +# Copyright (c) 2010, CCP Games +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of CCP Games nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY CCP GAMES ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL CCP GAMES BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""This module is API-equivalent to the standard library :mod:`profile` module +lbut it is greenthread-aware as well as thread-aware. Use this module +to profile Eventlet-based applications in preference to either :mod:`profile` or :mod:`cProfile`. +FIXME: No testcases for this module. +""" + +profile_orig = __import__('profile') +__all__ = profile_orig.__all__ + +from eventlet.patcher import slurp_properties +slurp_properties(profile_orig, globals(), srckeys=dir(profile_orig)) + +import sys +import functools + +from eventlet import greenthread +from eventlet import patcher +from eventlet.support import six + +thread = patcher.original('thread') # non-monkeypatched module needed + + +# This class provides the start() and stop() functions +class Profile(profile_orig.Profile): + base = profile_orig.Profile + + def __init__(self, timer=None, bias=None): + self.current_tasklet = greenthread.getcurrent() + self.thread_id = thread.get_ident() + self.base.__init__(self, timer, bias) + self.sleeping = {} + + def __call__(self, *args): + """make callable, allowing an instance to be the profiler""" + self.dispatcher(*args) + + def _setup(self): + self._has_setup = True + self.cur = None + self.timings = {} + self.current_tasklet = greenthread.getcurrent() + self.thread_id = thread.get_ident() + self.simulate_call("profiler") + + def start(self, name="start"): + if getattr(self, "running", False): + return + self._setup() + self.simulate_call("start") + self.running = True + sys.setprofile(self.dispatcher) + + def stop(self): + sys.setprofile(None) + self.running = False + self.TallyTimings() + + # special cases for the original run commands, makin sure to + # clear the timer context. + def runctx(self, cmd, globals, locals): + if not getattr(self, "_has_setup", False): + self._setup() + try: + return profile_orig.Profile.runctx(self, cmd, globals, locals) + finally: + self.TallyTimings() + + def runcall(self, func, *args, **kw): + if not getattr(self, "_has_setup", False): + self._setup() + try: + return profile_orig.Profile.runcall(self, func, *args, **kw) + finally: + self.TallyTimings() + + def trace_dispatch_return_extend_back(self, frame, t): + """A hack function to override error checking in parent class. It + allows invalid returns (where frames weren't preveiously entered into + the profiler) which can happen for all the tasklets that suddenly start + to get monitored. This means that the time will eventually be attributed + to a call high in the chain, when there is a tasklet switch + """ + if isinstance(self.cur[-2], Profile.fake_frame): + return False + self.trace_dispatch_call(frame, 0) + return self.trace_dispatch_return(frame, t) + + def trace_dispatch_c_return_extend_back(self, frame, t): + # same for c return + if isinstance(self.cur[-2], Profile.fake_frame): + return False # ignore bogus returns + self.trace_dispatch_c_call(frame, 0) + return self.trace_dispatch_return(frame, t) + + # Add "return safety" to the dispatchers + dispatch = dict(profile_orig.Profile.dispatch) + dispatch.update({ + "return": trace_dispatch_return_extend_back, + "c_return": trace_dispatch_c_return_extend_back, + }) + + def SwitchTasklet(self, t0, t1, t): + # tally the time spent in the old tasklet + pt, it, et, fn, frame, rcur = self.cur + cur = (pt, it + t, et, fn, frame, rcur) + + # we are switching to a new tasklet, store the old + self.sleeping[t0] = cur, self.timings + self.current_tasklet = t1 + + # find the new one + try: + self.cur, self.timings = self.sleeping.pop(t1) + except KeyError: + self.cur, self.timings = None, {} + self.simulate_call("profiler") + self.simulate_call("new_tasklet") + + def ContextWrap(f): + @functools.wraps(f) + def ContextWrapper(self, arg, t): + current = greenthread.getcurrent() + if current != self.current_tasklet: + self.SwitchTasklet(self.current_tasklet, current, t) + t = 0.0 # the time was billed to the previous tasklet + return f(self, arg, t) + return ContextWrapper + + # Add automatic tasklet detection to the callbacks. + dispatch = dict([(key, ContextWrap(val)) for key, val in six.iteritems(dispatch)]) + + def TallyTimings(self): + oldtimings = self.sleeping + self.sleeping = {} + + # first, unwind the main "cur" + self.cur = self.Unwind(self.cur, self.timings) + + # we must keep the timings dicts separate for each tasklet, since it contains + # the 'ns' item, recursion count of each function in that tasklet. This is + # used in the Unwind dude. + for tasklet, (cur, timings) in six.iteritems(oldtimings): + self.Unwind(cur, timings) + + for k, v in six.iteritems(timings): + if k not in self.timings: + self.timings[k] = v + else: + # accumulate all to the self.timings + cc, ns, tt, ct, callers = self.timings[k] + # ns should be 0 after unwinding + cc += v[0] + tt += v[2] + ct += v[3] + for k1, v1 in six.iteritems(v[4]): + callers[k1] = callers.get(k1, 0) + v1 + self.timings[k] = cc, ns, tt, ct, callers + + def Unwind(self, cur, timings): + "A function to unwind a 'cur' frame and tally the results" + "see profile.trace_dispatch_return() for details" + # also see simulate_cmd_complete() + while(cur[-1]): + rpt, rit, ret, rfn, frame, rcur = cur + frame_total = rit + ret + + if rfn in timings: + cc, ns, tt, ct, callers = timings[rfn] + else: + cc, ns, tt, ct, callers = 0, 0, 0, 0, {} + + if not ns: + ct = ct + frame_total + cc = cc + 1 + + if rcur: + ppt, pit, pet, pfn, pframe, pcur = rcur + else: + pfn = None + + if pfn in callers: + callers[pfn] = callers[pfn] + 1 # hack: gather more + elif pfn: + callers[pfn] = 1 + + timings[rfn] = cc, ns - 1, tt + rit, ct, callers + + ppt, pit, pet, pfn, pframe, pcur = rcur + rcur = ppt, pit + rpt, pet + frame_total, pfn, pframe, pcur + cur = rcur + return cur + + +# run statements shamelessly stolen from profile.py +def run(statement, filename=None, sort=-1): + """Run statement under profiler optionally saving results in filename + + This function takes a single argument that can be passed to the + "exec" statement, and an optional file name. In all cases this + routine attempts to "exec" its first argument and gather profiling + statistics from the execution. If no file name is present, then this + function automatically prints a simple profiling report, sorted by the + standard name string (file/line/function-name) that is presented in + each line. + """ + prof = Profile() + try: + prof = prof.run(statement) + except SystemExit: + pass + if filename is not None: + prof.dump_stats(filename) + else: + return prof.print_stats(sort) + + +def runctx(statement, globals, locals, filename=None): + """Run statement under profiler, supplying your own globals and locals, + optionally saving results in filename. + + statement and filename have the same semantics as profile.run + """ + prof = Profile() + try: + prof = prof.runctx(statement, globals, locals) + except SystemExit: + pass + + if filename is not None: + prof.dump_stats(filename) + else: + return prof.print_stats()