test_organization_events_mep.py 64 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805
  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_duration_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_size_filtering(self):
  1206. self.store_transaction_metric(
  1207. 1,
  1208. metric="measurements.datacenter_memory",
  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.datacenter_memory",
  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["units"]["max(measurements.datacenter_memory)"] == "pebibyte"
  1241. assert meta["fields"]["max(measurements.datacenter_memory)"] == "size"
  1242. assert meta["isMetricsData"]
  1243. def test_environment_param(self):
  1244. self.create_environment(self.project, name="staging")
  1245. self.store_transaction_metric(
  1246. 1,
  1247. tags={"transaction": "foo_transaction", "environment": "staging"},
  1248. timestamp=self.min_ago,
  1249. )
  1250. self.store_transaction_metric(
  1251. 100,
  1252. tags={"transaction": "foo_transaction"},
  1253. timestamp=self.min_ago,
  1254. )
  1255. query = {
  1256. "project": [self.project.id],
  1257. "environment": "staging",
  1258. "orderby": "p50(transaction.duration)",
  1259. "field": [
  1260. "transaction",
  1261. "environment",
  1262. "p50(transaction.duration)",
  1263. ],
  1264. "statsPeriod": "24h",
  1265. "dataset": "metricsEnhanced",
  1266. "per_page": 50,
  1267. }
  1268. response = self.do_request(query)
  1269. assert response.status_code == 200, response.content
  1270. assert len(response.data["data"]) == 1
  1271. data = response.data["data"]
  1272. meta = response.data["meta"]
  1273. assert data[0]["transaction"] == "foo_transaction"
  1274. assert data[0]["environment"] == "staging"
  1275. assert data[0]["p50(transaction.duration)"] == 1
  1276. assert meta["isMetricsData"]
  1277. def test_environment_query(self):
  1278. self.create_environment(self.project, name="staging")
  1279. self.store_transaction_metric(
  1280. 1,
  1281. tags={"transaction": "foo_transaction", "environment": "staging"},
  1282. timestamp=self.min_ago,
  1283. )
  1284. self.store_transaction_metric(
  1285. 100,
  1286. tags={"transaction": "foo_transaction"},
  1287. timestamp=self.min_ago,
  1288. )
  1289. query = {
  1290. "project": [self.project.id],
  1291. "orderby": "p50(transaction.duration)",
  1292. "field": [
  1293. "transaction",
  1294. "environment",
  1295. "p50(transaction.duration)",
  1296. ],
  1297. "query": "!has:environment",
  1298. "statsPeriod": "24h",
  1299. "dataset": "metricsEnhanced",
  1300. "per_page": 50,
  1301. }
  1302. response = self.do_request(query)
  1303. assert response.status_code == 200, response.content
  1304. assert len(response.data["data"]) == 1
  1305. data = response.data["data"]
  1306. meta = response.data["meta"]
  1307. assert data[0]["transaction"] == "foo_transaction"
  1308. assert data[0]["environment"] is None
  1309. assert data[0]["p50(transaction.duration)"] == 100
  1310. assert meta["isMetricsData"]
  1311. def test_has_transaction(self):
  1312. self.store_transaction_metric(
  1313. 1,
  1314. tags={},
  1315. timestamp=self.min_ago,
  1316. )
  1317. self.store_transaction_metric(
  1318. 100,
  1319. tags={"transaction": "foo_transaction"},
  1320. timestamp=self.min_ago,
  1321. )
  1322. query = {
  1323. "project": [self.project.id],
  1324. "orderby": "p50(transaction.duration)",
  1325. "field": [
  1326. "transaction",
  1327. "p50(transaction.duration)",
  1328. ],
  1329. "query": "has:transaction",
  1330. "statsPeriod": "24h",
  1331. "dataset": "metricsEnhanced",
  1332. "per_page": 50,
  1333. }
  1334. response = self.do_request(query)
  1335. assert response.status_code == 200, response.content
  1336. assert len(response.data["data"]) == 2
  1337. data = response.data["data"]
  1338. meta = response.data["meta"]
  1339. assert data[0]["transaction"] == "<< unparameterized >>"
  1340. assert data[0]["p50(transaction.duration)"] == 1
  1341. assert data[1]["transaction"] == "foo_transaction"
  1342. assert data[1]["p50(transaction.duration)"] == 100
  1343. assert meta["isMetricsData"]
  1344. query = {
  1345. "project": [self.project.id],
  1346. "orderby": "p50(transaction.duration)",
  1347. "field": [
  1348. "transaction",
  1349. "p50(transaction.duration)",
  1350. ],
  1351. "query": "!has:transaction",
  1352. "statsPeriod": "24h",
  1353. "dataset": "metricsEnhanced",
  1354. "per_page": 50,
  1355. }
  1356. response = self.do_request(query)
  1357. assert response.status_code == 400, response.content
  1358. def test_apdex_transaction_threshold(self):
  1359. ProjectTransactionThresholdOverride.objects.create(
  1360. transaction="foo_transaction",
  1361. project=self.project,
  1362. organization=self.project.organization,
  1363. threshold=600,
  1364. metric=TransactionMetric.LCP.value,
  1365. )
  1366. ProjectTransactionThresholdOverride.objects.create(
  1367. transaction="bar_transaction",
  1368. project=self.project,
  1369. organization=self.project.organization,
  1370. threshold=600,
  1371. metric=TransactionMetric.LCP.value,
  1372. )
  1373. self.store_transaction_metric(
  1374. 1,
  1375. tags={
  1376. "transaction": "foo_transaction",
  1377. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
  1378. },
  1379. timestamp=self.min_ago,
  1380. )
  1381. self.store_transaction_metric(
  1382. 1,
  1383. "measurements.lcp",
  1384. tags={
  1385. "transaction": "bar_transaction",
  1386. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
  1387. },
  1388. timestamp=self.min_ago,
  1389. )
  1390. response = self.do_request(
  1391. {
  1392. "field": [
  1393. "transaction",
  1394. "apdex()",
  1395. ],
  1396. "orderby": ["apdex()"],
  1397. "query": "event.type:transaction",
  1398. "dataset": "metrics",
  1399. "per_page": 50,
  1400. }
  1401. )
  1402. assert len(response.data["data"]) == 2
  1403. data = response.data["data"]
  1404. meta = response.data["meta"]
  1405. field_meta = meta["fields"]
  1406. assert data[0]["transaction"] == "bar_transaction"
  1407. # Threshold is lcp based
  1408. assert data[0]["apdex()"] == 1
  1409. assert data[1]["transaction"] == "foo_transaction"
  1410. # Threshold is lcp based
  1411. assert data[1]["apdex()"] == 0
  1412. assert meta["isMetricsData"]
  1413. assert field_meta["transaction"] == "string"
  1414. assert field_meta["apdex()"] == "number"
  1415. def test_apdex_project_threshold(self):
  1416. ProjectTransactionThreshold.objects.create(
  1417. project=self.project,
  1418. organization=self.project.organization,
  1419. threshold=600,
  1420. metric=TransactionMetric.LCP.value,
  1421. )
  1422. self.store_transaction_metric(
  1423. 1,
  1424. tags={
  1425. "transaction": "foo_transaction",
  1426. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
  1427. },
  1428. timestamp=self.min_ago,
  1429. )
  1430. self.store_transaction_metric(
  1431. 1,
  1432. "measurements.lcp",
  1433. tags={
  1434. "transaction": "bar_transaction",
  1435. constants.METRIC_SATISFACTION_TAG_KEY: constants.METRIC_SATISFIED_TAG_VALUE,
  1436. },
  1437. timestamp=self.min_ago,
  1438. )
  1439. response = self.do_request(
  1440. {
  1441. "field": [
  1442. "transaction",
  1443. "apdex()",
  1444. ],
  1445. "orderby": ["apdex()"],
  1446. "query": "event.type:transaction",
  1447. "dataset": "metrics",
  1448. "per_page": 50,
  1449. }
  1450. )
  1451. assert len(response.data["data"]) == 2
  1452. data = response.data["data"]
  1453. meta = response.data["meta"]
  1454. field_meta = meta["fields"]
  1455. assert data[0]["transaction"] == "bar_transaction"
  1456. # Threshold is lcp based
  1457. assert data[0]["apdex()"] == 1
  1458. assert data[1]["transaction"] == "foo_transaction"
  1459. # Threshold is lcp based
  1460. assert data[1]["apdex()"] == 0
  1461. assert meta["isMetricsData"]
  1462. assert field_meta["transaction"] == "string"
  1463. assert field_meta["apdex()"] == "number"
  1464. def test_apdex_satisfaction_param(self):
  1465. for function in ["apdex(300)", "user_misery(300)", "count_miserable(user, 300)"]:
  1466. query = {
  1467. "project": [self.project.id],
  1468. "field": [
  1469. "transaction",
  1470. function,
  1471. ],
  1472. "statsPeriod": "24h",
  1473. "dataset": "metricsEnhanced",
  1474. "per_page": 50,
  1475. }
  1476. response = self.do_request(query)
  1477. assert response.status_code == 200, response.content
  1478. assert len(response.data["data"]) == 0
  1479. meta = response.data["meta"]
  1480. assert not meta["isMetricsData"], function
  1481. query = {
  1482. "project": [self.project.id],
  1483. "field": [
  1484. "transaction",
  1485. function,
  1486. ],
  1487. "statsPeriod": "24h",
  1488. "dataset": "metrics",
  1489. "per_page": 50,
  1490. }
  1491. response = self.do_request(query)
  1492. assert response.status_code == 400, function
  1493. assert b"threshold parameter" in response.content, function
  1494. def test_mobile_metrics(self):
  1495. self.store_transaction_metric(
  1496. 0.4,
  1497. "measurements.frames_frozen_rate",
  1498. tags={
  1499. "transaction": "bar_transaction",
  1500. },
  1501. timestamp=self.min_ago,
  1502. )
  1503. query = {
  1504. "project": [self.project.id],
  1505. "field": [
  1506. "transaction",
  1507. "p50(measurements.frames_frozen_rate)",
  1508. ],
  1509. "statsPeriod": "24h",
  1510. "dataset": "metrics",
  1511. "per_page": 50,
  1512. }
  1513. response = self.do_request(query)
  1514. assert response.status_code == 200, response.content
  1515. assert len(response.data["data"]) == 1
  1516. assert response.data["data"][0]["p50(measurements.frames_frozen_rate)"] == 0.4
  1517. def test_merge_null_unparam(self):
  1518. self.store_transaction_metric(
  1519. 1,
  1520. # Transaction: unparam
  1521. tags={
  1522. "transaction": "<< unparameterized >>",
  1523. },
  1524. timestamp=self.min_ago,
  1525. )
  1526. self.store_transaction_metric(
  1527. 2,
  1528. # Transaction:null
  1529. tags={},
  1530. timestamp=self.min_ago,
  1531. )
  1532. query = {
  1533. "project": [self.project.id],
  1534. "field": [
  1535. "transaction",
  1536. "p50(transaction.duration)",
  1537. ],
  1538. "statsPeriod": "24h",
  1539. "dataset": "metrics",
  1540. "per_page": 50,
  1541. }
  1542. response = self.do_request(query)
  1543. assert response.status_code == 200, response.content
  1544. assert len(response.data["data"]) == 1
  1545. assert response.data["data"][0]["p50(transaction.duration)"] == 1.5
  1546. def test_custom_measurements_without_function(self):
  1547. self.store_transaction_metric(
  1548. 33,
  1549. metric="measurements.datacenter_memory",
  1550. internal_metric="d:transactions/measurements.datacenter_memory@pebibyte",
  1551. entity="metrics_distributions",
  1552. tags={"transaction": "foo_transaction"},
  1553. timestamp=self.min_ago,
  1554. )
  1555. transaction_data = load_data("transaction", timestamp=self.min_ago)
  1556. transaction_data["measurements"]["datacenter_memory"] = {
  1557. "value": 33,
  1558. "unit": "pebibyte",
  1559. }
  1560. self.store_event(transaction_data, self.project.id)
  1561. measurement = "measurements.datacenter_memory"
  1562. response = self.do_request(
  1563. {
  1564. "field": ["transaction", measurement],
  1565. "query": "",
  1566. "dataset": "discover",
  1567. }
  1568. )
  1569. assert response.status_code == 200, response.content
  1570. data = response.data["data"]
  1571. assert len(data) == 1
  1572. assert data[0][measurement] == 33
  1573. meta = response.data["meta"]
  1574. field_meta = meta["fields"]
  1575. unit_meta = meta["units"]
  1576. assert field_meta[measurement] == "size"
  1577. assert unit_meta[measurement] == "pebibyte"
  1578. assert not meta["isMetricsData"]
  1579. def test_custom_measurements_with_function(self):
  1580. self.store_transaction_metric(
  1581. 33,
  1582. metric="measurements.datacenter_memory",
  1583. internal_metric="d:transactions/measurements.datacenter_memory@pebibyte",
  1584. entity="metrics_distributions",
  1585. tags={"transaction": "foo_transaction"},
  1586. timestamp=self.min_ago,
  1587. )
  1588. transaction_data = load_data("transaction", timestamp=self.min_ago)
  1589. transaction_data["measurements"]["datacenter_memory"] = {
  1590. "value": 33,
  1591. "unit": "pebibyte",
  1592. }
  1593. self.store_event(transaction_data, self.project.id)
  1594. measurement = "p50(measurements.datacenter_memory)"
  1595. response = self.do_request(
  1596. {
  1597. "field": ["transaction", measurement],
  1598. "query": "",
  1599. "dataset": "discover",
  1600. }
  1601. )
  1602. assert response.status_code == 200, response.content
  1603. data = response.data["data"]
  1604. assert len(data) == 1
  1605. assert data[0][measurement] == 33
  1606. meta = response.data["meta"]
  1607. field_meta = meta["fields"]
  1608. unit_meta = meta["units"]
  1609. assert field_meta[measurement] == "size"
  1610. assert unit_meta[measurement] == "pebibyte"
  1611. assert not meta["isMetricsData"]