diff --git a/src/sentry/ingest/consumer/processors.py b/src/sentry/ingest/consumer/processors.py index 690c81f1fd9c15..3f067f0d8dbfb2 100644 --- a/src/sentry/ingest/consumer/processors.py +++ b/src/sentry/ingest/consumer/processors.py @@ -1,6 +1,6 @@ import functools import logging -from collections.abc import Mapping +from collections.abc import Mapping, MutableMapping from typing import Any import orjson @@ -187,6 +187,11 @@ def process_event( event_id=event_id, project_id=project_id, ) + + try: + collect_span_metrics(project, data) + except Exception: + pass elif data.get("type") == "feedback": if features.has("organizations:user-feedback-ingest", project.organization, actor=None): save_event_feedback.delay( @@ -324,3 +329,20 @@ def process_userreport(message: IngestMessage, project: Project) -> bool: # If you want to remove this make sure to have triaged all errors in Sentry logger.exception("userreport.save.crash") return False + + +def collect_span_metrics( + project: Project, + data: MutableMapping[str, Any], +): + if not features.has("organizations:am3-tier", project.organization) and not features.has( + "organizations:dynamic-sampling", project.organization + ): + amount = ( + len(data.get("spans", [])) + 1 + ) # Segment spans also get added to the total span count. + metrics.incr( + "event.save_event.unsampled.spans.count", + amount=amount, + tags={"organization": project.organization.slug}, + ) diff --git a/tests/sentry/ingest/ingest_consumer/test_ingest_consumer_processing.py b/tests/sentry/ingest/ingest_consumer/test_ingest_consumer_processing.py index 190f3bf6be3f49..0b66568001ab05 100644 --- a/tests/sentry/ingest/ingest_consumer/test_ingest_consumer_processing.py +++ b/tests/sentry/ingest/ingest_consumer/test_ingest_consumer_processing.py @@ -6,7 +6,7 @@ import zipfile from io import BytesIO from typing import Any -from unittest.mock import Mock +from unittest.mock import Mock, patch import orjson import pytest @@ -19,6 +19,7 @@ from sentry import eventstore from sentry.event_manager import EventManager from sentry.ingest.consumer.processors import ( + collect_span_metrics, process_attachment_chunk, process_event, process_individual_attachment, @@ -28,6 +29,7 @@ from sentry.models.eventattachment import EventAttachment from sentry.models.userreport import UserReport from sentry.options import set +from sentry.testutils.helpers.features import Feature from sentry.testutils.pytest.fixtures import django_db_all from sentry.testutils.skips import requires_snuba, requires_symbolicator from sentry.usage_accountant import accountant @@ -595,3 +597,19 @@ def test_individual_attachments_missing_chunks(default_project, factories, monke attachments = list(EventAttachment.objects.filter(project_id=project_id, event_id=event_id)) assert not attachments + + +@django_db_all +def test_collect_span_metrics(default_project): + with Feature({"organizations:dynamic-sampling": True, "organization:am3-tier": True}): + with patch("sentry.ingest.consumer.processors.metrics") as mock_metrics: + assert mock_metrics.incr.call_count == 0 + collect_span_metrics(default_project, {"spans": [1, 2, 3]}) + assert mock_metrics.incr.call_count == 0 + + with Feature({"organizations:dynamic-sampling": False, "organization:am3-tier": False}): + with patch("sentry.ingest.consumer.processors.metrics") as mock_metrics: + + assert mock_metrics.incr.call_count == 0 + collect_span_metrics(default_project, {"spans": [1, 2, 3]}) + assert mock_metrics.incr.call_count == 1