Skip to content

Commit cebc8c7

Browse files
authored
feat(metrics): remove percentiles from meta endpoint in Metrics API (#70934)
1 parent 3f2cb55 commit cebc8c7

File tree

5 files changed

+94
-89
lines changed

5 files changed

+94
-89
lines changed

src/sentry/options/defaults.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2528,3 +2528,12 @@
25282528
register(
25292529
"api.organization-activity.brownout-duration", default="PT1M", flags=FLAG_AUTOMATOR_MODIFIABLE
25302530
)
2531+
2532+
# Enable percentile operations in the metrics/meta endpoint in the Metrics API for the orgs in the list. This is used to
2533+
# also hide those expensive operations from view in the Metrics UI for everyone except the whitelist.
2534+
register(
2535+
"sentry-metrics.metrics-api.enable-percentile-operations-for-orgs",
2536+
type=Sequence,
2537+
default=[],
2538+
flags=FLAG_ALLOW_EMPTY | FLAG_AUTOMATOR_MODIFIABLE,
2539+
)

src/sentry/sentry_metrics/querying/metadata/metrics.py

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
from collections import defaultdict
22
from collections.abc import Sequence
3+
from typing import cast
34

5+
from sentry import options
46
from sentry.models.organization import Organization
57
from sentry.models.project import Project
8+
from sentry.sentry_metrics.querying.metadata.utils import METRICS_API_HIDDEN_OPERATIONS
69
from sentry.sentry_metrics.use_case_id_registry import UseCaseID
710
from sentry.snuba.metrics import parse_mri
8-
from sentry.snuba.metrics.datasource import (
9-
_build_metric_meta,
10-
get_metrics_blocking_state_of_projects,
11+
from sentry.snuba.metrics.datasource import get_metrics_blocking_state_of_projects
12+
from sentry.snuba.metrics.naming_layer.mri import ParsedMRI, get_available_operations
13+
from sentry.snuba.metrics.utils import (
14+
BlockedMetric,
15+
MetricMeta,
16+
MetricOperationType,
17+
MetricType,
18+
MetricUnit,
1119
)
12-
from sentry.snuba.metrics.utils import BlockedMetric, MetricMeta
1320
from sentry.snuba.metrics_layer.query import fetch_metric_mris
1421

1522

@@ -49,7 +56,9 @@ def get_metrics_meta(
4956
# not stored.
5057
del metrics_blocking_state[metric_mri]
5158

52-
metrics_metas.append(_build_metric_meta(parsed_mri, project_ids, blocking_status))
59+
metrics_metas.append(
60+
_build_metric_meta(organization, parsed_mri, project_ids, blocking_status)
61+
)
5362

5463
for metric_mri, metric_blocking in metrics_blocking_state.items():
5564
parsed_mri = parse_mri(metric_mri)
@@ -58,6 +67,7 @@ def get_metrics_meta(
5867

5968
metrics_metas.append(
6069
_build_metric_meta(
70+
organization,
6171
parsed_mri,
6272
[],
6373
[
@@ -95,3 +105,31 @@ def _convert_to_mris_to_project_ids_mapping(project_id_to_mris: dict[int, list[s
95105
mris_to_project_ids.setdefault(mri, []).append(project_id)
96106

97107
return mris_to_project_ids
108+
109+
110+
def _build_metric_meta(
111+
organization: Organization,
112+
parsed_mri: ParsedMRI,
113+
project_ids: Sequence[int],
114+
blocking_status: Sequence[BlockedMetric],
115+
) -> MetricMeta:
116+
available_operations = get_available_operations(parsed_mri)
117+
118+
if organization.id not in options.get(
119+
"sentry-metrics.metrics-api.enable-percentile-operations-for-orgs"
120+
):
121+
available_operations = [
122+
operation
123+
for operation in available_operations
124+
if operation not in METRICS_API_HIDDEN_OPERATIONS
125+
]
126+
127+
return MetricMeta(
128+
type=cast(MetricType, parsed_mri.entity),
129+
name=parsed_mri.name,
130+
unit=cast(MetricUnit, parsed_mri.unit),
131+
mri=parsed_mri.mri_string,
132+
operations=cast(Sequence[MetricOperationType], available_operations),
133+
projectIds=project_ids,
134+
blockingStatus=blocking_status,
135+
)

src/sentry/sentry_metrics/querying/metadata/utils.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from sentry.snuba.metrics import get_mri
22
from sentry.snuba.metrics.naming_layer.mri import is_mri
33

4+
METRICS_API_HIDDEN_OPERATIONS = ["p50", "p75", "p90", "p95", "p99"]
5+
46

57
def convert_metric_names_to_mris(metric_names: list[str]) -> list[str]:
68
mris: list[str] = []

src/sentry/snuba/metrics/datasource.py

Lines changed: 2 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
"""
1515

1616
__all__ = (
17-
"get_metrics_meta",
1817
"get_all_tags",
1918
"get_tag_values",
2019
"get_series",
@@ -28,7 +27,7 @@
2827
from dataclasses import dataclass, replace
2928
from datetime import datetime
3029
from operator import itemgetter
31-
from typing import Any, cast
30+
from typing import Any
3231

3332
from snuba_sdk import And, Column, Condition, Function, Op, Or, Query, Request
3433
from snuba_sdk.conditions import ConditionGroup
@@ -55,13 +54,7 @@
5554
org_id_from_projects,
5655
)
5756
from sentry.snuba.metrics.naming_layer.mapping import get_mri
58-
from sentry.snuba.metrics.naming_layer.mri import (
59-
ParsedMRI,
60-
get_available_operations,
61-
is_custom_measurement,
62-
is_mri,
63-
parse_mri,
64-
)
57+
from sentry.snuba.metrics.naming_layer.mri import is_custom_measurement, is_mri, parse_mri
6558
from sentry.snuba.metrics.query import Groupable, MetricField, MetricsQuery
6659
from sentry.snuba.metrics.query_builder import (
6760
SnubaQueryBuilder,
@@ -74,14 +67,11 @@
7467
CUSTOM_MEASUREMENT_DATASETS,
7568
METRIC_TYPE_TO_ENTITY,
7669
UNALLOWED_TAGS,
77-
BlockedMetric,
7870
DerivedMetricParseException,
7971
MetricDoesNotExistInIndexer,
8072
MetricMeta,
8173
MetricMetaWithTagKeys,
82-
MetricOperationType,
8374
MetricType,
84-
MetricUnit,
8575
NotSupportedOverCompositeEntityException,
8676
Tag,
8777
TagValue,
@@ -310,78 +300,6 @@ def get_metrics_blocking_state_of_projects(
310300
return metrics_blocking_state_by_mri
311301

312302

313-
def _build_metric_meta(
314-
parsed_mri: ParsedMRI, project_ids: Sequence[int], blocking_status: Sequence[BlockedMetric]
315-
) -> MetricMeta:
316-
return MetricMeta(
317-
type=parsed_mri.entity,
318-
name=parsed_mri.name,
319-
unit=cast(MetricUnit, parsed_mri.unit),
320-
mri=parsed_mri.mri_string,
321-
operations=cast(Sequence[MetricOperationType], get_available_operations(parsed_mri)),
322-
projectIds=project_ids,
323-
blockingStatus=blocking_status,
324-
)
325-
326-
327-
def get_metrics_meta(
328-
projects: Sequence[Project],
329-
use_case_ids: Sequence[UseCaseID],
330-
start: datetime | None = None,
331-
end: datetime | None = None,
332-
) -> Sequence[MetricMeta]:
333-
if not projects:
334-
return []
335-
336-
stored_metrics = get_stored_metrics_of_projects(projects, use_case_ids, start, end)
337-
metrics_blocking_state = (
338-
get_metrics_blocking_state_of_projects(projects) if UseCaseID.CUSTOM in use_case_ids else {}
339-
)
340-
341-
metrics_metas = []
342-
for metric_mri, project_ids in stored_metrics.items():
343-
parsed_mri = parse_mri(metric_mri)
344-
if parsed_mri is None:
345-
with sentry_sdk.push_scope() as scope:
346-
scope.set_extra("project_ids", project_ids)
347-
scope.set_extra("metric_mri", metric_mri)
348-
sentry_sdk.capture_message("Invalid metric MRI detected")
349-
350-
continue
351-
352-
blocking_status = []
353-
if (metric_blocking := metrics_blocking_state.get(metric_mri)) is not None:
354-
blocking_status = [
355-
BlockedMetric(isBlocked=is_blocked, blockedTags=blocked_tags, projectId=project_id)
356-
for is_blocked, blocked_tags, project_id in metric_blocking
357-
]
358-
# We delete the metric so that in the next steps we can just merge the remaining blocked metrics that are
359-
# not stored.
360-
del metrics_blocking_state[metric_mri]
361-
362-
metrics_metas.append(_build_metric_meta(parsed_mri, project_ids, blocking_status))
363-
364-
for metric_mri, metric_blocking in metrics_blocking_state.items():
365-
parsed_mri = parse_mri(metric_mri)
366-
if parsed_mri is None:
367-
continue
368-
369-
metrics_metas.append(
370-
_build_metric_meta(
371-
parsed_mri,
372-
[],
373-
[
374-
BlockedMetric(
375-
isBlocked=is_blocked, blockedTags=blocked_tags, projectId=project_id
376-
)
377-
for is_blocked, blocked_tags, project_id in metric_blocking
378-
],
379-
)
380-
)
381-
382-
return metrics_metas
383-
384-
385303
def get_stored_metrics_of_projects(
386304
projects: Sequence[Project],
387305
use_case_ids: Sequence[UseCaseID],

tests/sentry/api/endpoints/test_organization_metrics_details.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
)
1010
from sentry.sentry_metrics.visibility import block_metric, block_tags_of_metric
1111
from sentry.testutils.cases import MetricsAPIBaseTestCase, OrganizationMetricsIntegrationTestCase
12+
from sentry.testutils.helpers import override_options
1213
from sentry.testutils.skips import requires_snuba
1314

1415
pytestmark = [pytest.mark.sentry_metrics, requires_snuba]
@@ -220,3 +221,40 @@ def test_metrics_details_for_custom_metrics(self):
220221
assert data[2]["blockingStatus"] == [
221222
{"isBlocked": True, "blockedTags": [], "projectId": project_1.id}
222223
]
224+
assert sorted(data[1]["operations"]) == [
225+
"avg",
226+
"count",
227+
"histogram",
228+
"max",
229+
"max_timestamp",
230+
"min",
231+
"min_timestamp",
232+
"sum",
233+
]
234+
235+
with override_options(
236+
{
237+
"sentry-metrics.metrics-api.enable-percentile-operations-for-orgs": [
238+
self.organization.id
239+
]
240+
},
241+
):
242+
response = self.get_success_response(
243+
self.organization.slug, project=[project_1.id, project_2.id], useCase="custom"
244+
)
245+
data = sorted(response.data, key=lambda d: d["mri"])
246+
assert sorted(data[1]["operations"]) == [
247+
"avg",
248+
"count",
249+
"histogram",
250+
"max",
251+
"max_timestamp",
252+
"min",
253+
"min_timestamp",
254+
"p50",
255+
"p75",
256+
"p90",
257+
"p95",
258+
"p99",
259+
"sum",
260+
]

0 commit comments

Comments
 (0)