1
+ import logging
1
2
from collections import defaultdict
2
3
from datetime import datetime , timedelta , timezone
3
4
from typing import Any , TypedDict
13
14
from sentry .search .events .fields import get_function_alias
14
15
from sentry .search .events .types import SnubaParams
15
16
from sentry .snuba .entity_subscription import apply_dataset_query_conditions
17
+ from sentry .snuba .metrics import parse_mri_field
16
18
from sentry .snuba .metrics .extraction import MetricSpecType
17
19
from sentry .snuba .metrics_performance import timeseries_query
18
20
from sentry .snuba .models import SnubaQuery
21
+ from sentry .snuba .referrer import Referrer
19
22
from sentry .snuba .spans_rpc import run_timeseries_query
20
23
from sentry .utils .snuba import SnubaTSResult
21
24
25
+ logger = logging .getLogger (__name__ )
26
+
22
27
23
28
class TSResultForComparison (TypedDict ):
24
29
result : SnubaTSResult
@@ -40,7 +45,7 @@ def make_rpc_request(
40
45
snuba_params ,
41
46
query_string = query_parts ["query" ],
42
47
y_axes = query_parts ["selected_columns" ],
43
- referrer = "job-runner.compare-timeseries" ,
48
+ referrer = Referrer . JOB_COMPARE_TIMESERIES . value ,
44
49
granularity_secs = time_window ,
45
50
config = SearchResolverConfig (),
46
51
)
@@ -62,7 +67,7 @@ def make_snql_request(
62
67
query ,
63
68
snuba_params = snuba_params ,
64
69
rollup = time_window ,
65
- referrer = "job-runner.compare-timeseries" ,
70
+ referrer = Referrer . JOB_COMPARE_TIMESERIES . value ,
66
71
on_demand_metrics_enabled = on_demand_metrics_enabled ,
67
72
on_demand_metrics_type = MetricSpecType .SIMPLE_QUERY ,
68
73
zerofill_results = True ,
@@ -86,7 +91,7 @@ def fill_aligned_series(data: dict[str, Any], alias: str, key: str):
86
91
return aligned_results
87
92
88
93
89
- def assert_timeseries_close (aligned_timeseries ):
94
+ def assert_timeseries_close (aligned_timeseries , alert_rule ):
90
95
mismatches : dict [int , dict [str , float ]] = {}
91
96
missing_buckets = 0
92
97
for timestamp , values in aligned_timeseries .items ():
@@ -116,12 +121,22 @@ def assert_timeseries_close(aligned_timeseries):
116
121
if mismatches :
117
122
with sentry_sdk .isolation_scope () as scope :
118
123
scope .set_extra ("mismatches" , mismatches )
124
+ scope .set_extra ("alert_id" , alert_rule .id )
119
125
sentry_sdk .capture_message ("Timeseries mismatch" , level = "info" )
126
+ logger .info ("Alert %s has too many mismatches" , alert_rule .id )
127
+
128
+ return False , mismatches
120
129
121
130
if missing_buckets > 1 :
122
- sentry_sdk .capture_message ("Multiple missing buckets!" , level = "info" )
131
+ with sentry_sdk .isolation_scope () as scope :
132
+ scope .set_extra ("alert_id" , alert_rule .id )
133
+ sentry_sdk .capture_message ("Multiple missing buckets" , level = "info" )
134
+ logger .info ("Alert %s has multiple missing buckets" , alert_rule .id )
135
+
136
+ return False , mismatches
123
137
124
- return mismatches
138
+ logger .info ("Alert %s timeseries is close" , alert_rule .id )
139
+ return True , mismatches
125
140
126
141
127
142
def compare_timeseries_for_alert_rule (alert_rule : AlertRule ):
@@ -130,6 +145,22 @@ def compare_timeseries_for_alert_rule(alert_rule: AlertRule):
130
145
if not project :
131
146
raise NoProjects
132
147
148
+ if snuba_query .aggregate in ["apdex()" ]:
149
+ logger .info (
150
+ "Skipping alert %s, %s aggregate not yet supported by RPC" ,
151
+ alert_rule .id ,
152
+ snuba_query .aggregate ,
153
+ )
154
+ return {"skipped" : True , "is_close" : False }
155
+
156
+ if parse_mri_field (snuba_query .aggregate ):
157
+ logger .info (
158
+ "Skipping alert %s, %s, MRI fields not supported in aggregates" ,
159
+ alert_rule .id ,
160
+ snuba_query .aggregate ,
161
+ )
162
+ return {"skipped" : True , "is_close" : False }
163
+
133
164
organization = Organization .objects .get_from_cache (id = project .organization_id )
134
165
135
166
on_demand_metrics_enabled = features .has (
@@ -139,8 +170,13 @@ def compare_timeseries_for_alert_rule(alert_rule: AlertRule):
139
170
140
171
# Align time to the nearest hour because RPCs roll up on exact timestamps.
141
172
now = datetime .now (tz = timezone .utc ).replace (minute = 0 , second = 0 , microsecond = 0 )
173
+
174
+ environments = []
175
+ if snuba_query .environment :
176
+ environments = [snuba_query .environment ]
177
+
142
178
snuba_params = SnubaParams (
143
- environments = [ snuba_query . environment ] ,
179
+ environments = environments ,
144
180
projects = [project ],
145
181
organization = organization ,
146
182
start = now - timedelta (days = 1 ),
@@ -164,4 +200,6 @@ def compare_timeseries_for_alert_rule(alert_rule: AlertRule):
164
200
165
201
aligned_timeseries = align_timeseries (snql_result = snql_result , rpc_result = rpc_result )
166
202
167
- return assert_timeseries_close (aligned_timeseries )
203
+ is_close , mismatches = assert_timeseries_close (aligned_timeseries , alert_rule )
204
+
205
+ return {"is_close" : is_close , "skipped" : False , "mismatches" : mismatches }
0 commit comments