diff --git a/src/sentry/search/events/datasets/metrics.py b/src/sentry/search/events/datasets/metrics.py index dd5a822ddcbff5..66a23531448607 100644 --- a/src/sentry/search/events/datasets/metrics.py +++ b/src/sentry/search/events/datasets/metrics.py @@ -662,11 +662,11 @@ def function_converter(self) -> Mapping[str, fields.MetricsFunction]: default_result_type="percentage", ), fields.MetricsFunction( - "http_500_rate", + "http_error_rate", snql_distribution=lambda args, alias: Function( "divide", [ - self._resolve_http_500_count(args), + self._resolve_http_error_count(args), Function( "countIf", [ @@ -685,6 +685,11 @@ def function_converter(self) -> Mapping[str, fields.MetricsFunction]: ), default_result_type="percentage", ), + fields.MetricsFunction( + "http_error_count", + snql_distribution=self._resolve_http_error_count, + default_result_type="integer", + ), ] } @@ -1008,7 +1013,7 @@ def _resolve_failure_count( alias, ) - def _resolve_http_500_count( + def _resolve_http_error_count( self, _: Mapping[str, Union[str, Column, SelectType, int, float]], alias: Optional[str] = None, diff --git a/src/sentry/search/events/datasets/spans_metrics.py b/src/sentry/search/events/datasets/spans_metrics.py index 3ffb6567059920..d965987783a930 100644 --- a/src/sentry/search/events/datasets/spans_metrics.py +++ b/src/sentry/search/events/datasets/spans_metrics.py @@ -220,6 +220,35 @@ def function_converter(self) -> Mapping[str, fields.MetricsFunction]: ), default_result_type="duration", ), + fields.MetricsFunction( + "http_error_rate", + snql_distribution=lambda args, alias: Function( + "divide", + [ + self._resolve_http_error_count(args), + Function( + "countIf", + [ + Column("value"), + Function( + "equals", + [ + Column("metric_id"), + self.resolve_metric("span.duration"), + ], + ), + ], + ), + ], + alias, + ), + default_result_type="percentage", + ), + fields.MetricsFunction( + "http_error_count", + snql_distribution=self._resolve_http_error_count, + default_result_type="integer", + ), ] } @@ -229,6 +258,28 @@ def function_converter(self) -> Mapping[str, fields.MetricsFunction]: return function_converter + # Query Functions + def _resolve_count_if( + self, + metric_condition: Function, + condition: Function, + alias: Optional[str] = None, + ) -> SelectType: + return Function( + "countIf", + [ + Column("value"), + Function( + "and", + [ + metric_condition, + condition, + ], + ), + ], + alias, + ) + def _resolve_total_span_duration(self, alias: str) -> SelectType: """This calculates the app's total time, so other filters that are a part of the original query will not be applies. Only filter conditions @@ -278,6 +329,32 @@ def _resolve_time_spent_percentage( alias, ) + def _resolve_http_error_count( + self, + _: Mapping[str, Union[str, Column, SelectType, int, float]], + alias: Optional[str] = None, + ) -> SelectType: + statuses = [ + self.builder.resolve_tag_value(status) for status in constants.HTTP_SERVER_ERROR_STATUS + ] + return self._resolve_count_if( + Function( + "equals", + [ + Column("metric_id"), + self.resolve_metric("span.duration"), + ], + ), + Function( + "in", + [ + self.builder.column("span.status_code"), + list(status for status in statuses if status is not None), + ], + ), + alias, + ) + @property def orderby_converter(self) -> Mapping[str, OrderBy]: return {} diff --git a/tests/snuba/api/endpoints/test_organization_events_mep.py b/tests/snuba/api/endpoints/test_organization_events_mep.py index 9a893ee6372e01..01dea7f2dfa2b8 100644 --- a/tests/snuba/api/endpoints/test_organization_events_mep.py +++ b/tests/snuba/api/endpoints/test_organization_events_mep.py @@ -2163,7 +2163,7 @@ def test_os_name_falls_back(self): meta = response.data["meta"] assert not meta["isMetricsData"] - def test_http_500_rate(self): + def test_http_error_rate(self): self.store_transaction_metric( 1, tags={ @@ -2183,7 +2183,7 @@ def test_http_500_rate(self): response = self.do_request( { "field": [ - "http_500_rate()", + "http_error_rate()", ], "dataset": "metrics", } @@ -2192,7 +2192,7 @@ def test_http_500_rate(self): assert response.status_code == 200, response.content data = response.data["data"] assert len(data) == 1 - assert data[0]["http_500_rate()"] == 0.5 + assert data[0]["http_error_rate()"] == 0.5 meta = response.data["meta"] assert meta["isMetricsData"] @@ -2255,5 +2255,5 @@ def test_time_spent(self): super().test_custom_measurement_size_filtering() @pytest.mark.xfail(reason="Not supported") - def test_http_500_rate(self): + def test_http_error_rate(self): super().test_having_condition()