test_organization_events_span_metrics.py 75 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213
  1. from datetime import timedelta
  2. import pytest
  3. from django.urls import reverse
  4. from sentry.search.events import constants
  5. from sentry.search.utils import map_device_class_level
  6. from sentry.testutils.cases import MetricsEnhancedPerformanceTestCase
  7. from sentry.testutils.helpers.datetime import before_now
  8. pytestmark = pytest.mark.sentry_metrics
  9. SPAN_DURATION_MRI = "d:spans/duration@millisecond"
  10. class OrganizationEventsMetricsEnhancedPerformanceEndpointTest(MetricsEnhancedPerformanceTestCase):
  11. viewname = "sentry-api-0-organization-events"
  12. # Poor intentionally omitted for test_measurement_rating_that_does_not_exist
  13. METRIC_STRINGS = [
  14. "foo_transaction",
  15. "bar_transaction",
  16. ]
  17. def setUp(self):
  18. super().setUp()
  19. self.min_ago = before_now(minutes=1)
  20. self.six_min_ago = before_now(minutes=6)
  21. self.three_days_ago = before_now(days=3)
  22. self.features = {
  23. "organizations:starfish-view": True,
  24. }
  25. def do_request(self, query, features=None):
  26. if features is None:
  27. features = {"organizations:discover-basic": True}
  28. features.update(self.features)
  29. self.login_as(user=self.user)
  30. url = reverse(
  31. self.viewname,
  32. kwargs={"organization_id_or_slug": self.organization.slug},
  33. )
  34. with self.feature(features):
  35. return self.client.get(url, query, format="json")
  36. def test_p50_with_no_data(self):
  37. response = self.do_request(
  38. {
  39. "field": ["p50()"],
  40. "query": "",
  41. "project": self.project.id,
  42. "dataset": "spansMetrics",
  43. }
  44. )
  45. assert response.status_code == 200, response.content
  46. data = response.data["data"]
  47. meta = response.data["meta"]
  48. assert len(data) == 1
  49. assert data[0]["p50()"] == 0
  50. assert meta["dataset"] == "spansMetrics"
  51. @pytest.mark.querybuilder
  52. def test_count(self):
  53. self.store_span_metric(
  54. 1,
  55. internal_metric=constants.SELF_TIME_LIGHT,
  56. timestamp=self.three_days_ago,
  57. )
  58. response = self.do_request(
  59. {
  60. "field": ["count()"],
  61. "query": "",
  62. "project": self.project.id,
  63. "dataset": "spansMetrics",
  64. "statsPeriod": "7d",
  65. }
  66. )
  67. assert response.status_code == 200, response.content
  68. data = response.data["data"]
  69. meta = response.data["meta"]
  70. assert len(data) == 1
  71. assert data[0]["count()"] == 1
  72. assert meta["dataset"] == "spansMetrics"
  73. def test_count_if(self):
  74. self.store_span_metric(
  75. 2,
  76. internal_metric=constants.SELF_TIME_LIGHT,
  77. timestamp=self.three_days_ago,
  78. tags={"release": "1.0.0"},
  79. )
  80. self.store_span_metric(
  81. 2,
  82. internal_metric=constants.SELF_TIME_LIGHT,
  83. timestamp=self.three_days_ago,
  84. tags={"release": "1.0.0"},
  85. )
  86. self.store_span_metric(
  87. 2,
  88. internal_metric=constants.SELF_TIME_LIGHT,
  89. timestamp=self.three_days_ago,
  90. tags={"release": "2.0.0"},
  91. )
  92. fieldRelease1 = "count_if(release,1.0.0)"
  93. fieldRelease2 = "count_if(release,2.0.0)"
  94. response = self.do_request(
  95. {
  96. "field": [fieldRelease1, fieldRelease2],
  97. "query": "",
  98. "project": self.project.id,
  99. "dataset": "spansMetrics",
  100. "statsPeriod": "7d",
  101. }
  102. )
  103. assert response.status_code == 200, response.content
  104. data = response.data["data"]
  105. meta = response.data["meta"]
  106. assert len(data) == 1
  107. assert data[0][fieldRelease1] == 2
  108. assert data[0][fieldRelease2] == 1
  109. assert meta["dataset"] == "spansMetrics"
  110. def test_division(self):
  111. # 10 slow frames, 20 frozen frames, 100 total frames
  112. self.store_span_metric(
  113. {
  114. "min": 1,
  115. "max": 1,
  116. "sum": 10,
  117. "count": 10,
  118. "last": 1,
  119. },
  120. entity="metrics_gauges",
  121. metric="mobile.slow_frames",
  122. timestamp=self.three_days_ago,
  123. )
  124. self.store_span_metric(
  125. {
  126. "min": 1,
  127. "max": 1,
  128. "sum": 20,
  129. "count": 20,
  130. "last": 1,
  131. },
  132. entity="metrics_gauges",
  133. metric="mobile.frozen_frames",
  134. timestamp=self.three_days_ago,
  135. )
  136. self.store_span_metric(
  137. {
  138. "min": 1,
  139. "max": 1,
  140. "sum": 100,
  141. "count": 100,
  142. "last": 1,
  143. },
  144. entity="metrics_gauges",
  145. metric="mobile.total_frames",
  146. timestamp=self.three_days_ago,
  147. )
  148. slow_frame_rate = "division(mobile.slow_frames,mobile.total_frames)"
  149. frozen_frame_rate = "division(mobile.frozen_frames,mobile.total_frames)"
  150. response = self.do_request(
  151. {
  152. "field": [slow_frame_rate, frozen_frame_rate],
  153. "query": "",
  154. "project": self.project.id,
  155. "dataset": "spansMetrics",
  156. "statsPeriod": "7d",
  157. }
  158. )
  159. assert response.status_code == 200, response.content
  160. data = response.data["data"]
  161. meta = response.data["meta"]
  162. assert len(data) == 1
  163. assert data[0][slow_frame_rate] == 10 / 100
  164. assert data[0][frozen_frame_rate] == 20 / 100
  165. assert meta["dataset"] == "spansMetrics"
  166. def test_division_if(self):
  167. self.store_span_metric(
  168. {
  169. "min": 1,
  170. "max": 1,
  171. "sum": 1,
  172. "count": 1,
  173. "last": 1,
  174. },
  175. entity="metrics_gauges",
  176. metric="mobile.slow_frames",
  177. timestamp=self.three_days_ago,
  178. tags={"release": "1.0.0"},
  179. )
  180. self.store_span_metric(
  181. {
  182. "min": 1,
  183. "max": 1,
  184. "sum": 15,
  185. "count": 15,
  186. "last": 1,
  187. },
  188. entity="metrics_gauges",
  189. metric="mobile.total_frames",
  190. timestamp=self.three_days_ago,
  191. tags={"release": "1.0.0"},
  192. )
  193. self.store_span_metric(
  194. {
  195. "min": 1,
  196. "max": 1,
  197. "sum": 2,
  198. "count": 2,
  199. "last": 1,
  200. },
  201. entity="metrics_gauges",
  202. metric="mobile.frozen_frames",
  203. timestamp=self.three_days_ago,
  204. tags={"release": "2.0.0"},
  205. )
  206. self.store_span_metric(
  207. {
  208. "min": 1,
  209. "max": 1,
  210. "sum": 10,
  211. "count": 10,
  212. "last": 1,
  213. },
  214. entity="metrics_gauges",
  215. metric="mobile.total_frames",
  216. timestamp=self.three_days_ago,
  217. tags={"release": "2.0.0"},
  218. )
  219. fieldRelease1 = "division_if(mobile.slow_frames,mobile.total_frames,release,1.0.0)"
  220. fieldRelease2 = "division_if(mobile.frozen_frames,mobile.total_frames,release,2.0.0)"
  221. response = self.do_request(
  222. {
  223. "field": [fieldRelease1, fieldRelease2],
  224. "query": "",
  225. "project": self.project.id,
  226. "dataset": "spansMetrics",
  227. "statsPeriod": "7d",
  228. }
  229. )
  230. assert response.status_code == 200, response.content
  231. data = response.data["data"]
  232. meta = response.data["meta"]
  233. assert len(data) == 1
  234. assert data[0][fieldRelease1] == 1 / 15
  235. assert data[0][fieldRelease2] == 2 / 10
  236. assert meta["dataset"] == "spansMetrics"
  237. def test_count_unique(self):
  238. self.store_span_metric(
  239. 1,
  240. "user",
  241. timestamp=self.min_ago,
  242. )
  243. self.store_span_metric(
  244. 2,
  245. "user",
  246. timestamp=self.min_ago,
  247. )
  248. response = self.do_request(
  249. {
  250. "field": ["count_unique(user)"],
  251. "query": "",
  252. "project": self.project.id,
  253. "dataset": "spansMetrics",
  254. }
  255. )
  256. assert response.status_code == 200, response.content
  257. data = response.data["data"]
  258. meta = response.data["meta"]
  259. assert len(data) == 1
  260. assert data[0]["count_unique(user)"] == 2
  261. assert meta["dataset"] == "spansMetrics"
  262. def test_sum(self):
  263. self.store_span_metric(
  264. 321,
  265. internal_metric=constants.SELF_TIME_LIGHT,
  266. timestamp=self.min_ago,
  267. )
  268. self.store_span_metric(
  269. 99,
  270. internal_metric=constants.SELF_TIME_LIGHT,
  271. timestamp=self.min_ago,
  272. )
  273. response = self.do_request(
  274. {
  275. "field": ["sum(span.self_time)"],
  276. "query": "",
  277. "project": self.project.id,
  278. "dataset": "spansMetrics",
  279. }
  280. )
  281. assert response.status_code == 200, response.content
  282. data = response.data["data"]
  283. meta = response.data["meta"]
  284. assert len(data) == 1
  285. assert data[0]["sum(span.self_time)"] == 420
  286. assert meta["dataset"] == "spansMetrics"
  287. def test_percentile(self):
  288. self.store_span_metric(
  289. 1,
  290. internal_metric=constants.SELF_TIME_LIGHT,
  291. timestamp=self.min_ago,
  292. )
  293. response = self.do_request(
  294. {
  295. "field": ["percentile(span.self_time, 0.95)"],
  296. "query": "",
  297. "project": self.project.id,
  298. "dataset": "spansMetrics",
  299. }
  300. )
  301. assert response.status_code == 200, response.content
  302. data = response.data["data"]
  303. meta = response.data["meta"]
  304. assert len(data) == 1
  305. assert data[0]["percentile(span.self_time, 0.95)"] == 1
  306. assert meta["dataset"] == "spansMetrics"
  307. def test_fixed_percentile_functions(self):
  308. self.store_span_metric(
  309. 1,
  310. internal_metric=constants.SELF_TIME_LIGHT,
  311. timestamp=self.min_ago,
  312. )
  313. for function in ["p50()", "p75()", "p95()", "p99()", "p100()"]:
  314. response = self.do_request(
  315. {
  316. "field": [function],
  317. "query": "",
  318. "project": self.project.id,
  319. "dataset": "spansMetrics",
  320. }
  321. )
  322. assert response.status_code == 200, response.content
  323. data = response.data["data"]
  324. meta = response.data["meta"]
  325. assert len(data) == 1
  326. assert data[0][function] == 1, function
  327. assert meta["dataset"] == "spansMetrics", function
  328. assert meta["fields"][function] == "duration", function
  329. def test_fixed_percentile_functions_with_duration(self):
  330. self.store_span_metric(
  331. 1,
  332. internal_metric=constants.SPAN_METRICS_MAP["span.duration"],
  333. timestamp=self.min_ago,
  334. )
  335. for function in [
  336. "p50(span.duration)",
  337. "p75(span.duration)",
  338. "p95(span.duration)",
  339. "p99(span.duration)",
  340. "p100(span.duration)",
  341. ]:
  342. response = self.do_request(
  343. {
  344. "field": [function],
  345. "query": "",
  346. "project": self.project.id,
  347. "dataset": "spansMetrics",
  348. }
  349. )
  350. assert response.status_code == 200, response.content
  351. data = response.data["data"]
  352. meta = response.data["meta"]
  353. assert len(data) == 1, function
  354. assert data[0][function] == 1, function
  355. assert meta["dataset"] == "spansMetrics", function
  356. assert meta["fields"][function] == "duration", function
  357. def test_avg(self):
  358. self.store_span_metric(
  359. 1,
  360. internal_metric=constants.SELF_TIME_LIGHT,
  361. timestamp=self.min_ago,
  362. )
  363. response = self.do_request(
  364. {
  365. "field": ["avg()"],
  366. "query": "",
  367. "project": self.project.id,
  368. "dataset": "spansMetrics",
  369. }
  370. )
  371. assert response.status_code == 200, response.content
  372. data = response.data["data"]
  373. meta = response.data["meta"]
  374. assert len(data) == 1
  375. assert data[0]["avg()"] == 1
  376. assert meta["dataset"] == "spansMetrics"
  377. def test_avg_on_http_response(self):
  378. self.store_span_metric(
  379. 10,
  380. internal_metric=constants.SPAN_METRICS_MAP["http.response_content_length"],
  381. timestamp=self.min_ago,
  382. )
  383. self.store_span_metric(
  384. 15,
  385. internal_metric=constants.SPAN_METRICS_MAP["http.response_transfer_size"],
  386. timestamp=self.min_ago,
  387. )
  388. self.store_span_metric(
  389. 20,
  390. internal_metric=constants.SPAN_METRICS_MAP["http.decoded_response_content_length"],
  391. timestamp=self.min_ago,
  392. )
  393. response = self.do_request(
  394. {
  395. "field": [
  396. "avg(http.response_content_length)",
  397. "avg(http.response_transfer_size)",
  398. "avg(http.decoded_response_content_length)",
  399. ],
  400. "query": "",
  401. "project": self.project.id,
  402. "dataset": "spansMetrics",
  403. }
  404. )
  405. assert response.status_code == 200, response.content
  406. data = response.data["data"]
  407. assert len(data) == 1
  408. assert data[0]["avg(http.response_content_length)"] == 10
  409. assert data[0]["avg(http.response_transfer_size)"] == 15
  410. assert data[0]["avg(http.decoded_response_content_length)"] == 20
  411. meta = response.data["meta"]
  412. assert meta["fields"]["avg(http.response_content_length)"] == "size"
  413. assert meta["fields"]["avg(http.response_transfer_size)"] == "size"
  414. assert meta["fields"]["avg(http.decoded_response_content_length)"] == "size"
  415. assert meta["units"]["avg(http.response_content_length)"] == "byte"
  416. assert meta["units"]["avg(http.response_transfer_size)"] == "byte"
  417. assert meta["units"]["avg(http.decoded_response_content_length)"] == "byte"
  418. assert meta["dataset"] == "spansMetrics"
  419. def test_eps(self):
  420. for _ in range(6):
  421. self.store_span_metric(
  422. 1,
  423. internal_metric=constants.SELF_TIME_LIGHT,
  424. timestamp=self.min_ago,
  425. )
  426. response = self.do_request(
  427. {
  428. "field": ["eps()", "sps()"],
  429. "query": "",
  430. "project": self.project.id,
  431. "dataset": "spansMetrics",
  432. "statsPeriod": "10m",
  433. }
  434. )
  435. assert response.status_code == 200, response.content
  436. data = response.data["data"]
  437. meta = response.data["meta"]
  438. assert len(data) == 1
  439. assert data[0]["eps()"] == 0.01
  440. assert data[0]["sps()"] == 0.01
  441. assert meta["fields"]["eps()"] == "rate"
  442. assert meta["fields"]["sps()"] == "rate"
  443. assert meta["units"]["eps()"] == "1/second"
  444. assert meta["units"]["sps()"] == "1/second"
  445. assert meta["dataset"] == "spansMetrics"
  446. def test_epm(self):
  447. for _ in range(6):
  448. self.store_span_metric(
  449. 1,
  450. internal_metric=constants.SELF_TIME_LIGHT,
  451. timestamp=self.min_ago,
  452. )
  453. response = self.do_request(
  454. {
  455. "field": ["epm()", "spm()"],
  456. "query": "",
  457. "project": self.project.id,
  458. "dataset": "spansMetrics",
  459. "statsPeriod": "10m",
  460. }
  461. )
  462. assert response.status_code == 200, response.content
  463. data = response.data["data"]
  464. meta = response.data["meta"]
  465. assert len(data) == 1
  466. assert data[0]["epm()"] == 0.6
  467. assert data[0]["spm()"] == 0.6
  468. assert meta["fields"]["epm()"] == "rate"
  469. assert meta["fields"]["spm()"] == "rate"
  470. assert meta["units"]["epm()"] == "1/minute"
  471. assert meta["units"]["spm()"] == "1/minute"
  472. assert meta["dataset"] == "spansMetrics"
  473. def test_time_spent_percentage(self):
  474. for _ in range(4):
  475. self.store_span_metric(
  476. 1,
  477. internal_metric=constants.SELF_TIME_LIGHT,
  478. tags={"transaction": "foo_transaction"},
  479. timestamp=self.min_ago,
  480. )
  481. self.store_span_metric(
  482. 1,
  483. tags={"transaction": "foo_transaction"},
  484. timestamp=self.min_ago,
  485. )
  486. self.store_span_metric(
  487. 1,
  488. internal_metric=constants.SELF_TIME_LIGHT,
  489. tags={"transaction": "bar_transaction"},
  490. timestamp=self.min_ago,
  491. )
  492. self.store_span_metric(
  493. 1,
  494. tags={"transaction": "bar_transaction"},
  495. timestamp=self.min_ago,
  496. )
  497. response = self.do_request(
  498. {
  499. "field": ["transaction", "time_spent_percentage()"],
  500. "query": "",
  501. "orderby": ["-time_spent_percentage()"],
  502. "project": self.project.id,
  503. "dataset": "spansMetrics",
  504. "statsPeriod": "10m",
  505. }
  506. )
  507. assert response.status_code == 200, response.content
  508. data = response.data["data"]
  509. meta = response.data["meta"]
  510. assert len(data) == 2
  511. assert data[0]["time_spent_percentage()"] == 0.8
  512. assert data[0]["transaction"] == "foo_transaction"
  513. assert data[1]["time_spent_percentage()"] == 0.2
  514. assert data[1]["transaction"] == "bar_transaction"
  515. assert meta["dataset"] == "spansMetrics"
  516. def test_time_spent_percentage_local(self):
  517. response = self.do_request(
  518. {
  519. "field": ["time_spent_percentage(local)"],
  520. "query": "",
  521. "orderby": ["-time_spent_percentage(local)"],
  522. "project": self.project.id,
  523. "dataset": "spansMetrics",
  524. "statsPeriod": "10m",
  525. }
  526. )
  527. assert response.status_code == 200, response.content
  528. data = response.data["data"]
  529. meta = response.data["meta"]
  530. assert len(data) == 1
  531. assert data[0]["time_spent_percentage(local)"] is None
  532. assert meta["dataset"] == "spansMetrics"
  533. def test_time_spent_percentage_on_span_duration(self):
  534. for _ in range(4):
  535. self.store_span_metric(
  536. 1,
  537. internal_metric=constants.SPAN_METRICS_MAP["span.duration"],
  538. tags={"transaction": "foo_transaction"},
  539. timestamp=self.min_ago,
  540. )
  541. self.store_span_metric(
  542. 1,
  543. internal_metric=constants.SPAN_METRICS_MAP["span.duration"],
  544. tags={"transaction": "bar_transaction"},
  545. timestamp=self.min_ago,
  546. )
  547. response = self.do_request(
  548. {
  549. "field": ["transaction", "time_spent_percentage(app,span.duration)"],
  550. "query": "",
  551. "orderby": ["-time_spent_percentage(app,span.duration)"],
  552. "project": self.project.id,
  553. "dataset": "spansMetrics",
  554. "statsPeriod": "10m",
  555. }
  556. )
  557. assert response.status_code == 200, response.content
  558. data = response.data["data"]
  559. meta = response.data["meta"]
  560. assert len(data) == 2
  561. assert data[0]["time_spent_percentage(app,span.duration)"] == 0.8
  562. assert data[0]["transaction"] == "foo_transaction"
  563. assert data[1]["time_spent_percentage(app,span.duration)"] == 0.2
  564. assert data[1]["transaction"] == "bar_transaction"
  565. assert meta["dataset"] == "spansMetrics"
  566. def test_http_error_rate_and_count(self):
  567. for _ in range(4):
  568. self.store_span_metric(
  569. 1,
  570. internal_metric=constants.SELF_TIME_LIGHT,
  571. tags={"span.status_code": "500"},
  572. timestamp=self.min_ago,
  573. )
  574. self.store_span_metric(
  575. 1,
  576. internal_metric=constants.SELF_TIME_LIGHT,
  577. tags={"span.status_code": "200"},
  578. timestamp=self.min_ago,
  579. )
  580. response = self.do_request(
  581. {
  582. "field": ["http_error_count()", "http_error_rate()"],
  583. "query": "",
  584. "orderby": ["-http_error_rate()"],
  585. "project": self.project.id,
  586. "dataset": "spansMetrics",
  587. "statsPeriod": "10m",
  588. }
  589. )
  590. assert response.status_code == 200, response.content
  591. data = response.data["data"]
  592. meta = response.data["meta"]
  593. assert len(data) == 1
  594. assert data[0]["http_error_rate()"] == 0.8
  595. assert meta["dataset"] == "spansMetrics"
  596. assert meta["fields"]["http_error_count()"] == "integer"
  597. assert meta["fields"]["http_error_rate()"] == "percentage"
  598. def test_ttid_rate_and_count(self):
  599. for _ in range(8):
  600. self.store_span_metric(
  601. 1,
  602. internal_metric=constants.SELF_TIME_LIGHT,
  603. tags={"ttid": "ttid", "ttfd": "ttfd"},
  604. timestamp=self.min_ago,
  605. )
  606. self.store_span_metric(
  607. 1,
  608. internal_metric=constants.SELF_TIME_LIGHT,
  609. tags={"ttfd": "ttfd", "ttid": ""},
  610. timestamp=self.min_ago,
  611. )
  612. self.store_span_metric(
  613. 1,
  614. internal_metric=constants.SELF_TIME_LIGHT,
  615. tags={"ttfd": "", "ttid": ""},
  616. timestamp=self.min_ago,
  617. )
  618. response = self.do_request(
  619. {
  620. "field": [
  621. "ttid_contribution_rate()",
  622. "ttid_count()",
  623. "ttfd_contribution_rate()",
  624. "ttfd_count()",
  625. ],
  626. "query": "",
  627. "orderby": ["-ttid_contribution_rate()"],
  628. "project": self.project.id,
  629. "dataset": "spansMetrics",
  630. "statsPeriod": "10m",
  631. }
  632. )
  633. assert response.status_code == 200, response.content
  634. data = response.data["data"]
  635. meta = response.data["meta"]
  636. assert len(data) == 1
  637. assert data[0]["ttid_contribution_rate()"] == 0.8
  638. assert data[0]["ttid_count()"] == 8
  639. assert data[0]["ttfd_contribution_rate()"] == 0.9
  640. assert data[0]["ttfd_count()"] == 9
  641. assert meta["dataset"] == "spansMetrics"
  642. assert meta["fields"]["ttid_count()"] == "integer"
  643. assert meta["fields"]["ttid_contribution_rate()"] == "percentage"
  644. assert meta["fields"]["ttfd_count()"] == "integer"
  645. assert meta["fields"]["ttfd_contribution_rate()"] == "percentage"
  646. def test_main_thread_count(self):
  647. for _ in range(8):
  648. self.store_span_metric(
  649. 1,
  650. internal_metric=constants.SELF_TIME_LIGHT,
  651. tags={"span.main_thread": "true"},
  652. timestamp=self.min_ago,
  653. )
  654. self.store_span_metric(
  655. 1,
  656. internal_metric=constants.SELF_TIME_LIGHT,
  657. tags={},
  658. timestamp=self.min_ago,
  659. )
  660. self.store_span_metric(
  661. 1,
  662. internal_metric=constants.SELF_TIME_LIGHT,
  663. tags={"span.main_thread": ""},
  664. timestamp=self.min_ago,
  665. )
  666. response = self.do_request(
  667. {
  668. "field": [
  669. "main_thread_count()",
  670. ],
  671. "query": "",
  672. "orderby": ["-main_thread_count()"],
  673. "project": self.project.id,
  674. "dataset": "spansMetrics",
  675. "statsPeriod": "10m",
  676. }
  677. )
  678. assert response.status_code == 200, response.content
  679. data = response.data["data"]
  680. meta = response.data["meta"]
  681. assert len(data) == 1
  682. assert data[0]["main_thread_count()"] == 8
  683. assert meta["dataset"] == "spansMetrics"
  684. assert meta["fields"]["main_thread_count()"] == "integer"
  685. def test_use_self_time_light(self):
  686. self.store_span_metric(
  687. 100,
  688. internal_metric=constants.SELF_TIME_LIGHT,
  689. tags={"transaction": "foo_transaction"},
  690. timestamp=self.min_ago,
  691. )
  692. response = self.do_request(
  693. {
  694. "field": ["p50(span.self_time)"],
  695. # Should be 0 since its filtering on transaction
  696. "query": "transaction:foo_transaction",
  697. "orderby": ["-p50(span.self_time)"],
  698. "project": self.project.id,
  699. "dataset": "spansMetrics",
  700. "statsPeriod": "10m",
  701. }
  702. )
  703. assert response.status_code == 200, response.content
  704. data = response.data["data"]
  705. meta = response.data["meta"]
  706. assert len(data) == 1
  707. assert data[0]["p50(span.self_time)"] == 0
  708. assert meta["dataset"] == "spansMetrics"
  709. assert meta["fields"]["p50(span.self_time)"] == "duration"
  710. response = self.do_request(
  711. {
  712. # Should be 0 since it has a transaction column
  713. "field": ["transaction", "p50(span.self_time)"],
  714. "query": "",
  715. "orderby": ["-p50(span.self_time)"],
  716. "project": self.project.id,
  717. "dataset": "spansMetrics",
  718. "statsPeriod": "10m",
  719. }
  720. )
  721. assert response.status_code == 200, response.content
  722. data = response.data["data"]
  723. meta = response.data["meta"]
  724. assert len(data) == 0
  725. response = self.do_request(
  726. {
  727. "field": ["p50(span.self_time)"],
  728. # Should be 100 since its not filtering on transaction
  729. "query": "",
  730. "orderby": ["-p50(span.self_time)"],
  731. "project": self.project.id,
  732. "dataset": "spansMetrics",
  733. "statsPeriod": "10m",
  734. }
  735. )
  736. assert response.status_code == 200, response.content
  737. data = response.data["data"]
  738. meta = response.data["meta"]
  739. assert len(data) == 1
  740. assert data[0]["p50(span.self_time)"] == 100
  741. assert meta["dataset"] == "spansMetrics"
  742. assert meta["fields"]["p50(span.self_time)"] == "duration"
  743. def test_span_module(self):
  744. self.store_span_metric(
  745. 1,
  746. internal_metric=constants.SELF_TIME_LIGHT,
  747. timestamp=self.six_min_ago,
  748. tags={"span.category": "http", "span.description": "f"},
  749. )
  750. self.store_span_metric(
  751. 3,
  752. internal_metric=constants.SELF_TIME_LIGHT,
  753. timestamp=self.six_min_ago,
  754. tags={"span.category": "db", "span.description": "e"},
  755. )
  756. self.store_span_metric(
  757. 5,
  758. internal_metric=constants.SELF_TIME_LIGHT,
  759. timestamp=self.six_min_ago,
  760. tags={"span.category": "foobar", "span.description": "d"},
  761. )
  762. self.store_span_metric(
  763. 7,
  764. internal_metric=constants.SELF_TIME_LIGHT,
  765. timestamp=self.six_min_ago,
  766. tags={"span.category": "cache", "span.description": "c"},
  767. )
  768. self.store_span_metric(
  769. 9,
  770. internal_metric=constants.SELF_TIME_LIGHT,
  771. timestamp=self.six_min_ago,
  772. tags={"span.category": "db", "span.op": "db.redis", "span.description": "b"},
  773. )
  774. self.store_span_metric(
  775. 11,
  776. internal_metric=constants.SELF_TIME_LIGHT,
  777. timestamp=self.six_min_ago,
  778. tags={"span.category": "db", "span.op": "db.sql.room", "span.description": "a"},
  779. )
  780. response = self.do_request(
  781. {
  782. "field": ["span.module", "span.description", "p50(span.self_time)"],
  783. "query": "",
  784. "orderby": ["-p50(span.self_time)"],
  785. "project": self.project.id,
  786. "dataset": "spansMetrics",
  787. "statsPeriod": "10m",
  788. }
  789. )
  790. assert response.status_code == 200, response.content
  791. data = response.data["data"]
  792. meta = response.data["meta"]
  793. assert len(data) == 6
  794. assert data[0]["p50(span.self_time)"] == 11
  795. assert data[0]["span.module"] == "other"
  796. assert data[0]["span.description"] == "a"
  797. assert data[1]["p50(span.self_time)"] == 9
  798. assert data[1]["span.module"] == "cache"
  799. assert data[1]["span.description"] == "b"
  800. assert data[2]["p50(span.self_time)"] == 7
  801. assert data[2]["span.module"] == "cache"
  802. assert data[2]["span.description"] == "c"
  803. assert data[3]["p50(span.self_time)"] == 5
  804. assert data[3]["span.module"] == "other"
  805. assert data[3]["span.description"] == "d"
  806. assert data[4]["p50(span.self_time)"] == 3
  807. assert data[4]["span.module"] == "db"
  808. assert data[4]["span.description"] == "e"
  809. assert data[5]["p50(span.self_time)"] == 1
  810. assert data[5]["span.module"] == "http"
  811. assert data[5]["span.description"] == "f"
  812. assert meta["dataset"] == "spansMetrics"
  813. assert meta["fields"]["p50(span.self_time)"] == "duration"
  814. def test_tag_search(self):
  815. self.store_span_metric(
  816. 321,
  817. internal_metric=constants.SELF_TIME_LIGHT,
  818. timestamp=self.min_ago,
  819. tags={"span.description": "foo"},
  820. )
  821. self.store_span_metric(
  822. 99,
  823. internal_metric=constants.SELF_TIME_LIGHT,
  824. timestamp=self.min_ago,
  825. tags={"span.description": "bar"},
  826. )
  827. response = self.do_request(
  828. {
  829. "field": ["sum(span.self_time)"],
  830. "query": "span.description:bar",
  831. "project": self.project.id,
  832. "dataset": "spansMetrics",
  833. }
  834. )
  835. assert response.status_code == 200, response.content
  836. data = response.data["data"]
  837. meta = response.data["meta"]
  838. assert len(data) == 1
  839. assert data[0]["sum(span.self_time)"] == 99
  840. assert meta["dataset"] == "spansMetrics"
  841. def test_free_text_search(self):
  842. self.store_span_metric(
  843. 321,
  844. internal_metric=constants.SELF_TIME_LIGHT,
  845. timestamp=self.min_ago,
  846. tags={"span.description": "foo"},
  847. )
  848. self.store_span_metric(
  849. 99,
  850. internal_metric=constants.SELF_TIME_LIGHT,
  851. timestamp=self.min_ago,
  852. tags={"span.description": "bar"},
  853. )
  854. response = self.do_request(
  855. {
  856. "field": ["sum(span.self_time)"],
  857. "query": "foo",
  858. "project": self.project.id,
  859. "dataset": "spansMetrics",
  860. }
  861. )
  862. assert response.status_code == 200, response.content
  863. data = response.data["data"]
  864. meta = response.data["meta"]
  865. assert len(data) == 1
  866. assert data[0]["sum(span.self_time)"] == 321
  867. assert meta["dataset"] == "spansMetrics"
  868. def test_avg_compare(self):
  869. self.store_span_metric(
  870. 100,
  871. internal_metric=constants.SELF_TIME_LIGHT,
  872. timestamp=self.min_ago,
  873. tags={"release": "foo"},
  874. )
  875. self.store_span_metric(
  876. 10,
  877. internal_metric=constants.SELF_TIME_LIGHT,
  878. timestamp=self.min_ago,
  879. tags={"release": "bar"},
  880. )
  881. for function_name in [
  882. "avg_compare(span.self_time, release, foo, bar)",
  883. 'avg_compare(span.self_time, release, "foo", "bar")',
  884. ]:
  885. response = self.do_request(
  886. {
  887. "field": [function_name],
  888. "query": "",
  889. "project": self.project.id,
  890. "dataset": "spansMetrics",
  891. }
  892. )
  893. assert response.status_code == 200, response.content
  894. data = response.data["data"]
  895. meta = response.data["meta"]
  896. assert len(data) == 1
  897. assert data[0][function_name] == -0.9
  898. assert meta["dataset"] == "spansMetrics"
  899. assert meta["fields"][function_name] == "percent_change"
  900. def test_avg_compare_invalid_column(self):
  901. response = self.do_request(
  902. {
  903. "field": ["avg_compare(span.self_time, transaction, foo, bar)"],
  904. "query": "",
  905. "project": self.project.id,
  906. "dataset": "spansMetrics",
  907. }
  908. )
  909. assert response.status_code == 400, response.content
  910. def test_span_domain_array(self):
  911. self.store_span_metric(
  912. 321,
  913. internal_metric=constants.SELF_TIME_LIGHT,
  914. timestamp=self.min_ago,
  915. tags={"span.domain": ",sentry_table1,"},
  916. )
  917. self.store_span_metric(
  918. 21,
  919. internal_metric=constants.SELF_TIME_LIGHT,
  920. timestamp=self.min_ago,
  921. tags={"span.domain": ",sentry_table1,sentry_table2,"},
  922. )
  923. response = self.do_request(
  924. {
  925. "field": ["span.domain", "p75(span.self_time)"],
  926. "query": "",
  927. "project": self.project.id,
  928. "orderby": ["-p75(span.self_time)"],
  929. "dataset": "spansMetrics",
  930. }
  931. )
  932. assert response.status_code == 200, response.content
  933. data = response.data["data"]
  934. meta = response.data["meta"]
  935. assert len(data) == 2
  936. assert data[0]["span.domain"] == ["sentry_table1"]
  937. assert data[1]["span.domain"] == ["sentry_table1", "sentry_table2"]
  938. assert meta["dataset"] == "spansMetrics"
  939. assert meta["fields"]["span.domain"] == "array"
  940. def test_span_domain_array_filter(self):
  941. self.store_span_metric(
  942. 321,
  943. internal_metric=constants.SELF_TIME_LIGHT,
  944. timestamp=self.min_ago,
  945. tags={"span.domain": ",sentry_table1,"},
  946. )
  947. self.store_span_metric(
  948. 21,
  949. internal_metric=constants.SELF_TIME_LIGHT,
  950. timestamp=self.min_ago,
  951. tags={"span.domain": ",sentry_table1,sentry_table2,"},
  952. )
  953. response = self.do_request(
  954. {
  955. "field": ["span.domain", "p75(span.self_time)"],
  956. "query": "span.domain:sentry_table2",
  957. "project": self.project.id,
  958. "dataset": "spansMetrics",
  959. }
  960. )
  961. assert response.status_code == 200, response.content
  962. data = response.data["data"]
  963. meta = response.data["meta"]
  964. assert len(data) == 1
  965. assert data[0]["span.domain"] == ["sentry_table1", "sentry_table2"]
  966. assert meta["dataset"] == "spansMetrics"
  967. assert meta["fields"]["span.domain"] == "array"
  968. def test_span_domain_array_filter_wildcard(self):
  969. self.store_span_metric(
  970. 321,
  971. internal_metric=constants.SELF_TIME_LIGHT,
  972. timestamp=self.min_ago,
  973. tags={"span.domain": ",sentry_table1,"},
  974. )
  975. self.store_span_metric(
  976. 21,
  977. internal_metric=constants.SELF_TIME_LIGHT,
  978. timestamp=self.min_ago,
  979. tags={"span.domain": ",sentry_table1,sentry_table2,"},
  980. )
  981. for query in ["sentry*2", "*table2", "sentry_table2*"]:
  982. response = self.do_request(
  983. {
  984. "field": ["span.domain", "p75(span.self_time)"],
  985. "query": f"span.domain:{query}",
  986. "project": self.project.id,
  987. "dataset": "spansMetrics",
  988. }
  989. )
  990. assert response.status_code == 200, response.content
  991. data = response.data["data"]
  992. meta = response.data["meta"]
  993. assert len(data) == 1, query
  994. assert data[0]["span.domain"] == ["sentry_table1", "sentry_table2"], query
  995. assert meta["dataset"] == "spansMetrics", query
  996. assert meta["fields"]["span.domain"] == "array"
  997. def test_span_domain_array_has_filter(self):
  998. self.store_span_metric(
  999. 321,
  1000. internal_metric=constants.SELF_TIME_LIGHT,
  1001. timestamp=self.min_ago,
  1002. tags={"span.domain": ""},
  1003. )
  1004. self.store_span_metric(
  1005. 21,
  1006. internal_metric=constants.SELF_TIME_LIGHT,
  1007. timestamp=self.min_ago,
  1008. tags={"span.domain": ",sentry_table1,sentry_table2,"},
  1009. )
  1010. response = self.do_request(
  1011. {
  1012. "field": ["span.domain", "p75(span.self_time)"],
  1013. "query": "has:span.domain",
  1014. "project": self.project.id,
  1015. "dataset": "spansMetrics",
  1016. }
  1017. )
  1018. assert response.status_code == 200, response.content
  1019. data = response.data["data"]
  1020. meta = response.data["meta"]
  1021. assert len(data) == 1
  1022. assert data[0]["span.domain"] == ["sentry_table1", "sentry_table2"]
  1023. assert meta["dataset"] == "spansMetrics"
  1024. response = self.do_request(
  1025. {
  1026. "field": ["span.domain", "p75(span.self_time)"],
  1027. "query": "!has:span.domain",
  1028. "project": self.project.id,
  1029. "dataset": "spansMetrics",
  1030. }
  1031. )
  1032. assert response.status_code == 200, response.content
  1033. data = response.data["data"]
  1034. meta = response.data["meta"]
  1035. assert len(data) == 1
  1036. assert meta["dataset"] == "spansMetrics"
  1037. assert meta["fields"]["span.domain"] == "array"
  1038. def test_unique_values_span_domain(self):
  1039. self.store_span_metric(
  1040. 321,
  1041. internal_metric=constants.SELF_TIME_LIGHT,
  1042. timestamp=self.min_ago,
  1043. tags={"span.domain": ",sentry_table1,"},
  1044. )
  1045. self.store_span_metric(
  1046. 21,
  1047. internal_metric=constants.SELF_TIME_LIGHT,
  1048. timestamp=self.min_ago,
  1049. tags={"span.domain": ",sentry_table2,sentry_table3,"},
  1050. )
  1051. response = self.do_request(
  1052. {
  1053. "field": ["unique.span_domains", "count()"],
  1054. "query": "",
  1055. "orderby": "unique.span_domains",
  1056. "project": self.project.id,
  1057. "dataset": "spansMetrics",
  1058. }
  1059. )
  1060. assert response.status_code == 200, response.content
  1061. data = response.data["data"]
  1062. meta = response.data["meta"]
  1063. assert len(data) == 3
  1064. assert data[0]["unique.span_domains"] == "sentry_table1"
  1065. assert data[1]["unique.span_domains"] == "sentry_table2"
  1066. assert data[2]["unique.span_domains"] == "sentry_table3"
  1067. assert meta["fields"]["unique.span_domains"] == "string"
  1068. def test_unique_values_span_domain_with_filter(self):
  1069. self.store_span_metric(
  1070. 321,
  1071. internal_metric=constants.SELF_TIME_LIGHT,
  1072. timestamp=self.min_ago,
  1073. tags={"span.domain": ",sentry_tible1,"},
  1074. )
  1075. self.store_span_metric(
  1076. 21,
  1077. internal_metric=constants.SELF_TIME_LIGHT,
  1078. timestamp=self.min_ago,
  1079. tags={"span.domain": ",sentry_table2,sentry_table3,"},
  1080. )
  1081. response = self.do_request(
  1082. {
  1083. "field": ["unique.span_domains", "count()"],
  1084. "query": "span.domain:sentry_tab*",
  1085. "orderby": "unique.span_domains",
  1086. "project": self.project.id,
  1087. "dataset": "spansMetrics",
  1088. }
  1089. )
  1090. assert response.status_code == 200, response.content
  1091. data = response.data["data"]
  1092. meta = response.data["meta"]
  1093. assert len(data) == 2
  1094. assert data[0]["unique.span_domains"] == "sentry_table2"
  1095. assert data[1]["unique.span_domains"] == "sentry_table3"
  1096. assert meta["fields"]["unique.span_domains"] == "string"
  1097. def test_avg_if(self):
  1098. self.store_span_metric(
  1099. 100,
  1100. internal_metric=constants.SELF_TIME_LIGHT,
  1101. timestamp=self.min_ago,
  1102. tags={"release": "foo"},
  1103. )
  1104. self.store_span_metric(
  1105. 200,
  1106. internal_metric=constants.SELF_TIME_LIGHT,
  1107. timestamp=self.min_ago,
  1108. tags={"release": "foo"},
  1109. )
  1110. self.store_span_metric(
  1111. 10,
  1112. internal_metric=constants.SELF_TIME_LIGHT,
  1113. timestamp=self.min_ago,
  1114. tags={"release": "bar"},
  1115. )
  1116. self.store_span_metric(
  1117. 300,
  1118. internal_metric=constants.SELF_TIME_LIGHT,
  1119. timestamp=self.min_ago,
  1120. tags={"span.op": "queue.process"},
  1121. )
  1122. response = self.do_request(
  1123. {
  1124. "field": [
  1125. "avg_if(span.self_time, release, foo)",
  1126. "avg_if(span.self_time, span.op, queue.process)",
  1127. ],
  1128. "query": "",
  1129. "project": self.project.id,
  1130. "dataset": "spansMetrics",
  1131. }
  1132. )
  1133. assert response.status_code == 200, response.content
  1134. data = response.data["data"]
  1135. meta = response.data["meta"]
  1136. assert len(data) == 1
  1137. assert data[0]["avg_if(span.self_time, release, foo)"] == 150
  1138. assert data[0]["avg_if(span.self_time, span.op, queue.process)"] == 300
  1139. assert meta["dataset"] == "spansMetrics"
  1140. assert meta["fields"]["avg_if(span.self_time, release, foo)"] == "duration"
  1141. assert meta["fields"]["avg_if(span.self_time, span.op, queue.process)"] == "duration"
  1142. def test_device_class(self):
  1143. self.store_span_metric(
  1144. 123,
  1145. internal_metric=constants.SELF_TIME_LIGHT,
  1146. timestamp=self.min_ago,
  1147. tags={"device.class": "1"},
  1148. )
  1149. self.store_span_metric(
  1150. 678,
  1151. internal_metric=constants.SELF_TIME_LIGHT,
  1152. timestamp=self.min_ago,
  1153. tags={"device.class": "2"},
  1154. )
  1155. self.store_span_metric(
  1156. 999,
  1157. internal_metric=constants.SELF_TIME_LIGHT,
  1158. timestamp=self.min_ago,
  1159. tags={"device.class": ""},
  1160. )
  1161. response = self.do_request(
  1162. {
  1163. "field": ["device.class", "p95()"],
  1164. "query": "",
  1165. "orderby": "p95()",
  1166. "project": self.project.id,
  1167. "dataset": "spansMetrics",
  1168. }
  1169. )
  1170. assert response.status_code == 200, response.content
  1171. data = response.data["data"]
  1172. meta = response.data["meta"]
  1173. assert len(data) == 3
  1174. # Need to actually check the dict since the level for 1 isn't guaranteed to stay `low` or `medium`
  1175. assert data[0]["device.class"] == map_device_class_level("1")
  1176. assert data[1]["device.class"] == map_device_class_level("2")
  1177. assert data[2]["device.class"] == "Unknown"
  1178. assert meta["fields"]["device.class"] == "string"
  1179. def test_device_class_filter(self):
  1180. self.store_span_metric(
  1181. 123,
  1182. internal_metric=constants.SELF_TIME_LIGHT,
  1183. timestamp=self.min_ago,
  1184. tags={"device.class": "1"},
  1185. )
  1186. # Need to actually check the dict since the level for 1 isn't guaranteed to stay `low`
  1187. level = map_device_class_level("1")
  1188. response = self.do_request(
  1189. {
  1190. "field": ["device.class", "count()"],
  1191. "query": f"device.class:{level}",
  1192. "orderby": "count()",
  1193. "project": self.project.id,
  1194. "dataset": "spansMetrics",
  1195. }
  1196. )
  1197. assert response.status_code == 200, response.content
  1198. data = response.data["data"]
  1199. meta = response.data["meta"]
  1200. assert len(data) == 1
  1201. assert data[0]["device.class"] == level
  1202. assert meta["fields"]["device.class"] == "string"
  1203. def test_device_class_filter_unknown(self):
  1204. self.store_span_metric(
  1205. 123,
  1206. internal_metric=constants.SELF_TIME_LIGHT,
  1207. timestamp=self.min_ago,
  1208. tags={"device.class": ""},
  1209. )
  1210. response = self.do_request(
  1211. {
  1212. "field": ["device.class", "count()"],
  1213. "query": "device.class:Unknown",
  1214. "orderby": "count()",
  1215. "project": self.project.id,
  1216. "dataset": "spansMetrics",
  1217. }
  1218. )
  1219. assert response.status_code == 200, response.content
  1220. data = response.data["data"]
  1221. meta = response.data["meta"]
  1222. assert len(data) == 1
  1223. assert data[0]["device.class"] == "Unknown"
  1224. assert meta["fields"]["device.class"] == "string"
  1225. def test_cache_hit_rate(self):
  1226. self.store_span_metric(
  1227. 1,
  1228. internal_metric=constants.SELF_TIME_LIGHT,
  1229. timestamp=self.min_ago,
  1230. tags={"cache.hit": "true"},
  1231. )
  1232. self.store_span_metric(
  1233. 1,
  1234. internal_metric=constants.SELF_TIME_LIGHT,
  1235. timestamp=self.min_ago,
  1236. tags={"cache.hit": "false"},
  1237. )
  1238. response = self.do_request(
  1239. {
  1240. "field": ["cache_hit_rate()"],
  1241. "query": "",
  1242. "project": self.project.id,
  1243. "dataset": "spansMetrics",
  1244. }
  1245. )
  1246. assert response.status_code == 200, response.content
  1247. data = response.data["data"]
  1248. meta = response.data["meta"]
  1249. assert len(data) == 1
  1250. assert data[0]["cache_hit_rate()"] == 0.5
  1251. assert meta["dataset"] == "spansMetrics"
  1252. assert meta["fields"]["cache_hit_rate()"] == "percentage"
  1253. def test_cache_miss_rate(self):
  1254. self.store_span_metric(
  1255. 1,
  1256. internal_metric=constants.SELF_TIME_LIGHT,
  1257. timestamp=self.min_ago,
  1258. tags={"cache.hit": "true"},
  1259. )
  1260. self.store_span_metric(
  1261. 1,
  1262. internal_metric=constants.SELF_TIME_LIGHT,
  1263. timestamp=self.min_ago,
  1264. tags={"cache.hit": "false"},
  1265. )
  1266. self.store_span_metric(
  1267. 1,
  1268. internal_metric=constants.SELF_TIME_LIGHT,
  1269. timestamp=self.min_ago,
  1270. tags={"cache.hit": "false"},
  1271. )
  1272. self.store_span_metric(
  1273. 1,
  1274. internal_metric=constants.SELF_TIME_LIGHT,
  1275. timestamp=self.min_ago,
  1276. tags={"cache.hit": "false"},
  1277. )
  1278. response = self.do_request(
  1279. {
  1280. "field": ["cache_miss_rate()"],
  1281. "query": "",
  1282. "project": self.project.id,
  1283. "dataset": "spansMetrics",
  1284. }
  1285. )
  1286. assert response.status_code == 200, response.content
  1287. data = response.data["data"]
  1288. meta = response.data["meta"]
  1289. assert len(data) == 1
  1290. assert data[0]["cache_miss_rate()"] == 0.75
  1291. assert meta["dataset"] == "spansMetrics"
  1292. assert meta["fields"]["cache_miss_rate()"] == "percentage"
  1293. def test_http_response_rate(self):
  1294. self.store_span_metric(
  1295. 1,
  1296. internal_metric=constants.SELF_TIME_LIGHT,
  1297. timestamp=self.min_ago,
  1298. tags={"span.status_code": "200"},
  1299. )
  1300. self.store_span_metric(
  1301. 3,
  1302. internal_metric=constants.SELF_TIME_LIGHT,
  1303. timestamp=self.min_ago,
  1304. tags={"span.status_code": "301"},
  1305. )
  1306. self.store_span_metric(
  1307. 3,
  1308. internal_metric=constants.SELF_TIME_LIGHT,
  1309. timestamp=self.min_ago,
  1310. tags={"span.status_code": "404"},
  1311. )
  1312. self.store_span_metric(
  1313. 4,
  1314. internal_metric=constants.SELF_TIME_LIGHT,
  1315. timestamp=self.min_ago,
  1316. tags={"span.status_code": "503"},
  1317. )
  1318. self.store_span_metric(
  1319. 5,
  1320. internal_metric=constants.SELF_TIME_LIGHT,
  1321. timestamp=self.min_ago,
  1322. tags={"span.status_code": "501"},
  1323. )
  1324. response = self.do_request(
  1325. {
  1326. "field": [
  1327. "http_response_rate(200)", # By exact code
  1328. "http_response_rate(3)", # By code class
  1329. "http_response_rate(4)",
  1330. "http_response_rate(5)",
  1331. ],
  1332. "query": "",
  1333. "project": self.project.id,
  1334. "dataset": "spansMetrics",
  1335. }
  1336. )
  1337. assert response.status_code == 200, response.content
  1338. data = response.data["data"]
  1339. assert len(data) == 1
  1340. assert data[0]["http_response_rate(200)"] == 0.2
  1341. assert data[0]["http_response_rate(3)"] == 0.2
  1342. assert data[0]["http_response_rate(4)"] == 0.2
  1343. assert data[0]["http_response_rate(5)"] == 0.4
  1344. meta = response.data["meta"]
  1345. assert meta["dataset"] == "spansMetrics"
  1346. assert meta["fields"]["http_response_rate(200)"] == "percentage"
  1347. def test_regression_score_regression(self):
  1348. # This span increases in duration
  1349. self.store_span_metric(
  1350. 1,
  1351. internal_metric=SPAN_DURATION_MRI,
  1352. timestamp=self.six_min_ago,
  1353. tags={"transaction": "/api/0/projects/", "span.description": "Regressed Span"},
  1354. project=self.project.id,
  1355. )
  1356. self.store_span_metric(
  1357. 100,
  1358. internal_metric=SPAN_DURATION_MRI,
  1359. timestamp=self.min_ago,
  1360. tags={"transaction": "/api/0/projects/", "span.description": "Regressed Span"},
  1361. project=self.project.id,
  1362. )
  1363. # This span stays the same
  1364. self.store_span_metric(
  1365. 1,
  1366. internal_metric=SPAN_DURATION_MRI,
  1367. timestamp=self.three_days_ago,
  1368. tags={"transaction": "/api/0/projects/", "span.description": "Non-regressed"},
  1369. project=self.project.id,
  1370. )
  1371. self.store_span_metric(
  1372. 1,
  1373. internal_metric=SPAN_DURATION_MRI,
  1374. timestamp=self.min_ago,
  1375. tags={"transaction": "/api/0/projects/", "span.description": "Non-regressed"},
  1376. project=self.project.id,
  1377. )
  1378. response = self.do_request(
  1379. {
  1380. "field": [
  1381. "span.description",
  1382. f"regression_score(span.duration,{int(self.two_min_ago.timestamp())})",
  1383. ],
  1384. "query": "transaction:/api/0/projects/",
  1385. "dataset": "spansMetrics",
  1386. "orderby": [
  1387. f"-regression_score(span.duration,{int(self.two_min_ago.timestamp())})"
  1388. ],
  1389. "start": (self.six_min_ago - timedelta(minutes=1)).isoformat(),
  1390. "end": before_now(minutes=0),
  1391. }
  1392. )
  1393. assert response.status_code == 200, response.content
  1394. data = response.data["data"]
  1395. assert len(data) == 2
  1396. assert [row["span.description"] for row in data] == ["Regressed Span", "Non-regressed"]
  1397. def test_regression_score_added_span(self):
  1398. # This span only exists after the breakpoint
  1399. self.store_span_metric(
  1400. 100,
  1401. internal_metric=SPAN_DURATION_MRI,
  1402. timestamp=self.min_ago,
  1403. tags={"transaction": "/api/0/projects/", "span.description": "Added span"},
  1404. project=self.project.id,
  1405. )
  1406. # This span stays the same
  1407. self.store_span_metric(
  1408. 1,
  1409. internal_metric=SPAN_DURATION_MRI,
  1410. timestamp=self.three_days_ago,
  1411. tags={"transaction": "/api/0/projects/", "span.description": "Non-regressed"},
  1412. project=self.project.id,
  1413. )
  1414. self.store_span_metric(
  1415. 1,
  1416. internal_metric=SPAN_DURATION_MRI,
  1417. timestamp=self.min_ago,
  1418. tags={"transaction": "/api/0/projects/", "span.description": "Non-regressed"},
  1419. project=self.project.id,
  1420. )
  1421. response = self.do_request(
  1422. {
  1423. "field": [
  1424. "span.description",
  1425. f"regression_score(span.duration,{int(self.two_min_ago.timestamp())})",
  1426. ],
  1427. "query": "transaction:/api/0/projects/",
  1428. "dataset": "spansMetrics",
  1429. "orderby": [
  1430. f"-regression_score(span.duration,{int(self.two_min_ago.timestamp())})"
  1431. ],
  1432. "start": (self.six_min_ago - timedelta(minutes=1)).isoformat(),
  1433. "end": before_now(minutes=0),
  1434. }
  1435. )
  1436. assert response.status_code == 200, response.content
  1437. data = response.data["data"]
  1438. assert len(data) == 2
  1439. assert [row["span.description"] for row in data] == ["Added span", "Non-regressed"]
  1440. def test_regression_score_removed_span(self):
  1441. # This span only exists before the breakpoint
  1442. self.store_span_metric(
  1443. 100,
  1444. internal_metric=SPAN_DURATION_MRI,
  1445. timestamp=self.six_min_ago,
  1446. tags={"transaction": "/api/0/projects/", "span.description": "Removed span"},
  1447. project=self.project.id,
  1448. )
  1449. # This span stays the same
  1450. self.store_span_metric(
  1451. 1,
  1452. internal_metric=SPAN_DURATION_MRI,
  1453. timestamp=self.three_days_ago,
  1454. tags={"transaction": "/api/0/projects/", "span.description": "Non-regressed"},
  1455. project=self.project.id,
  1456. )
  1457. self.store_span_metric(
  1458. 1,
  1459. internal_metric=SPAN_DURATION_MRI,
  1460. timestamp=self.min_ago,
  1461. tags={"transaction": "/api/0/projects/", "span.description": "Non-regressed"},
  1462. project=self.project.id,
  1463. )
  1464. response = self.do_request(
  1465. {
  1466. "field": [
  1467. "span.description",
  1468. f"regression_score(span.duration,{int(self.two_min_ago.timestamp())})",
  1469. ],
  1470. "query": "transaction:/api/0/projects/",
  1471. "dataset": "spansMetrics",
  1472. "orderby": [
  1473. f"-regression_score(span.duration,{int(self.two_min_ago.timestamp())})"
  1474. ],
  1475. "start": (self.six_min_ago - timedelta(minutes=1)).isoformat(),
  1476. "end": before_now(minutes=0),
  1477. }
  1478. )
  1479. assert response.status_code == 200, response.content
  1480. data = response.data["data"]
  1481. assert len(data) == 2
  1482. assert [row["span.description"] for row in data] == ["Non-regressed", "Removed span"]
  1483. # The regression score is <0 for removed spans, this can act as
  1484. # a way to filter out removed spans when necessary
  1485. assert data[1][f"regression_score(span.duration,{int(self.two_min_ago.timestamp())})"] < 0
  1486. def test_avg_self_time_by_timestamp(self):
  1487. self.store_span_metric(
  1488. 1,
  1489. internal_metric=constants.SELF_TIME_LIGHT,
  1490. timestamp=self.six_min_ago,
  1491. tags={},
  1492. )
  1493. self.store_span_metric(
  1494. 3,
  1495. internal_metric=constants.SELF_TIME_LIGHT,
  1496. timestamp=self.min_ago,
  1497. tags={},
  1498. )
  1499. response = self.do_request(
  1500. {
  1501. "field": [
  1502. f"avg_by_timestamp(span.self_time,less,{int(self.two_min_ago.timestamp())})",
  1503. f"avg_by_timestamp(span.self_time,greater,{int(self.two_min_ago.timestamp())})",
  1504. ],
  1505. "query": "",
  1506. "project": self.project.id,
  1507. "dataset": "spansMetrics",
  1508. "statsPeriod": "1h",
  1509. }
  1510. )
  1511. assert response.status_code == 200, response.content
  1512. data = response.data["data"]
  1513. assert len(data) == 1
  1514. assert data[0] == {
  1515. f"avg_by_timestamp(span.self_time,less,{int(self.two_min_ago.timestamp())})": 1.0,
  1516. f"avg_by_timestamp(span.self_time,greater,{int(self.two_min_ago.timestamp())})": 3.0,
  1517. }
  1518. def test_avg_self_time_by_timestamp_invalid_condition(self):
  1519. response = self.do_request(
  1520. {
  1521. "field": [
  1522. f"avg_by_timestamp(span.self_time,INVALID_ARG,{int(self.two_min_ago.timestamp())})",
  1523. ],
  1524. "query": "",
  1525. "project": self.project.id,
  1526. "dataset": "spansMetrics",
  1527. "statsPeriod": "1h",
  1528. }
  1529. )
  1530. assert response.status_code == 400, response.content
  1531. assert (
  1532. response.data["detail"]
  1533. == "avg_by_timestamp: condition argument invalid: string must be one of ['greater', 'less']"
  1534. )
  1535. def test_epm_by_timestamp(self):
  1536. self.store_span_metric(
  1537. 1,
  1538. internal_metric=SPAN_DURATION_MRI,
  1539. timestamp=self.six_min_ago,
  1540. tags={},
  1541. )
  1542. # More events occur after the timestamp
  1543. for _ in range(3):
  1544. self.store_span_metric(
  1545. 3,
  1546. internal_metric=SPAN_DURATION_MRI,
  1547. timestamp=self.min_ago,
  1548. tags={},
  1549. )
  1550. response = self.do_request(
  1551. {
  1552. "field": [
  1553. f"epm_by_timestamp(less,{int(self.two_min_ago.timestamp())})",
  1554. f"epm_by_timestamp(greater,{int(self.two_min_ago.timestamp())})",
  1555. ],
  1556. "query": "",
  1557. "project": self.project.id,
  1558. "dataset": "spansMetrics",
  1559. "statsPeriod": "1h",
  1560. }
  1561. )
  1562. assert response.status_code == 200, response.content
  1563. data = response.data["data"]
  1564. assert len(data) == 1
  1565. assert data[0][f"epm_by_timestamp(less,{int(self.two_min_ago.timestamp())})"] < 1.0
  1566. assert data[0][f"epm_by_timestamp(greater,{int(self.two_min_ago.timestamp())})"] > 1.0
  1567. def test_epm_by_timestamp_invalid_condition(self):
  1568. response = self.do_request(
  1569. {
  1570. "field": [
  1571. f"epm_by_timestamp(INVALID_ARG,{int(self.two_min_ago.timestamp())})",
  1572. ],
  1573. "query": "",
  1574. "project": self.project.id,
  1575. "dataset": "spansMetrics",
  1576. "statsPeriod": "1h",
  1577. }
  1578. )
  1579. assert response.status_code == 400, response.content
  1580. assert (
  1581. response.data["detail"]
  1582. == "epm_by_timestamp: condition argument invalid: string must be one of ['greater', 'less']"
  1583. )
  1584. def test_any_function(self):
  1585. for char in "abc":
  1586. for transaction in ["foo", "bar"]:
  1587. self.store_span_metric(
  1588. 1,
  1589. internal_metric=constants.SELF_TIME_LIGHT,
  1590. timestamp=self.six_min_ago,
  1591. tags={"span.description": char, "transaction": transaction},
  1592. )
  1593. response = self.do_request(
  1594. {
  1595. "field": [
  1596. "transaction",
  1597. "any(span.description)",
  1598. ],
  1599. "query": "",
  1600. "orderby": ["transaction"],
  1601. "project": self.project.id,
  1602. "dataset": "spansMetrics",
  1603. "statsPeriod": "1h",
  1604. }
  1605. )
  1606. assert response.status_code == 200, response.content
  1607. assert response.data["data"] == [
  1608. {"transaction": "bar", "any(span.description)": "a"},
  1609. {"transaction": "foo", "any(span.description)": "a"},
  1610. ]
  1611. def test_count_op(self):
  1612. self.store_span_metric(
  1613. 1,
  1614. internal_metric=constants.SELF_TIME_LIGHT,
  1615. timestamp=self.six_min_ago,
  1616. tags={"span.op": "queue.publish"},
  1617. )
  1618. self.store_span_metric(
  1619. 1,
  1620. internal_metric=constants.SELF_TIME_LIGHT,
  1621. timestamp=self.six_min_ago,
  1622. tags={"span.op": "queue.process"},
  1623. )
  1624. response = self.do_request(
  1625. {
  1626. "field": [
  1627. "count_op(queue.publish)",
  1628. "count_op(queue.process)",
  1629. ],
  1630. "query": "",
  1631. "project": self.project.id,
  1632. "dataset": "spansMetrics",
  1633. "statsPeriod": "1h",
  1634. }
  1635. )
  1636. assert response.status_code == 200, response.content
  1637. data = response.data["data"]
  1638. assert data == [
  1639. {"count_op(queue.publish)": 1, "count_op(queue.process)": 1},
  1640. ]
  1641. def test_project_mapping(self):
  1642. self.store_span_metric(
  1643. 1,
  1644. internal_metric=constants.SELF_TIME_LIGHT,
  1645. timestamp=self.six_min_ago,
  1646. tags={},
  1647. )
  1648. # More events occur after the timestamp
  1649. for _ in range(3):
  1650. self.store_span_metric(
  1651. 3,
  1652. internal_metric=constants.SELF_TIME_LIGHT,
  1653. timestamp=self.min_ago,
  1654. tags={},
  1655. )
  1656. response = self.do_request(
  1657. {
  1658. "field": ["project", "project.name", "count()"],
  1659. "query": "",
  1660. "project": self.project.id,
  1661. "dataset": "spansMetrics",
  1662. "statsPeriod": "1h",
  1663. }
  1664. )
  1665. assert response.status_code == 200, response.content
  1666. data = response.data["data"]
  1667. assert data[0]["project"] == self.project.slug
  1668. assert data[0]["project.name"] == self.project.slug
  1669. def test_slow_frames_gauge_metric(self):
  1670. self.store_span_metric(
  1671. {
  1672. "min": 5,
  1673. "max": 5,
  1674. "sum": 5,
  1675. "count": 1,
  1676. "last": 5,
  1677. },
  1678. entity="metrics_gauges",
  1679. metric="mobile.slow_frames",
  1680. timestamp=self.six_min_ago,
  1681. tags={"release": "foo"},
  1682. )
  1683. self.store_span_metric(
  1684. {
  1685. "min": 10,
  1686. "max": 10,
  1687. "sum": 10,
  1688. "count": 1,
  1689. "last": 10,
  1690. },
  1691. entity="metrics_gauges",
  1692. metric="mobile.slow_frames",
  1693. timestamp=self.six_min_ago,
  1694. tags={"release": "bar"},
  1695. )
  1696. response = self.do_request(
  1697. {
  1698. "field": [
  1699. "avg_if(mobile.slow_frames,release,foo)",
  1700. "avg_if(mobile.slow_frames,release,bar)",
  1701. "avg_compare(mobile.slow_frames,release,foo,bar)",
  1702. ],
  1703. "query": "",
  1704. "project": self.project.id,
  1705. "dataset": "spansMetrics",
  1706. "statsPeriod": "1h",
  1707. }
  1708. )
  1709. assert response.status_code == 200, response.content
  1710. data = response.data["data"]
  1711. assert data == [
  1712. {
  1713. "avg_compare(mobile.slow_frames,release,foo,bar)": 1.0,
  1714. "avg_if(mobile.slow_frames,release,foo)": 5.0,
  1715. "avg_if(mobile.slow_frames,release,bar)": 10.0,
  1716. }
  1717. ]
  1718. def test_frames_delay_gauge_metric(self):
  1719. self.store_span_metric(
  1720. {
  1721. "min": 5,
  1722. "max": 5,
  1723. "sum": 5,
  1724. "count": 1,
  1725. "last": 5,
  1726. },
  1727. entity="metrics_gauges",
  1728. metric="mobile.frames_delay",
  1729. timestamp=self.six_min_ago,
  1730. tags={"release": "foo"},
  1731. )
  1732. self.store_span_metric(
  1733. {
  1734. "min": 10,
  1735. "max": 10,
  1736. "sum": 10,
  1737. "count": 1,
  1738. "last": 10,
  1739. },
  1740. entity="metrics_gauges",
  1741. metric="mobile.frames_delay",
  1742. timestamp=self.six_min_ago,
  1743. tags={"release": "bar"},
  1744. )
  1745. response = self.do_request(
  1746. {
  1747. "field": [
  1748. "avg_if(mobile.frames_delay,release,foo)",
  1749. "avg_if(mobile.frames_delay,release,bar)",
  1750. "avg_compare(mobile.frames_delay,release,foo,bar)",
  1751. ],
  1752. "query": "",
  1753. "project": self.project.id,
  1754. "dataset": "spansMetrics",
  1755. "statsPeriod": "1h",
  1756. }
  1757. )
  1758. assert response.status_code == 200, response.content
  1759. data = response.data["data"]
  1760. meta = response.data["meta"]
  1761. assert data == [
  1762. {
  1763. "avg_compare(mobile.frames_delay,release,foo,bar)": 1.0,
  1764. "avg_if(mobile.frames_delay,release,foo)": 5.0,
  1765. "avg_if(mobile.frames_delay,release,bar)": 10.0,
  1766. }
  1767. ]
  1768. assert meta["units"]["avg_if(mobile.frames_delay,release,foo)"] == "second"
  1769. assert meta["units"]["avg_if(mobile.frames_delay,release,bar)"] == "second"
  1770. assert meta["units"]["avg_compare(mobile.frames_delay,release,foo,bar)"] is None
  1771. def test_resolve_messaging_message_receive_latency_gauge(self):
  1772. self.store_span_metric(
  1773. {
  1774. "min": 5,
  1775. "max": 5,
  1776. "sum": 5,
  1777. "count": 1,
  1778. "last": 5,
  1779. },
  1780. entity="metrics_gauges",
  1781. metric="messaging.message.receive.latency",
  1782. timestamp=self.six_min_ago,
  1783. tags={"messaging.destination.name": "foo", "trace.status": "ok"},
  1784. )
  1785. self.store_span_metric(
  1786. {
  1787. "min": 10,
  1788. "max": 10,
  1789. "sum": 10,
  1790. "count": 1,
  1791. "last": 10,
  1792. },
  1793. entity="metrics_gauges",
  1794. metric="messaging.message.receive.latency",
  1795. timestamp=self.six_min_ago,
  1796. tags={"messaging.destination.name": "bar", "trace.status": "ok"},
  1797. )
  1798. response = self.do_request(
  1799. {
  1800. "field": [
  1801. "messaging.destination.name",
  1802. "trace.status",
  1803. "avg(messaging.message.receive.latency)",
  1804. ],
  1805. "query": "",
  1806. "project": self.project.id,
  1807. "dataset": "spansMetrics",
  1808. "statsPeriod": "1h",
  1809. }
  1810. )
  1811. assert response.status_code == 200, response.content
  1812. data = response.data["data"]
  1813. assert data == [
  1814. {
  1815. "messaging.destination.name": "bar",
  1816. "trace.status": "ok",
  1817. "avg(messaging.message.receive.latency)": 10.0,
  1818. },
  1819. {
  1820. "messaging.destination.name": "foo",
  1821. "trace.status": "ok",
  1822. "avg(messaging.message.receive.latency)": 5.0,
  1823. },
  1824. ]
  1825. def test_messaging_does_not_exist_as_metric(self):
  1826. self.store_span_metric(
  1827. 100,
  1828. internal_metric=constants.SPAN_METRICS_MAP["span.duration"],
  1829. tags={"messaging.destination.name": "foo", "trace.status": "ok"},
  1830. timestamp=self.min_ago,
  1831. )
  1832. response = self.do_request(
  1833. {
  1834. "field": [
  1835. "messaging.destination.name",
  1836. "trace.status",
  1837. "avg(messaging.message.receive.latency)",
  1838. "avg(span.duration)",
  1839. ],
  1840. "query": "",
  1841. "project": self.project.id,
  1842. "dataset": "spansMetrics",
  1843. "statsPeriod": "1h",
  1844. }
  1845. )
  1846. assert response.status_code == 200, response.content
  1847. data = response.data["data"]
  1848. assert data == [
  1849. {
  1850. "messaging.destination.name": "foo",
  1851. "trace.status": "ok",
  1852. "avg(messaging.message.receive.latency)": None,
  1853. "avg(span.duration)": 100,
  1854. },
  1855. ]
  1856. meta = response.data["meta"]
  1857. assert meta["fields"]["avg(messaging.message.receive.latency)"] == "null"
  1858. def test_cache_item_size_does_not_exist_as_metric(self):
  1859. self.store_span_metric(
  1860. 100,
  1861. internal_metric=constants.SPAN_METRICS_MAP["span.duration"],
  1862. tags={"cache.item": "true"},
  1863. timestamp=self.min_ago,
  1864. )
  1865. response = self.do_request(
  1866. {
  1867. "field": [
  1868. "avg(cache.item_size)",
  1869. "avg(span.duration)",
  1870. ],
  1871. "query": "",
  1872. "project": self.project.id,
  1873. "dataset": "spansMetrics",
  1874. "statsPeriod": "1h",
  1875. }
  1876. )
  1877. assert response.status_code == 200, response.content
  1878. data = response.data["data"]
  1879. assert data == [
  1880. {
  1881. "avg(cache.item_size)": None,
  1882. "avg(span.duration)": 100,
  1883. },
  1884. ]
  1885. meta = response.data["meta"]
  1886. assert meta["fields"]["avg(cache.item_size)"] == "null"
  1887. def test_trace_status_rate(self):
  1888. self.store_span_metric(
  1889. 1,
  1890. internal_metric=constants.SELF_TIME_LIGHT,
  1891. timestamp=self.min_ago,
  1892. tags={"trace.status": "unknown"},
  1893. )
  1894. self.store_span_metric(
  1895. 3,
  1896. internal_metric=constants.SELF_TIME_LIGHT,
  1897. timestamp=self.min_ago,
  1898. tags={"trace.status": "internal_error"},
  1899. )
  1900. self.store_span_metric(
  1901. 3,
  1902. internal_metric=constants.SELF_TIME_LIGHT,
  1903. timestamp=self.min_ago,
  1904. tags={"trace.status": "unauthenticated"},
  1905. )
  1906. self.store_span_metric(
  1907. 4,
  1908. internal_metric=constants.SELF_TIME_LIGHT,
  1909. timestamp=self.min_ago,
  1910. tags={"trace.status": "ok"},
  1911. )
  1912. self.store_span_metric(
  1913. 5,
  1914. internal_metric=constants.SELF_TIME_LIGHT,
  1915. timestamp=self.min_ago,
  1916. tags={"trace.status": "ok"},
  1917. )
  1918. response = self.do_request(
  1919. {
  1920. "field": [
  1921. "trace_status_rate(ok)",
  1922. "trace_status_rate(unknown)",
  1923. "trace_status_rate(internal_error)",
  1924. "trace_status_rate(unauthenticated)",
  1925. ],
  1926. "query": "",
  1927. "project": self.project.id,
  1928. "dataset": "spansMetrics",
  1929. "statsPeriod": "1h",
  1930. }
  1931. )
  1932. assert response.status_code == 200, response.content
  1933. data = response.data["data"]
  1934. assert len(data) == 1
  1935. assert data[0]["trace_status_rate(ok)"] == 0.4
  1936. assert data[0]["trace_status_rate(unknown)"] == 0.2
  1937. assert data[0]["trace_status_rate(internal_error)"] == 0.2
  1938. assert data[0]["trace_status_rate(unauthenticated)"] == 0.2
  1939. meta = response.data["meta"]
  1940. assert meta["dataset"] == "spansMetrics"
  1941. assert meta["fields"]["trace_status_rate(ok)"] == "percentage"
  1942. assert meta["fields"]["trace_status_rate(unknown)"] == "percentage"
  1943. assert meta["fields"]["trace_status_rate(internal_error)"] == "percentage"
  1944. assert meta["fields"]["trace_status_rate(unauthenticated)"] == "percentage"
  1945. def test_trace_error_rate(self):
  1946. self.store_span_metric(
  1947. 1,
  1948. internal_metric=constants.SELF_TIME_LIGHT,
  1949. timestamp=self.min_ago,
  1950. tags={"trace.status": "unknown"},
  1951. )
  1952. self.store_span_metric(
  1953. 3,
  1954. internal_metric=constants.SELF_TIME_LIGHT,
  1955. timestamp=self.min_ago,
  1956. tags={"trace.status": "internal_error"},
  1957. )
  1958. self.store_span_metric(
  1959. 3,
  1960. internal_metric=constants.SELF_TIME_LIGHT,
  1961. timestamp=self.min_ago,
  1962. tags={"trace.status": "unauthenticated"},
  1963. )
  1964. self.store_span_metric(
  1965. 4,
  1966. internal_metric=constants.SELF_TIME_LIGHT,
  1967. timestamp=self.min_ago,
  1968. tags={"trace.status": "ok"},
  1969. )
  1970. self.store_span_metric(
  1971. 5,
  1972. internal_metric=constants.SELF_TIME_LIGHT,
  1973. timestamp=self.min_ago,
  1974. tags={"trace.status": "ok"},
  1975. )
  1976. response = self.do_request(
  1977. {
  1978. "field": [
  1979. "trace_error_rate()",
  1980. ],
  1981. "query": "",
  1982. "project": self.project.id,
  1983. "dataset": "spansMetrics",
  1984. }
  1985. )
  1986. assert response.status_code == 200, response.content
  1987. data = response.data["data"]
  1988. assert len(data) == 1
  1989. assert data[0]["trace_error_rate()"] == 0.4
  1990. meta = response.data["meta"]
  1991. assert meta["dataset"] == "spansMetrics"
  1992. assert meta["fields"]["trace_error_rate()"] == "percentage"
  1993. class OrganizationEventsMetricsEnhancedPerformanceEndpointTestWithMetricLayer(
  1994. OrganizationEventsMetricsEnhancedPerformanceEndpointTest
  1995. ):
  1996. def setUp(self):
  1997. super().setUp()
  1998. self.features["organizations:use-metrics-layer"] = True
  1999. @pytest.mark.xfail(reason="Not implemented")
  2000. def test_time_spent_percentage(self):
  2001. super().test_time_spent_percentage()
  2002. @pytest.mark.xfail(reason="Not implemented")
  2003. def test_time_spent_percentage_local(self):
  2004. super().test_time_spent_percentage_local()
  2005. @pytest.mark.xfail(reason="Not implemented")
  2006. def test_time_spent_percentage_on_span_duration(self):
  2007. super().test_time_spent_percentage_on_span_duration()
  2008. @pytest.mark.xfail(reason="Cannot group by function 'if'")
  2009. def test_span_module(self):
  2010. super().test_span_module()
  2011. @pytest.mark.xfail(reason="Cannot search by tags")
  2012. def test_tag_search(self):
  2013. super().test_tag_search()
  2014. @pytest.mark.xfail(reason="Cannot search by tags")
  2015. def test_free_text_search(self):
  2016. super().test_free_text_search()
  2017. @pytest.mark.xfail(reason="Not implemented")
  2018. def test_avg_compare(self):
  2019. super().test_avg_compare()
  2020. @pytest.mark.xfail(reason="Not implemented")
  2021. def test_span_domain_array(self):
  2022. super().test_span_domain_array()
  2023. @pytest.mark.xfail(reason="Not implemented")
  2024. def test_span_domain_array_filter(self):
  2025. super().test_span_domain_array_filter()
  2026. @pytest.mark.xfail(reason="Not implemented")
  2027. def test_span_domain_array_filter_wildcard(self):
  2028. super().test_span_domain_array_filter_wildcard()
  2029. @pytest.mark.xfail(reason="Not implemented")
  2030. def test_span_domain_array_has_filter(self):
  2031. super().test_span_domain_array_has_filter()
  2032. @pytest.mark.xfail(reason="Not implemented")
  2033. def test_unique_values_span_domain(self):
  2034. super().test_unique_values_span_domain()
  2035. @pytest.mark.xfail(reason="Not implemented")
  2036. def test_unique_values_span_domain_with_filter(self):
  2037. super().test_unique_values_span_domain_with_filter()
  2038. @pytest.mark.xfail(reason="Not implemented")
  2039. def test_avg_if(self):
  2040. super().test_avg_if()
  2041. @pytest.mark.xfail(reason="Not implemented")
  2042. def test_device_class_filter(self):
  2043. super().test_device_class_filter()
  2044. @pytest.mark.xfail(reason="Not implemented")
  2045. def test_device_class(self):
  2046. super().test_device_class()
  2047. @pytest.mark.xfail(reason="Not implemented")
  2048. def test_count_op(self):
  2049. super().test_count_op()