aboutsummaryrefslogtreecommitdiffstats
path: root/python/gevent/_tracer.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/gevent/_tracer.py')
-rw-r--r--python/gevent/_tracer.py179
1 files changed, 179 insertions, 0 deletions
diff --git a/python/gevent/_tracer.py b/python/gevent/_tracer.py
new file mode 100644
index 0000000..98307ab
--- /dev/null
+++ b/python/gevent/_tracer.py
@@ -0,0 +1,179 @@
+# Copyright (c) 2018 gevent. See LICENSE for details.
+# cython: auto_pickle=False,embedsignature=True,always_allow_keywords=False
+from __future__ import print_function, absolute_import, division
+
+import sys
+import traceback
+
+from greenlet import settrace
+from greenlet import getcurrent
+
+from gevent.util import format_run_info
+
+from gevent._compat import perf_counter
+from gevent._util import gmctime
+
+
+__all__ = [
+ 'GreenletTracer',
+ 'HubSwitchTracer',
+ 'MaxSwitchTracer',
+]
+
+# Recall these classes are cython compiled, so
+# class variable declarations are bad.
+
+
+class GreenletTracer(object):
+ def __init__(self):
+ # A counter, incremented by the greenlet trace function
+ # we install on every greenlet switch. This is reset when the
+ # periodic monitoring thread runs.
+
+ self.greenlet_switch_counter = 0
+
+ # The greenlet last switched to.
+ self.active_greenlet = None
+
+ # The trace function that was previously installed,
+ # if any.
+ # NOTE: Calling a class instance is cheaper than
+ # calling a bound method (at least when compiled with cython)
+ # even when it redirects to another function.
+ prev_trace = settrace(self)
+
+ self.previous_trace_function = prev_trace
+
+ self._killed = False
+
+ def kill(self):
+ # Must be called in the monitored thread.
+ if not self._killed:
+ self._killed = True
+ settrace(self.previous_trace_function)
+ self.previous_trace_function = None
+
+ def _trace(self, event, args):
+ # This function runs in the thread we are monitoring.
+ self.greenlet_switch_counter += 1
+ if event in ('switch', 'throw'):
+ # args is (origin, target). This is the only defined
+ # case
+ self.active_greenlet = args[1]
+ else:
+ self.active_greenlet = None
+ if self.previous_trace_function is not None:
+ self.previous_trace_function(event, args)
+
+ def __call__(self, event, args):
+ return self._trace(event, args)
+
+ def did_block_hub(self, hub):
+ # Check to see if we have blocked since the last call to this
+ # method. Returns a true value if we blocked (not in the hub),
+ # a false value if everything is fine.
+
+ # This may be called in the same thread being traced or a
+ # different thread; if a different thread, there is a race
+ # condition with this being incremented in the thread we're
+ # monitoring, but probably not often enough to lead to
+ # annoying false positives.
+
+ active_greenlet = self.active_greenlet
+ did_switch = self.greenlet_switch_counter != 0
+ self.greenlet_switch_counter = 0
+
+ if did_switch or active_greenlet is None or active_greenlet is hub:
+ # Either we switched, or nothing is running (we got a
+ # trace event we don't know about or were requested to
+ # ignore), or we spent the whole time in the hub, blocked
+ # for IO. Nothing to report.
+ return False
+ return True, active_greenlet
+
+ def ignore_current_greenlet_blocking(self):
+ # Don't pay attention to the current greenlet.
+ self.active_greenlet = None
+
+ def monitor_current_greenlet_blocking(self):
+ self.active_greenlet = getcurrent()
+
+ def did_block_hub_report(self, hub, active_greenlet, format_kwargs):
+ report = ['=' * 80,
+ '\n%s : Greenlet %s appears to be blocked' %
+ (gmctime(), active_greenlet)]
+ report.append(" Reported by %s" % (self,))
+ try:
+ frame = sys._current_frames()[hub.thread_ident]
+ except KeyError:
+ # The thread holding the hub has died. Perhaps we shouldn't
+ # even report this?
+ stack = ["Unknown: No thread found for hub %r\n" % (hub,)]
+ else:
+ stack = traceback.format_stack(frame)
+ report.append('Blocked Stack (for thread id %s):' % (hex(hub.thread_ident),))
+ report.append(''.join(stack))
+ report.append("Info:")
+ report.extend(format_run_info(**format_kwargs))
+
+ return report
+
+
+class _HubTracer(GreenletTracer):
+ def __init__(self, hub, max_blocking_time):
+ GreenletTracer.__init__(self)
+ self.max_blocking_time = max_blocking_time
+ self.hub = hub
+
+ def kill(self):
+ self.hub = None
+ GreenletTracer.kill(self)
+
+
+class HubSwitchTracer(_HubTracer):
+ # A greenlet tracer that records the last time we switched *into* the hub.
+
+ def __init__(self, hub, max_blocking_time):
+ _HubTracer.__init__(self, hub, max_blocking_time)
+ self.last_entered_hub = 0
+
+ def _trace(self, event, args):
+ GreenletTracer._trace(self, event, args)
+ if self.active_greenlet is self.hub:
+ self.last_entered_hub = perf_counter()
+
+ def did_block_hub(self, hub):
+ if perf_counter() - self.last_entered_hub > self.max_blocking_time:
+ return True, self.active_greenlet
+
+
+class MaxSwitchTracer(_HubTracer):
+ # A greenlet tracer that records the maximum time between switches,
+ # not including time spent in the hub.
+
+ def __init__(self, hub, max_blocking_time):
+ _HubTracer.__init__(self, hub, max_blocking_time)
+ self.last_switch = perf_counter()
+ self.max_blocking = 0
+
+ def _trace(self, event, args):
+ old_active = self.active_greenlet
+ GreenletTracer._trace(self, event, args)
+ if old_active is not self.hub and old_active is not None:
+ # If we're switching out of the hub, the blocking
+ # time doesn't count.
+ switched_at = perf_counter()
+ self.max_blocking = max(self.max_blocking,
+ switched_at - self.last_switch)
+
+ def did_block_hub(self, hub):
+ if self.max_blocking == 0:
+ # We never switched. Check the time now
+ self.max_blocking = perf_counter() - self.last_switch
+
+ if self.max_blocking > self.max_blocking_time:
+ return True, self.active_greenlet
+
+
+from gevent._util import import_c_accel
+import_c_accel(globals(), 'gevent.__tracer')