Skip to content

Commit 50283de

Browse files
authored
Merge pull request #239 from appsignal/remove-redundant-cron-check-in-pairs
Remove redundant cron check-in pairs
2 parents 4c5ffba + 392a9e7 commit 50283de

File tree

4 files changed

+113
-3
lines changed

4 files changed

+113
-3
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
bump: patch
3+
type: change
4+
---
5+
6+
Remove redundant cron check-in pairs. When more than one pair of start and finish cron check-in events is reported for the same identifier in the same period, only one of them will be reported to AppSignal.

src/appsignal/check_in/event.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,49 @@ def describe(events: list[Event]) -> str:
7070

7171
# This shouldn't happen.
7272
return "unknown check-in event" # type: ignore[unreachable]
73+
74+
75+
def deduplicate_cron(events: list[Event]) -> None:
76+
"""Remove redundant pairs of cron events in-place, keeping only
77+
the most recent complete pair."""
78+
start_digests: dict[str, set[str | None]] = {}
79+
finish_digests: dict[str, set[str | None]] = {}
80+
complete_digests: dict[str, set[str | None]] = {}
81+
keep_digest: dict[str, str | None] = {}
82+
83+
# Find complete pairs (start+finish) and track the latest one
84+
for event in events:
85+
if event["check_in_type"] != "cron" or event["kind"] not in ["start", "finish"]:
86+
continue
87+
88+
identifier = event["identifier"]
89+
digest = event.get("digest")
90+
if identifier not in start_digests:
91+
start_digests[identifier] = set()
92+
finish_digests[identifier] = set()
93+
complete_digests[identifier] = set()
94+
95+
if event["kind"] == "start":
96+
start_digests[identifier].add(digest)
97+
if digest in finish_digests[identifier]:
98+
complete_digests[identifier].add(digest)
99+
keep_digest[identifier] = digest
100+
elif event["kind"] == "finish":
101+
finish_digests[identifier].add(digest)
102+
if digest in start_digests[identifier]:
103+
complete_digests[identifier].add(digest)
104+
keep_digest[identifier] = digest
105+
106+
# Iterate through the events and remove unwanted ones in place
107+
i = 0
108+
while i < len(events):
109+
event = events[i]
110+
if (
111+
event["check_in_type"] == "cron"
112+
and event.get("kind") in ("start", "finish")
113+
and event.get("digest") in complete_digests.get(event["identifier"], set())
114+
and event.get("digest") != keep_digest.get(event["identifier"])
115+
):
116+
del events[i]
117+
else:
118+
i += 1

src/appsignal/check_in/scheduler.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from ..client import Client
1010
from ..config import Config
1111
from ..transmitter import transmit
12-
from .event import Event, describe, is_redundant
12+
from .event import Event, deduplicate_cron, describe, is_redundant
1313

1414

1515
class Scheduler:
@@ -133,7 +133,9 @@ def _stop_waker(self) -> None:
133133
def _push_events(self) -> None:
134134
if not self.events:
135135
return
136-
self.queue.put(self.events.copy())
136+
events_copy = self.events.copy()
137+
deduplicate_cron(events_copy)
138+
self.queue.put(events_copy)
137139
self.events.clear()
138140
self._start_waker(self.BETWEEN_TRANSMISSIONS_DEBOUNCE_SECONDS)
139141

tests/check_in/test_event.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
import json
2+
from itertools import permutations
23
from typing import cast
34

4-
from appsignal.check_in.event import Event, cron, describe, heartbeat, is_redundant
5+
from appsignal.check_in.event import (
6+
Event,
7+
cron,
8+
deduplicate_cron,
9+
describe,
10+
heartbeat,
11+
is_redundant,
12+
)
513

614

715
def test_describe_no_events():
@@ -93,3 +101,51 @@ def test_heartbeat_event_json_serialised():
93101
assert '"timestamp":' in serialised
94102
assert '"kind":' not in serialised
95103
assert '"digest":' not in serialised
104+
105+
106+
def test_deduplicate_cron_removes_redundant_pairs():
107+
first_start = cron("checkin-name", "first", "start")
108+
first_finish = cron("checkin-name", "first", "finish")
109+
second_start = cron("checkin-name", "second", "start")
110+
second_finish = cron("checkin-name", "second", "finish")
111+
base_events = [first_start, first_finish, second_start, second_finish]
112+
113+
for events_tuple in permutations(base_events):
114+
events = list(events_tuple) # Convert tuple to list for in-place modification
115+
deduplicate_cron(events)
116+
117+
assert len(events) == 2
118+
assert events[0]["digest"] == events[1]["digest"]
119+
assert {"start", "finish"} == {events[0]["kind"], events[1]["kind"]}
120+
121+
122+
def test_deduplicate_cron_keeps_unmatched_pairs():
123+
first_start = cron("checkin-name", "first", "start")
124+
second_start = cron("checkin-name", "second", "start")
125+
second_finish = cron("checkin-name", "second", "finish")
126+
third_finish = cron("checkin-name", "third", "finish")
127+
base_events = [first_start, second_start, second_finish, third_finish]
128+
129+
for events_tuple in permutations(base_events):
130+
events = list(events_tuple) # Convert tuple to list for in-place modification
131+
deduplicate_cron(events)
132+
133+
assert len(events) == 4
134+
for event in [first_start, second_start, second_finish, third_finish]:
135+
assert event in events
136+
137+
138+
def test_deduplicate_cron_keeps_different_identifiers():
139+
first_start = cron("checkin-name", "first", "start")
140+
first_finish = cron("checkin-name", "first", "finish")
141+
second_start = cron("other-name", "second", "start")
142+
second_finish = cron("other-name", "second", "finish")
143+
base_events = [first_start, first_finish, second_start, second_finish]
144+
145+
for events_tuple in permutations(base_events):
146+
events = list(events_tuple) # Convert tuple to list for in-place modification
147+
deduplicate_cron(events)
148+
149+
assert len(events) == 4
150+
for event in [first_start, first_finish, second_start, second_finish]:
151+
assert event in events

0 commit comments

Comments
 (0)