Browse Source

feat(trace-explorer): Return project for trace root (#69782)

This includes the project for the trace root. It does so while applying
a small optimization. Instead of reading the transaction name again
while aggregating the traces, we just look at the transactions returned
by the breakdown. Also fixes a bug where the breakdowns were being
aggregated instead of returning raw events.
Tony Xiao 10 months ago
parent
commit
dffd63186e

+ 19 - 11
src/sentry/api/endpoints/organization_traces.py

@@ -1,6 +1,6 @@
 import dataclasses
 from collections import defaultdict
-from collections.abc import Mapping
+from collections.abc import Mapping, MutableMapping
 from datetime import datetime, timedelta
 from typing import Any, Literal, TypedDict, cast
 
@@ -38,6 +38,7 @@ class TraceResult(TypedDict):
     numErrors: int
     numOccurrences: int
     numSpans: int
+    project: str | None
     name: str | None
     duration: int
     start: int
@@ -222,13 +223,21 @@ class OrganizationTracesEndpoint(OrganizationEventsV2EndpointBase):
                 sentry_sdk.capture_exception(e)
                 breakdowns = defaultdict(list)
 
+            names_by_trace: MutableMapping[str, tuple[str, str]] = {}
+            for row in traces_breakdowns_results["data"]:
+                # The underlying column is a Nullable(UInt64) but we write a default of 0 to it.
+                # So make sure to handle both in case something changes.
+                if not row["parent_span"] or int(row["parent_span"], 16) == 0:
+                    names_by_trace[row["trace"]] = (row["project"], row["transaction"])
+
             traces: list[TraceResult] = [
                 {
                     "trace": row["trace"],
                     "numErrors": errors_by_trace.get(row["trace"], 0),
                     "numOccurrences": occurrences_by_trace.get(row["trace"], 0),
                     "numSpans": row["count()"],
-                    "name": row["trace_name()"],
+                    "project": names_by_trace.get(row["trace"], (None, None))[0],
+                    "name": names_by_trace.get(row["trace"], (None, None))[1],
                     "duration": row["last_seen()"] - row["first_seen()"],
                     "start": row["first_seen()"],
                     "end": row["last_seen()"],
@@ -350,17 +359,17 @@ class OrganizationTracesEndpoint(OrganizationEventsV2EndpointBase):
                 selected_columns=[
                     "trace",
                     "project",
+                    "parent_span",
                     "transaction",
-                    "first_seen()",
-                    "last_seen()",
+                    "precise.start_ts",
+                    "precise.finish_ts",
                 ],
-                orderby=["first_seen()", "last_seen()"],
+                orderby=["precise.start_ts", "precise.finish_ts"],
                 # limit the number of segments we fetch per trace so a single
                 # large trace does not result in the rest being blank
                 limitby=("trace", int(10_000 / len(trace_ids))),
                 limit=10_000,
                 config=QueryBuilderConfig(
-                    functions_acl=["trace_name", "first_seen", "last_seen"],
                     transform_alias_to_input_format=True,
                 ),
             )
@@ -385,13 +394,12 @@ class OrganizationTracesEndpoint(OrganizationEventsV2EndpointBase):
                     "trace",
                     "count()",
                     # TODO: count if of matching spans
-                    "trace_name()",
                     "first_seen()",
                     "last_seen()",
                 ],
                 limit=len(trace_ids),
                 config=QueryBuilderConfig(
-                    functions_acl=["trace_name", "first_seen", "last_seen"],
+                    functions_acl=["first_seen", "last_seen"],
                     transform_alias_to_input_format=True,
                 ),
             )
@@ -590,10 +598,10 @@ def process_breakdowns(data, traces_range):
         trace = row["trace"]
 
         cur: TraceInterval = {
-            "project": row["project"],
-            "start": row["first_seen()"],
-            "end": row["last_seen()"],
             "kind": "project",
+            "project": row["project"],
+            "start": int(row["precise.start_ts"] * 1000),
+            "end": int(row["precise.finish_ts"] * 1000),
         }
 
         # Clear the stack of any intervals that end before the current interval

