Add python-eventlet 0.16.1
[packages/trusty/python-eventlet.git] / eventlet / eventlet / green / profile.py
diff --git a/eventlet/eventlet/green/profile.py b/eventlet/eventlet/green/profile.py
new file mode 100644 (file)
index 0000000..e323adc
--- /dev/null
@@ -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()