query_hints.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import { size } from 'lodash';
  2. import { QueryHint, QueryFix } from '@grafana/data';
  3. import { PrometheusDatasource } from './datasource';
  4. /**
  5. * Number of time series results needed before starting to suggest sum aggregation hints
  6. */
  7. export const SUM_HINT_THRESHOLD_COUNT = 20;
  8. export function getQueryHints(query: string, series?: any[], datasource?: PrometheusDatasource): QueryHint[] {
  9. const hints = [];
  10. // ..._bucket metric needs a histogram_quantile()
  11. const histogramMetric = query.trim().match(/^\w+_bucket$|^\w+_bucket{.*}$/);
  12. if (histogramMetric) {
  13. const label = 'Selected metric has buckets.';
  14. hints.push({
  15. type: 'HISTOGRAM_QUANTILE',
  16. label,
  17. fix: {
  18. label: 'Consider calculating aggregated quantile by adding histogram_quantile().',
  19. action: {
  20. type: 'ADD_HISTOGRAM_QUANTILE',
  21. query,
  22. },
  23. } as QueryFix,
  24. });
  25. }
  26. // Check for need of rate()
  27. if (query.indexOf('rate(') === -1 && query.indexOf('increase(') === -1) {
  28. // Use metric metadata for exact types
  29. const nameMatch = query.match(/\b(\w+_(total|sum|count))\b/);
  30. let counterNameMetric = nameMatch ? nameMatch[1] : '';
  31. const metricsMetadata = datasource?.languageProvider?.metricsMetadata ?? {};
  32. const metricMetadataKeys = Object.keys(metricsMetadata);
  33. let certain = false;
  34. if (metricMetadataKeys.length > 0) {
  35. counterNameMetric =
  36. metricMetadataKeys.find((metricName) => {
  37. // Only considering first type information, could be non-deterministic
  38. const metadata = metricsMetadata[metricName];
  39. if (metadata.type.toLowerCase() === 'counter') {
  40. const metricRegex = new RegExp(`\\b${metricName}\\b`);
  41. if (query.match(metricRegex)) {
  42. certain = true;
  43. return true;
  44. }
  45. }
  46. return false;
  47. }) ?? '';
  48. }
  49. if (counterNameMetric) {
  50. // FixableQuery consists of metric name and optionally label-value pairs. We are not offering fix for complex queries yet.
  51. const fixableQuery = query.trim().match(/^\w+$|^\w+{.*}$/);
  52. const verb = certain ? 'is' : 'looks like';
  53. let label = `Selected metric ${verb} a counter.`;
  54. let fix: QueryFix | undefined;
  55. if (fixableQuery) {
  56. fix = {
  57. label: 'Consider calculating rate of counter by adding rate().',
  58. action: {
  59. type: 'ADD_RATE',
  60. query,
  61. },
  62. };
  63. } else {
  64. label = `${label} Consider calculating rate of counter by adding rate().`;
  65. }
  66. hints.push({
  67. type: 'APPLY_RATE',
  68. label,
  69. fix,
  70. });
  71. }
  72. }
  73. // Check for recording rules expansion
  74. if (datasource && datasource.ruleMappings) {
  75. const mapping = datasource.ruleMappings;
  76. const mappingForQuery = Object.keys(mapping).reduce((acc, ruleName) => {
  77. if (query.search(ruleName) > -1) {
  78. return {
  79. ...acc,
  80. [ruleName]: mapping[ruleName],
  81. };
  82. }
  83. return acc;
  84. }, {});
  85. if (size(mappingForQuery) > 0) {
  86. const label = 'Query contains recording rules.';
  87. hints.push({
  88. type: 'EXPAND_RULES',
  89. label,
  90. fix: {
  91. label: 'Expand rules',
  92. action: {
  93. type: 'EXPAND_RULES',
  94. query,
  95. mapping: mappingForQuery,
  96. },
  97. } as any as QueryFix,
  98. });
  99. }
  100. }
  101. if (series && series.length >= SUM_HINT_THRESHOLD_COUNT) {
  102. const simpleMetric = query.trim().match(/^\w+$/);
  103. if (simpleMetric) {
  104. hints.push({
  105. type: 'ADD_SUM',
  106. label: 'Many time series results returned.',
  107. fix: {
  108. label: 'Consider aggregating with sum().',
  109. action: {
  110. type: 'ADD_SUM',
  111. query: query,
  112. preventSubmit: true,
  113. },
  114. } as QueryFix,
  115. });
  116. }
  117. }
  118. return hints;
  119. }
  120. export function getInitHints(datasource: PrometheusDatasource): QueryHint[] {
  121. const hints = [];
  122. // Hint if using Loki as Prometheus data source
  123. if (datasource.directUrl.includes('/loki') && !datasource.languageProvider.metrics.length) {
  124. hints.push({
  125. label: `Using Loki as a Prometheus data source is no longer supported. You must use the Loki data source for your Loki instance.`,
  126. type: 'INFO',
  127. });
  128. }
  129. // Hint for big disabled lookups
  130. if (datasource.lookupsDisabled) {
  131. hints.push({
  132. label: `Labels and metrics lookup was disabled in data source settings.`,
  133. type: 'INFO',
  134. });
  135. }
  136. return hints;
  137. }