From c33b4f2f9df0f8f6ac62180316dc9a5f9dc68b36 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Wed, 12 Aug 2026 18:48:08 +0000 Subject: [PATCH 1/3] perf(profiling): consolidate greenlet switch updates --- .../datadog/profiling/stack/__init__.pyi | 8 ++++- .../datadog/profiling/stack/_stack.pyi | 8 ++++- .../profiling/stack/include/sampler.hpp | 6 +++- .../profiling/stack/src/echion/threads.cc | 2 +- .../datadog/profiling/stack/src/sampler.cpp | 19 +++++++---- .../datadog/profiling/stack/src/stack.cpp | 16 ++++++---- ddtrace/profiling/_gevent.py | 32 ++++++++++++------- tests/profiling/collector/test_stack.py | 2 +- tests/profiling/test_gevent.py | 17 ++++++++++ 9 files changed, 82 insertions(+), 28 deletions(-) diff --git a/ddtrace/internal/datadog/profiling/stack/__init__.pyi b/ddtrace/internal/datadog/profiling/stack/__init__.pyi index b41e62760f2..ed54c03829d 100644 --- a/ddtrace/internal/datadog/profiling/stack/__init__.pyi +++ b/ddtrace/internal/datadog/profiling/stack/__init__.pyi @@ -98,7 +98,13 @@ def init_asyncio( def track_greenlet(greenlet_id: int, name: str, frame: Union[FrameType, bool, None]) -> None: ... def untrack_greenlet(greenlet_id: int) -> None: ... def link_greenlets(greenlet_id: int, parent_id: int) -> None: ... -def update_greenlet_frame(greenlet_id: int, frame: Union[FrameType, bool, None]) -> None: ... +def update_greenlet_switch( + origin_id: int, + origin_frame: Union[FrameType, bool, None], + target_id: int, + target_frame: Union[FrameType, bool, None], + update_target_frame: bool, +) -> None: ... # Module attributes is_available: bool diff --git a/ddtrace/internal/datadog/profiling/stack/_stack.pyi b/ddtrace/internal/datadog/profiling/stack/_stack.pyi index b5abfecba4a..855c0a31509 100644 --- a/ddtrace/internal/datadog/profiling/stack/_stack.pyi +++ b/ddtrace/internal/datadog/profiling/stack/_stack.pyi @@ -66,7 +66,13 @@ def init_asyncio( def track_greenlet(greenlet_id: int, name: str, frame: Union[FrameType, bool, None]) -> None: ... def untrack_greenlet(greenlet_id: int) -> None: ... def link_greenlets(greenlet_id: int, parent_id: int) -> None: ... -def update_greenlet_frame(greenlet_id: int, frame: Union[FrameType, bool, None]) -> None: ... +def update_greenlet_switch( + origin_id: int, + origin_frame: Union[FrameType, bool, None], + target_id: int, + target_frame: Union[FrameType, bool, None], + update_target_frame: bool, +) -> None: ... # Native call monitoring (sys.monitoring bridge) def start_native_monitoring() -> None: ... diff --git a/ddtrace/internal/datadog/profiling/stack/include/sampler.hpp b/ddtrace/internal/datadog/profiling/stack/include/sampler.hpp index 9d3b4490c07..043a34fad6e 100644 --- a/ddtrace/internal/datadog/profiling/stack/include/sampler.hpp +++ b/ddtrace/internal/datadog/profiling/stack/include/sampler.hpp @@ -136,7 +136,11 @@ class Sampler void track_greenlet(uintptr_t greenlet_id, TaskName name, PyObject* frame); void untrack_greenlet(uintptr_t greenlet_id); void link_greenlets(uintptr_t parent, uintptr_t child); - void update_greenlet_frame(uintptr_t greenlet_id, PyObject* frame); + void update_greenlet_switch(uintptr_t origin_id, + PyObject* origin_frame, + uintptr_t target_id, + PyObject* target_frame, + bool update_target_frame); void set_uvloop_mode(uintptr_t thread_id, bool value); // The Python side dynamically adjusts the sampling rate based on overhead, so we need to be able to update our diff --git a/ddtrace/internal/datadog/profiling/stack/src/echion/threads.cc b/ddtrace/internal/datadog/profiling/stack/src/echion/threads.cc index 980f466dcb1..c5edba9b8d5 100644 --- a/ddtrace/internal/datadog/profiling/stack/src/echion/threads.cc +++ b/ddtrace/internal/datadog/profiling/stack/src/echion/threads.cc @@ -604,7 +604,7 @@ ThreadInfo::unwind_greenlets(EchionSampler& echion, PyThreadState* tstate, unsig // Phase 1: Snapshot greenlet data under the lock. // This minimises the time we hold greenlet_info_map_lock, which is also - // acquired by update_greenlet_frame() on every greenlet switch. Holding + // acquired by update_greenlet_switch() on every greenlet switch. Holding // the lock during the expensive unwind (Phase 2) would block ALL greenlet // switches and lead to resource exhaustion (e.g. DB connection pools). { diff --git a/ddtrace/internal/datadog/profiling/stack/src/sampler.cpp b/ddtrace/internal/datadog/profiling/stack/src/sampler.cpp index c0f36cc2313..4b0213a7a9a 100644 --- a/ddtrace/internal/datadog/profiling/stack/src/sampler.cpp +++ b/ddtrace/internal/datadog/profiling/stack/src/sampler.cpp @@ -924,15 +924,22 @@ Sampler::link_greenlets(uintptr_t parent, uintptr_t child) } void -Sampler::update_greenlet_frame(uintptr_t greenlet_id, PyObject* frame) +Sampler::update_greenlet_switch(uintptr_t origin_id, + PyObject* origin_frame, + uintptr_t target_id, + PyObject* target_frame, + bool update_target_frame) { std::lock_guard guard(echion->greenlet_info_map_lock()); - auto& greenlet_info_map = echion->greenlet_info_map(); - auto entry = greenlet_info_map.find(greenlet_id); - if (entry != greenlet_info_map.end()) { - // Update the frame of the greenlet - entry->second->frame = frame; + + if (auto origin = greenlet_info_map.find(origin_id); origin != greenlet_info_map.end()) { + origin->second->frame = origin_frame; + } + if (update_target_frame) { + if (auto target = greenlet_info_map.find(target_id); target != greenlet_info_map.end()) { + target->second->frame = target_frame; + } } } diff --git a/ddtrace/internal/datadog/profiling/stack/src/stack.cpp b/ddtrace/internal/datadog/profiling/stack/src/stack.cpp index 45b1347f37b..7f717ee6e04 100644 --- a/ddtrace/internal/datadog/profiling/stack/src/stack.cpp +++ b/ddtrace/internal/datadog/profiling/stack/src/stack.cpp @@ -510,16 +510,20 @@ link_greenlets(PyObject* Py_UNUSED(m), PyObject* args) } static PyObject* -update_greenlet_frame(PyObject* Py_UNUSED(m), PyObject* args) +update_greenlet_switch(PyObject* Py_UNUSED(m), PyObject* args) { - uintptr_t greenlet_id; - PyObject* frame; + uintptr_t origin_id; + PyObject* origin_frame; + uintptr_t target_id; + PyObject* target_frame; + int update_target_frame; - if (!PyArg_ParseTuple(args, "lO", &greenlet_id, &frame)) + if (!PyArg_ParseTuple(args, "lOlOp", &origin_id, &origin_frame, &target_id, &target_frame, &update_target_frame)) return nullptr; Py_BEGIN_ALLOW_THREADS; - Sampler::get().update_greenlet_frame(greenlet_id, frame); + Sampler::get().update_greenlet_switch( + origin_id, origin_frame, target_id, target_frame, static_cast(update_target_frame)); Py_END_ALLOW_THREADS; Py_RETURN_NONE; @@ -1023,7 +1027,7 @@ static PyMethodDef stack_methods[] = { { "track_greenlet", track_greenlet, METH_VARARGS, "Map a greenlet with its identifier" }, { "untrack_greenlet", untrack_greenlet, METH_VARARGS, "Untrack a terminated greenlet" }, { "link_greenlets", link_greenlets, METH_VARARGS, "Link two greenlets" }, - { "update_greenlet_frame", update_greenlet_frame, METH_VARARGS, "Update the frame of a greenlet" }, + { "update_greenlet_switch", update_greenlet_switch, METH_VARARGS, "Record a greenlet context switch" }, { "set_adaptive_sampling", stack_set_adaptive_sampling, METH_VARARGS, "Set adaptive sampling" }, { "set_target_overhead", diff --git a/ddtrace/profiling/_gevent.py b/ddtrace/profiling/_gevent.py index d943ac186f0..f3ce8ae4f17 100644 --- a/ddtrace/profiling/_gevent.py +++ b/ddtrace/profiling/_gevent.py @@ -66,9 +66,17 @@ def track_gevent_greenlet(gl: _Greenlet, _from_tracer: bool = False) -> _Greenle return gl -def update_greenlet_frame(greenlet_id: int, frame: t.Union[FrameType, bool, None]) -> None: - _tracked_greenlets.add(greenlet_id) - stack.update_greenlet_frame(greenlet_id, frame) +def update_greenlet_switch( + origin_id: int, + origin_frame: t.Union[FrameType, bool, None], + target_id: int, + target_frame: t.Union[FrameType, bool, None], + update_target_frame: bool, +) -> None: + _tracked_greenlets.add(origin_id) + if update_target_frame: + _tracked_greenlets.add(target_id) + stack.update_greenlet_switch(origin_id, origin_frame, target_id, target_frame, update_target_frame) def greenlet_tracer(event: str, args: t.Any) -> None: @@ -97,16 +105,18 @@ def greenlet_tracer(event: str, args: t.Any) -> None: try: # If this is being set to None, it means the greenlet is likely # finished. We use the sentinel again to signal this. - update_greenlet_frame( + origin_frame = t.cast(t.Optional[FrameType], origin.gr_frame) or FRAME_NOT_SET + # We don't want to wipe the frame of a parent greenlet because + # we need to unwind it. We definitely know it is still running + # so if we allow the tracer to set its tracked frame to None, + # we won't be able to unwind the full stack. + update_greenlet_switch( origin_id, - t.cast(t.Optional[FrameType], origin.gr_frame) or FRAME_NOT_SET, + origin_frame, + target_id, + target.gr_frame, # This is None for the running target. + target_id not in _parent_greenlet_count, ) - if target_id not in _parent_greenlet_count: - # We don't want to wipe the frame of a parent greenlet because - # we need to unwind it. We definitely know it is still running - # so if we allow the tracer to set its tracked frame to None, - # we won't be able to unwind the full stack. - update_greenlet_frame(target_id, target.gr_frame) # this *is* None except KeyError: # TODO: Log missing greenlet pass diff --git a/tests/profiling/collector/test_stack.py b/tests/profiling/collector/test_stack.py index 065af199827..22b42ebf14b 100644 --- a/tests/profiling/collector/test_stack.py +++ b/tests/profiling/collector/test_stack.py @@ -1099,7 +1099,7 @@ def test_gevent_greenlet_switch_not_blocked_by_profiler() -> None: Before the fix, unwind_greenlets() held greenlet_info_map_lock for the entire stack unwinding of ALL tracked greenlets. Every greenlet switch - calls update_greenlet_frame() under the same lock, so more tracked + calls update_greenlet_switch() under the same lock, so more tracked greenlets meant longer lock hold and more switch blocking. This test measures greenlet-switch wall time with zero vs many idle diff --git a/tests/profiling/test_gevent.py b/tests/profiling/test_gevent.py index 4d93ed7e1a4..2f7f7566500 100644 --- a/tests/profiling/test_gevent.py +++ b/tests/profiling/test_gevent.py @@ -11,6 +11,23 @@ ) +@pytest.mark.skipif( + not GEVENT_COMPATIBLE_WITH_PYTHON_VERSION, + reason=f"gevent is not compatible with Python {'.'.join(map(str, tuple(sys.version_info)[:3]))}", +) +@pytest.mark.subprocess() +def test_greenlet_switch_uses_one_native_update() -> None: + from unittest.mock import patch + + from ddtrace.profiling import _gevent as _gevent_module + + with patch.object(_gevent_module, "stack") as native_stack: + _gevent_module.update_greenlet_switch(1, False, 2, None, True) + + native_stack.update_greenlet_switch.assert_called_once_with(1, False, 2, None, True) + assert {1, 2} <= _gevent_module._tracked_greenlets + + @pytest.mark.skipif( not GEVENT_COMPATIBLE_WITH_PYTHON_VERSION, reason=f"gevent is not compatible with Python {'.'.join(map(str, tuple(sys.version_info)[:3]))}", From 4f3ad005a977e7a20332ecbb2557ab89436bdfbe Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Wed, 12 Aug 2026 19:04:40 +0000 Subject: [PATCH 2/3] test(profiling): cover linked greenlet switches --- .../stack/test/test_sampling_cycle_state.cpp | 42 +++++++++++++++++++ tests/profiling/test_gevent.py | 17 -------- 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/ddtrace/internal/datadog/profiling/stack/test/test_sampling_cycle_state.cpp b/ddtrace/internal/datadog/profiling/stack/test/test_sampling_cycle_state.cpp index 8fa3bad7556..32fd3941ab6 100644 --- a/ddtrace/internal/datadog/profiling/stack/test/test_sampling_cycle_state.cpp +++ b/ddtrace/internal/datadog/profiling/stack/test/test_sampling_cycle_state.cpp @@ -1,5 +1,6 @@ #include "echion/echion_sampler.h" #include "echion/task_name.h" +#include "sampler.hpp" #include @@ -23,3 +24,44 @@ TEST(SamplingCycleState, UnwindReplacesTaskAndGreenletStacksFromPriorCycle) EXPECT_TRUE(thread.current_tasks.empty()); EXPECT_TRUE(thread.current_greenlets.empty()); } + +TEST(SamplingCycleState, GreenletSwitchPreservesLinkedParentFrame) +{ + constexpr GreenletInfo::ID child_id = 101; + constexpr GreenletInfo::ID parent_id = 102; + PyObject child_running_frame{}; + PyObject child_suspended_frame{}; + PyObject parent_suspended_frame{}; + PyObject parent_resumed_frame{}; + + Datadog::Sampler& sampler = Datadog::Sampler::get(); + EchionSampler& echion = sampler.get_echion(); + { + std::lock_guard guard(echion.greenlet_info_map_lock()); + auto& greenlets = echion.greenlet_info_map(); + greenlets.emplace( + child_id, std::make_unique(child_id, &child_running_frame, TaskName::from_literal("child"))); + greenlets.emplace( + parent_id, + std::make_unique(parent_id, &parent_suspended_frame, TaskName::from_literal("parent"))); + } + sampler.link_greenlets(parent_id, child_id); + + sampler.update_greenlet_switch(child_id, &child_suspended_frame, parent_id, &parent_resumed_frame, false); + { + std::lock_guard guard(echion.greenlet_info_map_lock()); + EXPECT_EQ(echion.greenlet_parent_map().at(child_id), parent_id); + EXPECT_EQ(echion.greenlet_info_map().at(child_id)->frame, &child_suspended_frame); + EXPECT_EQ(echion.greenlet_info_map().at(parent_id)->frame, &parent_suspended_frame); + } + + sampler.update_greenlet_switch(child_id, &child_running_frame, parent_id, &parent_resumed_frame, true); + { + std::lock_guard guard(echion.greenlet_info_map_lock()); + EXPECT_EQ(echion.greenlet_info_map().at(child_id)->frame, &child_running_frame); + EXPECT_EQ(echion.greenlet_info_map().at(parent_id)->frame, &parent_resumed_frame); + echion.greenlet_info_map().erase(child_id); + echion.greenlet_info_map().erase(parent_id); + echion.greenlet_parent_map().erase(child_id); + } +} diff --git a/tests/profiling/test_gevent.py b/tests/profiling/test_gevent.py index 2f7f7566500..4d93ed7e1a4 100644 --- a/tests/profiling/test_gevent.py +++ b/tests/profiling/test_gevent.py @@ -11,23 +11,6 @@ ) -@pytest.mark.skipif( - not GEVENT_COMPATIBLE_WITH_PYTHON_VERSION, - reason=f"gevent is not compatible with Python {'.'.join(map(str, tuple(sys.version_info)[:3]))}", -) -@pytest.mark.subprocess() -def test_greenlet_switch_uses_one_native_update() -> None: - from unittest.mock import patch - - from ddtrace.profiling import _gevent as _gevent_module - - with patch.object(_gevent_module, "stack") as native_stack: - _gevent_module.update_greenlet_switch(1, False, 2, None, True) - - native_stack.update_greenlet_switch.assert_called_once_with(1, False, 2, None, True) - assert {1, 2} <= _gevent_module._tracked_greenlets - - @pytest.mark.skipif( not GEVENT_COMPATIBLE_WITH_PYTHON_VERSION, reason=f"gevent is not compatible with Python {'.'.join(map(str, tuple(sys.version_info)[:3]))}", From 93702898f8e8da2d7d33e20fcd3666f3ebea6e0a Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Wed, 12 Aug 2026 21:54:37 +0000 Subject: [PATCH 3/3] refactor(profiling): rename greenlet switch recorder --- ddtrace/internal/datadog/profiling/stack/__init__.pyi | 2 +- ddtrace/internal/datadog/profiling/stack/_stack.pyi | 2 +- .../internal/datadog/profiling/stack/include/sampler.hpp | 2 +- .../internal/datadog/profiling/stack/src/echion/threads.cc | 2 +- ddtrace/internal/datadog/profiling/stack/src/sampler.cpp | 2 +- ddtrace/internal/datadog/profiling/stack/src/stack.cpp | 6 +++--- .../profiling/stack/test/test_sampling_cycle_state.cpp | 4 ++-- ddtrace/profiling/_gevent.py | 6 +++--- tests/profiling/collector/test_stack.py | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/ddtrace/internal/datadog/profiling/stack/__init__.pyi b/ddtrace/internal/datadog/profiling/stack/__init__.pyi index ed54c03829d..134657dfb29 100644 --- a/ddtrace/internal/datadog/profiling/stack/__init__.pyi +++ b/ddtrace/internal/datadog/profiling/stack/__init__.pyi @@ -98,7 +98,7 @@ def init_asyncio( def track_greenlet(greenlet_id: int, name: str, frame: Union[FrameType, bool, None]) -> None: ... def untrack_greenlet(greenlet_id: int) -> None: ... def link_greenlets(greenlet_id: int, parent_id: int) -> None: ... -def update_greenlet_switch( +def record_greenlet_switch( origin_id: int, origin_frame: Union[FrameType, bool, None], target_id: int, diff --git a/ddtrace/internal/datadog/profiling/stack/_stack.pyi b/ddtrace/internal/datadog/profiling/stack/_stack.pyi index 855c0a31509..9b976d01439 100644 --- a/ddtrace/internal/datadog/profiling/stack/_stack.pyi +++ b/ddtrace/internal/datadog/profiling/stack/_stack.pyi @@ -66,7 +66,7 @@ def init_asyncio( def track_greenlet(greenlet_id: int, name: str, frame: Union[FrameType, bool, None]) -> None: ... def untrack_greenlet(greenlet_id: int) -> None: ... def link_greenlets(greenlet_id: int, parent_id: int) -> None: ... -def update_greenlet_switch( +def record_greenlet_switch( origin_id: int, origin_frame: Union[FrameType, bool, None], target_id: int, diff --git a/ddtrace/internal/datadog/profiling/stack/include/sampler.hpp b/ddtrace/internal/datadog/profiling/stack/include/sampler.hpp index 043a34fad6e..363a4257d15 100644 --- a/ddtrace/internal/datadog/profiling/stack/include/sampler.hpp +++ b/ddtrace/internal/datadog/profiling/stack/include/sampler.hpp @@ -136,7 +136,7 @@ class Sampler void track_greenlet(uintptr_t greenlet_id, TaskName name, PyObject* frame); void untrack_greenlet(uintptr_t greenlet_id); void link_greenlets(uintptr_t parent, uintptr_t child); - void update_greenlet_switch(uintptr_t origin_id, + void record_greenlet_switch(uintptr_t origin_id, PyObject* origin_frame, uintptr_t target_id, PyObject* target_frame, diff --git a/ddtrace/internal/datadog/profiling/stack/src/echion/threads.cc b/ddtrace/internal/datadog/profiling/stack/src/echion/threads.cc index c5edba9b8d5..18c13599ffb 100644 --- a/ddtrace/internal/datadog/profiling/stack/src/echion/threads.cc +++ b/ddtrace/internal/datadog/profiling/stack/src/echion/threads.cc @@ -604,7 +604,7 @@ ThreadInfo::unwind_greenlets(EchionSampler& echion, PyThreadState* tstate, unsig // Phase 1: Snapshot greenlet data under the lock. // This minimises the time we hold greenlet_info_map_lock, which is also - // acquired by update_greenlet_switch() on every greenlet switch. Holding + // acquired by record_greenlet_switch() on every greenlet switch. Holding // the lock during the expensive unwind (Phase 2) would block ALL greenlet // switches and lead to resource exhaustion (e.g. DB connection pools). { diff --git a/ddtrace/internal/datadog/profiling/stack/src/sampler.cpp b/ddtrace/internal/datadog/profiling/stack/src/sampler.cpp index 4b0213a7a9a..d7df2e5574c 100644 --- a/ddtrace/internal/datadog/profiling/stack/src/sampler.cpp +++ b/ddtrace/internal/datadog/profiling/stack/src/sampler.cpp @@ -924,7 +924,7 @@ Sampler::link_greenlets(uintptr_t parent, uintptr_t child) } void -Sampler::update_greenlet_switch(uintptr_t origin_id, +Sampler::record_greenlet_switch(uintptr_t origin_id, PyObject* origin_frame, uintptr_t target_id, PyObject* target_frame, diff --git a/ddtrace/internal/datadog/profiling/stack/src/stack.cpp b/ddtrace/internal/datadog/profiling/stack/src/stack.cpp index 7f717ee6e04..68b17abbf83 100644 --- a/ddtrace/internal/datadog/profiling/stack/src/stack.cpp +++ b/ddtrace/internal/datadog/profiling/stack/src/stack.cpp @@ -510,7 +510,7 @@ link_greenlets(PyObject* Py_UNUSED(m), PyObject* args) } static PyObject* -update_greenlet_switch(PyObject* Py_UNUSED(m), PyObject* args) +record_greenlet_switch(PyObject* Py_UNUSED(m), PyObject* args) { uintptr_t origin_id; PyObject* origin_frame; @@ -522,7 +522,7 @@ update_greenlet_switch(PyObject* Py_UNUSED(m), PyObject* args) return nullptr; Py_BEGIN_ALLOW_THREADS; - Sampler::get().update_greenlet_switch( + Sampler::get().record_greenlet_switch( origin_id, origin_frame, target_id, target_frame, static_cast(update_target_frame)); Py_END_ALLOW_THREADS; @@ -1027,7 +1027,7 @@ static PyMethodDef stack_methods[] = { { "track_greenlet", track_greenlet, METH_VARARGS, "Map a greenlet with its identifier" }, { "untrack_greenlet", untrack_greenlet, METH_VARARGS, "Untrack a terminated greenlet" }, { "link_greenlets", link_greenlets, METH_VARARGS, "Link two greenlets" }, - { "update_greenlet_switch", update_greenlet_switch, METH_VARARGS, "Record a greenlet context switch" }, + { "record_greenlet_switch", record_greenlet_switch, METH_VARARGS, "Record a greenlet context switch" }, { "set_adaptive_sampling", stack_set_adaptive_sampling, METH_VARARGS, "Set adaptive sampling" }, { "set_target_overhead", diff --git a/ddtrace/internal/datadog/profiling/stack/test/test_sampling_cycle_state.cpp b/ddtrace/internal/datadog/profiling/stack/test/test_sampling_cycle_state.cpp index 32fd3941ab6..5d0419189d1 100644 --- a/ddtrace/internal/datadog/profiling/stack/test/test_sampling_cycle_state.cpp +++ b/ddtrace/internal/datadog/profiling/stack/test/test_sampling_cycle_state.cpp @@ -47,7 +47,7 @@ TEST(SamplingCycleState, GreenletSwitchPreservesLinkedParentFrame) } sampler.link_greenlets(parent_id, child_id); - sampler.update_greenlet_switch(child_id, &child_suspended_frame, parent_id, &parent_resumed_frame, false); + sampler.record_greenlet_switch(child_id, &child_suspended_frame, parent_id, &parent_resumed_frame, false); { std::lock_guard guard(echion.greenlet_info_map_lock()); EXPECT_EQ(echion.greenlet_parent_map().at(child_id), parent_id); @@ -55,7 +55,7 @@ TEST(SamplingCycleState, GreenletSwitchPreservesLinkedParentFrame) EXPECT_EQ(echion.greenlet_info_map().at(parent_id)->frame, &parent_suspended_frame); } - sampler.update_greenlet_switch(child_id, &child_running_frame, parent_id, &parent_resumed_frame, true); + sampler.record_greenlet_switch(child_id, &child_running_frame, parent_id, &parent_resumed_frame, true); { std::lock_guard guard(echion.greenlet_info_map_lock()); EXPECT_EQ(echion.greenlet_info_map().at(child_id)->frame, &child_running_frame); diff --git a/ddtrace/profiling/_gevent.py b/ddtrace/profiling/_gevent.py index f3ce8ae4f17..37dd98ceeef 100644 --- a/ddtrace/profiling/_gevent.py +++ b/ddtrace/profiling/_gevent.py @@ -66,7 +66,7 @@ def track_gevent_greenlet(gl: _Greenlet, _from_tracer: bool = False) -> _Greenle return gl -def update_greenlet_switch( +def record_greenlet_switch( origin_id: int, origin_frame: t.Union[FrameType, bool, None], target_id: int, @@ -76,7 +76,7 @@ def update_greenlet_switch( _tracked_greenlets.add(origin_id) if update_target_frame: _tracked_greenlets.add(target_id) - stack.update_greenlet_switch(origin_id, origin_frame, target_id, target_frame, update_target_frame) + stack.record_greenlet_switch(origin_id, origin_frame, target_id, target_frame, update_target_frame) def greenlet_tracer(event: str, args: t.Any) -> None: @@ -110,7 +110,7 @@ def greenlet_tracer(event: str, args: t.Any) -> None: # we need to unwind it. We definitely know it is still running # so if we allow the tracer to set its tracked frame to None, # we won't be able to unwind the full stack. - update_greenlet_switch( + record_greenlet_switch( origin_id, origin_frame, target_id, diff --git a/tests/profiling/collector/test_stack.py b/tests/profiling/collector/test_stack.py index 22b42ebf14b..ef7c35d7f65 100644 --- a/tests/profiling/collector/test_stack.py +++ b/tests/profiling/collector/test_stack.py @@ -1099,7 +1099,7 @@ def test_gevent_greenlet_switch_not_blocked_by_profiler() -> None: Before the fix, unwind_greenlets() held greenlet_info_map_lock for the entire stack unwinding of ALL tracked greenlets. Every greenlet switch - calls update_greenlet_switch() under the same lock, so more tracked + calls record_greenlet_switch() under the same lock, so more tracked greenlets meant longer lock hold and more switch blocking. This test measures greenlet-switch wall time with zero vs many idle