From 080c625cb74a9b3b83dd2c33b3765c916aa12988 Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Sat, 15 Nov 2025 15:16:18 -0800 Subject: [PATCH] fix(explorer): fix profile thread selection --- src/sentry/seer/explorer/tools.py | 6 ++++++ src/sentry/seer/explorer/utils.py | 15 ++++++--------- tests/sentry/seer/autofix/test_autofix.py | 8 +++++--- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/sentry/seer/explorer/tools.py b/src/sentry/seer/explorer/tools.py index 5c945a98eb6afd..387de9cd4af04d 100644 --- a/src/sentry/seer/explorer/tools.py +++ b/src/sentry/seer/explorer/tools.py @@ -582,6 +582,11 @@ def rpc_get_profile_flamegraph(profile_id: str, organization_id: int) -> dict[st ) return {"error": "Failed to generate execution tree from profile data"} + # Extract thread_id from profile data + profile = profile_data.get("profile") or profile_data.get("chunk", {}).get("profile") + samples = profile.get("samples", []) if profile else [] + thread_id = str(samples[0]["thread_id"]) if samples else None + return { "execution_tree": execution_tree, "metadata": { @@ -590,6 +595,7 @@ def rpc_get_profile_flamegraph(profile_id: str, organization_id: int) -> dict[st "is_continuous": is_continuous, "start_ts": min_start_ts, "end_ts": max_end_ts, + "thread_id": thread_id, }, } diff --git a/src/sentry/seer/explorer/utils.py b/src/sentry/seer/explorer/utils.py index 967fba28d3e87f..e221c5913c084a 100644 --- a/src/sentry/seer/explorer/utils.py +++ b/src/sentry/seer/explorer/utils.py @@ -68,15 +68,12 @@ def _convert_profile_to_execution_tree(profile_data: dict) -> list[dict]: if not all([frames, stacks, samples]): return [] - # Find the MainThread ID - thread_metadata = profile.get("thread_metadata", {}) or {} - main_thread_id = next( - (key for key, value in thread_metadata.items() if value.get("name") == "MainThread"), None - ) - if ( - not main_thread_id and len(thread_metadata) == 1 - ): # if there is only one thread, use that as the main thread - main_thread_id = list(thread_metadata.keys())[0] + # Use the thread ID from the first sample as this should be the one with the most application logic + if samples: + main_thread_id = str(samples[0]["thread_id"]) + else: + # No samples - can't determine thread + main_thread_id = None def _get_elapsed_since_start_ns( sample: dict[str, Any], all_samples: list[dict[str, Any]] diff --git a/tests/sentry/seer/autofix/test_autofix.py b/tests/sentry/seer/autofix/test_autofix.py index 41494c7fe8e082..663f348702a1e0 100644 --- a/tests/sentry/seer/autofix/test_autofix.py +++ b/tests/sentry/seer/autofix/test_autofix.py @@ -81,7 +81,7 @@ def test_convert_profile_to_execution_tree(self) -> None: assert len(child["children"]) == 0 # No children for the last in_app frame def test_convert_profile_to_execution_tree_non_main_thread(self) -> None: - """Test that non-MainThread samples are excluded from execution tree""" + """Test that the first sample's thread is used (even if not MainThread)""" profile_data = { "profile": { "frames": [ @@ -101,8 +101,10 @@ def test_convert_profile_to_execution_tree_non_main_thread(self) -> None: execution_tree = _convert_profile_to_execution_tree(profile_data) - # Should be empty since no MainThread samples - assert len(execution_tree) == 0 + # Should include the worker thread since it's the first sample's thread + assert len(execution_tree) == 1 + assert execution_tree[0]["function"] == "worker" + assert execution_tree[0]["filename"] == "worker.py" def test_convert_profile_to_execution_tree_merges_duplicate_frames(self) -> None: """Test that duplicate frames in different samples are merged correctly"""