From 06c49829163d0be14e7926aa917cc9da75986385 Mon Sep 17 00:00:00 2001 From: George Gritsouk <989898+gggritso@users.noreply.github.com> Date: Mon, 11 Mar 2024 13:03:52 -0400 Subject: [PATCH] chore(perf): Add fixture for Slow DB Query alert email (#66595) Preparing for some potential uncoming improvements. - add a JSON fixture for an occurrence and event of a Slow DB Issue - produce an occurrence when loading the preview, otherwise it errors out --- .../samples/transaction-slow-db-query.json | 154 ++++++++++++++++++ .../templates/sentry/debug/mail/preview.html | 1 + src/sentry/testutils/helpers/notifications.py | 27 +++ src/sentry/web/frontend/debug/mail.py | 10 +- 4 files changed, 189 insertions(+), 3 deletions(-) create mode 100644 src/sentry/data/samples/transaction-slow-db-query.json diff --git a/src/sentry/data/samples/transaction-slow-db-query.json b/src/sentry/data/samples/transaction-slow-db-query.json new file mode 100644 index 00000000000000..1b57d33fdde241 --- /dev/null +++ b/src/sentry/data/samples/transaction-slow-db-query.json @@ -0,0 +1,154 @@ +{ + "platform": "python", + "message": "", + "tags": [ + ["browser", "Safari 15.5"], + ["browser.name", "Safari"], + ["client_os", "Mac OS X 10.15.7"], + ["client_os.name", "Mac OS X"], + ["device", "Mac"], + ["device.family", "Mac"], + ["environment", "production"], + ["sentry:release", "0.1"], + ["http.status_code", "200"], + ["level", "info"], + ["runtime", "CPython 3.8.9"], + ["runtime.name", "CPython"], + ["user", "ip:127.0.0.1"], + ["transaction", "/api/marketing-info/"], + ["url", "http://service.io/api/marketing-info/"] + ], + "_metrics": { "bytes.ingested.event": 45944, "bytes.stored.event": 48948 }, + "breakdowns": { + "span_ops": { + "ops.db": { "value": 509.444952, "unit": "millisecond" }, + "total.time": { "value": 2065.712928, "unit": "millisecond" } + } + }, + "contexts": { + "browser": { "name": "Safari", "version": "15.5", "type": "browser" }, + "client_os": { "name": "Mac OS X", "type": "os" }, + "response": { "status_code": 200, "type": "response" }, + "runtime": { + "name": "CPython", + "version": "3.11.6", + "build": "3.11.6 (main, Nov 29 2023)", + "type": "runtime" + }, + "trace": { + "trace_id": "544bb57898d8452a55baff3a83e373", + "span_id": "9d3a7889de40f396", + "op": "http.server", + "status": "ok", + "exclusive_time": 1.35088, + "client_sample_rate": 1.0, + "hash": "4c65d02b403d7cfd", + "type": "trace" + } + }, + "culprit": "/api/marketing-info/", + "environment": "production", + "extra": { "sys.argv": ["uwsgi"] }, + "grouping_config": { + "enhancements": "KLUv_SCrDQQAgoccI7CnDVQUpQkWg3WZl4GVObL7IItQShcbOqCaSEq4PSwiGyEBCboIuvZFbwr9z_Jm0b8YezsLrsfvusXHrAUeVApY43fPPDSqTdBF0LUp8bc786Nk1pj6hP1s8OeUpKNahVKKWpQBszCEdnQ-oiGjAhIBBBAFLNdQx6dwQXVn", + "id": "newstyle:2023-01-11" + }, + "key_id": "7531", + "level": "info", + "location": "/api/marketing-info/", + "logger": "", + "measurements": { "num_of_spans": { "value": 2.0, "unit": "none" } }, + "metadata": { + "location": "/api/marketing-info/", + "title": "/api/marketing-info/" + }, + "nodestore_insert": 1709676070.473263, + "received": 1709676065.739439, + "request": { + "url": "http://service.io/api/marketing-info/", + "method": "GET", + "data": {}, + "headers": [ + ["Accept", "application/json; charset=utf-8"], + ["Accept-Encoding", "gzip, deflate, br, zstd"], + ["Accept-Language", "en-US,en;q=0.9"], + ["Content-Length", "0"], + ["Content-Type", "application/json"], + ["Host", "service.io"] + ], + "env": { + "REMOTE_ADDR": "127.0.0.1", + "SERVER_NAME": "myserver.local", + "SERVER_PORT": "9000" + }, + "inferred_content_type": "application/json" + }, + "sdk": { + "name": "sentry.python.django", + "version": "1.39.2", + "integrations": ["django"], + "packages": [{ "name": "pypi:sentry-sdk", "version": "1.39.2" }] + }, + "span_grouping_config": { "id": "default:2022-10-27" }, + "spans": [ + { + "timestamp": 1709676065.721245, + "start_timestamp": 1709676065.192259, + "exclusive_time": 0.072002, + "description": "django.middleware", + "op": "middleware.django", + "span_id": "aeba241186079368", + "parent_span_id": "9d3a7889de40f396", + "trace_id": "544bb57898d8452a55baff3a83e373", + "hash": "410f212e9e071c82", + "same_process_as_parent": true + }, + { + "timestamp": 1709676065.710968, + "start_timestamp": 1709676065.21067, + "exclusive_time": 500.298023, + "description": "SELECT \"marketing_information\".\"id\", \"marketing_information\".\"email\", \"marketing_information\".\"subscribed\", \"marketing_information\".\"updated_at\", \"marketing_information\".\"category\", \"marketing_information\".\"opted_out\" FROM \"marketing_information\" WHERE \"marketing_information\".\"email\" IN (SELECT U0.\"email\" FROM \"users\" U0 WHERE U0.\"user_id\" = %s)", + "op": "db", + "span_id": "b3da1046fed392a3", + "trace_id": "544bb57898d8452a55baff3a83e373", + "data": { + "code.filepath": "src/marketing.py", + "code.function": "do_marketing", + "code.lineno": 111, + "code.namespace": "marketing", + "db.name": "control", + "db.system": "postgresql", + "server.address": "127.0.0.1", + "server.port": "6432" + }, + "sentry_tags": { + "action": "SELECT", + "browser.name": "Google Chrome", + "category": "db", + "description": "SELECT .. FROM marketing_information WHERE email IN (SELECT email FROM users AS U0 WHERE user_id = %s)", + "domain": ",marketing_information,users,", + "environment": "prod", + "group": "e1d1cce539387892", + "op": "db", + "platform": "python", + "sdk.name": "sentry.python.django", + "sdk.version": "1.39.2", + "system": "postgresql", + "transaction": "/api/marketing-info/", + "transaction.method": "GET", + "transaction.op": "http.server", + "user": "id:1" + }, + "hash": "34d43cb5d9e46c99", + "same_process_as_parent": true + } + ], + "title": "/api/marketing-info/", + "transaction": "/api/marketing-info/", + "transaction_info": { "source": "route" }, + "type": "transaction", + "user": { + "ip_address": "127.0.0.1" + }, + "version": "7" +} diff --git a/src/sentry/templates/sentry/debug/mail/preview.html b/src/sentry/templates/sentry/debug/mail/preview.html index 19d23e0d6a1d6b..a8feca4f8a2354 100644 --- a/src/sentry/templates/sentry/debug/mail/preview.html +++ b/src/sentry/templates/sentry/debug/mail/preview.html @@ -26,6 +26,7 @@ + diff --git a/src/sentry/testutils/helpers/notifications.py b/src/sentry/testutils/helpers/notifications.py index ddfa3680b1aee3..bbead7b24f6f96 100644 --- a/src/sentry/testutils/helpers/notifications.py +++ b/src/sentry/testutils/helpers/notifications.py @@ -9,6 +9,7 @@ PerformanceNPlusOneAPICallsGroupType, PerformanceNPlusOneGroupType, PerformanceRenderBlockingAssetSpanGroupType, + PerformanceSlowDBQueryGroupType, ProfileFileIOGroupType, ) from sentry.issues.issue_occurrence import IssueEvidence, IssueOccurrence @@ -123,6 +124,32 @@ def get_title_link(self, *args): ) SAMPLE_TO_OCCURRENCE_MAP = { + "transaction-slow-db-query": IssueOccurrence( + uuid.uuid4().hex, + 1, + uuid.uuid4().hex, + ["ad800f7f33d223e714d718cb4e7d3ce1"], + "Slow DB Query", + 'SELECT "marketing_information"."id", "marketing_information"."email", "marketing_information"."subscribed", "marketing_information"."updated_at", "marketing_information"."category", "marketing_information"."opted_out" FROM "marketing_information" WHERE "marketing_information"."email" IN (SELECT U0."email" FROM "users" U0 WHERE U0."user_id" = %s)', + None, + { + "op": "db", + "cause_span_ids": [], + "parent_span_ids": [], + "offender_span_ids": [ + "b3da1046fed392a3", + ], + "transaction_name": "", + "num_repeating_spans": "1", + "repeating_spans": 'SELECT "marketing_information"."id", "marketing_information"."email", "marketing_information"."subscribed", "marketing_information"."updated_at", "marketing_information"."category", "marketing_information"."opted_out" FROM "marketing_information" WHERE "marketing_information"."email" IN (SELECT U0."email" FROM "users" U0 WHERE U0."user_id" = %s)', + "repeating_spans_compact": 'SELECT "marketing_information"."id", "marketing_information"."email", "marketing_information"."subscribed", "marketing_information"."updated_at", "marketing_information"."category", "marketing_information"."opted_out" FROM "marketing_information" WHERE "marketing_information"."email" IN (SELECT U0."email" FROM "users" U0 WHERE U0."user_id" = %s)', + }, + [], + PerformanceSlowDBQueryGroupType, + datetime.now(UTC), + "info", + "/api/marketing-info/", + ), "transaction-n-plus-one-api-call": IssueOccurrence( uuid.uuid4().hex, 1, diff --git a/src/sentry/web/frontend/debug/mail.py b/src/sentry/web/frontend/debug/mail.py index 6fae9320fde119..b6bbbb5649095c 100644 --- a/src/sentry/web/frontend/debug/mail.py +++ b/src/sentry/web/frontend/debug/mail.py @@ -30,8 +30,9 @@ from sentry.digests.utils import get_digest_metadata from sentry.event_manager import EventManager, get_event_type from sentry.http import get_server_hostname -from sentry.issues.grouptype import NoiseConfig, PerformanceNPlusOneGroupType +from sentry.issues.grouptype import NoiseConfig from sentry.issues.occurrence_consumer import process_event_and_issue_occurrence +from sentry.issues.producer import PayloadType, produce_occurrence_to_kafka from sentry.mail.notifications import get_builder_args from sentry.models.activity import Activity from sentry.models.group import Group, GroupStatus @@ -203,19 +204,22 @@ def make_performance_event(project: Project, sample_name: str): timestamp = datetime(2017, 9, 6, 0, 0) start_timestamp = timestamp - timedelta(seconds=3) event_id = "44f1419e73884cd2b45c79918f4b6dc4" - occurrence_data = SAMPLE_TO_OCCURRENCE_MAP[sample_name].to_dict() + mock_occurrence = SAMPLE_TO_OCCURRENCE_MAP[sample_name] + occurrence_data = mock_occurrence.to_dict() occurrence_data["event_id"] = event_id perf_data = dict(load_data(sample_name, start_timestamp=start_timestamp, timestamp=timestamp)) perf_data["event_id"] = event_id perf_data["project_id"] = project.id with mock.patch.object( - PerformanceNPlusOneGroupType, "noise_config", new=NoiseConfig(0, timedelta(minutes=1)) + mock_occurrence.type, "noise_config", new=NoiseConfig(0, timedelta(minutes=1)) ): occurrence, group_info = process_event_and_issue_occurrence( occurrence_data, perf_data, ) + produce_occurrence_to_kafka(payload_type=PayloadType.OCCURRENCE, occurrence=occurrence) + assert group_info is not None generic_group = group_info.group group_event = generic_group.get_latest_event()