--- /dev/null
+# 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()