2021-04-21 02:38:00 -05:00
|
|
|
import { size } from 'lodash';
|
2022-04-22 08:33:13 -05:00
|
|
|
|
2019-10-31 04:48:05 -05:00
|
|
|
import { QueryHint, QueryFix } from '@grafana/data';
|
2022-04-22 08:33:13 -05:00
|
|
|
|
2020-01-03 05:55:50 -06:00
|
|
|
import { PrometheusDatasource } from './datasource';
|
2018-10-24 07:55:56 -05:00
|
|
|
|
2018-10-28 08:03:39 -05:00
|
|
|
/**
|
|
|
|
* Number of time series results needed before starting to suggest sum aggregation hints
|
|
|
|
*/
|
|
|
|
export const SUM_HINT_THRESHOLD_COUNT = 20;
|
|
|
|
|
2020-07-06 14:16:27 -05:00
|
|
|
export function getQueryHints(query: string, series?: any[], datasource?: PrometheusDatasource): QueryHint[] {
|
2018-10-23 08:12:53 -05:00
|
|
|
const hints = [];
|
2018-10-04 08:48:08 -05:00
|
|
|
|
2018-10-23 08:12:53 -05:00
|
|
|
// ..._bucket metric needs a histogram_quantile()
|
2022-02-16 02:34:32 -06:00
|
|
|
const histogramMetric = query.trim().match(/^\w+_bucket$|^\w+_bucket{.*}$/);
|
2018-10-23 08:12:53 -05:00
|
|
|
if (histogramMetric) {
|
2022-02-11 09:50:35 -06:00
|
|
|
const label = 'Selected metric has buckets.';
|
2018-10-23 08:12:53 -05:00
|
|
|
hints.push({
|
|
|
|
type: 'HISTOGRAM_QUANTILE',
|
|
|
|
label,
|
|
|
|
fix: {
|
2022-02-11 09:50:35 -06:00
|
|
|
label: 'Consider calculating aggregated quantile by adding histogram_quantile().',
|
2018-10-23 08:12:53 -05:00
|
|
|
action: {
|
|
|
|
type: 'ADD_HISTOGRAM_QUANTILE',
|
|
|
|
query,
|
2018-10-04 08:48:08 -05:00
|
|
|
},
|
2019-06-18 04:01:12 -05:00
|
|
|
} as QueryFix,
|
2018-10-23 08:12:53 -05:00
|
|
|
});
|
|
|
|
}
|
2018-10-04 08:48:08 -05:00
|
|
|
|
2020-01-03 05:55:50 -06:00
|
|
|
// Check for need of rate()
|
2020-02-06 05:56:53 -06:00
|
|
|
if (query.indexOf('rate(') === -1 && query.indexOf('increase(') === -1) {
|
2020-01-03 05:55:50 -06:00
|
|
|
// Use metric metadata for exact types
|
|
|
|
const nameMatch = query.match(/\b(\w+_(total|sum|count))\b/);
|
|
|
|
let counterNameMetric = nameMatch ? nameMatch[1] : '';
|
2022-08-11 08:47:07 -05:00
|
|
|
const metricsMetadata = datasource?.languageProvider?.metricsMetadata;
|
2020-01-03 05:55:50 -06:00
|
|
|
let certain = false;
|
2020-07-06 14:16:27 -05:00
|
|
|
|
2022-08-11 08:47:07 -05:00
|
|
|
if (metricsMetadata) {
|
|
|
|
// Tokenize the query into its identifiers (see https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels)
|
|
|
|
const queryTokens = Array.from(query.matchAll(/\$?[a-zA-Z_:][a-zA-Z0-9_:]*/g))
|
|
|
|
.map(([match]) => match)
|
|
|
|
// Exclude variable identifiers
|
|
|
|
.filter((token) => !token.startsWith('$'))
|
|
|
|
// Split composite keys to match the tokens returned by the language provider
|
|
|
|
.flatMap((token) => token.split(':'));
|
|
|
|
// Determine whether any of the query identifier tokens refers to a counter metric
|
2020-07-06 14:16:27 -05:00
|
|
|
counterNameMetric =
|
2022-08-11 08:47:07 -05:00
|
|
|
queryTokens.find((metricName) => {
|
2020-07-06 14:16:27 -05:00
|
|
|
// Only considering first type information, could be non-deterministic
|
2021-09-21 08:19:52 -05:00
|
|
|
const metadata = metricsMetadata[metricName];
|
2022-08-11 08:47:07 -05:00
|
|
|
if (metadata && metadata.type.toLowerCase() === 'counter') {
|
|
|
|
certain = true;
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
2018-10-23 08:12:53 -05:00
|
|
|
}
|
2020-07-06 14:16:27 -05:00
|
|
|
}) ?? '';
|
2020-01-03 05:55:50 -06:00
|
|
|
}
|
2020-07-06 14:16:27 -05:00
|
|
|
|
2020-01-03 05:55:50 -06:00
|
|
|
if (counterNameMetric) {
|
2022-02-16 02:34:32 -06:00
|
|
|
// FixableQuery consists of metric name and optionally label-value pairs. We are not offering fix for complex queries yet.
|
|
|
|
const fixableQuery = query.trim().match(/^\w+$|^\w+{.*}$/);
|
2020-01-03 05:55:50 -06:00
|
|
|
const verb = certain ? 'is' : 'looks like';
|
2022-02-11 09:50:35 -06:00
|
|
|
let label = `Selected metric ${verb} a counter.`;
|
2020-07-06 14:16:27 -05:00
|
|
|
let fix: QueryFix | undefined;
|
|
|
|
|
2022-02-16 02:34:32 -06:00
|
|
|
if (fixableQuery) {
|
2020-01-03 05:55:50 -06:00
|
|
|
fix = {
|
2022-02-11 09:50:35 -06:00
|
|
|
label: 'Consider calculating rate of counter by adding rate().',
|
2020-01-03 05:55:50 -06:00
|
|
|
action: {
|
|
|
|
type: 'ADD_RATE',
|
|
|
|
query,
|
|
|
|
},
|
2020-07-06 14:16:27 -05:00
|
|
|
};
|
2020-01-03 05:55:50 -06:00
|
|
|
} else {
|
2022-02-11 09:50:35 -06:00
|
|
|
label = `${label} Consider calculating rate of counter by adding rate().`;
|
2018-10-04 08:48:08 -05:00
|
|
|
}
|
2020-07-06 14:16:27 -05:00
|
|
|
|
2020-01-03 05:55:50 -06:00
|
|
|
hints.push({
|
|
|
|
type: 'APPLY_RATE',
|
|
|
|
label,
|
|
|
|
fix,
|
|
|
|
});
|
|
|
|
}
|
2018-10-23 08:12:53 -05:00
|
|
|
}
|
2018-10-04 08:48:08 -05:00
|
|
|
|
2018-10-23 08:12:53 -05:00
|
|
|
// Check for recording rules expansion
|
|
|
|
if (datasource && datasource.ruleMappings) {
|
|
|
|
const mapping = datasource.ruleMappings;
|
|
|
|
const mappingForQuery = Object.keys(mapping).reduce((acc, ruleName) => {
|
|
|
|
if (query.search(ruleName) > -1) {
|
2018-10-04 08:48:08 -05:00
|
|
|
return {
|
2018-10-23 08:12:53 -05:00
|
|
|
...acc,
|
|
|
|
[ruleName]: mapping[ruleName],
|
2018-10-04 08:48:08 -05:00
|
|
|
};
|
|
|
|
}
|
2018-10-23 08:12:53 -05:00
|
|
|
return acc;
|
|
|
|
}, {});
|
2021-04-21 02:38:00 -05:00
|
|
|
if (size(mappingForQuery) > 0) {
|
2018-10-23 08:12:53 -05:00
|
|
|
const label = 'Query contains recording rules.';
|
|
|
|
hints.push({
|
|
|
|
type: 'EXPAND_RULES',
|
|
|
|
label,
|
2022-02-02 06:02:32 -06:00
|
|
|
fix: {
|
2018-10-23 08:12:53 -05:00
|
|
|
label: 'Expand rules',
|
|
|
|
action: {
|
|
|
|
type: 'EXPAND_RULES',
|
|
|
|
query,
|
2022-07-18 07:13:34 -05:00
|
|
|
options: mappingForQuery,
|
2018-10-23 08:12:53 -05:00
|
|
|
},
|
2022-10-25 04:04:35 -05:00
|
|
|
} as unknown as QueryFix,
|
2018-10-23 08:12:53 -05:00
|
|
|
});
|
2018-10-04 08:48:08 -05:00
|
|
|
}
|
2018-10-23 08:12:53 -05:00
|
|
|
}
|
2018-10-28 08:03:39 -05:00
|
|
|
|
2018-11-26 06:43:22 -06:00
|
|
|
if (series && series.length >= SUM_HINT_THRESHOLD_COUNT) {
|
2018-10-28 08:03:39 -05:00
|
|
|
const simpleMetric = query.trim().match(/^\w+$/);
|
|
|
|
if (simpleMetric) {
|
|
|
|
hints.push({
|
|
|
|
type: 'ADD_SUM',
|
|
|
|
label: 'Many time series results returned.',
|
|
|
|
fix: {
|
|
|
|
label: 'Consider aggregating with sum().',
|
|
|
|
action: {
|
|
|
|
type: 'ADD_SUM',
|
|
|
|
query: query,
|
|
|
|
preventSubmit: true,
|
|
|
|
},
|
2019-06-18 04:01:12 -05:00
|
|
|
} as QueryFix,
|
2018-10-28 08:03:39 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-06 14:16:27 -05:00
|
|
|
return hints;
|
2018-10-04 08:48:08 -05:00
|
|
|
}
|
2021-05-28 10:10:10 -05:00
|
|
|
|
|
|
|
export function getInitHints(datasource: PrometheusDatasource): QueryHint[] {
|
|
|
|
const hints = [];
|
|
|
|
// Hint if using Loki as Prometheus data source
|
|
|
|
if (datasource.directUrl.includes('/loki') && !datasource.languageProvider.metrics.length) {
|
|
|
|
hints.push({
|
|
|
|
label: `Using Loki as a Prometheus data source is no longer supported. You must use the Loki data source for your Loki instance.`,
|
|
|
|
type: 'INFO',
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Hint for big disabled lookups
|
|
|
|
if (datasource.lookupsDisabled) {
|
|
|
|
hints.push({
|
|
|
|
label: `Labels and metrics lookup was disabled in data source settings.`,
|
|
|
|
type: 'INFO',
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return hints;
|
|
|
|
}
|