mirror of
https://github.com/grafana/grafana.git
synced 2025-02-15 10:03:33 -06:00
406 lines
11 KiB
TypeScript
406 lines
11 KiB
TypeScript
import { PrometheusDatasource } from './datasource';
|
|
import _ from 'lodash';
|
|
|
|
export class PromCompleter {
|
|
labelQueryCache: any;
|
|
labelNameCache: any;
|
|
labelValueCache: any;
|
|
templateVariableCompletions: any;
|
|
|
|
identifierRegexps = [/\[/, /[a-zA-Z0-9_:]/];
|
|
|
|
constructor(private datasource: PrometheusDatasource, private templateSrv) {
|
|
this.labelQueryCache = {};
|
|
this.labelNameCache = {};
|
|
this.labelValueCache = {};
|
|
this.templateVariableCompletions = this.templateSrv.variables.map(variable => {
|
|
return {
|
|
caption: '$' + variable.name,
|
|
value: '$' + variable.name,
|
|
meta: 'variable',
|
|
score: Number.MAX_VALUE,
|
|
};
|
|
});
|
|
}
|
|
|
|
getCompletions(editor, session, pos, prefix, callback) {
|
|
const wrappedCallback = (err, completions) => {
|
|
completions = completions.concat(this.templateVariableCompletions);
|
|
return callback(err, completions);
|
|
};
|
|
|
|
const token = session.getTokenAt(pos.row, pos.column);
|
|
|
|
switch (token.type) {
|
|
case 'entity.name.tag.label-matcher':
|
|
this.getCompletionsForLabelMatcherName(session, pos).then(completions => {
|
|
wrappedCallback(null, completions);
|
|
});
|
|
return;
|
|
case 'string.quoted.label-matcher':
|
|
this.getCompletionsForLabelMatcherValue(session, pos).then(completions => {
|
|
wrappedCallback(null, completions);
|
|
});
|
|
return;
|
|
case 'entity.name.tag.label-list-matcher':
|
|
this.getCompletionsForBinaryOperator(session, pos).then(completions => {
|
|
wrappedCallback(null, completions);
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (token.type === 'paren.lparen' && token.value === '[') {
|
|
const vectors = [];
|
|
for (const unit of ['s', 'm', 'h']) {
|
|
for (const value of [1, 5, 10, 30]) {
|
|
vectors.push({
|
|
caption: value + unit,
|
|
value: '[' + value + unit,
|
|
meta: 'range vector',
|
|
});
|
|
}
|
|
}
|
|
|
|
vectors.unshift({
|
|
caption: '$__interval_ms',
|
|
value: '[$__interval_ms',
|
|
meta: 'range vector',
|
|
});
|
|
|
|
vectors.unshift({
|
|
caption: '$__interval',
|
|
value: '[$__interval',
|
|
meta: 'range vector',
|
|
});
|
|
|
|
wrappedCallback(null, vectors);
|
|
return;
|
|
}
|
|
|
|
const query = prefix;
|
|
|
|
return this.datasource.performSuggestQuery(query, true).then(metricNames => {
|
|
wrappedCallback(
|
|
null,
|
|
metricNames.map(name => {
|
|
let value = name;
|
|
if (prefix === '(') {
|
|
value = '(' + name;
|
|
}
|
|
|
|
return {
|
|
caption: name,
|
|
value: value,
|
|
meta: 'metric',
|
|
};
|
|
})
|
|
);
|
|
});
|
|
}
|
|
|
|
getCompletionsForLabelMatcherName(session, pos) {
|
|
const metricName = this.findMetricName(session, pos.row, pos.column);
|
|
if (!metricName) {
|
|
return Promise.resolve(this.transformToCompletions(['__name__', 'instance', 'job'], 'label name'));
|
|
}
|
|
|
|
if (this.labelNameCache[metricName]) {
|
|
return Promise.resolve(this.labelNameCache[metricName]);
|
|
}
|
|
|
|
return this.getLabelNameAndValueForExpression(metricName, 'metricName').then(result => {
|
|
const labelNames = this.transformToCompletions(
|
|
_.uniq(
|
|
_.flatten(
|
|
result.map(r => {
|
|
return Object.keys(r);
|
|
})
|
|
)
|
|
),
|
|
'label name'
|
|
);
|
|
this.labelNameCache[metricName] = labelNames;
|
|
return Promise.resolve(labelNames);
|
|
});
|
|
}
|
|
|
|
getCompletionsForLabelMatcherValue(session, pos) {
|
|
const metricName = this.findMetricName(session, pos.row, pos.column);
|
|
if (!metricName) {
|
|
return Promise.resolve([]);
|
|
}
|
|
|
|
const labelNameToken = this.findToken(
|
|
session,
|
|
pos.row,
|
|
pos.column,
|
|
'entity.name.tag.label-matcher',
|
|
null,
|
|
'paren.lparen.label-matcher'
|
|
);
|
|
if (!labelNameToken) {
|
|
return Promise.resolve([]);
|
|
}
|
|
const labelName = labelNameToken.value;
|
|
|
|
if (this.labelValueCache[metricName] && this.labelValueCache[metricName][labelName]) {
|
|
return Promise.resolve(this.labelValueCache[metricName][labelName]);
|
|
}
|
|
|
|
return this.getLabelNameAndValueForExpression(metricName, 'metricName').then(result => {
|
|
const labelValues = this.transformToCompletions(
|
|
_.uniq(
|
|
result.map(r => {
|
|
return r[labelName];
|
|
})
|
|
),
|
|
'label value'
|
|
);
|
|
this.labelValueCache[metricName] = this.labelValueCache[metricName] || {};
|
|
this.labelValueCache[metricName][labelName] = labelValues;
|
|
return Promise.resolve(labelValues);
|
|
});
|
|
}
|
|
|
|
getCompletionsForBinaryOperator(session, pos) {
|
|
const keywordOperatorToken = this.findToken(session, pos.row, pos.column, 'keyword.control', null, 'identifier');
|
|
if (!keywordOperatorToken) {
|
|
return Promise.resolve([]);
|
|
}
|
|
let rparenToken, expr;
|
|
switch (keywordOperatorToken.value) {
|
|
case 'by':
|
|
case 'without':
|
|
rparenToken = this.findToken(
|
|
session,
|
|
keywordOperatorToken.row,
|
|
keywordOperatorToken.column,
|
|
'paren.rparen',
|
|
null,
|
|
'identifier'
|
|
);
|
|
if (!rparenToken) {
|
|
return Promise.resolve([]);
|
|
}
|
|
expr = this.findExpressionMatchedParen(session, rparenToken.row, rparenToken.column);
|
|
if (expr === '') {
|
|
return Promise.resolve([]);
|
|
}
|
|
return this.getLabelNameAndValueForExpression(expr, 'expression').then(result => {
|
|
const labelNames = this.transformToCompletions(
|
|
_.uniq(
|
|
_.flatten(
|
|
result.map(r => {
|
|
return Object.keys(r);
|
|
})
|
|
)
|
|
),
|
|
'label name'
|
|
);
|
|
this.labelNameCache[expr] = labelNames;
|
|
return labelNames;
|
|
});
|
|
case 'on':
|
|
case 'ignoring':
|
|
case 'group_left':
|
|
case 'group_right':
|
|
const binaryOperatorToken = this.findToken(
|
|
session,
|
|
keywordOperatorToken.row,
|
|
keywordOperatorToken.column,
|
|
'keyword.operator.binary',
|
|
null,
|
|
'identifier'
|
|
);
|
|
if (!binaryOperatorToken) {
|
|
return Promise.resolve([]);
|
|
}
|
|
rparenToken = this.findToken(
|
|
session,
|
|
binaryOperatorToken.row,
|
|
binaryOperatorToken.column,
|
|
'paren.rparen',
|
|
null,
|
|
'identifier'
|
|
);
|
|
if (rparenToken) {
|
|
expr = this.findExpressionMatchedParen(session, rparenToken.row, rparenToken.column);
|
|
if (expr === '') {
|
|
return Promise.resolve([]);
|
|
}
|
|
return this.getLabelNameAndValueForExpression(expr, 'expression').then(result => {
|
|
const labelNames = this.transformToCompletions(
|
|
_.uniq(
|
|
_.flatten(
|
|
result.map(r => {
|
|
return Object.keys(r);
|
|
})
|
|
)
|
|
),
|
|
'label name'
|
|
);
|
|
this.labelNameCache[expr] = labelNames;
|
|
return labelNames;
|
|
});
|
|
} else {
|
|
const metricName = this.findMetricName(session, binaryOperatorToken.row, binaryOperatorToken.column);
|
|
return this.getLabelNameAndValueForExpression(metricName, 'metricName').then(result => {
|
|
const labelNames = this.transformToCompletions(
|
|
_.uniq(
|
|
_.flatten(
|
|
result.map(r => {
|
|
return Object.keys(r);
|
|
})
|
|
)
|
|
),
|
|
'label name'
|
|
);
|
|
this.labelNameCache[metricName] = labelNames;
|
|
return Promise.resolve(labelNames);
|
|
});
|
|
}
|
|
}
|
|
|
|
return Promise.resolve([]);
|
|
}
|
|
|
|
getLabelNameAndValueForExpression(expr: string, type: string): Promise<any> {
|
|
if (this.labelQueryCache[expr]) {
|
|
return Promise.resolve(this.labelQueryCache[expr]);
|
|
}
|
|
let query = expr;
|
|
if (type === 'metricName') {
|
|
let op = '=~';
|
|
if (/[a-zA-Z_:][a-zA-Z0-9_:]*/.test(expr)) {
|
|
op = '=';
|
|
}
|
|
query = '{__name__' + op + '"' + expr + '"}';
|
|
}
|
|
const { start, end } = this.datasource.getTimeRange();
|
|
const url = '/api/v1/series?match[]=' + encodeURIComponent(query) + '&start=' + start + '&end=' + end;
|
|
return this.datasource.metadataRequest(url).then(response => {
|
|
this.labelQueryCache[expr] = response.data.data;
|
|
return response.data.data;
|
|
});
|
|
}
|
|
|
|
transformToCompletions(words, meta) {
|
|
return words.map(name => {
|
|
return {
|
|
caption: name,
|
|
value: name,
|
|
meta: meta,
|
|
score: Number.MAX_VALUE,
|
|
};
|
|
});
|
|
}
|
|
|
|
findMetricName(session, row, column) {
|
|
let metricName = '';
|
|
|
|
let tokens;
|
|
const nameLabelNameToken = this.findToken(
|
|
session,
|
|
row,
|
|
column,
|
|
'entity.name.tag.label-matcher',
|
|
'__name__',
|
|
'paren.lparen.label-matcher'
|
|
);
|
|
if (nameLabelNameToken) {
|
|
tokens = session.getTokens(nameLabelNameToken.row);
|
|
const nameLabelValueToken = tokens[nameLabelNameToken.index + 2];
|
|
if (nameLabelValueToken && nameLabelValueToken.type === 'string.quoted.label-matcher') {
|
|
metricName = nameLabelValueToken.value.slice(1, -1); // cut begin/end quotation
|
|
}
|
|
} else {
|
|
const metricNameToken = this.findToken(session, row, column, 'identifier', null, null);
|
|
if (metricNameToken) {
|
|
tokens = session.getTokens(metricNameToken.row);
|
|
metricName = metricNameToken.value;
|
|
}
|
|
}
|
|
|
|
return metricName;
|
|
}
|
|
|
|
findToken(session, row, column, target, value, guard) {
|
|
let tokens, idx;
|
|
// find index and get column of previous token
|
|
for (let r = row; r >= 0; r--) {
|
|
let c;
|
|
tokens = session.getTokens(r);
|
|
if (r === row) {
|
|
// current row
|
|
c = 0;
|
|
for (idx = 0; idx < tokens.length; idx++) {
|
|
const nc = c + tokens[idx].value.length;
|
|
if (nc >= column) {
|
|
break;
|
|
}
|
|
c = nc;
|
|
}
|
|
} else {
|
|
idx = tokens.length - 1;
|
|
c =
|
|
_.sum(
|
|
tokens.map(t => {
|
|
return t.value.length;
|
|
})
|
|
) - tokens[tokens.length - 1].value.length;
|
|
}
|
|
|
|
for (; idx >= 0; idx--) {
|
|
if (tokens[idx].type === guard) {
|
|
return null;
|
|
}
|
|
|
|
if (tokens[idx].type === target && (!value || tokens[idx].value === value)) {
|
|
tokens[idx].row = r;
|
|
tokens[idx].column = c;
|
|
tokens[idx].index = idx;
|
|
return tokens[idx];
|
|
}
|
|
c -= tokens[idx].value.length;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
findExpressionMatchedParen(session, row, column) {
|
|
let tokens, idx;
|
|
let deep = 1;
|
|
let expression = ')';
|
|
for (let r = row; r >= 0; r--) {
|
|
tokens = session.getTokens(r);
|
|
if (r === row) {
|
|
// current row
|
|
let c = 0;
|
|
for (idx = 0; idx < tokens.length; idx++) {
|
|
c += tokens[idx].value.length;
|
|
if (c >= column) {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
idx = tokens.length - 1;
|
|
}
|
|
|
|
for (; idx >= 0; idx--) {
|
|
expression = tokens[idx].value + expression;
|
|
if (tokens[idx].type === 'paren.rparen') {
|
|
deep++;
|
|
} else if (tokens[idx].type === 'paren.lparen') {
|
|
deep--;
|
|
if (deep === 0) {
|
|
return expression;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return expression;
|
|
}
|
|
}
|