Skip to content

Commit 7b2719e

Browse files
committed
gh-135953: Add GIL contention markers to sampling profiler Gecko format
This commit enhances the Gecko format reporter in the sampling profiler to include markers for GIL acquisition events. The implementation adds: - GIL contention tracking in pystate and ceval_gil - New marker types in the Gecko collector for GIL wait events - External inspection support for reading GIL state - Sample collection of GIL acquisition times and thread states - Comprehensive tests for marker export functionality The markers provide visibility into GIL contention patterns, showing when threads are waiting to acquire the GIL and how long they wait.
1 parent 75b1afe commit 7b2719e

File tree

9 files changed

+661
-77
lines changed

9 files changed

+661
-77
lines changed

Include/cpython/pystate.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ struct _ts {
113113
/* Currently holds the GIL. Must be its own field to avoid data races */
114114
int holds_gil;
115115

116+
/* Currently requesting the GIL */
117+
int gil_requested;
118+
116119
int _whence;
117120

118121
/* Thread state (_Py_THREAD_ATTACHED, _Py_THREAD_DETACHED, _Py_THREAD_SUSPENDED).

Include/internal/pycore_debug_offsets.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ typedef struct _Py_DebugOffsets {
106106
uint64_t native_thread_id;
107107
uint64_t datastack_chunk;
108108
uint64_t status;
109+
uint64_t holds_gil;
110+
uint64_t gil_requested;
109111
} thread_state;
110112

111113
// InterpreterFrame offset;
@@ -273,6 +275,8 @@ typedef struct _Py_DebugOffsets {
273275
.native_thread_id = offsetof(PyThreadState, native_thread_id), \
274276
.datastack_chunk = offsetof(PyThreadState, datastack_chunk), \
275277
.status = offsetof(PyThreadState, _status), \
278+
.holds_gil = offsetof(PyThreadState, holds_gil), \
279+
.gil_requested = offsetof(PyThreadState, gil_requested), \
276280
}, \
277281
.interpreter_frame = { \
278282
.size = sizeof(_PyInterpreterFrame), \
Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
from abc import ABC, abstractmethod
22

3-
# Enums are slow
4-
THREAD_STATE_RUNNING = 0
5-
THREAD_STATE_IDLE = 1
6-
THREAD_STATE_GIL_WAIT = 2
7-
THREAD_STATE_UNKNOWN = 3
8-
9-
STATUS = {
10-
THREAD_STATE_RUNNING: "running",
11-
THREAD_STATE_IDLE: "idle",
12-
THREAD_STATE_GIL_WAIT: "gil_wait",
13-
THREAD_STATE_UNKNOWN: "unknown",
14-
}
3+
# Thread status flags
4+
try:
5+
from _remote_debugging import THREAD_STATUS_HAS_GIL, THREAD_STATUS_ON_CPU, THREAD_STATUS_UNKNOWN
6+
except ImportError:
7+
# Fallback for tests or when module is not available
8+
THREAD_STATUS_HAS_GIL = (1 << 0)
9+
THREAD_STATUS_ON_CPU = (1 << 1)
10+
THREAD_STATUS_UNKNOWN = (1 << 2)
1511

1612
class Collector(ABC):
1713
@abstractmethod
@@ -26,8 +22,14 @@ def _iter_all_frames(self, stack_frames, skip_idle=False):
2622
"""Iterate over all frame stacks from all interpreters and threads."""
2723
for interpreter_info in stack_frames:
2824
for thread_info in interpreter_info.threads:
29-
if skip_idle and thread_info.status != THREAD_STATE_RUNNING:
30-
continue
25+
# skip_idle now means: skip if thread is not actively running
26+
# A thread is "active" if it has the GIL OR is on CPU
27+
if skip_idle:
28+
status_flags = thread_info.status
29+
has_gil = bool(status_flags & THREAD_STATUS_HAS_GIL)
30+
on_cpu = bool(status_flags & THREAD_STATUS_ON_CPU)
31+
if not (has_gil or on_cpu):
32+
continue
3133
frames = thread_info.frame_info
3234
if frames:
3335
yield frames, thread_info.thread_id

0 commit comments

Comments
 (0)