mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Prometheus: Add hints for native histograms (#87017)
This commit is contained in:
parent
31d5dd0a12
commit
eeabb6f066
@ -743,6 +743,30 @@ export class PrometheusDatasource
|
|||||||
expression = `histogram_quantile(0.95, sum(rate(${expression}[$__rate_interval])) by (le))`;
|
expression = `histogram_quantile(0.95, sum(rate(${expression}[$__rate_interval])) by (le))`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'ADD_HISTOGRAM_AVG': {
|
||||||
|
expression = `histogram_avg(rate(${expression}[$__rate_interval]))`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'ADD_HISTOGRAM_FRACTION': {
|
||||||
|
expression = `histogram_fraction(0,0.2,rate(${expression}[$__rate_interval]))`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'ADD_HISTOGRAM_COUNT': {
|
||||||
|
expression = `histogram_count(rate(${expression}[$__rate_interval]))`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'ADD_HISTOGRAM_SUM': {
|
||||||
|
expression = `histogram_sum(rate(${expression}[$__rate_interval]))`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'ADD_HISTOGRAM_STDDEV': {
|
||||||
|
expression = `histogram_stddev(rate(${expression}[$__rate_interval]))`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'ADD_HISTOGRAM_STDVAR': {
|
||||||
|
expression = `histogram_stdvar(rate(${expression}[$__rate_interval]))`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'ADD_RATE': {
|
case 'ADD_RATE': {
|
||||||
expression = `rate(${expression}[$__rate_interval])`;
|
expression = `rate(${expression}[$__rate_interval])`;
|
||||||
break;
|
break;
|
||||||
|
@ -190,4 +190,44 @@ describe('getQueryHints()', () => {
|
|||||||
hints = getQueryHints('container_cpu_usage_seconds_total:irate_total', series);
|
hints = getQueryHints('container_cpu_usage_seconds_total:irate_total', series);
|
||||||
expect(hints!.length).toBe(0);
|
expect(hints!.length).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// native histograms
|
||||||
|
it('returns hints for native histogram by metric type without suffix "_bucket"', () => {
|
||||||
|
const series = [
|
||||||
|
{
|
||||||
|
datapoints: [
|
||||||
|
[23, 1000],
|
||||||
|
[24, 1001],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const mock: unknown = { languageProvider: { metricsMetadata: { foo: { type: 'histogram' } } } };
|
||||||
|
const datasource = mock as PrometheusDatasource;
|
||||||
|
|
||||||
|
let hints = getQueryHints('foo', series, datasource);
|
||||||
|
expect(hints!.length).toBe(6);
|
||||||
|
const hintsString = JSON.stringify(hints);
|
||||||
|
expect(hintsString).toContain('ADD_HISTOGRAM_AVG');
|
||||||
|
expect(hintsString).toContain('ADD_HISTOGRAM_COUNT');
|
||||||
|
expect(hintsString).toContain('ADD_HISTOGRAM_SUM');
|
||||||
|
expect(hintsString).toContain('ADD_HISTOGRAM_FRACTION');
|
||||||
|
expect(hintsString).toContain('ADD_HISTOGRAM_AVG');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns no hints for native histogram when there are native histogram functions in the query', () => {
|
||||||
|
const queryWithNativeHistogramFunction = 'histogram_avg(foo)';
|
||||||
|
const series = [
|
||||||
|
{
|
||||||
|
datapoints: [
|
||||||
|
[23, 1000],
|
||||||
|
[24, 1001],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const mock: unknown = { languageProvider: { metricsMetadata: { foo: { type: 'histogram' } } } };
|
||||||
|
const datasource = mock as PrometheusDatasource;
|
||||||
|
|
||||||
|
let hints = getQueryHints(queryWithNativeHistogramFunction, series, datasource);
|
||||||
|
expect(hints!.length).toBe(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -4,6 +4,7 @@ import { size } from 'lodash';
|
|||||||
import { QueryFix, QueryHint } from '@grafana/data';
|
import { QueryFix, QueryHint } from '@grafana/data';
|
||||||
|
|
||||||
import { PrometheusDatasource } from './datasource';
|
import { PrometheusDatasource } from './datasource';
|
||||||
|
import { PromMetricsMetadata } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of time series results needed before starting to suggest sum aggregation hints
|
* Number of time series results needed before starting to suggest sum aggregation hints
|
||||||
@ -13,9 +14,12 @@ export const SUM_HINT_THRESHOLD_COUNT = 20;
|
|||||||
export function getQueryHints(query: string, series?: unknown[], datasource?: PrometheusDatasource): QueryHint[] {
|
export function getQueryHints(query: string, series?: unknown[], datasource?: PrometheusDatasource): QueryHint[] {
|
||||||
const hints = [];
|
const hints = [];
|
||||||
|
|
||||||
|
const metricsMetadata = datasource?.languageProvider?.metricsMetadata;
|
||||||
|
|
||||||
// ..._bucket metric needs a histogram_quantile()
|
// ..._bucket metric needs a histogram_quantile()
|
||||||
const histogramMetric = query.trim().match(/^\w+_bucket$|^\w+_bucket{.*}$/);
|
// this regex also prevents hints from being shown when a query already has a function
|
||||||
if (histogramMetric) {
|
const oldHistogramMetric = query.trim().match(/^\w+_bucket$|^\w+_bucket{.*}$/);
|
||||||
|
if (oldHistogramMetric) {
|
||||||
const label = 'Selected metric has buckets.';
|
const label = 'Selected metric has buckets.';
|
||||||
hints.push({
|
hints.push({
|
||||||
type: 'HISTOGRAM_QUANTILE',
|
type: 'HISTOGRAM_QUANTILE',
|
||||||
@ -28,6 +32,94 @@ export function getQueryHints(query: string, series?: unknown[], datasource?: Pr
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
} else if (metricsMetadata && simpleQueryCheck(query)) {
|
||||||
|
// having migrated to native histograms
|
||||||
|
// there will be no more old histograms (no buckets)
|
||||||
|
// and we can identify a native histogram by the following
|
||||||
|
// type === 'histogram'
|
||||||
|
// metric name does not include '_bucket'
|
||||||
|
const queryTokens = getQueryTokens(query);
|
||||||
|
|
||||||
|
// Determine whether any of the query identifier tokens refers to a native histogram metric
|
||||||
|
const { nameMetric } = checkMetricType(queryTokens, 'histogram', metricsMetadata, false);
|
||||||
|
|
||||||
|
const nativeHistogramNameMetric = nameMetric;
|
||||||
|
|
||||||
|
if (nativeHistogramNameMetric) {
|
||||||
|
// add hints:
|
||||||
|
// histogram_avg, histogram_count, histogram_sum, histogram_fraction, histogram_stddev, histogram_stdvar
|
||||||
|
const label = 'Selected metric is a native histogram.';
|
||||||
|
hints.push(
|
||||||
|
{
|
||||||
|
type: 'HISTOGRAM_AVG',
|
||||||
|
label,
|
||||||
|
fix: {
|
||||||
|
label: 'Consider calculating the arithmetic average of observed values by adding histogram_avg().',
|
||||||
|
action: {
|
||||||
|
type: 'ADD_HISTOGRAM_AVG',
|
||||||
|
query,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'HISTOGRAM_COUNT',
|
||||||
|
label,
|
||||||
|
fix: {
|
||||||
|
label: 'Consider calculating the count of observations by adding histogram_count().',
|
||||||
|
action: {
|
||||||
|
type: 'ADD_HISTOGRAM_COUNT',
|
||||||
|
query,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'HISTOGRAM_SUM',
|
||||||
|
label,
|
||||||
|
fix: {
|
||||||
|
label: 'Consider calculating the sum of observations by adding histogram_sum().',
|
||||||
|
action: {
|
||||||
|
type: 'ADD_HISTOGRAM_SUM',
|
||||||
|
query,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'HISTOGRAM_FRACTION',
|
||||||
|
label,
|
||||||
|
fix: {
|
||||||
|
label:
|
||||||
|
'Consider calculating the estimated fraction of observations between the provided lower and upper values by adding histogram_fraction().',
|
||||||
|
action: {
|
||||||
|
type: 'ADD_HISTOGRAM_FRACTION',
|
||||||
|
query,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'HISTOGRAM_STDDEV',
|
||||||
|
label,
|
||||||
|
fix: {
|
||||||
|
label:
|
||||||
|
'Consider calculating the estimated standard deviation of observations by adding histogram_stddev().',
|
||||||
|
action: {
|
||||||
|
type: 'ADD_HISTOGRAM_STDDEV',
|
||||||
|
query,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'HISTOGRAM_STDVAR',
|
||||||
|
label,
|
||||||
|
fix: {
|
||||||
|
label: 'Consider calculating the estimated standard variance of observations by adding histogram_stdvar().',
|
||||||
|
action: {
|
||||||
|
type: 'ADD_HISTOGRAM_STDVAR',
|
||||||
|
query,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for need of rate()
|
// Check for need of rate()
|
||||||
@ -35,34 +127,21 @@ export function getQueryHints(query: string, series?: unknown[], datasource?: Pr
|
|||||||
// Use metric metadata for exact types
|
// Use metric metadata for exact types
|
||||||
const nameMatch = query.match(/\b((?<!:)\w+_(total|sum|count)(?!:))\b/);
|
const nameMatch = query.match(/\b((?<!:)\w+_(total|sum|count)(?!:))\b/);
|
||||||
let counterNameMetric = nameMatch ? nameMatch[1] : '';
|
let counterNameMetric = nameMatch ? nameMatch[1] : '';
|
||||||
const metricsMetadata = datasource?.languageProvider?.metricsMetadata;
|
|
||||||
let certain = false;
|
let certain = false;
|
||||||
|
|
||||||
if (metricsMetadata) {
|
if (metricsMetadata) {
|
||||||
// Tokenize the query into its identifiers (see https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels)
|
// 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))
|
const queryTokens = getQueryTokens(query);
|
||||||
.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
|
// Determine whether any of the query identifier tokens refers to a counter metric
|
||||||
counterNameMetric =
|
const metricTypeChecked = checkMetricType(queryTokens, 'counter', metricsMetadata, certain);
|
||||||
queryTokens.find((metricName) => {
|
|
||||||
// Only considering first type information, could be non-deterministic
|
counterNameMetric = metricTypeChecked.nameMetric;
|
||||||
const metadata = metricsMetadata[metricName];
|
certain = metricTypeChecked.certain;
|
||||||
if (metadata && metadata.type.toLowerCase() === 'counter') {
|
|
||||||
certain = true;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}) ?? '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (counterNameMetric) {
|
if (counterNameMetric) {
|
||||||
// FixableQuery consists of metric name and optionally label-value pairs. We are not offering fix for complex queries yet.
|
// 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+{.*}$/);
|
const fixableQuery = simpleQueryCheck(query);
|
||||||
const verb = certain ? 'is' : 'looks like';
|
const verb = certain ? 'is' : 'looks like';
|
||||||
let label = `Selected metric ${verb} a counter.`;
|
let label = `Selected metric ${verb} a counter.`;
|
||||||
let fix: QueryFix | undefined;
|
let fix: QueryFix | undefined;
|
||||||
@ -150,3 +229,44 @@ export function getInitHints(datasource: PrometheusDatasource): QueryHint[] {
|
|||||||
|
|
||||||
return hints;
|
return hints;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getQueryTokens(query: string) {
|
||||||
|
return (
|
||||||
|
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(':'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkMetricType(
|
||||||
|
queryTokens: string[],
|
||||||
|
metricType: string,
|
||||||
|
metricsMetadata: PromMetricsMetadata,
|
||||||
|
certain: boolean
|
||||||
|
) {
|
||||||
|
// update certain to change language for counters
|
||||||
|
const nameMetric =
|
||||||
|
queryTokens.find((metricName) => {
|
||||||
|
// Only considering first type information, could be non-deterministic
|
||||||
|
const metadata = metricsMetadata[metricName];
|
||||||
|
if (metadata && metadata.type.toLowerCase() === metricType) {
|
||||||
|
certain = true;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}) ?? '';
|
||||||
|
|
||||||
|
return { nameMetric, certain };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This regex check looks for only metric name and label filters.
|
||||||
|
* This prevents hints from being shown when a query already has a functions or is complex.
|
||||||
|
* */
|
||||||
|
function simpleQueryCheck(query: string) {
|
||||||
|
return query.trim().match(/^\w+$|^\w+{.*}$/);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user