test_organization_events_mep.py 67 KB

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