+ 65 - 63
tests/sentry/api/endpoints/test_organization_traces.py

@@ -266,6 +266,7 @@ class OrganizationTracesEndpointTest(BaseSpansTestCase, APITestCase):
                         "numErrors": 1,
                         "numOccurrences": 0,
                         "numSpans": 4,
+                        "project": project_1.slug,
                         "name": "foo",
                         "duration": 60_100,
                         "start": int(timestamps[0].timestamp() * 1000),
@@ -314,6 +315,7 @@ class OrganizationTracesEndpointTest(BaseSpansTestCase, APITestCase):
                         "numErrors": 0,
                         "numOccurrences": 0,
                         "numSpans": 3,
+                        "project": project_1.slug,
                         "name": "bar",
                         "duration": 90_123,
                         "start": int(timestamps[4].timestamp() * 1000),
@@ -353,7 +355,7 @@ class OrganizationTracesEndpointTest(BaseSpansTestCase, APITestCase):
                         ],
                     },
                 ],
-                key=lambda trace: trace["trace"],  # type: ignore[arg-type, return-value]
+                key=lambda trace: trace["trace"],
             )
 
 
@@ -366,8 +368,8 @@ class OrganizationTracesEndpointTest(BaseSpansTestCase, APITestCase):
                     "trace": "a" * 32,
                     "project": "foo",
                     "transaction": "foo1",
-                    "first_seen()": 0,
-                    "last_seen()": 100,
+                    "precise.start_ts": 0,
+                    "precise.finish_ts": 0.1,
                 },
             ],
             {"a" * 32: (0, 100)},
@@ -390,15 +392,15 @@ class OrganizationTracesEndpointTest(BaseSpansTestCase, APITestCase):
                     "trace": "a" * 32,
                     "project": "foo",
                     "transaction": "foo1",
-                    "first_seen()": 0,
-                    "last_seen()": 100,
+                    "precise.start_ts": 0,
+                    "precise.finish_ts": 0.1,
                 },
                 {
                     "trace": "a" * 32,
                     "project": "bar",
                     "transaction": "bar1",
-                    "first_seen()": 25,
-                    "last_seen()": 75,
+                    "precise.start_ts": 0.025,
+                    "precise.finish_ts": 0.075,
                 },
             ],
             {"a" * 32: (0, 100)},
@@ -427,22 +429,22 @@ class OrganizationTracesEndpointTest(BaseSpansTestCase, APITestCase):
                     "trace": "a" * 32,
                     "project": "foo",
                     "transaction": "foo1",
-                    "first_seen()": 0,
-                    "last_seen()": 50,
+                    "precise.start_ts": 0,
+                    "precise.finish_ts": 0.05,
                 },
                 {
                     "trace": "a" * 32,
                     "project": "bar",
                     "transaction": "bar1",
-                    "first_seen()": 25,
-                    "last_seen()": 75,
+                    "precise.start_ts": 0.025,
+                    "precise.finish_ts": 0.075,
                 },
                 {
                     "trace": "a" * 32,
                     "project": "baz",
                     "transaction": "baz1",
-                    "first_seen()": 50,
-                    "last_seen()": 100,
+                    "precise.start_ts": 0.05,
+                    "precise.finish_ts": 0.1,
                 },
             ],
             {"a" * 32: (0, 100)},
@@ -477,15 +479,15 @@ class OrganizationTracesEndpointTest(BaseSpansTestCase, APITestCase):
                     "trace": "a" * 32,
                     "project": "foo",
                     "transaction": "foo1",
-                    "first_seen()": 0,
-                    "last_seen()": 25,
+                    "precise.start_ts": 0,
+                    "precise.finish_ts": 0.025,
                 },
                 {
                     "trace": "a" * 32,
                     "project": "bar",
                     "transaction": "bar1",
-                    "first_seen()": 50,
-                    "last_seen()": 75,
+                    "precise.start_ts": 0.05,
+                    "precise.finish_ts": 0.075,
                 },
             ],
             {"a" * 32: (0, 75)},
