From e2bc01a7ae4bc8c3a3a6c1ec2d4a59844b91ad53 Mon Sep 17 00:00:00 2001 From: Edward Gou Date: Mon, 8 Dec 2025 16:54:57 -0500 Subject: [PATCH 1/5] adds more tags and metrics --- .../tasks/web_vitals_issue_detection.py | 50 ++++++++++++++++--- .../web_vitals/issue_platform_adapter.py | 7 +++ 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/sentry/tasks/web_vitals_issue_detection.py b/src/sentry/tasks/web_vitals_issue_detection.py index 5661984da6d37e..c6e7fd8c0e6b66 100644 --- a/src/sentry/tasks/web_vitals_issue_detection.py +++ b/src/sentry/tasks/web_vitals_issue_detection.py @@ -103,7 +103,7 @@ def dispatch_detection_for_project_ids( results[project.id] = {"success": False, "reason": "web_vitals_detection_not_enabled"} continue - detect_web_vitals_issues_for_project.delay(project.id) + detect_web_vitals_issues_for_project.delay(project) results[project.id] = {"success": True} projects_dispatched_count += 1 @@ -126,18 +126,29 @@ def dispatch_detection_for_project_ids( namespace=issues_tasks, processing_deadline_duration=120, ) -def detect_web_vitals_issues_for_project(project_id: int) -> None: +def detect_web_vitals_issues_for_project(project: Project) -> None: """ Process a single project for Web Vitals issue detection. """ if not options.get("issue-detection.web-vitals-detection.enabled"): + metrics.incr( + "web_vitals_issue_detection.projects.skipped", + amount=1, + tags={ + "reason": "disabled", + "project_id": project.id, + "organization": project.organization.slug, + }, + sample_rate=1.0, + ) return web_vital_issue_groups = get_highest_opportunity_page_vitals_for_project( - project_id, limit=TRANSACTIONS_PER_PROJECT_LIMIT + project.id, limit=TRANSACTIONS_PER_PROJECT_LIMIT ) sent_counts: dict[WebVitalIssueDetectionGroupingType, int] = defaultdict(int) rejected_no_trace_count = 0 + rejected_already_exists_count = 0 for web_vital_issue_group in web_vital_issue_groups: scores = web_vital_issue_group["scores"] values = web_vital_issue_group["values"] @@ -149,7 +160,7 @@ def detect_web_vitals_issues_for_project(project_id: int) -> None: trace = get_trace_by_web_vital_measurement( web_vital_issue_group["transaction"], - project_id, + project.id, vital, p75_vital_value, start_time_delta=DEFAULT_START_TIME_DELTA, @@ -158,6 +169,8 @@ def detect_web_vitals_issues_for_project(project_id: int) -> None: sent = send_web_vitals_issue_to_platform(web_vital_issue_group, trace_id=trace.trace_id) if sent: sent_counts[web_vital_issue_group["vital_grouping"]] += 1 + else: + rejected_already_exists_count += 1 else: rejected_no_trace_count += 1 @@ -165,14 +178,33 @@ def detect_web_vitals_issues_for_project(project_id: int) -> None: metrics.incr( "web_vitals_issue_detection.issues.sent", amount=count, - tags={"kind": vital_grouping}, # rendering, cls, or inp + tags={ + "kind": vital_grouping, + "project_id": project.id, + "organization": project.organization.slug, + }, # rendering, cls, or inp sample_rate=1.0, ) metrics.incr( "web_vitals_issue_detection.rejected", amount=rejected_no_trace_count, - tags={"reason": "no_trace"}, + tags={ + "reason": "no_trace", + "project_id": project.id, + "organization": project.organization.slug, + }, + sample_rate=1.0, + ) + + metrics.incr( + "web_vitals_issue_detection.rejected", + amount=rejected_already_exists_count, + tags={ + "reason": "already_exists", + "project_id": project.id, + "organization": project.organization.slug, + }, sample_rate=1.0, ) @@ -291,7 +323,11 @@ def get_highest_opportunity_page_vitals_for_project( metrics.incr( "web_vitals_issue_detection.rejected", amount=rejected_insufficient_samples_count, - tags={"reason": "insufficient_samples"}, + tags={ + "reason": "insufficient_samples", + "project_id": project.id, + "organization": project.organization.slug, + }, sample_rate=1.0, ) diff --git a/src/sentry/web_vitals/issue_platform_adapter.py b/src/sentry/web_vitals/issue_platform_adapter.py index a82ad5fdb8456c..bd6bc7e5082648 100644 --- a/src/sentry/web_vitals/issue_platform_adapter.py +++ b/src/sentry/web_vitals/issue_platform_adapter.py @@ -2,6 +2,8 @@ from datetime import UTC, datetime from uuid import uuid4 +import sentry_sdk + from sentry.issues.grouptype import WebVitalsGroup from sentry.issues.ingest import hash_fingerprint from sentry.issues.issue_occurrence import IssueEvidence, IssueOccurrence @@ -16,7 +18,12 @@ def create_fingerprint(vital_grouping: WebVitalIssueDetectionGroupingType, trans return fingerprint +@sentry_sdk.tracing.trace def send_web_vitals_issue_to_platform(data: WebVitalIssueGroupData, trace_id: str) -> bool: + project = data["project"] + sentry_sdk.set_tag("project_id", project.id) + sentry_sdk.set_tag("organization_id", project.organization_id) + # Do not create a new web vital issue if an open issue already exists if check_unresolved_web_vitals_issue_exists(data): return False From bbda1ea83f2b7d4d89aceae5c694c7ac27c8be11 Mon Sep 17 00:00:00 2001 From: Edward Gou Date: Tue, 9 Dec 2025 10:31:34 -0500 Subject: [PATCH 2/5] fix --- .../tasks/web_vitals_issue_detection.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/sentry/tasks/web_vitals_issue_detection.py b/src/sentry/tasks/web_vitals_issue_detection.py index c6e7fd8c0e6b66..971f546cf8cc70 100644 --- a/src/sentry/tasks/web_vitals_issue_detection.py +++ b/src/sentry/tasks/web_vitals_issue_detection.py @@ -103,7 +103,7 @@ def dispatch_detection_for_project_ids( results[project.id] = {"success": False, "reason": "web_vitals_detection_not_enabled"} continue - detect_web_vitals_issues_for_project.delay(project) + detect_web_vitals_issues_for_project.delay(project.id, project.organization.slug) results[project.id] = {"success": True} projects_dispatched_count += 1 @@ -126,7 +126,7 @@ def dispatch_detection_for_project_ids( namespace=issues_tasks, processing_deadline_duration=120, ) -def detect_web_vitals_issues_for_project(project: Project) -> None: +def detect_web_vitals_issues_for_project(project_id: int, organization_slug: str) -> None: """ Process a single project for Web Vitals issue detection. """ @@ -136,15 +136,15 @@ def detect_web_vitals_issues_for_project(project: Project) -> None: amount=1, tags={ "reason": "disabled", - "project_id": project.id, - "organization": project.organization.slug, + "project_id": project_id, + "organization": organization_slug, }, sample_rate=1.0, ) return web_vital_issue_groups = get_highest_opportunity_page_vitals_for_project( - project.id, limit=TRANSACTIONS_PER_PROJECT_LIMIT + project_id, limit=TRANSACTIONS_PER_PROJECT_LIMIT ) sent_counts: dict[WebVitalIssueDetectionGroupingType, int] = defaultdict(int) rejected_no_trace_count = 0 @@ -160,7 +160,7 @@ def detect_web_vitals_issues_for_project(project: Project) -> None: trace = get_trace_by_web_vital_measurement( web_vital_issue_group["transaction"], - project.id, + project_id, vital, p75_vital_value, start_time_delta=DEFAULT_START_TIME_DELTA, @@ -180,8 +180,8 @@ def detect_web_vitals_issues_for_project(project: Project) -> None: amount=count, tags={ "kind": vital_grouping, - "project_id": project.id, - "organization": project.organization.slug, + "project_id": project_id, + "organization": organization_slug, }, # rendering, cls, or inp sample_rate=1.0, ) @@ -191,8 +191,8 @@ def detect_web_vitals_issues_for_project(project: Project) -> None: amount=rejected_no_trace_count, tags={ "reason": "no_trace", - "project_id": project.id, - "organization": project.organization.slug, + "project_id": project_id, + "organization": organization_slug, }, sample_rate=1.0, ) @@ -202,8 +202,8 @@ def detect_web_vitals_issues_for_project(project: Project) -> None: amount=rejected_already_exists_count, tags={ "reason": "already_exists", - "project_id": project.id, - "organization": project.organization.slug, + "project_id": project_id, + "organization": organization_slug, }, sample_rate=1.0, ) From 9498a8ff1e2fddb496fff6b8144a9b3776c670ac Mon Sep 17 00:00:00 2001 From: Edward Gou Date: Tue, 9 Dec 2025 11:21:16 -0500 Subject: [PATCH 3/5] fix --- src/sentry/tasks/web_vitals_issue_detection.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sentry/tasks/web_vitals_issue_detection.py b/src/sentry/tasks/web_vitals_issue_detection.py index 971f546cf8cc70..793c00330a89ce 100644 --- a/src/sentry/tasks/web_vitals_issue_detection.py +++ b/src/sentry/tasks/web_vitals_issue_detection.py @@ -126,7 +126,9 @@ def dispatch_detection_for_project_ids( namespace=issues_tasks, processing_deadline_duration=120, ) -def detect_web_vitals_issues_for_project(project_id: int, organization_slug: str) -> None: +def detect_web_vitals_issues_for_project( + project_id: int, organization_slug: str | None = None +) -> None: """ Process a single project for Web Vitals issue detection. """ From 758f4eb77f7bd8a5f979cdc09a9b048af4008c51 Mon Sep 17 00:00:00 2001 From: Edward Gou Date: Tue, 9 Dec 2025 12:07:26 -0500 Subject: [PATCH 4/5] use select related --- src/sentry/tasks/web_vitals_issue_detection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sentry/tasks/web_vitals_issue_detection.py b/src/sentry/tasks/web_vitals_issue_detection.py index 793c00330a89ce..bbbd749e72a775 100644 --- a/src/sentry/tasks/web_vitals_issue_detection.py +++ b/src/sentry/tasks/web_vitals_issue_detection.py @@ -224,7 +224,7 @@ def get_highest_opportunity_page_vitals_for_project( The number of samples is set by SAMPLES_COUNT_THRESHOLD. """ try: - project = Project.objects.get(id=project_id) + project = Project.objects.get(id=project_id).select_related("organization") except Project.DoesNotExist: logger.exception( "Project does not exist; cannot fetch transactions", extra={"project_id": project_id} From cc88910834a22e1e6a8d891c057593ed546690f4 Mon Sep 17 00:00:00 2001 From: Edward Gou Date: Tue, 9 Dec 2025 12:40:24 -0500 Subject: [PATCH 5/5] fix --- src/sentry/tasks/web_vitals_issue_detection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sentry/tasks/web_vitals_issue_detection.py b/src/sentry/tasks/web_vitals_issue_detection.py index bbbd749e72a775..429ad7db83b19a 100644 --- a/src/sentry/tasks/web_vitals_issue_detection.py +++ b/src/sentry/tasks/web_vitals_issue_detection.py @@ -224,7 +224,7 @@ def get_highest_opportunity_page_vitals_for_project( The number of samples is set by SAMPLES_COUNT_THRESHOLD. """ try: - project = Project.objects.get(id=project_id).select_related("organization") + project = Project.objects.select_related("organization").get(id=project_id) except Project.DoesNotExist: logger.exception( "Project does not exist; cannot fetch transactions", extra={"project_id": project_id}