Skip to content

Commit 8be3e0c

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 8be3e0c

File tree

9 files changed

+671
-77
lines changed

9 files changed

+671
-77
lines changed

Include/cpython/pystate.h

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

116+
#ifndef Py_GIL_DISABLED
117+
/* Currently requesting the GIL (waiting for it). Must be its own field to avoid data races */
118+
int gil_requested;
119+
#endif
120+
116121
int _whence;
117122

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

Include/internal/pycore_debug_offsets.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,14 @@ extern "C" {
5555
# define _Py_Debug_code_object_co_tlbc offsetof(PyCodeObject, co_tlbc)
5656
# define _Py_Debug_interpreter_frame_tlbc_index offsetof(_PyInterpreterFrame, tlbc_index)
5757
# define _Py_Debug_interpreter_state_tlbc_generation offsetof(PyInterpreterState, tlbc_indices.tlbc_generation)
58+
# define _Py_Debug_thread_state_gil_requested 0
5859
#else
5960
# define _Py_Debug_gilruntimestate_enabled 0
6061
# define _Py_Debug_Free_Threaded 0
6162
# define _Py_Debug_code_object_co_tlbc 0
6263
# define _Py_Debug_interpreter_frame_tlbc_index 0
6364
# define _Py_Debug_interpreter_state_tlbc_generation 0
65+
# define _Py_Debug_thread_state_gil_requested offsetof(PyThreadState, gil_requested)
6466
#endif
6567

6668

@@ -106,6 +108,8 @@ typedef struct _Py_DebugOffsets {
106108
uint64_t native_thread_id;
107109
uint64_t datastack_chunk;
108110
uint64_t status;
111+
uint64_t holds_gil;
112+
uint64_t gil_requested;
109113
} thread_state;
110114

111115
// InterpreterFrame offset;
@@ -273,6 +277,8 @@ typedef struct _Py_DebugOffsets {
273277
.native_thread_id = offsetof(PyThreadState, native_thread_id), \
274278
.datastack_chunk = offsetof(PyThreadState, datastack_chunk), \
275279
.status = offsetof(PyThreadState, _status), \
280+
.holds_gil = offsetof(PyThreadState, holds_gil), \
281+
.gil_requested = _Py_Debug_thread_state_gil_requested, \
276282
}, \
277283
.interpreter_frame = { \
278284
.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)