test_performance_issues.py 5.2 KB

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