test_organization_events_mep.py 57 KB

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