@@ -520,15 +522,15 @@ class OrganizationTracesEndpointTest(BaseSpansTestCase, APITestCase):
                     "trace": "a" * 32,
                     "project": "foo",
                     "transaction": "foo1",
-                    "first_seen()": 0,
-                    "last_seen()": 100,
+                    "precise.start_ts": 0,
+                    "precise.finish_ts": 0.1,
                 },
                 {
                     "trace": "a" * 32,
                     "project": "foo",
                     "transaction": "foo2",
-                    "first_seen()": 25,
-                    "last_seen()": 75,
+                    "precise.start_ts": 0.025,
+                    "precise.finish_ts": 0.075,
                 },
             ],
             {"a" * 32: (0, 100)},
@@ -551,15 +553,15 @@ class OrganizationTracesEndpointTest(BaseSpansTestCase, APITestCase):
                     "trace": "a" * 32,
                     "project": "foo",
                     "transaction": "foo1",
-                    "first_seen()": 0,
-                    "last_seen()": 75,
+                    "precise.start_ts": 0,
+                    "precise.finish_ts": 0.075,
                 },
                 {
                     "trace": "a" * 32,
                     "project": "foo",
                     "transaction": "foo2",
-                    "first_seen()": 25,
-                    "last_seen()": 100,
+                    "precise.start_ts": 0.025,
+                    "precise.finish_ts": 0.1,
                 },
             ],
             {"a" * 32: (0, 100)},
@@ -582,15 +584,15 @@ class OrganizationTracesEndpointTest(BaseSpansTestCase, APITestCase):
                     "trace": "a" * 32,
                     "project": "foo",
                     "transaction": "foo1",
-                    "first_seen()": 0,
-                    "last_seen()": 25,
+                    "precise.start_ts": 0,
+                    "precise.finish_ts": 0.025,
                 },
                 {
                     "trace": "a" * 32,
                     "project": "foo",
                     "transaction": "foo2",
-                    "first_seen()": 50,
-                    "last_seen()": 75,
+                    "precise.start_ts": 0.05,
+                    "precise.finish_ts": 0.075,
                 },
             ],
             {"a" * 32: (0, 75)},
@@ -625,22 +627,22 @@ class OrganizationTracesEndpointTest(BaseSpansTestCase, APITestCase):
                     "trace": "a" * 32,
                     "project": "foo",
                     "transaction": "foo1",
-                    "first_seen()": 0,
-                    "last_seen()": 100,
+                    "precise.start_ts": 0,
+                    "precise.finish_ts": 0.1,
                 },
                 {
                     "trace": "a" * 32,
                     "project": "bar",
                     "transaction": "bar1",
-                    "first_seen()": 20,
-                    "last_seen()": 80,
+                    "precise.start_ts": 0.02,
+                    "precise.finish_ts": 0.08,
                 },
                 {
                     "trace": "a" * 32,
                     "project": "baz",
                     "transaction": "baz1",
-                    "first_seen()": 40,
-                    "last_seen()": 60,
+                    "precise.start_ts": 0.04,
+                    "precise.finish_ts": 0.06,
                 },
             ],
             {"a" * 32: (0, 100)},
@@ -675,22 +677,22 @@ class OrganizationTracesEndpointTest(BaseSpansTestCase, APITestCase):
                     "trace": "a" * 32,
                     "project": "foo",
                     "transaction": "foo1",
-                    "first_seen()": 0,
-                    "last_seen()": 100,
+                    "precise.start_ts": 0,
+                    "precise.finish_ts": 0.1,
                 },
                 {
                     "trace": "a" * 32,
                     "project": "bar",
                     "transaction": "bar1",
-                    "first_seen()": 25,
-                    "last_seen()": 50,
+                    "precise.start_ts": 0.025,
+                    "precise.finish_ts": 0.05,
                 },
                 {
                     "trace": "a" * 32,
                     "project": "baz",
                     "transaction": "baz1",
-                    "first_seen()": 50,
-                    "last_seen()": 75,
+                    "precise.start_ts": 0.05,
+                    "precise.finish_ts": 0.075,
                 },
             ],
             {"a" * 32: (0, 100)},
