Skip to content

Commit 3b1db14

Browse files
authored
feat(profiling): support nodestore as a possible backend for span-scoped flamegraph (#52166)
This is meant to be used temporarily to build a poc since the `indexed_span` dataset is currently only available for internal projects.
1 parent 85b378c commit 3b1db14

File tree

2 files changed

+69
-16
lines changed

2 files changed

+69
-16
lines changed

src/sentry/api/endpoints/organization_profiling_profiles.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,18 @@ def get(self, request: Request, organization: Organization) -> HttpResponse:
5757

5858
span_group = request.query_params.get("spans.group", None)
5959
if span_group is not None:
60+
backend = request.query_params.get("backend", "indexed_spans")
6061
profile_ids = get_profile_ids_with_spans(
6162
organization.id,
6263
project_ids[0],
6364
params,
6465
span_group,
66+
backend,
6567
request.query_params.get("query", None),
6668
)
6769
else:
6870
profile_ids = get_profile_ids(params, request.query_params.get("query", None))
71+
6972
kwargs: Dict[str, Any] = {
7073
"method": "POST",
7174
"path": f"/organizations/{organization.id}/projects/{project_ids[0]}/flamegraph",

src/sentry/profiles/flamegraph.py

Lines changed: 66 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from datetime import datetime
22
from typing import Any, Dict, List, Optional, Tuple
33

4+
from rest_framework.exceptions import ParseError
45
from snuba_sdk import Column, Condition, Entity, Function, Op, Query, Request
56

7+
from sentry import eventstore
68
from sentry.search.events.builder import QueryBuilder
79
from sentry.search.events.types import ParamsType
810
from sentry.snuba.dataset import Dataset, EntityKey
@@ -78,6 +80,7 @@ def get_span_intervals(
7880
Condition(Column("timestamp"), Op.LT, params["end"]),
7981
],
8082
)
83+
8184
request = Request(
8285
dataset=Dataset.SpansIndexed.value,
8386
app_id="default",
@@ -87,17 +90,59 @@ def get_span_intervals(
8790
"organization_id": organization_id,
8891
},
8992
)
90-
return raw_snql_query(
93+
data = raw_snql_query(
9194
request,
9295
referrer=Referrer.API_STARFISH_PROFILE_FLAMEGRAPH.value,
9396
)["data"]
97+
spans_interval = []
98+
for row in data:
99+
start_ns = (int(datetime.fromisoformat(row["start_timestamp"]).timestamp()) * 10**9) + (
100+
row["start_ms"] * 10**6
101+
)
102+
end_ns = (int(datetime.fromisoformat(row["end_timestamp"]).timestamp()) * 10**9) + (
103+
row["end_ms"] * 10**6
104+
)
105+
interval = {}
106+
interval["transaction_id"] = row["transaction_id"]
107+
interval["start_ns"] = str(start_ns)
108+
interval["end_ns"] = str(end_ns)
109+
spans_interval.append(interval)
110+
return spans_interval
111+
112+
113+
def get_span_intervals_from_nodestore(
114+
project_id: str,
115+
span_group: str,
116+
transaction_ids: List[str],
117+
) -> List[Dict[str, Any]]:
118+
spans_interval = []
119+
for id in transaction_ids:
120+
nodestore_event = eventstore.backend.get_event_by_id(project_id, id)
121+
data = nodestore_event.data
122+
for span in data.get("spans", []):
123+
if span["hash"] == span_group:
124+
125+
start_sec, start_us = map(int, str(span["start_timestamp"]).split("."))
126+
end_sec, end_us = map(int, str(span["timestamp"]).split("."))
127+
128+
start_ns = (start_sec * 10**9) + (start_us * 10**3)
129+
end_ns = (end_sec * 10**9) + (end_us * 10**3)
130+
131+
interval = {}
132+
interval["transaction_id"] = id
133+
interval["start_ns"] = str(start_ns)
134+
interval["end_ns"] = str(end_ns)
135+
136+
spans_interval.append(interval)
137+
return spans_interval
94138

95139

96140
def get_profile_ids_with_spans(
97141
organization_id: str,
98142
project_id: str,
99143
params: ParamsType,
100144
span_group: str,
145+
backend: str,
101146
query: Optional[str] = None,
102147
):
103148
data = query_profiles_data(
@@ -115,24 +160,29 @@ def get_profile_ids_with_spans(
115160
row["id"]: (row["profile.id"], []) for row in data
116161
}
117162

118-
data = get_span_intervals(
119-
project_id,
120-
span_group,
121-
list(transaction_to_prof.keys()),
122-
organization_id,
123-
params,
124-
)
125-
126-
for row in data:
127-
start_ns = (int(datetime.fromisoformat(row["start_timestamp"]).timestamp()) * 10**9) + (
128-
row["start_ms"] * 10**6
163+
if backend == "nodestore":
164+
data = get_span_intervals_from_nodestore(
165+
project_id,
166+
span_group,
167+
list(transaction_to_prof.keys()),
129168
)
130-
end_ns = (int(datetime.fromisoformat(row["end_timestamp"]).timestamp()) * 10**9) + (
131-
row["end_ms"] * 10**6
169+
elif backend == "indexed_spans":
170+
data = get_span_intervals(
171+
project_id,
172+
span_group,
173+
list(transaction_to_prof.keys()),
174+
organization_id,
175+
params,
176+
)
177+
else:
178+
raise ParseError(
179+
detail="Backend not supported: choose between 'indexed_spans' or 'nodestore'."
132180
)
133-
transaction_id = row["transaction_id"]
134181

135-
transaction_to_prof[transaction_id][1].append({"start": str(start_ns), "end": str(end_ns)})
182+
for row in data:
183+
transaction_to_prof[row["transaction_id"]][1].append(
184+
{"start": row["start_ns"], "end": row["end_ns"]}
185+
)
136186

137187
profile_ids = [tup[0] for tup in transaction_to_prof.values()]
138188
spans = [tup[1] for tup in transaction_to_prof.values()]

0 commit comments

Comments
 (0)