2025-07-10 18:44:24 +01:00
|
|
|
from abc import ABC, abstractmethod
|
|
|
|
|
|
2025-11-17 12:46:26 +00:00
|
|
|
# Thread status flags
|
|
|
|
|
try:
|
|
|
|
|
from _remote_debugging import THREAD_STATUS_HAS_GIL, THREAD_STATUS_ON_CPU, THREAD_STATUS_UNKNOWN, THREAD_STATUS_GIL_REQUESTED
|
|
|
|
|
except ImportError:
|
|
|
|
|
# Fallback for tests or when module is not available
|
|
|
|
|
THREAD_STATUS_HAS_GIL = (1 << 0)
|
|
|
|
|
THREAD_STATUS_ON_CPU = (1 << 1)
|
|
|
|
|
THREAD_STATUS_UNKNOWN = (1 << 2)
|
|
|
|
|
THREAD_STATUS_GIL_REQUESTED = (1 << 3)
|
2025-07-10 18:44:24 +01:00
|
|
|
|
|
|
|
|
class Collector(ABC):
|
|
|
|
|
@abstractmethod
|
|
|
|
|
def collect(self, stack_frames):
|
|
|
|
|
"""Collect profiling data from stack frames."""
|
|
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
|
def export(self, filename):
|
|
|
|
|
"""Export collected data to a file."""
|
2025-09-09 00:41:08 +01:00
|
|
|
|
2025-09-19 19:17:28 +01:00
|
|
|
def _iter_all_frames(self, stack_frames, skip_idle=False):
|
2025-09-09 00:41:08 +01:00
|
|
|
"""Iterate over all frame stacks from all interpreters and threads."""
|
|
|
|
|
for interpreter_info in stack_frames:
|
|
|
|
|
for thread_info in interpreter_info.threads:
|
2025-11-17 12:46:26 +00:00
|
|
|
# skip_idle now means: skip if thread is not actively running
|
|
|
|
|
# A thread is "active" if it has the GIL OR is on CPU
|
|
|
|
|
if skip_idle:
|
|
|
|
|
status_flags = thread_info.status
|
|
|
|
|
has_gil = bool(status_flags & THREAD_STATUS_HAS_GIL)
|
|
|
|
|
on_cpu = bool(status_flags & THREAD_STATUS_ON_CPU)
|
|
|
|
|
if not (has_gil or on_cpu):
|
|
|
|
|
continue
|
2025-09-09 00:41:08 +01:00
|
|
|
frames = thread_info.frame_info
|
|
|
|
|
if frames:
|
2025-09-25 15:34:57 +01:00
|
|
|
yield frames, thread_info.thread_id
|