@@ -725,22 +727,22 @@ class OrganizationTracesEndpointTest(BaseSpansTestCase, APITestCase):
                     "trace": "a" * 32,
                     "project": "foo",
                     "transaction": "foo1",
-                    "first_seen()": 0,
-                    "last_seen()": 50,
+                    "precise.start_ts": 0,
+                    "precise.finish_ts": 0.05,
                 },
                 {
                     "trace": "a" * 32,
                     "project": "bar",
                     "transaction": "bar1",
-                    "first_seen()": 20,
-                    "last_seen()": 30,
+                    "precise.start_ts": 0.02,
+                    "precise.finish_ts": 0.03,
                 },
                 {
                     "trace": "a" * 32,
                     "project": "baz",
                     "transaction": "baz1",
-                    "first_seen()": 50,
-                    "last_seen()": 75,
+                    "precise.start_ts": 0.05,
+                    "precise.finish_ts": 0.075,
                 },
             ],
             {"a" * 32: (0, 75)},
@@ -775,22 +777,22 @@ class OrganizationTracesEndpointTest(BaseSpansTestCase, APITestCase):
                     "trace": "a" * 32,
                     "project": "foo",
                     "transaction": "foo1",
-                    "first_seen()": 0,
-                    "last_seen()": 50,
+                    "precise.start_ts": 0,
+                    "precise.finish_ts": 0.05,
                 },
                 {
                     "trace": "a" * 32,
                     "project": "bar",
                     "transaction": "bar1",
-                    "first_seen()": 20,
-                    "last_seen()": 30,
+                    "precise.start_ts": 0.02,
+                    "precise.finish_ts": 0.03,
                 },
                 {
                     "trace": "a" * 32,
                     "project": "baz",
                     "transaction": "baz1",
-                    "first_seen()": 40,
-                    "last_seen()": 60,
+                    "precise.start_ts": 0.04,
+                    "precise.finish_ts": 0.06,
                 },
             ],
             {"a" * 32: (0, 60)},
@@ -825,22 +827,22 @@ class OrganizationTracesEndpointTest(BaseSpansTestCase, APITestCase):
                     "trace": "a" * 32,
                     "project": "foo",
                     "transaction": "foo1",
-                    "first_seen()": 0,
-                    "last_seen()": 50,
+                    "precise.start_ts": 0,
+                    "precise.finish_ts": 0.05,
                 },
                 {
                     "trace": "a" * 32,
                     "project": "bar",
                     "transaction": "bar1",
-                    "first_seen()": 10,
-                    "last_seen()": 20,
+                    "precise.start_ts": 0.01,
+                    "precise.finish_ts": 0.02,
                 },
                 {
                     "trace": "a" * 32,
                     "project": "foo",
                     "transaction": "foo1",
-                    "first_seen()": 30,
-                    "last_seen()": 40,
+                    "precise.start_ts": 0.03,
+                    "precise.finish_ts": 0.04,
                 },
             ],
             {"a" * 32: (0, 50)},
@@ -869,8 +871,8 @@ class OrganizationTracesEndpointTest(BaseSpansTestCase, APITestCase):
                     "trace": "a" * 32,
                     "project": "foo",
                     "transaction": "foo1",
-                    "first_seen()": 0,
-                    "last_seen()": 100,
+                    "precise.start_ts": 0,
+                    "precise.finish_ts": 0.1,
                 },
             ],
             {"a" * 32: (0, 50)},
@@ -893,8 +895,8 @@ class OrganizationTracesEndpointTest(BaseSpansTestCase, APITestCase):
                     "trace": "a" * 32,
                     "project": "foo",
                     "transaction": "foo1",
-                    "first_seen()": 0,
-                    "last_seen()": 50,
+                    "precise.start_ts": 0,
+                    "precise.finish_ts": 0.05,
                 },
             ],
             {"a" * 32: (0, 100)},