test_organization_events_trace.py 44 KB

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