Skip to content

Commit cb01154

Browse files
fix(profiling): correctly detect on-cpu tasks
1 parent 559d111 commit cb01154

File tree

4 files changed

+39
-1
lines changed

4 files changed

+39
-1
lines changed

ddtrace/internal/datadog/profiling/stack_v2/echion/echion/long.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include <Python.h>
77
#if PY_VERSION_HEX >= 0x030c0000
8+
#define Py_BUILD_CORE
89
#include <internal/pycore_long.h>
910
// Note: Even if use the right PYLONG_BITS_IN_DIGIT that is specified in the
1011
// Python we use to build echion, it can be different from the Python that is

ddtrace/internal/datadog/profiling/stack_v2/echion/echion/tasks.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
#pragma once
66

7+
#include <optional>
8+
79
#define PY_SSIZE_T_CLEAN
810
#include <Python.h>
911
#include <weakrefobject.h>
@@ -176,6 +178,7 @@ class TaskInfo
176178

177179
// Information to reconstruct the async stack as best as we can
178180
TaskInfo::Ptr waiter = nullptr;
181+
std::optional<bool> is_on_cpu_ = std::nullopt;
179182

180183
[[nodiscard]] static Result<TaskInfo::Ptr> create(TaskObj*);
181184
TaskInfo(PyObject* origin, PyObject* loop, GenInfo::Ptr coro, StringTable::Key name, TaskInfo::Ptr waiter)
@@ -189,6 +192,24 @@ class TaskInfo
189192

190193
[[nodiscard]] static Result<TaskInfo::Ptr> current(PyObject*);
191194
inline size_t unwind(FrameStack&);
195+
196+
// Check if any coroutine in the chain is currently running (on CPU)
197+
inline bool is_on_cpu()
198+
{
199+
if (is_on_cpu_.has_value()) {
200+
return *is_on_cpu_;
201+
}
202+
203+
for (auto coroutine = this->coro.get(); coroutine != nullptr; coroutine = coroutine->await.get()) {
204+
if (coroutine->is_running) {
205+
is_on_cpu_ = true;
206+
return true;
207+
}
208+
}
209+
210+
is_on_cpu_.emplace(false);
211+
return false;
212+
}
192213
};
193214

194215
inline std::unordered_map<PyObject*, PyObject*> task_link_map;

ddtrace/internal/datadog/profiling/stack_v2/echion/echion/threads.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,19 @@ ThreadInfo::unwind_tasks()
252252
}
253253
}
254254

255+
// Only one Task can be on CPU at a time.
256+
// Since determining if a task is on CPU is somewhat costly, we
257+
// stop checking if Tasks are on CPU after seeing the first one.
258+
bool on_cpu_task_seen = false;
255259
for (auto& leaf_task : leaf_tasks) {
256-
bool on_cpu = leaf_task.get().coro->is_running;
260+
bool on_cpu = false;
261+
if (!on_cpu_task_seen) {
262+
on_cpu = leaf_task.get().is_on_cpu();
263+
if (on_cpu) {
264+
on_cpu_task_seen = true;
265+
}
266+
}
267+
257268
auto stack_info = std::make_unique<StackInfo>(leaf_task.get().name, on_cpu);
258269
auto& stack = stack_info->stack;
259270
for (auto current_task = leaf_task;;) {
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
fixes:
2+
- |
3+
profiling: This fix improves the detection of on-CPU asyncio Tasks. Previously, the Profiler would only
4+
consider a Task as running if its coroutine was running. The Profiler now recursively checks if any coroutine in the
5+
await chain of the Task's coroutine is running.

0 commit comments

Comments
 (0)