test_organization_events_trace.py 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468
  1. from datetime import timedelta
  2. from unittest import mock
  3. from uuid import uuid4
  4. import pytest
  5. from django.urls import NoReverseMatch, reverse
  6. from sentry import options
  7. from sentry.issues.grouptype import NoiseConfig, PerformanceFileIOMainThreadGroupType
  8. from sentry.testutils.cases import APITestCase, SnubaTestCase
  9. from sentry.testutils.helpers import override_options
  10. from sentry.testutils.helpers.datetime import before_now, iso_format
  11. from sentry.testutils.silo import region_silo_test
  12. from sentry.utils.dates import to_timestamp_from_iso_format
  13. from sentry.utils.samples import load_data
  14. class OrganizationEventsTraceEndpointBase(APITestCase, SnubaTestCase):
  15. url_name: str
  16. FEATURES = [
  17. "organizations:performance-view",
  18. "organizations:performance-file-io-main-thread-detector",
  19. ]
  20. def get_start_end(self, duration):
  21. return self.day_ago, self.day_ago + timedelta(milliseconds=duration)
  22. def create_event(
  23. self,
  24. trace,
  25. transaction,
  26. spans,
  27. parent_span_id,
  28. project_id,
  29. tags=None,
  30. duration=4000,
  31. span_id=None,
  32. measurements=None,
  33. file_io_performance_issue=False,
  34. **kwargs,
  35. ):
  36. start, end = self.get_start_end(duration)
  37. data = load_data(
  38. "transaction",
  39. trace=trace,
  40. spans=spans,
  41. timestamp=end,
  42. start_timestamp=start,
  43. )
  44. data["transaction"] = transaction
  45. data["contexts"]["trace"]["parent_span_id"] = parent_span_id
  46. if span_id:
  47. data["contexts"]["trace"]["span_id"] = span_id
  48. if measurements:
  49. for key, value in measurements.items():
  50. data["measurements"][key]["value"] = value
  51. if tags is not None:
  52. data["tags"] = tags
  53. if file_io_performance_issue:
  54. span = data["spans"][0]
  55. if "data" not in span:
  56. span["data"] = {}
  57. span["op"] = "file.write"
  58. span["data"].update({"duration": 1, "blocked_main_thread": True})
  59. with self.feature(self.FEATURES):
  60. with mock.patch.object(
  61. PerformanceFileIOMainThreadGroupType,
  62. "noise_config",
  63. new=NoiseConfig(0, timedelta(minutes=1)),
  64. ), override_options(
  65. {
  66. "performance.issues.all.problem-detection": 1.0,
  67. "performance-file-io-main-thread-creation": 1.0,
  68. }
  69. ), self.feature(
  70. ["projects:performance-suspect-spans-ingestion"]
  71. ):
  72. return self.store_event(data, project_id=project_id, **kwargs)
  73. def setUp(self):
  74. """
  75. Span structure:
  76. root
  77. gen1-0
  78. gen2-0
  79. gen3-0
  80. gen1-1
  81. gen2-1
  82. gen1-2
  83. gen2-2
  84. """
  85. super().setUp()
  86. options.set("performance.issues.all.problem-detection", 1.0)
  87. options.set("performance.issues.file_io_main_thread.problem-creation", 1.0)
  88. self.login_as(user=self.user)
  89. self.day_ago = before_now(days=1).replace(hour=10, minute=0, second=0, microsecond=0)
  90. self.root_span_ids = [uuid4().hex[:16] for _ in range(3)]
  91. self.trace_id = uuid4().hex
  92. self.url = reverse(
  93. self.url_name,
  94. kwargs={"organization_slug": self.project.organization.slug, "trace_id": self.trace_id},
  95. )
  96. def load_trace(self):
  97. self.root_event = self.create_event(
  98. trace=self.trace_id,
  99. transaction="root",
  100. spans=[
  101. {
  102. "same_process_as_parent": True,
  103. "op": "http",
  104. "description": f"GET gen1-{i}",
  105. "span_id": root_span_id,
  106. "trace_id": self.trace_id,
  107. }
  108. for i, root_span_id in enumerate(self.root_span_ids)
  109. ],
  110. measurements={
  111. "lcp": 1000,
  112. "fcp": 750,
  113. },
  114. parent_span_id=None,
  115. file_io_performance_issue=True,
  116. project_id=self.project.id,
  117. duration=3000,
  118. )
  119. # First Generation
  120. self.gen1_span_ids = [uuid4().hex[:16] for _ in range(3)]
  121. self.gen1_project = self.create_project(organization=self.organization)
  122. self.gen1_events = [
  123. self.create_event(
  124. trace=self.trace_id,
  125. transaction=f"/transaction/gen1-{i}",
  126. spans=[
  127. {
  128. "same_process_as_parent": True,
  129. "op": "http",
  130. "description": f"GET gen2-{i}",
  131. "span_id": gen1_span_id,
  132. "trace_id": self.trace_id,
  133. }
  134. ],
  135. parent_span_id=root_span_id,
  136. project_id=self.gen1_project.id,
  137. duration=2000,
  138. )
  139. for i, (root_span_id, gen1_span_id) in enumerate(
  140. zip(self.root_span_ids, self.gen1_span_ids)
  141. )
  142. ]
  143. # Second Generation
  144. self.gen2_span_ids = [uuid4().hex[:16] for _ in range(3)]
  145. self.gen2_project = self.create_project(organization=self.organization)
  146. # Intentially pick a span id that starts with 0s
  147. self.gen2_span_id = "0011" * 4
  148. self.gen2_events = [
  149. self.create_event(
  150. trace=self.trace_id,
  151. transaction=f"/transaction/gen2-{i}",
  152. spans=[
  153. {
  154. "same_process_as_parent": True,
  155. "op": "http",
  156. "description": f"GET gen3-{i}" if i == 0 else f"SPAN gen3-{i}",
  157. "span_id": gen2_span_id,
  158. "trace_id": self.trace_id,
  159. }
  160. ],
  161. parent_span_id=gen1_span_id,
  162. span_id=self.gen2_span_id if i == 0 else None,
  163. project_id=self.gen2_project.id,
  164. duration=1000,
  165. )
  166. for i, (gen1_span_id, gen2_span_id) in enumerate(
  167. zip(self.gen1_span_ids, self.gen2_span_ids)
  168. )
  169. ]
  170. # Third generation
  171. self.gen3_project = self.create_project(organization=self.organization)
  172. self.gen3_event = self.create_event(
  173. trace=self.trace_id,
  174. transaction="/transaction/gen3-0",
  175. spans=[],
  176. project_id=self.gen3_project.id,
  177. parent_span_id=self.gen2_span_id,
  178. duration=500,
  179. )
  180. def load_errors(self):
  181. start, _ = self.get_start_end(1000)
  182. error_data = load_data(
  183. "javascript",
  184. timestamp=start,
  185. )
  186. error_data["contexts"]["trace"] = {
  187. "type": "trace",
  188. "trace_id": self.trace_id,
  189. "span_id": self.gen1_span_ids[0],
  190. }
  191. error_data["level"] = "fatal"
  192. error = self.store_event(error_data, project_id=self.gen1_project.id)
  193. error_data["level"] = "warning"
  194. error1 = self.store_event(error_data, project_id=self.gen1_project.id)
  195. return error, error1
  196. def load_default(self):
  197. start, _ = self.get_start_end(1000)
  198. return self.store_event(
  199. {
  200. "timestamp": iso_format(start),
  201. "contexts": {
  202. "trace": {
  203. "type": "trace",
  204. "trace_id": self.trace_id,
  205. "span_id": self.root_span_ids[0],
  206. },
  207. },
  208. "level": "debug",
  209. "message": "this is a log message",
  210. },
  211. project_id=self.gen1_project.id,
  212. )
  213. @region_silo_test
  214. class OrganizationEventsTraceLightEndpointTest(OrganizationEventsTraceEndpointBase):
  215. url_name = "sentry-api-0-organization-events-trace-light"
  216. def test_no_projects(self):
  217. user = self.create_user()
  218. org = self.create_organization(owner=user)
  219. self.login_as(user=user)
  220. url = reverse(
  221. self.url_name,
  222. kwargs={"organization_slug": org.slug, "trace_id": uuid4().hex},
  223. )
  224. with self.feature(self.FEATURES):
  225. response = self.client.get(
  226. url,
  227. format="json",
  228. )
  229. assert response.status_code == 404, response.content
  230. def test_bad_ids(self):
  231. # Fake event id
  232. with self.feature(self.FEATURES):
  233. response = self.client.get(
  234. self.url,
  235. data={"event_id": uuid4().hex},
  236. format="json",
  237. )
  238. assert response.status_code == 404, response.content
  239. # Invalid event id
  240. with self.feature(self.FEATURES):
  241. response = self.client.get(
  242. self.url,
  243. data={"event_id": "not-a-event"},
  244. format="json",
  245. )
  246. assert response.status_code == 400, response.content
  247. # Fake trace id
  248. self.url = reverse(
  249. "sentry-api-0-organization-events-trace-light",
  250. kwargs={"organization_slug": self.project.organization.slug, "trace_id": uuid4().hex},
  251. )
  252. with self.feature(self.FEATURES):
  253. response = self.client.get(
  254. self.url,
  255. data={"event_id": "a" * 32},
  256. format="json",
  257. )
  258. assert response.status_code == 404, response.content
  259. # Invalid trace id
  260. with pytest.raises(NoReverseMatch):
  261. self.url = reverse(
  262. "sentry-api-0-organization-events-trace-light",
  263. kwargs={
  264. "organization_slug": self.project.organization.slug,
  265. "trace_id": "not-a-trace",
  266. },
  267. )
  268. def test_no_roots(self):
  269. """Even when there's no root, we return the current event"""
  270. self.load_trace()
  271. no_root_trace = uuid4().hex
  272. parent_span_id = uuid4().hex[:16]
  273. no_root_event = self.create_event(
  274. trace=no_root_trace,
  275. transaction="/not_root/but_only_transaction",
  276. spans=[],
  277. parent_span_id=parent_span_id,
  278. project_id=self.project.id,
  279. )
  280. url = reverse(
  281. "sentry-api-0-organization-events-trace-light",
  282. kwargs={"organization_slug": self.project.organization.slug, "trace_id": no_root_trace},
  283. )
  284. with self.feature(self.FEATURES):
  285. response = self.client.get(
  286. url,
  287. data={"event_id": no_root_event.event_id},
  288. format="json",
  289. )
  290. assert response.status_code == 200, response.content
  291. assert len(response.data) == 1
  292. event = response.data[0]
  293. # Basically know nothing about this event
  294. assert event["generation"] is None
  295. assert event["parent_event_id"] is None
  296. assert event["parent_span_id"] == parent_span_id
  297. assert event["event_id"] == no_root_event.event_id
  298. def test_multiple_roots(self):
  299. self.load_trace()
  300. second_root = self.create_event(
  301. trace=self.trace_id,
  302. transaction="/second_root",
  303. spans=[],
  304. parent_span_id=None,
  305. project_id=self.project.id,
  306. )
  307. with self.feature(self.FEATURES):
  308. response = self.client.get(
  309. self.url,
  310. data={"event_id": second_root.event_id, "project": -1},
  311. format="json",
  312. )
  313. assert response.status_code == 200, response.content
  314. assert len(response.data) == 1
  315. event = response.data[0]
  316. assert event["generation"] == 0
  317. assert event["parent_event_id"] is None
  318. assert event["parent_span_id"] is None
  319. def test_root_event(self):
  320. self.load_trace()
  321. root_event_id = self.root_event.event_id
  322. with self.feature(self.FEATURES):
  323. response = self.client.get(
  324. self.url,
  325. data={"event_id": root_event_id, "project": -1},
  326. format="json",
  327. )
  328. assert response.status_code == 200, response.content
  329. assert len(response.data) == 4
  330. events = {item["event_id"]: item for item in response.data}
  331. assert root_event_id in events
  332. event = events[root_event_id]
  333. assert event["generation"] == 0
  334. assert event["parent_event_id"] is None
  335. assert event["parent_span_id"] is None
  336. for i, child_event in enumerate(self.gen1_events):
  337. child_event_id = child_event.event_id
  338. assert child_event_id in events
  339. event = events[child_event_id]
  340. assert event["generation"] == 1
  341. assert event["parent_event_id"] == root_event_id
  342. assert event["parent_span_id"] == self.root_span_ids[i]
  343. def test_root_with_multiple_roots(self):
  344. self.load_trace()
  345. root_event_id = self.root_event.event_id
  346. self.create_event(
  347. trace=self.trace_id,
  348. transaction="/second_root",
  349. spans=[],
  350. parent_span_id=None,
  351. project_id=self.project.id,
  352. )
  353. with self.feature(self.FEATURES):
  354. response = self.client.get(
  355. self.url,
  356. data={"event_id": self.root_event.event_id},
  357. format="json",
  358. )
  359. assert response.status_code == 200, response.content
  360. assert len(response.data) == 4
  361. events = {item["event_id"]: item for item in response.data}
  362. assert root_event_id in events
  363. event = events[root_event_id]
  364. assert event["generation"] == 0
  365. assert event["parent_event_id"] is None
  366. assert event["parent_span_id"] is None
  367. for i, child_event in enumerate(self.gen1_events):
  368. child_event_id = child_event.event_id
  369. assert child_event_id in events
  370. event = events[child_event_id]
  371. assert event["generation"] == 1
  372. assert event["parent_event_id"] == root_event_id
  373. assert event["parent_span_id"] == self.root_span_ids[i]
  374. def test_direct_parent_with_children(self):
  375. self.load_trace()
  376. root_event_id = self.root_event.event_id
  377. current_event = self.gen1_events[0].event_id
  378. child_event_id = self.gen2_events[0].event_id
  379. with self.feature(self.FEATURES):
  380. response = self.client.get(
  381. self.url,
  382. data={"event_id": current_event, "project": -1},
  383. format="json",
  384. )
  385. assert response.status_code == 200, response.content
  386. assert len(response.data) == 3
  387. events = {item["event_id"]: item for item in response.data}
  388. assert root_event_id in events
  389. event = events[root_event_id]
  390. assert event["generation"] == 0
  391. assert event["parent_event_id"] is None
  392. assert event["parent_span_id"] is None
  393. assert current_event in events
  394. event = events[current_event]
  395. assert event["generation"] == 1
  396. assert event["parent_event_id"] == root_event_id
  397. assert event["parent_span_id"] == self.root_span_ids[0]
  398. assert child_event_id in events
  399. event = events[child_event_id]
  400. assert event["generation"] == 2
  401. assert event["parent_event_id"] == current_event
  402. assert event["parent_span_id"] == self.gen1_span_ids[0]
  403. def test_direct_parent_with_children_and_multiple_root(self):
  404. self.load_trace()
  405. root_event_id = self.root_event.event_id
  406. current_event = self.gen1_events[0].event_id
  407. child_event_id = self.gen2_events[0].event_id
  408. self.create_event(
  409. trace=self.trace_id,
  410. transaction="/second_root",
  411. spans=[],
  412. parent_span_id=None,
  413. project_id=self.project.id,
  414. )
  415. with self.feature(self.FEATURES):
  416. response = self.client.get(
  417. self.url,
  418. data={"event_id": current_event, "project": -1},
  419. format="json",
  420. )
  421. assert response.status_code == 200, response.content
  422. assert len(response.data) == 3
  423. events = {item["event_id"]: item for item in response.data}
  424. assert root_event_id in events
  425. event = events[root_event_id]
  426. assert event["generation"] == 0
  427. assert event["parent_event_id"] is None
  428. assert event["parent_span_id"] is None
  429. assert current_event in events
  430. event = events[current_event]
  431. assert event["generation"] == 1
  432. assert event["parent_event_id"] == root_event_id
  433. assert event["parent_span_id"] == self.root_span_ids[0]
  434. assert child_event_id in events
  435. event = events[child_event_id]
  436. assert event["generation"] == 2
  437. assert event["parent_event_id"] == current_event
  438. assert event["parent_span_id"] == self.gen1_span_ids[0]
  439. def test_second_generation_with_children(self):
  440. self.load_trace()
  441. current_event = self.gen2_events[0].event_id
  442. child_event_id = self.gen3_event.event_id
  443. with self.feature(self.FEATURES):
  444. response = self.client.get(
  445. self.url,
  446. data={"event_id": current_event, "project": -1},
  447. format="json",
  448. )
  449. assert response.status_code == 200, response.content
  450. assert len(response.data) == 2
  451. events = {item["event_id"]: item for item in response.data}
  452. assert current_event in events
  453. event = events[current_event]
  454. # Parent/generation is unknown in this case
  455. assert event["generation"] is None
  456. assert event["parent_event_id"] is None
  457. # But we still know the parent_span
  458. assert event["parent_span_id"] == self.gen1_span_ids[0]
  459. assert child_event_id in events
  460. event = events[child_event_id]
  461. assert event["generation"] is None
  462. assert event["parent_event_id"] == current_event
  463. assert event["parent_span_id"] == self.gen2_span_id
  464. def test_third_generation_no_children(self):
  465. self.load_trace()
  466. current_event = self.gen3_event.event_id
  467. with self.feature(self.FEATURES):
  468. response = self.client.get(
  469. self.url,
  470. data={"event_id": current_event, "project": -1},
  471. format="json",
  472. )
  473. assert response.status_code == 200, response.content
  474. assert len(response.data) == 1
  475. event = response.data[0]
  476. assert event["generation"] is None
  477. # Parent is unknown in this case
  478. assert event["parent_event_id"] is None
  479. # But we still know the parent_span
  480. assert event["parent_span_id"] == self.gen2_span_id
  481. def test_sibling_transactions(self):
  482. """More than one transaction can share a parent_span_id"""
  483. self.load_trace()
  484. gen3_event_siblings = [
  485. self.create_event(
  486. trace=self.trace_id,
  487. transaction="/transaction/gen3-1",
  488. spans=[],
  489. project_id=self.create_project(organization=self.organization).id,
  490. parent_span_id=self.gen2_span_ids[1],
  491. duration=500,
  492. ).event_id,
  493. self.create_event(
  494. trace=self.trace_id,
  495. transaction="/transaction/gen3-2",
  496. spans=[],
  497. project_id=self.create_project(organization=self.organization).id,
  498. parent_span_id=self.gen2_span_ids[1],
  499. duration=525,
  500. ).event_id,
  501. ]
  502. current_event = self.gen2_events[1].event_id
  503. with self.feature(self.FEATURES):
  504. response = self.client.get(
  505. self.url,
  506. data={"event_id": current_event, "project": -1},
  507. format="json",
  508. )
  509. assert len(response.data) == 3
  510. events = {item["event_id"]: item for item in response.data}
  511. for child_event_id in gen3_event_siblings:
  512. assert child_event_id in events
  513. event = events[child_event_id]
  514. assert event["generation"] is None
  515. assert event["parent_event_id"] == current_event
  516. assert event["parent_span_id"] == self.gen2_span_ids[1]
  517. def test_with_error_event(self):
  518. self.load_trace()
  519. root_event_id = self.root_event.event_id
  520. current_transaction_event = self.gen1_events[0].event_id
  521. start, _ = self.get_start_end(1000)
  522. error_data = load_data(
  523. "javascript",
  524. timestamp=start,
  525. )
  526. error_data["contexts"]["trace"] = {
  527. "type": "trace",
  528. "trace_id": self.trace_id,
  529. "span_id": self.gen1_span_ids[0],
  530. }
  531. error_data["tags"] = [["transaction", "/transaction/gen1-0"]]
  532. error = self.store_event(error_data, project_id=self.gen1_project.id)
  533. def assertions(response):
  534. assert response.status_code == 200, response.content
  535. assert len(response.data) == 3
  536. events = {item["event_id"]: item for item in response.data}
  537. assert root_event_id in events
  538. event = events[root_event_id]
  539. assert event["generation"] == 0
  540. assert event["parent_event_id"] is None
  541. assert event["parent_span_id"] is None
  542. assert len(event["errors"]) == 0
  543. assert current_transaction_event in events
  544. event = events[current_transaction_event]
  545. assert event["generation"] == 1
  546. assert event["parent_event_id"] == root_event_id
  547. assert event["parent_span_id"] == self.root_span_ids[0]
  548. assert len(event["errors"]) == 1
  549. assert event["errors"][0]["event_id"] == error.event_id
  550. assert event["errors"][0]["issue_id"] == error.group_id
  551. with self.feature(self.FEATURES):
  552. response = self.client.get(
  553. self.url,
  554. data={"event_id": error.event_id, "project": -1},
  555. format="json",
  556. )
  557. assertions(response)
  558. with self.feature(self.FEATURES):
  559. response = self.client.get(
  560. self.url,
  561. data={"event_id": current_transaction_event, "project": -1},
  562. format="json",
  563. )
  564. assertions(response)
  565. @region_silo_test
  566. class OrganizationEventsTraceEndpointTest(OrganizationEventsTraceEndpointBase):
  567. url_name = "sentry-api-0-organization-events-trace"
  568. def assert_event(self, result, event_data, message):
  569. assert result["event_id"] == event_data.event_id, message
  570. assert result["timestamp"] == event_data.data["timestamp"], message
  571. assert result["start_timestamp"] == event_data.data["start_timestamp"], message
  572. def assert_trace_data(self, root, gen2_no_children=True):
  573. """see the setUp docstring for an idea of what the response structure looks like"""
  574. self.assert_event(root, self.root_event, "root")
  575. assert root["parent_event_id"] is None
  576. assert root["parent_span_id"] is None
  577. assert root["generation"] == 0
  578. assert root["transaction.duration"] == 3000
  579. assert len(root["children"]) == 3
  580. assert len(root["performance_issues"]) == 1
  581. assert root["performance_issues"][0]["suspect_spans"][0] == self.root_span_ids[0]
  582. for i, gen1 in enumerate(root["children"]):
  583. self.assert_event(gen1, self.gen1_events[i], f"gen1_{i}")
  584. assert gen1["parent_event_id"] == self.root_event.event_id
  585. assert gen1["parent_span_id"] == self.root_span_ids[i]
  586. assert gen1["generation"] == 1
  587. assert gen1["transaction.duration"] == 2000
  588. assert len(gen1["children"]) == 1
  589. gen2 = gen1["children"][0]
  590. self.assert_event(gen2, self.gen2_events[i], f"gen2_{i}")
  591. assert gen2["parent_event_id"] == self.gen1_events[i].event_id
  592. assert gen2["parent_span_id"] == self.gen1_span_ids[i]
  593. assert gen2["generation"] == 2
  594. assert gen2["transaction.duration"] == 1000
  595. # Only the first gen2 descendent has a child
  596. if i == 0:
  597. assert len(gen2["children"]) == 1
  598. gen3 = gen2["children"][0]
  599. self.assert_event(gen3, self.gen3_event, f"gen3_{i}")
  600. assert gen3["parent_event_id"] == self.gen2_events[i].event_id
  601. assert gen3["parent_span_id"] == self.gen2_span_id
  602. assert gen3["generation"] == 3
  603. assert gen3["transaction.duration"] == 500
  604. assert len(gen3["children"]) == 0
  605. elif gen2_no_children:
  606. assert len(gen2["children"]) == 0
  607. def test_no_projects(self):
  608. user = self.create_user()
  609. org = self.create_organization(owner=user)
  610. self.login_as(user=user)
  611. url = reverse(
  612. self.url_name,
  613. kwargs={"organization_slug": org.slug, "trace_id": uuid4().hex},
  614. )
  615. with self.feature(self.FEATURES):
  616. response = self.client.get(
  617. url,
  618. format="json",
  619. )
  620. assert response.status_code == 404, response.content
  621. def test_simple(self):
  622. self.load_trace()
  623. with self.feature(self.FEATURES):
  624. response = self.client.get(
  625. self.url,
  626. data={"project": -1},
  627. format="json",
  628. )
  629. assert response.status_code == 200, response.content
  630. self.assert_trace_data(response.data[0])
  631. # We shouldn't have detailed fields here
  632. assert "transaction.status" not in response.data[0]
  633. assert "tags" not in response.data[0]
  634. assert "measurements" not in response.data[0]
  635. def test_detailed_trace(self):
  636. self.load_trace()
  637. with self.feature(self.FEATURES):
  638. response = self.client.get(
  639. self.url,
  640. data={"project": -1, "detailed": 1},
  641. format="json",
  642. )
  643. assert response.status_code == 200, response.content
  644. self.assert_trace_data(response.data[0])
  645. root = response.data[0]
  646. assert root["transaction.status"] == "ok"
  647. root_tags = {tag["key"]: tag["value"] for tag in root["tags"]}
  648. for [key, value] in self.root_event.tags:
  649. if not key.startswith("sentry:"):
  650. assert root_tags[key] == value, f"tags - {key}"
  651. else:
  652. assert root_tags[key[7:]] == value, f"tags - {key}"
  653. assert root["measurements"]["lcp"]["value"] == 1000
  654. assert root["measurements"]["fcp"]["value"] == 750
  655. assert "issue_short_id" in response.data[0]["performance_issues"][0]
  656. assert response.data[0]["performance_issues"][0]["culprit"] == "root"
  657. assert (
  658. response.data[0]["performance_issues"][0]["type"]
  659. == PerformanceFileIOMainThreadGroupType.type_id
  660. )
  661. def test_detailed_trace_with_bad_tags(self):
  662. """Basically test that we're actually using the event serializer's method for tags"""
  663. trace = uuid4().hex
  664. self.create_event(
  665. trace=trace,
  666. transaction="bad-tags",
  667. parent_span_id=None,
  668. spans=[],
  669. project_id=self.project.id,
  670. tags=[["somethinglong" * 250, "somethinglong" * 250]],
  671. duration=3000,
  672. assert_no_errors=False,
  673. )
  674. url = reverse(
  675. self.url_name,
  676. kwargs={"organization_slug": self.project.organization.slug, "trace_id": trace},
  677. )
  678. with self.feature(self.FEATURES):
  679. response = self.client.get(
  680. url,
  681. data={"project": -1, "detailed": 1},
  682. format="json",
  683. )
  684. assert response.status_code == 200, response.content
  685. root = response.data[0]
  686. assert root["transaction.status"] == "ok"
  687. assert {"key": None, "value": None} in root["tags"]
  688. def test_bad_span_loop(self):
  689. """Maliciously create a loop in the span structure
  690. Structure then becomes something like this:
  691. root
  692. gen1-0...
  693. gen1-1
  694. gen2-1
  695. gen3-1
  696. gen_2-1
  697. gen3-1...
  698. """
  699. self.load_trace()
  700. gen3_loop_event = self.create_event(
  701. trace=self.trace_id,
  702. transaction="/transaction/gen3-1/loop",
  703. spans=[
  704. {
  705. "same_process_as_parent": True,
  706. "op": "http",
  707. "description": "GET gen2-1",
  708. "span_id": self.gen1_span_ids[1],
  709. "trace_id": self.trace_id,
  710. }
  711. ],
  712. parent_span_id=self.gen2_span_ids[1],
  713. project_id=self.project.id,
  714. )
  715. with self.feature(self.FEATURES):
  716. response = self.client.get(
  717. self.url,
  718. data={"project": -1},
  719. format="json",
  720. )
  721. assert response.status_code == 200, response.content
  722. # Should be the same as the simple testcase
  723. self.assert_trace_data(response.data[0], gen2_no_children=False)
  724. # The difference is that gen3-1 should exist with no children
  725. gen2_1 = response.data[0]["children"][1]["children"][0]
  726. assert len(gen2_1["children"]) == 1
  727. gen3_1 = gen2_1["children"][0]
  728. assert gen3_1["event_id"] == gen3_loop_event.event_id
  729. # We didn't even try to start the loop of spans
  730. assert len(gen3_1["children"]) == 0
  731. def test_bad_orphan_span_loop(self):
  732. """Maliciously create a loop in the span structure but for an orphan event"""
  733. root_span_id = uuid4().hex[:16]
  734. root_parent_span = uuid4().hex[:16]
  735. root_event = self.create_event(
  736. trace=self.trace_id,
  737. transaction="/orphan/root/",
  738. spans=[
  739. {
  740. "same_process_as_parent": True,
  741. "op": "http",
  742. "description": "GET orphan_child",
  743. "span_id": root_span_id,
  744. "trace_id": self.trace_id,
  745. }
  746. ],
  747. parent_span_id=root_parent_span,
  748. project_id=self.project.id,
  749. duration=3000,
  750. )
  751. orphan_child = self.create_event(
  752. trace=self.trace_id,
  753. transaction="/orphan/child/",
  754. spans=[
  755. {
  756. "same_process_as_parent": True,
  757. "op": "http",
  758. "description": "GET orphan_root",
  759. "span_id": root_parent_span,
  760. "trace_id": self.trace_id,
  761. }
  762. ],
  763. parent_span_id=root_span_id,
  764. project_id=self.project.id,
  765. duration=300,
  766. )
  767. with self.feature(self.FEATURES):
  768. response = self.client.get(
  769. self.url,
  770. data={"project": -1},
  771. format="json",
  772. )
  773. assert response.status_code == 200, response.content
  774. assert len(response.data) == 1
  775. # There really isn't a right answer to which orphan is the "root" since this loops, but the current
  776. # implementation will make the older event the root
  777. root = response.data[0]
  778. self.assert_event(root, root_event, "root")
  779. assert len(root["children"]) == 1
  780. child = root["children"][0]
  781. self.assert_event(child, orphan_child, "child")
  782. def test_multiple_roots(self):
  783. trace_id = uuid4().hex
  784. first_root = self.create_event(
  785. trace=trace_id,
  786. transaction="/first_root",
  787. spans=[],
  788. parent_span_id=None,
  789. project_id=self.project.id,
  790. duration=500,
  791. )
  792. second_root = self.create_event(
  793. trace=trace_id,
  794. transaction="/second_root",
  795. spans=[],
  796. parent_span_id=None,
  797. project_id=self.project.id,
  798. duration=1000,
  799. )
  800. self.url = reverse(
  801. self.url_name,
  802. kwargs={"organization_slug": self.project.organization.slug, "trace_id": trace_id},
  803. )
  804. with self.feature(self.FEATURES):
  805. response = self.client.get(
  806. self.url,
  807. data={"project": -1},
  808. format="json",
  809. )
  810. assert response.status_code == 200, response.content
  811. assert len(response.data) == 2
  812. self.assert_event(response.data[0], first_root, "first_root")
  813. self.assert_event(response.data[1], second_root, "second_root")
  814. def test_sibling_transactions(self):
  815. """More than one transaction can share a parent_span_id"""
  816. self.load_trace()
  817. gen3_event_siblings = [
  818. self.create_event(
  819. trace=self.trace_id,
  820. transaction="/transaction/gen3-1",
  821. spans=[],
  822. project_id=self.create_project(organization=self.organization).id,
  823. parent_span_id=self.gen2_span_ids[1],
  824. duration=500,
  825. ).event_id,
  826. self.create_event(
  827. trace=self.trace_id,
  828. transaction="/transaction/gen3-2",
  829. spans=[],
  830. project_id=self.create_project(organization=self.organization).id,
  831. parent_span_id=self.gen2_span_ids[1],
  832. duration=525,
  833. ).event_id,
  834. ]
  835. with self.feature(self.FEATURES):
  836. response = self.client.get(
  837. self.url,
  838. data={"project": -1},
  839. format="json",
  840. )
  841. assert response.status_code == 200, response.content
  842. # Should be the same as the simple testcase, but skip checking gen2 children
  843. self.assert_trace_data(response.data[0], gen2_no_children=False)
  844. gen2_parent = response.data[0]["children"][1]["children"][0]
  845. assert len(gen2_parent["children"]) == 2
  846. assert [child["event_id"] for child in gen2_parent["children"]] == gen3_event_siblings
  847. def test_with_orphan_siblings(self):
  848. self.load_trace()
  849. parent_span_id = uuid4().hex[:16]
  850. root_event = self.create_event(
  851. trace=self.trace_id,
  852. transaction="/orphan/root",
  853. spans=[],
  854. # Some random id so its separated from the rest of the trace
  855. parent_span_id=parent_span_id,
  856. project_id=self.project.id,
  857. # Shorter duration means that this event happened first, and should be ordered first
  858. duration=1000,
  859. )
  860. root_sibling_event = self.create_event(
  861. trace=self.trace_id,
  862. transaction="/orphan/root-sibling",
  863. spans=[],
  864. parent_span_id=parent_span_id,
  865. project_id=self.project.id,
  866. duration=1250,
  867. )
  868. with self.feature(self.FEATURES):
  869. response = self.client.get(
  870. self.url,
  871. data={"project": -1},
  872. format="json",
  873. )
  874. assert response.status_code == 200, response.content
  875. assert len(response.data) == 3
  876. # The first item of the response should be the main trace
  877. main, *orphans = response.data
  878. self.assert_trace_data(main)
  879. assert [root_event.event_id, root_sibling_event.event_id] == [
  880. orphan["event_id"] for orphan in orphans
  881. ]
  882. def test_with_orphan_trace(self):
  883. self.load_trace()
  884. orphan_span_ids = {
  885. key: uuid4().hex[:16]
  886. for key in ["root", "root_span", "child", "child_span", "grandchild", "grandchild_span"]
  887. }
  888. # Create the orphan transactions
  889. root_event = self.create_event(
  890. trace=self.trace_id,
  891. transaction="/orphan/root",
  892. spans=[
  893. {
  894. "same_process_as_parent": True,
  895. "op": "http",
  896. "description": "GET gen1 orphan",
  897. "span_id": orphan_span_ids["root_span"],
  898. "trace_id": self.trace_id,
  899. }
  900. ],
  901. # Some random id so its separated from the rest of the trace
  902. parent_span_id=uuid4().hex[:16],
  903. span_id=orphan_span_ids["root"],
  904. project_id=self.project.id,
  905. duration=1000,
  906. )
  907. child_event = self.create_event(
  908. trace=self.trace_id,
  909. transaction="/orphan/child1-0",
  910. spans=[
  911. {
  912. "same_process_as_parent": True,
  913. "op": "http",
  914. "description": "GET gen1 orphan",
  915. "span_id": orphan_span_ids["child_span"],
  916. "trace_id": self.trace_id,
  917. }
  918. ],
  919. parent_span_id=orphan_span_ids["root_span"],
  920. span_id=orphan_span_ids["child"],
  921. project_id=self.gen1_project.id,
  922. # Because the snuba query orders based is_root then timestamp, this causes grandchild1-0 to be added to
  923. # results first before child1-0
  924. duration=2500,
  925. )
  926. grandchild_event = self.create_event(
  927. trace=self.trace_id,
  928. transaction="/orphan/grandchild1-0",
  929. spans=[
  930. {
  931. "same_process_as_parent": True,
  932. "op": "http",
  933. "description": "GET gen1 orphan",
  934. "span_id": orphan_span_ids["grandchild_span"],
  935. "trace_id": self.trace_id,
  936. }
  937. ],
  938. parent_span_id=orphan_span_ids["child_span"],
  939. span_id=orphan_span_ids["grandchild"],
  940. project_id=self.gen1_project.id,
  941. duration=1500,
  942. )
  943. with self.feature(self.FEATURES):
  944. response = self.client.get(
  945. self.url,
  946. data={"project": -1},
  947. format="json",
  948. )
  949. assert response.status_code == 200, response.content
  950. assert len(response.data) == 2
  951. # The first item of the response should be the main trace
  952. main, orphans = response.data
  953. self.assert_trace_data(main)
  954. self.assert_event(orphans, root_event, "orphan-root")
  955. assert len(orphans["children"]) == 1
  956. assert orphans["generation"] == 0
  957. assert orphans["parent_event_id"] is None
  958. child = orphans["children"][0]
  959. self.assert_event(child, child_event, "orphan-child")
  960. assert len(child["children"]) == 1
  961. assert child["generation"] == 1
  962. assert child["parent_event_id"] == root_event.event_id
  963. grandchild = child["children"][0]
  964. self.assert_event(grandchild, grandchild_event, "orphan-grandchild")
  965. assert grandchild["generation"] == 2
  966. assert grandchild["parent_event_id"] == child_event.event_id
  967. def test_with_errors(self):
  968. self.load_trace()
  969. error, error1 = self.load_errors()
  970. with self.feature(self.FEATURES):
  971. response = self.client.get(
  972. self.url,
  973. data={"project": -1},
  974. format="json",
  975. )
  976. assert response.status_code == 200, response.content
  977. self.assert_trace_data(response.data[0])
  978. gen1_event = response.data[0]["children"][0]
  979. assert len(gen1_event["errors"]) == 2
  980. assert {
  981. "event_id": error.event_id,
  982. "issue_id": error.group_id,
  983. "span": self.gen1_span_ids[0],
  984. "project_id": self.gen1_project.id,
  985. "project_slug": self.gen1_project.slug,
  986. "level": "fatal",
  987. "title": error.title,
  988. "timestamp": to_timestamp_from_iso_format(error.timestamp),
  989. "generation": 0,
  990. "event_type": "error",
  991. } in gen1_event["errors"]
  992. assert {
  993. "event_id": error1.event_id,
  994. "issue_id": error1.group_id,
  995. "span": self.gen1_span_ids[0],
  996. "project_id": self.gen1_project.id,
  997. "project_slug": self.gen1_project.slug,
  998. "level": "warning",
  999. "title": error1.title,
  1000. "timestamp": to_timestamp_from_iso_format(error1.timestamp),
  1001. "generation": 0,
  1002. "event_type": "error",
  1003. } in gen1_event["errors"]
  1004. def test_with_only_orphan_errors_with_same_span_ids(self):
  1005. span_id = uuid4().hex[:16]
  1006. start, end = self.get_start_end(10000)
  1007. # Error 1
  1008. error_data = load_data(
  1009. "javascript",
  1010. timestamp=end,
  1011. )
  1012. error_data["contexts"]["trace"] = {
  1013. "type": "trace",
  1014. "trace_id": self.trace_id,
  1015. "span_id": span_id,
  1016. }
  1017. error_data["level"] = "fatal"
  1018. error = self.store_event(error_data, project_id=self.project.id)
  1019. # Error 2 before after Error 1
  1020. error_data1 = load_data(
  1021. "javascript",
  1022. timestamp=start,
  1023. )
  1024. error_data1["level"] = "warning"
  1025. error_data1["contexts"]["trace"] = {
  1026. "type": "trace",
  1027. "trace_id": self.trace_id,
  1028. "span_id": span_id,
  1029. }
  1030. error1 = self.store_event(error_data1, project_id=self.project.id)
  1031. with self.feature(
  1032. [*self.FEATURES, "organizations:performance-tracing-without-performance"]
  1033. ):
  1034. response = self.client.get(
  1035. self.url,
  1036. data={"project": -1},
  1037. format="json",
  1038. )
  1039. assert response.status_code == 200, response.content
  1040. assert len(response.data) == 2
  1041. # Sorting by timestamp puts Error1 after Error2 in the response
  1042. assert {
  1043. "event_id": error.event_id,
  1044. "issue_id": error.group_id,
  1045. "span": span_id,
  1046. "project_id": self.project.id,
  1047. "project_slug": self.project.slug,
  1048. "level": "fatal",
  1049. "title": error.title,
  1050. "timestamp": to_timestamp_from_iso_format(error.timestamp),
  1051. "generation": 0,
  1052. "event_type": "error",
  1053. } == response.data["orphan_errors"][1]
  1054. assert {
  1055. "event_id": error1.event_id,
  1056. "issue_id": error1.group_id,
  1057. "span": span_id,
  1058. "project_id": self.project.id,
  1059. "project_slug": self.project.slug,
  1060. "level": "warning",
  1061. "title": error1.title,
  1062. "timestamp": to_timestamp_from_iso_format(error1.timestamp),
  1063. "generation": 0,
  1064. "event_type": "error",
  1065. } == response.data["orphan_errors"][0]
  1066. def test_with_only_orphan_errors_with_different_span_ids(self):
  1067. start, _ = self.get_start_end(1000)
  1068. span_id = uuid4().hex[:16]
  1069. error_data = load_data(
  1070. "javascript",
  1071. timestamp=start,
  1072. )
  1073. error_data["contexts"]["trace"] = {
  1074. "type": "trace",
  1075. "trace_id": self.trace_id,
  1076. "span_id": span_id,
  1077. }
  1078. error_data["level"] = "fatal"
  1079. error = self.store_event(error_data, project_id=self.project.id)
  1080. error_data["level"] = "warning"
  1081. span_id1 = uuid4().hex[:16]
  1082. error_data["contexts"]["trace"] = {
  1083. "type": "trace",
  1084. "trace_id": self.trace_id,
  1085. "span_id": span_id1,
  1086. }
  1087. error1 = self.store_event(error_data, project_id=self.project.id)
  1088. with self.feature(
  1089. [*self.FEATURES, "organizations:performance-tracing-without-performance"]
  1090. ):
  1091. response = self.client.get(
  1092. self.url,
  1093. data={"project": -1},
  1094. format="json",
  1095. )
  1096. assert response.status_code == 200, response.content
  1097. assert len(response.data["orphan_errors"]) == 2
  1098. assert {
  1099. "event_id": error.event_id,
  1100. "issue_id": error.group_id,
  1101. "span": span_id,
  1102. "project_id": self.project.id,
  1103. "project_slug": self.project.slug,
  1104. "level": "fatal",
  1105. "title": error.title,
  1106. "timestamp": to_timestamp_from_iso_format(error.timestamp),
  1107. "generation": 0,
  1108. "event_type": "error",
  1109. } in response.data["orphan_errors"]
  1110. assert {
  1111. "event_id": error1.event_id,
  1112. "issue_id": error1.group_id,
  1113. "span": span_id1,
  1114. "project_id": self.project.id,
  1115. "project_slug": self.project.slug,
  1116. "level": "warning",
  1117. "title": error1.title,
  1118. "timestamp": to_timestamp_from_iso_format(error1.timestamp),
  1119. "generation": 0,
  1120. "event_type": "error",
  1121. } in response.data["orphan_errors"]
  1122. def test_with_mixup_of_orphan_errors_with_simple_trace_data(self):
  1123. self.load_trace()
  1124. start, _ = self.get_start_end(1000)
  1125. span_id = uuid4().hex[:16]
  1126. error_data = load_data(
  1127. "javascript",
  1128. timestamp=start,
  1129. )
  1130. error_data["contexts"]["trace"] = {
  1131. "type": "trace",
  1132. "trace_id": self.trace_id,
  1133. "span_id": span_id,
  1134. }
  1135. error_data["level"] = "fatal"
  1136. error = self.store_event(error_data, project_id=self.project.id)
  1137. error_data["level"] = "warning"
  1138. span_id1 = uuid4().hex[:16]
  1139. error_data["contexts"]["trace"] = {
  1140. "type": "trace",
  1141. "trace_id": self.trace_id,
  1142. "span_id": span_id1,
  1143. }
  1144. with self.feature(
  1145. [*self.FEATURES, "organizations:performance-tracing-without-performance"]
  1146. ):
  1147. response = self.client.get(
  1148. self.url,
  1149. data={"project": -1},
  1150. format="json",
  1151. )
  1152. assert response.status_code == 200, response.content
  1153. assert len(response.data["transactions"]) == 1
  1154. assert len(response.data["orphan_errors"]) == 1
  1155. self.assert_trace_data(response.data["transactions"][0])
  1156. assert {
  1157. "event_id": error.event_id,
  1158. "issue_id": error.group_id,
  1159. "span": span_id,
  1160. "project_id": self.project.id,
  1161. "project_slug": self.project.slug,
  1162. "level": "fatal",
  1163. "title": error.title,
  1164. "timestamp": to_timestamp_from_iso_format(error.timestamp),
  1165. "generation": 0,
  1166. "event_type": "error",
  1167. } in response.data["orphan_errors"]
  1168. def test_with_default(self):
  1169. self.load_trace()
  1170. start, _ = self.get_start_end(1000)
  1171. default_event = self.load_default()
  1172. with self.feature(self.FEATURES):
  1173. response = self.client.get(
  1174. self.url,
  1175. data={"project": -1},
  1176. format="json",
  1177. )
  1178. assert response.status_code == 200, response.content
  1179. self.assert_trace_data(response.data[0])
  1180. root_event = response.data[0]
  1181. assert len(root_event["errors"]) == 1
  1182. assert {
  1183. "event_id": default_event.event_id,
  1184. "issue_id": default_event.group_id,
  1185. "span": self.root_span_ids[0],
  1186. "project_id": self.gen1_project.id,
  1187. "project_slug": self.gen1_project.slug,
  1188. "level": "debug",
  1189. "title": "this is a log message",
  1190. "timestamp": to_timestamp_from_iso_format(default_event.timestamp),
  1191. "generation": 0,
  1192. "event_type": "error",
  1193. } in root_event["errors"]
  1194. def test_pruning_root(self):
  1195. self.load_trace()
  1196. # Pruning shouldn't happen for the root event
  1197. with self.feature(self.FEATURES):
  1198. response = self.client.get(
  1199. self.url,
  1200. data={"project": -1, "event_id": self.root_event.event_id},
  1201. format="json",
  1202. )
  1203. assert response.status_code == 200, response.content
  1204. self.assert_trace_data(response.data[0])
  1205. def test_pruning_event(self):
  1206. self.load_trace()
  1207. with self.feature(self.FEATURES):
  1208. response = self.client.get(
  1209. self.url,
  1210. data={"project": -1, "event_id": self.gen2_events[0].event_id},
  1211. format="json",
  1212. )
  1213. assert response.status_code == 200, response.content
  1214. root = response.data[0]
  1215. self.assert_event(root, self.root_event, "root")
  1216. # Because of snuba query orders by timestamp we should still have all of the root's children
  1217. assert len(root["children"]) == 3
  1218. for i, gen1 in enumerate(root["children"]):
  1219. self.assert_event(gen1, self.gen1_events[i], f"gen1_{i}")
  1220. if i == 0:
  1221. assert len(gen1["children"]) == 1
  1222. gen2 = gen1["children"][0]
  1223. self.assert_event(gen2, self.gen2_events[0], "gen2_0")
  1224. assert len(gen2["children"]) == 1
  1225. gen3 = gen2["children"][0]
  1226. self.assert_event(gen3, self.gen3_event, "gen3_0")
  1227. else:
  1228. assert len(gen1["children"]) == 0
  1229. @region_silo_test
  1230. class OrganizationEventsTraceMetaEndpointTest(OrganizationEventsTraceEndpointBase):
  1231. url_name = "sentry-api-0-organization-events-trace-meta"
  1232. def test_no_projects(self):
  1233. user = self.create_user()
  1234. org = self.create_organization(owner=user)
  1235. self.login_as(user=user)
  1236. url = reverse(
  1237. self.url_name,
  1238. kwargs={"organization_slug": org.slug, "trace_id": uuid4().hex},
  1239. )
  1240. with self.feature(self.FEATURES):
  1241. response = self.client.get(
  1242. url,
  1243. format="json",
  1244. )
  1245. assert response.status_code == 404, response.content
  1246. def test_bad_ids(self):
  1247. # Fake trace id
  1248. self.url = reverse(
  1249. self.url_name,
  1250. kwargs={"organization_slug": self.project.organization.slug, "trace_id": uuid4().hex},
  1251. )
  1252. with self.feature(self.FEATURES):
  1253. response = self.client.get(
  1254. self.url,
  1255. format="json",
  1256. )
  1257. assert response.status_code == 200, response.content
  1258. data = response.data
  1259. assert data["projects"] == 0
  1260. assert data["transactions"] == 0
  1261. assert data["errors"] == 0
  1262. assert data["performance_issues"] == 0
  1263. # Invalid trace id
  1264. with pytest.raises(NoReverseMatch):
  1265. self.url = reverse(
  1266. self.url_name,
  1267. kwargs={
  1268. "organization_slug": self.project.organization.slug,
  1269. "trace_id": "not-a-trace",
  1270. },
  1271. )
  1272. def test_simple(self):
  1273. self.load_trace()
  1274. with self.feature(self.FEATURES):
  1275. response = self.client.get(
  1276. self.url,
  1277. data={"project": -1},
  1278. format="json",
  1279. )
  1280. assert response.status_code == 200, response.content
  1281. data = response.data
  1282. assert data["projects"] == 4
  1283. assert data["transactions"] == 8
  1284. assert data["errors"] == 0
  1285. assert data["performance_issues"] == 1
  1286. def test_with_errors(self):
  1287. self.load_trace()
  1288. self.load_errors()
  1289. with self.feature(self.FEATURES):
  1290. response = self.client.get(
  1291. self.url,
  1292. data={"project": -1},
  1293. format="json",
  1294. )
  1295. assert response.status_code == 200, response.content
  1296. data = response.data
  1297. assert data["projects"] == 4
  1298. assert data["transactions"] == 8
  1299. assert data["errors"] == 2
  1300. assert data["performance_issues"] == 1
  1301. def test_with_default(self):
  1302. self.load_trace()
  1303. self.load_default()
  1304. with self.feature(self.FEATURES):
  1305. response = self.client.get(
  1306. self.url,
  1307. data={"project": -1},
  1308. format="json",
  1309. )
  1310. assert response.status_code == 200, response.content
  1311. data = response.data
  1312. assert data["projects"] == 4
  1313. assert data["transactions"] == 8
  1314. assert data["errors"] == 1
  1315. assert data["performance_issues"] == 1