test_organization_events_span_metrics.py 68 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971
  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_cache_item_size_does_not_exist_as_metric(self):
  1637. self.store_span_metric(
  1638. 100,
  1639. internal_metric=constants.SPAN_METRICS_MAP["span.duration"],
  1640. tags={"cache.item": "true"},
  1641. timestamp=self.min_ago,
  1642. )
  1643. response = self.do_request(
  1644. {
  1645. "field": [
  1646. "avg(cache.item_size)",
  1647. "avg(span.duration)",
  1648. ],
  1649. "query": "",
  1650. "project": self.project.id,
  1651. "dataset": "spansMetrics",
  1652. "statsPeriod": "1h",
  1653. }
  1654. )
  1655. assert response.status_code == 200, response.content
  1656. data = response.data["data"]
  1657. assert data == [
  1658. {
  1659. "avg(cache.item_size)": None,
  1660. "avg(span.duration)": 100,
  1661. },
  1662. ]
  1663. meta = response.data["meta"]
  1664. assert meta["fields"]["avg(cache.item_size)"] == "null"
  1665. def test_trace_status_rate(self):
  1666. self.store_span_metric(
  1667. 1,
  1668. internal_metric=constants.SELF_TIME_LIGHT,
  1669. timestamp=self.min_ago,
  1670. tags={"trace.status": "unknown"},
  1671. )
  1672. self.store_span_metric(
  1673. 3,
  1674. internal_metric=constants.SELF_TIME_LIGHT,
  1675. timestamp=self.min_ago,
  1676. tags={"trace.status": "internal_error"},
  1677. )
  1678. self.store_span_metric(
  1679. 3,
  1680. internal_metric=constants.SELF_TIME_LIGHT,
  1681. timestamp=self.min_ago,
  1682. tags={"trace.status": "unauthenticated"},
  1683. )
  1684. self.store_span_metric(
  1685. 4,
  1686. internal_metric=constants.SELF_TIME_LIGHT,
  1687. timestamp=self.min_ago,
  1688. tags={"trace.status": "ok"},
  1689. )
  1690. self.store_span_metric(
  1691. 5,
  1692. internal_metric=constants.SELF_TIME_LIGHT,
  1693. timestamp=self.min_ago,
  1694. tags={"trace.status": "ok"},
  1695. )
  1696. response = self.do_request(
  1697. {
  1698. "field": [
  1699. "trace_status_rate(ok)",
  1700. "trace_status_rate(unknown)",
  1701. "trace_status_rate(internal_error)",
  1702. "trace_status_rate(unauthenticated)",
  1703. ],
  1704. "query": "",
  1705. "project": self.project.id,
  1706. "dataset": "spansMetrics",
  1707. "statsPeriod": "1h",
  1708. }
  1709. )
  1710. assert response.status_code == 200, response.content
  1711. data = response.data["data"]
  1712. assert len(data) == 1
  1713. assert data[0]["trace_status_rate(ok)"] == 0.4
  1714. assert data[0]["trace_status_rate(unknown)"] == 0.2
  1715. assert data[0]["trace_status_rate(internal_error)"] == 0.2
  1716. assert data[0]["trace_status_rate(unauthenticated)"] == 0.2
  1717. meta = response.data["meta"]
  1718. assert meta["dataset"] == "spansMetrics"
  1719. assert meta["fields"]["trace_status_rate(ok)"] == "percentage"
  1720. assert meta["fields"]["trace_status_rate(unknown)"] == "percentage"
  1721. assert meta["fields"]["trace_status_rate(internal_error)"] == "percentage"
  1722. assert meta["fields"]["trace_status_rate(unauthenticated)"] == "percentage"
  1723. def test_trace_error_rate(self):
  1724. self.store_span_metric(
  1725. 1,
  1726. internal_metric=constants.SELF_TIME_LIGHT,
  1727. timestamp=self.min_ago,
  1728. tags={"trace.status": "unknown"},
  1729. )
  1730. self.store_span_metric(
  1731. 3,
  1732. internal_metric=constants.SELF_TIME_LIGHT,
  1733. timestamp=self.min_ago,
  1734. tags={"trace.status": "internal_error"},
  1735. )
  1736. self.store_span_metric(
  1737. 3,
  1738. internal_metric=constants.SELF_TIME_LIGHT,
  1739. timestamp=self.min_ago,
  1740. tags={"trace.status": "unauthenticated"},
  1741. )
  1742. self.store_span_metric(
  1743. 4,
  1744. internal_metric=constants.SELF_TIME_LIGHT,
  1745. timestamp=self.min_ago,
  1746. tags={"trace.status": "ok"},
  1747. )
  1748. self.store_span_metric(
  1749. 5,
  1750. internal_metric=constants.SELF_TIME_LIGHT,
  1751. timestamp=self.min_ago,
  1752. tags={"trace.status": "ok"},
  1753. )
  1754. response = self.do_request(
  1755. {
  1756. "field": [
  1757. "trace_error_rate()",
  1758. ],
  1759. "query": "",
  1760. "project": self.project.id,
  1761. "dataset": "spansMetrics",
  1762. }
  1763. )
  1764. assert response.status_code == 200, response.content
  1765. data = response.data["data"]
  1766. assert len(data) == 1
  1767. assert data[0]["trace_error_rate()"] == 0.4
  1768. meta = response.data["meta"]
  1769. assert meta["dataset"] == "spansMetrics"
  1770. assert meta["fields"]["trace_error_rate()"] == "percentage"
  1771. class OrganizationEventsMetricsEnhancedPerformanceEndpointTestWithMetricLayer(
  1772. OrganizationEventsMetricsEnhancedPerformanceEndpointTest
  1773. ):
  1774. def setUp(self):
  1775. super().setUp()
  1776. self.features["organizations:use-metrics-layer"] = True
  1777. @pytest.mark.xfail(reason="Not implemented")
  1778. def test_time_spent_percentage(self):
  1779. super().test_time_spent_percentage()
  1780. @pytest.mark.xfail(reason="Not implemented")
  1781. def test_time_spent_percentage_local(self):
  1782. super().test_time_spent_percentage_local()
  1783. @pytest.mark.xfail(reason="Not implemented")
  1784. def test_time_spent_percentage_on_span_duration(self):
  1785. super().test_time_spent_percentage_on_span_duration()
  1786. @pytest.mark.xfail(reason="Cannot group by function 'if'")
  1787. def test_span_module(self):
  1788. super().test_span_module()
  1789. @pytest.mark.xfail(reason="Cannot search by tags")
  1790. def test_tag_search(self):
  1791. super().test_tag_search()
  1792. @pytest.mark.xfail(reason="Cannot search by tags")
  1793. def test_free_text_search(self):
  1794. super().test_free_text_search()
  1795. @pytest.mark.xfail(reason="Not implemented")
  1796. def test_avg_compare(self):
  1797. super().test_avg_compare()
  1798. @pytest.mark.xfail(reason="Not implemented")
  1799. def test_span_domain_array(self):
  1800. super().test_span_domain_array()
  1801. @pytest.mark.xfail(reason="Not implemented")
  1802. def test_span_domain_array_filter(self):
  1803. super().test_span_domain_array_filter()
  1804. @pytest.mark.xfail(reason="Not implemented")
  1805. def test_span_domain_array_filter_wildcard(self):
  1806. super().test_span_domain_array_filter_wildcard()
  1807. @pytest.mark.xfail(reason="Not implemented")
  1808. def test_span_domain_array_has_filter(self):
  1809. super().test_span_domain_array_has_filter()
  1810. @pytest.mark.xfail(reason="Not implemented")
  1811. def test_unique_values_span_domain(self):
  1812. super().test_unique_values_span_domain()
  1813. @pytest.mark.xfail(reason="Not implemented")
  1814. def test_unique_values_span_domain_with_filter(self):
  1815. super().test_unique_values_span_domain_with_filter()
  1816. @pytest.mark.xfail(reason="Not implemented")
  1817. def test_avg_if(self):
  1818. super().test_avg_if()
  1819. @pytest.mark.xfail(reason="Not implemented")
  1820. def test_device_class_filter(self):
  1821. super().test_device_class_filter()
  1822. @pytest.mark.xfail(reason="Not implemented")
  1823. def test_device_class(self):
  1824. super().test_device_class()
  1825. @pytest.mark.xfail(reason="Not implemented")
  1826. def test_count_op(self):
  1827. super().test_count_op()