test_organization_events_span_metrics.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  1. import pytest
  2. from django.urls import reverse
  3. from sentry.search.events import constants
  4. from sentry.testutils import MetricsEnhancedPerformanceTestCase
  5. from sentry.testutils.helpers.datetime import before_now
  6. from sentry.testutils.silo import region_silo_test
  7. pytestmark = pytest.mark.sentry_metrics
  8. @region_silo_test
  9. class OrganizationEventsMetricsEnhancedPerformanceEndpointTest(MetricsEnhancedPerformanceTestCase):
  10. viewname = "sentry-api-0-organization-events"
  11. # Poor intentionally omitted for test_measurement_rating_that_does_not_exist
  12. METRIC_STRINGS = [
  13. "foo_transaction",
  14. "bar_transaction",
  15. ]
  16. def setUp(self):
  17. super().setUp()
  18. self.min_ago = before_now(minutes=1)
  19. self.six_min_ago = before_now(minutes=6)
  20. self.three_days_ago = before_now(days=3)
  21. self.features = {
  22. "organizations:starfish-view": True,
  23. }
  24. def do_request(self, query, features=None):
  25. if features is None:
  26. features = {"organizations:discover-basic": True}
  27. features.update(self.features)
  28. self.login_as(user=self.user)
  29. url = reverse(
  30. self.viewname,
  31. kwargs={"organization_slug": self.organization.slug},
  32. )
  33. with self.feature(features):
  34. return self.client.get(url, query, format="json")
  35. def test_p50_with_no_data(self):
  36. response = self.do_request(
  37. {
  38. "field": ["p50()"],
  39. "query": "",
  40. "project": self.project.id,
  41. "dataset": "spansMetrics",
  42. }
  43. )
  44. assert response.status_code == 200, response.content
  45. data = response.data["data"]
  46. meta = response.data["meta"]
  47. assert len(data) == 1
  48. assert data[0]["p50()"] == 0
  49. assert meta["dataset"] == "spansMetrics"
  50. def test_count(self):
  51. self.store_span_metric(
  52. 1,
  53. internal_metric=constants.SELF_TIME_LIGHT,
  54. timestamp=self.three_days_ago,
  55. )
  56. response = self.do_request(
  57. {
  58. "field": ["count()"],
  59. "query": "",
  60. "project": self.project.id,
  61. "dataset": "spansMetrics",
  62. "statsPeriod": "7d",
  63. }
  64. )
  65. assert response.status_code == 200, response.content
  66. data = response.data["data"]
  67. meta = response.data["meta"]
  68. assert len(data) == 1
  69. assert data[0]["count()"] == 1
  70. assert meta["dataset"] == "spansMetrics"
  71. def test_count_unique(self):
  72. self.store_span_metric(
  73. 1,
  74. "user",
  75. timestamp=self.min_ago,
  76. )
  77. self.store_span_metric(
  78. 2,
  79. "user",
  80. timestamp=self.min_ago,
  81. )
  82. response = self.do_request(
  83. {
  84. "field": ["count_unique(user)"],
  85. "query": "",
  86. "project": self.project.id,
  87. "dataset": "spansMetrics",
  88. }
  89. )
  90. assert response.status_code == 200, response.content
  91. data = response.data["data"]
  92. meta = response.data["meta"]
  93. assert len(data) == 1
  94. assert data[0]["count_unique(user)"] == 2
  95. assert meta["dataset"] == "spansMetrics"
  96. def test_sum(self):
  97. self.store_span_metric(
  98. 321,
  99. internal_metric=constants.SELF_TIME_LIGHT,
  100. timestamp=self.min_ago,
  101. )
  102. self.store_span_metric(
  103. 99,
  104. internal_metric=constants.SELF_TIME_LIGHT,
  105. timestamp=self.min_ago,
  106. )
  107. response = self.do_request(
  108. {
  109. "field": ["sum(span.self_time)"],
  110. "query": "",
  111. "project": self.project.id,
  112. "dataset": "spansMetrics",
  113. }
  114. )
  115. assert response.status_code == 200, response.content
  116. data = response.data["data"]
  117. meta = response.data["meta"]
  118. assert len(data) == 1
  119. assert data[0]["sum(span.self_time)"] == 420
  120. assert meta["dataset"] == "spansMetrics"
  121. def test_percentile(self):
  122. self.store_span_metric(
  123. 1,
  124. internal_metric=constants.SELF_TIME_LIGHT,
  125. timestamp=self.min_ago,
  126. )
  127. response = self.do_request(
  128. {
  129. "field": ["percentile(span.self_time, 0.95)"],
  130. "query": "",
  131. "project": self.project.id,
  132. "dataset": "spansMetrics",
  133. }
  134. )
  135. assert response.status_code == 200, response.content
  136. data = response.data["data"]
  137. meta = response.data["meta"]
  138. assert len(data) == 1
  139. assert data[0]["percentile(span.self_time, 0.95)"] == 1
  140. assert meta["dataset"] == "spansMetrics"
  141. def test_p50(self):
  142. self.store_span_metric(
  143. 1,
  144. internal_metric=constants.SELF_TIME_LIGHT,
  145. timestamp=self.min_ago,
  146. )
  147. response = self.do_request(
  148. {
  149. "field": ["p50()"],
  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]["p50()"] == 1
  160. assert meta["dataset"] == "spansMetrics"
  161. def test_p50_with_duration(self):
  162. self.store_span_metric(
  163. 1,
  164. internal_metric=constants.SPAN_METRICS_MAP["span.duration"],
  165. timestamp=self.min_ago,
  166. )
  167. response = self.do_request(
  168. {
  169. "field": ["p50(span.duration)"],
  170. "query": "",
  171. "project": self.project.id,
  172. "dataset": "spansMetrics",
  173. }
  174. )
  175. assert response.status_code == 200, response.content
  176. data = response.data["data"]
  177. meta = response.data["meta"]
  178. assert len(data) == 1
  179. assert data[0]["p50(span.duration)"] == 1
  180. assert meta["dataset"] == "spansMetrics"
  181. def test_avg(self):
  182. self.store_span_metric(
  183. 1,
  184. internal_metric=constants.SELF_TIME_LIGHT,
  185. timestamp=self.min_ago,
  186. )
  187. response = self.do_request(
  188. {
  189. "field": ["avg()"],
  190. "query": "",
  191. "project": self.project.id,
  192. "dataset": "spansMetrics",
  193. }
  194. )
  195. assert response.status_code == 200, response.content
  196. data = response.data["data"]
  197. meta = response.data["meta"]
  198. assert len(data) == 1
  199. assert data[0]["avg()"] == 1
  200. assert meta["dataset"] == "spansMetrics"
  201. def test_eps(self):
  202. for _ in range(6):
  203. self.store_span_metric(
  204. 1,
  205. internal_metric=constants.SELF_TIME_LIGHT,
  206. timestamp=self.min_ago,
  207. )
  208. response = self.do_request(
  209. {
  210. "field": ["eps()", "sps()"],
  211. "query": "",
  212. "project": self.project.id,
  213. "dataset": "spansMetrics",
  214. "statsPeriod": "10m",
  215. }
  216. )
  217. assert response.status_code == 200, response.content
  218. data = response.data["data"]
  219. meta = response.data["meta"]
  220. assert len(data) == 1
  221. assert data[0]["eps()"] == 0.01
  222. assert data[0]["sps()"] == 0.01
  223. assert meta["fields"]["eps()"] == "rate"
  224. assert meta["fields"]["sps()"] == "rate"
  225. assert meta["units"]["eps()"] == "1/second"
  226. assert meta["units"]["sps()"] == "1/second"
  227. assert meta["dataset"] == "spansMetrics"
  228. def test_epm(self):
  229. for _ in range(6):
  230. self.store_span_metric(
  231. 1,
  232. internal_metric=constants.SELF_TIME_LIGHT,
  233. timestamp=self.min_ago,
  234. )
  235. response = self.do_request(
  236. {
  237. "field": ["epm()", "spm()"],
  238. "query": "",
  239. "project": self.project.id,
  240. "dataset": "spansMetrics",
  241. "statsPeriod": "10m",
  242. }
  243. )
  244. assert response.status_code == 200, response.content
  245. data = response.data["data"]
  246. meta = response.data["meta"]
  247. assert len(data) == 1
  248. assert data[0]["epm()"] == 0.6
  249. assert data[0]["spm()"] == 0.6
  250. assert meta["fields"]["epm()"] == "rate"
  251. assert meta["fields"]["spm()"] == "rate"
  252. assert meta["units"]["epm()"] == "1/minute"
  253. assert meta["units"]["spm()"] == "1/minute"
  254. assert meta["dataset"] == "spansMetrics"
  255. def test_time_spent_percentage(self):
  256. for _ in range(4):
  257. self.store_span_metric(
  258. 1,
  259. internal_metric=constants.SELF_TIME_LIGHT,
  260. tags={"transaction": "foo_transaction"},
  261. timestamp=self.min_ago,
  262. )
  263. self.store_span_metric(
  264. 1,
  265. tags={"transaction": "foo_transaction"},
  266. timestamp=self.min_ago,
  267. )
  268. self.store_span_metric(
  269. 1,
  270. internal_metric=constants.SELF_TIME_LIGHT,
  271. tags={"transaction": "bar_transaction"},
  272. timestamp=self.min_ago,
  273. )
  274. self.store_span_metric(
  275. 1,
  276. tags={"transaction": "bar_transaction"},
  277. timestamp=self.min_ago,
  278. )
  279. response = self.do_request(
  280. {
  281. "field": ["transaction", "time_spent_percentage()"],
  282. "query": "",
  283. "orderby": ["-time_spent_percentage()"],
  284. "project": self.project.id,
  285. "dataset": "spansMetrics",
  286. "statsPeriod": "10m",
  287. }
  288. )
  289. assert response.status_code == 200, response.content
  290. data = response.data["data"]
  291. meta = response.data["meta"]
  292. assert len(data) == 2
  293. assert data[0]["time_spent_percentage()"] == 0.8
  294. assert data[0]["transaction"] == "foo_transaction"
  295. assert data[1]["time_spent_percentage()"] == 0.2
  296. assert data[1]["transaction"] == "bar_transaction"
  297. assert meta["dataset"] == "spansMetrics"
  298. def test_time_spent_percentage_local(self):
  299. response = self.do_request(
  300. {
  301. "field": ["time_spent_percentage(local)"],
  302. "query": "",
  303. "orderby": ["-time_spent_percentage(local)"],
  304. "project": self.project.id,
  305. "dataset": "spansMetrics",
  306. "statsPeriod": "10m",
  307. }
  308. )
  309. assert response.status_code == 200, response.content
  310. data = response.data["data"]
  311. meta = response.data["meta"]
  312. assert len(data) == 1
  313. assert data[0]["time_spent_percentage(local)"] is None
  314. assert meta["dataset"] == "spansMetrics"
  315. def test_http_error_rate_and_count(self):
  316. for _ in range(4):
  317. self.store_span_metric(
  318. 1,
  319. internal_metric=constants.SELF_TIME_LIGHT,
  320. tags={"span.status_code": "500"},
  321. timestamp=self.min_ago,
  322. )
  323. self.store_span_metric(
  324. 1,
  325. internal_metric=constants.SELF_TIME_LIGHT,
  326. tags={"span.status_code": "200"},
  327. timestamp=self.min_ago,
  328. )
  329. response = self.do_request(
  330. {
  331. "field": ["http_error_count()", "http_error_rate()"],
  332. "query": "",
  333. "orderby": ["-http_error_rate()"],
  334. "project": self.project.id,
  335. "dataset": "spansMetrics",
  336. "statsPeriod": "10m",
  337. }
  338. )
  339. assert response.status_code == 200, response.content
  340. data = response.data["data"]
  341. meta = response.data["meta"]
  342. assert len(data) == 1
  343. assert data[0]["http_error_rate()"] == 0.8
  344. assert meta["dataset"] == "spansMetrics"
  345. assert meta["fields"]["http_error_count()"] == "integer"
  346. assert meta["fields"]["http_error_rate()"] == "percentage"
  347. def test_percentile_percent_change(self):
  348. self.store_span_metric(
  349. 5,
  350. internal_metric=constants.SELF_TIME_LIGHT,
  351. timestamp=self.six_min_ago,
  352. )
  353. self.store_span_metric(
  354. 10,
  355. internal_metric=constants.SELF_TIME_LIGHT,
  356. timestamp=self.min_ago,
  357. )
  358. response = self.do_request(
  359. {
  360. "field": ["percentile_percent_change(span.self_time, 0.95)"],
  361. "query": "",
  362. "orderby": ["-percentile_percent_change(span.self_time, 0.95)"],
  363. "project": self.project.id,
  364. "dataset": "spansMetrics",
  365. "statsPeriod": "10m",
  366. }
  367. )
  368. assert response.status_code == 200, response.content
  369. data = response.data["data"]
  370. meta = response.data["meta"]
  371. assert len(data) == 1
  372. assert data[0]["percentile_percent_change(span.self_time, 0.95)"] == 1
  373. assert meta["dataset"] == "spansMetrics"
  374. assert meta["fields"]["percentile_percent_change(span.self_time, 0.95)"] == "percent_change"
  375. def test_http_error_count_percent_change(self):
  376. for _ in range(4):
  377. self.store_span_metric(
  378. 1,
  379. internal_metric=constants.SELF_TIME_LIGHT,
  380. tags={"span.status_code": "500"},
  381. timestamp=self.six_min_ago,
  382. )
  383. self.store_span_metric(
  384. 1,
  385. internal_metric=constants.SELF_TIME_LIGHT,
  386. tags={"span.status_code": "500"},
  387. timestamp=self.min_ago,
  388. )
  389. response = self.do_request(
  390. {
  391. "field": ["http_error_count_percent_change()"],
  392. "query": "",
  393. "orderby": ["-http_error_count_percent_change()"],
  394. "project": self.project.id,
  395. "dataset": "spansMetrics",
  396. "statsPeriod": "10m",
  397. }
  398. )
  399. assert response.status_code == 200, response.content
  400. data = response.data["data"]
  401. meta = response.data["meta"]
  402. assert len(data) == 1
  403. assert data[0]["http_error_count_percent_change()"] == -0.75
  404. assert meta["dataset"] == "spansMetrics"
  405. assert meta["fields"]["http_error_count_percent_change()"] == "percent_change"
  406. def test_epm_percent_change(self):
  407. for _ in range(4):
  408. self.store_span_metric(
  409. 1,
  410. internal_metric=constants.SELF_TIME_LIGHT,
  411. timestamp=self.six_min_ago,
  412. )
  413. self.store_span_metric(
  414. 1,
  415. internal_metric=constants.SELF_TIME_LIGHT,
  416. timestamp=self.min_ago,
  417. )
  418. response = self.do_request(
  419. {
  420. "field": ["epm_percent_change()", "spm_percent_change()"],
  421. "query": "",
  422. "orderby": ["-epm_percent_change()"],
  423. "project": self.project.id,
  424. "dataset": "spansMetrics",
  425. "statsPeriod": "10m",
  426. }
  427. )
  428. assert response.status_code == 200, response.content
  429. data = response.data["data"]
  430. meta = response.data["meta"]
  431. assert len(data) == 1
  432. assert data[0]["epm_percent_change()"] == pytest.approx(-0.75)
  433. assert data[0]["spm_percent_change()"] == pytest.approx(-0.75)
  434. assert meta["dataset"] == "spansMetrics"
  435. assert meta["fields"]["epm_percent_change()"] == "percent_change"
  436. assert meta["fields"]["spm_percent_change()"] == "percent_change"
  437. def test_eps_percent_change(self):
  438. for _ in range(4):
  439. self.store_span_metric(
  440. 1,
  441. internal_metric=constants.SELF_TIME_LIGHT,
  442. timestamp=self.min_ago,
  443. )
  444. self.store_span_metric(
  445. 1,
  446. internal_metric=constants.SELF_TIME_LIGHT,
  447. timestamp=self.six_min_ago,
  448. )
  449. response = self.do_request(
  450. {
  451. "field": ["eps_percent_change()", "sps_percent_change()"],
  452. "query": "",
  453. "orderby": ["-eps_percent_change()"],
  454. "project": self.project.id,
  455. "dataset": "spansMetrics",
  456. "statsPeriod": "10m",
  457. }
  458. )
  459. assert response.status_code == 200, response.content
  460. data = response.data["data"]
  461. meta = response.data["meta"]
  462. assert len(data) == 1
  463. assert data[0]["eps_percent_change()"] == pytest.approx(3)
  464. assert data[0]["sps_percent_change()"] == pytest.approx(3)
  465. assert meta["dataset"] == "spansMetrics"
  466. assert meta["fields"]["eps_percent_change()"] == "percent_change"
  467. assert meta["fields"]["sps_percent_change()"] == "percent_change"
  468. def test_use_self_time_light(self):
  469. self.store_span_metric(
  470. 100,
  471. internal_metric=constants.SELF_TIME_LIGHT,
  472. tags={"transaction": "foo_transaction"},
  473. timestamp=self.min_ago,
  474. )
  475. response = self.do_request(
  476. {
  477. "field": ["p50(span.self_time)"],
  478. # Should be 0 since its filtering on transaction
  479. "query": "transaction:foo_transaction",
  480. "orderby": ["-p50(span.self_time)"],
  481. "project": self.project.id,
  482. "dataset": "spansMetrics",
  483. "statsPeriod": "10m",
  484. }
  485. )
  486. assert response.status_code == 200, response.content
  487. data = response.data["data"]
  488. meta = response.data["meta"]
  489. assert len(data) == 1
  490. assert data[0]["p50(span.self_time)"] == 0
  491. assert meta["dataset"] == "spansMetrics"
  492. assert meta["fields"]["p50(span.self_time)"] == "duration"
  493. response = self.do_request(
  494. {
  495. # Should be 0 since it has a transaction column
  496. "field": ["transaction", "p50(span.self_time)"],
  497. "query": "",
  498. "orderby": ["-p50(span.self_time)"],
  499. "project": self.project.id,
  500. "dataset": "spansMetrics",
  501. "statsPeriod": "10m",
  502. }
  503. )
  504. assert response.status_code == 200, response.content
  505. data = response.data["data"]
  506. meta = response.data["meta"]
  507. assert len(data) == 0
  508. response = self.do_request(
  509. {
  510. "field": ["p50(span.self_time)"],
  511. # Should be 100 since its not filtering on transaction
  512. "query": "",
  513. "orderby": ["-p50(span.self_time)"],
  514. "project": self.project.id,
  515. "dataset": "spansMetrics",
  516. "statsPeriod": "10m",
  517. }
  518. )
  519. assert response.status_code == 200, response.content
  520. data = response.data["data"]
  521. meta = response.data["meta"]
  522. assert len(data) == 1
  523. assert data[0]["p50(span.self_time)"] == 100
  524. assert meta["dataset"] == "spansMetrics"
  525. assert meta["fields"]["p50(span.self_time)"] == "duration"
  526. def test_span_module(self):
  527. self.store_span_metric(
  528. 1,
  529. internal_metric=constants.SELF_TIME_LIGHT,
  530. timestamp=self.six_min_ago,
  531. tags={"span.category": "http"},
  532. )
  533. self.store_span_metric(
  534. 3,
  535. internal_metric=constants.SELF_TIME_LIGHT,
  536. timestamp=self.six_min_ago,
  537. tags={"span.category": "db"},
  538. )
  539. self.store_span_metric(
  540. 5,
  541. internal_metric=constants.SELF_TIME_LIGHT,
  542. timestamp=self.six_min_ago,
  543. tags={"span.category": "foobar"},
  544. )
  545. self.store_span_metric(
  546. 7,
  547. internal_metric=constants.SELF_TIME_LIGHT,
  548. timestamp=self.six_min_ago,
  549. tags={"span.category": "cache"},
  550. )
  551. response = self.do_request(
  552. {
  553. "field": ["span.module", "p50(span.self_time)"],
  554. "query": "",
  555. "orderby": ["-p50(span.self_time)"],
  556. "project": self.project.id,
  557. "dataset": "spansMetrics",
  558. "statsPeriod": "10m",
  559. }
  560. )
  561. assert response.status_code == 200, response.content
  562. data = response.data["data"]
  563. meta = response.data["meta"]
  564. assert len(data) == 4
  565. assert data[0]["p50(span.self_time)"] == 7
  566. assert data[0]["span.module"] == "cache"
  567. assert data[1]["p50(span.self_time)"] == 5
  568. assert data[1]["span.module"] == "other"
  569. assert data[2]["p50(span.self_time)"] == 3
  570. assert data[2]["span.module"] == "db"
  571. assert data[3]["p50(span.self_time)"] == 1
  572. assert data[3]["span.module"] == "http"
  573. assert meta["dataset"] == "spansMetrics"
  574. assert meta["fields"]["p50(span.self_time)"] == "duration"
  575. @region_silo_test
  576. class OrganizationEventsMetricsEnhancedPerformanceEndpointTestWithMetricLayer(
  577. OrganizationEventsMetricsEnhancedPerformanceEndpointTest
  578. ):
  579. def setUp(self):
  580. super().setUp()
  581. self.features["organizations:use-metrics-layer"] = True
  582. @pytest.mark.xfail(reason="Not implemented")
  583. def test_time_spent_percentage(self):
  584. super().test_time_spent_percentage()
  585. @pytest.mark.xfail(reason="Not implemented")
  586. def test_time_spent_percentage_local(self):
  587. super().test_time_spent_percentage_local()
  588. @pytest.mark.xfail(reason="Not implemented")
  589. def test_http_error_rate_and_count(self):
  590. super().test_http_error_rate_and_count()
  591. @pytest.mark.xfail(reason="Not implemented")
  592. def test_percentile_percent_change(self):
  593. super().test_percentile_percent_change()
  594. @pytest.mark.xfail(reason="Not implemented")
  595. def test_http_error_count_percent_change(self):
  596. super().test_http_error_count_percent_change()
  597. @pytest.mark.xfail(reason="Not implemented")
  598. def test_epm_percent_change(self):
  599. super().test_epm_percent_change()
  600. @pytest.mark.xfail(reason="Not implemented")
  601. def test_eps_percent_change(self):
  602. super().test_eps_percent_change()
  603. @pytest.mark.xfail(reason="Cannot group by transform")
  604. def test_span_module(self):
  605. super().test_span_module()