test_organization_events_span_indexed.py 13 KB

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