diff --git a/public/app/plugins/datasource/prometheus/completer.ts b/public/app/plugins/datasource/prometheus/completer.ts index e1b0984399d..eaf3f034d67 100644 --- a/public/app/plugins/datasource/prometheus/completer.ts +++ b/public/app/plugins/datasource/prometheus/completer.ts @@ -1,11 +1,15 @@ /// import {PrometheusDatasource} from "./datasource"; +import _ from 'lodash'; export class PromCompleter { + labelNameCache: any; + identifierRegexps = [/[\[\]a-zA-Z_0-9=]/]; constructor(private datasource: PrometheusDatasource) { + this.labelNameCache = {}; } getCompletions(editor, session, pos, prefix, callback) { @@ -13,14 +17,31 @@ export class PromCompleter { 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 - }; - })); + var metricName = this.findMetricName(session, pos.row, pos.column); + if (!metricName) { + callback(null, this.transformToCompletions(['__name__', 'instance', 'job'])); + return; + } + + if (this.labelNameCache[metricName]) { + callback(null, this.labelNameCache[metricName]); + return; + } + + var op = '=~'; + if (/[a-zA-Z_:][a-zA-Z0-9_:]*/.test(metricName)) { + op = '='; + } + var expr = '{__name__' + op + '"' + metricName + '"}'; + this.datasource.performInstantQuery({ expr: expr }, new Date().getTime() / 1000).then(response => { + var labelNames = this.transformToCompletions( + _.uniq(_.flatten(response.data.data.result.map(r => { + return Object.keys(r.metric); + }))) + ); + this.labelNameCache[metricName] = labelNames; + callback(null, labelNames); + }); return; case 'label.value': callback(null, []); @@ -57,4 +78,72 @@ export class PromCompleter { }); } + transformToCompletions(words) { + return words.map(name => { + return { + caption: name, + value: name, + meta: "label name", + score: Number.MAX_VALUE + }; + }); + } + + findMetricName(session, row, column) { + var metricName = ''; + + var tokens; + var nameLabelNameToken = this.findToken(session, row, column, 'label.name', '__name__', 'paren.lparen'); + if (nameLabelNameToken) { + tokens = session.getTokens(nameLabelNameToken.row); + var nameLabelValueToken = tokens[nameLabelNameToken.index + 2]; + if (nameLabelValueToken && nameLabelValueToken.type === 'label.value') { + 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/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index dd071d09fe6..ad03607baf0 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -158,6 +158,11 @@ export class PrometheusDatasource { return this._request('GET', url, query.requestId); } + performInstantQuery(query, time) { + var url = '/api/v1/query?query=' + encodeURIComponent(query.expr) + '&time=' + time; + return this._request('GET', url, query.requestId); + } + performSuggestQuery(query, cache = false) { var url = '/api/v1/label/__name__/values';