add_label_to_query.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. import _ from 'lodash';
  2. const keywords = 'by|without|on|ignoring|group_left|group_right|bool|or|and|unless';
  3. // Duplicate from mode-prometheus.js, which can't be used in tests due to global ace not being loaded.
  4. const builtInWords = [
  5. keywords,
  6. 'count|count_values|min|max|avg|sum|stddev|stdvar|bottomk|topk|quantile',
  7. 'true|false|null|__name__|job',
  8. 'abs|absent|ceil|changes|clamp_max|clamp_min|count_scalar|day_of_month|day_of_week|days_in_month|delta|deriv',
  9. 'drop_common_labels|exp|floor|histogram_quantile|holt_winters|hour|idelta|increase|irate|label_replace|ln|log2',
  10. 'log10|minute|month|predict_linear|rate|resets|round|scalar|sort|sort_desc|sqrt|time|vector|year|avg_over_time',
  11. 'min_over_time|max_over_time|sum_over_time|count_over_time|quantile_over_time|stddev_over_time|stdvar_over_time',
  12. ]
  13. .join('|')
  14. .split('|');
  15. const metricNameRegexp = /([A-Za-z:][\w:]*)\b(?![\(\]{=!",])/g;
  16. const selectorRegexp = /{([^{]*)}/g;
  17. // addLabelToQuery('foo', 'bar', 'baz') => 'foo{bar="baz"}'
  18. export function addLabelToQuery(query: string, key: string, value: string, operator?: string): string {
  19. if (!key || !value) {
  20. throw new Error('Need label to add to query.');
  21. }
  22. // Add empty selectors to bare metric names
  23. let previousWord: string;
  24. query = query.replace(metricNameRegexp, (match, word, offset) => {
  25. const insideSelector = isPositionInsideChars(query, offset, '{', '}');
  26. // Handle "sum by (key) (metric)"
  27. const previousWordIsKeyWord = previousWord && keywords.split('|').indexOf(previousWord) > -1;
  28. // check for colon as as "word boundary" symbol
  29. const isColonBounded = word.endsWith(':');
  30. previousWord = word;
  31. if (!insideSelector && !isColonBounded && !previousWordIsKeyWord && builtInWords.indexOf(word) === -1) {
  32. return `${word}{}`;
  33. }
  34. return word;
  35. });
  36. // Adding label to existing selectors
  37. let match = selectorRegexp.exec(query);
  38. const parts = [];
  39. let lastIndex = 0;
  40. let suffix = '';
  41. while (match) {
  42. const prefix = query.slice(lastIndex, match.index);
  43. const selector = match[1];
  44. const selectorWithLabel = addLabelToSelector(selector, key, value, operator);
  45. lastIndex = match.index + match[1].length + 2;
  46. suffix = query.slice(match.index + match[0].length);
  47. parts.push(prefix, selectorWithLabel);
  48. match = selectorRegexp.exec(query);
  49. }
  50. parts.push(suffix);
  51. return parts.join('');
  52. }
  53. const labelRegexp = /(\w+)\s*(=|!=|=~|!~)\s*("[^"]*")/g;
  54. export function addLabelToSelector(selector: string, labelKey: string, labelValue: string, labelOperator?: string) {
  55. const parsedLabels = [];
  56. // Split selector into labels
  57. if (selector) {
  58. let match = labelRegexp.exec(selector);
  59. while (match) {
  60. parsedLabels.push({ key: match[1], operator: match[2], value: match[3] });
  61. match = labelRegexp.exec(selector);
  62. }
  63. }
  64. // Add new label
  65. const operatorForLabelKey = labelOperator || '=';
  66. parsedLabels.push({ key: labelKey, operator: operatorForLabelKey, value: `"${labelValue}"` });
  67. // Sort labels by key and put them together
  68. const formatted = _.chain(parsedLabels)
  69. .uniqWith(_.isEqual)
  70. .compact()
  71. .sortBy('key')
  72. .map(({ key, operator, value }) => `${key}${operator}${value}`)
  73. .value()
  74. .join(',');
  75. return `{${formatted}}`;
  76. }
  77. function isPositionInsideChars(text: string, position: number, openChar: string, closeChar: string) {
  78. const nextSelectorStart = text.slice(position).indexOf(openChar);
  79. const nextSelectorEnd = text.slice(position).indexOf(closeChar);
  80. return nextSelectorEnd > -1 && (nextSelectorStart === -1 || nextSelectorStart > nextSelectorEnd);
  81. }
  82. export default addLabelToQuery;