Просмотр исходного кода

Merge pull request #9664 from mtanda/prometheus_on_by

(prometheus) show label name in paren after by/without/on/ignoring/group_left/group_right
Carl Bergquist 8 лет назад
Родитель
Сommit
89ae4d6685

+ 250 - 74
public/app/plugins/datasource/prometheus/completer.ts

@@ -17,66 +17,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':
-        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;
-        }
-
-        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 'entity.name.tag.label-matcher':
+        this.getCompletionsForLabelMatcherName(session, pos).then(completions => {
+          callback(null, completions);
         });
         });
-      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;
-        }
-
-        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 'string.quoted.label-matcher':
+        this.getCompletionsForLabelMatcherValue(session, pos).then(completions => {
+          callback(null, completions);
         });
         });
+        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 === '[') {
@@ -128,17 +84,186 @@ export class PromCompleter {
     });
     });
   }
   }
 
 
-  getLabelNameAndValueForMetric(metricName) {
-    if (this.labelQueryCache[metricName]) {
-      return Promise.resolve(this.labelQueryCache[metricName]);
+  getCompletionsForLabelMatcherName(session, pos) {
+    let 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 => {
+      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]);
     }
     }
-    var op = '=~';
-    if (/[a-zA-Z_:][a-zA-Z0-9_:]*/.test(metricName)) {
-      op = '=';
+    let query = expr;
+    if (type === 'metricName') {
+      let op = '=~';
+      if (/[a-zA-Z_:][a-zA-Z0-9_:]*/.test(expr)) {
+        op = '=';
+      }
+      query = '{__name__' + op + '"' + expr + '"}';
     }
     }
-    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 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;
     });
     });
   }
   }
@@ -158,20 +283,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;
       }
       }
     }
     }
 
 
@@ -180,19 +310,28 @@ 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++) {
         for (idx = 0; idx < tokens.length; idx++) {
-          c += tokens[idx].value.length;
-          if (c >= column) {
+          let nc = c + tokens[idx].value.length;
+          if (nc >= column) {
             break;
             break;
           }
           }
+          c = nc;
         }
         }
       } else {
       } else {
         idx = tokens.length - 1;
         idx = tokens.length - 1;
+        c =
+          _.sum(
+            tokens.map(t => {
+              return t.value.length;
+            })
+          ) - tokens[tokens.length - 1].value.length;
       }
       }
 
 
       for (; idx >= 0; idx--) {
       for (; idx >= 0; idx--) {
@@ -202,12 +341,49 @@ export class PromCompleter {
 
 
         if (tokens[idx].type === target && (!value || tokens[idx].value === value)) {
         if (tokens[idx].type === target && (!value || tokens[idx].value === value)) {
           tokens[idx].row = r;
           tokens[idx].row = r;
+          tokens[idx].column = c;
           tokens[idx].index = idx;
           tokens[idx].index = idx;
           return tokens[idx];
           return tokens[idx];
         }
         }
+        c -= tokens[idx].value.length;
       }
       }
     }
     }
 
 
     return null;
     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;
+  }
 }
 }

+ 34 - 12
public/app/plugins/datasource/prometheus/mode-prometheus.js

@@ -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, []);
     }
     }
 
 

+ 69 - 18
public/app/plugins/datasource/prometheus/specs/completer_specs.ts

@@ -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: 'entity.name.tag', value: 'j', index: 2, start: 9 },
-          { type: 'paren.rparen', value: '}' },
+          { type: 'paren.lparen.label-matcher', 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: '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: '}' },
+          { type: 'paren.lparen.label-matcher', value: '{' },
+          { type: 'entity.name.tag.label-matcher', value: '__name__' },
+          { type: 'keyword.operator.label-matcher', value: '=~' },
+          { type: 'string.quoted.label-matcher', value: '"node_cpu"' },
+          { type: 'punctuation.operator.label-matcher', 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: 'entity.name.tag', value: 'job' },
-          { type: 'keyword.operator', value: '=' },
-          { type: 'string.quoted', value: '"n"', index: 4, start: 13 },
-          { type: 'paren.rparen', value: '}' },
+          { type: 'paren.lparen.label-matcher', value: '{' },
+          { type: 'entity.name.tag.label-matcher', value: 'job' },
+          { type: 'keyword.operator.label-matcher', 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');
+      });
+    });
+  });
 });
 });