mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +00:00
gh-138122: Add thread status statistics to flamegraph profiler (#141900)
Co-authored-by: ivonastojanovic <80911834+ivonastojanovic@users.noreply.github.com>
This commit is contained in:
parent
db098a475a
commit
ea51e745c7
8 changed files with 777 additions and 21 deletions
|
|
@ -19,7 +19,6 @@ def export(self, filename):
|
|||
"""Export collected data to a file."""
|
||||
|
||||
def _iter_all_frames(self, stack_frames, skip_idle=False):
|
||||
"""Iterate over all frame stacks from all interpreters and threads."""
|
||||
for interpreter_info in stack_frames:
|
||||
for thread_info in interpreter_info.threads:
|
||||
# skip_idle now means: skip if thread is not actively running
|
||||
|
|
@ -33,3 +32,83 @@ def _iter_all_frames(self, stack_frames, skip_idle=False):
|
|||
frames = thread_info.frame_info
|
||||
if frames:
|
||||
yield frames, thread_info.thread_id
|
||||
|
||||
def _is_gc_frame(self, frame):
|
||||
if isinstance(frame, tuple):
|
||||
funcname = frame[2] if len(frame) >= 3 else ""
|
||||
else:
|
||||
funcname = getattr(frame, "funcname", "")
|
||||
|
||||
return "<GC>" in funcname or "gc_collect" in funcname
|
||||
|
||||
def _collect_thread_status_stats(self, stack_frames):
|
||||
"""Collect aggregate and per-thread status statistics from a sample.
|
||||
|
||||
Returns:
|
||||
tuple: (aggregate_status_counts, has_gc_frame, per_thread_stats)
|
||||
- aggregate_status_counts: dict with has_gil, on_cpu, etc.
|
||||
- has_gc_frame: bool indicating if any thread has GC frames
|
||||
- per_thread_stats: dict mapping thread_id to per-thread counts
|
||||
"""
|
||||
status_counts = {
|
||||
"has_gil": 0,
|
||||
"on_cpu": 0,
|
||||
"gil_requested": 0,
|
||||
"unknown": 0,
|
||||
"total": 0,
|
||||
}
|
||||
has_gc_frame = False
|
||||
per_thread_stats = {}
|
||||
|
||||
for interpreter_info in stack_frames:
|
||||
threads = getattr(interpreter_info, "threads", [])
|
||||
for thread_info in threads:
|
||||
status_counts["total"] += 1
|
||||
|
||||
# Track thread status using bit flags
|
||||
status_flags = getattr(thread_info, "status", 0)
|
||||
|
||||
if status_flags & THREAD_STATUS_HAS_GIL:
|
||||
status_counts["has_gil"] += 1
|
||||
if status_flags & THREAD_STATUS_ON_CPU:
|
||||
status_counts["on_cpu"] += 1
|
||||
if status_flags & THREAD_STATUS_GIL_REQUESTED:
|
||||
status_counts["gil_requested"] += 1
|
||||
if status_flags & THREAD_STATUS_UNKNOWN:
|
||||
status_counts["unknown"] += 1
|
||||
|
||||
# Track per-thread statistics
|
||||
thread_id = getattr(thread_info, "thread_id", None)
|
||||
if thread_id is not None:
|
||||
if thread_id not in per_thread_stats:
|
||||
per_thread_stats[thread_id] = {
|
||||
"has_gil": 0,
|
||||
"on_cpu": 0,
|
||||
"gil_requested": 0,
|
||||
"unknown": 0,
|
||||
"total": 0,
|
||||
"gc_samples": 0,
|
||||
}
|
||||
|
||||
thread_stats = per_thread_stats[thread_id]
|
||||
thread_stats["total"] += 1
|
||||
|
||||
if status_flags & THREAD_STATUS_HAS_GIL:
|
||||
thread_stats["has_gil"] += 1
|
||||
if status_flags & THREAD_STATUS_ON_CPU:
|
||||
thread_stats["on_cpu"] += 1
|
||||
if status_flags & THREAD_STATUS_GIL_REQUESTED:
|
||||
thread_stats["gil_requested"] += 1
|
||||
if status_flags & THREAD_STATUS_UNKNOWN:
|
||||
thread_stats["unknown"] += 1
|
||||
|
||||
# Check for GC frames in this thread
|
||||
frames = getattr(thread_info, "frame_info", None)
|
||||
if frames:
|
||||
for frame in frames:
|
||||
if self._is_gc_frame(frame):
|
||||
thread_stats["gc_samples"] += 1
|
||||
has_gc_frame = True
|
||||
break
|
||||
|
||||
return status_counts, has_gc_frame, per_thread_stats
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue