test_organization_events_trace.py 58 KB

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