test_organization_events_mep.py 69 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951
  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. "orderby": "tpm()",
  368. "query": "event.type:transaction",
  369. "dataset": dataset,
  370. "per_page": 50,
  371. }
  372. )
  373. assert len(response.data["data"]) == 1
  374. data = response.data["data"][0]
  375. meta = response.data["meta"]
  376. field_meta = meta["fields"]
  377. assert data["transaction"] == "foo_transaction"
  378. assert data["project"] == self.project.slug
  379. assert data["p75(measurements.fcp)"] == 1.0
  380. assert data["p75(measurements.lcp)"] == 2.0
  381. assert data["p75(measurements.fid)"] == 3.0
  382. assert data["p75(measurements.cls)"] == 4.0
  383. assert data["apdex()"] == 1.0
  384. assert data["count_miserable(user)"] == 1.0
  385. assert data["user_misery()"] == 0.058
  386. assert data["failure_rate()"] == 1
  387. assert meta["isMetricsData"]
  388. assert field_meta["transaction"] == "string"
  389. assert field_meta["project"] == "string"
  390. assert field_meta["p75(measurements.fcp)"] == "duration"
  391. assert field_meta["p75(measurements.lcp)"] == "duration"
  392. assert field_meta["p75(measurements.fid)"] == "duration"
  393. assert field_meta["p75(measurements.cls)"] == "number"
  394. assert field_meta["apdex()"] == "number"
  395. assert field_meta["count_miserable(user)"] == "integer"
  396. assert field_meta["user_misery()"] == "number"
  397. assert field_meta["failure_rate()"] == "percentage"
  398. def test_no_team_key_transactions(self):
  399. self.store_transaction_metric(
  400. 1, tags={"transaction": "foo_transaction"}, timestamp=self.min_ago
  401. )
  402. self.store_transaction_metric(
  403. 100, tags={"transaction": "bar_transaction"}, timestamp=self.min_ago
  404. )
  405. query = {
  406. "team": "myteams",
  407. "project": [self.project.id],
  408. # TODO sort by transaction here once that's possible for order to match the same test without metrics
  409. "orderby": "p95()",
  410. "field": [
  411. "team_key_transaction",
  412. "transaction",
  413. "transaction.status",
  414. "project",
  415. "epm()",
  416. "failure_rate()",
  417. "p95()",
  418. ],
  419. "per_page": 50,
  420. "dataset": "metricsEnhanced",
  421. }
  422. response = self.do_request(query)
  423. assert response.status_code == 200, response.content
  424. assert len(response.data["data"]) == 2
  425. data = response.data["data"]
  426. meta = response.data["meta"]
  427. field_meta = meta["fields"]
  428. assert data[0]["team_key_transaction"] == 0
  429. assert data[0]["transaction"] == "foo_transaction"
  430. assert data[1]["team_key_transaction"] == 0
  431. assert data[1]["transaction"] == "bar_transaction"
  432. assert meta["isMetricsData"]
  433. assert field_meta["team_key_transaction"] == "boolean"
  434. assert field_meta["transaction"] == "string"
  435. def test_team_key_transactions_my_teams(self):
  436. team1 = self.create_team(organization=self.organization, name="Team A")
  437. self.create_team_membership(team1, user=self.user)
  438. self.project.add_team(team1)
  439. team2 = self.create_team(organization=self.organization, name="Team B")
  440. self.project.add_team(team2)
  441. key_transactions = [
  442. (team1, "foo_transaction"),
  443. (team2, "baz_transaction"),
  444. ]
  445. # Not a key transaction
  446. self.store_transaction_metric(
  447. 100, tags={"transaction": "bar_transaction"}, timestamp=self.min_ago
  448. )
  449. for team, transaction in key_transactions:
  450. self.store_transaction_metric(
  451. 1, tags={"transaction": transaction}, timestamp=self.min_ago
  452. )
  453. TeamKeyTransaction.objects.create(
  454. organization=self.organization,
  455. transaction=transaction,
  456. project_team=ProjectTeam.objects.get(project=self.project, team=team),
  457. )
  458. query = {
  459. "team": "myteams",
  460. "project": [self.project.id],
  461. "field": [
  462. "team_key_transaction",
  463. "transaction",
  464. "transaction.status",
  465. "project",
  466. "epm()",
  467. "failure_rate()",
  468. "p95()",
  469. ],
  470. "per_page": 50,
  471. "dataset": "metricsEnhanced",
  472. }
  473. query["orderby"] = ["team_key_transaction", "p95()"]
  474. response = self.do_request(query)
  475. assert response.status_code == 200, response.content
  476. assert len(response.data["data"]) == 3
  477. data = response.data["data"]
  478. meta = response.data["meta"]
  479. field_meta = meta["fields"]
  480. assert data[0]["team_key_transaction"] == 0
  481. assert data[0]["transaction"] == "baz_transaction"
  482. assert data[1]["team_key_transaction"] == 0
  483. assert data[1]["transaction"] == "bar_transaction"
  484. assert data[2]["team_key_transaction"] == 1
  485. assert data[2]["transaction"] == "foo_transaction"
  486. assert meta["isMetricsData"]
  487. assert field_meta["team_key_transaction"] == "boolean"
  488. assert field_meta["transaction"] == "string"
  489. # not specifying any teams should use my teams
  490. query = {
  491. "project": [self.project.id],
  492. "field": [
  493. "team_key_transaction",
  494. "transaction",
  495. "transaction.status",
  496. "project",
  497. "epm()",
  498. "failure_rate()",
  499. "p95()",
  500. ],
  501. "per_page": 50,
  502. "dataset": "metricsEnhanced",
  503. }
  504. query["orderby"] = ["team_key_transaction", "p95()"]
  505. response = self.do_request(query)
  506. assert response.status_code == 200, response.content
  507. assert len(response.data["data"]) == 3
  508. data = response.data["data"]
  509. meta = response.data["meta"]
  510. field_meta = meta["fields"]
  511. assert data[0]["team_key_transaction"] == 0
  512. assert data[0]["transaction"] == "baz_transaction"
  513. assert data[1]["team_key_transaction"] == 0
  514. assert data[1]["transaction"] == "bar_transaction"
  515. assert data[2]["team_key_transaction"] == 1
  516. assert data[2]["transaction"] == "foo_transaction"
  517. assert meta["isMetricsData"]
  518. assert field_meta["team_key_transaction"] == "boolean"
  519. assert field_meta["transaction"] == "string"
  520. def test_team_key_transactions_orderby(self):
  521. team1 = self.create_team(organization=self.organization, name="Team A")
  522. team2 = self.create_team(organization=self.organization, name="Team B")
  523. key_transactions = [
  524. (team1, "foo_transaction", 1),
  525. (team2, "baz_transaction", 100),
  526. ]
  527. # Not a key transaction
  528. self.store_transaction_metric(
  529. 100, tags={"transaction": "bar_transaction"}, timestamp=self.min_ago
  530. )
  531. for team, transaction, value in key_transactions:
  532. self.store_transaction_metric(
  533. value, tags={"transaction": transaction}, timestamp=self.min_ago
  534. )
  535. self.create_team_membership(team, user=self.user)
  536. self.project.add_team(team)
  537. TeamKeyTransaction.objects.create(
  538. organization=self.organization,
  539. transaction=transaction,
  540. project_team=ProjectTeam.objects.get(project=self.project, team=team),
  541. )
  542. query = {
  543. "team": "myteams",
  544. "project": [self.project.id],
  545. "field": [
  546. "team_key_transaction",
  547. "transaction",
  548. "transaction.status",
  549. "project",
  550. "epm()",
  551. "failure_rate()",
  552. "p95()",
  553. ],
  554. "per_page": 50,
  555. "dataset": "metricsEnhanced",
  556. }
  557. # test ascending order
  558. query["orderby"] = ["team_key_transaction", "p95()"]
  559. response = self.do_request(query)
  560. assert response.status_code == 200, response.content
  561. assert len(response.data["data"]) == 3
  562. data = response.data["data"]
  563. meta = response.data["meta"]
  564. field_meta = meta["fields"]
  565. assert data[0]["team_key_transaction"] == 0
  566. assert data[0]["transaction"] == "bar_transaction"
  567. assert data[1]["team_key_transaction"] == 1
  568. assert data[1]["transaction"] == "foo_transaction"
  569. assert data[2]["team_key_transaction"] == 1
  570. assert data[2]["transaction"] == "baz_transaction"
  571. assert meta["isMetricsData"]
  572. assert field_meta["team_key_transaction"] == "boolean"
  573. assert field_meta["transaction"] == "string"
  574. # test descending order
  575. query["orderby"] = ["-team_key_transaction", "p95()"]
  576. response = self.do_request(query)
  577. assert response.status_code == 200, response.content
  578. assert len(response.data["data"]) == 3
  579. data = response.data["data"]
  580. meta = response.data["meta"]
  581. field_meta = meta["fields"]
  582. assert data[0]["team_key_transaction"] == 1
  583. assert data[0]["transaction"] == "foo_transaction"
  584. assert data[1]["team_key_transaction"] == 1
  585. assert data[1]["transaction"] == "baz_transaction"
  586. assert data[2]["team_key_transaction"] == 0
  587. assert data[2]["transaction"] == "bar_transaction"
  588. assert meta["isMetricsData"]
  589. assert field_meta["team_key_transaction"] == "boolean"
  590. assert field_meta["transaction"] == "string"
  591. def test_team_key_transactions_query(self):
  592. team1 = self.create_team(organization=self.organization, name="Team A")
  593. team2 = self.create_team(organization=self.organization, name="Team B")
  594. key_transactions = [
  595. (team1, "foo_transaction", 1),
  596. (team2, "baz_transaction", 100),
  597. ]
  598. # Not a key transaction
  599. self.store_transaction_metric(
  600. 100, tags={"transaction": "bar_transaction"}, timestamp=self.min_ago
  601. )
  602. for team, transaction, value in key_transactions:
  603. self.store_transaction_metric(
  604. value, tags={"transaction": transaction}, timestamp=self.min_ago
  605. )
  606. self.create_team_membership(team, user=self.user)
  607. self.project.add_team(team)
  608. TeamKeyTransaction.objects.create(
  609. organization=self.organization,
  610. transaction=transaction,
  611. project_team=ProjectTeam.objects.get(project=self.project, team=team),
  612. )
  613. query = {
  614. "team": "myteams",
  615. "project": [self.project.id],
  616. # use the order by to ensure the result order
  617. "orderby": "p95()",
  618. "field": [
  619. "team_key_transaction",
  620. "transaction",
  621. "transaction.status",
  622. "project",
  623. "epm()",
  624. "failure_rate()",
  625. "p95()",
  626. ],
  627. "per_page": 50,
  628. "dataset": "metricsEnhanced",
  629. }
  630. # key transactions
  631. query["query"] = "has:team_key_transaction"
  632. response = self.do_request(query)
  633. assert response.status_code == 200, response.content
  634. assert len(response.data["data"]) == 2
  635. data = response.data["data"]
  636. meta = response.data["meta"]
  637. field_meta = meta["fields"]
  638. assert data[0]["team_key_transaction"] == 1
  639. assert data[0]["transaction"] == "foo_transaction"
  640. assert data[1]["team_key_transaction"] == 1
  641. assert data[1]["transaction"] == "baz_transaction"
  642. assert meta["isMetricsData"]
  643. assert field_meta["team_key_transaction"] == "boolean"
  644. assert field_meta["transaction"] == "string"
  645. # key transactions
  646. query["query"] = "team_key_transaction:true"
  647. response = self.do_request(query)
  648. assert response.status_code == 200, response.content
  649. assert len(response.data["data"]) == 2
  650. data = response.data["data"]
  651. meta = response.data["meta"]
  652. field_meta = meta["fields"]
  653. assert data[0]["team_key_transaction"] == 1
  654. assert data[0]["transaction"] == "foo_transaction"
  655. assert data[1]["team_key_transaction"] == 1
  656. assert data[1]["transaction"] == "baz_transaction"
  657. assert meta["isMetricsData"]
  658. assert field_meta["team_key_transaction"] == "boolean"
  659. assert field_meta["transaction"] == "string"
  660. # not key transactions
  661. query["query"] = "!has:team_key_transaction"
  662. response = self.do_request(query)
  663. assert response.status_code == 200, response.content
  664. assert len(response.data["data"]) == 1
  665. data = response.data["data"]
  666. meta = response.data["meta"]
  667. field_meta = meta["fields"]
  668. assert data[0]["team_key_transaction"] == 0
  669. assert data[0]["transaction"] == "bar_transaction"
  670. assert meta["isMetricsData"]
  671. assert field_meta["team_key_transaction"] == "boolean"
  672. assert field_meta["transaction"] == "string"
  673. # not key transactions
  674. query["query"] = "team_key_transaction:false"
  675. response = self.do_request(query)
  676. assert response.status_code == 200, response.content
  677. assert len(response.data["data"]) == 1
  678. data = response.data["data"]
  679. meta = response.data["meta"]
  680. field_meta = meta["fields"]
  681. assert data[0]["team_key_transaction"] == 0
  682. assert data[0]["transaction"] == "bar_transaction"
  683. assert meta["isMetricsData"]
  684. assert field_meta["team_key_transaction"] == "boolean"
  685. assert field_meta["transaction"] == "string"
  686. def test_team_key_transaction_not_exists(self):
  687. team1 = self.create_team(organization=self.organization, name="Team A")
  688. team2 = self.create_team(organization=self.organization, name="Team B")
  689. key_transactions = [
  690. (team1, "foo_transaction", 1),
  691. (team2, "baz_transaction", 100),
  692. ]
  693. for team, transaction, value in key_transactions:
  694. self.store_transaction_metric(
  695. value, tags={"transaction": transaction}, timestamp=self.min_ago
  696. )
  697. self.create_team_membership(team, user=self.user)
  698. self.project.add_team(team)
  699. TeamKeyTransaction.objects.create(
  700. organization=self.organization,
  701. transaction=transaction,
  702. project_team=ProjectTeam.objects.get(project=self.project, team=team),
  703. )
  704. # Don't create a metric for this one
  705. TeamKeyTransaction.objects.create(
  706. organization=self.organization,
  707. transaction="not_in_metrics",
  708. project_team=ProjectTeam.objects.get(project=self.project, team=team1),
  709. )
  710. query = {
  711. "team": "myteams",
  712. "project": [self.project.id],
  713. # use the order by to ensure the result order
  714. "orderby": "p95()",
  715. "field": [
  716. "team_key_transaction",
  717. "transaction",
  718. "transaction.status",
  719. "project",
  720. "epm()",
  721. "failure_rate()",
  722. "p95()",
  723. ],
  724. "per_page": 50,
  725. "dataset": "metricsEnhanced",
  726. }
  727. # key transactions
  728. query["query"] = "has:team_key_transaction"
  729. response = self.do_request(query)
  730. assert response.status_code == 200, response.content
  731. assert len(response.data["data"]) == 2
  732. data = response.data["data"]
  733. meta = response.data["meta"]
  734. field_meta = meta["fields"]
  735. assert data[0]["team_key_transaction"] == 1
  736. assert data[0]["transaction"] == "foo_transaction"
  737. assert data[1]["team_key_transaction"] == 1
  738. assert data[1]["transaction"] == "baz_transaction"
  739. assert meta["isMetricsData"]
  740. assert field_meta["team_key_transaction"] == "boolean"
  741. assert field_meta["transaction"] == "string"
  742. # key transactions
  743. query["query"] = "team_key_transaction:true"
  744. response = self.do_request(query)
  745. assert response.status_code == 200, response.content
  746. assert len(response.data["data"]) == 2
  747. data = response.data["data"]
  748. meta = response.data["meta"]
  749. field_meta = meta["fields"]
  750. assert data[0]["team_key_transaction"] == 1
  751. assert data[0]["transaction"] == "foo_transaction"
  752. assert data[1]["team_key_transaction"] == 1
  753. assert data[1]["transaction"] == "baz_transaction"
  754. assert meta["isMetricsData"]
  755. assert field_meta["team_key_transaction"] == "boolean"
  756. assert field_meta["transaction"] == "string"
  757. # not key transactions
  758. query["query"] = "!has:team_key_transaction"
  759. response = self.do_request(query)
  760. assert response.status_code == 200, response.content
  761. assert len(response.data["data"]) == 0
  762. data = response.data["data"]
  763. meta = response.data["meta"]
  764. field_meta = meta["fields"]
  765. assert meta["isMetricsData"]
  766. assert field_meta["team_key_transaction"] == "boolean"
  767. assert field_meta["transaction"] == "string"
  768. # not key transactions
  769. query["query"] = "team_key_transaction:false"
  770. response = self.do_request(query)
  771. assert response.status_code == 200, response.content
  772. assert len(response.data["data"]) == 0
  773. data = response.data["data"]
  774. meta = response.data["meta"]
  775. field_meta = meta["fields"]
  776. assert meta["isMetricsData"]
  777. assert field_meta["team_key_transaction"] == "boolean"
  778. assert field_meta["transaction"] == "string"
  779. def test_too_many_team_key_transactions(self):
  780. MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS = 1
  781. with mock.patch(
  782. "sentry.search.events.fields.MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS",
  783. MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS,
  784. ):
  785. team = self.create_team(organization=self.organization, name="Team A")
  786. self.create_team_membership(team, user=self.user)
  787. self.project.add_team(team)
  788. project_team = ProjectTeam.objects.get(project=self.project, team=team)
  789. transactions = ["foo_transaction", "bar_transaction", "baz_transaction"]
  790. for i in range(MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS + 1):
  791. self.store_transaction_metric(
  792. 100, tags={"transaction": transactions[i]}, timestamp=self.min_ago
  793. )
  794. TeamKeyTransaction.objects.bulk_create(
  795. [
  796. TeamKeyTransaction(
  797. organization=self.organization,
  798. project_team=project_team,
  799. transaction=transactions[i],
  800. )
  801. for i in range(MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS + 1)
  802. ]
  803. )
  804. query = {
  805. "team": "myteams",
  806. "project": [self.project.id],
  807. "orderby": "p95()",
  808. "field": [
  809. "team_key_transaction",
  810. "transaction",
  811. "transaction.status",
  812. "project",
  813. "epm()",
  814. "failure_rate()",
  815. "p95()",
  816. ],
  817. "dataset": "metricsEnhanced",
  818. "per_page": 50,
  819. }
  820. response = self.do_request(query)
  821. assert response.status_code == 200, response.content
  822. assert len(response.data["data"]) == 2
  823. data = response.data["data"]
  824. meta = response.data["meta"]
  825. assert (
  826. sum(row["team_key_transaction"] for row in data)
  827. == MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS
  828. )
  829. assert meta["isMetricsData"]
  830. def test_measurement_rating(self):
  831. self.store_transaction_metric(
  832. 50,
  833. metric="measurements.lcp",
  834. tags={"measurement_rating": "good", "transaction": "foo_transaction"},
  835. timestamp=self.min_ago,
  836. )
  837. self.store_transaction_metric(
  838. 15,
  839. metric="measurements.fp",
  840. tags={"measurement_rating": "good", "transaction": "foo_transaction"},
  841. timestamp=self.min_ago,
  842. )
  843. self.store_transaction_metric(
  844. 1500,
  845. metric="measurements.fcp",
  846. tags={"measurement_rating": "meh", "transaction": "foo_transaction"},
  847. timestamp=self.min_ago,
  848. )
  849. self.store_transaction_metric(
  850. 125,
  851. metric="measurements.fid",
  852. tags={"measurement_rating": "meh", "transaction": "foo_transaction"},
  853. timestamp=self.min_ago,
  854. )
  855. self.store_transaction_metric(
  856. 0.15,
  857. metric="measurements.cls",
  858. tags={"measurement_rating": "good", "transaction": "foo_transaction"},
  859. timestamp=self.min_ago,
  860. )
  861. response = self.do_request(
  862. {
  863. "field": [
  864. "transaction",
  865. "count_web_vitals(measurements.lcp, good)",
  866. "count_web_vitals(measurements.fp, good)",
  867. "count_web_vitals(measurements.fcp, meh)",
  868. "count_web_vitals(measurements.fid, meh)",
  869. "count_web_vitals(measurements.cls, good)",
  870. ],
  871. "query": "event.type:transaction",
  872. "dataset": "metricsEnhanced",
  873. "per_page": 50,
  874. }
  875. )
  876. assert response.status_code == 200, response.content
  877. assert len(response.data["data"]) == 1
  878. data = response.data["data"]
  879. meta = response.data["meta"]
  880. field_meta = meta["fields"]
  881. assert data[0]["count_web_vitals(measurements.lcp, good)"] == 1
  882. assert data[0]["count_web_vitals(measurements.fp, good)"] == 1
  883. assert data[0]["count_web_vitals(measurements.fcp, meh)"] == 1
  884. assert data[0]["count_web_vitals(measurements.fid, meh)"] == 1
  885. assert data[0]["count_web_vitals(measurements.cls, good)"] == 1
  886. assert meta["isMetricsData"]
  887. assert field_meta["count_web_vitals(measurements.lcp, good)"] == "integer"
  888. assert field_meta["count_web_vitals(measurements.fp, good)"] == "integer"
  889. assert field_meta["count_web_vitals(measurements.fcp, meh)"] == "integer"
  890. assert field_meta["count_web_vitals(measurements.fid, meh)"] == "integer"
  891. assert field_meta["count_web_vitals(measurements.cls, good)"] == "integer"
  892. def test_measurement_rating_that_does_not_exist(self):
  893. self.store_transaction_metric(
  894. 1,
  895. metric="measurements.lcp",
  896. tags={"measurement_rating": "good", "transaction": "foo_transaction"},
  897. timestamp=self.min_ago,
  898. )
  899. response = self.do_request(
  900. {
  901. "field": ["transaction", "count_web_vitals(measurements.lcp, poor)"],
  902. "query": "event.type:transaction",
  903. "dataset": "metricsEnhanced",
  904. "per_page": 50,
  905. }
  906. )
  907. assert response.status_code == 200, response.content
  908. assert len(response.data["data"]) == 1
  909. data = response.data["data"]
  910. meta = response.data["meta"]
  911. assert data[0]["count_web_vitals(measurements.lcp, poor)"] == 0
  912. assert meta["isMetricsData"]
  913. assert meta["fields"]["count_web_vitals(measurements.lcp, poor)"] == "integer"
  914. def test_count_web_vitals_invalid_vital(self):
  915. query = {
  916. "field": [
  917. "count_web_vitals(measurements.foo, poor)",
  918. ],
  919. "project": [self.project.id],
  920. "dataset": "metricsEnhanced",
  921. }
  922. response = self.do_request(query)
  923. assert response.status_code == 400, response.content
  924. query = {
  925. "field": [
  926. "count_web_vitals(tags[lcp], poor)",
  927. ],
  928. "project": [self.project.id],
  929. "dataset": "metricsEnhanced",
  930. }
  931. response = self.do_request(query)
  932. assert response.status_code == 400, response.content
  933. query = {
  934. "field": [
  935. "count_web_vitals(transaction.duration, poor)",
  936. ],
  937. "project": [self.project.id],
  938. "dataset": "metricsEnhanced",
  939. }
  940. response = self.do_request(query)
  941. assert response.status_code == 400, response.content
  942. query = {
  943. "field": [
  944. "count_web_vitals(measurements.lcp, bad)",
  945. ],
  946. "project": [self.project.id],
  947. "dataset": "metricsEnhanced",
  948. }
  949. response = self.do_request(query)
  950. assert response.status_code == 400, response.content
  951. @mock.patch("sentry.snuba.metrics_performance.MetricsQueryBuilder")
  952. def test_failed_dry_run_does_not_error(self, mock_builder):
  953. with self.feature("organizations:performance-dry-run-mep"):
  954. mock_builder.side_effect = InvalidSearchQuery("Something bad")
  955. query = {
  956. "field": ["count()"],
  957. "project": [self.project.id],
  958. }
  959. response = self.do_request(query)
  960. assert response.status_code == 200, response.content
  961. assert len(mock_builder.mock_calls) == 1
  962. assert mock_builder.call_args.kwargs["dry_run"]
  963. mock_builder.side_effect = IncompatibleMetricsQuery("Something bad")
  964. query = {
  965. "field": ["count()"],
  966. "project": [self.project.id],
  967. }
  968. response = self.do_request(query)
  969. assert response.status_code == 200, response.content
  970. assert len(mock_builder.mock_calls) == 2
  971. assert mock_builder.call_args.kwargs["dry_run"]
  972. mock_builder.side_effect = InvalidConditionError("Something bad")
  973. query = {
  974. "field": ["count()"],
  975. "project": [self.project.id],
  976. }
  977. response = self.do_request(query)
  978. assert response.status_code == 200, response.content
  979. assert len(mock_builder.mock_calls) == 3
  980. assert mock_builder.call_args.kwargs["dry_run"]
  981. def test_count_unique_user_returns_zero(self):
  982. self.store_transaction_metric(
  983. 50,
  984. metric="user",
  985. tags={"transaction": "foo_transaction"},
  986. timestamp=self.min_ago,
  987. )
  988. self.store_transaction_metric(
  989. 50,
  990. tags={"transaction": "foo_transaction"},
  991. timestamp=self.min_ago,
  992. )
  993. self.store_transaction_metric(
  994. 100,
  995. tags={"transaction": "bar_transaction"},
  996. timestamp=self.min_ago,
  997. )
  998. query = {
  999. "project": [self.project.id],
  1000. "orderby": "p50()",
  1001. "field": [
  1002. "transaction",
  1003. "count_unique(user)",
  1004. "p50()",
  1005. ],
  1006. "dataset": "metricsEnhanced",
  1007. "per_page": 50,
  1008. }
  1009. response = self.do_request(query)
  1010. assert response.status_code == 200, response.content
  1011. assert len(response.data["data"]) == 2
  1012. data = response.data["data"]
  1013. meta = response.data["meta"]
  1014. assert data[0]["transaction"] == "foo_transaction"
  1015. assert data[0]["count_unique(user)"] == 1
  1016. assert data[1]["transaction"] == "bar_transaction"
  1017. assert data[1]["count_unique(user)"] == 0
  1018. assert meta["isMetricsData"]
  1019. def test_sum_transaction_duration(self):
  1020. self.store_transaction_metric(
  1021. 50,
  1022. tags={"transaction": "foo_transaction"},
  1023. timestamp=self.min_ago,
  1024. )
  1025. self.store_transaction_metric(
  1026. 100,
  1027. tags={"transaction": "foo_transaction"},
  1028. timestamp=self.min_ago,
  1029. )
  1030. self.store_transaction_metric(
  1031. 150,
  1032. tags={"transaction": "foo_transaction"},
  1033. timestamp=self.min_ago,
  1034. )
  1035. query = {
  1036. "project": [self.project.id],
  1037. "orderby": "sum(transaction.duration)",
  1038. "field": [
  1039. "transaction",
  1040. "sum(transaction.duration)",
  1041. ],
  1042. "dataset": "metricsEnhanced",
  1043. "per_page": 50,
  1044. }
  1045. response = self.do_request(query)
  1046. assert response.status_code == 200, response.content
  1047. assert len(response.data["data"]) == 1
  1048. data = response.data["data"]
  1049. meta = response.data["meta"]
  1050. assert data[0]["transaction"] == "foo_transaction"
  1051. assert data[0]["sum(transaction.duration)"] == 300
  1052. assert meta["isMetricsData"]
  1053. def test_custom_measurements_simple(self):
  1054. self.store_transaction_metric(
  1055. 1,
  1056. metric="measurements.something_custom",
  1057. internal_metric="d:transactions/measurements.something_custom@millisecond",
  1058. entity="metrics_distributions",
  1059. tags={"transaction": "foo_transaction"},
  1060. timestamp=self.min_ago,
  1061. )
  1062. query = {
  1063. "project": [self.project.id],
  1064. "orderby": "p50(measurements.something_custom)",
  1065. "field": [
  1066. "transaction",
  1067. "p50(measurements.something_custom)",
  1068. ],
  1069. "statsPeriod": "24h",
  1070. "dataset": "metricsEnhanced",
  1071. "per_page": 50,
  1072. }
  1073. response = self.do_request(query)
  1074. assert response.status_code == 200, response.content
  1075. assert len(response.data["data"]) == 1
  1076. data = response.data["data"]
  1077. meta = response.data["meta"]
  1078. assert data[0]["transaction"] == "foo_transaction"
  1079. assert data[0]["p50(measurements.something_custom)"] == 1
  1080. assert meta["isMetricsData"]
  1081. assert meta["fields"]["p50(measurements.something_custom)"] == "duration"
  1082. assert meta["units"]["p50(measurements.something_custom)"] == "millisecond"
  1083. def test_custom_measurement_size_meta_type(self):
  1084. self.store_transaction_metric(
  1085. 100,
  1086. metric="measurements.custom_type",
  1087. internal_metric="d:transactions/measurements.custom_type@somethingcustom",
  1088. entity="metrics_distributions",
  1089. tags={"transaction": "foo_transaction"},
  1090. timestamp=self.min_ago,
  1091. )
  1092. self.store_transaction_metric(
  1093. 100,
  1094. metric="measurements.percent",
  1095. internal_metric="d:transactions/measurements.percent@ratio",
  1096. entity="metrics_distributions",
  1097. tags={"transaction": "foo_transaction"},
  1098. timestamp=self.min_ago,
  1099. )
  1100. self.store_transaction_metric(
  1101. 100,
  1102. metric="measurements.longtaskcount",
  1103. internal_metric="d:transactions/measurements.longtaskcount@none",
  1104. entity="metrics_distributions",
  1105. tags={"transaction": "foo_transaction"},
  1106. timestamp=self.min_ago,
  1107. )
  1108. query = {
  1109. "project": [self.project.id],
  1110. "orderby": "p50(measurements.longtaskcount)",
  1111. "field": [
  1112. "transaction",
  1113. "p50(measurements.longtaskcount)",
  1114. "p50(measurements.percent)",
  1115. "p50(measurements.custom_type)",
  1116. ],
  1117. "statsPeriod": "24h",
  1118. "dataset": "metricsEnhanced",
  1119. "per_page": 50,
  1120. }
  1121. response = self.do_request(query)
  1122. assert response.status_code == 200, response.content
  1123. assert len(response.data["data"]) == 1
  1124. data = response.data["data"]
  1125. meta = response.data["meta"]
  1126. assert data[0]["transaction"] == "foo_transaction"
  1127. assert data[0]["p50(measurements.longtaskcount)"] == 100
  1128. assert data[0]["p50(measurements.percent)"] == 100
  1129. assert data[0]["p50(measurements.custom_type)"] == 100
  1130. assert meta["isMetricsData"]
  1131. assert meta["fields"]["p50(measurements.longtaskcount)"] == "integer"
  1132. assert meta["units"]["p50(measurements.longtaskcount)"] is None
  1133. assert meta["fields"]["p50(measurements.percent)"] == "percentage"
  1134. assert meta["units"]["p50(measurements.percent)"] is None
  1135. assert meta["fields"]["p50(measurements.custom_type)"] == "number"
  1136. assert meta["units"]["p50(measurements.custom_type)"] is None
  1137. def test_custom_measurement_none_type(self):
  1138. self.store_transaction_metric(
  1139. 1,
  1140. metric="measurements.cls",
  1141. entity="metrics_distributions",
  1142. tags={"transaction": "foo_transaction"},
  1143. timestamp=self.min_ago,
  1144. )
  1145. query = {
  1146. "project": [self.project.id],
  1147. "orderby": "p75(measurements.cls)",
  1148. "field": [
  1149. "transaction",
  1150. "p75(measurements.cls)",
  1151. "p99(measurements.cls)",
  1152. "max(measurements.cls)",
  1153. ],
  1154. "statsPeriod": "24h",
  1155. "dataset": "metricsEnhanced",
  1156. "per_page": 50,
  1157. }
  1158. response = self.do_request(query)
  1159. assert response.status_code == 200, response.content
  1160. assert len(response.data["data"]) == 1
  1161. data = response.data["data"]
  1162. meta = response.data["meta"]
  1163. assert data[0]["transaction"] == "foo_transaction"
  1164. assert data[0]["p75(measurements.cls)"] == 1
  1165. assert data[0]["p99(measurements.cls)"] == 1
  1166. assert data[0]["max(measurements.cls)"] == 1
  1167. assert meta["isMetricsData"]
  1168. assert meta["fields"]["p75(measurements.cls)"] == "number"
  1169. assert meta["units"]["p75(measurements.cls)"] is None
  1170. assert meta["fields"]["p99(measurements.cls)"] == "number"
  1171. assert meta["units"]["p99(measurements.cls)"] is None
  1172. assert meta["fields"]["max(measurements.cls)"] == "number"
  1173. assert meta["units"]["max(measurements.cls)"] is None
  1174. def test_custom_measurement_duration_filtering(self):
  1175. self.store_transaction_metric(
  1176. 1,
  1177. metric="measurements.runtime",
  1178. internal_metric="d:transactions/measurements.runtime@hour",
  1179. entity="metrics_distributions",
  1180. tags={"transaction": "foo_transaction"},
  1181. timestamp=self.min_ago,
  1182. )
  1183. self.store_transaction_metric(
  1184. 180,
  1185. metric="measurements.runtime",
  1186. internal_metric="d:transactions/measurements.runtime@hour",
  1187. entity="metrics_distributions",
  1188. tags={"transaction": "bar_transaction"},
  1189. timestamp=self.min_ago,
  1190. )
  1191. query = {
  1192. "project": [self.project.id],
  1193. "field": [
  1194. "transaction",
  1195. "max(measurements.runtime)",
  1196. ],
  1197. "query": "p50(measurements.runtime):>1wk",
  1198. "statsPeriod": "24h",
  1199. "dataset": "metricsEnhanced",
  1200. "per_page": 50,
  1201. }
  1202. response = self.do_request(query)
  1203. assert response.status_code == 200, response.content
  1204. assert len(response.data["data"]) == 1
  1205. data = response.data["data"]
  1206. meta = response.data["meta"]
  1207. assert data[0]["transaction"] == "bar_transaction"
  1208. assert data[0]["max(measurements.runtime)"] == 180
  1209. assert meta["isMetricsData"]
  1210. def test_custom_measurement_size_filtering(self):
  1211. self.store_transaction_metric(
  1212. 1,
  1213. metric="measurements.datacenter_memory",
  1214. internal_metric="d:transactions/measurements.datacenter_memory@petabyte",
  1215. entity="metrics_distributions",
  1216. tags={"transaction": "foo_transaction"},
  1217. timestamp=self.min_ago,
  1218. )
  1219. self.store_transaction_metric(
  1220. 100,
  1221. metric="measurements.datacenter_memory",
  1222. internal_metric="d:transactions/measurements.datacenter_memory@petabyte",
  1223. entity="metrics_distributions",
  1224. tags={"transaction": "bar_transaction"},
  1225. timestamp=self.min_ago,
  1226. )
  1227. query = {
  1228. "project": [self.project.id],
  1229. "field": [
  1230. "transaction",
  1231. "max(measurements.datacenter_memory)",
  1232. ],
  1233. "query": "p50(measurements.datacenter_memory):>5pb",
  1234. "statsPeriod": "24h",
  1235. "dataset": "metricsEnhanced",
  1236. "per_page": 50,
  1237. }
  1238. response = self.do_request(query)
  1239. assert response.status_code == 200, response.content
  1240. assert len(response.data["data"]) == 1
  1241. data = response.data["data"]
  1242. meta = response.data["meta"]
  1243. assert data[0]["transaction"] == "bar_transaction"
  1244. assert data[0]["max(measurements.datacenter_memory)"] == 100
  1245. assert meta["units"]["max(measurements.datacenter_memory)"] == "petabyte"
  1246. assert meta["fields"]["max(measurements.datacenter_memory)"] == "size"
  1247. assert meta["isMetricsData"]
  1248. def test_has_custom_measurement(self):
  1249. self.store_transaction_metric(
  1250. 33,
  1251. metric="measurements.datacenter_memory",
  1252. internal_metric="d:transactions/measurements.datacenter_memory@petabyte",
  1253. entity="metrics_distributions",
  1254. tags={"transaction": "foo_transaction"},
  1255. timestamp=self.min_ago,
  1256. )
  1257. transaction_data = load_data("transaction", timestamp=self.min_ago)
  1258. transaction_data["measurements"]["datacenter_memory"] = {
  1259. "value": 33,
  1260. "unit": "petabyte",
  1261. }
  1262. self.store_event(transaction_data, self.project.id)
  1263. measurement = "measurements.datacenter_memory"
  1264. response = self.do_request(
  1265. {
  1266. "field": ["transaction", measurement],
  1267. "query": "has:measurements.datacenter_memory",
  1268. "dataset": "discover",
  1269. }
  1270. )
  1271. assert response.status_code == 200, response.content
  1272. assert len(response.data["data"]) == 1
  1273. response = self.do_request(
  1274. {
  1275. "field": ["transaction", measurement],
  1276. "query": "!has:measurements.datacenter_memory",
  1277. "dataset": "discover",
  1278. }
  1279. )
  1280. assert response.status_code == 200, response.content
  1281. assert len(response.data["data"]) == 0
  1282. def test_environment_param(self):
  1283. self.create_environment(self.project, name="staging")
  1284. self.store_transaction_metric(
  1285. 1,
  1286. tags={"transaction": "foo_transaction", "environment": "staging"},
  1287. timestamp=self.min_ago,
  1288. )
  1289. self.store_transaction_metric(
  1290. 100,
  1291. tags={"transaction": "foo_transaction"},
  1292. timestamp=self.min_ago,
  1293. )
  1294. query = {
  1295. "project": [self.project.id],
  1296. "environment": "staging",
  1297. "orderby": "p50(transaction.duration)",
  1298. "field": [
  1299. "transaction",
  1300. "environment",
  1301. "p50(transaction.duration)",
  1302. ],
  1303. "statsPeriod": "24h",
  1304. "dataset": "metricsEnhanced",
  1305. "per_page": 50,
  1306. }
  1307. response = self.do_request(query)
  1308. assert response.status_code == 200, response.content
  1309. assert len(response.data["data"]) == 1
  1310. data = response.data["data"]
  1311. meta = response.data["meta"]
  1312. assert data[0]["transaction"] == "foo_transaction"
  1313. assert data[0]["environment"] == "staging"
  1314. assert data[0]["p50(transaction.duration)"] == 1
  1315. assert meta["isMetricsData"]
  1316. def test_environment_query(self):
  1317. self.create_environment(self.project, name="staging")
  1318. self.store_transaction_metric(
  1319. 1,
  1320. tags={"transaction": "foo_transaction", "environment": "staging"},
  1321. timestamp=self.min_ago,
  1322. )
  1323. self.store_transaction_metric(
  1324. 100,
  1325. tags={"transaction": "foo_transaction"},
  1326. timestamp=self.min_ago,
  1327. )
  1328. query = {
  1329. "project": [self.project.id],
  1330. "orderby": "p50(transaction.duration)",
  1331. "field": [
  1332. "transaction",
  1333. "environment",
  1334. "p50(transaction.duration)",
  1335. ],
  1336. "query": "!has:environment",
  1337. "statsPeriod": "24h",
  1338. "dataset": "metricsEnhanced",
  1339. "per_page": 50,
  1340. }
  1341. response = self.do_request(query)
  1342. assert response.status_code == 200, response.content
  1343. assert len(response.data["data"]) == 1
  1344. data = response.data["data"]
  1345. meta = response.data["meta"]
  1346. assert data[0]["transaction"] == "foo_transaction"
  1347. assert data[0]["environment"] is None or data[0]["environment"] == ""
  1348. assert data[0]["p50(transaction.duration)"] == 100
  1349. assert meta["isMetricsData"]
  1350. def test_has_transaction(self):
  1351. self.store_transaction_metric(
  1352. 1,
  1353. tags={},
  1354. timestamp=self.min_ago,
  1355. )
  1356. self.store_transaction_metric(
  1357. 100,
  1358. tags={"transaction": "foo_transaction"},
  1359. timestamp=self.min_ago,
  1360. )
  1361. query = {
  1362. "project": [self.project.id],
  1363. "orderby": "p50(transaction.duration)",
  1364. "field": [
  1365. "transaction",
  1366. "p50(transaction.duration)",
  1367. ],
  1368. "query": "has:transaction",
  1369. "statsPeriod": "24h",
  1370. "dataset": "metricsEnhanced",
  1371. "per_page": 50,
  1372. }
  1373. response = self.do_request(query)
  1374. assert response.status_code == 200, response.content
  1375. assert len(response.data["data"]) == 2
  1376. data = response.data["data"]
  1377. meta = response.data["meta"]
  1378. assert data[0]["transaction"] == "<< unparameterized >>"
  1379. assert data[0]["p50(transaction.duration)"] == 1
  1380. assert data[1]["transaction"] == "foo_transaction"
  1381. assert data[1]["p50(transaction.duration)"] == 100
  1382. assert meta["isMetricsData"]
  1383. query = {
  1384. "project": [self.project.id],
  1385. "orderby": "p50(transaction.duration)",
  1386. "field": [
  1387. "transaction",
  1388. "p50(transaction.duration)",
  1389. ],
  1390. "query": "!has:transaction",
  1391. "statsPeriod": "24h",
  1392. "dataset": "metricsEnhanced",
  1393. "per_page": 50,
  1394. }
  1395. response = self.do_request(query)
  1396. assert response.status_code == 400, response.content
  1397. def test_apdex_transaction_threshold(self):
  1398. ProjectTransactionThresholdOverride.objects.create(
  1399. transaction="foo_transaction",
  1400. project=self.project,
  1401. organization=self.project.organization,
  1402. threshold=600,
  1403. metric=TransactionMetric.LCP.value,
  1404. )
  1405. ProjectTransactionThresholdOverride.objects.create(
  1406. transaction="bar_transaction",
  1407. project=self.project,
  1408. organization=self.project.organization,
  1409. threshold=600,
  1410. metric=TransactionMetric.LCP.value,
  1411. )
  1412. self.store_transaction_metric(
  1413. 1,
  1414. tags={
  1415. "transaction": "foo_transaction",
  1416. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
  1417. },
  1418. timestamp=self.min_ago,
  1419. )
  1420. self.store_transaction_metric(
  1421. 1,
  1422. "measurements.lcp",
  1423. tags={
  1424. "transaction": "bar_transaction",
  1425. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
  1426. },
  1427. timestamp=self.min_ago,
  1428. )
  1429. response = self.do_request(
  1430. {
  1431. "field": [
  1432. "transaction",
  1433. "apdex()",
  1434. ],
  1435. "orderby": ["apdex()"],
  1436. "query": "event.type:transaction",
  1437. "dataset": "metrics",
  1438. "per_page": 50,
  1439. }
  1440. )
  1441. assert len(response.data["data"]) == 2
  1442. data = response.data["data"]
  1443. meta = response.data["meta"]
  1444. field_meta = meta["fields"]
  1445. assert data[0]["transaction"] == "bar_transaction"
  1446. # Threshold is lcp based
  1447. assert data[0]["apdex()"] == 1
  1448. assert data[1]["transaction"] == "foo_transaction"
  1449. # Threshold is lcp based
  1450. assert data[1]["apdex()"] == 0
  1451. assert meta["isMetricsData"]
  1452. assert field_meta["transaction"] == "string"
  1453. assert field_meta["apdex()"] == "number"
  1454. def test_apdex_project_threshold(self):
  1455. ProjectTransactionThreshold.objects.create(
  1456. project=self.project,
  1457. organization=self.project.organization,
  1458. threshold=600,
  1459. metric=TransactionMetric.LCP.value,
  1460. )
  1461. self.store_transaction_metric(
  1462. 1,
  1463. tags={
  1464. "transaction": "foo_transaction",
  1465. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
  1466. },
  1467. timestamp=self.min_ago,
  1468. )
  1469. self.store_transaction_metric(
  1470. 1,
  1471. "measurements.lcp",
  1472. tags={
  1473. "transaction": "bar_transaction",
  1474. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
  1475. },
  1476. timestamp=self.min_ago,
  1477. )
  1478. response = self.do_request(
  1479. {
  1480. "field": [
  1481. "transaction",
  1482. "apdex()",
  1483. ],
  1484. "orderby": ["apdex()"],
  1485. "query": "event.type:transaction",
  1486. "dataset": "metrics",
  1487. "per_page": 50,
  1488. }
  1489. )
  1490. assert response.status_code == 200, response.content
  1491. assert len(response.data["data"]) == 2
  1492. data = response.data["data"]
  1493. meta = response.data["meta"]
  1494. field_meta = meta["fields"]
  1495. assert data[0]["transaction"] == "bar_transaction"
  1496. # Threshold is lcp based
  1497. assert data[0]["apdex()"] == 1
  1498. assert data[1]["transaction"] == "foo_transaction"
  1499. # Threshold is lcp based
  1500. assert data[1]["apdex()"] == 0
  1501. assert meta["isMetricsData"]
  1502. assert field_meta["transaction"] == "string"
  1503. assert field_meta["apdex()"] == "number"
  1504. def test_apdex_satisfaction_param(self):
  1505. for function in ["apdex(300)", "user_misery(300)", "count_miserable(user, 300)"]:
  1506. query = {
  1507. "project": [self.project.id],
  1508. "field": [
  1509. "transaction",
  1510. function,
  1511. ],
  1512. "statsPeriod": "24h",
  1513. "dataset": "metricsEnhanced",
  1514. "per_page": 50,
  1515. }
  1516. response = self.do_request(query)
  1517. assert response.status_code == 200, response.content
  1518. assert len(response.data["data"]) == 0
  1519. meta = response.data["meta"]
  1520. assert not meta["isMetricsData"], function
  1521. query = {
  1522. "project": [self.project.id],
  1523. "field": [
  1524. "transaction",
  1525. function,
  1526. ],
  1527. "statsPeriod": "24h",
  1528. "dataset": "metrics",
  1529. "per_page": 50,
  1530. }
  1531. response = self.do_request(query)
  1532. assert response.status_code == 400, function
  1533. assert b"threshold parameter" in response.content, function
  1534. def test_mobile_metrics(self):
  1535. self.store_transaction_metric(
  1536. 0.4,
  1537. "measurements.frames_frozen_rate",
  1538. tags={
  1539. "transaction": "bar_transaction",
  1540. },
  1541. timestamp=self.min_ago,
  1542. )
  1543. query = {
  1544. "project": [self.project.id],
  1545. "field": [
  1546. "transaction",
  1547. "p50(measurements.frames_frozen_rate)",
  1548. ],
  1549. "statsPeriod": "24h",
  1550. "dataset": "metrics",
  1551. "per_page": 50,
  1552. }
  1553. response = self.do_request(query)
  1554. assert response.status_code == 200, response.content
  1555. assert len(response.data["data"]) == 1
  1556. assert response.data["data"][0]["p50(measurements.frames_frozen_rate)"] == 0.4
  1557. def test_merge_null_unparam(self):
  1558. self.store_transaction_metric(
  1559. 1,
  1560. # Transaction: unparam
  1561. tags={
  1562. "transaction": "<< unparameterized >>",
  1563. },
  1564. timestamp=self.min_ago,
  1565. )
  1566. self.store_transaction_metric(
  1567. 2,
  1568. # Transaction:null
  1569. tags={},
  1570. timestamp=self.min_ago,
  1571. )
  1572. query = {
  1573. "project": [self.project.id],
  1574. "field": [
  1575. "transaction",
  1576. "p50(transaction.duration)",
  1577. ],
  1578. "statsPeriod": "24h",
  1579. "dataset": "metrics",
  1580. "per_page": 50,
  1581. }
  1582. response = self.do_request(query)
  1583. assert response.status_code == 200, response.content
  1584. assert len(response.data["data"]) == 1
  1585. assert response.data["data"][0]["p50(transaction.duration)"] == 1.5
  1586. def test_unparam_filter(self):
  1587. self.store_transaction_metric(
  1588. 1,
  1589. # Transaction: unparam
  1590. tags={
  1591. "transaction": "<< unparameterized >>",
  1592. },
  1593. timestamp=self.min_ago,
  1594. )
  1595. self.store_transaction_metric(
  1596. 2,
  1597. # Transaction:null
  1598. tags={},
  1599. timestamp=self.min_ago,
  1600. )
  1601. self.store_transaction_metric(
  1602. 3,
  1603. tags={
  1604. "transaction": "foo_transaction",
  1605. },
  1606. timestamp=self.min_ago,
  1607. )
  1608. query = {
  1609. "project": [self.project.id],
  1610. "field": [
  1611. "transaction",
  1612. "count()",
  1613. ],
  1614. "query": 'transaction:"<< unparameterized >>"',
  1615. "statsPeriod": "24h",
  1616. "dataset": "metrics",
  1617. "per_page": 50,
  1618. }
  1619. response = self.do_request(query)
  1620. assert response.status_code == 200, response.content
  1621. assert len(response.data["data"]) == 1
  1622. assert response.data["data"][0]["transaction"] == "<< unparameterized >>"
  1623. assert response.data["data"][0]["count()"] == 2
  1624. def test_custom_measurements_without_function(self):
  1625. self.store_transaction_metric(
  1626. 33,
  1627. metric="measurements.datacenter_memory",
  1628. internal_metric="d:transactions/measurements.datacenter_memory@petabyte",
  1629. entity="metrics_distributions",
  1630. tags={"transaction": "foo_transaction"},
  1631. timestamp=self.min_ago,
  1632. )
  1633. transaction_data = load_data("transaction", timestamp=self.min_ago)
  1634. transaction_data["measurements"]["datacenter_memory"] = {
  1635. "value": 33,
  1636. "unit": "petabyte",
  1637. }
  1638. self.store_event(transaction_data, self.project.id)
  1639. measurement = "measurements.datacenter_memory"
  1640. response = self.do_request(
  1641. {
  1642. "field": ["transaction", measurement],
  1643. "query": "measurements.datacenter_memory:33pb",
  1644. "dataset": "discover",
  1645. }
  1646. )
  1647. assert response.status_code == 200, response.content
  1648. data = response.data["data"]
  1649. assert len(data) == 1
  1650. assert data[0][measurement] == 33
  1651. meta = response.data["meta"]
  1652. field_meta = meta["fields"]
  1653. unit_meta = meta["units"]
  1654. assert field_meta[measurement] == "size"
  1655. assert unit_meta[measurement] == "petabyte"
  1656. assert not meta["isMetricsData"]
  1657. def test_custom_measurements_with_function(self):
  1658. self.store_transaction_metric(
  1659. 33,
  1660. metric="measurements.datacenter_memory",
  1661. internal_metric="d:transactions/measurements.datacenter_memory@petabyte",
  1662. entity="metrics_distributions",
  1663. tags={"transaction": "foo_transaction"},
  1664. timestamp=self.min_ago,
  1665. )
  1666. transaction_data = load_data("transaction", timestamp=self.min_ago)
  1667. transaction_data["measurements"]["datacenter_memory"] = {
  1668. "value": 33,
  1669. "unit": "petabyte",
  1670. }
  1671. self.store_event(transaction_data, self.project.id)
  1672. measurement = "p50(measurements.datacenter_memory)"
  1673. response = self.do_request(
  1674. {
  1675. "field": ["transaction", measurement],
  1676. "query": "measurements.datacenter_memory:33pb",
  1677. "dataset": "discover",
  1678. }
  1679. )
  1680. assert response.status_code == 200, response.content
  1681. data = response.data["data"]
  1682. assert len(data) == 1
  1683. assert data[0][measurement] == 33
  1684. meta = response.data["meta"]
  1685. field_meta = meta["fields"]
  1686. unit_meta = meta["units"]
  1687. assert field_meta[measurement] == "size"
  1688. assert unit_meta[measurement] == "petabyte"
  1689. assert not meta["isMetricsData"]
  1690. def test_custom_measurements_equation(self):
  1691. self.store_transaction_metric(
  1692. 33,
  1693. metric="measurements.datacenter_memory",
  1694. internal_metric="d:transactions/measurements.datacenter_memory@petabyte",
  1695. entity="metrics_distributions",
  1696. tags={"transaction": "foo_transaction"},
  1697. timestamp=self.min_ago,
  1698. )
  1699. transaction_data = load_data("transaction", timestamp=self.min_ago)
  1700. transaction_data["measurements"]["datacenter_memory"] = {
  1701. "value": 33,
  1702. "unit": "petabyte",
  1703. }
  1704. self.store_event(transaction_data, self.project.id)
  1705. response = self.do_request(
  1706. {
  1707. "field": [
  1708. "transaction",
  1709. "measurements.datacenter_memory",
  1710. "equation|measurements.datacenter_memory / 3",
  1711. ],
  1712. "query": "",
  1713. "dataset": "discover",
  1714. }
  1715. )
  1716. assert response.status_code == 200, response.content
  1717. data = response.data["data"]
  1718. assert len(data) == 1
  1719. assert data[0]["measurements.datacenter_memory"] == 33
  1720. assert data[0]["equation|measurements.datacenter_memory / 3"] == 11
  1721. meta = response.data["meta"]
  1722. assert not meta["isMetricsData"]
  1723. class OrganizationEventsMetricsEnhancedPerformanceEndpointTestWithMetricLayer(
  1724. OrganizationEventsMetricsEnhancedPerformanceEndpointTest
  1725. ):
  1726. def setUp(self):
  1727. super().setUp()
  1728. self.features["organizations:use-metrics-layer"] = True
  1729. @pytest.mark.xfail(reason="Having not supported")
  1730. def test_custom_measurement_duration_filtering(self):
  1731. super().test_custom_measurement_size_filtering()
  1732. @pytest.mark.xfail(reason="Having not supported")
  1733. def test_having_condition_not_selected(self):
  1734. super().test_having_condition_not_selected()
  1735. @pytest.mark.xfail(reason="Having not supported")
  1736. def test_custom_measurement_size_filtering(self):
  1737. super().test_custom_measurement_size_filtering()
  1738. @pytest.mark.xfail(reason="Having not supported")
  1739. def test_having_condition(self):
  1740. super().test_having_condition()