test_organization_events_trace.py 43 KB

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