Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/sentry/options/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -2506,3 +2506,12 @@
register(
"api.organization-activity.brownout-duration", default="PT1M", flags=FLAG_AUTOMATOR_MODIFIABLE
)

# Enable percentile operations in the metrics/meta endpoint in the Metrics API for the orgs in the list. This is used to
# also hide those expensive operations from view in the Metrics UI for everyone except the whitelist.
register(
"sentry-metrics.metrics-api.enable-percentile-operations-for-orgs",
type=Sequence,
default=[],
flags=FLAG_ALLOW_EMPTY | FLAG_AUTOMATOR_MODIFIABLE,
)
48 changes: 43 additions & 5 deletions src/sentry/sentry_metrics/querying/metadata/metrics.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
from collections import defaultdict
from collections.abc import Sequence
from typing import cast

from sentry import options
from sentry.models.organization import Organization
from sentry.models.project import Project
from sentry.sentry_metrics.querying.metadata.utils import METRICS_API_HIDDEN_OPERATIONS
from sentry.sentry_metrics.use_case_id_registry import UseCaseID
from sentry.snuba.metrics import parse_mri
from sentry.snuba.metrics.datasource import (
_build_metric_meta,
get_metrics_blocking_state_of_projects,
from sentry.snuba.metrics.datasource import get_metrics_blocking_state_of_projects
from sentry.snuba.metrics.naming_layer.mri import ParsedMRI, get_available_operations
from sentry.snuba.metrics.utils import (
BlockedMetric,
MetricMeta,
MetricOperationType,
MetricType,
MetricUnit,
)
from sentry.snuba.metrics.utils import BlockedMetric, MetricMeta
from sentry.snuba.metrics_layer.query import fetch_metric_mris


Expand Down Expand Up @@ -49,7 +56,9 @@ def get_metrics_meta(
# not stored.
del metrics_blocking_state[metric_mri]

metrics_metas.append(_build_metric_meta(parsed_mri, project_ids, blocking_status))
metrics_metas.append(
_build_metric_meta(organization, parsed_mri, project_ids, blocking_status)
)

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

metrics_metas.append(
_build_metric_meta(
organization,
parsed_mri,
[],
[
Expand Down Expand Up @@ -95,3 +105,31 @@ def _convert_to_mris_to_project_ids_mapping(project_id_to_mris: dict[int, list[s
mris_to_project_ids.setdefault(mri, []).append(project_id)

return mris_to_project_ids


def _build_metric_meta(
organization: Organization,
parsed_mri: ParsedMRI,
project_ids: Sequence[int],
blocking_status: Sequence[BlockedMetric],
) -> MetricMeta:
available_operations = get_available_operations(parsed_mri)

if organization.id not in options.get(
"sentry-metrics.metrics-api.enable-percentile-operations-for-orgs"
):
available_operations = [
operation
for operation in available_operations
if operation not in METRICS_API_HIDDEN_OPERATIONS
]

return MetricMeta(
type=cast(MetricType, parsed_mri.entity),
name=parsed_mri.name,
unit=cast(MetricUnit, parsed_mri.unit),
mri=parsed_mri.mri_string,
operations=cast(Sequence[MetricOperationType], available_operations),
projectIds=project_ids,
blockingStatus=blocking_status,
)
2 changes: 2 additions & 0 deletions src/sentry/sentry_metrics/querying/metadata/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from sentry.snuba.metrics import get_mri
from sentry.snuba.metrics.naming_layer.mri import is_mri

METRICS_API_HIDDEN_OPERATIONS = ["p50", "p75", "p90", "p95", "p99"]


def convert_metric_names_to_mris(metric_names: list[str]) -> list[str]:
mris: list[str] = []
Expand Down
86 changes: 2 additions & 84 deletions src/sentry/snuba/metrics/datasource.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
"""

__all__ = (
"get_metrics_meta",
"get_all_tags",
"get_tag_values",
"get_series",
Expand All @@ -27,7 +26,7 @@
from dataclasses import dataclass, replace
from datetime import datetime
from operator import itemgetter
from typing import Any, cast
from typing import Any

from snuba_sdk import And, Column, Condition, Function, Op, Or, Query, Request
from snuba_sdk.conditions import ConditionGroup
Expand All @@ -54,13 +53,7 @@
org_id_from_projects,
)
from sentry.snuba.metrics.naming_layer.mapping import get_mri
from sentry.snuba.metrics.naming_layer.mri import (
ParsedMRI,
get_available_operations,
is_custom_measurement,
is_mri,
parse_mri,
)
from sentry.snuba.metrics.naming_layer.mri import is_custom_measurement, is_mri, parse_mri
from sentry.snuba.metrics.query import Groupable, MetricField, MetricsQuery
from sentry.snuba.metrics.query_builder import (
SnubaQueryBuilder,
Expand All @@ -73,14 +66,11 @@
CUSTOM_MEASUREMENT_DATASETS,
METRIC_TYPE_TO_ENTITY,
UNALLOWED_TAGS,
BlockedMetric,
DerivedMetricParseException,
MetricDoesNotExistInIndexer,
MetricMeta,
MetricMetaWithTagKeys,
MetricOperationType,
MetricType,
MetricUnit,
NotSupportedOverCompositeEntityException,
Tag,
TagValue,
Expand Down Expand Up @@ -309,78 +299,6 @@ def get_metrics_blocking_state_of_projects(
return metrics_blocking_state_by_mri


def _build_metric_meta(
parsed_mri: ParsedMRI, project_ids: Sequence[int], blocking_status: Sequence[BlockedMetric]
) -> MetricMeta:
return MetricMeta(
type=parsed_mri.entity,
name=parsed_mri.name,
unit=cast(MetricUnit, parsed_mri.unit),
mri=parsed_mri.mri_string,
operations=cast(Sequence[MetricOperationType], get_available_operations(parsed_mri)),
projectIds=project_ids,
blockingStatus=blocking_status,
)


def get_metrics_meta(
projects: Sequence[Project],
use_case_ids: Sequence[UseCaseID],
start: datetime | None = None,
end: datetime | None = None,
) -> Sequence[MetricMeta]:
if not projects:
return []

stored_metrics = get_stored_metrics_of_projects(projects, use_case_ids, start, end)
metrics_blocking_state = (
get_metrics_blocking_state_of_projects(projects) if UseCaseID.CUSTOM in use_case_ids else {}
)

metrics_metas = []
for metric_mri, project_ids in stored_metrics.items():
parsed_mri = parse_mri(metric_mri)
if parsed_mri is None:
with sentry_sdk.push_scope() as scope:
scope.set_extra("project_ids", project_ids)
scope.set_extra("metric_mri", metric_mri)
sentry_sdk.capture_message("Invalid metric MRI detected")

continue

blocking_status = []
if (metric_blocking := metrics_blocking_state.get(metric_mri)) is not None:
blocking_status = [
BlockedMetric(isBlocked=is_blocked, blockedTags=blocked_tags, projectId=project_id)
for is_blocked, blocked_tags, project_id in metric_blocking
]
# We delete the metric so that in the next steps we can just merge the remaining blocked metrics that are
# not stored.
del metrics_blocking_state[metric_mri]

metrics_metas.append(_build_metric_meta(parsed_mri, project_ids, blocking_status))

for metric_mri, metric_blocking in metrics_blocking_state.items():
parsed_mri = parse_mri(metric_mri)
if parsed_mri is None:
continue

metrics_metas.append(
_build_metric_meta(
parsed_mri,
[],
[
BlockedMetric(
isBlocked=is_blocked, blockedTags=blocked_tags, projectId=project_id
)
for is_blocked, blocked_tags, project_id in metric_blocking
],
)
)

return metrics_metas


def get_stored_metrics_of_projects(
projects: Sequence[Project],
use_case_ids: Sequence[UseCaseID],
Expand Down
38 changes: 38 additions & 0 deletions tests/sentry/api/endpoints/test_organization_metrics_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
)
from sentry.sentry_metrics.visibility import block_metric, block_tags_of_metric
from sentry.testutils.cases import MetricsAPIBaseTestCase, OrganizationMetricsIntegrationTestCase
from sentry.testutils.helpers import override_options
from sentry.testutils.skips import requires_snuba

pytestmark = [pytest.mark.sentry_metrics, requires_snuba]
Expand Down Expand Up @@ -220,3 +221,40 @@ def test_metrics_details_for_custom_metrics(self):
assert data[2]["blockingStatus"] == [
{"isBlocked": True, "blockedTags": [], "projectId": project_1.id}
]
assert sorted(data[1]["operations"]) == [
"avg",
"count",
"histogram",
"max",
"max_timestamp",
"min",
"min_timestamp",
"sum",
]

with override_options(
{
"sentry-metrics.metrics-api.enable-percentile-operations-for-orgs": [
self.organization.id
]
},
):
response = self.get_success_response(
self.organization.slug, project=[project_1.id, project_2.id], useCase="custom"
)
data = sorted(response.data, key=lambda d: d["mri"])
assert sorted(data[1]["operations"]) == [
"avg",
"count",
"histogram",
"max",
"max_timestamp",
"min",
"min_timestamp",
"p50",
"p75",
"p90",
"p95",
"p99",
"sum",
]