123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143 |
- import random
- import string
- from datetime import timedelta
- from unittest import mock
- from unittest.mock import patch
- import pytz
- from fixtures.page_objects.issue_details import IssueDetailsPage
- from sentry import options
- from sentry.issues.grouptype import (
- NoiseConfig,
- PerformanceNPlusOneAPICallsGroupType,
- PerformanceNPlusOneGroupType,
- )
- from sentry.issues.ingest import send_issue_occurrence_to_eventstream
- from sentry.models import Group
- from sentry.testutils.cases import AcceptanceTestCase, PerformanceIssueTestCase, SnubaTestCase
- from sentry.testutils.helpers.datetime import before_now
- from sentry.utils import json
- FEATURES = {
- "projects:performance-suspect-spans-ingestion": True,
- "organizations:performance-n-plus-one-api-calls-detector": True,
- }
- class PerformanceIssuesTest(AcceptanceTestCase, SnubaTestCase, PerformanceIssueTestCase):
- def setUp(self):
- super().setUp()
- self.org = self.create_organization(owner=self.user, name="Rowdy Tiger")
- self.team = self.create_team(
- organization=self.org, name="Mariachi Band", members=[self.user]
- )
- self.project = self.create_project(organization=self.org, teams=[self.team], name="Bengal")
- self.login_as(self.user)
- options.set("performance.issues.all.problem-detection", 1.0)
- options.set("performance.issues.n_plus_one_db.problem-creation", 1.0)
- options.set("performance.issues.n_plus_one_api_calls.problem-creation", 1.0)
- self.page = IssueDetailsPage(self.browser, self.client)
- self.dismiss_assistant()
- def create_sample_event(self, fixture, start_timestamp):
- event = json.loads(self.load_fixture(f"events/performance_problems/{fixture}.json"))
- for key in ["datetime", "location", "title"]:
- del event[key]
- event["contexts"] = {
- "trace": {"trace_id": "530c14e044aa464db6ddb43660e6474f", "span_id": "139fcdb7c5534eb4"}
- }
- ms_delta = start_timestamp - event["start_timestamp"]
- for item in [event, *event["spans"]]:
- item["start_timestamp"] += ms_delta
- item["timestamp"] += ms_delta
- return event
- def randomize_span_description(self, span):
- return {
- **span,
- "description": "".join(random.choice(string.ascii_lowercase) for _ in range(10)),
- }
- @patch("django.utils.timezone.now")
- def test_with_one_performance_issue(self, mock_now):
- mock_now.return_value = before_now(minutes=5).replace(tzinfo=pytz.utc)
- event_data = self.create_sample_event(
- "n-plus-one-in-django-new-view", mock_now.return_value.timestamp()
- )
- with self.feature(FEATURES), mock.patch(
- "sentry.issues.ingest.send_issue_occurrence_to_eventstream",
- side_effect=send_issue_occurrence_to_eventstream,
- ) as mock_eventstream, mock.patch.object(
- PerformanceNPlusOneGroupType,
- "noise_config",
- new=NoiseConfig(0, timedelta(minutes=1)),
- ), self.feature(
- "organizations:issue-platform"
- ):
- self.store_event(data=event_data, project_id=self.project.id)
- group = mock_eventstream.call_args[0][2].group
- self.page.visit_issue(self.org.slug, group.id)
- self.browser.snapshot("performance issue details", desktop_only=True)
- @patch("django.utils.timezone.now")
- def test_multiple_events_with_one_cause_are_grouped(self, mock_now):
- mock_now.return_value = before_now(minutes=5).replace(tzinfo=pytz.utc)
- event_data = self.create_sample_event(
- "n-plus-one-in-django-new-view", mock_now.return_value.timestamp()
- )
- self.create_performance_issue(event_data=event_data)
- assert Group.objects.count() == 1
- @patch("django.utils.timezone.now")
- def test_n_one_api_call_performance_issue(self, mock_now):
- mock_now.return_value = before_now(minutes=5).replace(tzinfo=pytz.utc)
- event_data = self.create_sample_event(
- "n-plus-one-api-calls/n-plus-one-api-calls-in-issue-stream",
- mock_now.return_value.timestamp(),
- )
- event_data["contexts"]["trace"]["op"] = "navigation"
- with self.feature(FEATURES), mock.patch(
- "sentry.issues.ingest.send_issue_occurrence_to_eventstream",
- side_effect=send_issue_occurrence_to_eventstream,
- ) as mock_eventstream, mock.patch.object(
- PerformanceNPlusOneAPICallsGroupType,
- "noise_config",
- new=NoiseConfig(0, timedelta(minutes=1)),
- ), self.feature(
- "organizations:issue-platform"
- ):
- self.store_event(data=event_data, project_id=self.project.id)
- group = mock_eventstream.call_args[0][2].group
- self.page.visit_issue(self.org.slug, group.id)
- self.browser.snapshot("N+1 API Call issue details", desktop_only=True)
- @patch("django.utils.timezone.now")
- def test_multiple_events_with_multiple_causes_are_not_grouped(self, mock_now):
- mock_now.return_value = before_now(minutes=5).replace(tzinfo=pytz.utc)
- # Create identical events with different parent spans
- for _ in range(3):
- event_data = self.create_sample_event(
- "n-plus-one-in-django-new-view", mock_now.return_value.timestamp()
- )
- event_data["spans"] = [
- self.randomize_span_description(span) if span["op"] == "django.view" else span
- for span in event_data["spans"]
- ]
- self.create_performance_issue(event_data=event_data)
- assert Group.objects.count() == 3
|