test_organization_events_span_indexed.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. import uuid
  2. import pytest
  3. from tests.snuba.api.endpoints.test_organization_events import OrganizationEventsEndpointTestBase
  4. class OrganizationEventsSpanIndexedEndpointTest(OrganizationEventsEndpointTestBase):
  5. is_eap = False
  6. """Test the indexed spans dataset.
  7. To run this locally you may need to set the ENABLE_SPANS_CONSUMER flag to True in Snuba.
  8. A way to do this is
  9. 1. run: `sentry devservices down snuba`
  10. 2. clone snuba locally
  11. 3. run: `export ENABLE_SPANS_CONSUMER=True`
  12. 4. run snuba
  13. At this point tests should work locally
  14. Once span ingestion is on by default this will no longer need to be done
  15. """
  16. @property
  17. def dataset(self):
  18. if self.is_eap:
  19. return "spans"
  20. else:
  21. return "spansIndexed"
  22. def setUp(self):
  23. super().setUp()
  24. self.features = {
  25. "organizations:starfish-view": True,
  26. }
  27. @pytest.mark.querybuilder
  28. def test_simple(self):
  29. self.store_spans(
  30. [
  31. self.create_span(
  32. {"description": "foo", "sentry_tags": {"status": "success"}},
  33. start_ts=self.ten_mins_ago,
  34. ),
  35. self.create_span(
  36. {"description": "bar", "sentry_tags": {"status": "invalid_argument"}},
  37. start_ts=self.ten_mins_ago,
  38. ),
  39. ],
  40. is_eap=self.is_eap,
  41. )
  42. response = self.do_request(
  43. {
  44. "field": ["span.status", "description", "count()"],
  45. "query": "",
  46. "orderby": "description",
  47. "project": self.project.id,
  48. "dataset": self.dataset,
  49. }
  50. )
  51. assert response.status_code == 200, response.content
  52. data = response.data["data"]
  53. meta = response.data["meta"]
  54. assert len(data) == 2
  55. assert data == [
  56. {
  57. "span.status": "invalid_argument",
  58. "description": "bar",
  59. "count()": 1,
  60. },
  61. {
  62. "span.status": "ok",
  63. "description": "foo",
  64. "count()": 1,
  65. },
  66. ]
  67. assert meta["dataset"] == self.dataset
  68. def test_sentry_tags_vs_tags(self):
  69. self.store_spans(
  70. [
  71. self.create_span(
  72. {"sentry_tags": {"transaction.method": "foo"}}, start_ts=self.ten_mins_ago
  73. ),
  74. ],
  75. is_eap=self.is_eap,
  76. )
  77. response = self.do_request(
  78. {
  79. "field": ["transaction.method", "count()"],
  80. "query": "",
  81. "orderby": "count()",
  82. "project": self.project.id,
  83. "dataset": self.dataset,
  84. }
  85. )
  86. assert response.status_code == 200, response.content
  87. data = response.data["data"]
  88. meta = response.data["meta"]
  89. assert len(data) == 1
  90. assert data[0]["transaction.method"] == "foo"
  91. assert meta["dataset"] == self.dataset
  92. def test_sentry_tags_syntax(self):
  93. self.store_spans(
  94. [
  95. self.create_span(
  96. {"sentry_tags": {"transaction.method": "foo"}}, start_ts=self.ten_mins_ago
  97. ),
  98. ],
  99. is_eap=self.is_eap,
  100. )
  101. response = self.do_request(
  102. {
  103. "field": ["sentry_tags[transaction.method]", "count()"],
  104. "query": "",
  105. "orderby": "count()",
  106. "project": self.project.id,
  107. "dataset": self.dataset,
  108. }
  109. )
  110. assert response.status_code == 200, response.content
  111. data = response.data["data"]
  112. meta = response.data["meta"]
  113. assert len(data) == 1
  114. assert data[0]["sentry_tags[transaction.method]"] == "foo"
  115. assert meta["dataset"] == self.dataset
  116. def test_module_alias(self):
  117. # Delegates `span.module` to `sentry_tags[category]`. Maps `"db.redis"` spans to the `"cache"` module
  118. self.store_spans(
  119. [
  120. self.create_span(
  121. {
  122. "op": "db.redis",
  123. "description": "EXEC *",
  124. "sentry_tags": {
  125. "description": "EXEC *",
  126. "category": "db",
  127. "op": "db.redis",
  128. "transaction": "/app/index",
  129. },
  130. },
  131. start_ts=self.ten_mins_ago,
  132. ),
  133. ],
  134. is_eap=self.is_eap,
  135. )
  136. response = self.do_request(
  137. {
  138. "field": ["span.module", "span.description"],
  139. "query": "span.module:cache",
  140. "project": self.project.id,
  141. "dataset": self.dataset,
  142. }
  143. )
  144. assert response.status_code == 200, response.content
  145. data = response.data["data"]
  146. meta = response.data["meta"]
  147. assert len(data) == 1
  148. assert data[0]["span.module"] == "cache"
  149. assert data[0]["span.description"] == "EXEC *"
  150. assert meta["dataset"] == self.dataset
  151. def test_device_class_filter_unknown(self):
  152. self.store_spans(
  153. [
  154. self.create_span({"sentry_tags": {"device.class": ""}}, start_ts=self.ten_mins_ago),
  155. ],
  156. is_eap=self.is_eap,
  157. )
  158. response = self.do_request(
  159. {
  160. "field": ["device.class", "count()"],
  161. "query": "device.class:Unknown",
  162. "orderby": "count()",
  163. "project": self.project.id,
  164. "dataset": self.dataset,
  165. }
  166. )
  167. assert response.status_code == 200, response.content
  168. data = response.data["data"]
  169. meta = response.data["meta"]
  170. assert len(data) == 1
  171. assert data[0]["device.class"] == "Unknown"
  172. assert meta["dataset"] == self.dataset
  173. def test_network_span(self):
  174. self.store_spans(
  175. [
  176. self.create_span(
  177. {
  178. "sentry_tags": {
  179. "action": "GET",
  180. "category": "http",
  181. "description": "GET https://*.resource.com",
  182. "domain": "*.resource.com",
  183. "op": "http.client",
  184. "status_code": "200",
  185. "transaction": "/api/0/data/",
  186. "transaction.method": "GET",
  187. "transaction.op": "http.server",
  188. }
  189. },
  190. start_ts=self.ten_mins_ago,
  191. ),
  192. ],
  193. is_eap=self.is_eap,
  194. )
  195. response = self.do_request(
  196. {
  197. "field": ["span.op", "span.status_code"],
  198. "query": "span.module:http span.status_code:200",
  199. "project": self.project.id,
  200. "dataset": self.dataset,
  201. }
  202. )
  203. assert response.status_code == 200, response.content
  204. data = response.data["data"]
  205. meta = response.data["meta"]
  206. assert len(data) == 1
  207. assert data[0]["span.op"] == "http.client"
  208. assert data[0]["span.status_code"] == "200"
  209. assert meta["dataset"] == self.dataset
  210. def test_inp_span(self):
  211. replay_id = uuid.uuid4().hex
  212. self.store_spans(
  213. [
  214. self.create_span(
  215. {
  216. "sentry_tags": {
  217. "replay_id": replay_id,
  218. "browser.name": "Chrome",
  219. "transaction": "/pageloads/",
  220. }
  221. },
  222. start_ts=self.ten_mins_ago,
  223. ),
  224. ],
  225. is_eap=self.is_eap,
  226. )
  227. response = self.do_request(
  228. {
  229. "field": ["replay.id", "browser.name", "origin.transaction", "count()"],
  230. "query": f"replay.id:{replay_id} AND browser.name:Chrome AND origin.transaction:/pageloads/",
  231. "orderby": "count()",
  232. "project": self.project.id,
  233. "dataset": self.dataset,
  234. }
  235. )
  236. assert response.status_code == 200, response.content
  237. data = response.data["data"]
  238. meta = response.data["meta"]
  239. assert len(data) == 1
  240. assert data[0]["replay.id"] == replay_id
  241. assert data[0]["browser.name"] == "Chrome"
  242. assert data[0]["origin.transaction"] == "/pageloads/"
  243. assert meta["dataset"] == self.dataset
  244. def test_id_filtering(self):
  245. span = self.create_span({"description": "foo"}, start_ts=self.ten_mins_ago)
  246. self.store_span(span, is_eap=self.is_eap)
  247. response = self.do_request(
  248. {
  249. "field": ["description", "count()"],
  250. "query": f"id:{span['span_id']}",
  251. "orderby": "description",
  252. "project": self.project.id,
  253. "dataset": self.dataset,
  254. }
  255. )
  256. assert response.status_code == 200, response.content
  257. data = response.data["data"]
  258. meta = response.data["meta"]
  259. assert len(data) == 1
  260. assert data[0]["description"] == "foo"
  261. assert meta["dataset"] == self.dataset
  262. response = self.do_request(
  263. {
  264. "field": ["description", "count()"],
  265. "query": f"transaction.id:{span['event_id']}",
  266. "orderby": "description",
  267. "project": self.project.id,
  268. "dataset": self.dataset,
  269. }
  270. )
  271. assert response.status_code == 200, response.content
  272. data = response.data["data"]
  273. meta = response.data["meta"]
  274. assert len(data) == 1
  275. assert data[0]["description"] == "foo"
  276. assert meta["dataset"] == self.dataset
  277. def test_span_op_casing(self):
  278. self.store_spans(
  279. [
  280. self.create_span(
  281. {
  282. "sentry_tags": {
  283. "replay_id": "abc123",
  284. "browser.name": "Chrome",
  285. "transaction": "/pageloads/",
  286. "op": "this is a transaction",
  287. }
  288. },
  289. start_ts=self.ten_mins_ago,
  290. ),
  291. ],
  292. is_eap=self.is_eap,
  293. )
  294. response = self.do_request(
  295. {
  296. "field": ["span.op", "count()"],
  297. "query": 'span.op:"ThIs Is a TraNSActiON"',
  298. "orderby": "count()",
  299. "project": self.project.id,
  300. "dataset": self.dataset,
  301. }
  302. )
  303. assert response.status_code == 200, response.content
  304. data = response.data["data"]
  305. meta = response.data["meta"]
  306. assert len(data) == 1
  307. assert data[0]["span.op"] == "this is a transaction"
  308. assert meta["dataset"] == self.dataset
  309. def test_queue_span(self):
  310. self.store_spans(
  311. [
  312. self.create_span(
  313. {
  314. "measurements": {
  315. "messaging.message.body.size": {"value": 1024, "unit": "byte"},
  316. "messaging.message.receive.latency": {
  317. "value": 1000,
  318. "unit": "millisecond",
  319. },
  320. "messaging.message.retry.count": {"value": 2, "unit": "none"},
  321. },
  322. "sentry_tags": {
  323. "transaction": "queue-processor",
  324. "messaging.destination.name": "events",
  325. "messaging.message.id": "abc123",
  326. "trace.status": "ok",
  327. },
  328. },
  329. start_ts=self.ten_mins_ago,
  330. ),
  331. ],
  332. is_eap=self.is_eap,
  333. )
  334. response = self.do_request(
  335. {
  336. "field": [
  337. "transaction",
  338. "messaging.destination.name",
  339. "messaging.message.id",
  340. "measurements.messaging.message.receive.latency",
  341. "measurements.messaging.message.body.size",
  342. "measurements.messaging.message.retry.count",
  343. "trace.status",
  344. "count()",
  345. ],
  346. "query": 'messaging.destination.name:"events"',
  347. "orderby": "count()",
  348. "project": self.project.id,
  349. "dataset": self.dataset,
  350. }
  351. )
  352. assert response.status_code == 200, response.content
  353. data = response.data["data"]
  354. meta = response.data["meta"]
  355. assert len(data) == 1
  356. assert data[0]["transaction"] == "queue-processor"
  357. assert data[0]["messaging.destination.name"] == "events"
  358. assert data[0]["messaging.message.id"] == "abc123"
  359. assert data[0]["trace.status"] == "ok"
  360. assert data[0]["measurements.messaging.message.receive.latency"] == 1000
  361. assert data[0]["measurements.messaging.message.body.size"] == 1024
  362. assert data[0]["measurements.messaging.message.retry.count"] == 2
  363. assert meta["dataset"] == self.dataset
  364. def test_tag_wildcards(self):
  365. self.store_spans(
  366. [
  367. self.create_span(
  368. {"description": "foo", "tags": {"foo": "BaR"}},
  369. start_ts=self.ten_mins_ago,
  370. ),
  371. self.create_span(
  372. {"description": "qux", "tags": {"foo": "QuX"}},
  373. start_ts=self.ten_mins_ago,
  374. ),
  375. ],
  376. is_eap=self.is_eap,
  377. )
  378. for query in [
  379. "foo:b*",
  380. "foo:*r",
  381. "foo:*a*",
  382. "foo:b*r",
  383. ]:
  384. response = self.do_request(
  385. {
  386. "field": ["foo", "count()"],
  387. "query": query,
  388. "project": self.project.id,
  389. "dataset": self.dataset,
  390. }
  391. )
  392. assert response.status_code == 200, response.content
  393. assert response.data["data"] == [{"foo": "BaR", "count()": 1}]
  394. class OrganizationEventsEAPSpanEndpointTest(OrganizationEventsSpanIndexedEndpointTest):
  395. is_eap = True
  396. def test_simple(self):
  397. self.store_spans(
  398. [
  399. self.create_span(
  400. {"description": "foo", "sentry_tags": {"status": "success"}},
  401. start_ts=self.ten_mins_ago,
  402. ),
  403. self.create_span(
  404. {"description": "bar", "sentry_tags": {"status": "invalid_argument"}},
  405. start_ts=self.ten_mins_ago,
  406. ),
  407. ],
  408. is_eap=self.is_eap,
  409. )
  410. response = self.do_request(
  411. {
  412. "field": ["span.status", "description", "count()"],
  413. "query": "",
  414. "orderby": "description",
  415. "project": self.project.id,
  416. "dataset": self.dataset,
  417. }
  418. )
  419. assert response.status_code == 200, response.content
  420. data = response.data["data"]
  421. meta = response.data["meta"]
  422. assert len(data) == 2
  423. assert data == [
  424. {
  425. "span.status": "invalid_argument",
  426. "description": "bar",
  427. "count()": 1,
  428. },
  429. {
  430. "span.status": "success",
  431. "description": "foo",
  432. "count()": 1,
  433. },
  434. ]
  435. assert meta["dataset"] == self.dataset
  436. @pytest.mark.xfail(reason="event_id isn't being written to the new table")
  437. def test_id_filtering(self):
  438. super().test_id_filtering()