test_organization_events_trace.py 58 KB

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