test_organization_events_span_metrics.py 67 KB

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