parsing.test.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  1. import { buildVisualQueryFromString } from './parsing';
  2. import { PromVisualQuery } from './types';
  3. describe('buildVisualQueryFromString', () => {
  4. it('creates no errors for empty query', () => {
  5. expect(buildVisualQueryFromString('')).toEqual(
  6. noErrors({
  7. labels: [],
  8. operations: [],
  9. metric: '',
  10. })
  11. );
  12. });
  13. it('parses simple query', () => {
  14. expect(buildVisualQueryFromString('counters_logins{app="frontend"}')).toEqual(
  15. noErrors({
  16. metric: 'counters_logins',
  17. labels: [
  18. {
  19. op: '=',
  20. value: 'frontend',
  21. label: 'app',
  22. },
  23. ],
  24. operations: [],
  25. })
  26. );
  27. });
  28. it('parses query with rate and interval', () => {
  29. expect(buildVisualQueryFromString('rate(counters_logins{app="frontend"}[5m])')).toEqual(
  30. noErrors({
  31. metric: 'counters_logins',
  32. labels: [
  33. {
  34. op: '=',
  35. value: 'frontend',
  36. label: 'app',
  37. },
  38. ],
  39. operations: [
  40. {
  41. id: 'rate',
  42. params: ['5m'],
  43. },
  44. ],
  45. })
  46. );
  47. });
  48. it('parses query with nested query and interval variable', () => {
  49. expect(
  50. buildVisualQueryFromString(
  51. 'avg(rate(access_evaluation_duration_count{instance="host.docker.internal:3000"}[$__rate_interval]))'
  52. )
  53. ).toEqual(
  54. noErrors({
  55. metric: 'access_evaluation_duration_count',
  56. labels: [
  57. {
  58. op: '=',
  59. value: 'host.docker.internal:3000',
  60. label: 'instance',
  61. },
  62. ],
  63. operations: [
  64. {
  65. id: 'rate',
  66. params: ['$__rate_interval'],
  67. },
  68. {
  69. id: 'avg',
  70. params: [],
  71. },
  72. ],
  73. })
  74. );
  75. });
  76. it('parses query with aggregation by labels', () => {
  77. const visQuery = {
  78. metric: 'metric_name',
  79. labels: [
  80. {
  81. label: 'instance',
  82. op: '=',
  83. value: 'internal:3000',
  84. },
  85. ],
  86. operations: [
  87. {
  88. id: '__sum_by',
  89. params: ['app', 'version'],
  90. },
  91. ],
  92. };
  93. expect(buildVisualQueryFromString('sum(metric_name{instance="internal:3000"}) by (app, version)')).toEqual(
  94. noErrors(visQuery)
  95. );
  96. expect(buildVisualQueryFromString('sum by (app, version)(metric_name{instance="internal:3000"})')).toEqual(
  97. noErrors(visQuery)
  98. );
  99. });
  100. it('parses query with aggregation without labels', () => {
  101. const visQuery = {
  102. metric: 'metric_name',
  103. labels: [
  104. {
  105. label: 'instance',
  106. op: '=',
  107. value: 'internal:3000',
  108. },
  109. ],
  110. operations: [
  111. {
  112. id: '__sum_without',
  113. params: ['app', 'version'],
  114. },
  115. ],
  116. };
  117. expect(buildVisualQueryFromString('sum(metric_name{instance="internal:3000"}) without (app, version)')).toEqual(
  118. noErrors(visQuery)
  119. );
  120. expect(buildVisualQueryFromString('sum without (app, version)(metric_name{instance="internal:3000"})')).toEqual(
  121. noErrors(visQuery)
  122. );
  123. });
  124. it('parses aggregation with params', () => {
  125. expect(buildVisualQueryFromString('topk(5, http_requests_total)')).toEqual(
  126. noErrors({
  127. metric: 'http_requests_total',
  128. labels: [],
  129. operations: [
  130. {
  131. id: 'topk',
  132. params: [5],
  133. },
  134. ],
  135. })
  136. );
  137. });
  138. it('parses aggregation with params and labels', () => {
  139. expect(buildVisualQueryFromString('topk by(instance, job) (5, http_requests_total)')).toEqual(
  140. noErrors({
  141. metric: 'http_requests_total',
  142. labels: [],
  143. operations: [
  144. {
  145. id: '__topk_by',
  146. params: [5, 'instance', 'job'],
  147. },
  148. ],
  149. })
  150. );
  151. });
  152. it('parses function with argument', () => {
  153. expect(
  154. buildVisualQueryFromString('histogram_quantile(0.99, rate(counters_logins{app="backend"}[$__rate_interval]))')
  155. ).toEqual(
  156. noErrors({
  157. metric: 'counters_logins',
  158. labels: [{ label: 'app', op: '=', value: 'backend' }],
  159. operations: [
  160. {
  161. id: 'rate',
  162. params: ['$__rate_interval'],
  163. },
  164. {
  165. id: 'histogram_quantile',
  166. params: [0.99],
  167. },
  168. ],
  169. })
  170. );
  171. });
  172. it('parses function with multiple arguments', () => {
  173. expect(
  174. buildVisualQueryFromString(
  175. 'label_replace(avg_over_time(http_requests_total{instance="foo"}[$__interval]), "instance", "$1", "", "(.*)")'
  176. )
  177. ).toEqual(
  178. noErrors({
  179. metric: 'http_requests_total',
  180. labels: [{ label: 'instance', op: '=', value: 'foo' }],
  181. operations: [
  182. {
  183. id: 'avg_over_time',
  184. params: ['$__interval'],
  185. },
  186. {
  187. id: 'label_replace',
  188. params: ['instance', '$1', '', '(.*)'],
  189. },
  190. ],
  191. })
  192. );
  193. });
  194. it('parses binary operation with scalar', () => {
  195. expect(buildVisualQueryFromString('avg_over_time(http_requests_total{instance="foo"}[$__interval]) / 2')).toEqual(
  196. noErrors({
  197. metric: 'http_requests_total',
  198. labels: [{ label: 'instance', op: '=', value: 'foo' }],
  199. operations: [
  200. {
  201. id: 'avg_over_time',
  202. params: ['$__interval'],
  203. },
  204. {
  205. id: '__divide_by',
  206. params: [2],
  207. },
  208. ],
  209. })
  210. );
  211. });
  212. it('parses binary operation with 2 queries', () => {
  213. expect(
  214. buildVisualQueryFromString('avg_over_time(http_requests_total{instance="foo"}[$__interval]) / sum(logins_count)')
  215. ).toEqual(
  216. noErrors({
  217. metric: 'http_requests_total',
  218. labels: [{ label: 'instance', op: '=', value: 'foo' }],
  219. operations: [{ id: 'avg_over_time', params: ['$__interval'] }],
  220. binaryQueries: [
  221. {
  222. operator: '/',
  223. query: {
  224. metric: 'logins_count',
  225. labels: [],
  226. operations: [{ id: 'sum', params: [] }],
  227. },
  228. },
  229. ],
  230. })
  231. );
  232. });
  233. it('parses template variables in strings', () => {
  234. expect(buildVisualQueryFromString('http_requests_total{instance="$label_variable"}')).toEqual(
  235. noErrors({
  236. metric: 'http_requests_total',
  237. labels: [{ label: 'instance', op: '=', value: '$label_variable' }],
  238. operations: [],
  239. })
  240. );
  241. });
  242. it('parses template variables for metric', () => {
  243. expect(buildVisualQueryFromString('$metric_variable{instance="foo"}')).toEqual(
  244. noErrors({
  245. metric: '$metric_variable',
  246. labels: [{ label: 'instance', op: '=', value: 'foo' }],
  247. operations: [],
  248. })
  249. );
  250. expect(buildVisualQueryFromString('${metric_variable:fmt}{instance="foo"}')).toEqual(
  251. noErrors({
  252. metric: '${metric_variable:fmt}',
  253. labels: [{ label: 'instance', op: '=', value: 'foo' }],
  254. operations: [],
  255. })
  256. );
  257. expect(buildVisualQueryFromString('[[metric_variable:fmt]]{instance="foo"}')).toEqual(
  258. noErrors({
  259. metric: '[[metric_variable:fmt]]',
  260. labels: [{ label: 'instance', op: '=', value: 'foo' }],
  261. operations: [],
  262. })
  263. );
  264. });
  265. it('parses template variables in label name', () => {
  266. expect(buildVisualQueryFromString('metric{${variable_label}="foo"}')).toEqual(
  267. noErrors({
  268. metric: 'metric',
  269. labels: [{ label: '${variable_label}', op: '=', value: 'foo' }],
  270. operations: [],
  271. })
  272. );
  273. });
  274. it('fails to parse variable for function', () => {
  275. expect(buildVisualQueryFromString('${func_var}(metric{bar="foo"})')).toEqual({
  276. errors: [
  277. {
  278. text: '(',
  279. from: 20,
  280. to: 21,
  281. parentType: 'VectorSelector',
  282. },
  283. {
  284. text: 'metric',
  285. from: 21,
  286. to: 27,
  287. parentType: 'VectorSelector',
  288. },
  289. ],
  290. query: {
  291. metric: '${func_var}',
  292. labels: [{ label: 'bar', op: '=', value: 'foo' }],
  293. operations: [],
  294. },
  295. });
  296. });
  297. it('fails to parse malformed query', () => {
  298. expect(buildVisualQueryFromString('asdf-metric{bar="})')).toEqual({
  299. errors: [
  300. {
  301. text: '',
  302. from: 19,
  303. to: 19,
  304. parentType: 'LabelMatchers',
  305. },
  306. ],
  307. query: {
  308. metric: 'asdf',
  309. labels: [],
  310. operations: [],
  311. binaryQueries: [
  312. {
  313. operator: '-',
  314. query: {
  315. metric: 'metric',
  316. labels: [{ label: 'bar', op: '=', value: '})' }],
  317. operations: [],
  318. },
  319. },
  320. ],
  321. },
  322. });
  323. });
  324. it('fails to parse malformed query 2', () => {
  325. expect(buildVisualQueryFromString('ewafweaf{afea=afe}')).toEqual({
  326. errors: [
  327. {
  328. text: 'afe}',
  329. from: 14,
  330. to: 18,
  331. parentType: 'LabelMatcher',
  332. },
  333. ],
  334. query: {
  335. metric: 'ewafweaf',
  336. labels: [{ label: 'afea', op: '=', value: '' }],
  337. operations: [],
  338. },
  339. });
  340. });
  341. it('parses query without metric', () => {
  342. expect(buildVisualQueryFromString('label_replace(rate([$__rate_interval]), "", "$1", "", "(.*)")')).toEqual({
  343. errors: [],
  344. query: {
  345. metric: '',
  346. labels: [],
  347. operations: [
  348. { id: 'rate', params: ['$__rate_interval'] },
  349. {
  350. id: 'label_replace',
  351. params: ['', '$1', '', '(.*)'],
  352. },
  353. ],
  354. },
  355. });
  356. });
  357. it('lone aggregation without params', () => {
  358. expect(buildVisualQueryFromString('sum()')).toEqual({
  359. errors: [],
  360. query: {
  361. metric: '',
  362. labels: [],
  363. operations: [{ id: 'sum', params: [] }],
  364. },
  365. });
  366. });
  367. it('handles multiple binary scalar operations', () => {
  368. expect(buildVisualQueryFromString('cluster_namespace_slug_dialer_name + 1 - 1 / 1 * 1 % 1 ^ 1')).toEqual({
  369. errors: [],
  370. query: {
  371. metric: 'cluster_namespace_slug_dialer_name',
  372. labels: [],
  373. operations: [
  374. {
  375. id: '__addition',
  376. params: [1],
  377. },
  378. {
  379. id: '__subtraction',
  380. params: [1],
  381. },
  382. {
  383. id: '__divide_by',
  384. params: [1],
  385. },
  386. {
  387. id: '__multiply_by',
  388. params: [1],
  389. },
  390. {
  391. id: '__modulo',
  392. params: [1],
  393. },
  394. {
  395. id: '__exponent',
  396. params: [1],
  397. },
  398. ],
  399. },
  400. });
  401. });
  402. it('handles scalar comparison operators', () => {
  403. expect(buildVisualQueryFromString('cluster_namespace_slug_dialer_name <= 2.5')).toEqual({
  404. errors: [],
  405. query: {
  406. metric: 'cluster_namespace_slug_dialer_name',
  407. labels: [],
  408. operations: [
  409. {
  410. id: '__less_or_equal',
  411. params: [2.5, false],
  412. },
  413. ],
  414. },
  415. });
  416. });
  417. it('handles bool with comparison operator', () => {
  418. expect(buildVisualQueryFromString('cluster_namespace_slug_dialer_name <= bool 2')).toEqual({
  419. errors: [],
  420. query: {
  421. metric: 'cluster_namespace_slug_dialer_name',
  422. labels: [],
  423. operations: [
  424. {
  425. id: '__less_or_equal',
  426. params: [2, true],
  427. },
  428. ],
  429. },
  430. });
  431. });
  432. it('handles multiple binary operations', () => {
  433. expect(buildVisualQueryFromString('foo{x="yy"} * metric{y="zz",a="bb"} * metric2')).toEqual({
  434. errors: [],
  435. query: {
  436. metric: 'foo',
  437. labels: [{ label: 'x', op: '=', value: 'yy' }],
  438. operations: [],
  439. binaryQueries: [
  440. {
  441. operator: '*',
  442. query: {
  443. metric: 'metric',
  444. labels: [
  445. { label: 'y', op: '=', value: 'zz' },
  446. { label: 'a', op: '=', value: 'bb' },
  447. ],
  448. operations: [],
  449. },
  450. },
  451. {
  452. operator: '*',
  453. query: {
  454. metric: 'metric2',
  455. labels: [],
  456. operations: [],
  457. },
  458. },
  459. ],
  460. },
  461. });
  462. });
  463. it('handles multiple binary operations and scalar', () => {
  464. expect(buildVisualQueryFromString('foo{x="yy"} * metric{y="zz",a="bb"} * 2')).toEqual({
  465. errors: [],
  466. query: {
  467. metric: 'foo',
  468. labels: [{ label: 'x', op: '=', value: 'yy' }],
  469. operations: [
  470. {
  471. id: '__multiply_by',
  472. params: [2],
  473. },
  474. ],
  475. binaryQueries: [
  476. {
  477. operator: '*',
  478. query: {
  479. metric: 'metric',
  480. labels: [
  481. { label: 'y', op: '=', value: 'zz' },
  482. { label: 'a', op: '=', value: 'bb' },
  483. ],
  484. operations: [],
  485. },
  486. },
  487. ],
  488. },
  489. });
  490. });
  491. it('handles binary operation with vector matchers', () => {
  492. expect(buildVisualQueryFromString('foo * on(foo, bar) metric')).toEqual({
  493. errors: [],
  494. query: {
  495. metric: 'foo',
  496. labels: [],
  497. operations: [],
  498. binaryQueries: [
  499. {
  500. operator: '*',
  501. vectorMatches: 'foo, bar',
  502. vectorMatchesType: 'on',
  503. query: { metric: 'metric', labels: [], operations: [] },
  504. },
  505. ],
  506. },
  507. });
  508. expect(buildVisualQueryFromString('foo * ignoring(foo) metric')).toEqual({
  509. errors: [],
  510. query: {
  511. metric: 'foo',
  512. labels: [],
  513. operations: [],
  514. binaryQueries: [
  515. {
  516. operator: '*',
  517. vectorMatches: 'foo',
  518. vectorMatchesType: 'ignoring',
  519. query: { metric: 'metric', labels: [], operations: [] },
  520. },
  521. ],
  522. },
  523. });
  524. });
  525. it('reports error on parenthesis', () => {
  526. expect(buildVisualQueryFromString('foo / (bar + baz)')).toEqual({
  527. errors: [
  528. {
  529. from: 6,
  530. parentType: 'Expr',
  531. text: '(bar + baz)',
  532. to: 17,
  533. },
  534. ],
  535. query: {
  536. metric: 'foo',
  537. labels: [],
  538. operations: [],
  539. binaryQueries: [
  540. {
  541. operator: '/',
  542. query: {
  543. binaryQueries: [{ operator: '+', query: { labels: [], metric: 'baz', operations: [] } }],
  544. metric: 'bar',
  545. labels: [],
  546. operations: [],
  547. },
  548. },
  549. ],
  550. },
  551. });
  552. });
  553. });
  554. function noErrors(query: PromVisualQuery) {
  555. return {
  556. errors: [],
  557. query,
  558. };
  559. }