test_performance_issues.py 4.5 KB

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