test_performance_trace_detail.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. from datetime import timedelta, timezone
  2. from unittest.mock import patch
  3. from uuid import uuid4
  4. from sentry.testutils.cases import AcceptanceTestCase, SnubaTestCase
  5. from sentry.testutils.helpers.datetime import before_now, iso_format
  6. from sentry.testutils.silo import no_silo_test
  7. from sentry.utils.samples import load_data
  8. FEATURE_NAMES = ["organizations:performance-view"]
  9. def make_span_id() -> str:
  10. return uuid4().hex[:16]
  11. @no_silo_test
  12. class PerformanceTraceDetailTest(AcceptanceTestCase, SnubaTestCase):
  13. def create_error(self, platform, trace_id, span_id, project_id, timestamp):
  14. data = load_data(platform, timestamp=timestamp)
  15. if "contexts" not in data:
  16. data["contexts"] = {}
  17. data["contexts"]["trace"] = {"type": "trace", "trace_id": trace_id, "span_id": span_id}
  18. return self.store_event(data, project_id=project_id)
  19. def create_transaction(
  20. self,
  21. transaction,
  22. trace_id,
  23. span_id,
  24. parent_span_id,
  25. spans,
  26. project_id,
  27. start_timestamp,
  28. duration,
  29. transaction_id=None,
  30. ):
  31. timestamp = start_timestamp + timedelta(milliseconds=duration)
  32. data = load_data(
  33. "transaction",
  34. trace=trace_id,
  35. span_id=span_id,
  36. spans=spans,
  37. start_timestamp=start_timestamp,
  38. timestamp=timestamp,
  39. )
  40. if transaction_id is not None:
  41. data["event_id"] = transaction_id
  42. data["transaction"] = transaction
  43. data["contexts"]["trace"]["parent_span_id"] = parent_span_id
  44. return self.store_event(data, project_id=project_id)
  45. def setUp(self):
  46. super().setUp()
  47. self.org = self.create_organization(owner=self.user, name="Rowdy Tiger")
  48. self.team = self.create_team(
  49. organization=self.org, name="Mariachi Band", members=[self.user]
  50. )
  51. self.frontend_project = self.create_project(
  52. organization=self.org, teams=[self.team], name="Frontend", platform="javascript"
  53. )
  54. self.backend_project = self.create_project(
  55. organization=self.org, teams=[self.team], name="Backend", platform="python"
  56. )
  57. self.service_project = self.create_project(
  58. organization=self.org, teams=[self.team], name="Service", platform="go"
  59. )
  60. self.task_project = self.create_project(
  61. organization=self.org, teams=[self.team], name="Task", platform="rust"
  62. )
  63. self.login_as(self.user)
  64. self.day_ago = before_now(days=1).replace(hour=10, minute=0, second=0, microsecond=0)
  65. self.trace_id = "a" * 32
  66. self.frontend_transaction_id = "b" * 16
  67. self.frontend_span_ids = [make_span_id() for _ in range(3)]
  68. self.backend_transaction_ids = [make_span_id() for _ in range(3)]
  69. # a chain of transactions that are orphans
  70. self.task_transactions = []
  71. last_transaction_id = make_span_id()
  72. for i in range(3):
  73. transaction_id = make_span_id()
  74. timestamp = self.day_ago + timedelta(seconds=i, microseconds=30000)
  75. self.create_error(
  76. platform="python",
  77. trace_id=self.trace_id,
  78. span_id=transaction_id,
  79. project_id=self.task_project.id,
  80. timestamp=timestamp,
  81. )
  82. self.task_transactions.append(
  83. self.create_transaction(
  84. transaction=f"task_transaction_{i}",
  85. trace_id=self.trace_id,
  86. span_id=transaction_id,
  87. parent_span_id=last_transaction_id,
  88. spans=None,
  89. project_id=self.task_project.id,
  90. start_timestamp=timestamp,
  91. duration=700 + 100 * (i + 1),
  92. )
  93. )
  94. last_transaction_id = transaction_id
  95. # two transactions attached to the same span
  96. self.service_transaction_s = [
  97. self.create_transaction(
  98. transaction=f"service_transaction_{i}",
  99. trace_id=self.trace_id,
  100. span_id=make_span_id(),
  101. parent_span_id=self.backend_transaction_ids[1],
  102. spans=None,
  103. project_id=self.service_project.id,
  104. start_timestamp=self.day_ago
  105. + timedelta(seconds=1, microseconds=100000 + i * 50000),
  106. duration=750 * (i + 1),
  107. )
  108. for i in range(2)
  109. ]
  110. # single transaction attached to the root span
  111. self.service_transaction_2 = self.create_transaction(
  112. transaction="service_transaction_2",
  113. trace_id=self.trace_id,
  114. span_id=make_span_id(),
  115. parent_span_id=self.backend_transaction_ids[2],
  116. spans=None,
  117. project_id=self.service_project.id,
  118. start_timestamp=self.day_ago + timedelta(microseconds=400000),
  119. duration=1000,
  120. )
  121. # 3 transactions attached to 3 different spans on the same transaction
  122. self.backend_transactions = [
  123. self.create_transaction(
  124. transaction=f"backend_transaction_{i}",
  125. trace_id=self.trace_id,
  126. span_id=backend_transaction_id,
  127. parent_span_id=frontend_span_id,
  128. spans=None,
  129. project_id=self.backend_project.id,
  130. start_timestamp=self.day_ago + timedelta(microseconds=100000 + i * 50000),
  131. duration=2500 + i * 500,
  132. )
  133. for i, (frontend_span_id, backend_transaction_id) in enumerate(
  134. zip(self.frontend_span_ids, self.backend_transaction_ids)
  135. )
  136. ]
  137. self.frontend_error = self.create_error(
  138. platform="javascript",
  139. trace_id=self.trace_id,
  140. span_id=self.frontend_span_ids[1],
  141. project_id=self.frontend_project.id,
  142. timestamp=self.day_ago,
  143. )
  144. self.frontend_transaction = self.create_transaction(
  145. transaction="frontend_transaction",
  146. trace_id=self.trace_id,
  147. span_id=self.frontend_transaction_id,
  148. parent_span_id=None,
  149. spans=[
  150. {
  151. "same_process_as_parent": True,
  152. "op": "http",
  153. "description": f"GET gen1-{i}",
  154. "span_id": frontend_span_id,
  155. "trace_id": self.trace_id,
  156. }
  157. for i, frontend_span_id in enumerate(self.frontend_span_ids)
  158. ],
  159. project_id=self.frontend_project.id,
  160. start_timestamp=self.day_ago,
  161. duration=4000,
  162. transaction_id="c" * 32,
  163. )
  164. @property
  165. def path(self):
  166. return "/organizations/{}/performance/trace/{}/?pageStart={}&pageEnd={}".format(
  167. self.org.slug,
  168. self.trace_id,
  169. iso_format(before_now(days=1).replace(hour=9, minute=0, second=0, microsecond=0)),
  170. iso_format(before_now(days=1).replace(hour=11, minute=0, second=0, microsecond=0)),
  171. )
  172. @patch("django.utils.timezone.now")
  173. def test_with_data(self, mock_now):
  174. mock_now.return_value = before_now().replace(tzinfo=timezone.utc)
  175. with self.feature(FEATURE_NAMES):
  176. self.browser.get(self.path)
  177. self.browser.wait_until_not('[data-test-id="loading-indicator"]')
  178. row_title = self.browser.elements('[data-test-id="transaction-row-title"]')[1]
  179. # HACK: Use JavaScript to execute click to avoid click intercepted issues
  180. self.browser.driver.execute_script("arguments[0].click()", row_title)