test_organization_events_span_metrics.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  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"},
  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"},
  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"},
  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"},
  438. )
  439. response = self.do_request(
  440. {
  441. "field": ["span.module", "p50(span.self_time)"],
  442. "query": "",
  443. "orderby": ["-p50(span.self_time)"],
  444. "project": self.project.id,
  445. "dataset": "spansMetrics",
  446. "statsPeriod": "10m",
  447. }
  448. )
  449. assert response.status_code == 200, response.content
  450. data = response.data["data"]
  451. meta = response.data["meta"]
  452. assert len(data) == 4
  453. assert data[0]["p50(span.self_time)"] == 7
  454. assert data[0]["span.module"] == "cache"
  455. assert data[1]["p50(span.self_time)"] == 5
  456. assert data[1]["span.module"] == "other"
  457. assert data[2]["p50(span.self_time)"] == 3
  458. assert data[2]["span.module"] == "db"
  459. assert data[3]["p50(span.self_time)"] == 1
  460. assert data[3]["span.module"] == "http"
  461. assert meta["dataset"] == "spansMetrics"
  462. assert meta["fields"]["p50(span.self_time)"] == "duration"
  463. def test_tag_search(self):
  464. self.store_span_metric(
  465. 321,
  466. internal_metric=constants.SELF_TIME_LIGHT,
  467. timestamp=self.min_ago,
  468. tags={"span.description": "foo"},
  469. )
  470. self.store_span_metric(
  471. 99,
  472. internal_metric=constants.SELF_TIME_LIGHT,
  473. timestamp=self.min_ago,
  474. tags={"span.description": "bar"},
  475. )
  476. response = self.do_request(
  477. {
  478. "field": ["sum(span.self_time)"],
  479. "query": "span.description:bar",
  480. "project": self.project.id,
  481. "dataset": "spansMetrics",
  482. }
  483. )
  484. assert response.status_code == 200, response.content
  485. data = response.data["data"]
  486. meta = response.data["meta"]
  487. assert len(data) == 1
  488. assert data[0]["sum(span.self_time)"] == 99
  489. assert meta["dataset"] == "spansMetrics"
  490. def test_free_text_search(self):
  491. self.store_span_metric(
  492. 321,
  493. internal_metric=constants.SELF_TIME_LIGHT,
  494. timestamp=self.min_ago,
  495. tags={"span.description": "foo"},
  496. )
  497. self.store_span_metric(
  498. 99,
  499. internal_metric=constants.SELF_TIME_LIGHT,
  500. timestamp=self.min_ago,
  501. tags={"span.description": "bar"},
  502. )
  503. response = self.do_request(
  504. {
  505. "field": ["sum(span.self_time)"],
  506. "query": "foo",
  507. "project": self.project.id,
  508. "dataset": "spansMetrics",
  509. }
  510. )
  511. assert response.status_code == 200, response.content
  512. data = response.data["data"]
  513. meta = response.data["meta"]
  514. assert len(data) == 1
  515. assert data[0]["sum(span.self_time)"] == 321
  516. assert meta["dataset"] == "spansMetrics"
  517. @region_silo_test
  518. class OrganizationEventsMetricsEnhancedPerformanceEndpointTestWithMetricLayer(
  519. OrganizationEventsMetricsEnhancedPerformanceEndpointTest
  520. ):
  521. def setUp(self):
  522. super().setUp()
  523. self.features["organizations:use-metrics-layer"] = True
  524. @pytest.mark.xfail(reason="Not implemented")
  525. def test_time_spent_percentage(self):
  526. super().test_time_spent_percentage()
  527. @pytest.mark.xfail(reason="Not implemented")
  528. def test_time_spent_percentage_local(self):
  529. super().test_time_spent_percentage_local()
  530. @pytest.mark.xfail(reason="Not implemented")
  531. def test_http_error_rate_and_count(self):
  532. super().test_http_error_rate_and_count()
  533. @pytest.mark.xfail(reason="Cannot group by transform")
  534. def test_span_module(self):
  535. super().test_span_module()
  536. @pytest.mark.xfail(reason="Cannot search by tags")
  537. def test_tag_search(self):
  538. super().test_tag_search()
  539. @pytest.mark.xfail(reason="Cannot search by tags")
  540. def test_free_text_search(self):
  541. super().test_free_text_search()