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()