Skip to content

Commit b980904

Browse files
feat(insights): Adds spm function to discover metrics and metrics layer datasets (#75661)
Adding `spm` to the metrics + metrics_layer dataset in discover, to be used for insights based alerts.
1 parent 449f651 commit b980904

File tree

3 files changed

+100
-2
lines changed

3 files changed

+100
-2
lines changed

src/sentry/search/events/datasets/metrics.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ def field_alias_converter(self) -> Mapping[str, Callable[[str], SelectType]]:
6666

6767
def resolve_metric(self, value: str) -> int:
6868
metric_id = self.builder.resolve_metric_index(constants.METRICS_MAP.get(value, value))
69+
if metric_id is None:
70+
metric_id = self.builder.resolve_metric_index(
71+
constants.SPAN_METRICS_MAP.get(value, value)
72+
)
6973
if metric_id is None:
7074
# Maybe this is a custom measurment?
7175
for measurement in self.builder.custom_measurement_map:
@@ -784,6 +788,12 @@ def function_converter(self) -> Mapping[str, fields.MetricsFunction]:
784788
optional_args=[fields.IntervalDefault("interval", 1, None)],
785789
default_result_type="rate",
786790
),
791+
fields.MetricsFunction(
792+
"spm",
793+
snql_distribution=self._resolve_spm,
794+
optional_args=[fields.IntervalDefault("interval", 1, None)],
795+
default_result_type="rate",
796+
),
787797
fields.MetricsFunction(
788798
"eps",
789799
snql_distribution=self._resolve_eps,
@@ -1973,6 +1983,14 @@ def _resolve_epm(
19731983
) -> SelectType:
19741984
return self._resolve_rate(60, args, alias, extra_condition)
19751985

1986+
def _resolve_spm(
1987+
self,
1988+
args: Mapping[str, str | Column | SelectType | int | float],
1989+
alias: str | None = None,
1990+
extra_condition: Function | None = None,
1991+
) -> SelectType:
1992+
return self._resolve_rate(60, args, alias, extra_condition, "span.self_time")
1993+
19761994
def _resolve_eps(
19771995
self,
19781996
args: Mapping[str, str | Column | SelectType | int | float],
@@ -1987,12 +2005,13 @@ def _resolve_rate(
19872005
args: Mapping[str, str | Column | SelectType | int | float],
19882006
alias: str | None = None,
19892007
extra_condition: Function | None = None,
2008+
metric: str | None = "transaction.duration",
19902009
) -> SelectType:
19912010
base_condition = Function(
19922011
"equals",
19932012
[
19942013
Column("metric_id"),
1995-
self.resolve_metric("transaction.duration"),
2014+
self.resolve_metric(metric),
19962015
],
19972016
)
19982017
if extra_condition:

src/sentry/search/events/datasets/metrics_layer.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from sentry.search.events.datasets import field_aliases, filter_aliases, function_aliases
1313
from sentry.search.events.datasets.metrics import MetricsDatasetConfig
1414
from sentry.search.events.types import SelectType, WhereType
15-
from sentry.snuba.metrics.naming_layer.mri import SessionMRI, TransactionMRI
15+
from sentry.snuba.metrics.naming_layer.mri import SessionMRI, SpanMRI, TransactionMRI
1616
from sentry.utils.numbers import format_grouped_length
1717

1818

@@ -401,6 +401,20 @@ def function_converter(self) -> Mapping[str, fields.MetricsFunction]:
401401
optional_args=[fields.IntervalDefault("interval", 1, None)],
402402
default_result_type="rate",
403403
),
404+
fields.MetricsFunction(
405+
"spm",
406+
snql_metric_layer=lambda args, alias: Function(
407+
"rate",
408+
[
409+
Column(SpanMRI.SELF_TIME.value),
410+
args["interval"],
411+
60,
412+
],
413+
alias,
414+
),
415+
optional_args=[fields.IntervalDefault("interval", 1, None)],
416+
default_result_type="rate",
417+
),
404418
fields.MetricsFunction(
405419
"eps",
406420
snql_metric_layer=lambda args, alias: Function(

tests/snuba/api/endpoints/test_organization_events_stats_mep.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,39 @@ def test_throughput_epm_hour_rollup(self):
7878
for test in zip(event_counts, rows):
7979
assert test[1][1][0]["count"] == test[0] / (3600.0 / 60.0)
8080

81+
@pytest.mark.querybuilder
82+
def test_throughput_spm_hour_rollup(self):
83+
# Each of these denotes how many events to create in each hour
84+
event_counts = [6, 0, 6, 3, 0, 3]
85+
for hour, count in enumerate(event_counts):
86+
for minute in range(count):
87+
self.store_span_metric(
88+
1,
89+
timestamp=self.day_ago + timedelta(hours=hour, minutes=minute),
90+
)
91+
92+
response = self.do_request(
93+
data={
94+
"start": iso_format(self.day_ago),
95+
"end": iso_format(self.day_ago + timedelta(hours=6)),
96+
"interval": "1h",
97+
"yAxis": "spm()",
98+
"project": self.project.id,
99+
"dataset": "metrics",
100+
**self.additional_params,
101+
},
102+
)
103+
104+
assert response.status_code == 200, response.content
105+
data = response.data["data"]
106+
assert len(data) == 6
107+
assert response.data["meta"]["dataset"] == "metrics"
108+
109+
rows = data[0:6]
110+
111+
for test in zip(event_counts, rows):
112+
assert test[1][1][0]["count"] == test[0] / (3600.0 / 60.0)
113+
81114
def test_throughput_epm_day_rollup(self):
82115
# Each of these denotes how many events to create in each minute
83116
event_counts = [6, 0, 6, 3, 0, 3]
@@ -1152,6 +1185,38 @@ def test_gauge_custom_metric(self):
11521185
for (_, value), expected_value in zip(count, [40, 80, 120, 160, 200, 240]):
11531186
assert value[0]["count"] == expected_value # type: ignore[index]
11541187

1188+
@pytest.mark.querybuilder
1189+
def test_throughput_spm_hour_rollup(self):
1190+
# Each of these denotes how many events to create in each hour
1191+
event_counts = [6, 0, 6, 3, 0, 3]
1192+
for hour, count in enumerate(event_counts):
1193+
for minute in range(count):
1194+
self.store_span_metric(
1195+
1,
1196+
timestamp=self.day_ago + timedelta(hours=hour, minutes=minute),
1197+
)
1198+
1199+
response = self.do_request(
1200+
data={
1201+
"start": iso_format(self.day_ago),
1202+
"end": iso_format(self.day_ago + timedelta(hours=6)),
1203+
"interval": "1h",
1204+
"yAxis": "spm()",
1205+
"project": self.project.id,
1206+
"dataset": "metrics",
1207+
},
1208+
)
1209+
1210+
assert response.status_code == 200, response.content
1211+
data = response.data["data"]
1212+
assert len(data) == 6
1213+
assert response.data["meta"]["dataset"] == "metrics"
1214+
1215+
rows = data[0:6]
1216+
1217+
for test in zip(event_counts, rows):
1218+
assert test[1][1][0]["count"] == test[0] / (3600.0 / 60.0)
1219+
11551220

11561221
class OrganizationEventsStatsMetricsEnhancedPerformanceEndpointTestWithOnDemandWidgets(
11571222
MetricsEnhancedPerformanceTestCase

0 commit comments

Comments
 (0)