test_organization_events_mep.py 68 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915
  1. from unittest import mock
  2. import pytest
  3. from django.urls import reverse
  4. from snuba_sdk.conditions import InvalidConditionError
  5. from sentry.discover.models import TeamKeyTransaction
  6. from sentry.exceptions import IncompatibleMetricsQuery, InvalidSearchQuery
  7. from sentry.models import ProjectTeam
  8. from sentry.models.transaction_threshold import (
  9. ProjectTransactionThreshold,
  10. ProjectTransactionThresholdOverride,
  11. TransactionMetric,
  12. )
  13. from sentry.search.events import constants
  14. from sentry.testutils import MetricsEnhancedPerformanceTestCase
  15. from sentry.testutils.helpers.datetime import before_now, iso_format
  16. from sentry.testutils.silo import region_silo_test
  17. from sentry.utils.samples import load_data
  18. pytestmark = pytest.mark.sentry_metrics
  19. @region_silo_test
  20. class OrganizationEventsMetricsEnhancedPerformanceEndpointTest(MetricsEnhancedPerformanceTestCase):
  21. viewname = "sentry-api-0-organization-events"
  22. # Poor intentionally omitted for test_measurement_rating_that_does_not_exist
  23. METRIC_STRINGS = [
  24. "foo_transaction",
  25. "bar_transaction",
  26. "baz_transaction",
  27. "staging",
  28. "measurement_rating",
  29. "good",
  30. "meh",
  31. "d:transactions/measurements.something_custom@millisecond",
  32. "d:transactions/measurements.runtime@hour",
  33. "d:transactions/measurements.bytes_transfered@byte",
  34. "d:transactions/measurements.datacenter_memory@petabyte",
  35. "d:transactions/measurements.custom.kilobyte@kilobyte",
  36. "d:transactions/measurements.longtaskcount@none",
  37. "d:transactions/measurements.percent@ratio",
  38. "d:transactions/measurements.custom_type@somethingcustom",
  39. ]
  40. def setUp(self):
  41. super().setUp()
  42. self.min_ago = before_now(minutes=1)
  43. self.two_min_ago = before_now(minutes=2)
  44. self.transaction_data = load_data("transaction", timestamp=before_now(minutes=1))
  45. self.features = {
  46. "organizations:performance-use-metrics": True,
  47. }
  48. def do_request(self, query, features=None):
  49. if features is None:
  50. features = {"organizations:discover-basic": True}
  51. features.update(self.features)
  52. self.login_as(user=self.user)
  53. url = reverse(
  54. self.viewname,
  55. kwargs={"organization_slug": self.organization.slug},
  56. )
  57. with self.feature(features):
  58. return self.client.get(url, query, format="json")
  59. def test_no_projects(self):
  60. response = self.do_request(
  61. {
  62. "dataset": "metricsEnhanced",
  63. }
  64. )
  65. assert response.status_code == 200, response.content
  66. def test_invalid_dataset(self):
  67. response = self.do_request(
  68. {
  69. "dataset": "aFakeDataset",
  70. "project": self.project.id,
  71. }
  72. )
  73. assert response.status_code == 400, response.content
  74. assert (
  75. response.data["detail"]
  76. == "dataset must be one of: discover, metricsEnhanced, metrics, profiles"
  77. )
  78. def test_out_of_retention(self):
  79. self.create_project()
  80. with self.options({"system.event-retention-days": 10}):
  81. query = {
  82. "field": ["id", "timestamp"],
  83. "orderby": ["-timestamp", "-id"],
  84. "query": "event.type:transaction",
  85. "start": iso_format(before_now(days=20)),
  86. "end": iso_format(before_now(days=15)),
  87. "dataset": "metricsEnhanced",
  88. }
  89. response = self.do_request(query)
  90. assert response.status_code == 400, response.content
  91. assert response.data["detail"] == "Invalid date range. Please try a more recent date range."
  92. def test_invalid_search_terms(self):
  93. response = self.do_request(
  94. {
  95. "field": ["epm()"],
  96. "query": "hi \n there",
  97. "project": self.project.id,
  98. "dataset": "metricsEnhanced",
  99. }
  100. )
  101. assert response.status_code == 400, response.content
  102. assert (
  103. response.data["detail"]
  104. == "Parse error at 'hi \n ther' (column 4). This is commonly caused by unmatched parentheses. Enclose any text in double quotes."
  105. )
  106. def test_percentile_with_no_data(self):
  107. response = self.do_request(
  108. {
  109. "field": ["p50()"],
  110. "query": "",
  111. "project": self.project.id,
  112. "dataset": "metricsEnhanced",
  113. }
  114. )
  115. assert response.status_code == 200, response.content
  116. data = response.data["data"]
  117. assert len(data) == 1
  118. assert data[0]["p50()"] == 0
  119. def test_project_name(self):
  120. self.store_transaction_metric(
  121. 1,
  122. tags={"environment": "staging"},
  123. timestamp=self.min_ago,
  124. )
  125. response = self.do_request(
  126. {
  127. "field": ["project.name", "environment", "epm()"],
  128. "query": "event.type:transaction",
  129. "dataset": "metricsEnhanced",
  130. "per_page": 50,
  131. }
  132. )
  133. assert response.status_code == 200, response.content
  134. assert len(response.data["data"]) == 1
  135. data = response.data["data"]
  136. meta = response.data["meta"]
  137. field_meta = meta["fields"]
  138. assert data[0]["project.name"] == self.project.slug
  139. assert "project.id" not in data[0]
  140. assert data[0]["environment"] == "staging"
  141. assert meta["isMetricsData"]
  142. assert field_meta["project.name"] == "string"
  143. assert field_meta["environment"] == "string"
  144. assert field_meta["epm()"] == "number"
  145. def test_title_alias(self):
  146. """title is an alias to transaction name"""
  147. self.store_transaction_metric(
  148. 1,
  149. tags={"transaction": "foo_transaction"},
  150. timestamp=self.min_ago,
  151. )
  152. response = self.do_request(
  153. {
  154. "field": ["title", "p50()"],
  155. "query": "event.type:transaction",
  156. "dataset": "metricsEnhanced",
  157. "per_page": 50,
  158. }
  159. )
  160. assert response.status_code == 200, response.content
  161. assert len(response.data["data"]) == 1
  162. data = response.data["data"]
  163. meta = response.data["meta"]
  164. field_meta = meta["fields"]
  165. assert data[0]["title"] == "foo_transaction"
  166. assert data[0]["p50()"] == 1
  167. assert meta["isMetricsData"]
  168. assert field_meta["title"] == "string"
  169. assert field_meta["p50()"] == "duration"
  170. def test_having_condition(self):
  171. self.store_transaction_metric(
  172. 1,
  173. tags={"environment": "staging", "transaction": "foo_transaction"},
  174. timestamp=self.min_ago,
  175. )
  176. self.store_transaction_metric(
  177. # shouldn't show up
  178. 100,
  179. tags={"environment": "staging", "transaction": "bar_transaction"},
  180. timestamp=self.min_ago,
  181. )
  182. response = self.do_request(
  183. {
  184. "field": ["transaction", "project", "p50(transaction.duration)"],
  185. "query": "event.type:transaction p50(transaction.duration):<50",
  186. "dataset": "metricsEnhanced",
  187. "per_page": 50,
  188. }
  189. )
  190. assert response.status_code == 200, response.content
  191. assert len(response.data["data"]) == 1
  192. data = response.data["data"]
  193. meta = response.data["meta"]
  194. field_meta = meta["fields"]
  195. assert data[0]["transaction"] == "foo_transaction"
  196. assert data[0]["project"] == self.project.slug
  197. assert data[0]["p50(transaction.duration)"] == 1
  198. assert meta["isMetricsData"]
  199. assert field_meta["transaction"] == "string"
  200. assert field_meta["project"] == "string"
  201. assert field_meta["p50(transaction.duration)"] == "duration"
  202. def test_having_condition_with_preventing_aggregates(self):
  203. self.store_transaction_metric(
  204. 1,
  205. tags={"environment": "staging", "transaction": "foo_transaction"},
  206. timestamp=self.min_ago,
  207. )
  208. self.store_transaction_metric(
  209. 100,
  210. tags={"environment": "staging", "transaction": "bar_transaction"},
  211. timestamp=self.min_ago,
  212. )
  213. response = self.do_request(
  214. {
  215. "field": ["transaction", "project", "p50(transaction.duration)"],
  216. "query": "event.type:transaction p50(transaction.duration):<50",
  217. "dataset": "metricsEnhanced",
  218. "preventMetricAggregates": "1",
  219. "per_page": 50,
  220. }
  221. )
  222. assert response.status_code == 200, response.content
  223. assert len(response.data["data"]) == 0
  224. meta = response.data["meta"]
  225. field_meta = meta["fields"]
  226. assert not meta["isMetricsData"]
  227. assert field_meta["transaction"] == "string"
  228. assert field_meta["project"] == "string"
  229. assert field_meta["p50(transaction.duration)"] == "duration"
  230. def test_having_condition_with_preventing_aggregate_metrics_only(self):
  231. """same as the previous test, but with the dataset on explicit metrics
  232. which should throw a 400 error instead"""
  233. response = self.do_request(
  234. {
  235. "field": ["transaction", "project", "p50(transaction.duration)"],
  236. "query": "event.type:transaction p50(transaction.duration):<50",
  237. "dataset": "metrics",
  238. "preventMetricAggregates": "1",
  239. "per_page": 50,
  240. "project": self.project.id,
  241. }
  242. )
  243. assert response.status_code == 400, response.content
  244. def test_having_condition_not_selected(self):
  245. self.store_transaction_metric(
  246. 1,
  247. tags={"environment": "staging", "transaction": "foo_transaction"},
  248. timestamp=self.min_ago,
  249. )
  250. self.store_transaction_metric(
  251. # shouldn't show up
  252. 100,
  253. tags={"environment": "staging", "transaction": "bar_transaction"},
  254. timestamp=self.min_ago,
  255. )
  256. response = self.do_request(
  257. {
  258. "field": ["transaction", "project", "p50(transaction.duration)"],
  259. "query": "event.type:transaction p75(transaction.duration):<50",
  260. "dataset": "metricsEnhanced",
  261. "per_page": 50,
  262. }
  263. )
  264. assert response.status_code == 200, response.content
  265. assert len(response.data["data"]) == 1
  266. data = response.data["data"]
  267. meta = response.data["meta"]
  268. field_meta = meta["fields"]
  269. assert data[0]["transaction"] == "foo_transaction"
  270. assert data[0]["project"] == self.project.slug
  271. assert data[0]["p50(transaction.duration)"] == 1
  272. assert meta["isMetricsData"]
  273. assert field_meta["transaction"] == "string"
  274. assert field_meta["project"] == "string"
  275. assert field_meta["p50(transaction.duration)"] == "duration"
  276. def test_non_metrics_tag_with_implicit_format(self):
  277. self.store_transaction_metric(
  278. 1,
  279. tags={"environment": "staging", "transaction": "foo_transaction"},
  280. timestamp=self.min_ago,
  281. )
  282. response = self.do_request(
  283. {
  284. "field": ["test", "p50(transaction.duration)"],
  285. "query": "event.type:transaction",
  286. "dataset": "metricsEnhanced",
  287. "per_page": 50,
  288. }
  289. )
  290. assert response.status_code == 200, response.content
  291. assert len(response.data["data"]) == 0
  292. assert not response.data["meta"]["isMetricsData"]
  293. def test_non_metrics_tag_with_implicit_format_metrics_dataset(self):
  294. self.store_transaction_metric(
  295. 1,
  296. tags={"environment": "staging", "transaction": "foo_transaction"},
  297. timestamp=self.min_ago,
  298. )
  299. response = self.do_request(
  300. {
  301. "field": ["test", "p50(transaction.duration)"],
  302. "query": "event.type:transaction",
  303. "dataset": "metrics",
  304. "per_page": 50,
  305. }
  306. )
  307. assert response.status_code == 400, response.content
  308. def test_performance_homepage_query(self):
  309. self.store_transaction_metric(
  310. 1,
  311. tags={
  312. "transaction": "foo_transaction",
  313. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
  314. },
  315. timestamp=self.min_ago,
  316. )
  317. self.store_transaction_metric(
  318. 1,
  319. "measurements.fcp",
  320. tags={"transaction": "foo_transaction"},
  321. timestamp=self.min_ago,
  322. )
  323. self.store_transaction_metric(
  324. 2,
  325. "measurements.lcp",
  326. tags={"transaction": "foo_transaction"},
  327. timestamp=self.min_ago,
  328. )
  329. self.store_transaction_metric(
  330. 3,
  331. "measurements.fid",
  332. tags={"transaction": "foo_transaction"},
  333. timestamp=self.min_ago,
  334. )
  335. self.store_transaction_metric(
  336. 4,
  337. "measurements.cls",
  338. tags={"transaction": "foo_transaction"},
  339. timestamp=self.min_ago,
  340. )
  341. self.store_transaction_metric(
  342. 1,
  343. "user",
  344. tags={
  345. "transaction": "foo_transaction",
  346. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_FRUSTRATED_TAG_VALUE,
  347. },
  348. timestamp=self.min_ago,
  349. )
  350. for dataset in ["metrics", "metricsEnhanced"]:
  351. response = self.do_request(
  352. {
  353. "field": [
  354. "transaction",
  355. "project",
  356. "tpm()",
  357. "p75(measurements.fcp)",
  358. "p75(measurements.lcp)",
  359. "p75(measurements.fid)",
  360. "p75(measurements.cls)",
  361. "count_unique(user)",
  362. "apdex()",
  363. "count_miserable(user)",
  364. "user_misery()",
  365. "failure_rate()",
  366. ],
  367. "query": "event.type:transaction",
  368. "dataset": dataset,
  369. "per_page": 50,
  370. }
  371. )
  372. assert len(response.data["data"]) == 1
  373. data = response.data["data"][0]
  374. meta = response.data["meta"]
  375. field_meta = meta["fields"]
  376. assert data["transaction"] == "foo_transaction"
  377. assert data["project"] == self.project.slug
  378. assert data["p75(measurements.fcp)"] == 1.0
  379. assert data["p75(measurements.lcp)"] == 2.0
  380. assert data["p75(measurements.fid)"] == 3.0
  381. assert data["p75(measurements.cls)"] == 4.0
  382. assert data["apdex()"] == 1.0
  383. assert data["count_miserable(user)"] == 1.0
  384. assert data["user_misery()"] == 0.058
  385. assert data["failure_rate()"] == 1
  386. assert meta["isMetricsData"]
  387. assert field_meta["transaction"] == "string"
  388. assert field_meta["project"] == "string"
  389. assert field_meta["p75(measurements.fcp)"] == "duration"
  390. assert field_meta["p75(measurements.lcp)"] == "duration"
  391. assert field_meta["p75(measurements.fid)"] == "duration"
  392. assert field_meta["p75(measurements.cls)"] == "number"
  393. assert field_meta["apdex()"] == "number"
  394. assert field_meta["count_miserable(user)"] == "integer"
  395. assert field_meta["user_misery()"] == "number"
  396. assert field_meta["failure_rate()"] == "percentage"
  397. def test_no_team_key_transactions(self):
  398. self.store_transaction_metric(
  399. 1, tags={"transaction": "foo_transaction"}, timestamp=self.min_ago
  400. )
  401. self.store_transaction_metric(
  402. 100, tags={"transaction": "bar_transaction"}, timestamp=self.min_ago
  403. )
  404. query = {
  405. "team": "myteams",
  406. "project": [self.project.id],
  407. # TODO sort by transaction here once that's possible for order to match the same test without metrics
  408. "orderby": "p95()",
  409. "field": [
  410. "team_key_transaction",
  411. "transaction",
  412. "transaction.status",
  413. "project",
  414. "epm()",
  415. "failure_rate()",
  416. "p95()",
  417. ],
  418. "per_page": 50,
  419. "dataset": "metricsEnhanced",
  420. }
  421. response = self.do_request(query)
  422. assert response.status_code == 200, response.content
  423. assert len(response.data["data"]) == 2
  424. data = response.data["data"]
  425. meta = response.data["meta"]
  426. field_meta = meta["fields"]
  427. assert data[0]["team_key_transaction"] == 0
  428. assert data[0]["transaction"] == "foo_transaction"
  429. assert data[1]["team_key_transaction"] == 0
  430. assert data[1]["transaction"] == "bar_transaction"
  431. assert meta["isMetricsData"]
  432. assert field_meta["team_key_transaction"] == "boolean"
  433. assert field_meta["transaction"] == "string"
  434. def test_team_key_transactions_my_teams(self):
  435. team1 = self.create_team(organization=self.organization, name="Team A")
  436. self.create_team_membership(team1, user=self.user)
  437. self.project.add_team(team1)
  438. team2 = self.create_team(organization=self.organization, name="Team B")
  439. self.project.add_team(team2)
  440. key_transactions = [
  441. (team1, "foo_transaction"),
  442. (team2, "baz_transaction"),
  443. ]
  444. # Not a key transaction
  445. self.store_transaction_metric(
  446. 100, tags={"transaction": "bar_transaction"}, timestamp=self.min_ago
  447. )
  448. for team, transaction in key_transactions:
  449. self.store_transaction_metric(
  450. 1, tags={"transaction": transaction}, timestamp=self.min_ago
  451. )
  452. TeamKeyTransaction.objects.create(
  453. organization=self.organization,
  454. transaction=transaction,
  455. project_team=ProjectTeam.objects.get(project=self.project, team=team),
  456. )
  457. query = {
  458. "team": "myteams",
  459. "project": [self.project.id],
  460. "field": [
  461. "team_key_transaction",
  462. "transaction",
  463. "transaction.status",
  464. "project",
  465. "epm()",
  466. "failure_rate()",
  467. "p95()",
  468. ],
  469. "per_page": 50,
  470. "dataset": "metricsEnhanced",
  471. }
  472. query["orderby"] = ["team_key_transaction", "p95()"]
  473. response = self.do_request(query)
  474. assert response.status_code == 200, response.content
  475. assert len(response.data["data"]) == 3
  476. data = response.data["data"]
  477. meta = response.data["meta"]
  478. field_meta = meta["fields"]
  479. assert data[0]["team_key_transaction"] == 0
  480. assert data[0]["transaction"] == "baz_transaction"
  481. assert data[1]["team_key_transaction"] == 0
  482. assert data[1]["transaction"] == "bar_transaction"
  483. assert data[2]["team_key_transaction"] == 1
  484. assert data[2]["transaction"] == "foo_transaction"
  485. assert meta["isMetricsData"]
  486. assert field_meta["team_key_transaction"] == "boolean"
  487. assert field_meta["transaction"] == "string"
  488. # not specifying any teams should use my teams
  489. query = {
  490. "project": [self.project.id],
  491. "field": [
  492. "team_key_transaction",
  493. "transaction",
  494. "transaction.status",
  495. "project",
  496. "epm()",
  497. "failure_rate()",
  498. "p95()",
  499. ],
  500. "per_page": 50,
  501. "dataset": "metricsEnhanced",
  502. }
  503. query["orderby"] = ["team_key_transaction", "p95()"]
  504. response = self.do_request(query)
  505. assert response.status_code == 200, response.content
  506. assert len(response.data["data"]) == 3
  507. data = response.data["data"]
  508. meta = response.data["meta"]
  509. field_meta = meta["fields"]
  510. assert data[0]["team_key_transaction"] == 0
  511. assert data[0]["transaction"] == "baz_transaction"
  512. assert data[1]["team_key_transaction"] == 0
  513. assert data[1]["transaction"] == "bar_transaction"
  514. assert data[2]["team_key_transaction"] == 1
  515. assert data[2]["transaction"] == "foo_transaction"
  516. assert meta["isMetricsData"]
  517. assert field_meta["team_key_transaction"] == "boolean"
  518. assert field_meta["transaction"] == "string"
  519. def test_team_key_transactions_orderby(self):
  520. team1 = self.create_team(organization=self.organization, name="Team A")
  521. team2 = self.create_team(organization=self.organization, name="Team B")
  522. key_transactions = [
  523. (team1, "foo_transaction", 1),
  524. (team2, "baz_transaction", 100),
  525. ]
  526. # Not a key transaction
  527. self.store_transaction_metric(
  528. 100, tags={"transaction": "bar_transaction"}, timestamp=self.min_ago
  529. )
  530. for team, transaction, value in key_transactions:
  531. self.store_transaction_metric(
  532. value, tags={"transaction": transaction}, timestamp=self.min_ago
  533. )
  534. self.create_team_membership(team, user=self.user)
  535. self.project.add_team(team)
  536. TeamKeyTransaction.objects.create(
  537. organization=self.organization,
  538. transaction=transaction,
  539. project_team=ProjectTeam.objects.get(project=self.project, team=team),
  540. )
  541. query = {
  542. "team": "myteams",
  543. "project": [self.project.id],
  544. "field": [
  545. "team_key_transaction",
  546. "transaction",
  547. "transaction.status",
  548. "project",
  549. "epm()",
  550. "failure_rate()",
  551. "p95()",
  552. ],
  553. "per_page": 50,
  554. "dataset": "metricsEnhanced",
  555. }
  556. # test ascending order
  557. query["orderby"] = ["team_key_transaction", "p95()"]
  558. response = self.do_request(query)
  559. assert response.status_code == 200, response.content
  560. assert len(response.data["data"]) == 3
  561. data = response.data["data"]
  562. meta = response.data["meta"]
  563. field_meta = meta["fields"]
  564. assert data[0]["team_key_transaction"] == 0
  565. assert data[0]["transaction"] == "bar_transaction"
  566. assert data[1]["team_key_transaction"] == 1
  567. assert data[1]["transaction"] == "foo_transaction"
  568. assert data[2]["team_key_transaction"] == 1
  569. assert data[2]["transaction"] == "baz_transaction"
  570. assert meta["isMetricsData"]
  571. assert field_meta["team_key_transaction"] == "boolean"
  572. assert field_meta["transaction"] == "string"
  573. # test descending order
  574. query["orderby"] = ["-team_key_transaction", "p95()"]
  575. response = self.do_request(query)
  576. assert response.status_code == 200, response.content
  577. assert len(response.data["data"]) == 3
  578. data = response.data["data"]
  579. meta = response.data["meta"]
  580. field_meta = meta["fields"]
  581. assert data[0]["team_key_transaction"] == 1
  582. assert data[0]["transaction"] == "foo_transaction"
  583. assert data[1]["team_key_transaction"] == 1
  584. assert data[1]["transaction"] == "baz_transaction"
  585. assert data[2]["team_key_transaction"] == 0
  586. assert data[2]["transaction"] == "bar_transaction"
  587. assert meta["isMetricsData"]
  588. assert field_meta["team_key_transaction"] == "boolean"
  589. assert field_meta["transaction"] == "string"
  590. def test_team_key_transactions_query(self):
  591. team1 = self.create_team(organization=self.organization, name="Team A")
  592. team2 = self.create_team(organization=self.organization, name="Team B")
  593. key_transactions = [
  594. (team1, "foo_transaction", 1),
  595. (team2, "baz_transaction", 100),
  596. ]
  597. # Not a key transaction
  598. self.store_transaction_metric(
  599. 100, tags={"transaction": "bar_transaction"}, timestamp=self.min_ago
  600. )
  601. for team, transaction, value in key_transactions:
  602. self.store_transaction_metric(
  603. value, tags={"transaction": transaction}, timestamp=self.min_ago
  604. )
  605. self.create_team_membership(team, user=self.user)
  606. self.project.add_team(team)
  607. TeamKeyTransaction.objects.create(
  608. organization=self.organization,
  609. transaction=transaction,
  610. project_team=ProjectTeam.objects.get(project=self.project, team=team),
  611. )
  612. query = {
  613. "team": "myteams",
  614. "project": [self.project.id],
  615. # use the order by to ensure the result order
  616. "orderby": "p95()",
  617. "field": [
  618. "team_key_transaction",
  619. "transaction",
  620. "transaction.status",
  621. "project",
  622. "epm()",
  623. "failure_rate()",
  624. "p95()",
  625. ],
  626. "per_page": 50,
  627. "dataset": "metricsEnhanced",
  628. }
  629. # key transactions
  630. query["query"] = "has:team_key_transaction"
  631. response = self.do_request(query)
  632. assert response.status_code == 200, response.content
  633. assert len(response.data["data"]) == 2
  634. data = response.data["data"]
  635. meta = response.data["meta"]
  636. field_meta = meta["fields"]
  637. assert data[0]["team_key_transaction"] == 1
  638. assert data[0]["transaction"] == "foo_transaction"
  639. assert data[1]["team_key_transaction"] == 1
  640. assert data[1]["transaction"] == "baz_transaction"
  641. assert meta["isMetricsData"]
  642. assert field_meta["team_key_transaction"] == "boolean"
  643. assert field_meta["transaction"] == "string"
  644. # key transactions
  645. query["query"] = "team_key_transaction:true"
  646. response = self.do_request(query)
  647. assert response.status_code == 200, response.content
  648. assert len(response.data["data"]) == 2
  649. data = response.data["data"]
  650. meta = response.data["meta"]
  651. field_meta = meta["fields"]
  652. assert data[0]["team_key_transaction"] == 1
  653. assert data[0]["transaction"] == "foo_transaction"
  654. assert data[1]["team_key_transaction"] == 1
  655. assert data[1]["transaction"] == "baz_transaction"
  656. assert meta["isMetricsData"]
  657. assert field_meta["team_key_transaction"] == "boolean"
  658. assert field_meta["transaction"] == "string"
  659. # not key transactions
  660. query["query"] = "!has:team_key_transaction"
  661. response = self.do_request(query)
  662. assert response.status_code == 200, response.content
  663. assert len(response.data["data"]) == 1
  664. data = response.data["data"]
  665. meta = response.data["meta"]
  666. field_meta = meta["fields"]
  667. assert data[0]["team_key_transaction"] == 0
  668. assert data[0]["transaction"] == "bar_transaction"
  669. assert meta["isMetricsData"]
  670. assert field_meta["team_key_transaction"] == "boolean"
  671. assert field_meta["transaction"] == "string"
  672. # not key transactions
  673. query["query"] = "team_key_transaction:false"
  674. response = self.do_request(query)
  675. assert response.status_code == 200, response.content
  676. assert len(response.data["data"]) == 1
  677. data = response.data["data"]
  678. meta = response.data["meta"]
  679. field_meta = meta["fields"]
  680. assert data[0]["team_key_transaction"] == 0
  681. assert data[0]["transaction"] == "bar_transaction"
  682. assert meta["isMetricsData"]
  683. assert field_meta["team_key_transaction"] == "boolean"
  684. assert field_meta["transaction"] == "string"
  685. def test_team_key_transaction_not_exists(self):
  686. team1 = self.create_team(organization=self.organization, name="Team A")
  687. team2 = self.create_team(organization=self.organization, name="Team B")
  688. key_transactions = [
  689. (team1, "foo_transaction", 1),
  690. (team2, "baz_transaction", 100),
  691. ]
  692. for team, transaction, value in key_transactions:
  693. self.store_transaction_metric(
  694. value, tags={"transaction": transaction}, timestamp=self.min_ago
  695. )
  696. self.create_team_membership(team, user=self.user)
  697. self.project.add_team(team)
  698. TeamKeyTransaction.objects.create(
  699. organization=self.organization,
  700. transaction=transaction,
  701. project_team=ProjectTeam.objects.get(project=self.project, team=team),
  702. )
  703. # Don't create a metric for this one
  704. TeamKeyTransaction.objects.create(
  705. organization=self.organization,
  706. transaction="not_in_metrics",
  707. project_team=ProjectTeam.objects.get(project=self.project, team=team1),
  708. )
  709. query = {
  710. "team": "myteams",
  711. "project": [self.project.id],
  712. # use the order by to ensure the result order
  713. "orderby": "p95()",
  714. "field": [
  715. "team_key_transaction",
  716. "transaction",
  717. "transaction.status",
  718. "project",
  719. "epm()",
  720. "failure_rate()",
  721. "p95()",
  722. ],
  723. "per_page": 50,
  724. "dataset": "metricsEnhanced",
  725. }
  726. # key transactions
  727. query["query"] = "has:team_key_transaction"
  728. response = self.do_request(query)
  729. assert response.status_code == 200, response.content
  730. assert len(response.data["data"]) == 2
  731. data = response.data["data"]
  732. meta = response.data["meta"]
  733. field_meta = meta["fields"]
  734. assert data[0]["team_key_transaction"] == 1
  735. assert data[0]["transaction"] == "foo_transaction"
  736. assert data[1]["team_key_transaction"] == 1
  737. assert data[1]["transaction"] == "baz_transaction"
  738. assert meta["isMetricsData"]
  739. assert field_meta["team_key_transaction"] == "boolean"
  740. assert field_meta["transaction"] == "string"
  741. # key transactions
  742. query["query"] = "team_key_transaction:true"
  743. response = self.do_request(query)
  744. assert response.status_code == 200, response.content
  745. assert len(response.data["data"]) == 2
  746. data = response.data["data"]
  747. meta = response.data["meta"]
  748. field_meta = meta["fields"]
  749. assert data[0]["team_key_transaction"] == 1
  750. assert data[0]["transaction"] == "foo_transaction"
  751. assert data[1]["team_key_transaction"] == 1
  752. assert data[1]["transaction"] == "baz_transaction"
  753. assert meta["isMetricsData"]
  754. assert field_meta["team_key_transaction"] == "boolean"
  755. assert field_meta["transaction"] == "string"
  756. # not key transactions
  757. query["query"] = "!has:team_key_transaction"
  758. response = self.do_request(query)
  759. assert response.status_code == 200, response.content
  760. assert len(response.data["data"]) == 0
  761. data = response.data["data"]
  762. meta = response.data["meta"]
  763. field_meta = meta["fields"]
  764. assert meta["isMetricsData"]
  765. assert field_meta["team_key_transaction"] == "boolean"
  766. assert field_meta["transaction"] == "string"
  767. # not key transactions
  768. query["query"] = "team_key_transaction:false"
  769. response = self.do_request(query)
  770. assert response.status_code == 200, response.content
  771. assert len(response.data["data"]) == 0
  772. data = response.data["data"]
  773. meta = response.data["meta"]
  774. field_meta = meta["fields"]
  775. assert meta["isMetricsData"]
  776. assert field_meta["team_key_transaction"] == "boolean"
  777. assert field_meta["transaction"] == "string"
  778. def test_too_many_team_key_transactions(self):
  779. MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS = 1
  780. with mock.patch(
  781. "sentry.search.events.fields.MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS",
  782. MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS,
  783. ):
  784. team = self.create_team(organization=self.organization, name="Team A")
  785. self.create_team_membership(team, user=self.user)
  786. self.project.add_team(team)
  787. project_team = ProjectTeam.objects.get(project=self.project, team=team)
  788. transactions = ["foo_transaction", "bar_transaction", "baz_transaction"]
  789. for i in range(MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS + 1):
  790. self.store_transaction_metric(
  791. 100, tags={"transaction": transactions[i]}, timestamp=self.min_ago
  792. )
  793. TeamKeyTransaction.objects.bulk_create(
  794. [
  795. TeamKeyTransaction(
  796. organization=self.organization,
  797. project_team=project_team,
  798. transaction=transactions[i],
  799. )
  800. for i in range(MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS + 1)
  801. ]
  802. )
  803. query = {
  804. "team": "myteams",
  805. "project": [self.project.id],
  806. "orderby": "p95()",
  807. "field": [
  808. "team_key_transaction",
  809. "transaction",
  810. "transaction.status",
  811. "project",
  812. "epm()",
  813. "failure_rate()",
  814. "p95()",
  815. ],
  816. "dataset": "metricsEnhanced",
  817. "per_page": 50,
  818. }
  819. response = self.do_request(query)
  820. assert response.status_code == 200, response.content
  821. assert len(response.data["data"]) == 2
  822. data = response.data["data"]
  823. meta = response.data["meta"]
  824. assert (
  825. sum(row["team_key_transaction"] for row in data)
  826. == MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS
  827. )
  828. assert meta["isMetricsData"]
  829. def test_measurement_rating(self):
  830. self.store_transaction_metric(
  831. 50,
  832. metric="measurements.lcp",
  833. tags={"measurement_rating": "good", "transaction": "foo_transaction"},
  834. timestamp=self.min_ago,
  835. )
  836. self.store_transaction_metric(
  837. 15,
  838. metric="measurements.fp",
  839. tags={"measurement_rating": "good", "transaction": "foo_transaction"},
  840. timestamp=self.min_ago,
  841. )
  842. self.store_transaction_metric(
  843. 1500,
  844. metric="measurements.fcp",
  845. tags={"measurement_rating": "meh", "transaction": "foo_transaction"},
  846. timestamp=self.min_ago,
  847. )
  848. self.store_transaction_metric(
  849. 125,
  850. metric="measurements.fid",
  851. tags={"measurement_rating": "meh", "transaction": "foo_transaction"},
  852. timestamp=self.min_ago,
  853. )
  854. self.store_transaction_metric(
  855. 0.15,
  856. metric="measurements.cls",
  857. tags={"measurement_rating": "good", "transaction": "foo_transaction"},
  858. timestamp=self.min_ago,
  859. )
  860. response = self.do_request(
  861. {
  862. "field": [
  863. "transaction",
  864. "count_web_vitals(measurements.lcp, good)",
  865. "count_web_vitals(measurements.fp, good)",
  866. "count_web_vitals(measurements.fcp, meh)",
  867. "count_web_vitals(measurements.fid, meh)",
  868. "count_web_vitals(measurements.cls, good)",
  869. ],
  870. "query": "event.type:transaction",
  871. "dataset": "metricsEnhanced",
  872. "per_page": 50,
  873. }
  874. )
  875. assert response.status_code == 200, response.content
  876. assert len(response.data["data"]) == 1
  877. data = response.data["data"]
  878. meta = response.data["meta"]
  879. field_meta = meta["fields"]
  880. assert data[0]["count_web_vitals(measurements.lcp, good)"] == 1
  881. assert data[0]["count_web_vitals(measurements.fp, good)"] == 1
  882. assert data[0]["count_web_vitals(measurements.fcp, meh)"] == 1
  883. assert data[0]["count_web_vitals(measurements.fid, meh)"] == 1
  884. assert data[0]["count_web_vitals(measurements.cls, good)"] == 1
  885. assert meta["isMetricsData"]
  886. assert field_meta["count_web_vitals(measurements.lcp, good)"] == "integer"
  887. assert field_meta["count_web_vitals(measurements.fp, good)"] == "integer"
  888. assert field_meta["count_web_vitals(measurements.fcp, meh)"] == "integer"
  889. assert field_meta["count_web_vitals(measurements.fid, meh)"] == "integer"
  890. assert field_meta["count_web_vitals(measurements.cls, good)"] == "integer"
  891. def test_measurement_rating_that_does_not_exist(self):
  892. self.store_transaction_metric(
  893. 1,
  894. metric="measurements.lcp",
  895. tags={"measurement_rating": "good", "transaction": "foo_transaction"},
  896. timestamp=self.min_ago,
  897. )
  898. response = self.do_request(
  899. {
  900. "field": ["transaction", "count_web_vitals(measurements.lcp, poor)"],
  901. "query": "event.type:transaction",
  902. "dataset": "metricsEnhanced",
  903. "per_page": 50,
  904. }
  905. )
  906. assert response.status_code == 200, response.content
  907. assert len(response.data["data"]) == 1
  908. data = response.data["data"]
  909. meta = response.data["meta"]
  910. assert data[0]["count_web_vitals(measurements.lcp, poor)"] == 0
  911. assert meta["isMetricsData"]
  912. assert meta["fields"]["count_web_vitals(measurements.lcp, poor)"] == "integer"
  913. def test_count_web_vitals_invalid_vital(self):
  914. query = {
  915. "field": [
  916. "count_web_vitals(measurements.foo, poor)",
  917. ],
  918. "project": [self.project.id],
  919. "dataset": "metricsEnhanced",
  920. }
  921. response = self.do_request(query)
  922. assert response.status_code == 400, response.content
  923. query = {
  924. "field": [
  925. "count_web_vitals(tags[lcp], poor)",
  926. ],
  927. "project": [self.project.id],
  928. "dataset": "metricsEnhanced",
  929. }
  930. response = self.do_request(query)
  931. assert response.status_code == 400, response.content
  932. query = {
  933. "field": [
  934. "count_web_vitals(transaction.duration, poor)",
  935. ],
  936. "project": [self.project.id],
  937. "dataset": "metricsEnhanced",
  938. }
  939. response = self.do_request(query)
  940. assert response.status_code == 400, response.content
  941. query = {
  942. "field": [
  943. "count_web_vitals(measurements.lcp, bad)",
  944. ],
  945. "project": [self.project.id],
  946. "dataset": "metricsEnhanced",
  947. }
  948. response = self.do_request(query)
  949. assert response.status_code == 400, response.content
  950. @mock.patch("sentry.snuba.metrics_performance.MetricsQueryBuilder")
  951. def test_failed_dry_run_does_not_error(self, mock_builder):
  952. with self.feature("organizations:performance-dry-run-mep"):
  953. mock_builder.side_effect = InvalidSearchQuery("Something bad")
  954. query = {
  955. "field": ["count()"],
  956. "project": [self.project.id],
  957. }
  958. response = self.do_request(query)
  959. assert response.status_code == 200, response.content
  960. assert len(mock_builder.mock_calls) == 1
  961. assert mock_builder.call_args.kwargs["dry_run"]
  962. mock_builder.side_effect = IncompatibleMetricsQuery("Something bad")
  963. query = {
  964. "field": ["count()"],
  965. "project": [self.project.id],
  966. }
  967. response = self.do_request(query)
  968. assert response.status_code == 200, response.content
  969. assert len(mock_builder.mock_calls) == 2
  970. assert mock_builder.call_args.kwargs["dry_run"]
  971. mock_builder.side_effect = InvalidConditionError("Something bad")
  972. query = {
  973. "field": ["count()"],
  974. "project": [self.project.id],
  975. }
  976. response = self.do_request(query)
  977. assert response.status_code == 200, response.content
  978. assert len(mock_builder.mock_calls) == 3
  979. assert mock_builder.call_args.kwargs["dry_run"]
  980. def test_count_unique_user_returns_zero(self):
  981. self.store_transaction_metric(
  982. 50,
  983. metric="user",
  984. tags={"transaction": "foo_transaction"},
  985. timestamp=self.min_ago,
  986. )
  987. self.store_transaction_metric(
  988. 50,
  989. tags={"transaction": "foo_transaction"},
  990. timestamp=self.min_ago,
  991. )
  992. self.store_transaction_metric(
  993. 100,
  994. tags={"transaction": "bar_transaction"},
  995. timestamp=self.min_ago,
  996. )
  997. query = {
  998. "project": [self.project.id],
  999. "orderby": "p50()",
  1000. "field": [
  1001. "transaction",
  1002. "count_unique(user)",
  1003. "p50()",
  1004. ],
  1005. "dataset": "metricsEnhanced",
  1006. "per_page": 50,
  1007. }
  1008. response = self.do_request(query)
  1009. assert response.status_code == 200, response.content
  1010. assert len(response.data["data"]) == 2
  1011. data = response.data["data"]
  1012. meta = response.data["meta"]
  1013. assert data[0]["transaction"] == "foo_transaction"
  1014. assert data[0]["count_unique(user)"] == 1
  1015. assert data[1]["transaction"] == "bar_transaction"
  1016. assert data[1]["count_unique(user)"] == 0
  1017. assert meta["isMetricsData"]
  1018. def test_sum_transaction_duration(self):
  1019. self.store_transaction_metric(
  1020. 50,
  1021. tags={"transaction": "foo_transaction"},
  1022. timestamp=self.min_ago,
  1023. )
  1024. self.store_transaction_metric(
  1025. 100,
  1026. tags={"transaction": "foo_transaction"},
  1027. timestamp=self.min_ago,
  1028. )
  1029. self.store_transaction_metric(
  1030. 150,
  1031. tags={"transaction": "foo_transaction"},
  1032. timestamp=self.min_ago,
  1033. )
  1034. query = {
  1035. "project": [self.project.id],
  1036. "orderby": "sum(transaction.duration)",
  1037. "field": [
  1038. "transaction",
  1039. "sum(transaction.duration)",
  1040. ],
  1041. "dataset": "metricsEnhanced",
  1042. "per_page": 50,
  1043. }
  1044. response = self.do_request(query)
  1045. assert response.status_code == 200, response.content
  1046. assert len(response.data["data"]) == 1
  1047. data = response.data["data"]
  1048. meta = response.data["meta"]
  1049. assert data[0]["transaction"] == "foo_transaction"
  1050. assert data[0]["sum(transaction.duration)"] == 300
  1051. assert meta["isMetricsData"]
  1052. def test_custom_measurements_simple(self):
  1053. self.store_transaction_metric(
  1054. 1,
  1055. metric="measurements.something_custom",
  1056. internal_metric="d:transactions/measurements.something_custom@millisecond",
  1057. entity="metrics_distributions",
  1058. tags={"transaction": "foo_transaction"},
  1059. timestamp=self.min_ago,
  1060. )
  1061. query = {
  1062. "project": [self.project.id],
  1063. "orderby": "p50(measurements.something_custom)",
  1064. "field": [
  1065. "transaction",
  1066. "p50(measurements.something_custom)",
  1067. ],
  1068. "statsPeriod": "24h",
  1069. "dataset": "metricsEnhanced",
  1070. "per_page": 50,
  1071. }
  1072. response = self.do_request(query)
  1073. assert response.status_code == 200, response.content
  1074. assert len(response.data["data"]) == 1
  1075. data = response.data["data"]
  1076. meta = response.data["meta"]
  1077. assert data[0]["transaction"] == "foo_transaction"
  1078. assert data[0]["p50(measurements.something_custom)"] == 1
  1079. assert meta["isMetricsData"]
  1080. assert meta["fields"]["p50(measurements.something_custom)"] == "duration"
  1081. assert meta["units"]["p50(measurements.something_custom)"] == "millisecond"
  1082. def test_custom_measurement_size_meta_type(self):
  1083. self.store_transaction_metric(
  1084. 100,
  1085. metric="measurements.custom_type",
  1086. internal_metric="d:transactions/measurements.custom_type@somethingcustom",
  1087. entity="metrics_distributions",
  1088. tags={"transaction": "foo_transaction"},
  1089. timestamp=self.min_ago,
  1090. )
  1091. self.store_transaction_metric(
  1092. 100,
  1093. metric="measurements.percent",
  1094. internal_metric="d:transactions/measurements.percent@ratio",
  1095. entity="metrics_distributions",
  1096. tags={"transaction": "foo_transaction"},
  1097. timestamp=self.min_ago,
  1098. )
  1099. self.store_transaction_metric(
  1100. 100,
  1101. metric="measurements.longtaskcount",
  1102. internal_metric="d:transactions/measurements.longtaskcount@none",
  1103. entity="metrics_distributions",
  1104. tags={"transaction": "foo_transaction"},
  1105. timestamp=self.min_ago,
  1106. )
  1107. query = {
  1108. "project": [self.project.id],
  1109. "orderby": "p50(measurements.longtaskcount)",
  1110. "field": [
  1111. "transaction",
  1112. "p50(measurements.longtaskcount)",
  1113. "p50(measurements.percent)",
  1114. "p50(measurements.custom_type)",
  1115. ],
  1116. "statsPeriod": "24h",
  1117. "dataset": "metricsEnhanced",
  1118. "per_page": 50,
  1119. }
  1120. response = self.do_request(query)
  1121. assert response.status_code == 200, response.content
  1122. assert len(response.data["data"]) == 1
  1123. data = response.data["data"]
  1124. meta = response.data["meta"]
  1125. assert data[0]["transaction"] == "foo_transaction"
  1126. assert data[0]["p50(measurements.longtaskcount)"] == 100
  1127. assert data[0]["p50(measurements.percent)"] == 100
  1128. assert data[0]["p50(measurements.custom_type)"] == 100
  1129. assert meta["isMetricsData"]
  1130. assert meta["fields"]["p50(measurements.longtaskcount)"] == "integer"
  1131. assert meta["units"]["p50(measurements.longtaskcount)"] is None
  1132. assert meta["fields"]["p50(measurements.percent)"] == "percentage"
  1133. assert meta["units"]["p50(measurements.percent)"] is None
  1134. assert meta["fields"]["p50(measurements.custom_type)"] == "number"
  1135. assert meta["units"]["p50(measurements.custom_type)"] is None
  1136. def test_custom_measurement_none_type(self):
  1137. self.store_transaction_metric(
  1138. 1,
  1139. metric="measurements.cls",
  1140. entity="metrics_distributions",
  1141. tags={"transaction": "foo_transaction"},
  1142. timestamp=self.min_ago,
  1143. )
  1144. query = {
  1145. "project": [self.project.id],
  1146. "orderby": "p75(measurements.cls)",
  1147. "field": [
  1148. "transaction",
  1149. "p75(measurements.cls)",
  1150. "p99(measurements.cls)",
  1151. "max(measurements.cls)",
  1152. ],
  1153. "statsPeriod": "24h",
  1154. "dataset": "metricsEnhanced",
  1155. "per_page": 50,
  1156. }
  1157. response = self.do_request(query)
  1158. assert response.status_code == 200, response.content
  1159. assert len(response.data["data"]) == 1
  1160. data = response.data["data"]
  1161. meta = response.data["meta"]
  1162. assert data[0]["transaction"] == "foo_transaction"
  1163. assert data[0]["p75(measurements.cls)"] == 1
  1164. assert data[0]["p99(measurements.cls)"] == 1
  1165. assert data[0]["max(measurements.cls)"] == 1
  1166. assert meta["isMetricsData"]
  1167. assert meta["fields"]["p75(measurements.cls)"] == "number"
  1168. assert meta["units"]["p75(measurements.cls)"] is None
  1169. assert meta["fields"]["p99(measurements.cls)"] == "number"
  1170. assert meta["units"]["p99(measurements.cls)"] is None
  1171. assert meta["fields"]["max(measurements.cls)"] == "number"
  1172. assert meta["units"]["max(measurements.cls)"] is None
  1173. def test_custom_measurement_duration_filtering(self):
  1174. self.store_transaction_metric(
  1175. 1,
  1176. metric="measurements.runtime",
  1177. internal_metric="d:transactions/measurements.runtime@hour",
  1178. entity="metrics_distributions",
  1179. tags={"transaction": "foo_transaction"},
  1180. timestamp=self.min_ago,
  1181. )
  1182. self.store_transaction_metric(
  1183. 180,
  1184. metric="measurements.runtime",
  1185. internal_metric="d:transactions/measurements.runtime@hour",
  1186. entity="metrics_distributions",
  1187. tags={"transaction": "bar_transaction"},
  1188. timestamp=self.min_ago,
  1189. )
  1190. query = {
  1191. "project": [self.project.id],
  1192. "field": [
  1193. "transaction",
  1194. "max(measurements.runtime)",
  1195. ],
  1196. "query": "p50(measurements.runtime):>1wk",
  1197. "statsPeriod": "24h",
  1198. "dataset": "metricsEnhanced",
  1199. "per_page": 50,
  1200. }
  1201. response = self.do_request(query)
  1202. assert response.status_code == 200, response.content
  1203. assert len(response.data["data"]) == 1
  1204. data = response.data["data"]
  1205. meta = response.data["meta"]
  1206. assert data[0]["transaction"] == "bar_transaction"
  1207. assert data[0]["max(measurements.runtime)"] == 180
  1208. assert meta["isMetricsData"]
  1209. def test_custom_measurement_size_filtering(self):
  1210. self.store_transaction_metric(
  1211. 1,
  1212. metric="measurements.datacenter_memory",
  1213. internal_metric="d:transactions/measurements.datacenter_memory@petabyte",
  1214. entity="metrics_distributions",
  1215. tags={"transaction": "foo_transaction"},
  1216. timestamp=self.min_ago,
  1217. )
  1218. self.store_transaction_metric(
  1219. 100,
  1220. metric="measurements.datacenter_memory",
  1221. internal_metric="d:transactions/measurements.datacenter_memory@petabyte",
  1222. entity="metrics_distributions",
  1223. tags={"transaction": "bar_transaction"},
  1224. timestamp=self.min_ago,
  1225. )
  1226. query = {
  1227. "project": [self.project.id],
  1228. "field": [
  1229. "transaction",
  1230. "max(measurements.datacenter_memory)",
  1231. ],
  1232. "query": "p50(measurements.datacenter_memory):>5pb",
  1233. "statsPeriod": "24h",
  1234. "dataset": "metricsEnhanced",
  1235. "per_page": 50,
  1236. }
  1237. response = self.do_request(query)
  1238. assert response.status_code == 200, response.content
  1239. assert len(response.data["data"]) == 1
  1240. data = response.data["data"]
  1241. meta = response.data["meta"]
  1242. assert data[0]["transaction"] == "bar_transaction"
  1243. assert data[0]["max(measurements.datacenter_memory)"] == 100
  1244. assert meta["units"]["max(measurements.datacenter_memory)"] == "petabyte"
  1245. assert meta["fields"]["max(measurements.datacenter_memory)"] == "size"
  1246. assert meta["isMetricsData"]
  1247. def test_has_custom_measurement(self):
  1248. self.store_transaction_metric(
  1249. 33,
  1250. metric="measurements.datacenter_memory",
  1251. internal_metric="d:transactions/measurements.datacenter_memory@petabyte",
  1252. entity="metrics_distributions",
  1253. tags={"transaction": "foo_transaction"},
  1254. timestamp=self.min_ago,
  1255. )
  1256. transaction_data = load_data("transaction", timestamp=self.min_ago)
  1257. transaction_data["measurements"]["datacenter_memory"] = {
  1258. "value": 33,
  1259. "unit": "petabyte",
  1260. }
  1261. self.store_event(transaction_data, self.project.id)
  1262. measurement = "measurements.datacenter_memory"
  1263. response = self.do_request(
  1264. {
  1265. "field": ["transaction", measurement],
  1266. "query": "has:measurements.datacenter_memory",
  1267. "dataset": "discover",
  1268. }
  1269. )
  1270. assert response.status_code == 200, response.content
  1271. assert len(response.data["data"]) == 1
  1272. response = self.do_request(
  1273. {
  1274. "field": ["transaction", measurement],
  1275. "query": "!has:measurements.datacenter_memory",
  1276. "dataset": "discover",
  1277. }
  1278. )
  1279. assert response.status_code == 200, response.content
  1280. assert len(response.data["data"]) == 0
  1281. def test_environment_param(self):
  1282. self.create_environment(self.project, name="staging")
  1283. self.store_transaction_metric(
  1284. 1,
  1285. tags={"transaction": "foo_transaction", "environment": "staging"},
  1286. timestamp=self.min_ago,
  1287. )
  1288. self.store_transaction_metric(
  1289. 100,
  1290. tags={"transaction": "foo_transaction"},
  1291. timestamp=self.min_ago,
  1292. )
  1293. query = {
  1294. "project": [self.project.id],
  1295. "environment": "staging",
  1296. "orderby": "p50(transaction.duration)",
  1297. "field": [
  1298. "transaction",
  1299. "environment",
  1300. "p50(transaction.duration)",
  1301. ],
  1302. "statsPeriod": "24h",
  1303. "dataset": "metricsEnhanced",
  1304. "per_page": 50,
  1305. }
  1306. response = self.do_request(query)
  1307. assert response.status_code == 200, response.content
  1308. assert len(response.data["data"]) == 1
  1309. data = response.data["data"]
  1310. meta = response.data["meta"]
  1311. assert data[0]["transaction"] == "foo_transaction"
  1312. assert data[0]["environment"] == "staging"
  1313. assert data[0]["p50(transaction.duration)"] == 1
  1314. assert meta["isMetricsData"]
  1315. def test_environment_query(self):
  1316. self.create_environment(self.project, name="staging")
  1317. self.store_transaction_metric(
  1318. 1,
  1319. tags={"transaction": "foo_transaction", "environment": "staging"},
  1320. timestamp=self.min_ago,
  1321. )
  1322. self.store_transaction_metric(
  1323. 100,
  1324. tags={"transaction": "foo_transaction"},
  1325. timestamp=self.min_ago,
  1326. )
  1327. query = {
  1328. "project": [self.project.id],
  1329. "orderby": "p50(transaction.duration)",
  1330. "field": [
  1331. "transaction",
  1332. "environment",
  1333. "p50(transaction.duration)",
  1334. ],
  1335. "query": "!has:environment",
  1336. "statsPeriod": "24h",
  1337. "dataset": "metricsEnhanced",
  1338. "per_page": 50,
  1339. }
  1340. response = self.do_request(query)
  1341. assert response.status_code == 200, response.content
  1342. assert len(response.data["data"]) == 1
  1343. data = response.data["data"]
  1344. meta = response.data["meta"]
  1345. assert data[0]["transaction"] == "foo_transaction"
  1346. assert data[0]["environment"] is None or data[0]["environment"] == ""
  1347. assert data[0]["p50(transaction.duration)"] == 100
  1348. assert meta["isMetricsData"]
  1349. def test_has_transaction(self):
  1350. self.store_transaction_metric(
  1351. 1,
  1352. tags={},
  1353. timestamp=self.min_ago,
  1354. )
  1355. self.store_transaction_metric(
  1356. 100,
  1357. tags={"transaction": "foo_transaction"},
  1358. timestamp=self.min_ago,
  1359. )
  1360. query = {
  1361. "project": [self.project.id],
  1362. "orderby": "p50(transaction.duration)",
  1363. "field": [
  1364. "transaction",
  1365. "p50(transaction.duration)",
  1366. ],
  1367. "query": "has:transaction",
  1368. "statsPeriod": "24h",
  1369. "dataset": "metricsEnhanced",
  1370. "per_page": 50,
  1371. }
  1372. response = self.do_request(query)
  1373. assert response.status_code == 200, response.content
  1374. assert len(response.data["data"]) == 2
  1375. data = response.data["data"]
  1376. meta = response.data["meta"]
  1377. assert data[0]["transaction"] == "<< unparameterized >>"
  1378. assert data[0]["p50(transaction.duration)"] == 1
  1379. assert data[1]["transaction"] == "foo_transaction"
  1380. assert data[1]["p50(transaction.duration)"] == 100
  1381. assert meta["isMetricsData"]
  1382. query = {
  1383. "project": [self.project.id],
  1384. "orderby": "p50(transaction.duration)",
  1385. "field": [
  1386. "transaction",
  1387. "p50(transaction.duration)",
  1388. ],
  1389. "query": "!has:transaction",
  1390. "statsPeriod": "24h",
  1391. "dataset": "metricsEnhanced",
  1392. "per_page": 50,
  1393. }
  1394. response = self.do_request(query)
  1395. assert response.status_code == 400, response.content
  1396. def test_apdex_transaction_threshold(self):
  1397. ProjectTransactionThresholdOverride.objects.create(
  1398. transaction="foo_transaction",
  1399. project=self.project,
  1400. organization=self.project.organization,
  1401. threshold=600,
  1402. metric=TransactionMetric.LCP.value,
  1403. )
  1404. ProjectTransactionThresholdOverride.objects.create(
  1405. transaction="bar_transaction",
  1406. project=self.project,
  1407. organization=self.project.organization,
  1408. threshold=600,
  1409. metric=TransactionMetric.LCP.value,
  1410. )
  1411. self.store_transaction_metric(
  1412. 1,
  1413. tags={
  1414. "transaction": "foo_transaction",
  1415. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
  1416. },
  1417. timestamp=self.min_ago,
  1418. )
  1419. self.store_transaction_metric(
  1420. 1,
  1421. "measurements.lcp",
  1422. tags={
  1423. "transaction": "bar_transaction",
  1424. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
  1425. },
  1426. timestamp=self.min_ago,
  1427. )
  1428. response = self.do_request(
  1429. {
  1430. "field": [
  1431. "transaction",
  1432. "apdex()",
  1433. ],
  1434. "orderby": ["apdex()"],
  1435. "query": "event.type:transaction",
  1436. "dataset": "metrics",
  1437. "per_page": 50,
  1438. }
  1439. )
  1440. assert len(response.data["data"]) == 2
  1441. data = response.data["data"]
  1442. meta = response.data["meta"]
  1443. field_meta = meta["fields"]
  1444. assert data[0]["transaction"] == "bar_transaction"
  1445. # Threshold is lcp based
  1446. assert data[0]["apdex()"] == 1
  1447. assert data[1]["transaction"] == "foo_transaction"
  1448. # Threshold is lcp based
  1449. assert data[1]["apdex()"] == 0
  1450. assert meta["isMetricsData"]
  1451. assert field_meta["transaction"] == "string"
  1452. assert field_meta["apdex()"] == "number"
  1453. def test_apdex_project_threshold(self):
  1454. ProjectTransactionThreshold.objects.create(
  1455. project=self.project,
  1456. organization=self.project.organization,
  1457. threshold=600,
  1458. metric=TransactionMetric.LCP.value,
  1459. )
  1460. self.store_transaction_metric(
  1461. 1,
  1462. tags={
  1463. "transaction": "foo_transaction",
  1464. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
  1465. },
  1466. timestamp=self.min_ago,
  1467. )
  1468. self.store_transaction_metric(
  1469. 1,
  1470. "measurements.lcp",
  1471. tags={
  1472. "transaction": "bar_transaction",
  1473. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
  1474. },
  1475. timestamp=self.min_ago,
  1476. )
  1477. response = self.do_request(
  1478. {
  1479. "field": [
  1480. "transaction",
  1481. "apdex()",
  1482. ],
  1483. "orderby": ["apdex()"],
  1484. "query": "event.type:transaction",
  1485. "dataset": "metrics",
  1486. "per_page": 50,
  1487. }
  1488. )
  1489. assert response.status_code == 200, response.content
  1490. assert len(response.data["data"]) == 2
  1491. data = response.data["data"]
  1492. meta = response.data["meta"]
  1493. field_meta = meta["fields"]
  1494. assert data[0]["transaction"] == "bar_transaction"
  1495. # Threshold is lcp based
  1496. assert data[0]["apdex()"] == 1
  1497. assert data[1]["transaction"] == "foo_transaction"
  1498. # Threshold is lcp based
  1499. assert data[1]["apdex()"] == 0
  1500. assert meta["isMetricsData"]
  1501. assert field_meta["transaction"] == "string"
  1502. assert field_meta["apdex()"] == "number"
  1503. def test_apdex_satisfaction_param(self):
  1504. for function in ["apdex(300)", "user_misery(300)", "count_miserable(user, 300)"]:
  1505. query = {
  1506. "project": [self.project.id],
  1507. "field": [
  1508. "transaction",
  1509. function,
  1510. ],
  1511. "statsPeriod": "24h",
  1512. "dataset": "metricsEnhanced",
  1513. "per_page": 50,
  1514. }
  1515. response = self.do_request(query)
  1516. assert response.status_code == 200, response.content
  1517. assert len(response.data["data"]) == 0
  1518. meta = response.data["meta"]
  1519. assert not meta["isMetricsData"], function
  1520. query = {
  1521. "project": [self.project.id],
  1522. "field": [
  1523. "transaction",
  1524. function,
  1525. ],
  1526. "statsPeriod": "24h",
  1527. "dataset": "metrics",
  1528. "per_page": 50,
  1529. }
  1530. response = self.do_request(query)
  1531. assert response.status_code == 400, function
  1532. assert b"threshold parameter" in response.content, function
  1533. def test_mobile_metrics(self):
  1534. self.store_transaction_metric(
  1535. 0.4,
  1536. "measurements.frames_frozen_rate",
  1537. tags={
  1538. "transaction": "bar_transaction",
  1539. },
  1540. timestamp=self.min_ago,
  1541. )
  1542. query = {
  1543. "project": [self.project.id],
  1544. "field": [
  1545. "transaction",
  1546. "p50(measurements.frames_frozen_rate)",
  1547. ],
  1548. "statsPeriod": "24h",
  1549. "dataset": "metrics",
  1550. "per_page": 50,
  1551. }
  1552. response = self.do_request(query)
  1553. assert response.status_code == 200, response.content
  1554. assert len(response.data["data"]) == 1
  1555. assert response.data["data"][0]["p50(measurements.frames_frozen_rate)"] == 0.4
  1556. def test_merge_null_unparam(self):
  1557. self.store_transaction_metric(
  1558. 1,
  1559. # Transaction: unparam
  1560. tags={
  1561. "transaction": "<< unparameterized >>",
  1562. },
  1563. timestamp=self.min_ago,
  1564. )
  1565. self.store_transaction_metric(
  1566. 2,
  1567. # Transaction:null
  1568. tags={},
  1569. timestamp=self.min_ago,
  1570. )
  1571. query = {
  1572. "project": [self.project.id],
  1573. "field": [
  1574. "transaction",
  1575. "p50(transaction.duration)",
  1576. ],
  1577. "statsPeriod": "24h",
  1578. "dataset": "metrics",
  1579. "per_page": 50,
  1580. }
  1581. response = self.do_request(query)
  1582. assert response.status_code == 200, response.content
  1583. assert len(response.data["data"]) == 1
  1584. assert response.data["data"][0]["p50(transaction.duration)"] == 1.5
  1585. def test_custom_measurements_without_function(self):
  1586. self.store_transaction_metric(
  1587. 33,
  1588. metric="measurements.datacenter_memory",
  1589. internal_metric="d:transactions/measurements.datacenter_memory@petabyte",
  1590. entity="metrics_distributions",
  1591. tags={"transaction": "foo_transaction"},
  1592. timestamp=self.min_ago,
  1593. )
  1594. transaction_data = load_data("transaction", timestamp=self.min_ago)
  1595. transaction_data["measurements"]["datacenter_memory"] = {
  1596. "value": 33,
  1597. "unit": "petabyte",
  1598. }
  1599. self.store_event(transaction_data, self.project.id)
  1600. measurement = "measurements.datacenter_memory"
  1601. response = self.do_request(
  1602. {
  1603. "field": ["transaction", measurement],
  1604. "query": "measurements.datacenter_memory:33pb",
  1605. "dataset": "discover",
  1606. }
  1607. )
  1608. assert response.status_code == 200, response.content
  1609. data = response.data["data"]
  1610. assert len(data) == 1
  1611. assert data[0][measurement] == 33
  1612. meta = response.data["meta"]
  1613. field_meta = meta["fields"]
  1614. unit_meta = meta["units"]
  1615. assert field_meta[measurement] == "size"
  1616. assert unit_meta[measurement] == "petabyte"
  1617. assert not meta["isMetricsData"]
  1618. def test_custom_measurements_with_function(self):
  1619. self.store_transaction_metric(
  1620. 33,
  1621. metric="measurements.datacenter_memory",
  1622. internal_metric="d:transactions/measurements.datacenter_memory@petabyte",
  1623. entity="metrics_distributions",
  1624. tags={"transaction": "foo_transaction"},
  1625. timestamp=self.min_ago,
  1626. )
  1627. transaction_data = load_data("transaction", timestamp=self.min_ago)
  1628. transaction_data["measurements"]["datacenter_memory"] = {
  1629. "value": 33,
  1630. "unit": "petabyte",
  1631. }
  1632. self.store_event(transaction_data, self.project.id)
  1633. measurement = "p50(measurements.datacenter_memory)"
  1634. response = self.do_request(
  1635. {
  1636. "field": ["transaction", measurement],
  1637. "query": "measurements.datacenter_memory:33pb",
  1638. "dataset": "discover",
  1639. }
  1640. )
  1641. assert response.status_code == 200, response.content
  1642. data = response.data["data"]
  1643. assert len(data) == 1
  1644. assert data[0][measurement] == 33
  1645. meta = response.data["meta"]
  1646. field_meta = meta["fields"]
  1647. unit_meta = meta["units"]
  1648. assert field_meta[measurement] == "size"
  1649. assert unit_meta[measurement] == "petabyte"
  1650. assert not meta["isMetricsData"]
  1651. def test_custom_measurements_equation(self):
  1652. self.store_transaction_metric(
  1653. 33,
  1654. metric="measurements.datacenter_memory",
  1655. internal_metric="d:transactions/measurements.datacenter_memory@petabyte",
  1656. entity="metrics_distributions",
  1657. tags={"transaction": "foo_transaction"},
  1658. timestamp=self.min_ago,
  1659. )
  1660. transaction_data = load_data("transaction", timestamp=self.min_ago)
  1661. transaction_data["measurements"]["datacenter_memory"] = {
  1662. "value": 33,
  1663. "unit": "petabyte",
  1664. }
  1665. self.store_event(transaction_data, self.project.id)
  1666. response = self.do_request(
  1667. {
  1668. "field": [
  1669. "transaction",
  1670. "measurements.datacenter_memory",
  1671. "equation|measurements.datacenter_memory / 3",
  1672. ],
  1673. "query": "",
  1674. "dataset": "discover",
  1675. }
  1676. )
  1677. assert response.status_code == 200, response.content
  1678. data = response.data["data"]
  1679. assert len(data) == 1
  1680. assert data[0]["measurements.datacenter_memory"] == 33
  1681. assert data[0]["equation|measurements.datacenter_memory / 3"] == 11
  1682. meta = response.data["meta"]
  1683. assert not meta["isMetricsData"]
  1684. class OrganizationEventsMetricsEnhancedPerformanceEndpointTestWithMetricLayer(
  1685. OrganizationEventsMetricsEnhancedPerformanceEndpointTest
  1686. ):
  1687. def setUp(self):
  1688. super().setUp()
  1689. self.features["organizations:use-metrics-layer"] = True
  1690. @pytest.mark.xfail(reason="Having not supported")
  1691. def test_custom_measurement_duration_filtering(self):
  1692. super().test_custom_measurement_size_filtering()
  1693. @pytest.mark.xfail(reason="Having not supported")
  1694. def test_having_condition_not_selected(self):
  1695. super().test_having_condition_not_selected()
  1696. @pytest.mark.xfail(reason="Having not supported")
  1697. def test_custom_measurement_size_filtering(self):
  1698. super().test_custom_measurement_size_filtering()
  1699. @pytest.mark.xfail(reason="Having not supported")
  1700. def test_having_condition(self):
  1701. super().test_having_condition()
  1702. @pytest.mark.xfail(reason="Metrics layer failing to support ordering by apdex")
  1703. def test_apdex_project_threshold(self):
  1704. super().test_apdex_project_threshold()
  1705. @pytest.mark.xfail(reason="Metrics layer failing to support ordering by apdex")
  1706. def test_apdex_transaction_threshold(self):
  1707. super().test_apdex_transaction_threshold()