|
@@ -0,0 +1,197 @@
|
|
|
+from datetime import timedelta
|
|
|
+from uuid import uuid4
|
|
|
+
|
|
|
+import pytz
|
|
|
+
|
|
|
+from sentry.testutils import AcceptanceTestCase, SnubaTestCase
|
|
|
+from sentry.testutils.helpers.datetime import before_now, iso_format
|
|
|
+from sentry.utils.compat.mock import patch
|
|
|
+from sentry.utils.samples import load_data
|
|
|
+
|
|
|
+FEATURE_NAMES = [
|
|
|
+ "organizations:performance-view",
|
|
|
+ "organizations:trace-view-summary",
|
|
|
+]
|
|
|
+
|
|
|
+
|
|
|
+def make_span_id() -> str:
|
|
|
+ return uuid4().hex[:16]
|
|
|
+
|
|
|
+
|
|
|
+class PerformanceTraceDetailTest(AcceptanceTestCase, SnubaTestCase):
|
|
|
+ def create_error(self, platform, trace_id, span_id, project_id, timestamp):
|
|
|
+ data = load_data(platform, timestamp=timestamp)
|
|
|
+ if "contexts" not in data:
|
|
|
+ data["contexts"] = {}
|
|
|
+ data["contexts"]["trace"] = {"type": "trace", "trace_id": trace_id, "span_id": span_id}
|
|
|
+ return self.store_event(data, project_id=project_id)
|
|
|
+
|
|
|
+ def create_transaction(
|
|
|
+ self,
|
|
|
+ transaction,
|
|
|
+ trace_id,
|
|
|
+ span_id,
|
|
|
+ parent_span_id,
|
|
|
+ spans,
|
|
|
+ project_id,
|
|
|
+ start_timestamp,
|
|
|
+ duration,
|
|
|
+ ):
|
|
|
+ timestamp = start_timestamp + timedelta(milliseconds=duration)
|
|
|
+
|
|
|
+ data = load_data(
|
|
|
+ "transaction",
|
|
|
+ trace=trace_id,
|
|
|
+ span_id=span_id,
|
|
|
+ spans=spans,
|
|
|
+ start_timestamp=start_timestamp,
|
|
|
+ timestamp=timestamp,
|
|
|
+ )
|
|
|
+ data["transaction"] = transaction
|
|
|
+ data["contexts"]["trace"]["parent_span_id"] = parent_span_id
|
|
|
+ return self.store_event(data, project_id=project_id)
|
|
|
+
|
|
|
+ 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.frontend_project = self.create_project(
|
|
|
+ organization=self.org, teams=[self.team], name="Frontend", platform="javascript"
|
|
|
+ )
|
|
|
+ self.backend_project = self.create_project(
|
|
|
+ organization=self.org, teams=[self.team], name="Backend", platform="python"
|
|
|
+ )
|
|
|
+ self.service_project = self.create_project(
|
|
|
+ organization=self.org, teams=[self.team], name="Service", platform="go"
|
|
|
+ )
|
|
|
+ self.task_project = self.create_project(
|
|
|
+ organization=self.org, teams=[self.team], name="Task", platform="rust"
|
|
|
+ )
|
|
|
+ self.login_as(self.user)
|
|
|
+
|
|
|
+ self.day_ago = before_now(days=1).replace(hour=10, minute=0, second=0, microsecond=0)
|
|
|
+ self.trace_id = uuid4().hex
|
|
|
+
|
|
|
+ self.frontend_transaction_id = make_span_id()
|
|
|
+ self.frontend_span_ids = [make_span_id() for _ in range(3)]
|
|
|
+ self.backend_transaction_ids = [make_span_id() for _ in range(3)]
|
|
|
+
|
|
|
+ # a chain of transactions that are orphans
|
|
|
+ self.task_transactions = []
|
|
|
+ last_transaction_id = make_span_id()
|
|
|
+ for i in range(5):
|
|
|
+ transaction_id = make_span_id()
|
|
|
+ timestamp = self.day_ago + timedelta(seconds=i, microseconds=30000)
|
|
|
+ self.create_error(
|
|
|
+ platform="python",
|
|
|
+ trace_id=self.trace_id,
|
|
|
+ span_id=transaction_id,
|
|
|
+ project_id=self.task_project.id,
|
|
|
+ timestamp=timestamp,
|
|
|
+ )
|
|
|
+ self.task_transactions.append(
|
|
|
+ self.create_transaction(
|
|
|
+ transaction=f"task_transaction_{i}",
|
|
|
+ trace_id=self.trace_id,
|
|
|
+ span_id=transaction_id,
|
|
|
+ parent_span_id=last_transaction_id,
|
|
|
+ spans=None,
|
|
|
+ project_id=self.task_project.id,
|
|
|
+ start_timestamp=timestamp,
|
|
|
+ duration=700 + 100 * (i + 1),
|
|
|
+ )
|
|
|
+ )
|
|
|
+ last_transaction_id = transaction_id
|
|
|
+
|
|
|
+ # two transactions attached to the same span
|
|
|
+ self.service_transaction_s = [
|
|
|
+ self.create_transaction(
|
|
|
+ transaction=f"service_transaction_{i}",
|
|
|
+ trace_id=self.trace_id,
|
|
|
+ span_id=make_span_id(),
|
|
|
+ parent_span_id=self.backend_transaction_ids[1],
|
|
|
+ spans=None,
|
|
|
+ project_id=self.service_project.id,
|
|
|
+ start_timestamp=self.day_ago
|
|
|
+ + timedelta(seconds=1, microseconds=100000 + i * 50000),
|
|
|
+ duration=750 * (i + 1),
|
|
|
+ )
|
|
|
+ for i in range(2)
|
|
|
+ ]
|
|
|
+
|
|
|
+ # single transaction attached to the root span
|
|
|
+ self.service_transaction_2 = self.create_transaction(
|
|
|
+ transaction="service_transaction_2",
|
|
|
+ trace_id=self.trace_id,
|
|
|
+ span_id=make_span_id(),
|
|
|
+ parent_span_id=self.backend_transaction_ids[2],
|
|
|
+ spans=None,
|
|
|
+ project_id=self.service_project.id,
|
|
|
+ start_timestamp=self.day_ago + timedelta(microseconds=400000),
|
|
|
+ duration=1000,
|
|
|
+ )
|
|
|
+
|
|
|
+ # 3 transactions attached to 3 different spans on the same transaction
|
|
|
+ self.backend_transactions = [
|
|
|
+ self.create_transaction(
|
|
|
+ transaction=f"backend_transaction_{i}",
|
|
|
+ trace_id=self.trace_id,
|
|
|
+ span_id=backend_transaction_id,
|
|
|
+ parent_span_id=frontend_span_id,
|
|
|
+ spans=None,
|
|
|
+ project_id=self.backend_project.id,
|
|
|
+ start_timestamp=self.day_ago + timedelta(microseconds=100000 + i * 50000),
|
|
|
+ duration=2500 + i * 500,
|
|
|
+ )
|
|
|
+ for i, (frontend_span_id, backend_transaction_id) in enumerate(
|
|
|
+ zip(self.frontend_span_ids, self.backend_transaction_ids)
|
|
|
+ )
|
|
|
+ ]
|
|
|
+
|
|
|
+ self.frontend_error = self.create_error(
|
|
|
+ platform="javascript",
|
|
|
+ trace_id=self.trace_id,
|
|
|
+ span_id=self.frontend_span_ids[1],
|
|
|
+ project_id=self.frontend_project.id,
|
|
|
+ timestamp=self.day_ago,
|
|
|
+ )
|
|
|
+ self.frontend_transaction = self.create_transaction(
|
|
|
+ transaction="frontend_transaction",
|
|
|
+ trace_id=self.trace_id,
|
|
|
+ span_id=self.frontend_transaction_id,
|
|
|
+ parent_span_id=None,
|
|
|
+ spans=[
|
|
|
+ {
|
|
|
+ "same_process_as_parent": True,
|
|
|
+ "op": "http",
|
|
|
+ "description": f"GET gen1-{i}",
|
|
|
+ "span_id": frontend_span_id,
|
|
|
+ "trace_id": self.trace_id,
|
|
|
+ }
|
|
|
+ for i, frontend_span_id in enumerate(self.frontend_span_ids)
|
|
|
+ ],
|
|
|
+ project_id=self.frontend_project.id,
|
|
|
+ start_timestamp=self.day_ago,
|
|
|
+ duration=4000,
|
|
|
+ )
|
|
|
+
|
|
|
+ @property
|
|
|
+ def path(self):
|
|
|
+ return "/organizations/{}/performance/trace/{}/?pageStart={}&pageEnd={}".format(
|
|
|
+ self.org.slug,
|
|
|
+ self.trace_id,
|
|
|
+ iso_format(before_now(days=1).replace(hour=9, minute=0, second=0, microsecond=0)),
|
|
|
+ iso_format(before_now(days=1).replace(hour=11, minute=0, second=0, microsecond=0)),
|
|
|
+ )
|
|
|
+
|
|
|
+ @patch("django.utils.timezone.now")
|
|
|
+ def test_with_data(self, mock_now):
|
|
|
+ mock_now.return_value = before_now().replace(tzinfo=pytz.utc)
|
|
|
+
|
|
|
+ with self.feature(FEATURE_NAMES):
|
|
|
+ self.browser.get(self.path)
|
|
|
+ self.browser.wait_until_not(".loading-indicator")
|
|
|
+ self.browser.elements('[data-test-id="transaction-row-title"]')[1].click()
|
|
|
+ self.browser.snapshot("performance trace view - with data")
|