(prometheus) show label name in paren after by/without/on/ignoring/group_left/group_right

This commit is contained in:
Mitsuhiro Tanda 2017-10-25 17:23:09 +09:00
parent 24723cdb3c
commit e3274d6765
3 changed files with 360 additions and 111 deletions

View File

@ -19,66 +19,22 @@ export class PromCompleter {
getCompletions(editor, session, pos, prefix, callback) { getCompletions(editor, session, pos, prefix, callback) {
let token = session.getTokenAt(pos.row, pos.column); let token = session.getTokenAt(pos.row, pos.column);
var metricName;
switch (token.type) { switch (token.type) {
case 'entity.name.tag': case 'entity.name.tag.label-matcher':
metricName = this.findMetricName(session, pos.row, pos.column); this.getCompletionsForLabelMatcherName(session, pos).then(completions => {
if (!metricName) { callback(null, completions);
callback(null, this.transformToCompletions(['__name__', 'instance', 'job'], 'label name'));
return;
}
if (this.labelNameCache[metricName]) {
callback(null, this.labelNameCache[metricName]);
return;
}
return this.getLabelNameAndValueForMetric(metricName).then(result => {
var labelNames = this.transformToCompletions(
_.uniq(
_.flatten(
result.map(r => {
return Object.keys(r.metric);
})
)
),
'label name'
);
this.labelNameCache[metricName] = labelNames;
callback(null, labelNames);
}); });
case 'string.quoted': return;
metricName = this.findMetricName(session, pos.row, pos.column); case 'string.quoted.label-matcher':
if (!metricName) { this.getCompletionsForLabelMatcherValue(session, pos).then(completions => {
callback(null, []); callback(null, completions);
return;
}
var labelNameToken = this.findToken(session, pos.row, pos.column, 'entity.name.tag', null, 'paren.lparen');
if (!labelNameToken) {
callback(null, []);
return;
}
var labelName = labelNameToken.value;
if (this.labelValueCache[metricName] && this.labelValueCache[metricName][labelName]) {
callback(null, this.labelValueCache[metricName][labelName]);
return;
}
return this.getLabelNameAndValueForMetric(metricName).then(result => {
var labelValues = this.transformToCompletions(
_.uniq(
result.map(r => {
return r.metric[labelName];
})
),
'label value'
);
this.labelValueCache[metricName] = this.labelValueCache[metricName] || {};
this.labelValueCache[metricName][labelName] = labelValues;
callback(null, labelValues);
}); });
return;
case 'entity.name.tag.label-list-matcher':
this.getCompletionsForBinaryOperator(session, pos).then(completions => {
callback(null, completions);
});
return;
} }
if (token.type === 'paren.lparen' && token.value === '[') { if (token.type === 'paren.lparen' && token.value === '[') {
@ -127,17 +83,186 @@ export class PromCompleter {
}); });
} }
getLabelNameAndValueForMetric(metricName) { getCompletionsForLabelMatcherName(session, pos) {
if (this.labelQueryCache[metricName]) { let metricName = this.findMetricName(session, pos.row, pos.column);
return Promise.resolve(this.labelQueryCache[metricName]); if (!metricName) {
return Promise.resolve(this.transformToCompletions(['__name__', 'instance', 'job'], 'label name'));
} }
var op = '=~';
if (/[a-zA-Z_:][a-zA-Z0-9_:]*/.test(metricName)) { if (this.labelNameCache[metricName]) {
op = '='; return Promise.resolve(this.labelNameCache[metricName]);
} }
var expr = '{__name__' + op + '"' + metricName + '"}';
return this.datasource.performInstantQuery({ expr: expr }, new Date().getTime() / 1000).then(response => { return this.getLabelNameAndValueForExpression(metricName, 'metricName').then(result => {
this.labelQueryCache[metricName] = response.data.data.result; var labelNames = this.transformToCompletions(
_.uniq(
_.flatten(
result.map(r => {
return Object.keys(r.metric);
})
)
),
'label name'
);
this.labelNameCache[metricName] = labelNames;
return Promise.resolve(labelNames);
});
}
getCompletionsForLabelMatcherValue(session, pos) {
let metricName = this.findMetricName(session, pos.row, pos.column);
if (!metricName) {
return Promise.resolve([]);
}
var labelNameToken = this.findToken(
session,
pos.row,
pos.column,
'entity.name.tag.label-matcher',
null,
'paren.lparen.label-matcher'
);
if (!labelNameToken) {
return Promise.resolve([]);
}
var 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 => {
var labelValues = this.transformToCompletions(
_.uniq(
result.map(r => {
return r.metric[labelName];
})
),
'label value'
);
this.labelValueCache[metricName] = this.labelValueCache[metricName] || {};
this.labelValueCache[metricName][labelName] = labelValues;
return Promise.resolve(labelValues);
});
}
getCompletionsForBinaryOperator(session, pos) {
let 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 => {
var labelNames = this.transformToCompletions(
_.uniq(
_.flatten(
result.map(r => {
return Object.keys(r.metric);
})
)
),
'label name'
);
this.labelNameCache[expr] = labelNames;
return labelNames;
});
case 'on':
case 'ignoring':
case 'group_left':
case 'group_right':
let 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 => {
var labelNames = this.transformToCompletions(
_.uniq(
_.flatten(
result.map(r => {
return Object.keys(r.metric);
})
)
),
'label name'
);
this.labelNameCache[expr] = labelNames;
return labelNames;
});
} else {
let metricName = this.findMetricName(session, binaryOperatorToken.row, binaryOperatorToken.column);
return this.getLabelNameAndValueForExpression(metricName, 'metricName').then(result => {
var labelNames = this.transformToCompletions(
_.uniq(
_.flatten(
result.map(r => {
return Object.keys(r.metric);
})
)
),
'label name'
);
this.labelNameCache[metricName] = labelNames;
return Promise.resolve(labelNames);
});
}
}
return Promise.resolve([]);
}
getLabelNameAndValueForExpression(expr, type) {
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 + '"}';
}
return this.datasource.performInstantQuery({ expr: query }, new Date().getTime() / 1000).then(response => {
this.labelQueryCache[expr] = response.data.data.result;
return response.data.data.result; return response.data.data.result;
}); });
} }
@ -157,20 +282,25 @@ export class PromCompleter {
var metricName = ''; var metricName = '';
var tokens; var tokens;
var nameLabelNameToken = this.findToken(session, row, column, 'entity.name.tag', '__name__', 'paren.lparen'); var nameLabelNameToken = this.findToken(
session,
row,
column,
'entity.name.tag.label-matcher',
'__name__',
'paren.lparen.label-matcher'
);
if (nameLabelNameToken) { if (nameLabelNameToken) {
tokens = session.getTokens(nameLabelNameToken.row); tokens = session.getTokens(nameLabelNameToken.row);
var nameLabelValueToken = tokens[nameLabelNameToken.index + 2]; var nameLabelValueToken = tokens[nameLabelNameToken.index + 2];
if (nameLabelValueToken && nameLabelValueToken.type === 'string.quoted') { if (nameLabelValueToken && nameLabelValueToken.type === 'string.quoted.label-matcher') {
metricName = nameLabelValueToken.value.slice(1, -1); // cut begin/end quotation metricName = nameLabelValueToken.value.slice(1, -1); // cut begin/end quotation
} }
} else { } else {
var metricNameToken = this.findToken(session, row, column, 'identifier', null, null); var metricNameToken = this.findToken(session, row, column, 'identifier', null, null);
if (metricNameToken) { if (metricNameToken) {
tokens = session.getTokens(metricNameToken.row); tokens = session.getTokens(metricNameToken.row);
if (tokens[metricNameToken.index + 1].type === 'paren.lparen') { metricName = metricNameToken.value;
metricName = metricNameToken.value;
}
} }
} }
@ -179,11 +309,57 @@ export class PromCompleter {
findToken(session, row, column, target, value, guard) { findToken(session, row, column, target, value, guard) {
var tokens, idx; var tokens, idx;
// find index and get column of previous token
for (var r = row; r >= 0; r--) { for (var r = row; r >= 0; r--) {
let c;
tokens = session.getTokens(r); tokens = session.getTokens(r);
if (r === row) { if (r === row) {
// current row // current row
var c = 0; c = 0;
for (idx = 0; idx < tokens.length; idx++) {
let 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++) { for (idx = 0; idx < tokens.length; idx++) {
c += tokens[idx].value.length; c += tokens[idx].value.length;
if (c >= column) { if (c >= column) {
@ -195,18 +371,18 @@ export class PromCompleter {
} }
for (; idx >= 0; idx--) { for (; idx >= 0; idx--) {
if (tokens[idx].type === guard) { expression = tokens[idx].value + expression;
return null; if (tokens[idx].type === 'paren.rparen') {
} deep++;
} else if (tokens[idx].type === 'paren.lparen') {
if (tokens[idx].type === target && (!value || tokens[idx].value === value)) { deep--;
tokens[idx].row = r; if (deep === 0) {
tokens[idx].index = idx; return expression;
return tokens[idx]; }
} }
} }
} }
return null; return expression;
} }
} }

View File

@ -8,7 +8,6 @@ var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;
var PrometheusHighlightRules = function() { var PrometheusHighlightRules = function() {
var keywords = ( var keywords = (
"by|without|keep_common|offset|bool|and|or|unless|ignoring|on|group_left|group_right|" +
"count|count_values|min|max|avg|sum|stddev|stdvar|bottomk|topk|quantile" "count|count_values|min|max|avg|sum|stddev|stdvar|bottomk|topk|quantile"
); );
@ -41,45 +40,66 @@ var PrometheusHighlightRules = function() {
}, { }, {
token : "constant.language", // time token : "constant.language", // time
regex : "\\d+[smhdwy]" regex : "\\d+[smhdwy]"
}, {
token : "keyword.operator.binary",
regex : "\\+|\\-|\\*|\\/|%|\\^|==|!=|<=|>=|<|>|and|or|unless"
}, {
token : "keyword.other",
regex : "keep_common|offset|bool"
}, {
token : "keyword.control",
regex : "by|without|on|ignoring|group_left|group_right",
next : "start-label-list-matcher"
}, { }, {
token : keywordMapper, token : keywordMapper,
regex : "[a-zA-Z_:][a-zA-Z0-9_:]*" regex : "[a-zA-Z_:][a-zA-Z0-9_:]*"
}, {
token : "keyword.operator",
regex : "\\+|\\-|\\*|\\/|%|\\^|==|!=|<=|>=|<|>"
}, { }, {
token : "paren.lparen", token : "paren.lparen",
regex : "[[(]" regex : "[[(]"
}, { }, {
token : "paren.lparen", token : "paren.lparen.label-matcher",
regex : "{", regex : "{",
next : "start-label-matcher" next : "start-label-matcher"
}, { }, {
token : "paren.rparen", token : "paren.rparen",
regex : "[\\])]" regex : "[\\])]"
}, { }, {
token : "paren.rparen", token : "paren.rparen.label-matcher",
regex : "}" regex : "}"
}, { }, {
token : "text", token : "text",
regex : "\\s+" regex : "\\s+"
} ], } ],
"start-label-matcher" : [ { "start-label-matcher" : [ {
token : "entity.name.tag", token : "entity.name.tag.label-matcher",
regex : '[a-zA-Z_][a-zA-Z0-9_]*' regex : '[a-zA-Z_][a-zA-Z0-9_]*'
}, { }, {
token : "keyword.operator", token : "keyword.operator.label-matcher",
regex : '=~|=|!~|!=' regex : '=~|=|!~|!='
}, { }, {
token : "string.quoted", token : "string.quoted.label-matcher",
regex : '"[^"]*"|\'[^\']*\'' regex : '"[^"]*"|\'[^\']*\''
}, { }, {
token : "punctuation.operator", token : "punctuation.operator.label-matcher",
regex : "," regex : ","
}, { }, {
token : "paren.rparen", token : "paren.rparen.label-matcher",
regex : "}", regex : "}",
next : "start" next : "start"
} ],
"start-label-list-matcher" : [ {
token : "paren.lparen.label-list-matcher",
regex : "[(]"
}, {
token : "entity.name.tag.label-list-matcher",
regex : '[a-zA-Z_][a-zA-Z0-9_]*'
}, {
token : "punctuation.operator.label-list-matcher",
regex : ","
}, {
token : "paren.rparen.label-list-matcher",
regex : "[)]",
next : "start"
} ] } ]
}; };
@ -395,7 +415,9 @@ var PrometheusCompletions = function() {};
(function() { (function() {
this.getCompletions = function(state, session, pos, prefix, callback) { this.getCompletions = function(state, session, pos, prefix, callback) {
var token = session.getTokenAt(pos.row, pos.column); var token = session.getTokenAt(pos.row, pos.column);
if (token.type === 'entity.name.tag' || token.type === 'string.quoted') { if (token.type === 'entity.name.tag.label-matcher'
|| token.type === 'string.quoted.label-matcher'
|| token.type === 'entity.name.tag.label-list-matcher') {
return callback(null, []); return callback(null, []);
} }

View File

@ -61,16 +61,21 @@ describe('Prometheus editor completer', function() {
it('Should return label name list', () => { it('Should return label name list', () => {
const session = getSessionStub({ const session = getSessionStub({
currentToken: { currentToken: {
type: 'entity.name.tag', type: 'entity.name.tag.label-matcher',
value: 'j', value: 'j',
index: 2, index: 2,
start: 9, start: 9,
}, },
tokens: [ tokens: [
{ type: 'identifier', value: 'node_cpu' }, { type: 'identifier', value: 'node_cpu' },
{ type: 'paren.lparen', value: '{' }, { type: 'paren.lparen.label-matcher', value: '{' },
{ type: 'entity.name.tag', value: 'j', index: 2, start: 9 }, {
{ type: 'paren.rparen', value: '}' }, type: 'entity.name.tag.label-matcher',
value: 'j',
index: 2,
start: 9,
},
{ type: 'paren.rparen.label-matcher', value: '}' },
], ],
line: 'node_cpu{j}', line: 'node_cpu{j}',
}); });
@ -85,19 +90,24 @@ describe('Prometheus editor completer', function() {
it('Should return label name list', () => { it('Should return label name list', () => {
const session = getSessionStub({ const session = getSessionStub({
currentToken: { currentToken: {
type: 'entity.name.tag', type: 'entity.name.tag.label-matcher',
value: 'j', value: 'j',
index: 5, index: 5,
start: 22, start: 22,
}, },
tokens: [ tokens: [
{ type: 'paren.lparen', value: '{' }, { type: 'paren.lparen.label-matcher', value: '{' },
{ type: 'entity.name.tag', value: '__name__' }, { type: 'entity.name.tag.label-matcher', value: '__name__' },
{ type: 'keyword.operator', value: '=~' }, { type: 'keyword.operator.label-matcher', value: '=~' },
{ type: 'string.quoted', value: '"node_cpu"' }, { type: 'string.quoted.label-matcher', value: '"node_cpu"' },
{ type: 'punctuation.operator', value: ',' }, { type: 'punctuation.operator.label-matcher', value: ',' },
{ type: 'entity.name.tag', value: 'j', index: 5, start: 22 }, {
{ type: 'paren.rparen', value: '}' }, type: 'entity.name.tag.label-matcher',
value: 'j',
index: 5,
start: 22,
},
{ type: 'paren.rparen.label-matcher', value: '}' },
], ],
line: '{__name__=~"node_cpu",j}', line: '{__name__=~"node_cpu",j}',
}); });
@ -112,18 +122,23 @@ describe('Prometheus editor completer', function() {
it('Should return label value list', () => { it('Should return label value list', () => {
const session = getSessionStub({ const session = getSessionStub({
currentToken: { currentToken: {
type: 'string.quoted', type: 'string.quoted.label-matcher',
value: '"n"', value: '"n"',
index: 4, index: 4,
start: 13, start: 13,
}, },
tokens: [ tokens: [
{ type: 'identifier', value: 'node_cpu' }, { type: 'identifier', value: 'node_cpu' },
{ type: 'paren.lparen', value: '{' }, { type: 'paren.lparen.label-matcher', value: '{' },
{ type: 'entity.name.tag', value: 'job' }, { type: 'entity.name.tag.label-matcher', value: 'job' },
{ type: 'keyword.operator', value: '=' }, { type: 'keyword.operator.label-matcher', value: '=' },
{ type: 'string.quoted', value: '"n"', index: 4, start: 13 }, {
{ type: 'paren.rparen', value: '}' }, type: 'string.quoted.label-matcher',
value: '"n"',
index: 4,
start: 13,
},
{ type: 'paren.rparen.label-matcher', value: '}' },
], ],
line: 'node_cpu{job="n"}', line: 'node_cpu{job="n"}',
}); });
@ -133,4 +148,40 @@ describe('Prometheus editor completer', function() {
}); });
}); });
}); });
describe('When inside by', () => {
it('Should return label name list', () => {
const session = getSessionStub({
currentToken: {
type: 'entity.name.tag.label-list-matcher',
value: 'm',
index: 9,
start: 22,
},
tokens: [
{ type: 'paren.lparen', value: '(' },
{ type: 'keyword', value: 'count' },
{ type: 'paren.lparen', value: '(' },
{ type: 'identifier', value: 'node_cpu' },
{ type: 'paren.rparen', value: '))' },
{ type: 'text', value: ' ' },
{ type: 'keyword.control', value: 'by' },
{ type: 'text', value: ' ' },
{ type: 'paren.lparen.label-list-matcher', value: '(' },
{
type: 'entity.name.tag.label-list-matcher',
value: 'm',
index: 9,
start: 22,
},
{ type: 'paren.rparen.label-list-matcher', value: ')' },
],
line: '(count(node_cpu)) by (m)',
});
return completer.getCompletions(editor, session, { row: 0, column: 23 }, 'm', (s, res) => {
expect(res[0].meta).to.eql('label name');
});
});
});
}); });