12
12
13
13
14
14
@mock .patch ("sentry.monitors.clock_dispatch._dispatch_tick" )
15
- def test_monitor_task_trigger (dispatch_tick ):
15
+ @override_options ({"crons.use_clock_pulse_consumer" : False })
16
+ def test_monitor_task_trigger_legacy (dispatch_tick ):
16
17
now = timezone .now ().replace (second = 0 , microsecond = 0 )
17
18
18
19
# Assumes a single partition for simplicitly. Multi-partition cases are
19
20
# covered in further test cases.
20
21
21
- # First checkin triggers tasks
22
+ # First checkin triggers dispatch
22
23
try_monitor_clock_tick (ts = now , partition = 0 )
23
24
assert dispatch_tick .call_count == 1
24
25
25
- # 5 seconds later does NOT trigger the task
26
+ # 5 seconds later does NOT trigger the dispatch
26
27
try_monitor_clock_tick (ts = now + timedelta (seconds = 5 ), partition = 0 )
27
28
assert dispatch_tick .call_count == 1
28
29
29
- # a minute later DOES trigger the task
30
+ # a minute later DOES trigger the dispatch
30
31
try_monitor_clock_tick (ts = now + timedelta (minutes = 1 ), partition = 0 )
31
32
assert dispatch_tick .call_count == 2
32
33
33
- # Same time does NOT trigger the task
34
+ # Same time does NOT trigger the dispatch
34
35
try_monitor_clock_tick (ts = now + timedelta (minutes = 1 ), partition = 0 )
35
36
assert dispatch_tick .call_count == 2
36
37
@@ -42,6 +43,79 @@ def test_monitor_task_trigger(dispatch_tick):
42
43
capture_message .assert_called_with ("Monitor task dispatch minute skipped" )
43
44
44
45
46
+ @mock .patch ("sentry.monitors.clock_dispatch._dispatch_tick" )
47
+ @override_options ({"crons.use_clock_pulse_consumer" : False })
48
+ def test_monitor_task_trigger_partition_tick_skip_legacy (dispatch_tick ):
49
+ now = timezone .now ().replace (second = 0 , microsecond = 0 )
50
+
51
+ # Tick for 4 partitions
52
+ try_monitor_clock_tick (ts = now , partition = 0 )
53
+ try_monitor_clock_tick (ts = now , partition = 1 )
54
+ try_monitor_clock_tick (ts = now , partition = 2 )
55
+ try_monitor_clock_tick (ts = now , partition = 3 )
56
+ assert dispatch_tick .call_count == 1
57
+ assert dispatch_tick .mock_calls [0 ] == mock .call (now )
58
+
59
+ # Tick forward twice for 3 partitions
60
+ try_monitor_clock_tick (ts = now + timedelta (minutes = 1 ), partition = 0 )
61
+ try_monitor_clock_tick (ts = now + timedelta (minutes = 1 ), partition = 1 )
62
+ try_monitor_clock_tick (ts = now + timedelta (minutes = 1 ), partition = 2 )
63
+
64
+ try_monitor_clock_tick (ts = now + timedelta (minutes = 2 ), partition = 0 )
65
+ try_monitor_clock_tick (ts = now + timedelta (minutes = 3 ), partition = 1 )
66
+ try_monitor_clock_tick (ts = now + timedelta (minutes = 3 ), partition = 2 )
67
+ assert dispatch_tick .call_count == 1
68
+
69
+ # Slowest partition catches up, but has a timestamp gap, capture the fact
70
+ # that we skipped a minute
71
+ with mock .patch ("sentry_sdk.capture_message" ) as capture_message :
72
+ assert capture_message .call_count == 0
73
+ try_monitor_clock_tick (ts = now + timedelta (minutes = 2 ), partition = 3 )
74
+ capture_message .assert_called_with ("Monitor task dispatch minute skipped" )
75
+
76
+ # XXX(epurkhiser): Another approach we could take here is to detect the
77
+ # skipped minute and generate a tick for that minute, since we know
78
+ # processed past that minute.
79
+ #
80
+ # This still could be a problem though since it may mean we will not
81
+ # produce missed check-ins since the monitor already may have already
82
+ # checked-in after and moved the `next_checkin_latest` forward.
83
+ #
84
+ # In practice this should almost never happen since we have a high volume of
85
+
86
+ assert dispatch_tick .call_count == 2
87
+ assert dispatch_tick .mock_calls [1 ] == mock .call (now + timedelta (minutes = 2 ))
88
+
89
+
90
+ @mock .patch ("sentry.monitors.clock_dispatch._dispatch_tick" )
91
+ @override_options ({"crons.use_clock_pulse_consumer" : True })
92
+ def test_monitor_task_trigger (dispatch_tick ):
93
+ now = timezone .now ().replace (second = 0 , microsecond = 0 )
94
+
95
+ # Assumes a single partition for simplicitly. Multi-partition cases are
96
+ # covered in further test cases.
97
+
98
+ # First checkin triggers dispatch
99
+ try_monitor_clock_tick (ts = now , partition = 0 )
100
+ assert dispatch_tick .call_count == 1
101
+
102
+ # 5 seconds later does NOT trigger the dispatch
103
+ try_monitor_clock_tick (ts = now + timedelta (seconds = 5 ), partition = 0 )
104
+ assert dispatch_tick .call_count == 1
105
+
106
+ # a minute later DOES trigger the dispatch
107
+ try_monitor_clock_tick (ts = now + timedelta (minutes = 1 ), partition = 0 )
108
+ assert dispatch_tick .call_count == 2
109
+
110
+ # Same time does NOT trigger the dispatch
111
+ try_monitor_clock_tick (ts = now + timedelta (minutes = 1 ), partition = 0 )
112
+ assert dispatch_tick .call_count == 2
113
+
114
+ # A skipped minute triggers the dispatch multiple times
115
+ try_monitor_clock_tick (ts = now + timedelta (minutes = 3 , seconds = 5 ), partition = 0 )
116
+ assert dispatch_tick .call_count == 4
117
+
118
+
45
119
@mock .patch ("sentry.monitors.clock_dispatch._dispatch_tick" )
46
120
def test_monitor_task_trigger_partition_desync (dispatch_tick ):
47
121
"""
@@ -52,7 +126,7 @@ def test_monitor_task_trigger_partition_desync(dispatch_tick):
52
126
now = timezone .now ().replace (second = 0 , microsecond = 0 )
53
127
54
128
# First message in partition 0 with timestamp just after the minute
55
- # boundary triggers the task
129
+ # boundary triggers the dispatch
56
130
try_monitor_clock_tick (ts = now + timedelta (seconds = 1 ), partition = 0 )
57
131
assert dispatch_tick .call_count == 1
58
132
@@ -63,7 +137,7 @@ def test_monitor_task_trigger_partition_desync(dispatch_tick):
63
137
assert dispatch_tick .call_count == 1
64
138
65
139
# Third message in partition 1 again just after the minute boundary does
66
- # NOT trigger the task , we've already ticked at that time.
140
+ # NOT trigger the dispatch , we've already ticked at that time.
67
141
try_monitor_clock_tick (ts = now + timedelta (seconds = 1 ), partition = 1 )
68
142
assert dispatch_tick .call_count == 1
69
143
@@ -102,10 +176,12 @@ def test_monitor_task_trigger_partition_sync(dispatch_tick):
102
176
103
177
104
178
@mock .patch ("sentry.monitors.clock_dispatch._dispatch_tick" )
179
+ @override_options ({"crons.use_clock_pulse_consumer" : True })
105
180
def test_monitor_task_trigger_partition_tick_skip (dispatch_tick ):
106
181
"""
107
182
In a scenario where all partitions move multiple ticks past the slowest
108
- partition we may end up skipping a tick.
183
+ partition we may end up skipping a tick. In this scenario we will backfill
184
+ those ticks
109
185
"""
110
186
now = timezone .now ().replace (second = 0 , microsecond = 0 )
111
187
@@ -127,25 +203,12 @@ def test_monitor_task_trigger_partition_tick_skip(dispatch_tick):
127
203
try_monitor_clock_tick (ts = now + timedelta (minutes = 3 ), partition = 2 )
128
204
assert dispatch_tick .call_count == 1
129
205
130
- # Slowest partition catches up, but has a timestamp gap, capture the fact
131
- # that we skipped a minute
132
- with mock .patch ("sentry_sdk.capture_message" ) as capture_message :
133
- assert capture_message .call_count == 0
134
- try_monitor_clock_tick (ts = now + timedelta (minutes = 2 ), partition = 3 )
135
- capture_message .assert_called_with ("Monitor task dispatch minute skipped" )
206
+ # Slowest partition catches up, but has a timestamp gap
207
+ try_monitor_clock_tick (ts = now + timedelta (minutes = 2 ), partition = 3 )
136
208
137
- # XXX(epurkhiser): Another approach we could take here is to detect the
138
- # skipped minute and generate a tick for that minute, since we know
139
- # processed past that minute.
140
- #
141
- # This still could be a problem though since it may mean we will not
142
- # produce missed check-ins since the monitor already may have already
143
- # checked-in after and moved the `next_checkin_latest` forward.
144
- #
145
- # In practice this should almost never happen since we have a high volume of
146
-
147
- assert dispatch_tick .call_count == 2
148
- assert dispatch_tick .mock_calls [1 ] == mock .call (now + timedelta (minutes = 2 ))
209
+ assert dispatch_tick .call_count == 3
210
+ assert dispatch_tick .mock_calls [1 ] == mock .call (now + timedelta (minutes = 1 ))
211
+ assert dispatch_tick .mock_calls [2 ] == mock .call (now + timedelta (minutes = 2 ))
149
212
150
213
151
214
@override_settings (KAFKA_TOPIC_OVERRIDES = {"monitors-clock-tick" : "clock-tick-test-topic" })
0 commit comments