diff --git a/public/app/plugins/datasource/prometheus/completer.ts b/public/app/plugins/datasource/prometheus/completer.ts
index 2651f75df98..4c4c86cb3e3 100644
--- a/public/app/plugins/datasource/prometheus/completer.ts
+++ b/public/app/plugins/datasource/prometheus/completer.ts
@@ -1,29 +1,77 @@
///
import {PrometheusDatasource} from "./datasource";
+import _ from 'lodash';
export class PromCompleter {
+ labelQueryCache: any;
+ labelNameCache: any;
+ labelValueCache: any;
+
identifierRegexps = [/[\[\]a-zA-Z_0-9=]/];
constructor(private datasource: PrometheusDatasource) {
+ this.labelQueryCache = {};
+ this.labelNameCache = {};
+ this.labelValueCache = {};
}
getCompletions(editor, session, pos, prefix, callback) {
let token = session.getTokenAt(pos.row, pos.column);
+ var metricName;
switch (token.type) {
- case 'label.name':
- callback(null, ['instance', 'job'].map(function (key) {
- return {
- caption: key,
- value: key,
- meta: "label name",
- score: Number.MAX_VALUE
- };
- }));
+ case 'entity.name.tag':
+ metricName = this.findMetricName(session, pos.row, pos.column);
+ if (!metricName) {
+ callback(null, this.transformToCompletions(['__name__', 'instance', 'job'], 'label name'));
+ return;
+ }
+
+ if (this.labelNameCache[metricName]) {
+ callback(null, this.labelNameCache[metricName]);
+ 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);
+ });
return;
- case 'label.value':
- callback(null, []);
+ case 'string.quoted':
+ metricName = this.findMetricName(session, pos.row, pos.column);
+ if (!metricName) {
+ callback(null, []);
+ 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;
+ }
+
+ 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;
}
@@ -56,4 +104,87 @@ export class PromCompleter {
});
}
+ getLabelNameAndValueForMetric(metricName) {
+ if (this.labelQueryCache[metricName]) {
+ return Promise.resolve(this.labelQueryCache[metricName]);
+ }
+ var op = '=~';
+ if (/[a-zA-Z_:][a-zA-Z0-9_:]*/.test(metricName)) {
+ op = '=';
+ }
+ var expr = '{__name__' + op + '"' + metricName + '"}';
+ return this.datasource.performInstantQuery({ expr: expr }, new Date().getTime() / 1000).then(response => {
+ this.labelQueryCache[metricName] = response.data.data.result;
+ return response.data.data.result;
+ });
+ }
+
+ transformToCompletions(words, meta) {
+ return words.map(name => {
+ return {
+ caption: name,
+ value: name,
+ meta: meta,
+ score: Number.MAX_VALUE
+ };
+ });
+ }
+
+ findMetricName(session, row, column) {
+ var metricName = '';
+
+ var tokens;
+ var nameLabelNameToken = this.findToken(session, row, column, 'entity.name.tag', '__name__', 'paren.lparen');
+ if (nameLabelNameToken) {
+ tokens = session.getTokens(nameLabelNameToken.row);
+ var nameLabelValueToken = tokens[nameLabelNameToken.index + 2];
+ if (nameLabelValueToken && nameLabelValueToken.type === 'string.quoted') {
+ metricName = nameLabelValueToken.value.slice(1, -1); // cut begin/end quotation
+ }
+ } else {
+ var metricNameToken = this.findToken(session, row, column, 'identifier', null, null);
+ if (metricNameToken) {
+ tokens = session.getTokens(metricNameToken.row);
+ if (tokens[metricNameToken.index + 1].type === 'paren.lparen') {
+ metricName = metricNameToken.value;
+ }
+ }
+ }
+
+ return metricName;
+ }
+
+ findToken(session, row, column, target, value, guard) {
+ var tokens, idx;
+ for (var r = row; r >= 0; r--) {
+ tokens = session.getTokens(r);
+ if (r === row) { // current row
+ var 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--) {
+ if (tokens[idx].type === guard) {
+ return null;
+ }
+
+ if (tokens[idx].type === target
+ && (!value || tokens[idx].value === value)) {
+ tokens[idx].row = r;
+ tokens[idx].index = idx;
+ return tokens[idx];
+ }
+ }
+ }
+
+ return null;
+ }
+
}
diff --git a/public/app/plugins/datasource/prometheus/mode-prometheus.js b/public/app/plugins/datasource/prometheus/mode-prometheus.js
index 78edbbd30d1..165c1a364c4 100644
--- a/public/app/plugins/datasource/prometheus/mode-prometheus.js
+++ b/public/app/plugins/datasource/prometheus/mode-prometheus.js
@@ -65,13 +65,13 @@ var PrometheusHighlightRules = function() {
regex : "\\s+"
} ],
"start-label-matcher" : [ {
- token : "keyword",
+ token : "entity.name.tag",
regex : '[a-zA-Z_][a-zA-Z0-9_]*'
}, {
token : "keyword.operator",
regex : '=~|=|!~|!='
}, {
- token : "string",
+ token : "string.quoted",
regex : '"[^"]*"|\'[^\']*\''
}, {
token : "punctuation.operator",
@@ -401,7 +401,7 @@ var PrometheusCompletions = function() {};
(function() {
this.getCompletions = function(state, session, pos, prefix, callback) {
var token = session.getTokenAt(pos.row, pos.column);
- if (token.type === 'label.name' || token.type === 'label.value') {
+ if (token.type === 'entity.name.tag' || token.type === 'string.quoted') {
return callback(null, []);
}
diff --git a/public/app/plugins/datasource/prometheus/specs/completer_specs.ts b/public/app/plugins/datasource/prometheus/specs/completer_specs.ts
index 37d428859ae..5d160453e13 100644
--- a/public/app/plugins/datasource/prometheus/specs/completer_specs.ts
+++ b/public/app/plugins/datasource/prometheus/specs/completer_specs.ts
@@ -5,23 +5,110 @@ import {PrometheusDatasource} from '../datasource';
describe('Prometheus editor completer', function() {
- let editor = {};
- let session = {
- getTokenAt: sinon.stub().returns({}),
- getLine: sinon.stub().returns(""),
+ let sessionData = {
+ currentToken: {},
+ tokens: [],
+ line: ''
};
+ let session = {
+ getTokenAt: sinon.stub().returns(sessionData.currentToken),
+ getTokens: sinon.stub().returns(sessionData.tokens),
+ getLine: sinon.stub().returns(sessionData.line),
+ };
+ let editor = { session: session };
- let datasourceStub = {};
+ let datasourceStub = {
+ performInstantQuery: sinon.stub().withArgs({ expr: '{__name__="node_cpu"' }).returns(Promise.resolve(
+ [
+ {
+ metric: {
+ job: 'node',
+ instance: 'localhost:9100'
+ }
+ }
+ ]
+ )),
+ performSuggestQuery: sinon.stub().withArgs('node', true).returns(Promise.resolve(
+ [
+ 'node_cpu'
+ ]
+ ))
+ };
let completer = new PromCompleter(datasourceStub);
describe("When inside brackets", () => {
it("Should return range vectors", () => {
- completer.getCompletions(editor, session, 10, "[", (s, res) => {
+ completer.getCompletions(editor, session, { row: 0, column: 10 }, '[', (s, res) => {
expect(res[0]).to.eql({caption: '1s', value: '[1s', meta: 'range vector'});
});
});
});
+ describe("When inside label matcher, and located at label name", () => {
+ sessionData = {
+ currentToken: { type: 'entity.name.tag', value: 'j', index: 2, start: 9 },
+ tokens: [
+ { type: 'identifier', value: 'node_cpu' },
+ { type: 'paren.lparen', value: '{' },
+ { type: 'entity.name.tag', value: 'j', index: 2, start: 9 },
+ { type: 'paren.rparen', value: '}' }
+ ],
+ line: 'node_cpu{j}'
+ };
+
+ it("Should return label name list", () => {
+ completer.getCompletions(editor, session, { row: 0, column: 10 }, 'j', (s, res) => {
+ expect(res[0]).to.eql({caption: 'job', value: 'job', meta: 'label name'});
+ });
+ });
+
+ });
+
+ describe("When inside label matcher, and located at label name with __name__ match", () => {
+ sessionData = {
+ currentToken: { type: 'entity.name.tag', value: 'j', index: 5, start: 22 },
+ tokens: [
+ { type: 'paren.lparen', value: '{' },
+ { type: 'entity.name.tag', value: '__name__' },
+ { type: 'keyword.operator', value: '=~' },
+ { type: 'string.quoted', value: '"node_cpu"' },
+ { type: 'punctuation.operator', value: ',' },
+ { type: 'entity.name.tag', value: 'j', 'index': 5, 'start': 22 },
+ { type: 'paren.rparen', value: '}' }
+ ],
+ line: '{__name__=~"node_cpu",j}'
+ };
+
+ it("Should return label name list", () => {
+ completer.getCompletions(editor, session, { row: 0, column: 23 }, 'j', (s, res) => {
+ expect(res[0]).to.eql({caption: 'job', value: 'job', meta: 'label name'});
+ });
+ });
+
+ });
+
+ describe("When inside label matcher, and located at label value", () => {
+ sessionData = {
+ currentToken: { type: 'string.quoted', value: '"n"', index: 4, start: 13 },
+ tokens: [
+ { type: 'identifier', value: 'node_cpu' },
+ { type: 'paren.lparen', value: '{' },
+ { type: 'entity.name.tag', value: 'job' },
+ { type: 'keyword.operator', value: '=' },
+ { type: 'string.quoted', value: '"n"', index: 4, start: 13 },
+ { type: 'paren.rparen', value: '}' }
+ ],
+ line: 'node_cpu{job="n"}'
+ };
+
+ it("Should return label value list", () => {
+ completer.getCompletions(editor, session, { row: 0, column: 15 }, 'n', (s, res) => {
+ expect(res[0]).to.eql({caption: 'node', value: 'node', meta: 'label value'});
+ });
+ });
+
+ });
+
});