test_performance_issues.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import random
  2. import string
  3. from datetime import timedelta
  4. from unittest import mock
  5. from unittest.mock import patch
  6. import pytz
  7. from fixtures.page_objects.issue_details import IssueDetailsPage
  8. from sentry import options
  9. from sentry.issues.grouptype import (
  10. NoiseConfig,
  11. PerformanceNPlusOneAPICallsGroupType,
  12. PerformanceNPlusOneGroupType,
  13. )
  14. from sentry.issues.ingest import send_issue_occurrence_to_eventstream
  15. from sentry.models import Group
  16. from sentry.testutils import AcceptanceTestCase, SnubaTestCase
  17. from sentry.testutils.helpers.datetime import before_now
  18. from sentry.utils import json
  19. FEATURES = {
  20. "projects:performance-suspect-spans-ingestion": True,
  21. "organizations:performance-n-plus-one-api-calls-detector": True,
  22. }
  23. class PerformanceIssuesTest(AcceptanceTestCase, SnubaTestCase):
  24. def setUp(self):
  25. super().setUp()
  26. self.org = self.create_organization(owner=self.user, name="Rowdy Tiger")
  27. self.team = self.create_team(
  28. organization=self.org, name="Mariachi Band", members=[self.user]
  29. )
  30. self.project = self.create_project(organization=self.org, teams=[self.team], name="Bengal")
  31. self.login_as(self.user)
  32. options.set("performance.issues.all.problem-detection", 1.0)
  33. options.set("performance.issues.n_plus_one_db.problem-creation", 1.0)
  34. options.set("performance.issues.n_plus_one_api_calls.problem-creation", 1.0)
  35. self.page = IssueDetailsPage(self.browser, self.client)
  36. self.dismiss_assistant()
  37. def create_sample_event(self, fixture, start_timestamp):
  38. event = json.loads(self.load_fixture(f"events/performance_problems/{fixture}.json"))
  39. for key in ["datetime", "location", "title"]:
  40. del event[key]
  41. event["contexts"] = {
  42. "trace": {"trace_id": "530c14e044aa464db6ddb43660e6474f", "span_id": "139fcdb7c5534eb4"}
  43. }
  44. ms_delta = start_timestamp - event["start_timestamp"]
  45. for item in [event, *event["spans"]]:
  46. item["start_timestamp"] += ms_delta
  47. item["timestamp"] += ms_delta
  48. return event
  49. def randomize_span_description(self, span):
  50. return {
  51. **span,
  52. "description": "".join(random.choice(string.ascii_lowercase) for _ in range(10)),
  53. }
  54. @patch("django.utils.timezone.now")
  55. def test_with_one_performance_issue(self, mock_now):
  56. mock_now.return_value = before_now(minutes=5).replace(tzinfo=pytz.utc)
  57. event_data = self.create_sample_event(
  58. "n-plus-one-in-django-new-view", mock_now.return_value.timestamp()
  59. )
  60. with self.feature(FEATURES), mock.patch(
  61. "sentry.issues.ingest.send_issue_occurrence_to_eventstream",
  62. side_effect=send_issue_occurrence_to_eventstream,
  63. ) as mock_eventstream, mock.patch.object(
  64. PerformanceNPlusOneGroupType,
  65. "noise_config",
  66. new=NoiseConfig(0, timedelta(minutes=1)),
  67. ), self.options(
  68. {
  69. "performance.issues.send_to_issues_platform": True,
  70. "performance.issues.create_issues_through_platform": True,
  71. }
  72. ), self.feature(
  73. "organizations:issue-platform"
  74. ):
  75. self.store_event(data=event_data, project_id=self.project.id)
  76. group = mock_eventstream.call_args[0][2].group
  77. self.page.visit_issue(self.org.slug, group.id)
  78. self.browser.snapshot("performance issue details", desktop_only=True)
  79. @patch("django.utils.timezone.now")
  80. def test_multiple_events_with_one_cause_are_grouped(self, mock_now):
  81. mock_now.return_value = before_now(minutes=5).replace(tzinfo=pytz.utc)
  82. event_data = self.create_sample_event(
  83. "n-plus-one-in-django-new-view", mock_now.return_value.timestamp()
  84. )
  85. with self.feature(FEATURES):
  86. [self.store_event(data=event_data, project_id=self.project.id) for _ in range(3)]
  87. assert Group.objects.count() == 1
  88. @patch("django.utils.timezone.now")
  89. def test_n_one_api_call_performance_issue(self, mock_now):
  90. mock_now.return_value = before_now(minutes=5).replace(tzinfo=pytz.utc)
  91. event_data = self.create_sample_event(
  92. "n-plus-one-api-calls/n-plus-one-api-calls-in-issue-stream",
  93. mock_now.return_value.timestamp(),
  94. )
  95. event_data["contexts"]["trace"]["op"] = "navigation"
  96. with self.feature(FEATURES), mock.patch(
  97. "sentry.issues.ingest.send_issue_occurrence_to_eventstream",
  98. side_effect=send_issue_occurrence_to_eventstream,
  99. ) as mock_eventstream, mock.patch.object(
  100. PerformanceNPlusOneAPICallsGroupType,
  101. "noise_config",
  102. new=NoiseConfig(0, timedelta(minutes=1)),
  103. ), self.options(
  104. {
  105. "performance.issues.send_to_issues_platform": True,
  106. "performance.issues.create_issues_through_platform": True,
  107. }
  108. ), self.feature(
  109. "organizations:issue-platform"
  110. ):
  111. self.store_event(data=event_data, project_id=self.project.id)
  112. group = mock_eventstream.call_args[0][2].group
  113. self.page.visit_issue(self.org.slug, group.id)
  114. self.browser.snapshot("N+1 API Call issue details", desktop_only=True)
  115. @patch("django.utils.timezone.now")
  116. def test_multiple_events_with_multiple_causes_are_not_grouped(self, mock_now):
  117. mock_now.return_value = before_now(minutes=5).replace(tzinfo=pytz.utc)
  118. # Create identical events with different parent spans
  119. for _ in range(3):
  120. event_data = self.create_sample_event(
  121. "n-plus-one-in-django-new-view", mock_now.return_value.timestamp()
  122. )
  123. event_data["spans"] = [
  124. self.randomize_span_description(span) if span["op"] == "django.view" else span
  125. for span in event_data["spans"]
  126. ]
  127. with self.feature(FEATURES):
  128. self.store_event(data=event_data, project_id=self.project.id)
  129. assert Group.objects.count() == 3