test_organization_events_span_indexed.py 14 KB

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