test_organization_events_span_indexed.py 57 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677
  1. import uuid
  2. from datetime import datetime
  3. from unittest import mock
  4. import pytest
  5. from tests.snuba.api.endpoints.test_organization_events import OrganizationEventsEndpointTestBase
  6. class OrganizationEventsSpanIndexedEndpointTest(OrganizationEventsEndpointTestBase):
  7. is_eap = False
  8. use_rpc = False
  9. """Test the indexed spans dataset.
  10. To run this locally you may need to set the ENABLE_SPANS_CONSUMER flag to True in Snuba.
  11. A way to do this is
  12. 1. run: `sentry devservices down snuba`
  13. 2. clone snuba locally
  14. 3. run: `export ENABLE_SPANS_CONSUMER=True`
  15. 4. run snuba
  16. At this point tests should work locally
  17. Once span ingestion is on by default this will no longer need to be done
  18. """
  19. @property
  20. def dataset(self):
  21. if self.is_eap:
  22. return "spans"
  23. else:
  24. return "spansIndexed"
  25. def do_request(self, query, features=None, **kwargs):
  26. query["useRpc"] = "1" if self.use_rpc else "0"
  27. return super().do_request(query, features, **kwargs)
  28. def setUp(self):
  29. super().setUp()
  30. self.features = {
  31. "organizations:starfish-view": True,
  32. }
  33. @pytest.mark.querybuilder
  34. def test_simple(self):
  35. self.store_spans(
  36. [
  37. self.create_span(
  38. {"description": "foo", "sentry_tags": {"status": "success"}},
  39. start_ts=self.ten_mins_ago,
  40. ),
  41. self.create_span(
  42. {"description": "bar", "sentry_tags": {"status": "invalid_argument"}},
  43. start_ts=self.ten_mins_ago,
  44. ),
  45. ],
  46. is_eap=self.is_eap,
  47. )
  48. response = self.do_request(
  49. {
  50. "field": ["span.status", "description", "count()"],
  51. "query": "",
  52. "orderby": "description",
  53. "project": self.project.id,
  54. "dataset": self.dataset,
  55. }
  56. )
  57. assert response.status_code == 200, response.content
  58. data = response.data["data"]
  59. meta = response.data["meta"]
  60. assert len(data) == 2
  61. assert data == [
  62. {
  63. "span.status": "invalid_argument",
  64. "description": "bar",
  65. "count()": 1,
  66. },
  67. {
  68. "span.status": "ok",
  69. "description": "foo",
  70. "count()": 1,
  71. },
  72. ]
  73. assert meta["dataset"] == self.dataset
  74. def test_spm(self):
  75. self.store_spans(
  76. [
  77. self.create_span(
  78. {"description": "foo", "sentry_tags": {"status": "success"}},
  79. start_ts=self.ten_mins_ago,
  80. ),
  81. ],
  82. is_eap=self.is_eap,
  83. )
  84. response = self.do_request(
  85. {
  86. "field": ["description", "spm()"],
  87. "query": "",
  88. "orderby": "description",
  89. "project": self.project.id,
  90. "dataset": self.dataset,
  91. }
  92. )
  93. assert response.status_code == 200, response.content
  94. data = response.data["data"]
  95. meta = response.data["meta"]
  96. assert len(data) == 1
  97. assert data == [
  98. {
  99. "description": "foo",
  100. "spm()": 1 / (90 * 24 * 60),
  101. },
  102. ]
  103. assert meta["dataset"] == self.dataset
  104. def test_id_fields(self):
  105. self.store_spans(
  106. [
  107. self.create_span(
  108. {"description": "foo", "sentry_tags": {"status": "success"}},
  109. start_ts=self.ten_mins_ago,
  110. ),
  111. self.create_span(
  112. {"description": "bar", "sentry_tags": {"status": "invalid_argument"}},
  113. start_ts=self.ten_mins_ago,
  114. ),
  115. ],
  116. is_eap=self.is_eap,
  117. )
  118. response = self.do_request(
  119. {
  120. "field": ["id", "span_id"],
  121. "query": "",
  122. "orderby": "id",
  123. "project": self.project.id,
  124. "dataset": self.dataset,
  125. }
  126. )
  127. assert response.status_code == 200, response.content
  128. data = response.data["data"]
  129. meta = response.data["meta"]
  130. assert len(data) == 2
  131. for obj in data:
  132. assert obj["id"] == obj["span_id"]
  133. assert meta["dataset"] == self.dataset
  134. def test_sentry_tags_vs_tags(self):
  135. self.store_spans(
  136. [
  137. self.create_span(
  138. {"sentry_tags": {"transaction.method": "foo"}}, start_ts=self.ten_mins_ago
  139. ),
  140. ],
  141. is_eap=self.is_eap,
  142. )
  143. response = self.do_request(
  144. {
  145. "field": ["transaction.method", "count()"],
  146. "query": "",
  147. "orderby": "count()",
  148. "project": self.project.id,
  149. "dataset": self.dataset,
  150. }
  151. )
  152. assert response.status_code == 200, response.content
  153. data = response.data["data"]
  154. meta = response.data["meta"]
  155. assert len(data) == 1
  156. assert data[0]["transaction.method"] == "foo"
  157. assert meta["dataset"] == self.dataset
  158. def test_sentry_tags_syntax(self):
  159. self.store_spans(
  160. [
  161. self.create_span(
  162. {"sentry_tags": {"transaction.method": "foo"}}, start_ts=self.ten_mins_ago
  163. ),
  164. ],
  165. is_eap=self.is_eap,
  166. )
  167. response = self.do_request(
  168. {
  169. "field": ["sentry_tags[transaction.method]", "count()"],
  170. "query": "",
  171. "orderby": "count()",
  172. "project": self.project.id,
  173. "dataset": self.dataset,
  174. }
  175. )
  176. assert response.status_code == 200, response.content
  177. data = response.data["data"]
  178. meta = response.data["meta"]
  179. assert len(data) == 1
  180. assert data[0]["sentry_tags[transaction.method]"] == "foo"
  181. assert meta["dataset"] == self.dataset
  182. def test_module_alias(self):
  183. # Delegates `span.module` to `sentry_tags[category]`. Maps `"db.redis"` spans to the `"cache"` module
  184. self.store_spans(
  185. [
  186. self.create_span(
  187. {
  188. "op": "db.redis",
  189. "description": "EXEC *",
  190. "sentry_tags": {
  191. "description": "EXEC *",
  192. "category": "db",
  193. "op": "db.redis",
  194. "transaction": "/app/index",
  195. },
  196. },
  197. start_ts=self.ten_mins_ago,
  198. ),
  199. ],
  200. is_eap=self.is_eap,
  201. )
  202. response = self.do_request(
  203. {
  204. "field": ["span.module", "span.description"],
  205. "query": "span.module:cache",
  206. "project": self.project.id,
  207. "dataset": self.dataset,
  208. }
  209. )
  210. assert response.status_code == 200, response.content
  211. data = response.data["data"]
  212. meta = response.data["meta"]
  213. assert len(data) == 1
  214. assert data[0]["span.module"] == "cache"
  215. assert data[0]["span.description"] == "EXEC *"
  216. assert meta["dataset"] == self.dataset
  217. def test_device_class_filter_unknown(self):
  218. self.store_spans(
  219. [
  220. self.create_span({"sentry_tags": {"device.class": ""}}, start_ts=self.ten_mins_ago),
  221. ],
  222. is_eap=self.is_eap,
  223. )
  224. response = self.do_request(
  225. {
  226. "field": ["device.class", "count()"],
  227. "query": "device.class:Unknown",
  228. "orderby": "count()",
  229. "project": self.project.id,
  230. "dataset": self.dataset,
  231. }
  232. )
  233. assert response.status_code == 200, response.content
  234. data = response.data["data"]
  235. meta = response.data["meta"]
  236. assert len(data) == 1
  237. assert data[0]["device.class"] == "Unknown"
  238. assert meta["dataset"] == self.dataset
  239. def test_network_span(self):
  240. self.store_spans(
  241. [
  242. self.create_span(
  243. {
  244. "sentry_tags": {
  245. "action": "GET",
  246. "category": "http",
  247. "description": "GET https://*.resource.com",
  248. "domain": "*.resource.com",
  249. "op": "http.client",
  250. "status_code": "200",
  251. "transaction": "/api/0/data/",
  252. "transaction.method": "GET",
  253. "transaction.op": "http.server",
  254. }
  255. },
  256. start_ts=self.ten_mins_ago,
  257. ),
  258. ],
  259. is_eap=self.is_eap,
  260. )
  261. response = self.do_request(
  262. {
  263. "field": ["span.op", "span.status_code"],
  264. "query": "span.module:http span.status_code:200",
  265. "project": self.project.id,
  266. "dataset": self.dataset,
  267. }
  268. )
  269. assert response.status_code == 200, response.content
  270. data = response.data["data"]
  271. meta = response.data["meta"]
  272. assert len(data) == 1
  273. assert data[0]["span.op"] == "http.client"
  274. assert data[0]["span.status_code"] == "200"
  275. assert meta["dataset"] == self.dataset
  276. def test_other_category_span(self):
  277. self.store_spans(
  278. [
  279. self.create_span(
  280. {
  281. "sentry_tags": {
  282. "action": "GET",
  283. "category": "alternative",
  284. "description": "GET https://*.resource.com",
  285. "domain": "*.resource.com",
  286. "op": "alternative",
  287. "status_code": "200",
  288. "transaction": "/api/0/data/",
  289. "transaction.method": "GET",
  290. "transaction.op": "http.server",
  291. }
  292. },
  293. start_ts=self.ten_mins_ago,
  294. ),
  295. ],
  296. is_eap=self.is_eap,
  297. )
  298. response = self.do_request(
  299. {
  300. "field": ["span.op", "span.status_code"],
  301. "query": "span.module:other span.status_code:200",
  302. "project": self.project.id,
  303. "dataset": self.dataset,
  304. }
  305. )
  306. assert response.status_code == 200, response.content
  307. data = response.data["data"]
  308. meta = response.data["meta"]
  309. assert len(data) == 1
  310. assert data[0]["span.op"] == "alternative"
  311. assert data[0]["span.status_code"] == "200"
  312. assert meta["dataset"] == self.dataset
  313. def test_inp_span(self):
  314. replay_id = uuid.uuid4().hex
  315. self.store_spans(
  316. [
  317. self.create_span(
  318. {
  319. "sentry_tags": {
  320. "replay_id": replay_id,
  321. "browser.name": "Chrome",
  322. "transaction": "/pageloads/",
  323. }
  324. },
  325. start_ts=self.ten_mins_ago,
  326. ),
  327. ],
  328. is_eap=self.is_eap,
  329. )
  330. response = self.do_request(
  331. {
  332. "field": ["replay.id", "browser.name", "origin.transaction", "count()"],
  333. "query": f"replay.id:{replay_id} AND browser.name:Chrome AND origin.transaction:/pageloads/",
  334. "orderby": "count()",
  335. "project": self.project.id,
  336. "dataset": self.dataset,
  337. }
  338. )
  339. assert response.status_code == 200, response.content
  340. data = response.data["data"]
  341. meta = response.data["meta"]
  342. assert len(data) == 1
  343. assert data[0]["replay.id"] == replay_id
  344. assert data[0]["browser.name"] == "Chrome"
  345. assert data[0]["origin.transaction"] == "/pageloads/"
  346. assert meta["dataset"] == self.dataset
  347. def test_id_filtering(self):
  348. span = self.create_span({"description": "foo"}, start_ts=self.ten_mins_ago)
  349. self.store_span(span, is_eap=self.is_eap)
  350. response = self.do_request(
  351. {
  352. "field": ["description", "count()"],
  353. "query": f"id:{span['span_id']}",
  354. "orderby": "description",
  355. "project": self.project.id,
  356. "dataset": self.dataset,
  357. }
  358. )
  359. assert response.status_code == 200, response.content
  360. data = response.data["data"]
  361. meta = response.data["meta"]
  362. assert len(data) == 1
  363. assert data[0]["description"] == "foo"
  364. assert meta["dataset"] == self.dataset
  365. response = self.do_request(
  366. {
  367. "field": ["description", "count()"],
  368. "query": f"transaction.id:{span['event_id']}",
  369. "orderby": "description",
  370. "project": self.project.id,
  371. "dataset": self.dataset,
  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]["description"] == "foo"
  379. assert meta["dataset"] == self.dataset
  380. def test_span_op_casing(self):
  381. self.store_spans(
  382. [
  383. self.create_span(
  384. {
  385. "sentry_tags": {
  386. "replay_id": "abc123",
  387. "browser.name": "Chrome",
  388. "transaction": "/pageloads/",
  389. "op": "this is a transaction",
  390. }
  391. },
  392. start_ts=self.ten_mins_ago,
  393. ),
  394. ],
  395. is_eap=self.is_eap,
  396. )
  397. response = self.do_request(
  398. {
  399. "field": ["span.op", "count()"],
  400. "query": 'span.op:"ThIs Is a TraNSActiON"',
  401. "orderby": "count()",
  402. "project": self.project.id,
  403. "dataset": self.dataset,
  404. }
  405. )
  406. assert response.status_code == 200, response.content
  407. data = response.data["data"]
  408. meta = response.data["meta"]
  409. assert len(data) == 1
  410. assert data[0]["span.op"] == "this is a transaction"
  411. assert meta["dataset"] == self.dataset
  412. def test_queue_span(self):
  413. self.store_spans(
  414. [
  415. self.create_span(
  416. {
  417. "measurements": {
  418. "messaging.message.body.size": {"value": 1024, "unit": "byte"},
  419. "messaging.message.receive.latency": {
  420. "value": 1000,
  421. "unit": "millisecond",
  422. },
  423. "messaging.message.retry.count": {"value": 2, "unit": "none"},
  424. },
  425. "sentry_tags": {
  426. "transaction": "queue-processor",
  427. "messaging.destination.name": "events",
  428. "messaging.message.id": "abc123",
  429. "trace.status": "ok",
  430. },
  431. },
  432. start_ts=self.ten_mins_ago,
  433. ),
  434. ],
  435. is_eap=self.is_eap,
  436. )
  437. response = self.do_request(
  438. {
  439. "field": [
  440. "transaction",
  441. "messaging.destination.name",
  442. "messaging.message.id",
  443. "measurements.messaging.message.receive.latency",
  444. "measurements.messaging.message.body.size",
  445. "measurements.messaging.message.retry.count",
  446. "trace.status",
  447. "count()",
  448. ],
  449. "query": 'messaging.destination.name:"events"',
  450. "orderby": "count()",
  451. "project": self.project.id,
  452. "dataset": self.dataset,
  453. }
  454. )
  455. assert response.status_code == 200, response.content
  456. data = response.data["data"]
  457. meta = response.data["meta"]
  458. assert len(data) == 1
  459. assert data[0]["transaction"] == "queue-processor"
  460. assert data[0]["messaging.destination.name"] == "events"
  461. assert data[0]["messaging.message.id"] == "abc123"
  462. assert data[0]["trace.status"] == "ok"
  463. assert data[0]["measurements.messaging.message.receive.latency"] == 1000
  464. assert data[0]["measurements.messaging.message.body.size"] == 1024
  465. assert data[0]["measurements.messaging.message.retry.count"] == 2
  466. assert meta["dataset"] == self.dataset
  467. def test_tag_wildcards(self):
  468. self.store_spans(
  469. [
  470. self.create_span(
  471. {"description": "foo", "tags": {"foo": "BaR"}},
  472. start_ts=self.ten_mins_ago,
  473. ),
  474. self.create_span(
  475. {"description": "qux", "tags": {"foo": "QuX"}},
  476. start_ts=self.ten_mins_ago,
  477. ),
  478. ],
  479. is_eap=self.is_eap,
  480. )
  481. for query in [
  482. "foo:b*",
  483. "foo:*r",
  484. "foo:*a*",
  485. "foo:b*r",
  486. ]:
  487. response = self.do_request(
  488. {
  489. "field": ["foo", "count()"],
  490. "query": query,
  491. "project": self.project.id,
  492. "dataset": self.dataset,
  493. }
  494. )
  495. assert response.status_code == 200, response.content
  496. assert response.data["data"] == [{"foo": "BaR", "count()": 1}]
  497. def test_query_for_missing_tag(self):
  498. self.store_spans(
  499. [
  500. self.create_span(
  501. {"description": "foo"},
  502. start_ts=self.ten_mins_ago,
  503. ),
  504. self.create_span(
  505. {"description": "qux", "tags": {"foo": "bar"}},
  506. start_ts=self.ten_mins_ago,
  507. ),
  508. ],
  509. is_eap=self.is_eap,
  510. )
  511. response = self.do_request(
  512. {
  513. "field": ["foo", "count()"],
  514. "query": 'foo:""',
  515. "project": self.project.id,
  516. "dataset": self.dataset,
  517. }
  518. )
  519. assert response.status_code == 200, response.content
  520. assert response.data["data"] == [{"foo": "", "count()": 1}]
  521. def test_count_field_type(self):
  522. response = self.do_request(
  523. {
  524. "field": ["count()"],
  525. "project": self.project.id,
  526. "dataset": self.dataset,
  527. }
  528. )
  529. assert response.status_code == 200, response.content
  530. assert response.data["meta"]["fields"] == {"count()": "integer"}
  531. assert response.data["meta"]["units"] == {"count()": None}
  532. assert response.data["data"] == [{"count()": 0}]
  533. def test_simple_measurements(self):
  534. keys = [
  535. ("app_start_cold", "duration", "millisecond"),
  536. ("app_start_warm", "duration", "millisecond"),
  537. ("frames_frozen", "number", None), # should be integer but keeping it consistent
  538. ("frames_frozen_rate", "percentage", None),
  539. ("frames_slow", "number", None), # should be integer but keeping it consistent
  540. ("frames_slow_rate", "percentage", None),
  541. ("frames_total", "number", None), # should be integer but keeping it consistent
  542. ("time_to_initial_display", "duration", "millisecond"),
  543. ("time_to_full_display", "duration", "millisecond"),
  544. ("stall_count", "number", None), # should be integer but keeping it consistent
  545. ("stall_percentage", "percentage", None),
  546. ("stall_stall_longest_time", "number", None),
  547. ("stall_stall_total_time", "number", None),
  548. ("cls", "number", None),
  549. ("fcp", "duration", "millisecond"),
  550. ("fid", "duration", "millisecond"),
  551. ("fp", "duration", "millisecond"),
  552. ("inp", "duration", "millisecond"),
  553. ("lcp", "duration", "millisecond"),
  554. ("ttfb", "duration", "millisecond"),
  555. ("ttfb.requesttime", "duration", "millisecond"),
  556. ("score.cls", "number", None),
  557. ("score.fcp", "number", None),
  558. ("score.fid", "number", None),
  559. ("score.inp", "number", None),
  560. ("score.lcp", "number", None),
  561. ("score.ttfb", "number", None),
  562. ("score.total", "number", None),
  563. ("score.weight.cls", "number", None),
  564. ("score.weight.fcp", "number", None),
  565. ("score.weight.fid", "number", None),
  566. ("score.weight.inp", "number", None),
  567. ("score.weight.lcp", "number", None),
  568. ("score.weight.ttfb", "number", None),
  569. ("cache.item_size", "number", None),
  570. ("messaging.message.body.size", "number", None),
  571. ("messaging.message.receive.latency", "number", None),
  572. ("messaging.message.retry.count", "number", None),
  573. ]
  574. self.store_spans(
  575. [
  576. self.create_span(
  577. {
  578. "description": "foo",
  579. "sentry_tags": {"status": "success"},
  580. "tags": {"bar": "bar2"},
  581. },
  582. measurements={k: {"value": i + 1} for i, (k, _, _) in enumerate(keys)},
  583. start_ts=self.ten_mins_ago,
  584. ),
  585. ],
  586. is_eap=self.is_eap,
  587. )
  588. for i, (k, type, unit) in enumerate(keys):
  589. key = f"measurements.{k}"
  590. response = self.do_request(
  591. {
  592. "field": [key],
  593. "query": "description:foo",
  594. "project": self.project.id,
  595. "dataset": self.dataset,
  596. }
  597. )
  598. assert response.status_code == 200, response.content
  599. assert response.data["meta"] == {
  600. "dataset": mock.ANY,
  601. "datasetReason": "unchanged",
  602. "fields": {
  603. key: type,
  604. "id": "string",
  605. "project.name": "string",
  606. },
  607. "isMetricsData": False,
  608. "isMetricsExtractedData": False,
  609. "tips": {},
  610. "units": {
  611. key: unit,
  612. "id": None,
  613. "project.name": None,
  614. },
  615. }
  616. assert response.data["data"] == [
  617. {
  618. key: i + 1,
  619. "id": mock.ANY,
  620. "project.name": self.project.slug,
  621. }
  622. ]
  623. def test_environment(self):
  624. self.create_environment(self.project, name="prod")
  625. self.create_environment(self.project, name="test")
  626. self.store_spans(
  627. [
  628. self.create_span(
  629. {"description": "foo", "sentry_tags": {"environment": "prod"}},
  630. start_ts=self.ten_mins_ago,
  631. ),
  632. self.create_span(
  633. {"description": "foo", "sentry_tags": {"environment": "test"}},
  634. start_ts=self.ten_mins_ago,
  635. ),
  636. ],
  637. is_eap=self.is_eap,
  638. )
  639. response = self.do_request(
  640. {
  641. "field": ["environment", "count()"],
  642. "project": self.project.id,
  643. "environment": "prod",
  644. "dataset": self.dataset,
  645. }
  646. )
  647. assert response.status_code == 200, response.content
  648. assert response.data["data"] == [
  649. {"environment": "prod", "count()": 1},
  650. ]
  651. def test_transaction(self):
  652. self.store_spans(
  653. [
  654. self.create_span(
  655. {"description": "foo", "sentry_tags": {"transaction": "bar"}},
  656. start_ts=self.ten_mins_ago,
  657. ),
  658. ],
  659. is_eap=self.is_eap,
  660. )
  661. response = self.do_request(
  662. {
  663. "field": ["description", "count()"],
  664. "query": "transaction:bar",
  665. "orderby": "description",
  666. "project": self.project.id,
  667. "dataset": self.dataset,
  668. }
  669. )
  670. assert response.status_code == 200, response.content
  671. data = response.data["data"]
  672. meta = response.data["meta"]
  673. assert len(data) == 1
  674. assert data == [
  675. {
  676. "description": "foo",
  677. "count()": 1,
  678. },
  679. ]
  680. assert meta["dataset"] == self.dataset
  681. def test_orderby_alias(self):
  682. self.store_spans(
  683. [
  684. self.create_span(
  685. {"description": "foo", "sentry_tags": {"status": "success"}},
  686. start_ts=self.ten_mins_ago,
  687. ),
  688. self.create_span(
  689. {"description": "bar", "sentry_tags": {"status": "invalid_argument"}},
  690. duration=2000,
  691. start_ts=self.ten_mins_ago,
  692. ),
  693. ],
  694. is_eap=self.is_eap,
  695. )
  696. response = self.do_request(
  697. {
  698. "field": ["span.description", "sum(span.self_time)"],
  699. "query": "",
  700. "orderby": "sum_span_self_time",
  701. "project": self.project.id,
  702. "dataset": self.dataset,
  703. }
  704. )
  705. assert response.status_code == 200, response.content
  706. data = response.data["data"]
  707. meta = response.data["meta"]
  708. assert len(data) == 2
  709. assert data == [
  710. {
  711. "span.description": "foo",
  712. "sum(span.self_time)": 1000,
  713. },
  714. {
  715. "span.description": "bar",
  716. "sum(span.self_time)": 2000,
  717. },
  718. ]
  719. assert meta["dataset"] == self.dataset
  720. @pytest.mark.querybuilder
  721. def test_explore_sample_query(self):
  722. spans = [
  723. self.create_span(
  724. {"description": "foo", "sentry_tags": {"status": "success"}},
  725. start_ts=self.ten_mins_ago,
  726. ),
  727. self.create_span(
  728. {"description": "bar", "sentry_tags": {"status": "invalid_argument"}},
  729. start_ts=self.nine_mins_ago,
  730. ),
  731. ]
  732. self.store_spans(
  733. spans,
  734. is_eap=self.is_eap,
  735. )
  736. response = self.do_request(
  737. {
  738. "field": [
  739. "id",
  740. "project",
  741. "span.op",
  742. "span.description",
  743. "span.duration",
  744. "timestamp",
  745. "trace",
  746. "transaction.span_id",
  747. ],
  748. # This is to skip INP spans
  749. "query": "!transaction.span_id:00",
  750. "orderby": "timestamp",
  751. "statsPeriod": "1h",
  752. "project": self.project.id,
  753. "dataset": self.dataset,
  754. }
  755. )
  756. assert response.status_code == 200, response.content
  757. data = response.data["data"]
  758. meta = response.data["meta"]
  759. assert len(data) == 2
  760. for source, result in zip(spans, data):
  761. assert result["id"] == source["span_id"], "id"
  762. assert result["span.duration"] == 1000.0, "duration"
  763. assert result["span.op"] == "", "op"
  764. assert result["span.description"] == source["description"], "description"
  765. assert datetime.fromisoformat(result["timestamp"]).timestamp() == pytest.approx(
  766. source["end_timestamp_precise"], abs=5
  767. ), "timestamp"
  768. assert result["transaction.span_id"] == source["segment_id"], "transaction.span_id"
  769. assert result["project"] == result["project.name"] == self.project.slug, "project"
  770. assert meta["dataset"] == self.dataset
  771. def test_span_status(self):
  772. self.store_spans(
  773. [
  774. self.create_span(
  775. {"description": "foo", "sentry_tags": {"status": "internal_error"}},
  776. start_ts=self.ten_mins_ago,
  777. ),
  778. ],
  779. is_eap=self.is_eap,
  780. )
  781. response = self.do_request(
  782. {
  783. "field": ["description", "count()"],
  784. "query": "span.status:internal_error",
  785. "orderby": "description",
  786. "project": self.project.id,
  787. "dataset": self.dataset,
  788. }
  789. )
  790. assert response.status_code == 200, response.content
  791. data = response.data["data"]
  792. meta = response.data["meta"]
  793. assert len(data) == 1
  794. assert data == [
  795. {
  796. "description": "foo",
  797. "count()": 1,
  798. },
  799. ]
  800. assert meta["dataset"] == self.dataset
  801. class OrganizationEventsEAPSpanEndpointTest(OrganizationEventsSpanIndexedEndpointTest):
  802. is_eap = True
  803. use_rpc = False
  804. def test_simple(self):
  805. self.store_spans(
  806. [
  807. self.create_span(
  808. {"description": "foo", "sentry_tags": {"status": "success"}},
  809. start_ts=self.ten_mins_ago,
  810. ),
  811. self.create_span(
  812. {"description": "bar", "sentry_tags": {"status": "invalid_argument"}},
  813. start_ts=self.ten_mins_ago,
  814. ),
  815. ],
  816. is_eap=self.is_eap,
  817. )
  818. response = self.do_request(
  819. {
  820. "field": ["span.status", "description", "count()"],
  821. "query": "",
  822. "orderby": "description",
  823. "project": self.project.id,
  824. "dataset": self.dataset,
  825. }
  826. )
  827. assert response.status_code == 200, response.content
  828. data = response.data["data"]
  829. meta = response.data["meta"]
  830. assert len(data) == 2
  831. assert data == [
  832. {
  833. "span.status": "invalid_argument",
  834. "description": "bar",
  835. "count()": 1,
  836. },
  837. {
  838. "span.status": "success",
  839. "description": "foo",
  840. "count()": 1,
  841. },
  842. ]
  843. assert meta["dataset"] == self.dataset
  844. @pytest.mark.xfail(reason="event_id isn't being written to the new table")
  845. def test_id_filtering(self):
  846. super().test_id_filtering()
  847. def test_span_duration(self):
  848. spans = [
  849. self.create_span(
  850. {"description": "bar", "sentry_tags": {"status": "invalid_argument"}},
  851. start_ts=self.ten_mins_ago,
  852. ),
  853. self.create_span(
  854. {"description": "foo", "sentry_tags": {"status": "success"}},
  855. start_ts=self.ten_mins_ago,
  856. ),
  857. ]
  858. self.store_spans(spans, is_eap=self.is_eap)
  859. response = self.do_request(
  860. {
  861. "field": ["span.duration", "description"],
  862. "query": "",
  863. "orderby": "description",
  864. "project": self.project.id,
  865. "dataset": self.dataset,
  866. }
  867. )
  868. assert response.status_code == 200, response.content
  869. data = response.data["data"]
  870. meta = response.data["meta"]
  871. assert len(data) == 2
  872. assert data == [
  873. {
  874. "span.duration": 1000.0,
  875. "description": "bar",
  876. "project.name": self.project.slug,
  877. "id": spans[0]["span_id"],
  878. },
  879. {
  880. "span.duration": 1000.0,
  881. "description": "foo",
  882. "project.name": self.project.slug,
  883. "id": spans[1]["span_id"],
  884. },
  885. ]
  886. assert meta["dataset"] == self.dataset
  887. def test_aggregate_numeric_attr_weighted(self):
  888. self.store_spans(
  889. [
  890. self.create_span(
  891. {
  892. "description": "foo",
  893. "sentry_tags": {"status": "success"},
  894. "tags": {"bar": "bar1"},
  895. },
  896. start_ts=self.ten_mins_ago,
  897. ),
  898. self.create_span(
  899. {
  900. "description": "foo",
  901. "sentry_tags": {"status": "success"},
  902. "tags": {"bar": "bar2"},
  903. },
  904. measurements={"foo": {"value": 5}},
  905. start_ts=self.ten_mins_ago,
  906. ),
  907. self.create_span(
  908. {
  909. "description": "foo",
  910. "sentry_tags": {"status": "success"},
  911. "tags": {"bar": "bar3"},
  912. },
  913. start_ts=self.ten_mins_ago,
  914. ),
  915. ],
  916. is_eap=self.is_eap,
  917. )
  918. response = self.do_request(
  919. {
  920. "field": [
  921. "description",
  922. "count_unique(bar)",
  923. "count_unique(tags[bar])",
  924. "count_unique(tags[bar,string])",
  925. "count()",
  926. "count(span.duration)",
  927. "count(tags[foo, number])",
  928. "sum(tags[foo,number])",
  929. "avg(tags[foo,number])",
  930. "p50(tags[foo,number])",
  931. "p75(tags[foo,number])",
  932. "p95(tags[foo,number])",
  933. "p99(tags[foo,number])",
  934. "p100(tags[foo,number])",
  935. "min(tags[foo,number])",
  936. "max(tags[foo,number])",
  937. ],
  938. "query": "",
  939. "orderby": "description",
  940. "project": self.project.id,
  941. "dataset": self.dataset,
  942. }
  943. )
  944. assert response.status_code == 200, response.content
  945. assert len(response.data["data"]) == 1
  946. data = response.data["data"]
  947. assert data[0] == {
  948. "description": "foo",
  949. "count_unique(bar)": 3,
  950. "count_unique(tags[bar])": 3,
  951. "count_unique(tags[bar,string])": 3,
  952. "count()": 3,
  953. "count(span.duration)": 3,
  954. "count(tags[foo, number])": 1,
  955. "sum(tags[foo,number])": 5.0,
  956. "avg(tags[foo,number])": 5.0,
  957. "p50(tags[foo,number])": 5.0,
  958. "p75(tags[foo,number])": 5.0,
  959. "p95(tags[foo,number])": 5.0,
  960. "p99(tags[foo,number])": 5.0,
  961. "p100(tags[foo,number])": 5.0,
  962. "min(tags[foo,number])": 5.0,
  963. "max(tags[foo,number])": 5.0,
  964. }
  965. def test_numeric_attr_without_space(self):
  966. self.store_spans(
  967. [
  968. self.create_span(
  969. {
  970. "description": "foo",
  971. "sentry_tags": {"status": "success"},
  972. "tags": {"foo": "five"},
  973. },
  974. measurements={"foo": {"value": 5}},
  975. start_ts=self.ten_mins_ago,
  976. ),
  977. ],
  978. is_eap=self.is_eap,
  979. )
  980. response = self.do_request(
  981. {
  982. "field": ["description", "tags[foo,number]", "tags[foo,string]", "tags[foo]"],
  983. "query": "",
  984. "orderby": "description",
  985. "project": self.project.id,
  986. "dataset": self.dataset,
  987. }
  988. )
  989. assert response.status_code == 200, response.content
  990. assert len(response.data["data"]) == 1
  991. data = response.data["data"]
  992. assert data[0]["tags[foo,number]"] == 5
  993. assert data[0]["tags[foo,string]"] == "five"
  994. assert data[0]["tags[foo]"] == "five"
  995. def test_numeric_attr_with_spaces(self):
  996. self.store_spans(
  997. [
  998. self.create_span(
  999. {
  1000. "description": "foo",
  1001. "sentry_tags": {"status": "success"},
  1002. "tags": {"foo": "five"},
  1003. },
  1004. measurements={"foo": {"value": 5}},
  1005. start_ts=self.ten_mins_ago,
  1006. ),
  1007. ],
  1008. is_eap=self.is_eap,
  1009. )
  1010. response = self.do_request(
  1011. {
  1012. "field": ["description", "tags[foo, number]", "tags[foo, string]", "tags[foo]"],
  1013. "query": "",
  1014. "orderby": "description",
  1015. "project": self.project.id,
  1016. "dataset": self.dataset,
  1017. }
  1018. )
  1019. assert response.status_code == 200, response.content
  1020. assert len(response.data["data"]) == 1
  1021. data = response.data["data"]
  1022. assert data[0]["tags[foo, number]"] == 5
  1023. assert data[0]["tags[foo, string]"] == "five"
  1024. assert data[0]["tags[foo]"] == "five"
  1025. def test_numeric_attr_filtering(self):
  1026. self.store_spans(
  1027. [
  1028. self.create_span(
  1029. {
  1030. "description": "foo",
  1031. "sentry_tags": {"status": "success"},
  1032. "tags": {"foo": "five"},
  1033. },
  1034. measurements={"foo": {"value": 5}},
  1035. start_ts=self.ten_mins_ago,
  1036. ),
  1037. self.create_span(
  1038. {"description": "bar", "sentry_tags": {"status": "success", "foo": "five"}},
  1039. measurements={"foo": {"value": 8}},
  1040. start_ts=self.ten_mins_ago,
  1041. ),
  1042. ],
  1043. is_eap=self.is_eap,
  1044. )
  1045. response = self.do_request(
  1046. {
  1047. "field": ["description", "tags[foo,number]"],
  1048. "query": "tags[foo,number]:5",
  1049. "orderby": "description",
  1050. "project": self.project.id,
  1051. "dataset": self.dataset,
  1052. }
  1053. )
  1054. assert response.status_code == 200, response.content
  1055. assert len(response.data["data"]) == 1
  1056. data = response.data["data"]
  1057. assert data[0]["tags[foo,number]"] == 5
  1058. assert data[0]["description"] == "foo"
  1059. def test_long_attr_name(self):
  1060. response = self.do_request(
  1061. {
  1062. "field": ["description", "z" * 201],
  1063. "query": "",
  1064. "orderby": "description",
  1065. "project": self.project.id,
  1066. "dataset": self.dataset,
  1067. }
  1068. )
  1069. assert response.status_code == 400, response.content
  1070. assert "Is Too Long" in response.data["detail"].title()
  1071. def test_numeric_attr_orderby(self):
  1072. self.store_spans(
  1073. [
  1074. self.create_span(
  1075. {
  1076. "description": "baz",
  1077. "sentry_tags": {"status": "success"},
  1078. "tags": {"foo": "five"},
  1079. },
  1080. measurements={"foo": {"value": 71}},
  1081. start_ts=self.ten_mins_ago,
  1082. ),
  1083. self.create_span(
  1084. {
  1085. "description": "foo",
  1086. "sentry_tags": {"status": "success"},
  1087. "tags": {"foo": "five"},
  1088. },
  1089. measurements={"foo": {"value": 5}},
  1090. start_ts=self.ten_mins_ago,
  1091. ),
  1092. self.create_span(
  1093. {
  1094. "description": "bar",
  1095. "sentry_tags": {"status": "success"},
  1096. "tags": {"foo": "five"},
  1097. },
  1098. measurements={"foo": {"value": 8}},
  1099. start_ts=self.ten_mins_ago,
  1100. ),
  1101. ],
  1102. is_eap=self.is_eap,
  1103. )
  1104. response = self.do_request(
  1105. {
  1106. "field": ["description", "tags[foo,number]"],
  1107. "query": "",
  1108. "orderby": ["tags[foo,number]"],
  1109. "project": self.project.id,
  1110. "dataset": self.dataset,
  1111. }
  1112. )
  1113. assert response.status_code == 200, response.content
  1114. assert len(response.data["data"]) == 3
  1115. data = response.data["data"]
  1116. assert data[0]["tags[foo,number]"] == 5
  1117. assert data[0]["description"] == "foo"
  1118. assert data[1]["tags[foo,number]"] == 8
  1119. assert data[1]["description"] == "bar"
  1120. assert data[2]["tags[foo,number]"] == 71
  1121. assert data[2]["description"] == "baz"
  1122. def test_aggregate_numeric_attr(self):
  1123. self.store_spans(
  1124. [
  1125. self.create_span(
  1126. {
  1127. "description": "foo",
  1128. "sentry_tags": {"status": "success"},
  1129. "tags": {"bar": "bar1"},
  1130. },
  1131. start_ts=self.ten_mins_ago,
  1132. ),
  1133. self.create_span(
  1134. {
  1135. "description": "foo",
  1136. "sentry_tags": {"status": "success"},
  1137. "tags": {"bar": "bar2"},
  1138. },
  1139. measurements={"foo": {"value": 5}},
  1140. start_ts=self.ten_mins_ago,
  1141. ),
  1142. ],
  1143. is_eap=self.is_eap,
  1144. )
  1145. response = self.do_request(
  1146. {
  1147. "field": [
  1148. "description",
  1149. "count_unique(bar)",
  1150. "count_unique(tags[bar])",
  1151. "count_unique(tags[bar,string])",
  1152. "count()",
  1153. "count(span.duration)",
  1154. "count(tags[foo, number])",
  1155. "sum(tags[foo,number])",
  1156. "avg(tags[foo,number])",
  1157. "p50(tags[foo,number])",
  1158. "p75(tags[foo,number])",
  1159. "p95(tags[foo,number])",
  1160. "p99(tags[foo,number])",
  1161. "p100(tags[foo,number])",
  1162. "min(tags[foo,number])",
  1163. "max(tags[foo,number])",
  1164. ],
  1165. "query": "",
  1166. "orderby": "description",
  1167. "project": self.project.id,
  1168. "dataset": self.dataset,
  1169. }
  1170. )
  1171. assert response.status_code == 200, response.content
  1172. assert len(response.data["data"]) == 1
  1173. data = response.data["data"]
  1174. assert data[0] == {
  1175. "description": "foo",
  1176. "count_unique(bar)": 2,
  1177. "count_unique(tags[bar])": 2,
  1178. "count_unique(tags[bar,string])": 2,
  1179. "count()": 2,
  1180. "count(span.duration)": 2,
  1181. "count(tags[foo, number])": 1,
  1182. "sum(tags[foo,number])": 5.0,
  1183. "avg(tags[foo,number])": 5.0,
  1184. "p50(tags[foo,number])": 5.0,
  1185. "p75(tags[foo,number])": 5.0,
  1186. "p95(tags[foo,number])": 5.0,
  1187. "p99(tags[foo,number])": 5.0,
  1188. "p100(tags[foo,number])": 5.0,
  1189. "min(tags[foo,number])": 5.0,
  1190. "max(tags[foo,number])": 5.0,
  1191. }
  1192. def test_margin_of_error(self):
  1193. total_samples = 10
  1194. in_group = 5
  1195. spans = []
  1196. for _ in range(in_group):
  1197. spans.append(
  1198. self.create_span(
  1199. {
  1200. "description": "foo",
  1201. "sentry_tags": {"status": "success"},
  1202. "measurements": {"client_sample_rate": {"value": 0.00001}},
  1203. },
  1204. start_ts=self.ten_mins_ago,
  1205. )
  1206. )
  1207. for _ in range(total_samples - in_group):
  1208. spans.append(
  1209. self.create_span(
  1210. {
  1211. "description": "bar",
  1212. "sentry_tags": {"status": "success"},
  1213. "measurements": {"client_sample_rate": {"value": 0.00001}},
  1214. },
  1215. )
  1216. )
  1217. self.store_spans(
  1218. spans,
  1219. is_eap=self.is_eap,
  1220. )
  1221. response = self.do_request(
  1222. {
  1223. "field": [
  1224. "margin_of_error()",
  1225. "lower_count_limit()",
  1226. "upper_count_limit()",
  1227. "count()",
  1228. ],
  1229. "query": "description:foo",
  1230. "project": self.project.id,
  1231. "dataset": self.dataset,
  1232. }
  1233. )
  1234. assert response.status_code == 200, response.content
  1235. assert len(response.data["data"]) == 1
  1236. data = response.data["data"][0]
  1237. margin_of_error = data["margin_of_error()"]
  1238. lower_limit = data["lower_count_limit()"]
  1239. upper_limit = data["upper_count_limit()"]
  1240. extrapolated = data["count()"]
  1241. assert margin_of_error == pytest.approx(0.306, rel=1e-1)
  1242. # How to read this; these results mean that the extrapolated count is
  1243. # 500k, with a lower estimated bound of ~200k, and an upper bound of 800k
  1244. assert lower_limit == pytest.approx(190_000, abs=5000)
  1245. assert extrapolated == pytest.approx(500_000, abs=5000)
  1246. assert upper_limit == pytest.approx(810_000, abs=5000)
  1247. def test_skip_aggregate_conditions_option(self):
  1248. span_1 = self.create_span(
  1249. {"description": "foo", "sentry_tags": {"status": "success"}},
  1250. start_ts=self.ten_mins_ago,
  1251. )
  1252. span_2 = self.create_span(
  1253. {"description": "bar", "sentry_tags": {"status": "invalid_argument"}},
  1254. start_ts=self.ten_mins_ago,
  1255. )
  1256. self.store_spans(
  1257. [span_1, span_2],
  1258. is_eap=self.is_eap,
  1259. )
  1260. response = self.do_request(
  1261. {
  1262. "field": ["description"],
  1263. "query": "description:foo count():>1",
  1264. "orderby": "description",
  1265. "project": self.project.id,
  1266. "dataset": self.dataset,
  1267. "allowAggregateConditions": "0",
  1268. }
  1269. )
  1270. assert response.status_code == 200, response.content
  1271. data = response.data["data"]
  1272. meta = response.data["meta"]
  1273. assert len(data) == 1
  1274. assert data == [
  1275. {
  1276. "description": "foo",
  1277. "project.name": self.project.slug,
  1278. "id": span_1["span_id"],
  1279. },
  1280. ]
  1281. assert meta["dataset"] == self.dataset
  1282. def test_span_data_fields_http_resource(self):
  1283. self.store_spans(
  1284. [
  1285. self.create_span(
  1286. {
  1287. "op": "resource.img",
  1288. "description": "/image/",
  1289. "data": {
  1290. "http.decoded_response_content_length": 1,
  1291. "http.response_content_length": 2,
  1292. "http.response_transfer_size": 3,
  1293. },
  1294. },
  1295. start_ts=self.ten_mins_ago,
  1296. ),
  1297. ],
  1298. is_eap=self.is_eap,
  1299. )
  1300. response = self.do_request(
  1301. {
  1302. "field": [
  1303. "http.decoded_response_content_length",
  1304. "http.response_content_length",
  1305. "http.response_transfer_size",
  1306. ],
  1307. "project": self.project.id,
  1308. "dataset": self.dataset,
  1309. "allowAggregateConditions": "0",
  1310. }
  1311. )
  1312. assert response.status_code == 200, response.content
  1313. assert response.data["data"] == [
  1314. {
  1315. "http.decoded_response_content_length": 1,
  1316. "http.response_content_length": 2,
  1317. "http.response_transfer_size": 3,
  1318. "project.name": self.project.slug,
  1319. "id": mock.ANY,
  1320. },
  1321. ]
  1322. assert response.data["meta"] == {
  1323. "dataset": mock.ANY,
  1324. "datasetReason": "unchanged",
  1325. "fields": {
  1326. "http.decoded_response_content_length": "size",
  1327. "http.response_content_length": "size",
  1328. "http.response_transfer_size": "size",
  1329. "id": "string",
  1330. "project.name": "string",
  1331. },
  1332. "isMetricsData": False,
  1333. "isMetricsExtractedData": False,
  1334. "tips": {},
  1335. "units": {
  1336. "http.decoded_response_content_length": "byte",
  1337. "http.response_content_length": "byte",
  1338. "http.response_transfer_size": "byte",
  1339. "id": None,
  1340. "project.name": None,
  1341. },
  1342. }
  1343. class OrganizationEventsEAPRPCSpanEndpointTest(OrganizationEventsEAPSpanEndpointTest):
  1344. """These tests aren't fully passing yet, currently inheriting xfail from the eap tests"""
  1345. is_eap = True
  1346. use_rpc = True
  1347. def test_extrapolation(self):
  1348. """Extrapolation only changes the number when there's a sample rate"""
  1349. spans = []
  1350. spans.append(
  1351. self.create_span(
  1352. {
  1353. "description": "foo",
  1354. "sentry_tags": {"status": "success"},
  1355. "measurements": {"client_sample_rate": {"value": 0.1}},
  1356. },
  1357. start_ts=self.ten_mins_ago,
  1358. )
  1359. )
  1360. spans.append(
  1361. self.create_span(
  1362. {
  1363. "description": "bar",
  1364. "sentry_tags": {"status": "success"},
  1365. },
  1366. start_ts=self.ten_mins_ago,
  1367. )
  1368. )
  1369. self.store_spans(spans, is_eap=self.is_eap)
  1370. response = self.do_request(
  1371. {
  1372. "field": ["description", "count()"],
  1373. "orderby": "-count()",
  1374. "query": "",
  1375. "project": self.project.id,
  1376. "dataset": self.dataset,
  1377. }
  1378. )
  1379. assert response.status_code == 200, response.content
  1380. data = response.data["data"]
  1381. confidence = response.data["confidence"]
  1382. assert len(data) == 2
  1383. assert len(confidence) == 2
  1384. assert data[0]["count()"] == 10
  1385. assert confidence[0]["count()"] == "low"
  1386. assert data[1]["count()"] == 1
  1387. # Skipping this assert for now, IMO confidence for "bar" should be high, but checking with sns
  1388. # assert confidence[1]["count()"] == "high"
  1389. def test_span_duration(self):
  1390. spans = [
  1391. self.create_span(
  1392. {"description": "bar", "sentry_tags": {"status": "invalid_argument"}},
  1393. start_ts=self.ten_mins_ago,
  1394. ),
  1395. self.create_span(
  1396. {"description": "foo", "sentry_tags": {"status": "success"}},
  1397. start_ts=self.ten_mins_ago,
  1398. ),
  1399. ]
  1400. self.store_spans(spans, is_eap=self.is_eap)
  1401. response = self.do_request(
  1402. {
  1403. "field": ["span.duration", "description"],
  1404. "query": "",
  1405. "orderby": "description",
  1406. "project": self.project.id,
  1407. "dataset": self.dataset,
  1408. }
  1409. )
  1410. assert response.status_code == 200, response.content
  1411. data = response.data["data"]
  1412. meta = response.data["meta"]
  1413. assert len(data) == 2
  1414. assert data == [
  1415. {
  1416. "span.duration": 1000.0,
  1417. "description": "bar",
  1418. "project.name": self.project.slug,
  1419. "id": spans[0]["span_id"],
  1420. },
  1421. {
  1422. "span.duration": 1000.0,
  1423. "description": "foo",
  1424. "project.name": self.project.slug,
  1425. "id": spans[1]["span_id"],
  1426. },
  1427. ]
  1428. assert meta["dataset"] == self.dataset
  1429. @pytest.mark.xfail(reason="weighted functions will not be moved to the RPC")
  1430. def test_aggregate_numeric_attr_weighted(self):
  1431. super().test_aggregate_numeric_attr_weighted()
  1432. def test_aggregate_numeric_attr(self):
  1433. self.store_spans(
  1434. [
  1435. self.create_span(
  1436. {
  1437. "description": "foo",
  1438. "sentry_tags": {"status": "success"},
  1439. "tags": {"bar": "bar1"},
  1440. },
  1441. start_ts=self.ten_mins_ago,
  1442. ),
  1443. self.create_span(
  1444. {
  1445. "description": "foo",
  1446. "sentry_tags": {"status": "success"},
  1447. "tags": {"bar": "bar2"},
  1448. },
  1449. measurements={"foo": {"value": 5}},
  1450. start_ts=self.ten_mins_ago,
  1451. ),
  1452. ],
  1453. is_eap=self.is_eap,
  1454. )
  1455. response = self.do_request(
  1456. {
  1457. "field": [
  1458. "description",
  1459. "count_unique(bar)",
  1460. "count_unique(tags[bar])",
  1461. "count_unique(tags[bar,string])",
  1462. "count()",
  1463. "count(span.duration)",
  1464. "count(tags[foo, number])",
  1465. "sum(tags[foo,number])",
  1466. "avg(tags[foo,number])",
  1467. "p50(tags[foo,number])",
  1468. "p75(tags[foo,number])",
  1469. "p95(tags[foo,number])",
  1470. "p99(tags[foo,number])",
  1471. "p100(tags[foo,number])",
  1472. "min(tags[foo,number])",
  1473. "max(tags[foo,number])",
  1474. ],
  1475. "query": "",
  1476. "orderby": "description",
  1477. "project": self.project.id,
  1478. "dataset": self.dataset,
  1479. }
  1480. )
  1481. assert response.status_code == 200, response.content
  1482. assert len(response.data["data"]) == 1
  1483. data = response.data["data"]
  1484. assert data[0] == {
  1485. "description": "foo",
  1486. "count_unique(bar)": 2,
  1487. "count_unique(tags[bar])": 2,
  1488. "count_unique(tags[bar,string])": 2,
  1489. "count()": 2,
  1490. "count(span.duration)": 2,
  1491. "count(tags[foo, number])": 1,
  1492. "sum(tags[foo,number])": 5.0,
  1493. "avg(tags[foo,number])": 5.0,
  1494. "p50(tags[foo,number])": 5.0,
  1495. "p75(tags[foo,number])": 5.0,
  1496. "p95(tags[foo,number])": 5.0,
  1497. "p99(tags[foo,number])": 5.0,
  1498. "p100(tags[foo,number])": 5.0,
  1499. "min(tags[foo,number])": 5.0,
  1500. "max(tags[foo,number])": 5.0,
  1501. }
  1502. @pytest.mark.xfail(reason="margin will not be moved to the RPC")
  1503. def test_margin_of_error(self):
  1504. super().test_margin_of_error()
  1505. @pytest.mark.xfail(reason="rpc not handling attr_str vs attr_num with same alias")
  1506. def test_numeric_attr_without_space(self):
  1507. super().test_numeric_attr_without_space()
  1508. @pytest.mark.xfail(reason="rpc not handling attr_str vs attr_num with same alias")
  1509. def test_numeric_attr_with_spaces(self):
  1510. super().test_numeric_attr_with_spaces()
  1511. @pytest.mark.xfail(reason="module not migrated over")
  1512. def test_module_alias(self):
  1513. super().test_module_alias()
  1514. @pytest.mark.xfail(reason="wip: not implemented yet")
  1515. def test_inp_span(self):
  1516. super().test_inp_span()
  1517. @pytest.mark.xfail(reason="wip: not implemented yet")
  1518. def test_network_span(self):
  1519. super().test_network_span()
  1520. @pytest.mark.xfail(reason="wip: not implemented yet")
  1521. def test_other_category_span(self):
  1522. super().test_other_category_span()
  1523. @pytest.mark.xfail(reason="wip: not implemented yet")
  1524. def test_queue_span(self):
  1525. super().test_queue_span()
  1526. @pytest.mark.xfail(reason="wip: not implemented yet")
  1527. def test_sentry_tags_syntax(self):
  1528. super().test_sentry_tags_syntax()
  1529. @pytest.mark.xfail(reason="wip: not implemented yet")
  1530. def test_span_op_casing(self):
  1531. super().test_span_op_casing()
  1532. def test_tag_wildcards(self):
  1533. self.store_spans(
  1534. [
  1535. self.create_span(
  1536. {"description": "foo", "tags": {"foo": "bar"}},
  1537. start_ts=self.ten_mins_ago,
  1538. ),
  1539. self.create_span(
  1540. {"description": "qux", "tags": {"foo": "qux"}},
  1541. start_ts=self.ten_mins_ago,
  1542. ),
  1543. ],
  1544. is_eap=self.is_eap,
  1545. )
  1546. for query in [
  1547. "foo:b*",
  1548. "foo:*r",
  1549. "foo:*a*",
  1550. "foo:b*r",
  1551. ]:
  1552. response = self.do_request(
  1553. {
  1554. "field": ["foo", "count()"],
  1555. "query": query,
  1556. "project": self.project.id,
  1557. "dataset": self.dataset,
  1558. }
  1559. )
  1560. assert response.status_code == 200, response.content
  1561. assert response.data["data"] == [{"foo": "bar", "count()": 1}]
  1562. @pytest.mark.xfail(reason="rate not implemented yet")
  1563. def test_spm(self):
  1564. super().test_spm()