# 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): # XXX: On Python 2 with greenlet 1.0a1, '%s' formatting a greenlet # results in a unicode object. This is a bug in greenlet, I think. # https://github.com/python-greenlet/greenlet/issues/218 report = ['=' * 80, '\n%s : Greenlet %s appears to be blocked' % (gmctime(), str(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')