test_organization_events_span_indexed.py 74 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155
  1. import uuid
  2. from datetime import datetime, timezone
  3. from unittest import mock
  4. import pytest
  5. import urllib3
  6. from tests.snuba.api.endpoints.test_organization_events import OrganizationEventsEndpointTestBase
  7. class OrganizationEventsSpanIndexedEndpointTest(OrganizationEventsEndpointTestBase):
  8. is_eap = False
  9. use_rpc = False
  10. """Test the indexed spans dataset.
  11. To run this locally you may need to set the ENABLE_SPANS_CONSUMER flag to True in Snuba.
  12. A way to do this is
  13. 1. run: `sentry devservices down snuba`
  14. 2. clone snuba locally
  15. 3. run: `export ENABLE_SPANS_CONSUMER=True`
  16. 4. run snuba
  17. At this point tests should work locally
  18. Once span ingestion is on by default this will no longer need to be done
  19. """
  20. @property
  21. def dataset(self):
  22. if self.is_eap:
  23. return "spans"
  24. else:
  25. return "spansIndexed"
  26. def do_request(self, query, features=None, **kwargs):
  27. query["useRpc"] = "1" if self.use_rpc else "0"
  28. return super().do_request(query, features, **kwargs)
  29. def setUp(self):
  30. super().setUp()
  31. self.features = {
  32. "organizations:starfish-view": True,
  33. }
  34. @pytest.mark.querybuilder
  35. def test_simple(self):
  36. self.store_spans(
  37. [
  38. self.create_span(
  39. {"description": "foo", "sentry_tags": {"status": "success"}},
  40. start_ts=self.ten_mins_ago,
  41. ),
  42. self.create_span(
  43. {"description": "bar", "sentry_tags": {"status": "invalid_argument"}},
  44. start_ts=self.ten_mins_ago,
  45. ),
  46. ],
  47. is_eap=self.is_eap,
  48. )
  49. response = self.do_request(
  50. {
  51. "field": ["span.status", "description", "count()"],
  52. "query": "",
  53. "orderby": "description",
  54. "project": self.project.id,
  55. "dataset": self.dataset,
  56. }
  57. )
  58. assert response.status_code == 200, response.content
  59. data = response.data["data"]
  60. meta = response.data["meta"]
  61. assert len(data) == 2
  62. assert data == [
  63. {
  64. "span.status": "invalid_argument",
  65. "description": "bar",
  66. "count()": 1,
  67. },
  68. {
  69. "span.status": "ok",
  70. "description": "foo",
  71. "count()": 1,
  72. },
  73. ]
  74. assert meta["dataset"] == self.dataset
  75. def test_spm(self):
  76. self.store_spans(
  77. [
  78. self.create_span(
  79. {"description": "foo", "sentry_tags": {"status": "success"}},
  80. start_ts=self.ten_mins_ago,
  81. ),
  82. ],
  83. is_eap=self.is_eap,
  84. )
  85. response = self.do_request(
  86. {
  87. "field": ["description", "spm()"],
  88. "query": "",
  89. "orderby": "description",
  90. "project": self.project.id,
  91. "dataset": self.dataset,
  92. }
  93. )
  94. assert response.status_code == 200, response.content
  95. data = response.data["data"]
  96. meta = response.data["meta"]
  97. assert len(data) == 1
  98. assert data == [
  99. {
  100. "description": "foo",
  101. "spm()": 1 / (90 * 24 * 60),
  102. },
  103. ]
  104. assert meta["dataset"] == self.dataset
  105. def test_id_fields(self):
  106. self.store_spans(
  107. [
  108. self.create_span(
  109. {"description": "foo", "sentry_tags": {"status": "success"}},
  110. start_ts=self.ten_mins_ago,
  111. ),
  112. self.create_span(
  113. {"description": "bar", "sentry_tags": {"status": "invalid_argument"}},
  114. start_ts=self.ten_mins_ago,
  115. ),
  116. ],
  117. is_eap=self.is_eap,
  118. )
  119. response = self.do_request(
  120. {
  121. "field": ["id", "span_id"],
  122. "query": "",
  123. "orderby": "id",
  124. "project": self.project.id,
  125. "dataset": self.dataset,
  126. }
  127. )
  128. assert response.status_code == 200, response.content
  129. data = response.data["data"]
  130. meta = response.data["meta"]
  131. assert len(data) == 2
  132. for obj in data:
  133. assert obj["id"] == obj["span_id"]
  134. assert meta["dataset"] == self.dataset
  135. def test_sentry_tags_vs_tags(self):
  136. self.store_spans(
  137. [
  138. self.create_span(
  139. {"sentry_tags": {"transaction.method": "foo"}}, start_ts=self.ten_mins_ago
  140. ),
  141. ],
  142. is_eap=self.is_eap,
  143. )
  144. response = self.do_request(
  145. {
  146. "field": ["transaction.method", "count()"],
  147. "query": "",
  148. "orderby": "count()",
  149. "project": self.project.id,
  150. "dataset": self.dataset,
  151. }
  152. )
  153. assert response.status_code == 200, response.content
  154. data = response.data["data"]
  155. meta = response.data["meta"]
  156. assert len(data) == 1
  157. assert data[0]["transaction.method"] == "foo"
  158. assert meta["dataset"] == self.dataset
  159. def test_sentry_tags_syntax(self):
  160. self.store_spans(
  161. [
  162. self.create_span(
  163. {"sentry_tags": {"transaction.method": "foo"}}, start_ts=self.ten_mins_ago
  164. ),
  165. ],
  166. is_eap=self.is_eap,
  167. )
  168. response = self.do_request(
  169. {
  170. "field": ["sentry_tags[transaction.method]", "count()"],
  171. "query": "",
  172. "orderby": "count()",
  173. "project": self.project.id,
  174. "dataset": self.dataset,
  175. }
  176. )
  177. assert response.status_code == 200, response.content
  178. data = response.data["data"]
  179. meta = response.data["meta"]
  180. assert len(data) == 1
  181. assert data[0]["sentry_tags[transaction.method]"] == "foo"
  182. assert meta["dataset"] == self.dataset
  183. def test_module_alias(self):
  184. # Delegates `span.module` to `sentry_tags[category]`. Maps `"db.redis"` spans to the `"cache"` module
  185. self.store_spans(
  186. [
  187. self.create_span(
  188. {
  189. "op": "db.redis",
  190. "description": "EXEC *",
  191. "sentry_tags": {
  192. "description": "EXEC *",
  193. "category": "db",
  194. "op": "db.redis",
  195. "transaction": "/app/index",
  196. },
  197. },
  198. start_ts=self.ten_mins_ago,
  199. ),
  200. ],
  201. is_eap=self.is_eap,
  202. )
  203. response = self.do_request(
  204. {
  205. "field": ["span.module", "span.description"],
  206. "query": "span.module:cache",
  207. "project": self.project.id,
  208. "dataset": self.dataset,
  209. }
  210. )
  211. assert response.status_code == 200, response.content
  212. data = response.data["data"]
  213. meta = response.data["meta"]
  214. assert len(data) == 1
  215. assert data[0]["span.module"] == "cache"
  216. assert data[0]["span.description"] == "EXEC *"
  217. assert meta["dataset"] == self.dataset
  218. def test_device_class_filter_unknown(self):
  219. self.store_spans(
  220. [
  221. self.create_span({"sentry_tags": {"device.class": ""}}, start_ts=self.ten_mins_ago),
  222. ],
  223. is_eap=self.is_eap,
  224. )
  225. response = self.do_request(
  226. {
  227. "field": ["device.class", "count()"],
  228. "query": "device.class:Unknown",
  229. "orderby": "count()",
  230. "project": self.project.id,
  231. "dataset": self.dataset,
  232. }
  233. )
  234. assert response.status_code == 200, response.content
  235. data = response.data["data"]
  236. meta = response.data["meta"]
  237. assert len(data) == 1
  238. assert data[0]["device.class"] == "Unknown"
  239. assert meta["dataset"] == self.dataset
  240. def test_span_module(self):
  241. self.store_spans(
  242. [
  243. self.create_span(
  244. {
  245. "sentry_tags": {
  246. "op": "http",
  247. "category": "http",
  248. }
  249. },
  250. start_ts=self.ten_mins_ago,
  251. ),
  252. self.create_span(
  253. {
  254. "sentry_tags": {
  255. "op": "alternative",
  256. "category": "other",
  257. }
  258. },
  259. start_ts=self.ten_mins_ago,
  260. ),
  261. self.create_span(
  262. {
  263. "sentry_tags": {
  264. "op": "alternative",
  265. "category": "other",
  266. }
  267. },
  268. start_ts=self.ten_mins_ago,
  269. ),
  270. ],
  271. is_eap=self.is_eap,
  272. )
  273. response = self.do_request(
  274. {
  275. "field": ["span.module", "count()"],
  276. "query": "",
  277. "orderby": "-count()",
  278. "project": self.project.id,
  279. "dataset": self.dataset,
  280. }
  281. )
  282. assert response.status_code == 200, response.content
  283. data = response.data["data"]
  284. meta = response.data["meta"]
  285. assert len(data) == 2
  286. assert data[0]["span.module"] == "other"
  287. assert data[1]["span.module"] == "http"
  288. assert meta["dataset"] == self.dataset
  289. def test_network_span(self):
  290. self.store_spans(
  291. [
  292. self.create_span(
  293. {
  294. "sentry_tags": {
  295. "action": "GET",
  296. "category": "http",
  297. "description": "GET https://*.resource.com",
  298. "domain": "*.resource.com",
  299. "op": "http.client",
  300. "status_code": "200",
  301. "transaction": "/api/0/data/",
  302. "transaction.method": "GET",
  303. "transaction.op": "http.server",
  304. }
  305. },
  306. start_ts=self.ten_mins_ago,
  307. ),
  308. ],
  309. is_eap=self.is_eap,
  310. )
  311. response = self.do_request(
  312. {
  313. "field": ["span.op", "span.status_code"],
  314. "query": "span.status_code:200",
  315. "project": self.project.id,
  316. "dataset": self.dataset,
  317. }
  318. )
  319. assert response.status_code == 200, response.content
  320. data = response.data["data"]
  321. meta = response.data["meta"]
  322. assert len(data) == 1
  323. assert data[0]["span.op"] == "http.client"
  324. assert data[0]["span.status_code"] == "200"
  325. assert meta["dataset"] == self.dataset
  326. def test_other_category_span(self):
  327. self.store_spans(
  328. [
  329. self.create_span(
  330. {
  331. "sentry_tags": {
  332. "action": "GET",
  333. "category": "alternative",
  334. "description": "GET https://*.resource.com",
  335. "domain": "*.resource.com",
  336. "op": "alternative",
  337. "status_code": "200",
  338. "transaction": "/api/0/data/",
  339. "transaction.method": "GET",
  340. "transaction.op": "http.server",
  341. }
  342. },
  343. start_ts=self.ten_mins_ago,
  344. ),
  345. ],
  346. is_eap=self.is_eap,
  347. )
  348. response = self.do_request(
  349. {
  350. "field": ["span.op", "span.status_code"],
  351. "query": "span.module:other span.status_code:200",
  352. "project": self.project.id,
  353. "dataset": self.dataset,
  354. }
  355. )
  356. assert response.status_code == 200, response.content
  357. data = response.data["data"]
  358. meta = response.data["meta"]
  359. assert len(data) == 1
  360. assert data[0]["span.op"] == "alternative"
  361. assert data[0]["span.status_code"] == "200"
  362. assert meta["dataset"] == self.dataset
  363. def test_inp_span(self):
  364. replay_id = uuid.uuid4().hex
  365. self.store_spans(
  366. [
  367. self.create_span(
  368. {
  369. "sentry_tags": {
  370. "replay_id": replay_id,
  371. "browser.name": "Chrome",
  372. "transaction": "/pageloads/",
  373. }
  374. },
  375. start_ts=self.ten_mins_ago,
  376. ),
  377. ],
  378. is_eap=self.is_eap,
  379. )
  380. response = self.do_request(
  381. {
  382. "field": ["replay.id", "browser.name", "origin.transaction", "count()"],
  383. "query": f"replay.id:{replay_id} AND browser.name:Chrome AND origin.transaction:/pageloads/",
  384. "orderby": "count()",
  385. "project": self.project.id,
  386. "dataset": self.dataset,
  387. }
  388. )
  389. assert response.status_code == 200, response.content
  390. data = response.data["data"]
  391. meta = response.data["meta"]
  392. assert len(data) == 1
  393. assert data[0]["replay.id"] == replay_id
  394. assert data[0]["browser.name"] == "Chrome"
  395. assert data[0]["origin.transaction"] == "/pageloads/"
  396. assert meta["dataset"] == self.dataset
  397. def test_id_filtering(self):
  398. span = self.create_span({"description": "foo"}, start_ts=self.ten_mins_ago)
  399. self.store_span(span, is_eap=self.is_eap)
  400. response = self.do_request(
  401. {
  402. "field": ["description", "count()"],
  403. "query": f"id:{span['span_id']}",
  404. "orderby": "description",
  405. "project": self.project.id,
  406. "dataset": self.dataset,
  407. }
  408. )
  409. assert response.status_code == 200, response.content
  410. data = response.data["data"]
  411. meta = response.data["meta"]
  412. assert len(data) == 1
  413. assert data[0]["description"] == "foo"
  414. assert meta["dataset"] == self.dataset
  415. response = self.do_request(
  416. {
  417. "field": ["description", "count()"],
  418. "query": f"transaction.id:{span['event_id']}",
  419. "orderby": "description",
  420. "project": self.project.id,
  421. "dataset": self.dataset,
  422. }
  423. )
  424. assert response.status_code == 200, response.content
  425. data = response.data["data"]
  426. meta = response.data["meta"]
  427. assert len(data) == 1
  428. assert data[0]["description"] == "foo"
  429. assert meta["dataset"] == self.dataset
  430. def test_span_op_casing(self):
  431. self.store_spans(
  432. [
  433. self.create_span(
  434. {
  435. "sentry_tags": {
  436. "replay_id": "abc123",
  437. "browser.name": "Chrome",
  438. "transaction": "/pageloads/",
  439. "op": "this is a transaction",
  440. }
  441. },
  442. start_ts=self.ten_mins_ago,
  443. ),
  444. ],
  445. is_eap=self.is_eap,
  446. )
  447. response = self.do_request(
  448. {
  449. "field": ["span.op", "count()"],
  450. "query": 'span.op:"ThIs Is a TraNSActiON"',
  451. "orderby": "count()",
  452. "project": self.project.id,
  453. "dataset": self.dataset,
  454. }
  455. )
  456. assert response.status_code == 200, response.content
  457. data = response.data["data"]
  458. meta = response.data["meta"]
  459. assert len(data) == 1
  460. assert data[0]["span.op"] == "this is a transaction"
  461. assert meta["dataset"] == self.dataset
  462. def test_queue_span(self):
  463. self.store_spans(
  464. [
  465. self.create_span(
  466. {
  467. "measurements": {
  468. "messaging.message.body.size": {"value": 1024, "unit": "byte"},
  469. "messaging.message.receive.latency": {
  470. "value": 1000,
  471. "unit": "millisecond",
  472. },
  473. "messaging.message.retry.count": {"value": 2, "unit": "none"},
  474. },
  475. "sentry_tags": {
  476. "transaction": "queue-processor",
  477. "messaging.destination.name": "events",
  478. "messaging.message.id": "abc123",
  479. "trace.status": "ok",
  480. },
  481. },
  482. start_ts=self.ten_mins_ago,
  483. ),
  484. ],
  485. is_eap=self.is_eap,
  486. )
  487. response = self.do_request(
  488. {
  489. "field": [
  490. "transaction",
  491. "messaging.destination.name",
  492. "messaging.message.id",
  493. "measurements.messaging.message.receive.latency",
  494. "measurements.messaging.message.body.size",
  495. "measurements.messaging.message.retry.count",
  496. "trace.status",
  497. "count()",
  498. ],
  499. "query": 'messaging.destination.name:"events"',
  500. "orderby": "count()",
  501. "project": self.project.id,
  502. "dataset": self.dataset,
  503. }
  504. )
  505. assert response.status_code == 200, response.content
  506. data = response.data["data"]
  507. meta = response.data["meta"]
  508. assert len(data) == 1
  509. assert data[0]["transaction"] == "queue-processor"
  510. assert data[0]["messaging.destination.name"] == "events"
  511. assert data[0]["messaging.message.id"] == "abc123"
  512. assert data[0]["trace.status"] == "ok"
  513. assert data[0]["measurements.messaging.message.receive.latency"] == 1000
  514. assert data[0]["measurements.messaging.message.body.size"] == 1024
  515. assert data[0]["measurements.messaging.message.retry.count"] == 2
  516. assert meta["dataset"] == self.dataset
  517. def test_tag_wildcards(self):
  518. self.store_spans(
  519. [
  520. self.create_span(
  521. {"description": "foo", "tags": {"foo": "BaR"}},
  522. start_ts=self.ten_mins_ago,
  523. ),
  524. self.create_span(
  525. {"description": "qux", "tags": {"foo": "QuX"}},
  526. start_ts=self.ten_mins_ago,
  527. ),
  528. ],
  529. is_eap=self.is_eap,
  530. )
  531. for query in [
  532. "foo:b*",
  533. "foo:*r",
  534. "foo:*a*",
  535. "foo:b*r",
  536. ]:
  537. response = self.do_request(
  538. {
  539. "field": ["foo", "count()"],
  540. "query": query,
  541. "project": self.project.id,
  542. "dataset": self.dataset,
  543. }
  544. )
  545. assert response.status_code == 200, response.content
  546. assert response.data["data"] == [{"foo": "BaR", "count()": 1}]
  547. def test_query_for_missing_tag(self):
  548. self.store_spans(
  549. [
  550. self.create_span(
  551. {"description": "foo"},
  552. start_ts=self.ten_mins_ago,
  553. ),
  554. self.create_span(
  555. {"description": "qux", "tags": {"foo": "bar"}},
  556. start_ts=self.ten_mins_ago,
  557. ),
  558. ],
  559. is_eap=self.is_eap,
  560. )
  561. response = self.do_request(
  562. {
  563. "field": ["foo", "count()"],
  564. "query": 'foo:""',
  565. "project": self.project.id,
  566. "dataset": self.dataset,
  567. }
  568. )
  569. assert response.status_code == 200, response.content
  570. assert response.data["data"] == [{"foo": "", "count()": 1}]
  571. def test_count_field_type(self):
  572. response = self.do_request(
  573. {
  574. "field": ["count()"],
  575. "project": self.project.id,
  576. "dataset": self.dataset,
  577. }
  578. )
  579. assert response.status_code == 200, response.content
  580. assert response.data["meta"]["fields"] == {"count()": "integer"}
  581. assert response.data["meta"]["units"] == {"count()": None}
  582. assert response.data["data"] == [{"count()": 0}]
  583. def _test_simple_measurements(self, keys):
  584. self.store_spans(
  585. [
  586. self.create_span(
  587. {
  588. "description": "foo",
  589. "sentry_tags": {"status": "success"},
  590. "tags": {"bar": "bar2"},
  591. },
  592. measurements={k: {"value": (i + 1) / 10} for i, (k, _, _) in enumerate(keys)},
  593. start_ts=self.ten_mins_ago,
  594. ),
  595. ],
  596. is_eap=self.is_eap,
  597. )
  598. for i, (k, type, unit) in enumerate(keys):
  599. key = f"measurements.{k}"
  600. response = self.do_request(
  601. {
  602. "field": [key],
  603. "query": "description:foo",
  604. "project": self.project.id,
  605. "dataset": self.dataset,
  606. }
  607. )
  608. assert response.status_code == 200, response.content
  609. assert response.data["meta"] == {
  610. "dataset": mock.ANY,
  611. "datasetReason": "unchanged",
  612. "fields": {
  613. key: type,
  614. "id": "string",
  615. "project.name": "string",
  616. },
  617. "isMetricsData": False,
  618. "isMetricsExtractedData": False,
  619. "tips": {},
  620. "units": {
  621. key: unit,
  622. "id": None,
  623. "project.name": None,
  624. },
  625. }
  626. assert response.data["data"] == [
  627. {
  628. key: pytest.approx((i + 1) / 10),
  629. "id": mock.ANY,
  630. "project.name": self.project.slug,
  631. }
  632. ]
  633. def test_simple_measurements(self):
  634. keys = [
  635. ("app_start_cold", "duration", "millisecond"),
  636. ("app_start_warm", "duration", "millisecond"),
  637. ("frames_frozen", "number", None), # should be integer but keeping it consistent
  638. ("frames_frozen_rate", "percentage", None),
  639. ("frames_slow", "number", None), # should be integer but keeping it consistent
  640. ("frames_slow_rate", "percentage", None),
  641. ("frames_total", "number", None), # should be integer but keeping it consistent
  642. ("time_to_initial_display", "duration", "millisecond"),
  643. ("time_to_full_display", "duration", "millisecond"),
  644. ("stall_count", "number", None), # should be integer but keeping it consistent
  645. ("stall_percentage", "percentage", None),
  646. ("stall_stall_longest_time", "number", None),
  647. ("stall_stall_total_time", "number", None),
  648. ("cls", "number", None),
  649. ("fcp", "duration", "millisecond"),
  650. ("fid", "duration", "millisecond"),
  651. ("fp", "duration", "millisecond"),
  652. ("inp", "duration", "millisecond"),
  653. ("lcp", "duration", "millisecond"),
  654. ("ttfb", "duration", "millisecond"),
  655. ("ttfb.requesttime", "duration", "millisecond"),
  656. ("score.cls", "number", None),
  657. ("score.fcp", "number", None),
  658. ("score.fid", "number", None),
  659. ("score.inp", "number", None),
  660. ("score.lcp", "number", None),
  661. ("score.ttfb", "number", None),
  662. ("score.total", "number", None),
  663. ("score.weight.cls", "number", None),
  664. ("score.weight.fcp", "number", None),
  665. ("score.weight.fid", "number", None),
  666. ("score.weight.inp", "number", None),
  667. ("score.weight.lcp", "number", None),
  668. ("score.weight.ttfb", "number", None),
  669. ("messaging.message.receive.latency", "duration", "millisecond"),
  670. ("messaging.message.retry.count", "number", None),
  671. # size fields aren't property support pre-RPC
  672. ("cache.item_size", "number", None),
  673. ("messaging.message.body.size", "number", None),
  674. ]
  675. self._test_simple_measurements(keys)
  676. def test_environment(self):
  677. self.create_environment(self.project, name="prod")
  678. self.create_environment(self.project, name="test")
  679. self.store_spans(
  680. [
  681. self.create_span(
  682. {"description": "foo", "sentry_tags": {"environment": "prod"}},
  683. start_ts=self.ten_mins_ago,
  684. ),
  685. self.create_span(
  686. {"description": "foo", "sentry_tags": {"environment": "test"}},
  687. start_ts=self.ten_mins_ago,
  688. ),
  689. ],
  690. is_eap=self.is_eap,
  691. )
  692. response = self.do_request(
  693. {
  694. "field": ["environment", "count()"],
  695. "project": self.project.id,
  696. "environment": "prod",
  697. "dataset": self.dataset,
  698. }
  699. )
  700. assert response.status_code == 200, response.content
  701. assert response.data["data"] == [
  702. {"environment": "prod", "count()": 1},
  703. ]
  704. def test_transaction(self):
  705. self.store_spans(
  706. [
  707. self.create_span(
  708. {"description": "foo", "sentry_tags": {"transaction": "bar"}},
  709. start_ts=self.ten_mins_ago,
  710. ),
  711. ],
  712. is_eap=self.is_eap,
  713. )
  714. response = self.do_request(
  715. {
  716. "field": ["description", "count()"],
  717. "query": "transaction:bar",
  718. "orderby": "description",
  719. "project": self.project.id,
  720. "dataset": self.dataset,
  721. }
  722. )
  723. assert response.status_code == 200, response.content
  724. data = response.data["data"]
  725. meta = response.data["meta"]
  726. assert len(data) == 1
  727. assert data == [
  728. {
  729. "description": "foo",
  730. "count()": 1,
  731. },
  732. ]
  733. assert meta["dataset"] == self.dataset
  734. def test_orderby_alias(self):
  735. self.store_spans(
  736. [
  737. self.create_span(
  738. {"description": "foo", "sentry_tags": {"status": "success"}},
  739. start_ts=self.ten_mins_ago,
  740. ),
  741. self.create_span(
  742. {"description": "bar", "sentry_tags": {"status": "invalid_argument"}},
  743. duration=2000,
  744. start_ts=self.ten_mins_ago,
  745. ),
  746. ],
  747. is_eap=self.is_eap,
  748. )
  749. response = self.do_request(
  750. {
  751. "field": ["span.description", "sum(span.self_time)"],
  752. "query": "",
  753. "orderby": "sum_span_self_time",
  754. "project": self.project.id,
  755. "dataset": self.dataset,
  756. }
  757. )
  758. assert response.status_code == 200, response.content
  759. data = response.data["data"]
  760. meta = response.data["meta"]
  761. assert len(data) == 2
  762. assert data == [
  763. {
  764. "span.description": "foo",
  765. "sum(span.self_time)": 1000,
  766. },
  767. {
  768. "span.description": "bar",
  769. "sum(span.self_time)": 2000,
  770. },
  771. ]
  772. assert meta["dataset"] == self.dataset
  773. @pytest.mark.querybuilder
  774. def test_explore_sample_query(self):
  775. spans = [
  776. self.create_span(
  777. {"description": "foo", "sentry_tags": {"status": "success"}},
  778. start_ts=self.ten_mins_ago,
  779. ),
  780. self.create_span(
  781. {"description": "bar", "sentry_tags": {"status": "invalid_argument"}},
  782. start_ts=self.nine_mins_ago,
  783. ),
  784. ]
  785. self.store_spans(
  786. spans,
  787. is_eap=self.is_eap,
  788. )
  789. response = self.do_request(
  790. {
  791. "field": [
  792. "id",
  793. "project",
  794. "span.op",
  795. "span.description",
  796. "span.duration",
  797. "timestamp",
  798. "trace",
  799. "transaction.span_id",
  800. ],
  801. # This is to skip INP spans
  802. "query": "!transaction.span_id:00",
  803. "orderby": "timestamp",
  804. "statsPeriod": "1h",
  805. "project": self.project.id,
  806. "dataset": self.dataset,
  807. }
  808. )
  809. assert response.status_code == 200, response.content
  810. data = response.data["data"]
  811. meta = response.data["meta"]
  812. assert len(data) == 2
  813. for source, result in zip(spans, data):
  814. assert result["id"] == source["span_id"], "id"
  815. assert result["span.duration"] == 1000.0, "duration"
  816. assert result["span.op"] == "", "op"
  817. assert result["span.description"] == source["description"], "description"
  818. ts = datetime.fromisoformat(result["timestamp"])
  819. assert ts.tzinfo == timezone.utc
  820. assert ts.timestamp() == pytest.approx(
  821. source["end_timestamp_precise"], abs=5
  822. ), "timestamp"
  823. assert result["transaction.span_id"] == source["segment_id"], "transaction.span_id"
  824. assert result["project"] == result["project.name"] == self.project.slug, "project"
  825. assert meta["dataset"] == self.dataset
  826. def test_span_status(self):
  827. self.store_spans(
  828. [
  829. self.create_span(
  830. {"description": "foo", "sentry_tags": {"status": "internal_error"}},
  831. start_ts=self.ten_mins_ago,
  832. ),
  833. ],
  834. is_eap=self.is_eap,
  835. )
  836. response = self.do_request(
  837. {
  838. "field": ["description", "count()"],
  839. "query": "span.status:internal_error",
  840. "orderby": "description",
  841. "project": self.project.id,
  842. "dataset": self.dataset,
  843. }
  844. )
  845. assert response.status_code == 200, response.content
  846. data = response.data["data"]
  847. meta = response.data["meta"]
  848. assert len(data) == 1
  849. assert data == [
  850. {
  851. "description": "foo",
  852. "count()": 1,
  853. },
  854. ]
  855. assert meta["dataset"] == self.dataset
  856. def test_handle_nans_from_snuba(self):
  857. self.store_spans(
  858. [self.create_span({"description": "foo"}, start_ts=self.ten_mins_ago)],
  859. is_eap=self.is_eap,
  860. )
  861. response = self.do_request(
  862. {
  863. "field": ["description", "count()"],
  864. "query": "span.status:internal_error",
  865. "orderby": "description",
  866. "project": self.project.id,
  867. "dataset": self.dataset,
  868. }
  869. )
  870. assert response.status_code == 200, response.content
  871. def test_in_filter(self):
  872. self.store_spans(
  873. [
  874. self.create_span(
  875. {"description": "foo", "sentry_tags": {"transaction": "bar"}},
  876. start_ts=self.ten_mins_ago,
  877. ),
  878. self.create_span(
  879. {"description": "foo", "sentry_tags": {"transaction": "baz"}},
  880. start_ts=self.ten_mins_ago,
  881. ),
  882. self.create_span(
  883. {"description": "foo", "sentry_tags": {"transaction": "bat"}},
  884. start_ts=self.ten_mins_ago,
  885. ),
  886. ],
  887. is_eap=self.is_eap,
  888. )
  889. response = self.do_request(
  890. {
  891. "field": ["transaction", "count()"],
  892. "query": "transaction:[bar, baz]",
  893. "orderby": "transaction",
  894. "project": self.project.id,
  895. "dataset": self.dataset,
  896. }
  897. )
  898. assert response.status_code == 200, response.content
  899. data = response.data["data"]
  900. meta = response.data["meta"]
  901. assert len(data) == 2
  902. assert data == [
  903. {
  904. "transaction": "bar",
  905. "count()": 1,
  906. },
  907. {
  908. "transaction": "baz",
  909. "count()": 1,
  910. },
  911. ]
  912. assert meta["dataset"] == self.dataset
  913. def _test_aggregate_filter(self, queries):
  914. self.store_spans(
  915. [
  916. self.create_span(
  917. {"sentry_tags": {"transaction": "foo"}},
  918. measurements={
  919. "lcp": {"value": 5000},
  920. "http.response_content_length": {"value": 5000},
  921. },
  922. start_ts=self.ten_mins_ago,
  923. ),
  924. self.create_span(
  925. {"sentry_tags": {"transaction": "foo"}},
  926. measurements={
  927. "lcp": {"value": 5000},
  928. "http.response_content_length": {"value": 5000},
  929. },
  930. start_ts=self.ten_mins_ago,
  931. ),
  932. self.create_span(
  933. {"sentry_tags": {"transaction": "bar"}},
  934. measurements={
  935. "lcp": {"value": 1000},
  936. "http.response_content_length": {"value": 1000},
  937. },
  938. start_ts=self.ten_mins_ago,
  939. ),
  940. ],
  941. is_eap=self.is_eap,
  942. )
  943. for query in queries:
  944. response = self.do_request(
  945. {
  946. "field": ["transaction", "count()"],
  947. "query": query,
  948. "orderby": "transaction",
  949. "project": self.project.id,
  950. "dataset": self.dataset,
  951. }
  952. )
  953. assert response.status_code == 200, response.content
  954. data = response.data["data"]
  955. meta = response.data["meta"]
  956. assert len(data) == 1
  957. assert data[0]["transaction"] == "foo"
  958. assert data[0]["count()"] == 2
  959. assert meta["dataset"] == self.dataset
  960. def test_aggregate_filter(self):
  961. self._test_aggregate_filter(
  962. [
  963. "count():2",
  964. "count():>1",
  965. "avg(measurements.lcp):>3000",
  966. "avg(measurements.lcp):>3s",
  967. "count():>1 avg(measurements.lcp):>3000",
  968. "count():>1 AND avg(measurements.lcp):>3000",
  969. "count():>1 OR avg(measurements.lcp):>3000",
  970. ]
  971. )
  972. class OrganizationEventsEAPSpanEndpointTest(OrganizationEventsSpanIndexedEndpointTest):
  973. is_eap = True
  974. use_rpc = False
  975. def test_simple(self):
  976. self.store_spans(
  977. [
  978. self.create_span(
  979. {"description": "foo", "sentry_tags": {"status": "success"}},
  980. start_ts=self.ten_mins_ago,
  981. ),
  982. self.create_span(
  983. {"description": "bar", "sentry_tags": {"status": "invalid_argument"}},
  984. start_ts=self.ten_mins_ago,
  985. ),
  986. ],
  987. is_eap=self.is_eap,
  988. )
  989. response = self.do_request(
  990. {
  991. "field": ["span.status", "description", "count()"],
  992. "query": "",
  993. "orderby": "description",
  994. "project": self.project.id,
  995. "dataset": self.dataset,
  996. }
  997. )
  998. assert response.status_code == 200, response.content
  999. data = response.data["data"]
  1000. meta = response.data["meta"]
  1001. assert len(data) == 2
  1002. assert data == [
  1003. {
  1004. "span.status": "invalid_argument",
  1005. "description": "bar",
  1006. "count()": 1,
  1007. },
  1008. {
  1009. "span.status": "success",
  1010. "description": "foo",
  1011. "count()": 1,
  1012. },
  1013. ]
  1014. assert meta["dataset"] == self.dataset
  1015. @pytest.mark.xfail(reason="event_id isn't being written to the new table")
  1016. def test_id_filtering(self):
  1017. super().test_id_filtering()
  1018. def test_span_duration(self):
  1019. spans = [
  1020. self.create_span(
  1021. {"description": "bar", "sentry_tags": {"status": "invalid_argument"}},
  1022. start_ts=self.ten_mins_ago,
  1023. ),
  1024. self.create_span(
  1025. {"description": "foo", "sentry_tags": {"status": "success"}},
  1026. start_ts=self.ten_mins_ago,
  1027. ),
  1028. ]
  1029. self.store_spans(spans, is_eap=self.is_eap)
  1030. response = self.do_request(
  1031. {
  1032. "field": ["span.duration", "description"],
  1033. "query": "",
  1034. "orderby": "description",
  1035. "project": self.project.id,
  1036. "dataset": self.dataset,
  1037. }
  1038. )
  1039. assert response.status_code == 200, response.content
  1040. data = response.data["data"]
  1041. meta = response.data["meta"]
  1042. assert len(data) == 2
  1043. assert data == [
  1044. {
  1045. "span.duration": 1000.0,
  1046. "description": "bar",
  1047. "project.name": self.project.slug,
  1048. "id": spans[0]["span_id"],
  1049. },
  1050. {
  1051. "span.duration": 1000.0,
  1052. "description": "foo",
  1053. "project.name": self.project.slug,
  1054. "id": spans[1]["span_id"],
  1055. },
  1056. ]
  1057. assert meta["dataset"] == self.dataset
  1058. @pytest.mark.xfail
  1059. def test_aggregate_numeric_attr(self):
  1060. self.store_spans(
  1061. [
  1062. self.create_span(
  1063. {
  1064. "description": "foo",
  1065. "sentry_tags": {"status": "success"},
  1066. "tags": {"bar": "bar1"},
  1067. },
  1068. start_ts=self.ten_mins_ago,
  1069. ),
  1070. self.create_span(
  1071. {
  1072. "description": "foo",
  1073. "sentry_tags": {"status": "success"},
  1074. "tags": {"bar": "bar2"},
  1075. },
  1076. measurements={"foo": {"value": 5}},
  1077. start_ts=self.ten_mins_ago,
  1078. ),
  1079. self.create_span(
  1080. {
  1081. "description": "foo",
  1082. "sentry_tags": {"status": "success"},
  1083. "tags": {"bar": "bar3"},
  1084. },
  1085. start_ts=self.ten_mins_ago,
  1086. ),
  1087. ],
  1088. is_eap=self.is_eap,
  1089. )
  1090. response = self.do_request(
  1091. {
  1092. "field": [
  1093. "description",
  1094. "count_unique(bar)",
  1095. "count_unique(tags[bar])",
  1096. "count_unique(tags[bar,string])",
  1097. "count()",
  1098. "count(span.duration)",
  1099. "count(tags[foo, number])",
  1100. "sum(tags[foo,number])",
  1101. "avg(tags[foo,number])",
  1102. "p50(tags[foo,number])",
  1103. "p75(tags[foo,number])",
  1104. "p95(tags[foo,number])",
  1105. "p99(tags[foo,number])",
  1106. "p100(tags[foo,number])",
  1107. "min(tags[foo,number])",
  1108. "max(tags[foo,number])",
  1109. ],
  1110. "query": "",
  1111. "orderby": "description",
  1112. "project": self.project.id,
  1113. "dataset": self.dataset,
  1114. }
  1115. )
  1116. assert response.status_code == 200, response.content
  1117. assert len(response.data["data"]) == 1
  1118. data = response.data["data"]
  1119. assert data[0] == {
  1120. "description": "foo",
  1121. "count_unique(bar)": 3,
  1122. "count_unique(tags[bar])": 3,
  1123. "count_unique(tags[bar,string])": 3,
  1124. "count()": 3,
  1125. "count(span.duration)": 3,
  1126. "count(tags[foo, number])": 1,
  1127. "sum(tags[foo,number])": 5.0,
  1128. "avg(tags[foo,number])": 5.0,
  1129. "p50(tags[foo,number])": 5.0,
  1130. "p75(tags[foo,number])": 5.0,
  1131. "p95(tags[foo,number])": 5.0,
  1132. "p99(tags[foo,number])": 5.0,
  1133. "p100(tags[foo,number])": 5.0,
  1134. "min(tags[foo,number])": 5.0,
  1135. "max(tags[foo,number])": 5.0,
  1136. }
  1137. def test_numeric_attr_without_space(self):
  1138. self.store_spans(
  1139. [
  1140. self.create_span(
  1141. {
  1142. "description": "foo",
  1143. "sentry_tags": {"status": "success"},
  1144. "tags": {"foo": "five"},
  1145. },
  1146. measurements={"foo": {"value": 5}},
  1147. start_ts=self.ten_mins_ago,
  1148. ),
  1149. ],
  1150. is_eap=self.is_eap,
  1151. )
  1152. response = self.do_request(
  1153. {
  1154. "field": ["description", "tags[foo,number]", "tags[foo,string]", "tags[foo]"],
  1155. "query": "",
  1156. "orderby": "description",
  1157. "project": self.project.id,
  1158. "dataset": self.dataset,
  1159. }
  1160. )
  1161. assert response.status_code == 200, response.content
  1162. assert len(response.data["data"]) == 1
  1163. data = response.data["data"]
  1164. assert data[0]["tags[foo,number]"] == 5
  1165. assert data[0]["tags[foo,string]"] == "five"
  1166. assert data[0]["tags[foo]"] == "five"
  1167. def test_numeric_attr_with_spaces(self):
  1168. self.store_spans(
  1169. [
  1170. self.create_span(
  1171. {
  1172. "description": "foo",
  1173. "sentry_tags": {"status": "success"},
  1174. "tags": {"foo": "five"},
  1175. },
  1176. measurements={"foo": {"value": 5}},
  1177. start_ts=self.ten_mins_ago,
  1178. ),
  1179. ],
  1180. is_eap=self.is_eap,
  1181. )
  1182. response = self.do_request(
  1183. {
  1184. "field": ["description", "tags[foo, number]", "tags[foo, string]", "tags[foo]"],
  1185. "query": "",
  1186. "orderby": "description",
  1187. "project": self.project.id,
  1188. "dataset": self.dataset,
  1189. }
  1190. )
  1191. assert response.status_code == 200, response.content
  1192. assert len(response.data["data"]) == 1
  1193. data = response.data["data"]
  1194. assert data[0]["tags[foo, number]"] == 5
  1195. assert data[0]["tags[foo, string]"] == "five"
  1196. assert data[0]["tags[foo]"] == "five"
  1197. def test_numeric_attr_filtering(self):
  1198. self.store_spans(
  1199. [
  1200. self.create_span(
  1201. {
  1202. "description": "foo",
  1203. "sentry_tags": {"status": "success"},
  1204. "tags": {"foo": "five"},
  1205. },
  1206. measurements={"foo": {"value": 5}},
  1207. start_ts=self.ten_mins_ago,
  1208. ),
  1209. self.create_span(
  1210. {"description": "bar", "sentry_tags": {"status": "success", "foo": "five"}},
  1211. measurements={"foo": {"value": 8}},
  1212. start_ts=self.ten_mins_ago,
  1213. ),
  1214. ],
  1215. is_eap=self.is_eap,
  1216. )
  1217. response = self.do_request(
  1218. {
  1219. "field": ["description", "tags[foo,number]"],
  1220. "query": "tags[foo,number]:5",
  1221. "orderby": "description",
  1222. "project": self.project.id,
  1223. "dataset": self.dataset,
  1224. }
  1225. )
  1226. assert response.status_code == 200, response.content
  1227. assert len(response.data["data"]) == 1
  1228. data = response.data["data"]
  1229. assert data[0]["tags[foo,number]"] == 5
  1230. assert data[0]["description"] == "foo"
  1231. def test_long_attr_name(self):
  1232. response = self.do_request(
  1233. {
  1234. "field": ["description", "z" * 201],
  1235. "query": "",
  1236. "orderby": "description",
  1237. "project": self.project.id,
  1238. "dataset": self.dataset,
  1239. }
  1240. )
  1241. assert response.status_code == 400, response.content
  1242. assert "Is Too Long" in response.data["detail"].title()
  1243. def test_numeric_attr_orderby(self):
  1244. self.store_spans(
  1245. [
  1246. self.create_span(
  1247. {
  1248. "description": "baz",
  1249. "sentry_tags": {"status": "success"},
  1250. "tags": {"foo": "five"},
  1251. },
  1252. measurements={"foo": {"value": 71}},
  1253. start_ts=self.ten_mins_ago,
  1254. ),
  1255. self.create_span(
  1256. {
  1257. "description": "foo",
  1258. "sentry_tags": {"status": "success"},
  1259. "tags": {"foo": "five"},
  1260. },
  1261. measurements={"foo": {"value": 5}},
  1262. start_ts=self.ten_mins_ago,
  1263. ),
  1264. self.create_span(
  1265. {
  1266. "description": "bar",
  1267. "sentry_tags": {"status": "success"},
  1268. "tags": {"foo": "five"},
  1269. },
  1270. measurements={"foo": {"value": 8}},
  1271. start_ts=self.ten_mins_ago,
  1272. ),
  1273. ],
  1274. is_eap=self.is_eap,
  1275. )
  1276. response = self.do_request(
  1277. {
  1278. "field": ["description", "tags[foo,number]"],
  1279. "query": "",
  1280. "orderby": ["tags[foo,number]"],
  1281. "project": self.project.id,
  1282. "dataset": self.dataset,
  1283. }
  1284. )
  1285. assert response.status_code == 200, response.content
  1286. assert len(response.data["data"]) == 3
  1287. data = response.data["data"]
  1288. assert data[0]["tags[foo,number]"] == 5
  1289. assert data[0]["description"] == "foo"
  1290. assert data[1]["tags[foo,number]"] == 8
  1291. assert data[1]["description"] == "bar"
  1292. assert data[2]["tags[foo,number]"] == 71
  1293. assert data[2]["description"] == "baz"
  1294. def test_margin_of_error(self):
  1295. total_samples = 10
  1296. in_group = 5
  1297. spans = []
  1298. for _ in range(in_group):
  1299. spans.append(
  1300. self.create_span(
  1301. {
  1302. "description": "foo",
  1303. "sentry_tags": {"status": "success"},
  1304. "measurements": {"client_sample_rate": {"value": 0.00001}},
  1305. },
  1306. start_ts=self.ten_mins_ago,
  1307. )
  1308. )
  1309. for _ in range(total_samples - in_group):
  1310. spans.append(
  1311. self.create_span(
  1312. {
  1313. "description": "bar",
  1314. "sentry_tags": {"status": "success"},
  1315. "measurements": {"client_sample_rate": {"value": 0.00001}},
  1316. },
  1317. )
  1318. )
  1319. self.store_spans(
  1320. spans,
  1321. is_eap=self.is_eap,
  1322. )
  1323. response = self.do_request(
  1324. {
  1325. "field": [
  1326. "margin_of_error()",
  1327. "lower_count_limit()",
  1328. "upper_count_limit()",
  1329. "count()",
  1330. ],
  1331. "query": "description:foo",
  1332. "project": self.project.id,
  1333. "dataset": self.dataset,
  1334. }
  1335. )
  1336. assert response.status_code == 200, response.content
  1337. assert len(response.data["data"]) == 1
  1338. data = response.data["data"][0]
  1339. margin_of_error = data["margin_of_error()"]
  1340. lower_limit = data["lower_count_limit()"]
  1341. upper_limit = data["upper_count_limit()"]
  1342. extrapolated = data["count()"]
  1343. assert margin_of_error == pytest.approx(0.306, rel=1e-1)
  1344. # How to read this; these results mean that the extrapolated count is
  1345. # 500k, with a lower estimated bound of ~200k, and an upper bound of 800k
  1346. assert lower_limit == pytest.approx(190_000, abs=5000)
  1347. assert extrapolated == pytest.approx(500_000, abs=5000)
  1348. assert upper_limit == pytest.approx(810_000, abs=5000)
  1349. def test_skip_aggregate_conditions_option(self):
  1350. span_1 = self.create_span(
  1351. {"description": "foo", "sentry_tags": {"status": "success"}},
  1352. start_ts=self.ten_mins_ago,
  1353. )
  1354. span_2 = self.create_span(
  1355. {"description": "bar", "sentry_tags": {"status": "invalid_argument"}},
  1356. start_ts=self.ten_mins_ago,
  1357. )
  1358. self.store_spans(
  1359. [span_1, span_2],
  1360. is_eap=self.is_eap,
  1361. )
  1362. response = self.do_request(
  1363. {
  1364. "field": ["description"],
  1365. "query": "description:foo count():>1",
  1366. "orderby": "description",
  1367. "project": self.project.id,
  1368. "dataset": self.dataset,
  1369. "allowAggregateConditions": "0",
  1370. }
  1371. )
  1372. assert response.status_code == 200, response.content
  1373. data = response.data["data"]
  1374. meta = response.data["meta"]
  1375. assert len(data) == 1
  1376. assert data == [
  1377. {
  1378. "description": "foo",
  1379. "project.name": self.project.slug,
  1380. "id": span_1["span_id"],
  1381. },
  1382. ]
  1383. assert meta["dataset"] == self.dataset
  1384. def test_span_data_fields_http_resource(self):
  1385. self.store_spans(
  1386. [
  1387. self.create_span(
  1388. {
  1389. "op": "resource.img",
  1390. "description": "/image/",
  1391. "data": {
  1392. "http.decoded_response_content_length": 1,
  1393. "http.response_content_length": 2,
  1394. "http.response_transfer_size": 3,
  1395. },
  1396. },
  1397. start_ts=self.ten_mins_ago,
  1398. ),
  1399. ],
  1400. is_eap=self.is_eap,
  1401. )
  1402. response = self.do_request(
  1403. {
  1404. "field": [
  1405. "http.decoded_response_content_length",
  1406. "http.response_content_length",
  1407. "http.response_transfer_size",
  1408. ],
  1409. "project": self.project.id,
  1410. "dataset": self.dataset,
  1411. "allowAggregateConditions": "0",
  1412. }
  1413. )
  1414. assert response.status_code == 200, response.content
  1415. assert response.data["data"] == [
  1416. {
  1417. "http.decoded_response_content_length": 1,
  1418. "http.response_content_length": 2,
  1419. "http.response_transfer_size": 3,
  1420. "project.name": self.project.slug,
  1421. "id": mock.ANY,
  1422. },
  1423. ]
  1424. assert response.data["meta"] == {
  1425. "dataset": mock.ANY,
  1426. "datasetReason": "unchanged",
  1427. "fields": {
  1428. "http.decoded_response_content_length": "size",
  1429. "http.response_content_length": "size",
  1430. "http.response_transfer_size": "size",
  1431. "id": "string",
  1432. "project.name": "string",
  1433. },
  1434. "isMetricsData": False,
  1435. "isMetricsExtractedData": False,
  1436. "tips": {},
  1437. "units": {
  1438. "http.decoded_response_content_length": "byte",
  1439. "http.response_content_length": "byte",
  1440. "http.response_transfer_size": "byte",
  1441. "id": None,
  1442. "project.name": None,
  1443. },
  1444. }
  1445. def test_filtering_numeric_attr(self):
  1446. span_1 = self.create_span(
  1447. {"description": "foo"},
  1448. measurements={"foo": {"value": 30}},
  1449. start_ts=self.ten_mins_ago,
  1450. )
  1451. span_2 = self.create_span(
  1452. {"description": "foo"},
  1453. measurements={"foo": {"value": 10}},
  1454. start_ts=self.ten_mins_ago,
  1455. )
  1456. self.store_spans([span_1, span_2], is_eap=self.is_eap)
  1457. response = self.do_request(
  1458. {
  1459. "field": ["tags[foo,number]"],
  1460. "query": "span.duration:>=0 tags[foo,number]:>20",
  1461. "project": self.project.id,
  1462. "dataset": self.dataset,
  1463. }
  1464. )
  1465. assert response.status_code == 200, response.content
  1466. assert response.data["data"] == [
  1467. {
  1468. "id": span_1["span_id"],
  1469. "project.name": self.project.slug,
  1470. "tags[foo,number]": 30,
  1471. },
  1472. ]
  1473. def test_byte_fields(self):
  1474. self.store_spans(
  1475. [
  1476. self.create_span(
  1477. {
  1478. "description": "foo",
  1479. "data": {
  1480. "cache.item_size": 1,
  1481. "messaging.message.body.size": 2,
  1482. },
  1483. },
  1484. start_ts=self.ten_mins_ago,
  1485. ),
  1486. ],
  1487. is_eap=self.is_eap,
  1488. )
  1489. response = self.do_request(
  1490. {
  1491. "field": [
  1492. "cache.item_size",
  1493. "measurements.cache.item_size",
  1494. "messaging.message.body.size",
  1495. "measurements.messaging.message.body.size",
  1496. ],
  1497. "project": self.project.id,
  1498. "dataset": self.dataset,
  1499. }
  1500. )
  1501. assert response.data["data"] == [
  1502. {
  1503. "id": mock.ANY,
  1504. "project.name": self.project.slug,
  1505. "cache.item_size": 1.0,
  1506. "measurements.cache.item_size": 1.0,
  1507. "measurements.messaging.message.body.size": 2.0,
  1508. "messaging.message.body.size": 2.0,
  1509. },
  1510. ]
  1511. def test_aggregate_filter(self):
  1512. self._test_aggregate_filter(
  1513. [
  1514. "count():2",
  1515. "count():>1",
  1516. "avg(measurements.lcp):>3000",
  1517. "avg(measurements.lcp):>3s",
  1518. "count():>1 avg(measurements.lcp):>3000",
  1519. "count():>1 AND avg(measurements.lcp):>3000",
  1520. "count():>1 OR avg(measurements.lcp):>3000",
  1521. "(count():>1 AND avg(http.response_content_length):>3000) OR (count():>1 AND avg(measurements.lcp):>3000)",
  1522. ]
  1523. )
  1524. class OrganizationEventsEAPRPCSpanEndpointTest(OrganizationEventsEAPSpanEndpointTest):
  1525. """These tests aren't fully passing yet, currently inheriting xfail from the eap tests"""
  1526. is_eap = True
  1527. use_rpc = True
  1528. @mock.patch(
  1529. "sentry.utils.snuba_rpc._snuba_pool.urlopen", side_effect=urllib3.exceptions.TimeoutError
  1530. )
  1531. def test_timeout(self, mock_rpc):
  1532. response = self.do_request(
  1533. {
  1534. "field": ["span.status", "description", "count()"],
  1535. "query": "",
  1536. "orderby": "description",
  1537. "project": self.project.id,
  1538. "dataset": self.dataset,
  1539. }
  1540. )
  1541. assert response.status_code == 400, response.content
  1542. assert "Query timeout" in response.data["detail"]
  1543. def test_extrapolation(self):
  1544. """Extrapolation only changes the number when there's a sample rate"""
  1545. spans = []
  1546. spans.append(
  1547. self.create_span(
  1548. {
  1549. "description": "foo",
  1550. "sentry_tags": {"status": "success"},
  1551. "measurements": {"client_sample_rate": {"value": 0.1}},
  1552. },
  1553. start_ts=self.ten_mins_ago,
  1554. )
  1555. )
  1556. spans.append(
  1557. self.create_span(
  1558. {
  1559. "description": "bar",
  1560. "sentry_tags": {"status": "success"},
  1561. },
  1562. start_ts=self.ten_mins_ago,
  1563. )
  1564. )
  1565. self.store_spans(spans, is_eap=self.is_eap)
  1566. response = self.do_request(
  1567. {
  1568. "field": ["description", "count()"],
  1569. "orderby": "-count()",
  1570. "query": "",
  1571. "project": self.project.id,
  1572. "dataset": self.dataset,
  1573. }
  1574. )
  1575. assert response.status_code == 200, response.content
  1576. data = response.data["data"]
  1577. confidence = response.data["confidence"]
  1578. assert len(data) == 2
  1579. assert len(confidence) == 2
  1580. assert data[0]["count()"] == 10
  1581. assert confidence[0]["count()"] == "low"
  1582. assert data[1]["count()"] == 1
  1583. # While logically the confidence for 1 event at 100% sample rate should be high, we're going with low until we
  1584. # get customer feedback
  1585. assert confidence[1]["count()"] == "low"
  1586. def test_span_duration(self):
  1587. spans = [
  1588. self.create_span(
  1589. {"description": "bar", "sentry_tags": {"status": "invalid_argument"}},
  1590. start_ts=self.ten_mins_ago,
  1591. ),
  1592. self.create_span(
  1593. {"description": "foo", "sentry_tags": {"status": "success"}},
  1594. start_ts=self.ten_mins_ago,
  1595. ),
  1596. ]
  1597. self.store_spans(spans, is_eap=self.is_eap)
  1598. response = self.do_request(
  1599. {
  1600. "field": ["span.duration", "description"],
  1601. "query": "",
  1602. "orderby": "description",
  1603. "project": self.project.id,
  1604. "dataset": self.dataset,
  1605. }
  1606. )
  1607. assert response.status_code == 200, response.content
  1608. data = response.data["data"]
  1609. meta = response.data["meta"]
  1610. assert len(data) == 2
  1611. assert data == [
  1612. {
  1613. "span.duration": 1000.0,
  1614. "description": "bar",
  1615. "project.name": self.project.slug,
  1616. "id": spans[0]["span_id"],
  1617. },
  1618. {
  1619. "span.duration": 1000.0,
  1620. "description": "foo",
  1621. "project.name": self.project.slug,
  1622. "id": spans[1]["span_id"],
  1623. },
  1624. ]
  1625. assert meta["dataset"] == self.dataset
  1626. def test_average_sampling_rate(self):
  1627. spans = []
  1628. spans.append(
  1629. self.create_span(
  1630. {
  1631. "description": "foo",
  1632. "sentry_tags": {"status": "success"},
  1633. "measurements": {"client_sample_rate": {"value": 0.1}},
  1634. },
  1635. start_ts=self.ten_mins_ago,
  1636. )
  1637. )
  1638. spans.append(
  1639. self.create_span(
  1640. {
  1641. "description": "bar",
  1642. "sentry_tags": {"status": "success"},
  1643. "measurements": {"client_sample_rate": {"value": 0.85}},
  1644. },
  1645. start_ts=self.ten_mins_ago,
  1646. )
  1647. )
  1648. self.store_spans(spans, is_eap=self.is_eap)
  1649. response = self.do_request(
  1650. {
  1651. "field": [
  1652. "avg_sample(sampling_rate)",
  1653. "count()",
  1654. "min(sampling_rate)",
  1655. "count_sample()",
  1656. ],
  1657. "query": "",
  1658. "project": self.project.id,
  1659. "dataset": self.dataset,
  1660. }
  1661. )
  1662. assert response.status_code == 200, response.content
  1663. data = response.data["data"]
  1664. confidence = response.data["confidence"]
  1665. assert len(data) == 1
  1666. assert data[0]["avg_sample(sampling_rate)"] == pytest.approx(0.475)
  1667. assert data[0]["min(sampling_rate)"] == pytest.approx(0.1)
  1668. assert data[0]["count_sample()"] == 2
  1669. assert data[0]["count()"] == 11
  1670. assert confidence[0]["count()"] == "low"
  1671. def test_aggregate_numeric_attr(self):
  1672. self.store_spans(
  1673. [
  1674. self.create_span(
  1675. {
  1676. "description": "foo",
  1677. "sentry_tags": {"status": "success"},
  1678. "tags": {"bar": "bar1"},
  1679. },
  1680. start_ts=self.ten_mins_ago,
  1681. ),
  1682. self.create_span(
  1683. {
  1684. "description": "foo",
  1685. "sentry_tags": {"status": "success"},
  1686. "tags": {"bar": "bar2"},
  1687. },
  1688. measurements={"foo": {"value": 5}},
  1689. start_ts=self.ten_mins_ago,
  1690. ),
  1691. ],
  1692. is_eap=self.is_eap,
  1693. )
  1694. response = self.do_request(
  1695. {
  1696. "field": [
  1697. "description",
  1698. "count_unique(bar)",
  1699. "count_unique(tags[bar])",
  1700. "count_unique(tags[bar,string])",
  1701. "count()",
  1702. "count(span.duration)",
  1703. "count(tags[foo, number])",
  1704. "sum(tags[foo,number])",
  1705. "avg(tags[foo,number])",
  1706. "p50(tags[foo,number])",
  1707. "p75(tags[foo,number])",
  1708. "p95(tags[foo,number])",
  1709. "p99(tags[foo,number])",
  1710. "p100(tags[foo,number])",
  1711. "min(tags[foo,number])",
  1712. "max(tags[foo,number])",
  1713. ],
  1714. "query": "",
  1715. "orderby": "description",
  1716. "project": self.project.id,
  1717. "dataset": self.dataset,
  1718. }
  1719. )
  1720. assert response.status_code == 200, response.content
  1721. assert len(response.data["data"]) == 1
  1722. data = response.data["data"]
  1723. assert data[0] == {
  1724. "description": "foo",
  1725. "count_unique(bar)": 2,
  1726. "count_unique(tags[bar])": 2,
  1727. "count_unique(tags[bar,string])": 2,
  1728. "count()": 2,
  1729. "count(span.duration)": 2,
  1730. "count(tags[foo, number])": 1,
  1731. "sum(tags[foo,number])": 5.0,
  1732. "avg(tags[foo,number])": 5.0,
  1733. "p50(tags[foo,number])": 5.0,
  1734. "p75(tags[foo,number])": 5.0,
  1735. "p95(tags[foo,number])": 5.0,
  1736. "p99(tags[foo,number])": 5.0,
  1737. "p100(tags[foo,number])": 5.0,
  1738. "min(tags[foo,number])": 5.0,
  1739. "max(tags[foo,number])": 5.0,
  1740. }
  1741. @pytest.mark.skip(reason="margin will not be moved to the RPC")
  1742. def test_margin_of_error(self):
  1743. super().test_margin_of_error()
  1744. @pytest.mark.skip(reason="module not migrated over")
  1745. def test_module_alias(self):
  1746. super().test_module_alias()
  1747. @pytest.mark.xfail(
  1748. reason="wip: depends on rpc having a way to set a different default in virtual contexts"
  1749. )
  1750. def test_span_module(self):
  1751. super().test_span_module()
  1752. def test_inp_span(self):
  1753. replay_id = uuid.uuid4().hex
  1754. self.store_spans(
  1755. [
  1756. self.create_span(
  1757. {
  1758. "sentry_tags": {
  1759. "replay_id": replay_id,
  1760. "browser.name": "Chrome",
  1761. "transaction": "/pageloads/",
  1762. }
  1763. },
  1764. start_ts=self.ten_mins_ago,
  1765. ),
  1766. ],
  1767. is_eap=self.is_eap,
  1768. )
  1769. response = self.do_request(
  1770. {
  1771. # Not moving origin.transaction to RPC, its equivalent to transaction and just represents the
  1772. # transaction that's related to the span
  1773. "field": ["replay.id", "browser.name", "transaction", "count()"],
  1774. "query": f"replay.id:{replay_id} AND browser.name:Chrome AND transaction:/pageloads/",
  1775. "orderby": "count()",
  1776. "project": self.project.id,
  1777. "dataset": self.dataset,
  1778. }
  1779. )
  1780. assert response.status_code == 200, response.content
  1781. data = response.data["data"]
  1782. meta = response.data["meta"]
  1783. assert len(data) == 1
  1784. assert data[0]["replay.id"] == replay_id
  1785. assert data[0]["browser.name"] == "Chrome"
  1786. assert data[0]["transaction"] == "/pageloads/"
  1787. assert meta["dataset"] == self.dataset
  1788. @pytest.mark.xfail(
  1789. reason="wip: depends on rpc having a way to set a different default in virtual contexts"
  1790. )
  1791. # https://github.com/getsentry/projects/issues/215?issue=getsentry%7Cprojects%7C488
  1792. def test_other_category_span(self):
  1793. super().test_other_category_span()
  1794. @pytest.mark.xfail(
  1795. reason="wip: not implemented yet, depends on rpc having a way to filter based on casing"
  1796. )
  1797. # https://github.com/getsentry/projects/issues/215?issue=getsentry%7Cprojects%7C489
  1798. def test_span_op_casing(self):
  1799. super().test_span_op_casing()
  1800. def test_tag_wildcards(self):
  1801. self.store_spans(
  1802. [
  1803. self.create_span(
  1804. {"description": "foo", "tags": {"foo": "bar"}},
  1805. start_ts=self.ten_mins_ago,
  1806. ),
  1807. self.create_span(
  1808. {"description": "qux", "tags": {"foo": "qux"}},
  1809. start_ts=self.ten_mins_ago,
  1810. ),
  1811. ],
  1812. is_eap=self.is_eap,
  1813. )
  1814. for query in [
  1815. "foo:b*",
  1816. "foo:*r",
  1817. "foo:*a*",
  1818. "foo:b*r",
  1819. ]:
  1820. response = self.do_request(
  1821. {
  1822. "field": ["foo", "count()"],
  1823. "query": query,
  1824. "project": self.project.id,
  1825. "dataset": self.dataset,
  1826. }
  1827. )
  1828. assert response.status_code == 200, response.content
  1829. assert response.data["data"] == [{"foo": "bar", "count()": 1}]
  1830. @pytest.mark.xfail(reason="wip: rate not implemented yet")
  1831. def test_spm(self):
  1832. super().test_spm()
  1833. def test_is_transaction(self):
  1834. self.store_spans(
  1835. [
  1836. self.create_span(
  1837. {
  1838. "description": "foo",
  1839. "sentry_tags": {"status": "success"},
  1840. "is_segment": True,
  1841. },
  1842. start_ts=self.ten_mins_ago,
  1843. ),
  1844. self.create_span(
  1845. {
  1846. "description": "bar",
  1847. "sentry_tags": {"status": "success"},
  1848. "is_segment": False,
  1849. },
  1850. start_ts=self.ten_mins_ago,
  1851. ),
  1852. ],
  1853. is_eap=self.is_eap,
  1854. )
  1855. response = self.do_request(
  1856. {
  1857. "field": ["span.status", "description", "count()", "is_transaction"],
  1858. "query": "is_transaction:true",
  1859. "orderby": "description",
  1860. "project": self.project.id,
  1861. "dataset": self.dataset,
  1862. }
  1863. )
  1864. assert response.status_code == 200, response.content
  1865. data = response.data["data"]
  1866. meta = response.data["meta"]
  1867. assert len(data) == 1
  1868. assert data == [
  1869. {
  1870. "is_transaction": True,
  1871. "span.status": "success",
  1872. "description": "foo",
  1873. "count()": 1,
  1874. },
  1875. ]
  1876. assert meta["dataset"] == self.dataset
  1877. def test_is_not_transaction(self):
  1878. self.store_spans(
  1879. [
  1880. self.create_span(
  1881. {
  1882. "description": "foo",
  1883. "sentry_tags": {"status": "success"},
  1884. "is_segment": True,
  1885. },
  1886. start_ts=self.ten_mins_ago,
  1887. ),
  1888. self.create_span(
  1889. {
  1890. "description": "bar",
  1891. "sentry_tags": {"status": "success"},
  1892. "is_segment": False,
  1893. },
  1894. start_ts=self.ten_mins_ago,
  1895. ),
  1896. ],
  1897. is_eap=self.is_eap,
  1898. )
  1899. response = self.do_request(
  1900. {
  1901. "field": ["span.status", "description", "count()", "is_transaction"],
  1902. "query": "is_transaction:0",
  1903. "orderby": "description",
  1904. "project": self.project.id,
  1905. "dataset": self.dataset,
  1906. }
  1907. )
  1908. assert response.status_code == 200, response.content
  1909. data = response.data["data"]
  1910. meta = response.data["meta"]
  1911. assert len(data) == 1
  1912. assert data == [
  1913. {
  1914. "is_transaction": False,
  1915. "span.status": "success",
  1916. "description": "bar",
  1917. "count()": 1,
  1918. },
  1919. ]
  1920. assert meta["dataset"] == self.dataset
  1921. def test_byte_fields(self):
  1922. self.store_spans(
  1923. [
  1924. self.create_span(
  1925. {
  1926. "description": "foo",
  1927. "data": {
  1928. "cache.item_size": 1,
  1929. "messaging.message.body.size": 2,
  1930. },
  1931. },
  1932. start_ts=self.ten_mins_ago,
  1933. ),
  1934. ],
  1935. is_eap=self.is_eap,
  1936. )
  1937. response = self.do_request(
  1938. {
  1939. "field": [
  1940. "cache.item_size",
  1941. "measurements.cache.item_size",
  1942. "messaging.message.body.size",
  1943. "measurements.messaging.message.body.size",
  1944. ],
  1945. "project": self.project.id,
  1946. "dataset": self.dataset,
  1947. }
  1948. )
  1949. assert response.data["data"] == [
  1950. {
  1951. "id": mock.ANY,
  1952. "project.name": self.project.slug,
  1953. "cache.item_size": 1.0,
  1954. "measurements.cache.item_size": 1.0,
  1955. "measurements.messaging.message.body.size": 2.0,
  1956. "messaging.message.body.size": 2.0,
  1957. },
  1958. ]
  1959. assert response.data["meta"]["fields"] == {
  1960. "id": "string",
  1961. "project.name": "string",
  1962. "cache.item_size": "size",
  1963. "measurements.cache.item_size": "size",
  1964. "measurements.messaging.message.body.size": "size",
  1965. "messaging.message.body.size": "size",
  1966. }
  1967. assert response.data["meta"]["units"] == {
  1968. "id": None,
  1969. "project.name": None,
  1970. "cache.item_size": "byte",
  1971. "measurements.cache.item_size": "byte",
  1972. "measurements.messaging.message.body.size": "byte",
  1973. "messaging.message.body.size": "byte",
  1974. }
  1975. def test_simple_measurements(self):
  1976. keys = [
  1977. ("app_start_cold", "duration", "millisecond"),
  1978. ("app_start_warm", "duration", "millisecond"),
  1979. ("frames_frozen", "number", None), # should be integer but keeping it consistent
  1980. ("frames_frozen_rate", "percentage", None),
  1981. ("frames_slow", "number", None), # should be integer but keeping it consistent
  1982. ("frames_slow_rate", "percentage", None),
  1983. ("frames_total", "number", None), # should be integer but keeping it consistent
  1984. ("time_to_initial_display", "duration", "millisecond"),
  1985. ("time_to_full_display", "duration", "millisecond"),
  1986. ("stall_count", "number", None), # should be integer but keeping it consistent
  1987. ("stall_percentage", "percentage", None),
  1988. ("stall_stall_longest_time", "number", None),
  1989. ("stall_stall_total_time", "number", None),
  1990. ("cls", "number", None),
  1991. ("fcp", "duration", "millisecond"),
  1992. ("fid", "duration", "millisecond"),
  1993. ("fp", "duration", "millisecond"),
  1994. ("inp", "duration", "millisecond"),
  1995. ("lcp", "duration", "millisecond"),
  1996. ("ttfb", "duration", "millisecond"),
  1997. ("ttfb.requesttime", "duration", "millisecond"),
  1998. ("score.cls", "number", None),
  1999. ("score.fcp", "number", None),
  2000. ("score.fid", "number", None),
  2001. ("score.inp", "number", None),
  2002. ("score.lcp", "number", None),
  2003. ("score.ttfb", "number", None),
  2004. ("score.total", "number", None),
  2005. ("score.weight.cls", "number", None),
  2006. ("score.weight.fcp", "number", None),
  2007. ("score.weight.fid", "number", None),
  2008. ("score.weight.inp", "number", None),
  2009. ("score.weight.lcp", "number", None),
  2010. ("score.weight.ttfb", "number", None),
  2011. ("cache.item_size", "size", "byte"),
  2012. ("messaging.message.body.size", "size", "byte"),
  2013. ("messaging.message.receive.latency", "duration", "millisecond"),
  2014. ("messaging.message.retry.count", "number", None),
  2015. ]
  2016. self._test_simple_measurements(keys)