test_organization_events_mep.py 64 KB

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