test_organization_events_mep.py 61 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731
  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_team_key_transaction_not_exists(self):
  682. team1 = self.create_team(organization=self.organization, name="Team A")
  683. team2 = self.create_team(organization=self.organization, name="Team B")
  684. key_transactions = [
  685. (team1, "foo_transaction", 1),
  686. (team2, "baz_transaction", 100),
  687. ]
  688. for team, transaction, value in key_transactions:
  689. self.store_transaction_metric(
  690. value, tags={"transaction": transaction}, timestamp=self.min_ago
  691. )
  692. self.create_team_membership(team, user=self.user)
  693. self.project.add_team(team)
  694. TeamKeyTransaction.objects.create(
  695. organization=self.organization,
  696. transaction=transaction,
  697. project_team=ProjectTeam.objects.get(project=self.project, team=team),
  698. )
  699. # Don't create a metric for this one
  700. TeamKeyTransaction.objects.create(
  701. organization=self.organization,
  702. transaction="not_in_metrics",
  703. project_team=ProjectTeam.objects.get(project=self.project, team=team1),
  704. )
  705. query = {
  706. "team": "myteams",
  707. "project": [self.project.id],
  708. # use the order by to ensure the result order
  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. "per_page": 50,
  720. "dataset": "metricsEnhanced",
  721. }
  722. # key transactions
  723. query["query"] = "has:team_key_transaction"
  724. response = self.do_request(query)
  725. assert response.status_code == 200, response.content
  726. assert len(response.data["data"]) == 2
  727. data = response.data["data"]
  728. meta = response.data["meta"]
  729. field_meta = meta["fields"]
  730. assert data[0]["team_key_transaction"] == 1
  731. assert data[0]["transaction"] == "foo_transaction"
  732. assert data[1]["team_key_transaction"] == 1
  733. assert data[1]["transaction"] == "baz_transaction"
  734. assert meta["isMetricsData"]
  735. assert field_meta["team_key_transaction"] == "boolean"
  736. assert field_meta["transaction"] == "string"
  737. # key transactions
  738. query["query"] = "team_key_transaction:true"
  739. response = self.do_request(query)
  740. assert response.status_code == 200, response.content
  741. assert len(response.data["data"]) == 2
  742. data = response.data["data"]
  743. meta = response.data["meta"]
  744. field_meta = meta["fields"]
  745. assert data[0]["team_key_transaction"] == 1
  746. assert data[0]["transaction"] == "foo_transaction"
  747. assert data[1]["team_key_transaction"] == 1
  748. assert data[1]["transaction"] == "baz_transaction"
  749. assert meta["isMetricsData"]
  750. assert field_meta["team_key_transaction"] == "boolean"
  751. assert field_meta["transaction"] == "string"
  752. # not key transactions
  753. query["query"] = "!has:team_key_transaction"
  754. response = self.do_request(query)
  755. assert response.status_code == 200, response.content
  756. assert len(response.data["data"]) == 0
  757. data = response.data["data"]
  758. meta = response.data["meta"]
  759. field_meta = meta["fields"]
  760. assert meta["isMetricsData"]
  761. assert field_meta["team_key_transaction"] == "boolean"
  762. assert field_meta["transaction"] == "string"
  763. # not key transactions
  764. query["query"] = "team_key_transaction:false"
  765. response = self.do_request(query)
  766. assert response.status_code == 200, response.content
  767. assert len(response.data["data"]) == 0
  768. data = response.data["data"]
  769. meta = response.data["meta"]
  770. field_meta = meta["fields"]
  771. assert meta["isMetricsData"]
  772. assert field_meta["team_key_transaction"] == "boolean"
  773. assert field_meta["transaction"] == "string"
  774. def test_too_many_team_key_transactions(self):
  775. MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS = 1
  776. with mock.patch(
  777. "sentry.search.events.fields.MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS",
  778. MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS,
  779. ):
  780. team = self.create_team(organization=self.organization, name="Team A")
  781. self.create_team_membership(team, user=self.user)
  782. self.project.add_team(team)
  783. project_team = ProjectTeam.objects.get(project=self.project, team=team)
  784. transactions = ["foo_transaction", "bar_transaction", "baz_transaction"]
  785. for i in range(MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS + 1):
  786. self.store_transaction_metric(
  787. 100, tags={"transaction": transactions[i]}, timestamp=self.min_ago
  788. )
  789. TeamKeyTransaction.objects.bulk_create(
  790. [
  791. TeamKeyTransaction(
  792. organization=self.organization,
  793. project_team=project_team,
  794. transaction=transactions[i],
  795. )
  796. for i in range(MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS + 1)
  797. ]
  798. )
  799. query = {
  800. "team": "myteams",
  801. "project": [self.project.id],
  802. "orderby": "p95()",
  803. "field": [
  804. "team_key_transaction",
  805. "transaction",
  806. "transaction.status",
  807. "project",
  808. "epm()",
  809. "failure_rate()",
  810. "p95()",
  811. ],
  812. "dataset": "metricsEnhanced",
  813. "per_page": 50,
  814. }
  815. response = self.do_request(query)
  816. assert response.status_code == 200, response.content
  817. assert len(response.data["data"]) == 2
  818. data = response.data["data"]
  819. meta = response.data["meta"]
  820. assert (
  821. sum(row["team_key_transaction"] for row in data)
  822. == MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS
  823. )
  824. assert meta["isMetricsData"]
  825. def test_measurement_rating(self):
  826. self.store_transaction_metric(
  827. 50,
  828. metric="measurements.lcp",
  829. tags={"measurement_rating": "good", "transaction": "foo_transaction"},
  830. timestamp=self.min_ago,
  831. )
  832. self.store_transaction_metric(
  833. 15,
  834. metric="measurements.fp",
  835. tags={"measurement_rating": "good", "transaction": "foo_transaction"},
  836. timestamp=self.min_ago,
  837. )
  838. self.store_transaction_metric(
  839. 1500,
  840. metric="measurements.fcp",
  841. tags={"measurement_rating": "meh", "transaction": "foo_transaction"},
  842. timestamp=self.min_ago,
  843. )
  844. self.store_transaction_metric(
  845. 125,
  846. metric="measurements.fid",
  847. tags={"measurement_rating": "meh", "transaction": "foo_transaction"},
  848. timestamp=self.min_ago,
  849. )
  850. self.store_transaction_metric(
  851. 0.15,
  852. metric="measurements.cls",
  853. tags={"measurement_rating": "good", "transaction": "foo_transaction"},
  854. timestamp=self.min_ago,
  855. )
  856. response = self.do_request(
  857. {
  858. "field": [
  859. "transaction",
  860. "count_web_vitals(measurements.lcp, good)",
  861. "count_web_vitals(measurements.fp, good)",
  862. "count_web_vitals(measurements.fcp, meh)",
  863. "count_web_vitals(measurements.fid, meh)",
  864. "count_web_vitals(measurements.cls, good)",
  865. ],
  866. "query": "event.type:transaction",
  867. "dataset": "metricsEnhanced",
  868. "per_page": 50,
  869. }
  870. )
  871. assert response.status_code == 200, response.content
  872. assert len(response.data["data"]) == 1
  873. data = response.data["data"]
  874. meta = response.data["meta"]
  875. field_meta = meta["fields"]
  876. assert data[0]["count_web_vitals(measurements.lcp, good)"] == 1
  877. assert data[0]["count_web_vitals(measurements.fp, good)"] == 1
  878. assert data[0]["count_web_vitals(measurements.fcp, meh)"] == 1
  879. assert data[0]["count_web_vitals(measurements.fid, meh)"] == 1
  880. assert data[0]["count_web_vitals(measurements.cls, good)"] == 1
  881. assert meta["isMetricsData"]
  882. assert field_meta["count_web_vitals(measurements.lcp, good)"] == "integer"
  883. assert field_meta["count_web_vitals(measurements.fp, good)"] == "integer"
  884. assert field_meta["count_web_vitals(measurements.fcp, meh)"] == "integer"
  885. assert field_meta["count_web_vitals(measurements.fid, meh)"] == "integer"
  886. assert field_meta["count_web_vitals(measurements.cls, good)"] == "integer"
  887. def test_measurement_rating_that_does_not_exist(self):
  888. self.store_transaction_metric(
  889. 1,
  890. metric="measurements.lcp",
  891. tags={"measurement_rating": "good", "transaction": "foo_transaction"},
  892. timestamp=self.min_ago,
  893. )
  894. response = self.do_request(
  895. {
  896. "field": ["transaction", "count_web_vitals(measurements.lcp, poor)"],
  897. "query": "event.type:transaction",
  898. "dataset": "metricsEnhanced",
  899. "per_page": 50,
  900. }
  901. )
  902. assert response.status_code == 200, response.content
  903. assert len(response.data["data"]) == 1
  904. data = response.data["data"]
  905. meta = response.data["meta"]
  906. assert data[0]["count_web_vitals(measurements.lcp, poor)"] == 0
  907. assert meta["isMetricsData"]
  908. assert meta["fields"]["count_web_vitals(measurements.lcp, poor)"] == "integer"
  909. def test_count_web_vitals_invalid_vital(self):
  910. query = {
  911. "field": [
  912. "count_web_vitals(measurements.foo, poor)",
  913. ],
  914. "project": [self.project.id],
  915. "dataset": "metricsEnhanced",
  916. }
  917. response = self.do_request(query)
  918. assert response.status_code == 400, response.content
  919. query = {
  920. "field": [
  921. "count_web_vitals(tags[lcp], poor)",
  922. ],
  923. "project": [self.project.id],
  924. "dataset": "metricsEnhanced",
  925. }
  926. response = self.do_request(query)
  927. assert response.status_code == 400, response.content
  928. query = {
  929. "field": [
  930. "count_web_vitals(transaction.duration, poor)",
  931. ],
  932. "project": [self.project.id],
  933. "dataset": "metricsEnhanced",
  934. }
  935. response = self.do_request(query)
  936. assert response.status_code == 400, response.content
  937. query = {
  938. "field": [
  939. "count_web_vitals(measurements.lcp, bad)",
  940. ],
  941. "project": [self.project.id],
  942. "dataset": "metricsEnhanced",
  943. }
  944. response = self.do_request(query)
  945. assert response.status_code == 400, response.content
  946. @mock.patch("sentry.snuba.metrics_performance.MetricsQueryBuilder")
  947. def test_failed_dry_run_does_not_error(self, mock_builder):
  948. with self.feature("organizations:performance-dry-run-mep"):
  949. mock_builder.side_effect = InvalidSearchQuery("Something bad")
  950. query = {
  951. "field": ["count()"],
  952. "project": [self.project.id],
  953. }
  954. response = self.do_request(query)
  955. assert response.status_code == 200, response.content
  956. assert len(mock_builder.mock_calls) == 1
  957. assert mock_builder.call_args.kwargs["dry_run"]
  958. mock_builder.side_effect = IncompatibleMetricsQuery("Something bad")
  959. query = {
  960. "field": ["count()"],
  961. "project": [self.project.id],
  962. }
  963. response = self.do_request(query)
  964. assert response.status_code == 200, response.content
  965. assert len(mock_builder.mock_calls) == 2
  966. assert mock_builder.call_args.kwargs["dry_run"]
  967. mock_builder.side_effect = InvalidConditionError("Something bad")
  968. query = {
  969. "field": ["count()"],
  970. "project": [self.project.id],
  971. }
  972. response = self.do_request(query)
  973. assert response.status_code == 200, response.content
  974. assert len(mock_builder.mock_calls) == 3
  975. assert mock_builder.call_args.kwargs["dry_run"]
  976. def test_count_unique_user_returns_zero(self):
  977. self.store_transaction_metric(
  978. 50,
  979. metric="user",
  980. tags={"transaction": "foo_transaction"},
  981. timestamp=self.min_ago,
  982. )
  983. self.store_transaction_metric(
  984. 50,
  985. tags={"transaction": "foo_transaction"},
  986. timestamp=self.min_ago,
  987. )
  988. self.store_transaction_metric(
  989. 100,
  990. tags={"transaction": "bar_transaction"},
  991. timestamp=self.min_ago,
  992. )
  993. query = {
  994. "project": [self.project.id],
  995. "orderby": "p50()",
  996. "field": [
  997. "transaction",
  998. "count_unique(user)",
  999. "p50()",
  1000. ],
  1001. "dataset": "metricsEnhanced",
  1002. "per_page": 50,
  1003. }
  1004. response = self.do_request(query)
  1005. assert response.status_code == 200, response.content
  1006. assert len(response.data["data"]) == 2
  1007. data = response.data["data"]
  1008. meta = response.data["meta"]
  1009. assert data[0]["transaction"] == "foo_transaction"
  1010. assert data[0]["count_unique(user)"] == 1
  1011. assert data[1]["transaction"] == "bar_transaction"
  1012. assert data[1]["count_unique(user)"] == 0
  1013. assert meta["isMetricsData"]
  1014. def test_sum_transaction_duration(self):
  1015. self.store_transaction_metric(
  1016. 50,
  1017. tags={"transaction": "foo_transaction"},
  1018. timestamp=self.min_ago,
  1019. )
  1020. self.store_transaction_metric(
  1021. 100,
  1022. tags={"transaction": "foo_transaction"},
  1023. timestamp=self.min_ago,
  1024. )
  1025. self.store_transaction_metric(
  1026. 150,
  1027. tags={"transaction": "foo_transaction"},
  1028. timestamp=self.min_ago,
  1029. )
  1030. query = {
  1031. "project": [self.project.id],
  1032. "orderby": "sum(transaction.duration)",
  1033. "field": [
  1034. "transaction",
  1035. "sum(transaction.duration)",
  1036. ],
  1037. "dataset": "metricsEnhanced",
  1038. "per_page": 50,
  1039. }
  1040. response = self.do_request(query)
  1041. assert response.status_code == 200, response.content
  1042. assert len(response.data["data"]) == 1
  1043. data = response.data["data"]
  1044. meta = response.data["meta"]
  1045. assert data[0]["transaction"] == "foo_transaction"
  1046. assert data[0]["sum(transaction.duration)"] == 300
  1047. assert meta["isMetricsData"]
  1048. def test_custom_measurements_simple(self):
  1049. self.store_transaction_metric(
  1050. 1,
  1051. metric="measurements.something_custom",
  1052. internal_metric="d:transactions/measurements.something_custom@millisecond",
  1053. entity="metrics_distributions",
  1054. tags={"transaction": "foo_transaction"},
  1055. timestamp=self.min_ago,
  1056. )
  1057. query = {
  1058. "project": [self.project.id],
  1059. "orderby": "p50(measurements.something_custom)",
  1060. "field": [
  1061. "transaction",
  1062. "p50(measurements.something_custom)",
  1063. ],
  1064. "statsPeriod": "24h",
  1065. "dataset": "metricsEnhanced",
  1066. "per_page": 50,
  1067. }
  1068. response = self.do_request(query)
  1069. assert response.status_code == 200, response.content
  1070. assert len(response.data["data"]) == 1
  1071. data = response.data["data"]
  1072. meta = response.data["meta"]
  1073. assert data[0]["transaction"] == "foo_transaction"
  1074. assert data[0]["p50(measurements.something_custom)"] == 1
  1075. assert meta["isMetricsData"]
  1076. assert meta["fields"]["p50(measurements.something_custom)"] == "duration"
  1077. assert meta["units"]["p50(measurements.something_custom)"] == "millisecond"
  1078. def test_custom_measurement_size_meta_type(self):
  1079. self.store_transaction_metric(
  1080. 100,
  1081. metric="measurements.custom_type",
  1082. internal_metric="d:transactions/measurements.custom_type@somethingcustom",
  1083. entity="metrics_distributions",
  1084. tags={"transaction": "foo_transaction"},
  1085. timestamp=self.min_ago,
  1086. )
  1087. self.store_transaction_metric(
  1088. 100,
  1089. metric="measurements.percent",
  1090. internal_metric="d:transactions/measurements.percent@ratio",
  1091. entity="metrics_distributions",
  1092. tags={"transaction": "foo_transaction"},
  1093. timestamp=self.min_ago,
  1094. )
  1095. self.store_transaction_metric(
  1096. 100,
  1097. metric="measurements.longtaskcount",
  1098. internal_metric="d:transactions/measurements.longtaskcount@none",
  1099. entity="metrics_distributions",
  1100. tags={"transaction": "foo_transaction"},
  1101. timestamp=self.min_ago,
  1102. )
  1103. query = {
  1104. "project": [self.project.id],
  1105. "orderby": "p50(measurements.longtaskcount)",
  1106. "field": [
  1107. "transaction",
  1108. "p50(measurements.longtaskcount)",
  1109. "p50(measurements.percent)",
  1110. "p50(measurements.custom_type)",
  1111. ],
  1112. "statsPeriod": "24h",
  1113. "dataset": "metricsEnhanced",
  1114. "per_page": 50,
  1115. }
  1116. response = self.do_request(query)
  1117. assert response.status_code == 200, response.content
  1118. assert len(response.data["data"]) == 1
  1119. data = response.data["data"]
  1120. meta = response.data["meta"]
  1121. assert data[0]["transaction"] == "foo_transaction"
  1122. assert data[0]["p50(measurements.longtaskcount)"] == 100
  1123. assert data[0]["p50(measurements.percent)"] == 100
  1124. assert data[0]["p50(measurements.custom_type)"] == 100
  1125. assert meta["isMetricsData"]
  1126. assert meta["fields"]["p50(measurements.longtaskcount)"] == "integer"
  1127. assert meta["units"]["p50(measurements.longtaskcount)"] is None
  1128. assert meta["fields"]["p50(measurements.percent)"] == "percentage"
  1129. assert meta["units"]["p50(measurements.percent)"] is None
  1130. assert meta["fields"]["p50(measurements.custom_type)"] == "number"
  1131. assert meta["units"]["p50(measurements.custom_type)"] is None
  1132. def test_custom_measurement_none_type(self):
  1133. self.store_transaction_metric(
  1134. 1,
  1135. metric="measurements.cls",
  1136. entity="metrics_distributions",
  1137. tags={"transaction": "foo_transaction"},
  1138. timestamp=self.min_ago,
  1139. )
  1140. query = {
  1141. "project": [self.project.id],
  1142. "orderby": "p75(measurements.cls)",
  1143. "field": [
  1144. "transaction",
  1145. "p75(measurements.cls)",
  1146. "p99(measurements.cls)",
  1147. "max(measurements.cls)",
  1148. ],
  1149. "statsPeriod": "24h",
  1150. "dataset": "metricsEnhanced",
  1151. "per_page": 50,
  1152. }
  1153. response = self.do_request(query)
  1154. assert response.status_code == 200, response.content
  1155. assert len(response.data["data"]) == 1
  1156. data = response.data["data"]
  1157. meta = response.data["meta"]
  1158. assert data[0]["transaction"] == "foo_transaction"
  1159. assert data[0]["p75(measurements.cls)"] == 1
  1160. assert data[0]["p99(measurements.cls)"] == 1
  1161. assert data[0]["max(measurements.cls)"] == 1
  1162. assert meta["isMetricsData"]
  1163. assert meta["fields"]["p75(measurements.cls)"] == "number"
  1164. assert meta["units"]["p75(measurements.cls)"] is None
  1165. assert meta["fields"]["p99(measurements.cls)"] == "number"
  1166. assert meta["units"]["p99(measurements.cls)"] is None
  1167. assert meta["fields"]["max(measurements.cls)"] == "number"
  1168. assert meta["units"]["max(measurements.cls)"] is None
  1169. def test_custom_measurement_size_filtering(self):
  1170. self.store_transaction_metric(
  1171. 1,
  1172. metric="measurements.runtime",
  1173. internal_metric="d:transactions/measurements.runtime@hour",
  1174. entity="metrics_distributions",
  1175. tags={"transaction": "foo_transaction"},
  1176. timestamp=self.min_ago,
  1177. )
  1178. self.store_transaction_metric(
  1179. 180,
  1180. metric="measurements.runtime",
  1181. internal_metric="d:transactions/measurements.runtime@hour",
  1182. entity="metrics_distributions",
  1183. tags={"transaction": "bar_transaction"},
  1184. timestamp=self.min_ago,
  1185. )
  1186. query = {
  1187. "project": [self.project.id],
  1188. "field": [
  1189. "transaction",
  1190. "max(measurements.runtime)",
  1191. ],
  1192. "query": "p50(measurements.runtime):>1wk",
  1193. "statsPeriod": "24h",
  1194. "dataset": "metricsEnhanced",
  1195. "per_page": 50,
  1196. }
  1197. response = self.do_request(query)
  1198. assert response.status_code == 200, response.content
  1199. assert len(response.data["data"]) == 1
  1200. data = response.data["data"]
  1201. meta = response.data["meta"]
  1202. assert data[0]["transaction"] == "bar_transaction"
  1203. assert data[0]["max(measurements.runtime)"] == 180
  1204. assert meta["isMetricsData"]
  1205. def test_custom_measurement_duration_filtering(self):
  1206. self.store_transaction_metric(
  1207. 1,
  1208. metric="measurements.bytes_transfered",
  1209. internal_metric="d:transactions/measurements.datacenter_memory@pebibyte",
  1210. entity="metrics_distributions",
  1211. tags={"transaction": "foo_transaction"},
  1212. timestamp=self.min_ago,
  1213. )
  1214. self.store_transaction_metric(
  1215. 100,
  1216. metric="measurements.bytes_transfered",
  1217. internal_metric="d:transactions/measurements.datacenter_memory@pebibyte",
  1218. entity="metrics_distributions",
  1219. tags={"transaction": "bar_transaction"},
  1220. timestamp=self.min_ago,
  1221. )
  1222. query = {
  1223. "project": [self.project.id],
  1224. "field": [
  1225. "transaction",
  1226. "max(measurements.datacenter_memory)",
  1227. ],
  1228. "query": "p50(measurements.datacenter_memory):>5pb",
  1229. "statsPeriod": "24h",
  1230. "dataset": "metricsEnhanced",
  1231. "per_page": 50,
  1232. }
  1233. response = self.do_request(query)
  1234. assert response.status_code == 200, response.content
  1235. assert len(response.data["data"]) == 1
  1236. data = response.data["data"]
  1237. meta = response.data["meta"]
  1238. assert data[0]["transaction"] == "bar_transaction"
  1239. assert data[0]["max(measurements.datacenter_memory)"] == 100
  1240. assert meta["isMetricsData"]
  1241. def test_environment_param(self):
  1242. self.create_environment(self.project, name="staging")
  1243. self.store_transaction_metric(
  1244. 1,
  1245. tags={"transaction": "foo_transaction", "environment": "staging"},
  1246. timestamp=self.min_ago,
  1247. )
  1248. self.store_transaction_metric(
  1249. 100,
  1250. tags={"transaction": "foo_transaction"},
  1251. timestamp=self.min_ago,
  1252. )
  1253. query = {
  1254. "project": [self.project.id],
  1255. "environment": "staging",
  1256. "orderby": "p50(transaction.duration)",
  1257. "field": [
  1258. "transaction",
  1259. "environment",
  1260. "p50(transaction.duration)",
  1261. ],
  1262. "statsPeriod": "24h",
  1263. "dataset": "metricsEnhanced",
  1264. "per_page": 50,
  1265. }
  1266. response = self.do_request(query)
  1267. assert response.status_code == 200, response.content
  1268. assert len(response.data["data"]) == 1
  1269. data = response.data["data"]
  1270. meta = response.data["meta"]
  1271. assert data[0]["transaction"] == "foo_transaction"
  1272. assert data[0]["environment"] == "staging"
  1273. assert data[0]["p50(transaction.duration)"] == 1
  1274. assert meta["isMetricsData"]
  1275. def test_environment_query(self):
  1276. self.create_environment(self.project, name="staging")
  1277. self.store_transaction_metric(
  1278. 1,
  1279. tags={"transaction": "foo_transaction", "environment": "staging"},
  1280. timestamp=self.min_ago,
  1281. )
  1282. self.store_transaction_metric(
  1283. 100,
  1284. tags={"transaction": "foo_transaction"},
  1285. timestamp=self.min_ago,
  1286. )
  1287. query = {
  1288. "project": [self.project.id],
  1289. "orderby": "p50(transaction.duration)",
  1290. "field": [
  1291. "transaction",
  1292. "environment",
  1293. "p50(transaction.duration)",
  1294. ],
  1295. "query": "!has:environment",
  1296. "statsPeriod": "24h",
  1297. "dataset": "metricsEnhanced",
  1298. "per_page": 50,
  1299. }
  1300. response = self.do_request(query)
  1301. assert response.status_code == 200, response.content
  1302. assert len(response.data["data"]) == 1
  1303. data = response.data["data"]
  1304. meta = response.data["meta"]
  1305. assert data[0]["transaction"] == "foo_transaction"
  1306. assert data[0]["environment"] is None
  1307. assert data[0]["p50(transaction.duration)"] == 100
  1308. assert meta["isMetricsData"]
  1309. def test_has_transaction(self):
  1310. self.store_transaction_metric(
  1311. 1,
  1312. tags={},
  1313. timestamp=self.min_ago,
  1314. )
  1315. self.store_transaction_metric(
  1316. 100,
  1317. tags={"transaction": "foo_transaction"},
  1318. timestamp=self.min_ago,
  1319. )
  1320. query = {
  1321. "project": [self.project.id],
  1322. "orderby": "p50(transaction.duration)",
  1323. "field": [
  1324. "transaction",
  1325. "p50(transaction.duration)",
  1326. ],
  1327. "query": "has:transaction",
  1328. "statsPeriod": "24h",
  1329. "dataset": "metricsEnhanced",
  1330. "per_page": 50,
  1331. }
  1332. response = self.do_request(query)
  1333. assert response.status_code == 200, response.content
  1334. assert len(response.data["data"]) == 2
  1335. data = response.data["data"]
  1336. meta = response.data["meta"]
  1337. assert data[0]["transaction"] == "<< unparameterized >>"
  1338. assert data[0]["p50(transaction.duration)"] == 1
  1339. assert data[1]["transaction"] == "foo_transaction"
  1340. assert data[1]["p50(transaction.duration)"] == 100
  1341. assert meta["isMetricsData"]
  1342. query = {
  1343. "project": [self.project.id],
  1344. "orderby": "p50(transaction.duration)",
  1345. "field": [
  1346. "transaction",
  1347. "p50(transaction.duration)",
  1348. ],
  1349. "query": "!has:transaction",
  1350. "statsPeriod": "24h",
  1351. "dataset": "metricsEnhanced",
  1352. "per_page": 50,
  1353. }
  1354. response = self.do_request(query)
  1355. assert response.status_code == 400, response.content
  1356. def test_apdex_transaction_threshold(self):
  1357. ProjectTransactionThresholdOverride.objects.create(
  1358. transaction="foo_transaction",
  1359. project=self.project,
  1360. organization=self.project.organization,
  1361. threshold=600,
  1362. metric=TransactionMetric.LCP.value,
  1363. )
  1364. ProjectTransactionThresholdOverride.objects.create(
  1365. transaction="bar_transaction",
  1366. project=self.project,
  1367. organization=self.project.organization,
  1368. threshold=600,
  1369. metric=TransactionMetric.LCP.value,
  1370. )
  1371. self.store_transaction_metric(
  1372. 1,
  1373. tags={
  1374. "transaction": "foo_transaction",
  1375. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
  1376. },
  1377. timestamp=self.min_ago,
  1378. )
  1379. self.store_transaction_metric(
  1380. 1,
  1381. "measurements.lcp",
  1382. tags={
  1383. "transaction": "bar_transaction",
  1384. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
  1385. },
  1386. timestamp=self.min_ago,
  1387. )
  1388. response = self.do_request(
  1389. {
  1390. "field": [
  1391. "transaction",
  1392. "apdex()",
  1393. ],
  1394. "orderby": ["apdex()"],
  1395. "query": "event.type:transaction",
  1396. "dataset": "metrics",
  1397. "per_page": 50,
  1398. }
  1399. )
  1400. assert len(response.data["data"]) == 2
  1401. data = response.data["data"]
  1402. meta = response.data["meta"]
  1403. field_meta = meta["fields"]
  1404. assert data[0]["transaction"] == "bar_transaction"
  1405. # Threshold is lcp based
  1406. assert data[0]["apdex()"] == 1
  1407. assert data[1]["transaction"] == "foo_transaction"
  1408. # Threshold is lcp based
  1409. assert data[1]["apdex()"] == 0
  1410. assert meta["isMetricsData"]
  1411. assert field_meta["transaction"] == "string"
  1412. assert field_meta["apdex()"] == "number"
  1413. def test_apdex_project_threshold(self):
  1414. ProjectTransactionThreshold.objects.create(
  1415. project=self.project,
  1416. organization=self.project.organization,
  1417. threshold=600,
  1418. metric=TransactionMetric.LCP.value,
  1419. )
  1420. self.store_transaction_metric(
  1421. 1,
  1422. tags={
  1423. "transaction": "foo_transaction",
  1424. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
  1425. },
  1426. timestamp=self.min_ago,
  1427. )
  1428. self.store_transaction_metric(
  1429. 1,
  1430. "measurements.lcp",
  1431. tags={
  1432. "transaction": "bar_transaction",
  1433. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
  1434. },
  1435. timestamp=self.min_ago,
  1436. )
  1437. response = self.do_request(
  1438. {
  1439. "field": [
  1440. "transaction",
  1441. "apdex()",
  1442. ],
  1443. "orderby": ["apdex()"],
  1444. "query": "event.type:transaction",
  1445. "dataset": "metrics",
  1446. "per_page": 50,
  1447. }
  1448. )
  1449. assert len(response.data["data"]) == 2
  1450. data = response.data["data"]
  1451. meta = response.data["meta"]
  1452. field_meta = meta["fields"]
  1453. assert data[0]["transaction"] == "bar_transaction"
  1454. # Threshold is lcp based
  1455. assert data[0]["apdex()"] == 1
  1456. assert data[1]["transaction"] == "foo_transaction"
  1457. # Threshold is lcp based
  1458. assert data[1]["apdex()"] == 0
  1459. assert meta["isMetricsData"]
  1460. assert field_meta["transaction"] == "string"
  1461. assert field_meta["apdex()"] == "number"
  1462. def test_apdex_satisfaction_param(self):
  1463. for function in ["apdex(300)", "user_misery(300)", "count_miserable(user, 300)"]:
  1464. query = {
  1465. "project": [self.project.id],
  1466. "field": [
  1467. "transaction",
  1468. function,
  1469. ],
  1470. "statsPeriod": "24h",
  1471. "dataset": "metricsEnhanced",
  1472. "per_page": 50,
  1473. }
  1474. response = self.do_request(query)
  1475. assert response.status_code == 200, response.content
  1476. assert len(response.data["data"]) == 0
  1477. meta = response.data["meta"]
  1478. assert not meta["isMetricsData"], function
  1479. query = {
  1480. "project": [self.project.id],
  1481. "field": [
  1482. "transaction",
  1483. function,
  1484. ],
  1485. "statsPeriod": "24h",
  1486. "dataset": "metrics",
  1487. "per_page": 50,
  1488. }
  1489. response = self.do_request(query)
  1490. assert response.status_code == 400, function
  1491. assert b"threshold parameter" in response.content, function
  1492. def test_mobile_metrics(self):
  1493. self.store_transaction_metric(
  1494. 0.4,
  1495. "measurements.frames_frozen_rate",
  1496. tags={
  1497. "transaction": "bar_transaction",
  1498. },
  1499. timestamp=self.min_ago,
  1500. )
  1501. query = {
  1502. "project": [self.project.id],
  1503. "field": [
  1504. "transaction",
  1505. "p50(measurements.frames_frozen_rate)",
  1506. ],
  1507. "statsPeriod": "24h",
  1508. "dataset": "metrics",
  1509. "per_page": 50,
  1510. }
  1511. response = self.do_request(query)
  1512. assert response.status_code == 200, response.content
  1513. assert len(response.data["data"]) == 1
  1514. assert response.data["data"][0]["p50(measurements.frames_frozen_rate)"] == 0.4
  1515. def test_merge_null_unparam(self):
  1516. self.store_transaction_metric(
  1517. 1,
  1518. # Transaction: unparam
  1519. tags={
  1520. "transaction": "<< unparameterized >>",
  1521. },
  1522. timestamp=self.min_ago,
  1523. )
  1524. self.store_transaction_metric(
  1525. 2,
  1526. # Transaction:null
  1527. tags={},
  1528. timestamp=self.min_ago,
  1529. )
  1530. query = {
  1531. "project": [self.project.id],
  1532. "field": [
  1533. "transaction",
  1534. "p50(transaction.duration)",
  1535. ],
  1536. "statsPeriod": "24h",
  1537. "dataset": "metrics",
  1538. "per_page": 50,
  1539. }
  1540. response = self.do_request(query)
  1541. assert response.status_code == 200, response.content
  1542. assert len(response.data["data"]) == 1
  1543. assert response.data["data"][0]["p50(transaction.duration)"] == 1.5