test_organization_events_span_metrics.py 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149
  1. import pytest
  2. from django.urls import reverse
  3. from sentry.search.events import constants
  4. from sentry.search.utils import map_device_class_level
  5. from sentry.testutils.cases import MetricsEnhancedPerformanceTestCase
  6. from sentry.testutils.helpers.datetime import before_now
  7. pytestmark = pytest.mark.sentry_metrics
  8. class OrganizationEventsMetricsEnhancedPerformanceEndpointTest(MetricsEnhancedPerformanceTestCase):
  9. viewname = "sentry-api-0-organization-events"
  10. # Poor intentionally omitted for test_measurement_rating_that_does_not_exist
  11. METRIC_STRINGS = [
  12. "foo_transaction",
  13. "bar_transaction",
  14. ]
  15. def setUp(self):
  16. super().setUp()
  17. self.min_ago = before_now(minutes=1)
  18. self.six_min_ago = before_now(minutes=6)
  19. self.three_days_ago = before_now(days=3)
  20. self.features = {
  21. "organizations:starfish-view": True,
  22. }
  23. def do_request(self, query, features=None):
  24. if features is None:
  25. features = {"organizations:discover-basic": True}
  26. features.update(self.features)
  27. self.login_as(user=self.user)
  28. url = reverse(
  29. self.viewname,
  30. kwargs={"organization_slug": self.organization.slug},
  31. )
  32. with self.feature(features):
  33. return self.client.get(url, query, format="json")
  34. def test_p50_with_no_data(self):
  35. response = self.do_request(
  36. {
  37. "field": ["p50()"],
  38. "query": "",
  39. "project": self.project.id,
  40. "dataset": "spansMetrics",
  41. }
  42. )
  43. assert response.status_code == 200, response.content
  44. data = response.data["data"]
  45. meta = response.data["meta"]
  46. assert len(data) == 1
  47. assert data[0]["p50()"] == 0
  48. assert meta["dataset"] == "spansMetrics"
  49. def test_count(self):
  50. self.store_span_metric(
  51. 1,
  52. internal_metric=constants.SELF_TIME_LIGHT,
  53. timestamp=self.three_days_ago,
  54. )
  55. response = self.do_request(
  56. {
  57. "field": ["count()"],
  58. "query": "",
  59. "project": self.project.id,
  60. "dataset": "spansMetrics",
  61. "statsPeriod": "7d",
  62. }
  63. )
  64. assert response.status_code == 200, response.content
  65. data = response.data["data"]
  66. meta = response.data["meta"]
  67. assert len(data) == 1
  68. assert data[0]["count()"] == 1
  69. assert meta["dataset"] == "spansMetrics"
  70. def test_count_unique(self):
  71. self.store_span_metric(
  72. 1,
  73. "user",
  74. timestamp=self.min_ago,
  75. )
  76. self.store_span_metric(
  77. 2,
  78. "user",
  79. timestamp=self.min_ago,
  80. )
  81. response = self.do_request(
  82. {
  83. "field": ["count_unique(user)"],
  84. "query": "",
  85. "project": self.project.id,
  86. "dataset": "spansMetrics",
  87. }
  88. )
  89. assert response.status_code == 200, response.content
  90. data = response.data["data"]
  91. meta = response.data["meta"]
  92. assert len(data) == 1
  93. assert data[0]["count_unique(user)"] == 2
  94. assert meta["dataset"] == "spansMetrics"
  95. def test_sum(self):
  96. self.store_span_metric(
  97. 321,
  98. internal_metric=constants.SELF_TIME_LIGHT,
  99. timestamp=self.min_ago,
  100. )
  101. self.store_span_metric(
  102. 99,
  103. internal_metric=constants.SELF_TIME_LIGHT,
  104. timestamp=self.min_ago,
  105. )
  106. response = self.do_request(
  107. {
  108. "field": ["sum(span.self_time)"],
  109. "query": "",
  110. "project": self.project.id,
  111. "dataset": "spansMetrics",
  112. }
  113. )
  114. assert response.status_code == 200, response.content
  115. data = response.data["data"]
  116. meta = response.data["meta"]
  117. assert len(data) == 1
  118. assert data[0]["sum(span.self_time)"] == 420
  119. assert meta["dataset"] == "spansMetrics"
  120. def test_percentile(self):
  121. self.store_span_metric(
  122. 1,
  123. internal_metric=constants.SELF_TIME_LIGHT,
  124. timestamp=self.min_ago,
  125. )
  126. response = self.do_request(
  127. {
  128. "field": ["percentile(span.self_time, 0.95)"],
  129. "query": "",
  130. "project": self.project.id,
  131. "dataset": "spansMetrics",
  132. }
  133. )
  134. assert response.status_code == 200, response.content
  135. data = response.data["data"]
  136. meta = response.data["meta"]
  137. assert len(data) == 1
  138. assert data[0]["percentile(span.self_time, 0.95)"] == 1
  139. assert meta["dataset"] == "spansMetrics"
  140. def test_fixed_percentile_functions(self):
  141. self.store_span_metric(
  142. 1,
  143. internal_metric=constants.SELF_TIME_LIGHT,
  144. timestamp=self.min_ago,
  145. )
  146. for function in ["p50()", "p75()", "p95()", "p99()", "p100()"]:
  147. response = self.do_request(
  148. {
  149. "field": [function],
  150. "query": "",
  151. "project": self.project.id,
  152. "dataset": "spansMetrics",
  153. }
  154. )
  155. assert response.status_code == 200, response.content
  156. data = response.data["data"]
  157. meta = response.data["meta"]
  158. assert len(data) == 1
  159. assert data[0][function] == 1, function
  160. assert meta["dataset"] == "spansMetrics", function
  161. assert meta["fields"][function] == "duration", function
  162. def test_fixed_percentile_functions_with_duration(self):
  163. self.store_span_metric(
  164. 1,
  165. internal_metric=constants.SPAN_METRICS_MAP["span.duration"],
  166. timestamp=self.min_ago,
  167. )
  168. for function in [
  169. "p50(span.duration)",
  170. "p75(span.duration)",
  171. "p95(span.duration)",
  172. "p99(span.duration)",
  173. "p100(span.duration)",
  174. ]:
  175. response = self.do_request(
  176. {
  177. "field": [function],
  178. "query": "",
  179. "project": self.project.id,
  180. "dataset": "spansMetrics",
  181. }
  182. )
  183. assert response.status_code == 200, response.content
  184. data = response.data["data"]
  185. meta = response.data["meta"]
  186. assert len(data) == 1, function
  187. assert data[0][function] == 1, function
  188. assert meta["dataset"] == "spansMetrics", function
  189. assert meta["fields"][function] == "duration", function
  190. def test_avg(self):
  191. self.store_span_metric(
  192. 1,
  193. internal_metric=constants.SELF_TIME_LIGHT,
  194. timestamp=self.min_ago,
  195. )
  196. response = self.do_request(
  197. {
  198. "field": ["avg()"],
  199. "query": "",
  200. "project": self.project.id,
  201. "dataset": "spansMetrics",
  202. }
  203. )
  204. assert response.status_code == 200, response.content
  205. data = response.data["data"]
  206. meta = response.data["meta"]
  207. assert len(data) == 1
  208. assert data[0]["avg()"] == 1
  209. assert meta["dataset"] == "spansMetrics"
  210. def test_eps(self):
  211. for _ in range(6):
  212. self.store_span_metric(
  213. 1,
  214. internal_metric=constants.SELF_TIME_LIGHT,
  215. timestamp=self.min_ago,
  216. )
  217. response = self.do_request(
  218. {
  219. "field": ["eps()", "sps()"],
  220. "query": "",
  221. "project": self.project.id,
  222. "dataset": "spansMetrics",
  223. "statsPeriod": "10m",
  224. }
  225. )
  226. assert response.status_code == 200, response.content
  227. data = response.data["data"]
  228. meta = response.data["meta"]
  229. assert len(data) == 1
  230. assert data[0]["eps()"] == 0.01
  231. assert data[0]["sps()"] == 0.01
  232. assert meta["fields"]["eps()"] == "rate"
  233. assert meta["fields"]["sps()"] == "rate"
  234. assert meta["units"]["eps()"] == "1/second"
  235. assert meta["units"]["sps()"] == "1/second"
  236. assert meta["dataset"] == "spansMetrics"
  237. def test_epm(self):
  238. for _ in range(6):
  239. self.store_span_metric(
  240. 1,
  241. internal_metric=constants.SELF_TIME_LIGHT,
  242. timestamp=self.min_ago,
  243. )
  244. response = self.do_request(
  245. {
  246. "field": ["epm()", "spm()"],
  247. "query": "",
  248. "project": self.project.id,
  249. "dataset": "spansMetrics",
  250. "statsPeriod": "10m",
  251. }
  252. )
  253. assert response.status_code == 200, response.content
  254. data = response.data["data"]
  255. meta = response.data["meta"]
  256. assert len(data) == 1
  257. assert data[0]["epm()"] == 0.6
  258. assert data[0]["spm()"] == 0.6
  259. assert meta["fields"]["epm()"] == "rate"
  260. assert meta["fields"]["spm()"] == "rate"
  261. assert meta["units"]["epm()"] == "1/minute"
  262. assert meta["units"]["spm()"] == "1/minute"
  263. assert meta["dataset"] == "spansMetrics"
  264. def test_time_spent_percentage(self):
  265. for _ in range(4):
  266. self.store_span_metric(
  267. 1,
  268. internal_metric=constants.SELF_TIME_LIGHT,
  269. tags={"transaction": "foo_transaction"},
  270. timestamp=self.min_ago,
  271. )
  272. self.store_span_metric(
  273. 1,
  274. tags={"transaction": "foo_transaction"},
  275. timestamp=self.min_ago,
  276. )
  277. self.store_span_metric(
  278. 1,
  279. internal_metric=constants.SELF_TIME_LIGHT,
  280. tags={"transaction": "bar_transaction"},
  281. timestamp=self.min_ago,
  282. )
  283. self.store_span_metric(
  284. 1,
  285. tags={"transaction": "bar_transaction"},
  286. timestamp=self.min_ago,
  287. )
  288. response = self.do_request(
  289. {
  290. "field": ["transaction", "time_spent_percentage()"],
  291. "query": "",
  292. "orderby": ["-time_spent_percentage()"],
  293. "project": self.project.id,
  294. "dataset": "spansMetrics",
  295. "statsPeriod": "10m",
  296. }
  297. )
  298. assert response.status_code == 200, response.content
  299. data = response.data["data"]
  300. meta = response.data["meta"]
  301. assert len(data) == 2
  302. assert data[0]["time_spent_percentage()"] == 0.8
  303. assert data[0]["transaction"] == "foo_transaction"
  304. assert data[1]["time_spent_percentage()"] == 0.2
  305. assert data[1]["transaction"] == "bar_transaction"
  306. assert meta["dataset"] == "spansMetrics"
  307. def test_time_spent_percentage_local(self):
  308. response = self.do_request(
  309. {
  310. "field": ["time_spent_percentage(local)"],
  311. "query": "",
  312. "orderby": ["-time_spent_percentage(local)"],
  313. "project": self.project.id,
  314. "dataset": "spansMetrics",
  315. "statsPeriod": "10m",
  316. }
  317. )
  318. assert response.status_code == 200, response.content
  319. data = response.data["data"]
  320. meta = response.data["meta"]
  321. assert len(data) == 1
  322. assert data[0]["time_spent_percentage(local)"] is None
  323. assert meta["dataset"] == "spansMetrics"
  324. def test_http_error_rate_and_count(self):
  325. for _ in range(4):
  326. self.store_span_metric(
  327. 1,
  328. internal_metric=constants.SELF_TIME_LIGHT,
  329. tags={"span.status_code": "500"},
  330. timestamp=self.min_ago,
  331. )
  332. self.store_span_metric(
  333. 1,
  334. internal_metric=constants.SELF_TIME_LIGHT,
  335. tags={"span.status_code": "200"},
  336. timestamp=self.min_ago,
  337. )
  338. response = self.do_request(
  339. {
  340. "field": ["http_error_count()", "http_error_rate()"],
  341. "query": "",
  342. "orderby": ["-http_error_rate()"],
  343. "project": self.project.id,
  344. "dataset": "spansMetrics",
  345. "statsPeriod": "10m",
  346. }
  347. )
  348. assert response.status_code == 200, response.content
  349. data = response.data["data"]
  350. meta = response.data["meta"]
  351. assert len(data) == 1
  352. assert data[0]["http_error_rate()"] == 0.8
  353. assert meta["dataset"] == "spansMetrics"
  354. assert meta["fields"]["http_error_count()"] == "integer"
  355. assert meta["fields"]["http_error_rate()"] == "percentage"
  356. def test_ttid_rate_and_count(self):
  357. for _ in range(8):
  358. self.store_span_metric(
  359. 1,
  360. internal_metric=constants.SELF_TIME_LIGHT,
  361. tags={"ttid": "ttid", "ttfd": "ttfd"},
  362. timestamp=self.min_ago,
  363. )
  364. self.store_span_metric(
  365. 1,
  366. internal_metric=constants.SELF_TIME_LIGHT,
  367. tags={"ttfd": "ttfd", "ttid": ""},
  368. timestamp=self.min_ago,
  369. )
  370. self.store_span_metric(
  371. 1,
  372. internal_metric=constants.SELF_TIME_LIGHT,
  373. tags={"ttfd": "", "ttid": ""},
  374. timestamp=self.min_ago,
  375. )
  376. response = self.do_request(
  377. {
  378. "field": [
  379. "ttid_contribution_rate()",
  380. "ttid_count()",
  381. "ttfd_contribution_rate()",
  382. "ttfd_count()",
  383. ],
  384. "query": "",
  385. "orderby": ["-ttid_contribution_rate()"],
  386. "project": self.project.id,
  387. "dataset": "spansMetrics",
  388. "statsPeriod": "10m",
  389. }
  390. )
  391. assert response.status_code == 200, response.content
  392. data = response.data["data"]
  393. meta = response.data["meta"]
  394. assert len(data) == 1
  395. assert data[0]["ttid_contribution_rate()"] == 0.8
  396. assert data[0]["ttid_count()"] == 8
  397. assert data[0]["ttfd_contribution_rate()"] == 0.9
  398. assert data[0]["ttfd_count()"] == 9
  399. assert meta["dataset"] == "spansMetrics"
  400. assert meta["fields"]["ttid_count()"] == "integer"
  401. assert meta["fields"]["ttid_contribution_rate()"] == "percentage"
  402. assert meta["fields"]["ttfd_count()"] == "integer"
  403. assert meta["fields"]["ttfd_contribution_rate()"] == "percentage"
  404. def test_main_thread_count(self):
  405. for _ in range(8):
  406. self.store_span_metric(
  407. 1,
  408. internal_metric=constants.SELF_TIME_LIGHT,
  409. tags={"span.main_thread": "true"},
  410. timestamp=self.min_ago,
  411. )
  412. self.store_span_metric(
  413. 1,
  414. internal_metric=constants.SELF_TIME_LIGHT,
  415. tags={},
  416. timestamp=self.min_ago,
  417. )
  418. self.store_span_metric(
  419. 1,
  420. internal_metric=constants.SELF_TIME_LIGHT,
  421. tags={"span.main_thread": ""},
  422. timestamp=self.min_ago,
  423. )
  424. response = self.do_request(
  425. {
  426. "field": [
  427. "main_thread_count()",
  428. ],
  429. "query": "",
  430. "orderby": ["-main_thread_count()"],
  431. "project": self.project.id,
  432. "dataset": "spansMetrics",
  433. "statsPeriod": "10m",
  434. }
  435. )
  436. assert response.status_code == 200, response.content
  437. data = response.data["data"]
  438. meta = response.data["meta"]
  439. assert len(data) == 1
  440. assert data[0]["main_thread_count()"] == 8
  441. assert meta["dataset"] == "spansMetrics"
  442. assert meta["fields"]["main_thread_count()"] == "integer"
  443. def test_use_self_time_light(self):
  444. self.store_span_metric(
  445. 100,
  446. internal_metric=constants.SELF_TIME_LIGHT,
  447. tags={"transaction": "foo_transaction"},
  448. timestamp=self.min_ago,
  449. )
  450. response = self.do_request(
  451. {
  452. "field": ["p50(span.self_time)"],
  453. # Should be 0 since its filtering on transaction
  454. "query": "transaction:foo_transaction",
  455. "orderby": ["-p50(span.self_time)"],
  456. "project": self.project.id,
  457. "dataset": "spansMetrics",
  458. "statsPeriod": "10m",
  459. }
  460. )
  461. assert response.status_code == 200, response.content
  462. data = response.data["data"]
  463. meta = response.data["meta"]
  464. assert len(data) == 1
  465. assert data[0]["p50(span.self_time)"] == 0
  466. assert meta["dataset"] == "spansMetrics"
  467. assert meta["fields"]["p50(span.self_time)"] == "duration"
  468. response = self.do_request(
  469. {
  470. # Should be 0 since it has a transaction column
  471. "field": ["transaction", "p50(span.self_time)"],
  472. "query": "",
  473. "orderby": ["-p50(span.self_time)"],
  474. "project": self.project.id,
  475. "dataset": "spansMetrics",
  476. "statsPeriod": "10m",
  477. }
  478. )
  479. assert response.status_code == 200, response.content
  480. data = response.data["data"]
  481. meta = response.data["meta"]
  482. assert len(data) == 0
  483. response = self.do_request(
  484. {
  485. "field": ["p50(span.self_time)"],
  486. # Should be 100 since its not filtering on transaction
  487. "query": "",
  488. "orderby": ["-p50(span.self_time)"],
  489. "project": self.project.id,
  490. "dataset": "spansMetrics",
  491. "statsPeriod": "10m",
  492. }
  493. )
  494. assert response.status_code == 200, response.content
  495. data = response.data["data"]
  496. meta = response.data["meta"]
  497. assert len(data) == 1
  498. assert data[0]["p50(span.self_time)"] == 100
  499. assert meta["dataset"] == "spansMetrics"
  500. assert meta["fields"]["p50(span.self_time)"] == "duration"
  501. def test_span_module(self):
  502. self.store_span_metric(
  503. 1,
  504. internal_metric=constants.SELF_TIME_LIGHT,
  505. timestamp=self.six_min_ago,
  506. tags={"span.category": "http", "span.description": "f"},
  507. )
  508. self.store_span_metric(
  509. 3,
  510. internal_metric=constants.SELF_TIME_LIGHT,
  511. timestamp=self.six_min_ago,
  512. tags={"span.category": "db", "span.description": "e"},
  513. )
  514. self.store_span_metric(
  515. 5,
  516. internal_metric=constants.SELF_TIME_LIGHT,
  517. timestamp=self.six_min_ago,
  518. tags={"span.category": "foobar", "span.description": "d"},
  519. )
  520. self.store_span_metric(
  521. 7,
  522. internal_metric=constants.SELF_TIME_LIGHT,
  523. timestamp=self.six_min_ago,
  524. tags={"span.category": "cache", "span.description": "c"},
  525. )
  526. self.store_span_metric(
  527. 9,
  528. internal_metric=constants.SELF_TIME_LIGHT,
  529. timestamp=self.six_min_ago,
  530. tags={"span.category": "db", "span.op": "db.redis", "span.description": "b"},
  531. )
  532. self.store_span_metric(
  533. 11,
  534. internal_metric=constants.SELF_TIME_LIGHT,
  535. timestamp=self.six_min_ago,
  536. tags={"span.category": "db", "span.op": "db.sql.room", "span.description": "a"},
  537. )
  538. response = self.do_request(
  539. {
  540. "field": ["span.module", "span.description", "p50(span.self_time)"],
  541. "query": "",
  542. "orderby": ["-p50(span.self_time)"],
  543. "project": self.project.id,
  544. "dataset": "spansMetrics",
  545. "statsPeriod": "10m",
  546. }
  547. )
  548. assert response.status_code == 200, response.content
  549. data = response.data["data"]
  550. meta = response.data["meta"]
  551. assert len(data) == 6
  552. assert data[0]["p50(span.self_time)"] == 11
  553. assert data[0]["span.module"] == "other"
  554. assert data[0]["span.description"] == "a"
  555. assert data[1]["p50(span.self_time)"] == 9
  556. assert data[1]["span.module"] == "cache"
  557. assert data[1]["span.description"] == "b"
  558. assert data[2]["p50(span.self_time)"] == 7
  559. assert data[2]["span.module"] == "cache"
  560. assert data[2]["span.description"] == "c"
  561. assert data[3]["p50(span.self_time)"] == 5
  562. assert data[3]["span.module"] == "other"
  563. assert data[3]["span.description"] == "d"
  564. assert data[4]["p50(span.self_time)"] == 3
  565. assert data[4]["span.module"] == "db"
  566. assert data[4]["span.description"] == "e"
  567. assert data[5]["p50(span.self_time)"] == 1
  568. assert data[5]["span.module"] == "http"
  569. assert data[5]["span.description"] == "f"
  570. assert meta["dataset"] == "spansMetrics"
  571. assert meta["fields"]["p50(span.self_time)"] == "duration"
  572. def test_tag_search(self):
  573. self.store_span_metric(
  574. 321,
  575. internal_metric=constants.SELF_TIME_LIGHT,
  576. timestamp=self.min_ago,
  577. tags={"span.description": "foo"},
  578. )
  579. self.store_span_metric(
  580. 99,
  581. internal_metric=constants.SELF_TIME_LIGHT,
  582. timestamp=self.min_ago,
  583. tags={"span.description": "bar"},
  584. )
  585. response = self.do_request(
  586. {
  587. "field": ["sum(span.self_time)"],
  588. "query": "span.description:bar",
  589. "project": self.project.id,
  590. "dataset": "spansMetrics",
  591. }
  592. )
  593. assert response.status_code == 200, response.content
  594. data = response.data["data"]
  595. meta = response.data["meta"]
  596. assert len(data) == 1
  597. assert data[0]["sum(span.self_time)"] == 99
  598. assert meta["dataset"] == "spansMetrics"
  599. def test_free_text_search(self):
  600. self.store_span_metric(
  601. 321,
  602. internal_metric=constants.SELF_TIME_LIGHT,
  603. timestamp=self.min_ago,
  604. tags={"span.description": "foo"},
  605. )
  606. self.store_span_metric(
  607. 99,
  608. internal_metric=constants.SELF_TIME_LIGHT,
  609. timestamp=self.min_ago,
  610. tags={"span.description": "bar"},
  611. )
  612. response = self.do_request(
  613. {
  614. "field": ["sum(span.self_time)"],
  615. "query": "foo",
  616. "project": self.project.id,
  617. "dataset": "spansMetrics",
  618. }
  619. )
  620. assert response.status_code == 200, response.content
  621. data = response.data["data"]
  622. meta = response.data["meta"]
  623. assert len(data) == 1
  624. assert data[0]["sum(span.self_time)"] == 321
  625. assert meta["dataset"] == "spansMetrics"
  626. def test_avg_compare(self):
  627. self.store_span_metric(
  628. 100,
  629. internal_metric=constants.SELF_TIME_LIGHT,
  630. timestamp=self.min_ago,
  631. tags={"release": "foo"},
  632. )
  633. self.store_span_metric(
  634. 10,
  635. internal_metric=constants.SELF_TIME_LIGHT,
  636. timestamp=self.min_ago,
  637. tags={"release": "bar"},
  638. )
  639. for function_name in [
  640. "avg_compare(span.self_time, release, foo, bar)",
  641. 'avg_compare(span.self_time, release, "foo", "bar")',
  642. ]:
  643. response = self.do_request(
  644. {
  645. "field": [function_name],
  646. "query": "",
  647. "project": self.project.id,
  648. "dataset": "spansMetrics",
  649. }
  650. )
  651. assert response.status_code == 200, response.content
  652. data = response.data["data"]
  653. meta = response.data["meta"]
  654. assert len(data) == 1
  655. assert data[0][function_name] == -0.9
  656. assert meta["dataset"] == "spansMetrics"
  657. assert meta["fields"][function_name] == "percent_change"
  658. def test_avg_compare_invalid_column(self):
  659. response = self.do_request(
  660. {
  661. "field": ["avg_compare(span.self_time, transaction, foo, bar)"],
  662. "query": "",
  663. "project": self.project.id,
  664. "dataset": "spansMetrics",
  665. }
  666. )
  667. assert response.status_code == 400, response.content
  668. def test_span_domain_array(self):
  669. self.store_span_metric(
  670. 321,
  671. internal_metric=constants.SELF_TIME_LIGHT,
  672. timestamp=self.min_ago,
  673. tags={"span.domain": ",sentry_table1,"},
  674. )
  675. self.store_span_metric(
  676. 21,
  677. internal_metric=constants.SELF_TIME_LIGHT,
  678. timestamp=self.min_ago,
  679. tags={"span.domain": ",sentry_table1,sentry_table2,"},
  680. )
  681. response = self.do_request(
  682. {
  683. "field": ["span.domain", "p75(span.self_time)"],
  684. "query": "",
  685. "project": self.project.id,
  686. "orderby": ["-p75(span.self_time)"],
  687. "dataset": "spansMetrics",
  688. }
  689. )
  690. assert response.status_code == 200, response.content
  691. data = response.data["data"]
  692. meta = response.data["meta"]
  693. assert len(data) == 2
  694. assert data[0]["span.domain"] == ["sentry_table1"]
  695. assert data[1]["span.domain"] == ["sentry_table1", "sentry_table2"]
  696. assert meta["dataset"] == "spansMetrics"
  697. assert meta["fields"]["span.domain"] == "array"
  698. def test_span_domain_array_filter(self):
  699. self.store_span_metric(
  700. 321,
  701. internal_metric=constants.SELF_TIME_LIGHT,
  702. timestamp=self.min_ago,
  703. tags={"span.domain": ",sentry_table1,"},
  704. )
  705. self.store_span_metric(
  706. 21,
  707. internal_metric=constants.SELF_TIME_LIGHT,
  708. timestamp=self.min_ago,
  709. tags={"span.domain": ",sentry_table1,sentry_table2,"},
  710. )
  711. response = self.do_request(
  712. {
  713. "field": ["span.domain", "p75(span.self_time)"],
  714. "query": "span.domain:sentry_table2",
  715. "project": self.project.id,
  716. "dataset": "spansMetrics",
  717. }
  718. )
  719. assert response.status_code == 200, response.content
  720. data = response.data["data"]
  721. meta = response.data["meta"]
  722. assert len(data) == 1
  723. assert data[0]["span.domain"] == ["sentry_table1", "sentry_table2"]
  724. assert meta["dataset"] == "spansMetrics"
  725. assert meta["fields"]["span.domain"] == "array"
  726. def test_span_domain_array_filter_wildcard(self):
  727. self.store_span_metric(
  728. 321,
  729. internal_metric=constants.SELF_TIME_LIGHT,
  730. timestamp=self.min_ago,
  731. tags={"span.domain": ",sentry_table1,"},
  732. )
  733. self.store_span_metric(
  734. 21,
  735. internal_metric=constants.SELF_TIME_LIGHT,
  736. timestamp=self.min_ago,
  737. tags={"span.domain": ",sentry_table1,sentry_table2,"},
  738. )
  739. for query in ["sentry*2", "*table2", "sentry_table2*"]:
  740. response = self.do_request(
  741. {
  742. "field": ["span.domain", "p75(span.self_time)"],
  743. "query": f"span.domain:{query}",
  744. "project": self.project.id,
  745. "dataset": "spansMetrics",
  746. }
  747. )
  748. assert response.status_code == 200, response.content
  749. data = response.data["data"]
  750. meta = response.data["meta"]
  751. assert len(data) == 1, query
  752. assert data[0]["span.domain"] == ["sentry_table1", "sentry_table2"], query
  753. assert meta["dataset"] == "spansMetrics", query
  754. assert meta["fields"]["span.domain"] == "array"
  755. def test_span_domain_array_has_filter(self):
  756. self.store_span_metric(
  757. 321,
  758. internal_metric=constants.SELF_TIME_LIGHT,
  759. timestamp=self.min_ago,
  760. tags={"span.domain": ""},
  761. )
  762. self.store_span_metric(
  763. 21,
  764. internal_metric=constants.SELF_TIME_LIGHT,
  765. timestamp=self.min_ago,
  766. tags={"span.domain": ",sentry_table1,sentry_table2,"},
  767. )
  768. response = self.do_request(
  769. {
  770. "field": ["span.domain", "p75(span.self_time)"],
  771. "query": "has:span.domain",
  772. "project": self.project.id,
  773. "dataset": "spansMetrics",
  774. }
  775. )
  776. assert response.status_code == 200, response.content
  777. data = response.data["data"]
  778. meta = response.data["meta"]
  779. assert len(data) == 1
  780. assert data[0]["span.domain"] == ["sentry_table1", "sentry_table2"]
  781. assert meta["dataset"] == "spansMetrics"
  782. response = self.do_request(
  783. {
  784. "field": ["span.domain", "p75(span.self_time)"],
  785. "query": "!has:span.domain",
  786. "project": self.project.id,
  787. "dataset": "spansMetrics",
  788. }
  789. )
  790. assert response.status_code == 200, response.content
  791. data = response.data["data"]
  792. meta = response.data["meta"]
  793. assert len(data) == 1
  794. assert meta["dataset"] == "spansMetrics"
  795. assert meta["fields"]["span.domain"] == "array"
  796. def test_unique_values_span_domain(self):
  797. self.store_span_metric(
  798. 321,
  799. internal_metric=constants.SELF_TIME_LIGHT,
  800. timestamp=self.min_ago,
  801. tags={"span.domain": ",sentry_table1,"},
  802. )
  803. self.store_span_metric(
  804. 21,
  805. internal_metric=constants.SELF_TIME_LIGHT,
  806. timestamp=self.min_ago,
  807. tags={"span.domain": ",sentry_table2,sentry_table3,"},
  808. )
  809. response = self.do_request(
  810. {
  811. "field": ["unique.span_domains", "count()"],
  812. "query": "",
  813. "orderby": "unique.span_domains",
  814. "project": self.project.id,
  815. "dataset": "spansMetrics",
  816. }
  817. )
  818. assert response.status_code == 200, response.content
  819. data = response.data["data"]
  820. meta = response.data["meta"]
  821. assert len(data) == 3
  822. assert data[0]["unique.span_domains"] == "sentry_table1"
  823. assert data[1]["unique.span_domains"] == "sentry_table2"
  824. assert data[2]["unique.span_domains"] == "sentry_table3"
  825. assert meta["fields"]["unique.span_domains"] == "string"
  826. def test_unique_values_span_domain_with_filter(self):
  827. self.store_span_metric(
  828. 321,
  829. internal_metric=constants.SELF_TIME_LIGHT,
  830. timestamp=self.min_ago,
  831. tags={"span.domain": ",sentry_tible1,"},
  832. )
  833. self.store_span_metric(
  834. 21,
  835. internal_metric=constants.SELF_TIME_LIGHT,
  836. timestamp=self.min_ago,
  837. tags={"span.domain": ",sentry_table2,sentry_table3,"},
  838. )
  839. response = self.do_request(
  840. {
  841. "field": ["unique.span_domains", "count()"],
  842. "query": "span.domain:sentry_tab*",
  843. "orderby": "unique.span_domains",
  844. "project": self.project.id,
  845. "dataset": "spansMetrics",
  846. }
  847. )
  848. assert response.status_code == 200, response.content
  849. data = response.data["data"]
  850. meta = response.data["meta"]
  851. assert len(data) == 2
  852. assert data[0]["unique.span_domains"] == "sentry_table2"
  853. assert data[1]["unique.span_domains"] == "sentry_table3"
  854. assert meta["fields"]["unique.span_domains"] == "string"
  855. def test_avg_if(self):
  856. self.store_span_metric(
  857. 100,
  858. internal_metric=constants.SELF_TIME_LIGHT,
  859. timestamp=self.min_ago,
  860. tags={"release": "foo"},
  861. )
  862. self.store_span_metric(
  863. 200,
  864. internal_metric=constants.SELF_TIME_LIGHT,
  865. timestamp=self.min_ago,
  866. tags={"release": "foo"},
  867. )
  868. self.store_span_metric(
  869. 10,
  870. internal_metric=constants.SELF_TIME_LIGHT,
  871. timestamp=self.min_ago,
  872. tags={"release": "bar"},
  873. )
  874. response = self.do_request(
  875. {
  876. "field": ["avg_if(span.self_time, release, foo)"],
  877. "query": "",
  878. "project": self.project.id,
  879. "dataset": "spansMetrics",
  880. }
  881. )
  882. assert response.status_code == 200, response.content
  883. data = response.data["data"]
  884. meta = response.data["meta"]
  885. assert len(data) == 1
  886. assert data[0]["avg_if(span.self_time, release, foo)"] == 150
  887. assert meta["dataset"] == "spansMetrics"
  888. assert meta["fields"]["avg_if(span.self_time, release, foo)"] == "duration"
  889. def test_device_class(self):
  890. self.store_span_metric(
  891. 123,
  892. internal_metric=constants.SELF_TIME_LIGHT,
  893. timestamp=self.min_ago,
  894. tags={"device.class": "1"},
  895. )
  896. self.store_span_metric(
  897. 678,
  898. internal_metric=constants.SELF_TIME_LIGHT,
  899. timestamp=self.min_ago,
  900. tags={"device.class": "2"},
  901. )
  902. self.store_span_metric(
  903. 999,
  904. internal_metric=constants.SELF_TIME_LIGHT,
  905. timestamp=self.min_ago,
  906. tags={"device.class": ""},
  907. )
  908. response = self.do_request(
  909. {
  910. "field": ["device.class", "p95()"],
  911. "query": "",
  912. "orderby": "p95()",
  913. "project": self.project.id,
  914. "dataset": "spansMetrics",
  915. }
  916. )
  917. assert response.status_code == 200, response.content
  918. data = response.data["data"]
  919. meta = response.data["meta"]
  920. assert len(data) == 3
  921. # Need to actually check the dict since the level for 1 isn't guaranteed to stay `low` or `medium`
  922. assert data[0]["device.class"] == map_device_class_level("1")
  923. assert data[1]["device.class"] == map_device_class_level("2")
  924. assert data[2]["device.class"] == "Unknown"
  925. assert meta["fields"]["device.class"] == "string"
  926. def test_device_class_filter(self):
  927. self.store_span_metric(
  928. 123,
  929. internal_metric=constants.SELF_TIME_LIGHT,
  930. timestamp=self.min_ago,
  931. tags={"device.class": "1"},
  932. )
  933. # Need to actually check the dict since the level for 1 isn't guaranteed to stay `low`
  934. level = map_device_class_level("1")
  935. response = self.do_request(
  936. {
  937. "field": ["device.class", "count()"],
  938. "query": f"device.class:{level}",
  939. "orderby": "count()",
  940. "project": self.project.id,
  941. "dataset": "spansMetrics",
  942. }
  943. )
  944. assert response.status_code == 200, response.content
  945. data = response.data["data"]
  946. meta = response.data["meta"]
  947. assert len(data) == 1
  948. assert data[0]["device.class"] == level
  949. assert meta["fields"]["device.class"] == "string"
  950. def test_device_class_filter_unknown(self):
  951. self.store_span_metric(
  952. 123,
  953. internal_metric=constants.SELF_TIME_LIGHT,
  954. timestamp=self.min_ago,
  955. tags={"device.class": ""},
  956. )
  957. response = self.do_request(
  958. {
  959. "field": ["device.class", "count()"],
  960. "query": "device.class:Unknown",
  961. "orderby": "count()",
  962. "project": self.project.id,
  963. "dataset": "spansMetrics",
  964. }
  965. )
  966. assert response.status_code == 200, response.content
  967. data = response.data["data"]
  968. meta = response.data["meta"]
  969. assert len(data) == 1
  970. assert data[0]["device.class"] == "Unknown"
  971. assert meta["fields"]["device.class"] == "string"
  972. def test_http_response_rate(self):
  973. self.store_span_metric(
  974. 1,
  975. internal_metric=constants.SELF_TIME_LIGHT,
  976. timestamp=self.min_ago,
  977. tags={"span.status_code": "200"},
  978. )
  979. self.store_span_metric(
  980. 3,
  981. internal_metric=constants.SELF_TIME_LIGHT,
  982. timestamp=self.min_ago,
  983. tags={"span.status_code": "301"},
  984. )
  985. self.store_span_metric(
  986. 3,
  987. internal_metric=constants.SELF_TIME_LIGHT,
  988. timestamp=self.min_ago,
  989. tags={"span.status_code": "404"},
  990. )
  991. self.store_span_metric(
  992. 4,
  993. internal_metric=constants.SELF_TIME_LIGHT,
  994. timestamp=self.min_ago,
  995. tags={"span.status_code": "503"},
  996. )
  997. self.store_span_metric(
  998. 5,
  999. internal_metric=constants.SELF_TIME_LIGHT,
  1000. timestamp=self.min_ago,
  1001. tags={"span.status_code": "501"},
  1002. )
  1003. response = self.do_request(
  1004. {
  1005. "field": [
  1006. "http_response_rate(200)", # By exact code
  1007. "http_response_rate(3)", # By code class
  1008. "http_response_rate(4)",
  1009. "http_response_rate(5)",
  1010. ],
  1011. "query": "",
  1012. "project": self.project.id,
  1013. "dataset": "spansMetrics",
  1014. }
  1015. )
  1016. assert response.status_code == 200, response.content
  1017. data = response.data["data"]
  1018. assert len(data) == 1
  1019. assert data[0]["http_response_rate(200)"] == 0.2
  1020. assert data[0]["http_response_rate(3)"] == 0.2
  1021. assert data[0]["http_response_rate(4)"] == 0.2
  1022. assert data[0]["http_response_rate(5)"] == 0.4
  1023. meta = response.data["meta"]
  1024. assert meta["dataset"] == "spansMetrics"
  1025. assert meta["fields"]["http_response_rate(200)"] == "percentage"
  1026. class OrganizationEventsMetricsEnhancedPerformanceEndpointTestWithMetricLayer(
  1027. OrganizationEventsMetricsEnhancedPerformanceEndpointTest
  1028. ):
  1029. def setUp(self):
  1030. super().setUp()
  1031. self.features["organizations:use-metrics-layer"] = True
  1032. @pytest.mark.xfail(reason="Not implemented")
  1033. def test_time_spent_percentage(self):
  1034. super().test_time_spent_percentage()
  1035. @pytest.mark.xfail(reason="Not implemented")
  1036. def test_time_spent_percentage_local(self):
  1037. super().test_time_spent_percentage_local()
  1038. @pytest.mark.xfail(reason="Cannot group by function 'if'")
  1039. def test_span_module(self):
  1040. super().test_span_module()
  1041. @pytest.mark.xfail(reason="Cannot search by tags")
  1042. def test_tag_search(self):
  1043. super().test_tag_search()
  1044. @pytest.mark.xfail(reason="Cannot search by tags")
  1045. def test_free_text_search(self):
  1046. super().test_free_text_search()
  1047. @pytest.mark.xfail(reason="Not implemented")
  1048. def test_avg_compare(self):
  1049. super().test_avg_compare()
  1050. @pytest.mark.xfail(reason="Not implemented")
  1051. def test_span_domain_array(self):
  1052. super().test_span_domain_array()
  1053. @pytest.mark.xfail(reason="Not implemented")
  1054. def test_span_domain_array_filter(self):
  1055. super().test_span_domain_array_filter()
  1056. @pytest.mark.xfail(reason="Not implemented")
  1057. def test_span_domain_array_filter_wildcard(self):
  1058. super().test_span_domain_array_filter_wildcard()
  1059. @pytest.mark.xfail(reason="Not implemented")
  1060. def test_span_domain_array_has_filter(self):
  1061. super().test_span_domain_array_has_filter()
  1062. @pytest.mark.xfail(reason="Not implemented")
  1063. def test_unique_values_span_domain(self):
  1064. super().test_unique_values_span_domain()
  1065. @pytest.mark.xfail(reason="Not implemented")
  1066. def test_unique_values_span_domain_with_filter(self):
  1067. super().test_unique_values_span_domain_with_filter()
  1068. @pytest.mark.xfail(reason="Not implemented")
  1069. def test_avg_if(self):
  1070. super().test_avg_if()
  1071. @pytest.mark.xfail(reason="Not implemented")
  1072. def test_device_class_filter(self):
  1073. super().test_device_class_filter()
  1074. @pytest.mark.xfail(reason="Not implemented")
  1075. def test_device_class(self):
  1076. super().test_device_class()