test_organization_events_span_metrics.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  1. import pytest
  2. from django.urls import reverse
  3. from sentry.search.events import constants
  4. from sentry.testutils.cases 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. 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_use_self_time_light(self):
  357. self.store_span_metric(
  358. 100,
  359. internal_metric=constants.SELF_TIME_LIGHT,
  360. tags={"transaction": "foo_transaction"},
  361. timestamp=self.min_ago,
  362. )
  363. response = self.do_request(
  364. {
  365. "field": ["p50(span.self_time)"],
  366. # Should be 0 since its filtering on transaction
  367. "query": "transaction:foo_transaction",
  368. "orderby": ["-p50(span.self_time)"],
  369. "project": self.project.id,
  370. "dataset": "spansMetrics",
  371. "statsPeriod": "10m",
  372. }
  373. )
  374. assert response.status_code == 200, response.content
  375. data = response.data["data"]
  376. meta = response.data["meta"]
  377. assert len(data) == 1
  378. assert data[0]["p50(span.self_time)"] == 0
  379. assert meta["dataset"] == "spansMetrics"
  380. assert meta["fields"]["p50(span.self_time)"] == "duration"
  381. response = self.do_request(
  382. {
  383. # Should be 0 since it has a transaction column
  384. "field": ["transaction", "p50(span.self_time)"],
  385. "query": "",
  386. "orderby": ["-p50(span.self_time)"],
  387. "project": self.project.id,
  388. "dataset": "spansMetrics",
  389. "statsPeriod": "10m",
  390. }
  391. )
  392. assert response.status_code == 200, response.content
  393. data = response.data["data"]
  394. meta = response.data["meta"]
  395. assert len(data) == 0
  396. response = self.do_request(
  397. {
  398. "field": ["p50(span.self_time)"],
  399. # Should be 100 since its not filtering on transaction
  400. "query": "",
  401. "orderby": ["-p50(span.self_time)"],
  402. "project": self.project.id,
  403. "dataset": "spansMetrics",
  404. "statsPeriod": "10m",
  405. }
  406. )
  407. assert response.status_code == 200, response.content
  408. data = response.data["data"]
  409. meta = response.data["meta"]
  410. assert len(data) == 1
  411. assert data[0]["p50(span.self_time)"] == 100
  412. assert meta["dataset"] == "spansMetrics"
  413. assert meta["fields"]["p50(span.self_time)"] == "duration"
  414. def test_span_module(self):
  415. self.store_span_metric(
  416. 1,
  417. internal_metric=constants.SELF_TIME_LIGHT,
  418. timestamp=self.six_min_ago,
  419. tags={"span.category": "http", "span.description": "f"},
  420. )
  421. self.store_span_metric(
  422. 3,
  423. internal_metric=constants.SELF_TIME_LIGHT,
  424. timestamp=self.six_min_ago,
  425. tags={"span.category": "db", "span.description": "e"},
  426. )
  427. self.store_span_metric(
  428. 5,
  429. internal_metric=constants.SELF_TIME_LIGHT,
  430. timestamp=self.six_min_ago,
  431. tags={"span.category": "foobar", "span.description": "d"},
  432. )
  433. self.store_span_metric(
  434. 7,
  435. internal_metric=constants.SELF_TIME_LIGHT,
  436. timestamp=self.six_min_ago,
  437. tags={"span.category": "cache", "span.description": "c"},
  438. )
  439. self.store_span_metric(
  440. 9,
  441. internal_metric=constants.SELF_TIME_LIGHT,
  442. timestamp=self.six_min_ago,
  443. tags={"span.category": "db", "span.op": "db.redis", "span.description": "b"},
  444. )
  445. self.store_span_metric(
  446. 11,
  447. internal_metric=constants.SELF_TIME_LIGHT,
  448. timestamp=self.six_min_ago,
  449. tags={"span.category": "db", "span.op": "db.sql.room", "span.description": "a"},
  450. )
  451. response = self.do_request(
  452. {
  453. "field": ["span.module", "span.description", "p50(span.self_time)"],
  454. "query": "",
  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) == 6
  465. assert data[0]["p50(span.self_time)"] == 11
  466. assert data[0]["span.module"] == "other"
  467. assert data[0]["span.description"] == "a"
  468. assert data[1]["p50(span.self_time)"] == 9
  469. assert data[1]["span.module"] == "cache"
  470. assert data[1]["span.description"] == "b"
  471. assert data[2]["p50(span.self_time)"] == 7
  472. assert data[2]["span.module"] == "cache"
  473. assert data[2]["span.description"] == "c"
  474. assert data[3]["p50(span.self_time)"] == 5
  475. assert data[3]["span.module"] == "other"
  476. assert data[3]["span.description"] == "d"
  477. assert data[4]["p50(span.self_time)"] == 3
  478. assert data[4]["span.module"] == "db"
  479. assert data[4]["span.description"] == "e"
  480. assert data[5]["p50(span.self_time)"] == 1
  481. assert data[5]["span.module"] == "http"
  482. assert data[5]["span.description"] == "f"
  483. assert meta["dataset"] == "spansMetrics"
  484. assert meta["fields"]["p50(span.self_time)"] == "duration"
  485. def test_tag_search(self):
  486. self.store_span_metric(
  487. 321,
  488. internal_metric=constants.SELF_TIME_LIGHT,
  489. timestamp=self.min_ago,
  490. tags={"span.description": "foo"},
  491. )
  492. self.store_span_metric(
  493. 99,
  494. internal_metric=constants.SELF_TIME_LIGHT,
  495. timestamp=self.min_ago,
  496. tags={"span.description": "bar"},
  497. )
  498. response = self.do_request(
  499. {
  500. "field": ["sum(span.self_time)"],
  501. "query": "span.description:bar",
  502. "project": self.project.id,
  503. "dataset": "spansMetrics",
  504. }
  505. )
  506. assert response.status_code == 200, response.content
  507. data = response.data["data"]
  508. meta = response.data["meta"]
  509. assert len(data) == 1
  510. assert data[0]["sum(span.self_time)"] == 99
  511. assert meta["dataset"] == "spansMetrics"
  512. def test_free_text_search(self):
  513. self.store_span_metric(
  514. 321,
  515. internal_metric=constants.SELF_TIME_LIGHT,
  516. timestamp=self.min_ago,
  517. tags={"span.description": "foo"},
  518. )
  519. self.store_span_metric(
  520. 99,
  521. internal_metric=constants.SELF_TIME_LIGHT,
  522. timestamp=self.min_ago,
  523. tags={"span.description": "bar"},
  524. )
  525. response = self.do_request(
  526. {
  527. "field": ["sum(span.self_time)"],
  528. "query": "foo",
  529. "project": self.project.id,
  530. "dataset": "spansMetrics",
  531. }
  532. )
  533. assert response.status_code == 200, response.content
  534. data = response.data["data"]
  535. meta = response.data["meta"]
  536. assert len(data) == 1
  537. assert data[0]["sum(span.self_time)"] == 321
  538. assert meta["dataset"] == "spansMetrics"
  539. @region_silo_test
  540. class OrganizationEventsMetricsEnhancedPerformanceEndpointTestWithMetricLayer(
  541. OrganizationEventsMetricsEnhancedPerformanceEndpointTest
  542. ):
  543. def setUp(self):
  544. super().setUp()
  545. self.features["organizations:use-metrics-layer"] = True
  546. @pytest.mark.xfail(reason="Not implemented")
  547. def test_time_spent_percentage(self):
  548. super().test_time_spent_percentage()
  549. @pytest.mark.xfail(reason="Not implemented")
  550. def test_time_spent_percentage_local(self):
  551. super().test_time_spent_percentage_local()
  552. @pytest.mark.xfail(reason="Not implemented")
  553. def test_http_error_rate_and_count(self):
  554. super().test_http_error_rate_and_count()
  555. @pytest.mark.xfail(reason="Cannot group by function 'if'")
  556. def test_span_module(self):
  557. super().test_span_module()
  558. @pytest.mark.xfail(reason="Cannot search by tags")
  559. def test_tag_search(self):
  560. super().test_tag_search()
  561. @pytest.mark.xfail(reason="Cannot search by tags")
  562. def test_free_text_search(self):
  563. super().test_free_text_search()