Skip to content

Commit 0f21a00

Browse files
Julien DanjouKyle-Verhoog
andauthored
feat(collector): do not collect gevent Hub task (#2906)
This implements a customer greenlet tracer that do not capture switches to the gevent Hub between 2 tasks. This does not prevent (yet) Hub tracebacks to be reported, but the collected Hub stack trace will be assigned to the previously switched task. Co-authored-by: Kyle Verhoog <[email protected]>
1 parent adb926e commit 0f21a00

File tree

3 files changed

+54
-3
lines changed

3 files changed

+54
-3
lines changed

ddtrace/profiling/collector/_task.pyx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,17 @@ try:
99
except ImportError:
1010
_gevent_tracer = None
1111
else:
12+
class DDGreenletTracer(gevent._tracer.GreenletTracer):
13+
def _trace(self, event, args):
14+
# Do not trace gevent Hub: the Hub is a greenlet but we want to know the latest active greenlet *before*
15+
# the application yielded back to the Hub. There's no point showing the Hub most of the time to the users as
16+
# that does not give any information about user code.
17+
if not isinstance(args[1], gevent.hub.Hub):
18+
gevent._tracer.GreenletTracer._trace(self, event, args)
19+
1220
# NOTE: bold assumption: this module is always imported by the MainThread.
1321
# A GreenletTracer is local to the thread instantiating it and we assume this is run by the MainThread.
14-
_gevent_tracer = gevent._tracer.GreenletTracer()
22+
_gevent_tracer = DDGreenletTracer()
1523

1624

1725
cpdef get_task(thread_id):

tests/profiling/collector/test_stack.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import pytest
1010
import six
1111

12+
from ddtrace.internal import compat
1213
from ddtrace.internal import nogevent
1314
from ddtrace.profiling import collector
1415
from ddtrace.profiling import event as event_mod
@@ -328,7 +329,7 @@ def test_exception_collection():
328329
assert e.sampling_period > 0
329330
assert e.thread_id == nogevent.thread_get_ident()
330331
assert e.thread_name == "MainThread"
331-
assert e.frames == [(__file__, 322, "test_exception_collection")]
332+
assert e.frames == [(__file__, 323, "test_exception_collection")]
332333
assert e.nframes == 1
333334
assert e.exc_type == ValueError
334335

@@ -351,7 +352,7 @@ def test_exception_collection_trace(tracer):
351352
assert e.sampling_period > 0
352353
assert e.thread_id == nogevent.thread_get_ident()
353354
assert e.thread_name == "MainThread"
354-
assert e.frames == [(__file__, 345, "test_exception_collection_trace")]
355+
assert e.frames == [(__file__, 346, "test_exception_collection_trace")]
355356
assert e.nframes == 1
356357
assert e.exc_type == ValueError
357358
assert e.span_id == span.span_id
@@ -594,3 +595,34 @@ def test_thread_time_cache():
594595
assert set(tt._get_last_thread_time().keys()) == set(
595596
(pthread_id, _threading.get_thread_native_id(pthread_id)) for pthread_id in threads
596597
)
598+
599+
600+
@pytest.mark.skipif(_task._gevent_tracer is None, reason="gevent tasks not supported")
601+
def test_collect_gevent_thread_hub():
602+
r = recorder.Recorder()
603+
s = stack.StackCollector(r, ignore_profiler=True)
604+
605+
# Start some greenthreads: they do nothing we just keep switching between them.
606+
def _nothing():
607+
for _ in range(10000):
608+
# Do nothing and just switch to another greenlet
609+
time.sleep(0)
610+
611+
threads = []
612+
with s:
613+
for i in range(100):
614+
t = threading.Thread(target=_nothing, name="TestThread %d" % i)
615+
t.start()
616+
threads.append(t)
617+
for t in threads:
618+
t.join()
619+
620+
main_thread_found = False
621+
for event in r.events[stack.StackSampleEvent]:
622+
if event.thread_name == compat.main_thread.ident and event.task_name is None:
623+
pytest.fail("Task with no name detected, is it the Hub?")
624+
else:
625+
main_thread_found = True
626+
627+
# Make sure we did at least one check
628+
assert main_thread_found
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from ddtrace import compat
2+
from ddtrace.internal import nogevent
3+
from ddtrace.profiling.collector import _task
4+
5+
6+
def test_get_task_main():
7+
# type: (...) -> None
8+
if _task._gevent_tracer is None:
9+
assert _task.get_task(nogevent.main_thread_id) == (None, None)
10+
else:
11+
assert _task.get_task(nogevent.main_thread_id) == (compat.main_thread.ident, "MainThread")

0 commit comments

Comments
 (0)