https://github.com/python/cpython/commit/09ce59249960a2c382acaafc3d36128db6789fc7
commit: 09ce59249960a2c382acaafc3d36128db6789fc7
branch: main
author: Pablo Galindo Salgado <[email protected]>
committer: pablogsal <[email protected]>
date: 2026-01-02T12:09:36Z
summary:
gh-142927: Hide _sync_coordinator frames from profiler output (#143337)
files:
M Lib/profiling/sampling/collector.py
M Lib/profiling/sampling/constants.py
M Lib/profiling/sampling/gecko_collector.py
M Lib/test/test_profiling/test_sampling_profiler/test_collectors.py
diff --git a/Lib/profiling/sampling/collector.py
b/Lib/profiling/sampling/collector.py
index c70e1eefe276cc..7dc095c6c279bd 100644
--- a/Lib/profiling/sampling/collector.py
+++ b/Lib/profiling/sampling/collector.py
@@ -6,6 +6,7 @@
THREAD_STATUS_GIL_REQUESTED,
THREAD_STATUS_UNKNOWN,
THREAD_STATUS_HAS_EXCEPTION,
+ _INTERNAL_FRAME_SUFFIXES,
)
try:
@@ -42,6 +43,25 @@ def extract_lineno(location):
return 0
return location[0]
+def _is_internal_frame(frame):
+ if isinstance(frame, tuple):
+ filename = frame[0] if frame else ""
+ else:
+ filename = getattr(frame, "filename", "")
+
+ if not filename:
+ return False
+
+ return filename.endswith(_INTERNAL_FRAME_SUFFIXES)
+
+
+def filter_internal_frames(frames):
+ if not frames:
+ return frames
+
+ return [f for f in frames if not _is_internal_frame(f)]
+
+
class Collector(ABC):
@abstractmethod
def collect(self, stack_frames, timestamps_us=None):
@@ -63,6 +83,10 @@ def collect_failed_sample(self):
def export(self, filename):
"""Export collected data to a file."""
+ @staticmethod
+ def _filter_internal_frames(frames):
+ return filter_internal_frames(frames)
+
def _iter_all_frames(self, stack_frames, skip_idle=False):
for interpreter_info in stack_frames:
for thread_info in interpreter_info.threads:
@@ -76,7 +100,10 @@ def _iter_all_frames(self, stack_frames, skip_idle=False):
continue
frames = thread_info.frame_info
if frames:
- yield frames, thread_info.thread_id
+ # Filter out internal profiler frames from the bottom of
the stack
+ frames = self._filter_internal_frames(frames)
+ if frames:
+ yield frames, thread_info.thread_id
def _iter_async_frames(self, awaited_info_list):
# Phase 1: Index tasks and build parent relationships with
pre-computed selection
diff --git a/Lib/profiling/sampling/constants.py
b/Lib/profiling/sampling/constants.py
index 366cbb38365c9f..58a57700fbdd4a 100644
--- a/Lib/profiling/sampling/constants.py
+++ b/Lib/profiling/sampling/constants.py
@@ -23,6 +23,12 @@
# Format: (lineno, end_lineno, col_offset, end_col_offset)
DEFAULT_LOCATION = (0, 0, -1, -1)
+# Internal frame path suffixes to filter from profiling output
+# These are internal profiler modules that should not appear in user-facing
output
+_INTERNAL_FRAME_SUFFIXES = (
+ "_sync_coordinator.py",
+)
+
# Thread status flags
try:
from _remote_debugging import (
diff --git a/Lib/profiling/sampling/gecko_collector.py
b/Lib/profiling/sampling/gecko_collector.py
index c1c9cfcf3b93a9..28ef9b69bf7968 100644
--- a/Lib/profiling/sampling/gecko_collector.py
+++ b/Lib/profiling/sampling/gecko_collector.py
@@ -6,7 +6,7 @@
import threading
import time
-from .collector import Collector
+from .collector import Collector, filter_internal_frames
from .opcode_utils import get_opcode_info, format_opcode
try:
from _remote_debugging import THREAD_STATUS_HAS_GIL, THREAD_STATUS_ON_CPU,
THREAD_STATUS_UNKNOWN, THREAD_STATUS_GIL_REQUESTED, THREAD_STATUS_HAS_EXCEPTION
@@ -172,7 +172,7 @@ def collect(self, stack_frames, timestamps_us=None):
# Process threads
for interpreter_info in stack_frames:
for thread_info in interpreter_info.threads:
- frames = thread_info.frame_info
+ frames = filter_internal_frames(thread_info.frame_info)
tid = thread_info.thread_id
# Initialize thread if needed
diff --git a/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py
b/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py
index 13bdb4e111364c..ae336ccdb941ce 100644
--- a/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py
+++ b/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py
@@ -1823,3 +1823,131 @@ def test_gecko_collector_frame_format(self):
thread = profile["threads"][0]
# Should have recorded 3 functions
self.assertEqual(thread["funcTable"]["length"], 3)
+
+
+class TestInternalFrameFiltering(unittest.TestCase):
+ """Tests for filtering internal profiler frames from output."""
+
+ def test_filter_internal_frames(self):
+ """Test that _sync_coordinator frames are filtered from anywhere in
stack."""
+ from profiling.sampling.collector import filter_internal_frames
+
+ # Stack with _sync_coordinator in the middle (realistic scenario)
+ frames = [
+ MockFrameInfo("user_script.py", 10, "user_func"),
+ MockFrameInfo("/path/to/_sync_coordinator.py", 100, "main"),
+ MockFrameInfo("<frozen runpy>", 87, "_run_code"),
+ ]
+
+ filtered = filter_internal_frames(frames)
+ self.assertEqual(len(filtered), 2)
+ self.assertEqual(filtered[0].filename, "user_script.py")
+ self.assertEqual(filtered[1].filename, "<frozen runpy>")
+
+ def test_pstats_collector_filters_internal_frames(self):
+ """Test that PstatsCollector filters out internal frames."""
+ collector = PstatsCollector(sample_interval_usec=1000)
+
+ frames = [
+ MockInterpreterInfo(
+ 0,
+ [
+ MockThreadInfo(
+ 1,
+ [
+ MockFrameInfo("user_script.py", 10, "user_func"),
+ MockFrameInfo("/path/to/_sync_coordinator.py",
100, "main"),
+ MockFrameInfo("<frozen runpy>", 87, "_run_code"),
+ ],
+ status=THREAD_STATUS_HAS_GIL,
+ )
+ ],
+ )
+ ]
+ collector.collect(frames)
+
+ self.assertEqual(len(collector.result), 2)
+ self.assertIn(("user_script.py", 10, "user_func"), collector.result)
+ self.assertIn(("<frozen runpy>", 87, "_run_code"), collector.result)
+
+ def test_gecko_collector_filters_internal_frames(self):
+ """Test that GeckoCollector filters out internal frames."""
+ collector = GeckoCollector(sample_interval_usec=1000)
+
+ frames = [
+ MockInterpreterInfo(
+ 0,
+ [
+ MockThreadInfo(
+ 1,
+ [
+ MockFrameInfo("app.py", 50, "run"),
+ MockFrameInfo("/lib/_sync_coordinator.py", 100,
"main"),
+ ],
+ status=THREAD_STATUS_HAS_GIL,
+ )
+ ],
+ )
+ ]
+ collector.collect(frames)
+
+ profile = collector._build_profile()
+ string_array = profile["shared"]["stringArray"]
+
+ # Should not contain _sync_coordinator functions
+ for s in string_array:
+ self.assertNotIn("_sync_coordinator", s)
+
+ def test_flamegraph_collector_filters_internal_frames(self):
+ """Test that FlamegraphCollector filters out internal frames."""
+ collector = FlamegraphCollector(sample_interval_usec=1000)
+
+ frames = [
+ MockInterpreterInfo(
+ 0,
+ [
+ MockThreadInfo(
+ 1,
+ [
+ MockFrameInfo("app.py", 50, "run"),
+ MockFrameInfo("/lib/_sync_coordinator.py", 100,
"main"),
+ MockFrameInfo("<frozen runpy>", 87, "_run_code"),
+ ],
+ status=THREAD_STATUS_HAS_GIL,
+ )
+ ],
+ )
+ ]
+ collector.collect(frames)
+
+ data = collector._convert_to_flamegraph_format()
+ strings = data.get("strings", [])
+
+ for s in strings:
+ self.assertNotIn("_sync_coordinator", s)
+
+ def test_collapsed_stack_collector_filters_internal_frames(self):
+ """Test that CollapsedStackCollector filters out internal frames."""
+ collector = CollapsedStackCollector(sample_interval_usec=1000)
+
+ frames = [
+ MockInterpreterInfo(
+ 0,
+ [
+ MockThreadInfo(
+ 1,
+ [
+ MockFrameInfo("app.py", 50, "run"),
+ MockFrameInfo("/lib/_sync_coordinator.py", 100,
"main"),
+ ],
+ status=THREAD_STATUS_HAS_GIL,
+ )
+ ],
+ )
+ ]
+ collector.collect(frames)
+
+ # Check that no stack contains _sync_coordinator
+ for (call_tree, _), _ in collector.stack_counter.items():
+ for filename, _, _ in call_tree:
+ self.assertNotIn("_sync_coordinator", filename)
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]