test_organization_events_trace.py 56 KB

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