mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
refactor: data-trails auto query logic (#79435)
* refactor: data-trails auto query logic for most currently identified metric suffixes (excluding `_bucket`)
This commit is contained in:
parent
062e772bb2
commit
890d6a960f
@ -0,0 +1,170 @@
|
||||
import { getAutoQueriesForMetric } from './AutoQueryEngine';
|
||||
|
||||
function expandExpr(shortenedExpr: string) {
|
||||
return shortenedExpr.replace('...', '${metric}{${filters}}');
|
||||
}
|
||||
|
||||
describe('getAutoQueriesForMetric', () => {
|
||||
describe('Consider result.main query (only first)', () => {
|
||||
it.each([
|
||||
// no rate
|
||||
['my_metric_general', 'avg(...)', 'short', 1],
|
||||
['my_metric_bytes', 'avg(...)', 'bytes', 1],
|
||||
['my_metric_seconds', 'avg(...)', 's', 1],
|
||||
// rate with counts per second
|
||||
['my_metric_count', 'sum(rate(...[$__rate_interval]))', 'cps', 1], // cps = counts per second
|
||||
['my_metric_total', 'sum(rate(...[$__rate_interval]))', 'cps', 1],
|
||||
['my_metric_seconds_count', 'sum(rate(...[$__rate_interval]))', 'cps', 1],
|
||||
// rate with seconds per second
|
||||
['my_metric_seconds_total', 'sum(rate(...[$__rate_interval]))', 'short', 1], // s/s
|
||||
['my_metric_seconds_sum', 'avg(rate(...[$__rate_interval]))', 'short', 1],
|
||||
// rate with bytes per second
|
||||
['my_metric_bytes_total', 'sum(rate(...[$__rate_interval]))', 'Bps', 1], // bytes/s
|
||||
['my_metric_bytes_sum', 'avg(rate(...[$__rate_interval]))', 'Bps', 1],
|
||||
// Bucket
|
||||
['my_metric_bucket', 'histogram_quantile(0.99, sum by(le) (rate(...[$__rate_interval])))', 'short', 3],
|
||||
['my_metric_seconds_bucket', 'histogram_quantile(0.99, sum by(le) (rate(...[$__rate_interval])))', 's', 3],
|
||||
])('Given metric %p expect %p with unit %p', (metric, expr, unit, queryCount) => {
|
||||
const result = getAutoQueriesForMetric(metric);
|
||||
|
||||
const queryDef = result.main;
|
||||
|
||||
const expected = { expr: expandExpr(expr), unit, queryCount };
|
||||
const actual = { expr: queryDef.queries[0].expr, unit: queryDef.unit, queryCount: queryDef.queries.length };
|
||||
|
||||
expect(actual).toStrictEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Consider result.preview query (only first)', () => {
|
||||
it.each([
|
||||
// no rate
|
||||
['my_metric_general', 'avg(...)', 'short'],
|
||||
['my_metric_bytes', 'avg(...)', 'bytes'],
|
||||
['my_metric_seconds', 'avg(...)', 's'],
|
||||
// rate with counts per second
|
||||
['my_metric_count', 'sum(rate(...[$__rate_interval]))', 'cps'], // cps = counts per second
|
||||
['my_metric_total', 'sum(rate(...[$__rate_interval]))', 'cps'],
|
||||
['my_metric_seconds_count', 'sum(rate(...[$__rate_interval]))', 'cps'],
|
||||
// rate with seconds per second
|
||||
['my_metric_seconds_total', 'sum(rate(...[$__rate_interval]))', 'short'], // s/s
|
||||
['my_metric_seconds_sum', 'avg(rate(...[$__rate_interval]))', 'short'],
|
||||
// rate with bytes per second
|
||||
['my_metric_bytes_total', 'sum(rate(...[$__rate_interval]))', 'Bps'], // bytes/s
|
||||
['my_metric_bytes_sum', 'avg(rate(...[$__rate_interval]))', 'Bps'],
|
||||
// Bucket
|
||||
['my_metric_bucket', 'histogram_quantile(0.50, sum by(le) (rate(...[$__rate_interval])))', 'short'],
|
||||
['my_metric_seconds_bucket', 'histogram_quantile(0.50, sum by(le) (rate(...[$__rate_interval])))', 's'],
|
||||
])('Given metric %p expect %p with unit %p', (metric, expr, unit) => {
|
||||
const result = getAutoQueriesForMetric(metric);
|
||||
|
||||
const queryDef = result.preview;
|
||||
|
||||
const queryCount = 1;
|
||||
|
||||
const expected = { expr: expandExpr(expr), unit, queryCount };
|
||||
const actual = { expr: queryDef.queries[0].expr, unit: queryDef.unit, queryCount: queryDef.queries.length };
|
||||
|
||||
expect(actual).toStrictEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Consider result.breakdown query (only first)', () => {
|
||||
it.each([
|
||||
// no rate
|
||||
['my_metric_general', 'avg(...) by(${groupby})', 'short'],
|
||||
['my_metric_bytes', 'avg(...) by(${groupby})', 'bytes'],
|
||||
['my_metric_seconds', 'avg(...) by(${groupby})', 's'],
|
||||
// rate with counts per second
|
||||
['my_metric_count', 'sum(rate(...[$__rate_interval])) by(${groupby})', 'cps'], // cps = counts per second
|
||||
['my_metric_total', 'sum(rate(...[$__rate_interval])) by(${groupby})', 'cps'],
|
||||
['my_metric_seconds_count', 'sum(rate(...[$__rate_interval])) by(${groupby})', 'cps'],
|
||||
// rate with seconds per second
|
||||
['my_metric_seconds_total', 'sum(rate(...[$__rate_interval])) by(${groupby})', 'short'], // s/s
|
||||
['my_metric_seconds_sum', 'avg(rate(...[$__rate_interval])) by(${groupby})', 'short'],
|
||||
// rate with bytes per second
|
||||
['my_metric_bytes_total', 'sum(rate(...[$__rate_interval])) by(${groupby})', 'Bps'], // bytes/s
|
||||
['my_metric_bytes_sum', 'avg(rate(...[$__rate_interval])) by(${groupby})', 'Bps'],
|
||||
// Bucket
|
||||
['my_metric_bucket', 'histogram_quantile(0.50, sum by(le, ${groupby}) (rate(...[$__rate_interval])))', 'short'],
|
||||
[
|
||||
'my_metric_seconds_bucket',
|
||||
'histogram_quantile(0.50, sum by(le, ${groupby}) (rate(...[$__rate_interval])))',
|
||||
's',
|
||||
],
|
||||
])('Given metric %p expect %p with unit %p', (metric, expr, unit) => {
|
||||
const result = getAutoQueriesForMetric(metric);
|
||||
|
||||
const queryDef = result.breakdown;
|
||||
|
||||
const queryCount = 1;
|
||||
|
||||
const expected = { expr: expandExpr(expr), unit, queryCount };
|
||||
const actual = { expr: queryDef.queries[0].expr, unit: queryDef.unit, queryCount: queryDef.queries.length };
|
||||
|
||||
expect(actual).toStrictEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Consider result.variant', () => {
|
||||
it.each([
|
||||
// No variants
|
||||
['my_metric_count', []],
|
||||
['my_metric_seconds_count', []],
|
||||
['my_metric_bytes', []],
|
||||
['my_metric_seconds', []],
|
||||
['my_metric_general', []],
|
||||
['my_metric_seconds_total', []],
|
||||
['my_metric_seconds_sum', []],
|
||||
// Bucket variants
|
||||
[
|
||||
'my_metric_bucket',
|
||||
[
|
||||
{
|
||||
variant: 'percentiles',
|
||||
unit: 'short',
|
||||
exprs: [
|
||||
'histogram_quantile(0.99, sum by(le) (rate(${metric}{${filters}}[$__rate_interval])))',
|
||||
'histogram_quantile(0.90, sum by(le) (rate(${metric}{${filters}}[$__rate_interval])))',
|
||||
'histogram_quantile(0.50, sum by(le) (rate(${metric}{${filters}}[$__rate_interval])))',
|
||||
],
|
||||
},
|
||||
{
|
||||
variant: 'heatmap',
|
||||
unit: 'short',
|
||||
exprs: ['sum by(le) (rate(${metric}{${filters}}[$__rate_interval]))'],
|
||||
},
|
||||
],
|
||||
],
|
||||
[
|
||||
'my_metric_seconds_bucket',
|
||||
[
|
||||
{
|
||||
variant: 'percentiles',
|
||||
unit: 's',
|
||||
exprs: [
|
||||
'histogram_quantile(0.99, sum by(le) (rate(${metric}{${filters}}[$__rate_interval])))',
|
||||
'histogram_quantile(0.90, sum by(le) (rate(${metric}{${filters}}[$__rate_interval])))',
|
||||
'histogram_quantile(0.50, sum by(le) (rate(${metric}{${filters}}[$__rate_interval])))',
|
||||
],
|
||||
},
|
||||
{
|
||||
variant: 'heatmap',
|
||||
unit: 's',
|
||||
exprs: ['sum by(le) (rate(${metric}{${filters}}[$__rate_interval]))'],
|
||||
},
|
||||
],
|
||||
],
|
||||
])('Given metric %p should generate expected variants', (metric, expectedVariants) => {
|
||||
const defs = getAutoQueriesForMetric(metric);
|
||||
|
||||
const received = defs.variants.map((variant) => ({
|
||||
variant: variant.variant,
|
||||
unit: variant.unit,
|
||||
exprs: variant.queries.map((query) => query.expr),
|
||||
}));
|
||||
|
||||
expect(received).toStrictEqual(expectedVariants);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,178 +1,16 @@
|
||||
import { PanelBuilders, VizPanelBuilder } from '@grafana/scenes';
|
||||
import { PromQuery } from 'app/plugins/datasource/prometheus/types';
|
||||
import { HeatmapColorMode } from 'app/plugins/panel/heatmap/types';
|
||||
|
||||
import { VAR_FILTERS_EXPR, VAR_GROUP_BY_EXP, VAR_METRIC_EXPR } from '../shared';
|
||||
|
||||
export interface AutoQueryDef {
|
||||
variant: string;
|
||||
title: string;
|
||||
unit: string;
|
||||
queries: PromQuery[];
|
||||
vizBuilder: (def: AutoQueryDef) => VizPanelBuilder<{}, {}>;
|
||||
}
|
||||
|
||||
export interface AutoQueryInfo {
|
||||
preview: AutoQueryDef;
|
||||
main: AutoQueryDef;
|
||||
variants: AutoQueryDef[];
|
||||
breakdown: AutoQueryDef;
|
||||
}
|
||||
import { getQueryGeneratorFor } from './query-generators';
|
||||
import { AutoQueryInfo } from './types';
|
||||
|
||||
export function getAutoQueriesForMetric(metric: string): AutoQueryInfo {
|
||||
let unit = 'short';
|
||||
let agg = 'avg';
|
||||
let rate = false;
|
||||
let title = metric;
|
||||
const metricParts = metric.split('_');
|
||||
|
||||
if (metric.endsWith('seconds_sum')) {
|
||||
unit = 's';
|
||||
agg = 'avg';
|
||||
rate = true;
|
||||
} else if (metric.endsWith('seconds')) {
|
||||
unit = 's';
|
||||
agg = 'avg';
|
||||
rate = false;
|
||||
} else if (metric.endsWith('bytes')) {
|
||||
unit = 'bytes';
|
||||
agg = 'avg';
|
||||
rate = false;
|
||||
} else if (metric.endsWith('seconds_count') || metric.endsWith('seconds_total')) {
|
||||
agg = 'sum';
|
||||
rate = true;
|
||||
} else if (metric.endsWith('bucket')) {
|
||||
return getQueriesForBucketMetric(metric);
|
||||
} else if (metric.endsWith('count') || metric.endsWith('total')) {
|
||||
agg = 'sum';
|
||||
rate = true;
|
||||
const suffix = metricParts.at(-1);
|
||||
|
||||
const generator = getQueryGeneratorFor(suffix);
|
||||
|
||||
if (!generator) {
|
||||
throw new Error(`Unable to generate queries for metric "${metric}" due to issues with derived suffix "${suffix}"`);
|
||||
}
|
||||
|
||||
let query = `${VAR_METRIC_EXPR}${VAR_FILTERS_EXPR}`;
|
||||
if (rate) {
|
||||
query = `rate(${query}[$__rate_interval])`;
|
||||
}
|
||||
|
||||
const main: AutoQueryDef = {
|
||||
title: `${title}`,
|
||||
variant: 'graph',
|
||||
unit,
|
||||
queries: [{ refId: 'A', expr: `${agg}(${query})` }],
|
||||
vizBuilder: simpleGraphBuilder,
|
||||
};
|
||||
|
||||
const breakdown: AutoQueryDef = {
|
||||
title: `${title}`,
|
||||
variant: 'graph',
|
||||
unit,
|
||||
queries: [
|
||||
{
|
||||
refId: 'A',
|
||||
expr: `${agg}(${query}) by(${VAR_GROUP_BY_EXP})`,
|
||||
legendFormat: `{{${VAR_GROUP_BY_EXP}}}`,
|
||||
},
|
||||
],
|
||||
vizBuilder: simpleGraphBuilder,
|
||||
};
|
||||
|
||||
return { preview: main, main: main, breakdown: breakdown, variants: [] };
|
||||
}
|
||||
|
||||
function getQueriesForBucketMetric(metric: string): AutoQueryInfo {
|
||||
let unit = 'short';
|
||||
|
||||
if (metric.endsWith('seconds_bucket')) {
|
||||
unit = 's';
|
||||
}
|
||||
|
||||
const p50: AutoQueryDef = {
|
||||
title: metric,
|
||||
variant: 'p50',
|
||||
unit,
|
||||
queries: [
|
||||
{
|
||||
refId: 'A',
|
||||
expr: `histogram_quantile(0.50, sum by(le) (rate(${VAR_METRIC_EXPR}${VAR_FILTERS_EXPR}[$__rate_interval])))`,
|
||||
},
|
||||
],
|
||||
vizBuilder: simpleGraphBuilder,
|
||||
};
|
||||
|
||||
const breakdown: AutoQueryDef = {
|
||||
title: metric,
|
||||
variant: 'p50',
|
||||
unit,
|
||||
queries: [
|
||||
{
|
||||
refId: 'A',
|
||||
expr: `histogram_quantile(0.50, sum by(le, ${VAR_GROUP_BY_EXP}) (rate(${VAR_METRIC_EXPR}${VAR_FILTERS_EXPR}[$__rate_interval])))`,
|
||||
},
|
||||
],
|
||||
vizBuilder: simpleGraphBuilder,
|
||||
};
|
||||
|
||||
const percentiles: AutoQueryDef = {
|
||||
title: metric,
|
||||
variant: 'percentiles',
|
||||
unit,
|
||||
queries: [
|
||||
{
|
||||
refId: 'A',
|
||||
expr: `histogram_quantile(0.99, sum by(le) (rate(${VAR_METRIC_EXPR}${VAR_FILTERS_EXPR}[$__rate_interval])))`,
|
||||
legendFormat: '99th Percentile',
|
||||
},
|
||||
{
|
||||
refId: 'B',
|
||||
expr: `histogram_quantile(0.90, sum by(le) (rate(${VAR_METRIC_EXPR}${VAR_FILTERS_EXPR}[$__rate_interval])))`,
|
||||
legendFormat: '90th Percentile',
|
||||
},
|
||||
{
|
||||
refId: 'C',
|
||||
expr: `histogram_quantile(0.50, sum by(le) (rate(${VAR_METRIC_EXPR}${VAR_FILTERS_EXPR}[$__rate_interval])))`,
|
||||
legendFormat: '50th Percentile',
|
||||
},
|
||||
],
|
||||
vizBuilder: percentilesGraphBuilder,
|
||||
};
|
||||
|
||||
const heatmap: AutoQueryDef = {
|
||||
title: metric,
|
||||
variant: 'heatmap',
|
||||
unit,
|
||||
queries: [
|
||||
{
|
||||
refId: 'A',
|
||||
expr: `sum by(le) (rate(${VAR_METRIC_EXPR}${VAR_FILTERS_EXPR}[$__rate_interval]))`,
|
||||
format: 'heatmap',
|
||||
},
|
||||
],
|
||||
vizBuilder: heatmapGraphBuilder,
|
||||
};
|
||||
|
||||
return { preview: p50, main: percentiles, variants: [percentiles, heatmap], breakdown: breakdown };
|
||||
}
|
||||
|
||||
function simpleGraphBuilder(def: AutoQueryDef) {
|
||||
return PanelBuilders.timeseries()
|
||||
.setTitle(def.title)
|
||||
.setUnit(def.unit)
|
||||
.setOption('legend', { showLegend: false })
|
||||
.setCustomFieldConfig('fillOpacity', 9);
|
||||
}
|
||||
|
||||
function percentilesGraphBuilder(def: AutoQueryDef) {
|
||||
return PanelBuilders.timeseries().setTitle(def.title).setUnit(def.unit).setCustomFieldConfig('fillOpacity', 9);
|
||||
}
|
||||
|
||||
function heatmapGraphBuilder(def: AutoQueryDef) {
|
||||
return PanelBuilders.heatmap()
|
||||
.setTitle(def.title)
|
||||
.setUnit(def.unit)
|
||||
.setOption('calculate', false)
|
||||
.setOption('color', {
|
||||
mode: HeatmapColorMode.Scheme,
|
||||
exponent: 0.5,
|
||||
scheme: 'Spectral',
|
||||
steps: 32,
|
||||
reverse: false,
|
||||
});
|
||||
return generator(metricParts);
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import { Field, RadioButtonGroup, useStyles2, Stack } from '@grafana/ui';
|
||||
import { trailDS } from '../shared';
|
||||
import { getTrailSettings } from '../utils';
|
||||
|
||||
import { AutoQueryDef, AutoQueryInfo } from './AutoQueryEngine';
|
||||
import { AutoQueryInfo, AutoQueryDef } from './types';
|
||||
|
||||
export interface AutoVizPanelState extends SceneObjectState {
|
||||
panel?: VizPanel;
|
||||
|
@ -0,0 +1,18 @@
|
||||
import { PanelBuilders } from '@grafana/scenes';
|
||||
import { HeatmapColorMode } from 'app/plugins/panel/heatmap/types';
|
||||
|
||||
import { AutoQueryDef } from '../types';
|
||||
|
||||
export function heatmapGraphBuilder(def: AutoQueryDef) {
|
||||
return PanelBuilders.heatmap()
|
||||
.setTitle(def.title)
|
||||
.setUnit(def.unit)
|
||||
.setOption('calculate', false)
|
||||
.setOption('color', {
|
||||
mode: HeatmapColorMode.Scheme,
|
||||
exponent: 0.5,
|
||||
scheme: 'Spectral',
|
||||
steps: 32,
|
||||
reverse: false,
|
||||
});
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import { PanelBuilders } from '@grafana/scenes';
|
||||
|
||||
import { AutoQueryDef } from '../types';
|
||||
|
||||
export function percentilesGraphBuilder(def: AutoQueryDef) {
|
||||
return PanelBuilders.timeseries().setTitle(def.title).setUnit(def.unit).setCustomFieldConfig('fillOpacity', 9);
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
import { PanelBuilders } from '@grafana/scenes';
|
||||
|
||||
import { AutoQueryDef } from '../types';
|
||||
|
||||
export function simpleGraphBuilder(def: AutoQueryDef) {
|
||||
return PanelBuilders.timeseries()
|
||||
.setTitle(def.title)
|
||||
.setUnit(def.unit)
|
||||
.setOption('legend', { showLegend: false })
|
||||
.setCustomFieldConfig('fillOpacity', 9);
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
import { VAR_FILTERS_EXPR, VAR_GROUP_BY_EXP, VAR_METRIC_EXPR } from '../../../shared';
|
||||
import { heatmapGraphBuilder } from '../../graph-builders/heatmap';
|
||||
import { percentilesGraphBuilder } from '../../graph-builders/percentiles';
|
||||
import { simpleGraphBuilder } from '../../graph-builders/simple';
|
||||
import { AutoQueryDef } from '../../types';
|
||||
|
||||
function generator(metricParts: string[]) {
|
||||
let unit = 'short';
|
||||
|
||||
const title = `${VAR_METRIC_EXPR}`;
|
||||
|
||||
const unitSuffix = metricParts.at(-2);
|
||||
|
||||
if (unitSuffix === 'seconds') {
|
||||
// TODO Map to other units
|
||||
unit = 's';
|
||||
}
|
||||
|
||||
const p50: AutoQueryDef = {
|
||||
title,
|
||||
variant: 'p50',
|
||||
unit,
|
||||
queries: [
|
||||
{
|
||||
refId: 'A',
|
||||
expr: `histogram_quantile(0.50, sum by(le) (rate(${VAR_METRIC_EXPR}${VAR_FILTERS_EXPR}[$__rate_interval])))`,
|
||||
},
|
||||
],
|
||||
vizBuilder: simpleGraphBuilder,
|
||||
};
|
||||
|
||||
const breakdown: AutoQueryDef = {
|
||||
title,
|
||||
variant: 'p50',
|
||||
unit,
|
||||
queries: [
|
||||
{
|
||||
refId: 'A',
|
||||
expr: `histogram_quantile(0.50, sum by(le, ${VAR_GROUP_BY_EXP}) (rate(${VAR_METRIC_EXPR}${VAR_FILTERS_EXPR}[$__rate_interval])))`,
|
||||
},
|
||||
],
|
||||
vizBuilder: simpleGraphBuilder,
|
||||
};
|
||||
|
||||
const percentiles: AutoQueryDef = {
|
||||
title,
|
||||
variant: 'percentiles',
|
||||
unit,
|
||||
queries: [
|
||||
{
|
||||
refId: 'A',
|
||||
expr: `histogram_quantile(0.99, sum by(le) (rate(${VAR_METRIC_EXPR}${VAR_FILTERS_EXPR}[$__rate_interval])))`,
|
||||
legendFormat: '99th Percentile',
|
||||
},
|
||||
{
|
||||
refId: 'B',
|
||||
expr: `histogram_quantile(0.90, sum by(le) (rate(${VAR_METRIC_EXPR}${VAR_FILTERS_EXPR}[$__rate_interval])))`,
|
||||
legendFormat: '90th Percentile',
|
||||
},
|
||||
{
|
||||
refId: 'C',
|
||||
expr: `histogram_quantile(0.50, sum by(le) (rate(${VAR_METRIC_EXPR}${VAR_FILTERS_EXPR}[$__rate_interval])))`,
|
||||
legendFormat: '50th Percentile',
|
||||
},
|
||||
],
|
||||
vizBuilder: percentilesGraphBuilder,
|
||||
};
|
||||
|
||||
const heatmap: AutoQueryDef = {
|
||||
title,
|
||||
variant: 'heatmap',
|
||||
unit,
|
||||
queries: [
|
||||
{
|
||||
refId: 'A',
|
||||
expr: `sum by(le) (rate(${VAR_METRIC_EXPR}${VAR_FILTERS_EXPR}[$__rate_interval]))`,
|
||||
format: 'heatmap',
|
||||
},
|
||||
],
|
||||
vizBuilder: heatmapGraphBuilder,
|
||||
};
|
||||
|
||||
return { preview: p50, main: percentiles, variants: [percentiles, heatmap], breakdown: breakdown };
|
||||
}
|
||||
|
||||
export default { generator };
|
@ -0,0 +1,9 @@
|
||||
import { generateQueries } from './queries';
|
||||
import { getGeneratorParameters } from './rules';
|
||||
|
||||
function generator(metricParts: string[]) {
|
||||
const params = getGeneratorParameters(metricParts);
|
||||
return generateQueries(params);
|
||||
}
|
||||
|
||||
export default { generator };
|
@ -0,0 +1,62 @@
|
||||
import { VAR_GROUP_BY_EXP } from '../../../shared';
|
||||
import { AutoQueryDef, AutoQueryInfo } from '../../types';
|
||||
|
||||
import { generateQueries, getGeneralBaseQuery } from './queries';
|
||||
|
||||
describe('generateQueries', () => {
|
||||
const agg = 'mockagg';
|
||||
const unit = 'mockunit';
|
||||
|
||||
type QueryInfoKey = keyof AutoQueryInfo;
|
||||
|
||||
function testRateIndependentAssertions(queryDef: AutoQueryDef, key: QueryInfoKey) {
|
||||
describe('regardless of rate', () => {
|
||||
test(`specified unit must be propagated`, () => expect(queryDef.unit).toBe(unit));
|
||||
test(`only one query is expected`, () => expect(queryDef.queries.length).toBe(1));
|
||||
const query = queryDef.queries[0];
|
||||
test(`specified agg function must be propagated in the query expr`, () => {
|
||||
const queryAggFunction = query.expr.split('(', 2)[0];
|
||||
expect(queryAggFunction).toBe(agg);
|
||||
});
|
||||
if (key === 'breakdown') {
|
||||
const expectedSuffix = `by(${VAR_GROUP_BY_EXP})`;
|
||||
test(`breakdown query must end with "${expectedSuffix}"`, () => {
|
||||
const suffix = query.expr.substring(query.expr.length - expectedSuffix.length);
|
||||
expect(suffix).toBe(expectedSuffix);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function testRateSpecificAssertions(queryDef: AutoQueryDef, rate: boolean) {
|
||||
const query = queryDef.queries[0];
|
||||
const firstParen = query.expr.indexOf('(');
|
||||
const expectedBaseQuery = getGeneralBaseQuery(rate);
|
||||
const detectedBaseQuery = query.expr.substring(firstParen + 1, firstParen + 1 + expectedBaseQuery.length);
|
||||
|
||||
describe(`since rate is ${rate}`, () => {
|
||||
test(`base query must be "${expectedBaseQuery}"`, () => expect(detectedBaseQuery).toBe(expectedBaseQuery));
|
||||
});
|
||||
}
|
||||
|
||||
for (const rate of [true, false]) {
|
||||
describe(`when rate is ${rate}`, () => {
|
||||
const queryInfo = generateQueries({ agg, unit, rate });
|
||||
|
||||
let key: QueryInfoKey;
|
||||
for (key in queryInfo) {
|
||||
if (key !== 'variants') {
|
||||
const queryDef = queryInfo[key];
|
||||
describe(`queryInfo.${key}`, () => testRateIndependentAssertions(queryDef, key));
|
||||
describe(`queryInfo.${key}`, () => testRateSpecificAssertions(queryDef, rate));
|
||||
continue;
|
||||
}
|
||||
|
||||
queryInfo[key].forEach((queryDef, index) => {
|
||||
describe(`queryInfo.${key}[${index}]`, () => testRateIndependentAssertions(queryDef, key));
|
||||
describe(`queryInfo.${key}[${index}]`, () => testRateSpecificAssertions(queryDef, rate));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
@ -0,0 +1,48 @@
|
||||
import { VAR_FILTERS_EXPR, VAR_GROUP_BY_EXP, VAR_METRIC_EXPR } from '../../../shared';
|
||||
import { simpleGraphBuilder } from '../../graph-builders/simple';
|
||||
import { AutoQueryDef, AutoQueryInfo } from '../../types';
|
||||
|
||||
import { AutoQueryParameters } from './types';
|
||||
|
||||
const GENERAL_BASE_QUERY = `${VAR_METRIC_EXPR}${VAR_FILTERS_EXPR}`;
|
||||
const GENERAL_RATE_BASE_QUERY = `rate(${GENERAL_BASE_QUERY}[$__rate_interval])`;
|
||||
|
||||
export function getGeneralBaseQuery(rate: boolean) {
|
||||
return rate ? GENERAL_RATE_BASE_QUERY : GENERAL_BASE_QUERY;
|
||||
}
|
||||
|
||||
export function generateQueries({ agg, rate, unit }: AutoQueryParameters): AutoQueryInfo {
|
||||
const baseQuery = getGeneralBaseQuery(rate);
|
||||
|
||||
const main = createMainQuery(baseQuery, agg, unit);
|
||||
|
||||
const breakdown = createBreakdownQuery(baseQuery, agg, unit);
|
||||
|
||||
return { preview: main, main: main, breakdown: breakdown, variants: [] };
|
||||
}
|
||||
|
||||
function createMainQuery(baseQuery: string, agg: string, unit: string): AutoQueryDef {
|
||||
return {
|
||||
title: `${VAR_METRIC_EXPR}`,
|
||||
variant: 'graph',
|
||||
unit,
|
||||
queries: [{ refId: 'A', expr: `${agg}(${baseQuery})` }],
|
||||
vizBuilder: simpleGraphBuilder,
|
||||
};
|
||||
}
|
||||
|
||||
function createBreakdownQuery(baseQuery: string, agg: string, unit: string): AutoQueryDef {
|
||||
return {
|
||||
title: `${VAR_METRIC_EXPR}`,
|
||||
variant: 'graph',
|
||||
unit,
|
||||
queries: [
|
||||
{
|
||||
refId: 'A',
|
||||
expr: `${agg}(${baseQuery}) by(${VAR_GROUP_BY_EXP})`,
|
||||
legendFormat: `{{${VAR_GROUP_BY_EXP}}}`,
|
||||
},
|
||||
],
|
||||
vizBuilder: simpleGraphBuilder,
|
||||
};
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
import { getUnit, getPerSecondRateUnit } from '../../units';
|
||||
|
||||
import { AutoQueryParameters } from './types';
|
||||
|
||||
/** These suffixes will set rate to true */
|
||||
const RATE_SUFFIXES = new Set(['count', 'total', 'sum']);
|
||||
|
||||
/** Non-default aggregattion keyed by suffix */
|
||||
const SPECIFIC_AGGREGATIONS_FOR_SUFFIX: Record<string, string> = {
|
||||
count: 'sum',
|
||||
total: 'sum',
|
||||
sum: 'avg',
|
||||
};
|
||||
|
||||
function checkPreviousForUnit(suffix: string) {
|
||||
return suffix === 'total' || suffix === 'sum';
|
||||
}
|
||||
|
||||
export function getGeneratorParameters(metricParts: string[]): AutoQueryParameters {
|
||||
const suffix = metricParts.at(-1);
|
||||
|
||||
if (suffix == null) {
|
||||
throw new Error('Invalid metric parameter');
|
||||
}
|
||||
|
||||
const rate = RATE_SUFFIXES.has(suffix);
|
||||
|
||||
const unitSuffix = checkPreviousForUnit(suffix) ? metricParts.at(-2) : suffix;
|
||||
|
||||
const unit = rate ? getPerSecondRateUnit(unitSuffix) : getUnit(unitSuffix);
|
||||
|
||||
const agg = SPECIFIC_AGGREGATIONS_FOR_SUFFIX[suffix] || 'avg';
|
||||
|
||||
return {
|
||||
agg,
|
||||
unit,
|
||||
rate,
|
||||
};
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
export type AutoQueryParameters = {
|
||||
agg: string;
|
||||
unit: string;
|
||||
rate: boolean;
|
||||
};
|
@ -0,0 +1,14 @@
|
||||
import bucket from './bucket';
|
||||
import general from './common';
|
||||
import { MetricQueriesGenerator } from './types';
|
||||
|
||||
const SUFFIX_TO_ALTERNATIVE_GENERATOR: Record<string, MetricQueriesGenerator> = {
|
||||
bucket: bucket.generator,
|
||||
};
|
||||
|
||||
export function getQueryGeneratorFor(suffix?: string) {
|
||||
if (!suffix || suffix === '') {
|
||||
return null;
|
||||
}
|
||||
return SUFFIX_TO_ALTERNATIVE_GENERATOR[suffix] || general.generator;
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
import { AutoQueryInfo } from '../types';
|
||||
|
||||
export type MetricQueriesGenerator = (metricParts: string[]) => AutoQueryInfo;
|
17
public/app/features/trails/AutomaticMetricQueries/types.ts
Normal file
17
public/app/features/trails/AutomaticMetricQueries/types.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { VizPanelBuilder } from '@grafana/scenes';
|
||||
import { PromQuery } from 'app/plugins/datasource/prometheus/types';
|
||||
|
||||
export interface AutoQueryDef {
|
||||
variant: string;
|
||||
title: string;
|
||||
unit: string;
|
||||
queries: PromQuery[];
|
||||
vizBuilder: (def: AutoQueryDef) => VizPanelBuilder<{}, {}>;
|
||||
}
|
||||
|
||||
export interface AutoQueryInfo {
|
||||
preview: AutoQueryDef;
|
||||
main: AutoQueryDef;
|
||||
variants: AutoQueryDef[];
|
||||
breakdown: AutoQueryDef;
|
||||
}
|
21
public/app/features/trails/AutomaticMetricQueries/units.ts
Normal file
21
public/app/features/trails/AutomaticMetricQueries/units.ts
Normal file
@ -0,0 +1,21 @@
|
||||
const DEFAULT_UNIT = 'short';
|
||||
|
||||
export function getUnit(metricPart: string | undefined) {
|
||||
return (metricPart && UNIT_MAP[metricPart]) || DEFAULT_UNIT;
|
||||
}
|
||||
|
||||
const UNIT_MAP: Record<string, string> = {
|
||||
bytes: 'bytes',
|
||||
seconds: 's',
|
||||
};
|
||||
|
||||
const RATE_UNIT_MAP: Record<string, string> = {
|
||||
bytes: 'Bps', // bytes per second
|
||||
seconds: 'short', // seconds per second is unitless -- this may indicate a count of some resource that is active
|
||||
};
|
||||
|
||||
const DEFAULT_RATE_UNIT = 'cps'; // Count per second
|
||||
|
||||
export function getPerSecondRateUnit(metricPart: string | undefined) {
|
||||
return (metricPart && RATE_UNIT_MAP[metricPart]) || DEFAULT_RATE_UNIT;
|
||||
}
|
@ -24,7 +24,8 @@ import { Button, Field, RadioButtonGroup, useStyles2 } from '@grafana/ui';
|
||||
import { ALL_VARIABLE_VALUE } from 'app/features/variables/constants';
|
||||
|
||||
import { AddToFiltersGraphAction } from './AddToFiltersGraphAction';
|
||||
import { AutoQueryDef, getAutoQueriesForMetric } from './AutomaticMetricQueries/AutoQueryEngine';
|
||||
import { getAutoQueriesForMetric } from './AutomaticMetricQueries/AutoQueryEngine';
|
||||
import { AutoQueryDef } from './AutomaticMetricQueries/types';
|
||||
import { ByFrameRepeater } from './ByFrameRepeater';
|
||||
import { LayoutSwitcher } from './LayoutSwitcher';
|
||||
import { MetricScene } from './MetricScene';
|
||||
|
Loading…
Reference in New Issue
Block a user