Skip to content

Commit c2c41b0

Browse files
feat: Add logic to increment GroupTombstone hits asynchronously (#93685)
Contributes to https://linear.app/getsentry/issue/TET-595/display-discarded-issue-count-and-timestamp-for-each-entry-in
1 parent 691e695 commit c2c41b0

File tree

3 files changed

+68
-24
lines changed

3 files changed

+68
-24
lines changed

src/sentry/api/serializers/models/grouptombstone.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,6 @@ def serialize(self, obj, attrs, user, **kwargs):
2626
"type": obj.get_event_type(),
2727
"metadata": obj.get_event_metadata(),
2828
"actor": attrs.get("user"),
29+
"timesSeen": obj.times_seen,
30+
"lastSeen": obj.last_seen,
2931
}

src/sentry/event_manager.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,32 @@ def get_stored_crashreports(cache_key: str | None, event: Event, max_crashreport
294294
return query[:max_crashreports].count()
295295

296296

297+
def increment_group_tombstone_hit_counter(tombstone_id: int | None, event: Event) -> None:
298+
if tombstone_id is None:
299+
return
300+
try:
301+
from sentry.models.grouptombstone import GroupTombstone
302+
303+
group_tombstone = GroupTombstone.objects.get(id=tombstone_id)
304+
buffer_incr(
305+
GroupTombstone,
306+
{"times_seen": 1},
307+
{"id": tombstone_id},
308+
{
309+
"last_seen": (
310+
max(event.datetime, group_tombstone.last_seen)
311+
if group_tombstone.last_seen
312+
else event.datetime
313+
)
314+
},
315+
)
316+
except GroupTombstone.DoesNotExist:
317+
# This can happen due to a race condition with deletion.
318+
pass
319+
except Exception:
320+
logger.exception("Failed to update GroupTombstone count for id: %s", tombstone_id)
321+
322+
297323
ProjectsMapping = Mapping[int, Project]
298324

299325
Job = MutableMapping[str, Any]
@@ -510,7 +536,11 @@ def save_error_events(
510536
try:
511537
group_info = assign_event_to_group(event=job["event"], job=job, metric_tags=metric_tags)
512538

513-
except HashDiscarded:
539+
except HashDiscarded as e:
540+
if features.has("organizations:grouptombstones-hit-counter", project.organization):
541+
increment_group_tombstone_hit_counter(
542+
getattr(e, "tombstone_id", None), job["event"]
543+
)
514544
discard_event(job, attachments)
515545
raise
516546

tests/sentry/event_manager/test_event_manager.py

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2117,39 +2117,51 @@ def test_throws_when_matches_discarded_hash(self) -> None:
21172117
)
21182118
GroupHash.objects.filter(group=group).update(group=None, group_tombstone_id=tombstone.id)
21192119

2120-
manager = EventManager(
2121-
make_event(message="foo", event_id="b" * 32, fingerprint=["a" * 32]),
2122-
project=self.project,
2123-
)
2124-
manager.normalize()
2120+
from sentry.utils.outcomes import track_outcome
21252121

21262122
a1 = CachedAttachment(name="a1", data=b"hello")
21272123
a2 = CachedAttachment(name="a2", data=b"world")
21282124

2129-
cache_key = cache_key_for_event(manager.get_data())
2130-
attachment_cache.set(cache_key, attachments=[a1, a2])
2125+
for i, event_id in enumerate(["b" * 32, "c" * 32]):
2126+
manager = EventManager(
2127+
make_event(message="foo", event_id=event_id, fingerprint=["a" * 32]),
2128+
project=self.project,
2129+
)
2130+
manager.normalize()
2131+
discarded_event = Event(
2132+
project_id=self.project.id, event_id=event_id, data=manager.get_data()
2133+
)
21312134

2132-
from sentry.utils.outcomes import track_outcome
2135+
cache_key = cache_key_for_event(manager.get_data())
2136+
attachment_cache.set(cache_key, attachments=[a1, a2])
21332137

2134-
mock_track_outcome = mock.Mock(wraps=track_outcome)
2135-
with mock.patch("sentry.event_manager.track_outcome", mock_track_outcome):
2136-
with self.feature("organizations:event-attachments"):
2137-
with self.tasks():
2138-
with pytest.raises(HashDiscarded):
2139-
manager.save(self.project.id, cache_key=cache_key, has_attachments=True)
2138+
mock_track_outcome = mock.Mock(wraps=track_outcome)
2139+
with (
2140+
mock.patch("sentry.event_manager.track_outcome", mock_track_outcome),
2141+
self.feature("organizations:event-attachments"),
2142+
self.feature("organizations:grouptombstones-hit-counter"),
2143+
self.tasks(),
2144+
pytest.raises(HashDiscarded),
2145+
):
2146+
manager.save(self.project.id, cache_key=cache_key, has_attachments=True)
21402147

2141-
assert mock_track_outcome.call_count == 3
2148+
assert mock_track_outcome.call_count == 3
21422149

2143-
for o in mock_track_outcome.mock_calls:
2144-
assert o.kwargs["outcome"] == Outcome.FILTERED
2145-
assert o.kwargs["reason"] == FilterStatKeys.DISCARDED_HASH
2150+
event_outcome = mock_track_outcome.mock_calls[0].kwargs
2151+
assert event_outcome["outcome"] == Outcome.FILTERED
2152+
assert event_outcome["reason"] == FilterStatKeys.DISCARDED_HASH
2153+
assert event_outcome["category"] == DataCategory.ERROR
2154+
assert event_outcome["event_id"] == event_id
21462155

2147-
o = mock_track_outcome.mock_calls[0]
2148-
assert o.kwargs["category"] == DataCategory.ERROR
2156+
for call in mock_track_outcome.mock_calls[1:]:
2157+
attachment_outcome = call.kwargs
2158+
assert attachment_outcome["category"] == DataCategory.ATTACHMENT
2159+
assert attachment_outcome["quantity"] == 5
21492160

2150-
for o in mock_track_outcome.mock_calls[1:]:
2151-
assert o.kwargs["category"] == DataCategory.ATTACHMENT
2152-
assert o.kwargs["quantity"] == 5
2161+
expected_times_seen = 1 + i
2162+
tombstone.refresh_from_db()
2163+
assert tombstone.times_seen == expected_times_seen
2164+
assert tombstone.last_seen == discarded_event.datetime
21532165

21542166
def test_honors_crash_report_limit(self) -> None:
21552167
from sentry.utils.outcomes import track_outcome

0 commit comments

Comments
 (0)