test_organization_events_span_metrics.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  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_eps(self):
  162. for _ in range(6):
  163. self.store_span_metric(
  164. 1,
  165. internal_metric=constants.SELF_TIME_LIGHT,
  166. timestamp=self.min_ago,
  167. )
  168. response = self.do_request(
  169. {
  170. "field": ["eps()", "sps()"],
  171. "query": "",
  172. "project": self.project.id,
  173. "dataset": "spansMetrics",
  174. "statsPeriod": "10m",
  175. }
  176. )
  177. assert response.status_code == 200, response.content
  178. data = response.data["data"]
  179. meta = response.data["meta"]
  180. assert len(data) == 1
  181. assert data[0]["eps()"] == 0.01
  182. assert data[0]["sps()"] == 0.01
  183. assert meta["dataset"] == "spansMetrics"
  184. def test_epm(self):
  185. for _ in range(6):
  186. self.store_span_metric(
  187. 1,
  188. internal_metric=constants.SELF_TIME_LIGHT,
  189. timestamp=self.min_ago,
  190. )
  191. response = self.do_request(
  192. {
  193. "field": ["epm()", "spm()"],
  194. "query": "",
  195. "project": self.project.id,
  196. "dataset": "spansMetrics",
  197. "statsPeriod": "10m",
  198. }
  199. )
  200. assert response.status_code == 200, response.content
  201. data = response.data["data"]
  202. meta = response.data["meta"]
  203. assert len(data) == 1
  204. assert data[0]["epm()"] == 0.6
  205. assert data[0]["spm()"] == 0.6
  206. assert meta["dataset"] == "spansMetrics"
  207. def test_time_spent_percentage(self):
  208. for _ in range(4):
  209. self.store_span_metric(
  210. 1,
  211. internal_metric=constants.SELF_TIME_LIGHT,
  212. tags={"transaction": "foo_transaction"},
  213. timestamp=self.min_ago,
  214. )
  215. self.store_span_metric(
  216. 1,
  217. tags={"transaction": "foo_transaction"},
  218. timestamp=self.min_ago,
  219. )
  220. self.store_span_metric(
  221. 1,
  222. internal_metric=constants.SELF_TIME_LIGHT,
  223. tags={"transaction": "bar_transaction"},
  224. timestamp=self.min_ago,
  225. )
  226. self.store_span_metric(
  227. 1,
  228. tags={"transaction": "bar_transaction"},
  229. timestamp=self.min_ago,
  230. )
  231. response = self.do_request(
  232. {
  233. "field": ["transaction", "time_spent_percentage()"],
  234. "query": "",
  235. "orderby": ["-time_spent_percentage()"],
  236. "project": self.project.id,
  237. "dataset": "spansMetrics",
  238. "statsPeriod": "10m",
  239. }
  240. )
  241. assert response.status_code == 200, response.content
  242. data = response.data["data"]
  243. meta = response.data["meta"]
  244. assert len(data) == 2
  245. assert data[0]["time_spent_percentage()"] == 0.8
  246. assert data[0]["transaction"] == "foo_transaction"
  247. assert data[1]["time_spent_percentage()"] == 0.2
  248. assert data[1]["transaction"] == "bar_transaction"
  249. assert meta["dataset"] == "spansMetrics"
  250. def test_time_spent_percentage_local(self):
  251. response = self.do_request(
  252. {
  253. "field": ["time_spent_percentage(local)"],
  254. "query": "",
  255. "orderby": ["-time_spent_percentage(local)"],
  256. "project": self.project.id,
  257. "dataset": "spansMetrics",
  258. "statsPeriod": "10m",
  259. }
  260. )
  261. assert response.status_code == 200, response.content
  262. data = response.data["data"]
  263. meta = response.data["meta"]
  264. assert len(data) == 1
  265. assert data[0]["time_spent_percentage(local)"] is None
  266. assert meta["dataset"] == "spansMetrics"
  267. def test_http_error_rate_and_count(self):
  268. for _ in range(4):
  269. self.store_span_metric(
  270. 1,
  271. internal_metric=constants.SELF_TIME_LIGHT,
  272. tags={"span.status_code": "500"},
  273. timestamp=self.min_ago,
  274. )
  275. self.store_span_metric(
  276. 1,
  277. internal_metric=constants.SELF_TIME_LIGHT,
  278. tags={"span.status_code": "200"},
  279. timestamp=self.min_ago,
  280. )
  281. response = self.do_request(
  282. {
  283. "field": ["http_error_count()", "http_error_rate()"],
  284. "query": "",
  285. "orderby": ["-http_error_rate()"],
  286. "project": self.project.id,
  287. "dataset": "spansMetrics",
  288. "statsPeriod": "10m",
  289. }
  290. )
  291. assert response.status_code == 200, response.content
  292. data = response.data["data"]
  293. meta = response.data["meta"]
  294. assert len(data) == 1
  295. assert data[0]["http_error_rate()"] == 0.8
  296. assert meta["dataset"] == "spansMetrics"
  297. assert meta["fields"]["http_error_count()"] == "integer"
  298. assert meta["fields"]["http_error_rate()"] == "percentage"
  299. def test_percentile_percent_change(self):
  300. self.store_span_metric(
  301. 5,
  302. internal_metric=constants.SELF_TIME_LIGHT,
  303. timestamp=self.six_min_ago,
  304. )
  305. self.store_span_metric(
  306. 10,
  307. internal_metric=constants.SELF_TIME_LIGHT,
  308. timestamp=self.min_ago,
  309. )
  310. response = self.do_request(
  311. {
  312. "field": ["percentile_percent_change(span.self_time, 0.95)"],
  313. "query": "",
  314. "orderby": ["-percentile_percent_change(span.self_time, 0.95)"],
  315. "project": self.project.id,
  316. "dataset": "spansMetrics",
  317. "statsPeriod": "10m",
  318. }
  319. )
  320. assert response.status_code == 200, response.content
  321. data = response.data["data"]
  322. meta = response.data["meta"]
  323. assert len(data) == 1
  324. assert data[0]["percentile_percent_change(span.self_time, 0.95)"] == 1
  325. assert meta["dataset"] == "spansMetrics"
  326. assert meta["fields"]["percentile_percent_change(span.self_time, 0.95)"] == "percent_change"
  327. def test_http_error_count_percent_change(self):
  328. for _ in range(4):
  329. self.store_span_metric(
  330. 1,
  331. internal_metric=constants.SELF_TIME_LIGHT,
  332. tags={"span.status_code": "500"},
  333. timestamp=self.six_min_ago,
  334. )
  335. self.store_span_metric(
  336. 1,
  337. internal_metric=constants.SELF_TIME_LIGHT,
  338. tags={"span.status_code": "500"},
  339. timestamp=self.min_ago,
  340. )
  341. response = self.do_request(
  342. {
  343. "field": ["http_error_count_percent_change()"],
  344. "query": "",
  345. "orderby": ["-http_error_count_percent_change()"],
  346. "project": self.project.id,
  347. "dataset": "spansMetrics",
  348. "statsPeriod": "10m",
  349. }
  350. )
  351. assert response.status_code == 200, response.content
  352. data = response.data["data"]
  353. meta = response.data["meta"]
  354. assert len(data) == 1
  355. assert data[0]["http_error_count_percent_change()"] == -0.75
  356. assert meta["dataset"] == "spansMetrics"
  357. assert meta["fields"]["http_error_count_percent_change()"] == "percent_change"
  358. def test_epm_percent_change(self):
  359. for _ in range(4):
  360. self.store_span_metric(
  361. 1,
  362. internal_metric=constants.SELF_TIME_LIGHT,
  363. timestamp=self.six_min_ago,
  364. )
  365. self.store_span_metric(
  366. 1,
  367. internal_metric=constants.SELF_TIME_LIGHT,
  368. timestamp=self.min_ago,
  369. )
  370. response = self.do_request(
  371. {
  372. "field": ["epm_percent_change()", "spm_percent_change()"],
  373. "query": "",
  374. "orderby": ["-epm_percent_change()"],
  375. "project": self.project.id,
  376. "dataset": "spansMetrics",
  377. "statsPeriod": "10m",
  378. }
  379. )
  380. assert response.status_code == 200, response.content
  381. data = response.data["data"]
  382. meta = response.data["meta"]
  383. assert len(data) == 1
  384. assert data[0]["epm_percent_change()"] == pytest.approx(-0.75)
  385. assert data[0]["spm_percent_change()"] == pytest.approx(-0.75)
  386. assert meta["dataset"] == "spansMetrics"
  387. assert meta["fields"]["epm_percent_change()"] == "percent_change"
  388. assert meta["fields"]["spm_percent_change()"] == "percent_change"
  389. def test_eps_percent_change(self):
  390. for _ in range(4):
  391. self.store_span_metric(
  392. 1,
  393. internal_metric=constants.SELF_TIME_LIGHT,
  394. timestamp=self.min_ago,
  395. )
  396. self.store_span_metric(
  397. 1,
  398. internal_metric=constants.SELF_TIME_LIGHT,
  399. timestamp=self.six_min_ago,
  400. )
  401. response = self.do_request(
  402. {
  403. "field": ["eps_percent_change()", "sps_percent_change()"],
  404. "query": "",
  405. "orderby": ["-eps_percent_change()"],
  406. "project": self.project.id,
  407. "dataset": "spansMetrics",
  408. "statsPeriod": "10m",
  409. }
  410. )
  411. assert response.status_code == 200, response.content
  412. data = response.data["data"]
  413. meta = response.data["meta"]
  414. assert len(data) == 1
  415. assert data[0]["eps_percent_change()"] == pytest.approx(3)
  416. assert data[0]["sps_percent_change()"] == pytest.approx(3)
  417. assert meta["dataset"] == "spansMetrics"
  418. assert meta["fields"]["eps_percent_change()"] == "percent_change"
  419. assert meta["fields"]["sps_percent_change()"] == "percent_change"
  420. def test_use_self_time_light(self):
  421. self.store_span_metric(
  422. 100,
  423. internal_metric=constants.SELF_TIME_LIGHT,
  424. tags={"transaction": "foo_transaction"},
  425. timestamp=self.min_ago,
  426. )
  427. response = self.do_request(
  428. {
  429. "field": ["p50(span.self_time)"],
  430. # Should be 0 since its filtering on transaction
  431. "query": "transaction:foo_transaction",
  432. "orderby": ["-p50(span.self_time)"],
  433. "project": self.project.id,
  434. "dataset": "spansMetrics",
  435. "statsPeriod": "10m",
  436. }
  437. )
  438. assert response.status_code == 200, response.content
  439. data = response.data["data"]
  440. meta = response.data["meta"]
  441. assert len(data) == 1
  442. assert data[0]["p50(span.self_time)"] == 0
  443. assert meta["dataset"] == "spansMetrics"
  444. assert meta["fields"]["p50(span.self_time)"] == "duration"
  445. response = self.do_request(
  446. {
  447. # Should be 0 since it has a transaction column
  448. "field": ["transaction", "p50(span.self_time)"],
  449. "query": "",
  450. "orderby": ["-p50(span.self_time)"],
  451. "project": self.project.id,
  452. "dataset": "spansMetrics",
  453. "statsPeriod": "10m",
  454. }
  455. )
  456. assert response.status_code == 200, response.content
  457. data = response.data["data"]
  458. meta = response.data["meta"]
  459. assert len(data) == 0
  460. response = self.do_request(
  461. {
  462. "field": ["p50(span.self_time)"],
  463. # Should be 100 since its not filtering on transaction
  464. "query": "",
  465. "orderby": ["-p50(span.self_time)"],
  466. "project": self.project.id,
  467. "dataset": "spansMetrics",
  468. "statsPeriod": "10m",
  469. }
  470. )
  471. assert response.status_code == 200, response.content
  472. data = response.data["data"]
  473. meta = response.data["meta"]
  474. assert len(data) == 1
  475. assert data[0]["p50(span.self_time)"] == 100
  476. assert meta["dataset"] == "spansMetrics"
  477. assert meta["fields"]["p50(span.self_time)"] == "duration"