test_organization_events_trace.py 56 KB

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