-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
feat(tracemetrics): Parse metric from aggregations #103102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| from sentry_protos.snuba.v1.trace_item_filter_pb2 import AndFilter, TraceItemFilter | ||
|
|
||
|
|
||
| def and_trace_item_filters( | ||
| *trace_item_filters: TraceItemFilter | None, | ||
| ) -> TraceItemFilter | None: | ||
| filters: list[TraceItemFilter] = [f for f in trace_item_filters if f is not None] | ||
| if not filters: | ||
| return None | ||
|
|
||
| if len(filters) == 1: | ||
| return filters[0] | ||
|
|
||
| return TraceItemFilter(and_filter=AndFilter(filters=filters)) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| from dataclasses import dataclass | ||
| from typing import Literal, cast | ||
| from typing import cast | ||
|
|
||
| from rest_framework.request import Request | ||
| from sentry_protos.snuba.v1.trace_item_attribute_pb2 import AttributeKey, AttributeValue | ||
|
|
@@ -9,10 +9,18 @@ | |
| TraceItemFilter, | ||
| ) | ||
|
|
||
| from sentry.exceptions import InvalidSearchQuery | ||
| from sentry.search.eap.columns import ResolvedMetricAggregate | ||
| from sentry.search.eap.resolver import SearchResolver | ||
| from sentry.search.eap.types import SearchResolverConfig | ||
| from sentry.search.eap.types import MetricType, SearchResolverConfig | ||
| from sentry.search.events import fields | ||
|
|
||
| MetricType = Literal["counter", "gauge", "distribution"] | ||
|
|
||
| @dataclass(frozen=True, kw_only=True) | ||
| class Metric: | ||
| metric_name: str | ||
| metric_type: MetricType | ||
| metric_unit: str | None | ||
|
|
||
|
|
||
| @dataclass(frozen=True, kw_only=True) | ||
|
|
@@ -21,50 +29,123 @@ class TraceMetricsSearchResolverConfig(SearchResolverConfig): | |
| metric_type: MetricType | None | ||
| metric_unit: str | None | ||
|
|
||
| def extra_conditions(self, search_resolver: SearchResolver) -> TraceItemFilter | None: | ||
| if not self.metric_name or not self.metric_type: | ||
| def extra_conditions( | ||
| self, | ||
| search_resolver: SearchResolver, | ||
| selected_columns: list[str] | None, | ||
| equations: list[str] | None, | ||
| ) -> TraceItemFilter | None: | ||
| # use the metric from the config first if it exists | ||
| if extra_conditions := self._extra_conditions_from_metric(search_resolver): | ||
| return extra_conditions | ||
|
|
||
| # then try to parse metric from the aggregations | ||
| if extra_conditions := self._extra_conditions_from_columns( | ||
| search_resolver, selected_columns, equations | ||
| ): | ||
| return extra_conditions | ||
|
|
||
| return None | ||
|
|
||
| def _extra_conditions_from_columns( | ||
| self, | ||
| search_resolver: SearchResolver, | ||
| selected_columns: list[str] | None, | ||
| equations: list[str] | None, | ||
| ) -> TraceItemFilter | None: | ||
| selected_metrics: set[Metric] = set() | ||
|
|
||
| if selected_columns: | ||
| stripped_columns = [column.strip() for column in selected_columns] | ||
| for column in stripped_columns: | ||
| match = fields.is_function(column) | ||
| if not match: | ||
| continue | ||
|
|
||
| resolved_function, _ = search_resolver.resolve_function(column) | ||
| if not isinstance(resolved_function, ResolvedMetricAggregate): | ||
| continue | ||
|
|
||
| if not resolved_function.metric_name or not resolved_function.metric_type: | ||
| continue | ||
|
|
||
| metric = Metric( | ||
| metric_name=resolved_function.metric_name, | ||
| metric_type=resolved_function.metric_type, | ||
| metric_unit=resolved_function.metric_unit, | ||
| ) | ||
| selected_metrics.add(metric) | ||
|
|
||
| if not selected_metrics: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Equations Ignore Metric AggregationsThe |
||
| return None | ||
|
|
||
| metric_name, _ = search_resolver.resolve_column("metric.name") | ||
| if not isinstance(metric_name.proto_definition, AttributeKey): | ||
| raise ValueError("Unable to resolve metric.name") | ||
| if len(selected_metrics) > 1: | ||
| raise InvalidSearchQuery("Cannot aggregate multiple metrics in 1 query.") | ||
|
Comment on lines
+82
to
+83
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. two questions
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| metric_type, _ = search_resolver.resolve_column("metric.type") | ||
| if not isinstance(metric_type.proto_definition, AttributeKey): | ||
| raise ValueError("Unable to resolve metric.type") | ||
| selected_metric = selected_metrics.pop() | ||
|
|
||
| filters = [ | ||
| TraceItemFilter( | ||
| comparison_filter=ComparisonFilter( | ||
| key=metric_name.proto_definition, | ||
| op=ComparisonFilter.OP_EQUALS, | ||
| value=AttributeValue(val_str=self.metric_name), | ||
| ) | ||
| ), | ||
| return get_metric_filter(search_resolver, selected_metric) | ||
|
|
||
| def _extra_conditions_from_metric( | ||
| self, | ||
| search_resolver: SearchResolver, | ||
| ) -> TraceItemFilter | None: | ||
| if not self.metric_name or not self.metric_type: | ||
| return None | ||
|
|
||
| metric = Metric( | ||
| metric_name=self.metric_name, | ||
| metric_type=self.metric_type, | ||
| metric_unit=self.metric_unit, | ||
| ) | ||
|
|
||
| return get_metric_filter(search_resolver, metric) | ||
|
|
||
|
|
||
| def get_metric_filter( | ||
| search_resolver: SearchResolver, | ||
| metric: Metric, | ||
| ) -> TraceItemFilter: | ||
| metric_name, _ = search_resolver.resolve_column("metric.name") | ||
| if not isinstance(metric_name.proto_definition, AttributeKey): | ||
| raise ValueError("Unable to resolve metric.name") | ||
|
|
||
| metric_type, _ = search_resolver.resolve_column("metric.type") | ||
| if not isinstance(metric_type.proto_definition, AttributeKey): | ||
| raise ValueError("Unable to resolve metric.type") | ||
|
|
||
| filters = [ | ||
| TraceItemFilter( | ||
| comparison_filter=ComparisonFilter( | ||
| key=metric_name.proto_definition, | ||
| op=ComparisonFilter.OP_EQUALS, | ||
| value=AttributeValue(val_str=metric.metric_name), | ||
| ) | ||
| ), | ||
| TraceItemFilter( | ||
| comparison_filter=ComparisonFilter( | ||
| key=metric_type.proto_definition, | ||
| op=ComparisonFilter.OP_EQUALS, | ||
| value=AttributeValue(val_str=metric.metric_type), | ||
| ) | ||
| ), | ||
| ] | ||
|
|
||
| if metric.metric_unit: | ||
| metric_unit, _ = search_resolver.resolve_column("metric.unit") | ||
| if not isinstance(metric_unit.proto_definition, AttributeKey): | ||
| raise ValueError("Unable to resolve metric.unit") | ||
| filters.append( | ||
| TraceItemFilter( | ||
| comparison_filter=ComparisonFilter( | ||
| key=metric_type.proto_definition, | ||
| key=metric_unit.proto_definition, | ||
| op=ComparisonFilter.OP_EQUALS, | ||
| value=AttributeValue(val_str=self.metric_type), | ||
| ) | ||
| ), | ||
| ] | ||
|
|
||
| if self.metric_unit: | ||
| metric_unit, _ = search_resolver.resolve_column("metric.unit") | ||
| if not isinstance(metric_unit.proto_definition, AttributeKey): | ||
| raise ValueError("Unable to resolve metric.unit") | ||
| filters.append( | ||
| TraceItemFilter( | ||
| comparison_filter=ComparisonFilter( | ||
| key=metric_unit.proto_definition, | ||
| op=ComparisonFilter.OP_EQUALS, | ||
| value=AttributeValue(val_str=self.metric_unit), | ||
| ) | ||
| value=AttributeValue(val_str=metric.metric_unit), | ||
| ) | ||
| ) | ||
| ) | ||
|
|
||
| return TraceItemFilter(and_filter=AndFilter(filters=filters)) | ||
| return TraceItemFilter(and_filter=AndFilter(filters=filters)) | ||
|
|
||
|
|
||
| ALLOWED_METRIC_TYPES: list[MetricType] = ["counter", "gauge", "distribution"] | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This config is populated from the extra request params right? Is the long term plan to eventually stop supporting it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct. Trace metrics uses it own
TraceMetricsSearchResolverConfigthat inherits fromSearchResolverConfig. This currently has top level configs to specify 1 metric. Long term, we'll remove that and only rely on the metric specified in the aggregation.