Prometheus: Parsing for vector matching and UI changes (#46668)

This commit is contained in:
Andrej Ocenas
2022-03-17 10:50:49 +01:00
committed by GitHub
parent 7df22c1573
commit ee87ed3b51
5 changed files with 143 additions and 41 deletions

View File

@@ -258,26 +258,6 @@ describe('PromQueryModeller', () => {
).toBe('(metric_a * 1000) / metric_b');
});
it('Can render with binary queries with vectorMatches expression', () => {
expect(
modeller.renderQuery({
metric: 'metric_a',
labels: [],
operations: [],
binaryQueries: [
{
operator: '/',
vectorMatches: 'on(le)',
query: {
metric: 'metric_b',
labels: [],
operations: [],
},
},
],
})
).toBe('metric_a / on(le) metric_b');
});
it('Can render functions that require a range as a parameter', () => {
expect(
modeller.renderQuery({
@@ -305,6 +285,7 @@ describe('PromQueryModeller', () => {
})
).toBe('label_join(metric_a, "label_1", ",", "label_2")');
});
it('Can render label_join with extra parameters', () => {
expect(
modeller.renderQuery({
@@ -314,4 +295,26 @@ describe('PromQueryModeller', () => {
})
).toBe('label_join(metric_a, "label_1", ", ", "label_2", "label_3", "label_4", "label_5")');
});
it('can render vector matchers', () => {
expect(
modeller.renderQuery({
metric: 'metric_a',
labels: [],
operations: [],
binaryQueries: [
{
operator: '/',
vectorMatches: 'le, foo',
vectorMatchesType: 'on',
query: {
metric: 'metric_b',
labels: [],
operations: [],
},
},
],
})
).toBe('metric_a / on(le, foo) metric_b');
});
});

View File

@@ -37,18 +37,35 @@ export const NestedQuery = React.memo<Props>(({ nestedQuery, index, datasource,
}}
/>
<div className={styles.name}>Vector matches</div>
<AutoSizeInput
minWidth={20}
defaultValue={nestedQuery.vectorMatches}
onCommitChange={(evt) => {
onChange(index, {
...nestedQuery,
vectorMatches: evt.currentTarget.value,
});
}}
/>
<div className={styles.vectorMatchWrapper}>
<Select<PromVisualQueryBinary['vectorMatchesType']>
width="auto"
value={nestedQuery.vectorMatchesType || 'on'}
allowCustomValue
options={[
{ value: 'on', label: 'on' },
{ value: 'ignoring', label: 'ignoring' },
]}
onChange={(val) => {
onChange(index, {
...nestedQuery,
vectorMatchesType: val.value,
});
}}
/>
<AutoSizeInput
className={styles.vectorMatchInput}
minWidth={20}
defaultValue={nestedQuery.vectorMatches}
onCommitChange={(evt) => {
onChange(index, {
...nestedQuery,
vectorMatches: evt.currentTarget.value,
vectorMatchesType: nestedQuery.vectorMatchesType || 'on',
});
}}
/>
</div>
<FlexItem grow={1} />
<IconButton name="times" size="sm" onClick={() => onRemove(index)} />
</div>
@@ -76,21 +93,33 @@ NestedQuery.displayName = 'NestedQuery';
const getStyles = (theme: GrafanaTheme2) => {
return {
card: css({
label: 'card',
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(0.5),
}),
header: css({
label: 'header',
padding: theme.spacing(0.5, 0.5, 0.5, 1),
gap: theme.spacing(1),
display: 'flex',
alignItems: 'center',
}),
name: css({
label: 'name',
whiteSpace: 'nowrap',
}),
body: css({
label: 'body',
paddingLeft: theme.spacing(2),
}),
vectorMatchInput: css({
label: 'vectorMatchInput',
marginLeft: -1,
}),
vectorMatchWrapper: css({
label: 'vectorMatchWrapper',
display: 'flex',
}),
};
};

View File

@@ -505,6 +505,42 @@ describe('buildVisualQueryFromString', () => {
},
});
});
it('handles binary operation with vector matchers', () => {
expect(buildVisualQueryFromString('foo * on(foo, bar) metric')).toEqual({
errors: [],
query: {
metric: 'foo',
labels: [],
operations: [],
binaryQueries: [
{
operator: '*',
vectorMatches: 'foo, bar',
vectorMatchesType: 'on',
query: { metric: 'metric', labels: [], operations: [] },
},
],
},
});
expect(buildVisualQueryFromString('foo * ignoring(foo) metric')).toEqual({
errors: [],
query: {
metric: 'foo',
labels: [],
operations: [],
binaryQueries: [
{
operator: '*',
vectorMatches: 'foo',
vectorMatchesType: 'ignoring',
query: { metric: 'metric', labels: [], operations: [] },
},
],
},
});
});
});
function noErrors(query: PromVisualQuery) {

View File

@@ -1,7 +1,7 @@
import { parser } from 'lezer-promql';
import { SyntaxNode, TreeCursor } from '@lezer/common';
import { QueryBuilderLabelFilter, QueryBuilderOperation } from './shared/types';
import { PromVisualQuery } from './types';
import { PromVisualQuery, PromVisualQueryBinary } from './types';
import { binaryScalarDefs } from './binaryScalarOperations';
// Taken from template_srv, but copied so to not mess with the regex.index which is manipulated in the service
@@ -342,7 +342,8 @@ function handleBinary(expr: string, node: SyntaxNode, context: Context) {
const visQuery = context.query;
const left = node.firstChild!;
const op = getString(expr, left.nextSibling);
const binModifier = node.getChild('BinModifiers')?.getChild('Bool');
const binModifier = getBinaryModifier(expr, node.getChild('BinModifiers'));
const right = node.lastChild!;
const opDef = operatorToOpName[op];
@@ -362,13 +363,13 @@ function handleBinary(expr: string, node: SyntaxNode, context: Context) {
}
if (rightNumber) {
visQuery.operations.push(makeBinOp(opDef, expr, right, binModifier));
visQuery.operations.push(makeBinOp(opDef, expr, right, !!binModifier?.isBool));
} else if (rightBinary) {
// Due to the way binary ops are parsed we can get a binary operation on the right that starts with a number which
// is a factor for a current binary operation. So we have to add it as an operation now.
const leftMostChild = getLeftMostChild(right);
if (leftMostChild?.name === 'NumberLiteral') {
visQuery.operations.push(makeBinOp(opDef, expr, leftMostChild, binModifier));
visQuery.operations.push(makeBinOp(opDef, expr, leftMostChild, !!binModifier?.isBool));
}
// If we added the first number literal as operation here we still can continue and handle the rest as the first
@@ -376,7 +377,7 @@ function handleBinary(expr: string, node: SyntaxNode, context: Context) {
handleExpression(expr, right, context);
} else {
visQuery.binaryQueries = visQuery.binaryQueries || [];
const binQuery = {
const binQuery: PromVisualQueryBinary = {
operator: op,
query: {
metric: '',
@@ -384,6 +385,10 @@ function handleBinary(expr: string, node: SyntaxNode, context: Context) {
operations: [],
},
};
if (binModifier?.isMatcher) {
binQuery.vectorMatchesType = binModifier.matchType;
binQuery.vectorMatches = binModifier.matches;
}
visQuery.binaryQueries.push(binQuery);
handleExpression(expr, right, {
query: binQuery.query,
@@ -392,15 +397,43 @@ function handleBinary(expr: string, node: SyntaxNode, context: Context) {
}
}
function getBinaryModifier(
expr: string,
node: SyntaxNode | null
):
| { isBool: true; isMatcher: false }
| { isBool: false; isMatcher: true; matches: string; matchType: 'ignoring' | 'on' }
| undefined {
if (!node) {
return undefined;
}
if (node.getChild('Bool')) {
return { isBool: true, isMatcher: false };
} else {
const matcher = node.getChild('OnOrIgnoring');
if (!matcher) {
// Not sure what this could be, maybe should be an error.
return undefined;
}
const labels = getString(expr, matcher.getChild('GroupingLabels')?.getChild('GroupingLabelList'));
return {
isMatcher: true,
isBool: false,
matches: labels,
matchType: matcher.getChild('On') ? 'on' : 'ignoring',
};
}
}
function makeBinOp(
opDef: { id: string; comparison?: boolean },
expr: string,
numberNode: SyntaxNode,
binModifier?: SyntaxNode | null
hasBool: boolean
) {
const params: any[] = [parseFloat(getString(expr, numberNode))];
if (opDef.comparison) {
params.unshift(Boolean(binModifier));
params.unshift(hasBool);
}
return {
id: opDef.id,
@@ -414,7 +447,7 @@ function makeBinOp(
* @param expr
* @param node
*/
function getString(expr: string, node: SyntaxNode | TreeCursor | null) {
function getString(expr: string, node: SyntaxNode | TreeCursor | null | undefined) {
if (!node) {
return '';
}

View File

@@ -9,6 +9,7 @@ import {
export interface VisualQueryBinary<T> {
operator: string;
vectorMatchesType?: 'on' | 'ignoring';
vectorMatches?: string;
query: T;
}
@@ -66,7 +67,7 @@ export abstract class LokiAndPromQueryModellerBase<T extends QueryWithOperations
let result = leftOperand + ` ${binaryQuery.operator} `;
if (binaryQuery.vectorMatches) {
result += `${binaryQuery.vectorMatches} `;
result += `${binaryQuery.vectorMatchesType}(${binaryQuery.vectorMatches}) `;
}
return result + this.renderQuery(binaryQuery.query, true);