Commit 4231079a authored by Jason Madden's avatar Jason Madden

Move the greenlet tracers to their own file and compile with cython.

Unoptimized still makes them 25% faster:

+-------------------+-----------------+------------------------------+
| Benchmark         | 37_bench_tracer | 37_bench_tracer_first_cython |
+===================+=================+==============================+
| trivial tracer    | 792 ns          | 786 ns: 1.01x faster (-1%)   |
+-------------------+-----------------+------------------------------+
| monitor tracer    | 1.62 us         | 1.24 us: 1.31x faster (-24%) |
+-------------------+-----------------+------------------------------+
| max switch tracer | 3.06 us         | 1.89 us: 1.62x faster (-38%) |
+-------------------+-----------------+------------------------------+
| hub switch tracer | 2.16 us         | 1.68 us: 1.29x faster (-22%) |
+-------------------+-----------------+------------------------------+
parent 596773a1
...@@ -15,6 +15,7 @@ src/gevent/_imap.c ...@@ -15,6 +15,7 @@ src/gevent/_imap.c
src/gevent/event.c src/gevent/event.c
src/gevent/_hub_local.c src/gevent/_hub_local.c
src/gevent/_waiter.c src/gevent/_waiter.c
src/gevent/_tracer.c
src/gevent/queue.c src/gevent/queue.c
src/gevent/_hub_primitives.c src/gevent/_hub_primitives.c
src/gevent/_greenlet_primitives.c src/gevent/_greenlet_primitives.c
......
...@@ -13,7 +13,7 @@ import perf ...@@ -13,7 +13,7 @@ import perf
import greenlet import greenlet
import gevent import gevent
from gevent import _monitor as monitor from gevent import _tracer as monitor
N = 1000 N = 1000
......
...@@ -129,6 +129,10 @@ GLT_PRIMITIVES = Extension(name="gevent.__greenlet_primitives", ...@@ -129,6 +129,10 @@ GLT_PRIMITIVES = Extension(name="gevent.__greenlet_primitives",
depends=['src/gevent/__greenlet_primitives.pxd'], depends=['src/gevent/__greenlet_primitives.pxd'],
include_dirs=include_dirs) include_dirs=include_dirs)
TRACER = Extension(name="gevent.__tracer",
sources=["src/gevent/_tracer.py"],
depends=['src/gevent/__tracer.pxd'],
include_dirs=include_dirs)
_to_cythonize = [ _to_cythonize = [
...@@ -137,6 +141,7 @@ _to_cythonize = [ ...@@ -137,6 +141,7 @@ _to_cythonize = [
HUB_LOCAL, HUB_LOCAL,
WAITER, WAITER,
GREENLET, GREENLET,
TRACER,
SEMAPHORE, SEMAPHORE,
LOCAL, LOCAL,
...@@ -161,6 +166,7 @@ EXT_MODULES = [ ...@@ -161,6 +166,7 @@ EXT_MODULES = [
WAITER, WAITER,
HUB_PRIMITIVES, HUB_PRIMITIVES,
GLT_PRIMITIVES, GLT_PRIMITIVES,
TRACER,
] ]
LIBEV_CFFI_MODULE = 'src/gevent/libev/_corecffi_build.py:ffi' LIBEV_CFFI_MODULE = 'src/gevent/libev/_corecffi_build.py:ffi'
...@@ -254,9 +260,13 @@ if PYPY: ...@@ -254,9 +260,13 @@ if PYPY:
EXT_MODULES.remove(GLT_PRIMITIVES) EXT_MODULES.remove(GLT_PRIMITIVES)
_to_cythonize.remove(GLT_PRIMITIVES) _to_cythonize.remove(GLT_PRIMITIVES)
EXT_MODULES.remove(HUB_PRIMITIVES) EXT_MODULES.remove(HUB_PRIMITIVES)
_to_cythonize.remove(HUB_PRIMITIVES) _to_cythonize.remove(HUB_PRIMITIVES)
EXT_MODULES.remove(TRACER)
_to_cythonize.remove(TRACER)
for mod in _to_cythonize: for mod in _to_cythonize:
EXT_MODULES.remove(mod) EXT_MODULES.remove(mod)
......
...@@ -3,16 +3,13 @@ from __future__ import print_function, absolute_import, division ...@@ -3,16 +3,13 @@ from __future__ import print_function, absolute_import, division
import os import os
import sys import sys
import traceback
from weakref import ref as wref from weakref import ref as wref
from greenlet import settrace
from greenlet import getcurrent from greenlet import getcurrent
from gevent import config as GEVENT_CONFIG from gevent import config as GEVENT_CONFIG
from gevent.monkey import get_original from gevent.monkey import get_original
from gevent.util import format_run_info
from gevent.events import notify from gevent.events import notify
from gevent.events import EventLoopBlocked from gevent.events import EventLoopBlocked
from gevent.events import MemoryUsageThresholdExceeded from gevent.events import MemoryUsageThresholdExceeded
...@@ -20,9 +17,10 @@ from gevent.events import MemoryUsageUnderThreshold ...@@ -20,9 +17,10 @@ from gevent.events import MemoryUsageUnderThreshold
from gevent.events import IPeriodicMonitorThread from gevent.events import IPeriodicMonitorThread
from gevent.events import implementer from gevent.events import implementer
from gevent._tracer import GreenletTracer
from gevent._compat import thread_mod_name from gevent._compat import thread_mod_name
from gevent._compat import perf_counter from gevent._compat import perf_counter
from gevent._util import gmctime
__all__ = [ __all__ = [
...@@ -39,148 +37,6 @@ class MonitorWarning(RuntimeWarning): ...@@ -39,148 +37,6 @@ class MonitorWarning(RuntimeWarning):
"""The type of warnings we emit.""" """The type of warnings we emit."""
class GreenletTracer(object):
# A counter, incremented by the greenlet trace function
# we install on every greenlet switch. This is reset when the
# periodic monitoring thread runs.
greenlet_switch_counter = 0
# The greenlet last switched to.
active_greenlet = None
# The trace function that was previously installed,
# if any.
previous_trace_function = None
def __init__(self):
prev_trace = settrace(self)
self.previous_trace_function = prev_trace
def kill(self): # pylint:disable=method-hidden
# Must be called in the monitored thread.
settrace(self.previous_trace_function)
self.previous_trace_function = None
# Become a no-op
self.kill = lambda: None
def __call__(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 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): # pylint:disable=method-hidden
self.hub = None
GreenletTracer.kill(self)
class HubSwitchTracer(_HubTracer):
# A greenlet tracer that records the last time we switched *into* the hub.
last_entered_hub = 0
def __call__(self, event, args):
GreenletTracer.__call__(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.
max_blocking = 0
def __init__(self, hub, max_blocking_time):
_HubTracer.__init__(self, hub, max_blocking_time)
self.last_switch = perf_counter()
def __call__(self, event, args):
old_active = self.active_greenlet
GreenletTracer.__call__(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
class _MonitorEntry(object): class _MonitorEntry(object):
__slots__ = ('function', 'period', 'last_run_time') __slots__ = ('function', 'period', 'last_run_time')
......
# Copyright (c) 2018 gevent. See LICENSE for details.
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',
]
class GreenletTracer(object):
# A counter, incremented by the greenlet trace function
# we install on every greenlet switch. This is reset when the
# periodic monitoring thread runs.
greenlet_switch_counter = 0
# The greenlet last switched to.
active_greenlet = None
# The trace function that was previously installed,
# if any.
previous_trace_function = None
def __init__(self):
prev_trace = settrace(self)
self.previous_trace_function = prev_trace
def kill(self): # pylint:disable=method-hidden
# Must be called in the monitored thread.
settrace(self.previous_trace_function)
self.previous_trace_function = None
# Become a no-op
self.kill = lambda: None
def __call__(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 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): # pylint:disable=method-hidden
self.hub = None
GreenletTracer.kill(self)
class HubSwitchTracer(_HubTracer):
# A greenlet tracer that records the last time we switched *into* the hub.
last_entered_hub = 0
def __call__(self, event, args):
GreenletTracer.__call__(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.
max_blocking = 0
def __init__(self, hub, max_blocking_time):
_HubTracer.__init__(self, hub, max_blocking_time)
self.last_switch = perf_counter()
def __call__(self, event, args):
old_active = self.active_greenlet
GreenletTracer.__call__(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')
...@@ -557,7 +557,7 @@ class assert_switches(object): ...@@ -557,7 +557,7 @@ class assert_switches(object):
def __enter__(self): def __enter__(self):
from gevent import get_hub from gevent import get_hub
from gevent import _monitor from gevent import _tracer
self.hub = hub = get_hub() self.hub = hub = get_hub()
...@@ -565,11 +565,11 @@ class assert_switches(object): ...@@ -565,11 +565,11 @@ class assert_switches(object):
# installed by the monitoring thread, if there is one. # installed by the monitoring thread, if there is one.
# As it is, we will chain trace calls back to it. # As it is, we will chain trace calls back to it.
if not self.max_blocking_time: if not self.max_blocking_time:
self.tracer = _monitor.GreenletTracer() self.tracer = _tracer.GreenletTracer()
elif self.hub_only: elif self.hub_only:
self.tracer = _monitor.HubSwitchTracer(hub, self.max_blocking_time) self.tracer = _tracer.HubSwitchTracer(hub, self.max_blocking_time)
else: else:
self.tracer = _monitor.MaxSwitchTracer(hub, self.max_blocking_time) self.tracer = _tracer.MaxSwitchTracer(hub, self.max_blocking_time)
self.tracer.monitor_current_greenlet_blocking() self.tracer.monitor_current_greenlet_blocking()
return self return self
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment