Explorar o código

feat(prometheus): progress on new prometheus query editor, #5117

Torkel Ödegaard %!s(int64=9) %!d(string=hai) anos
pai
achega
c77d72b29f

+ 82 - 50
public/app/plugins/datasource/prometheus/metric_find_query.js

@@ -11,27 +11,33 @@ function (_) {
   }
 
   PrometheusMetricFindQuery.prototype.process = function() {
-    var label_values_regex = /^label_values\((?:(.+),\s*)?([a-zA-Z_][a-zA-Z0-9_]+)\)$/;
-    var metric_names_regex = /^metrics\((.+)\)$/;
-    var query_result_regex = /^query_result\((.+)\)$/;
-
-    var label_values_query = this.query.match(label_values_regex);
-    if (label_values_query) {
-      if (label_values_query[1]) {
-        return this.labelValuesQuery(label_values_query[2], label_values_query[1]);
+    var labelValuesRegex  = /^label_values\((?:(.+),\s*)?([a-zA-Z_][a-zA-Z0-9_]+)\)$/;
+    var metricNamesRegex = /^metrics\((.+)\)$/;
+    var labelsRegex = /^labels\((.+)\)$/;
+    var queryResultRegex = /^query_result\((.+)\)$/;
+
+    var labelsQuery = this.query.match(labelsRegex);
+    if (labelsQuery) {
+      return this.labelsQuery(labelsQuery[1]);
+    }
+
+    var labelValuesQuery = this.query.match(labelValuesRegex);
+    if (labelValuesQuery) {
+      if (labelValuesQuery[1]) {
+        return this.labelValuesQuery(labelValuesQuery[2], labelValuesQuery[1]);
       } else {
-        return this.labelValuesQuery(label_values_query[2], null);
+        return this.labelValuesQuery(labelValuesQuery[2], null);
       }
     }
 
-    var metric_names_query = this.query.match(metric_names_regex);
-    if (metric_names_query) {
-      return this.metricNameQuery(metric_names_query[1]);
+    var metricNamesQuery = this.query.match(metricNamesRegex);
+    if (metricNamesQuery) {
+      return this.metricNameQuery(metricNamesQuery[1]);
     }
 
-    var query_result_query = this.query.match(query_result_regex);
-    if (query_result_query) {
-      return this.queryResultQuery(query_result_query[1]);
+    var queryResultQuery = this.query.match(queryResultRegex);
+    if (queryResultQuery) {
+      return this.queryResultQuery(queryResultQuery[1]);
     }
 
     // if query contains full metric name, return metric name and label list
@@ -67,45 +73,71 @@ function (_) {
     }
   };
 
+  PrometheusMetricFindQuery.prototype.labelsQuery = function(metric) {
+    var url;
+
+    url = '/api/v1/series?match[]=' + encodeURIComponent(metric)
+      + '&start=' + (this.range.from.valueOf() / 1000)
+      + '&end=' + (this.range.to.valueOf() / 1000);
+
+    return this.datasource._request('GET', url)
+      .then(function(result) {
+        var tags = {};
+        _.each(result.data.data, function(metric) {
+          _.each(metric, function(value, key) {
+            if (key === "__name__") {
+              return;
+            }
+
+            tags[key] = key;
+          });
+        });
+
+        return _.map(tags, function(value) {
+          return {text: value, value: value};
+        });
+      });
+  };
+
   PrometheusMetricFindQuery.prototype.metricNameQuery = function(metricFilterPattern) {
     var url = '/api/v1/label/__name__/values';
 
     return this.datasource._request('GET', url)
-    .then(function(result) {
-      return _.chain(result.data.data)
-      .filter(function(metricName) {
-        var r = new RegExp(metricFilterPattern);
-        return r.test(metricName);
-      })
-      .map(function(matchedMetricName) {
-        return {
-          text: matchedMetricName,
-          expandable: true
-        };
-      })
-      .value();
-    });
+      .then(function(result) {
+        return _.chain(result.data.data)
+          .filter(function(metricName) {
+            var r = new RegExp(metricFilterPattern);
+            return r.test(metricName);
+          })
+        .map(function(matchedMetricName) {
+          return {
+            text: matchedMetricName,
+            expandable: true
+          };
+        })
+        .value();
+      });
   };
 
   PrometheusMetricFindQuery.prototype.queryResultQuery = function(query) {
     var url = '/api/v1/query?query=' + encodeURIComponent(query) + '&time=' + (this.range.to.valueOf() / 1000);
 
     return this.datasource._request('GET', url)
-    .then(function(result) {
-      return _.map(result.data.data.result, function(metricData) {
-        var text = metricData.metric.__name__ || '';
-        delete metricData.metric.__name__;
-        text += '{' +
-                _.map(metricData.metric, function(v, k) { return k + '="' + v + '"'; }).join(',') +
-                '}';
-        text += ' ' + metricData.value[1] + ' ' + metricData.value[0] * 1000;
-
-        return {
-          text: text,
-          expandable: true
-        };
+      .then(function(result) {
+        return _.map(result.data.data.result, function(metricData) {
+          var text = metricData.metric.__name__ || '';
+          delete metricData.metric.__name__;
+          text += '{' +
+            _.map(metricData.metric, function(v, k) { return k + '="' + v + '"'; }).join(',') +
+            '}';
+          text += ' ' + metricData.value[1] + ' ' + metricData.value[0] * 1000;
+
+          return {
+            text: text,
+            expandable: true
+          };
+        });
       });
-    });
   };
 
   PrometheusMetricFindQuery.prototype.metricNameAndLabelsQuery = function(query) {
@@ -115,14 +147,14 @@ function (_) {
 
     var self = this;
     return this.datasource._request('GET', url)
-    .then(function(result) {
-      return _.map(result.data.data, function(metric) {
-        return {
-          text: self.datasource.getOriginalMetricName(metric),
-          expandable: true
-        };
+      .then(function(result) {
+        return _.map(result.data.data, function(metric) {
+          return {
+            text: self.datasource.getOriginalMetricName(metric),
+            expandable: true
+          };
+        });
       });
-    });
   };
 
   return PrometheusMetricFindQuery;

+ 7 - 2
public/app/plugins/datasource/prometheus/partials/query.editor.html

@@ -1,5 +1,10 @@
-<query-editor-row query-ctrl="ctrl" can-collapse="false">
-	<div class="gf-form-inline">
+<query-editor-row query-ctrl="ctrl" can-collapse="false" has-text-edit-mode="true">
+
+  <div class="gf-form" ng-if="!ctrl.target.editorMode">
+    <input type="text" class="gf-form-input" ng-model="ctrl.target.expr" spellcheck="false" ng-blur="ctrl.refresh()"></input>
+	</div>
+
+	<div class="gf-form-inline" ng-if="ctrl.target.editorMode">
 		<div class="gf-form">
 			<label class="gf-form-label width-8 query-keyword">Query</label>
 			<metric-segment segment="ctrl.metricSegment" get-options="ctrl.getMetricOptions()" on-change="ctrl.queryChanged()"></metric-segment>

+ 26 - 1
public/app/plugins/datasource/prometheus/prom_query.ts

@@ -2,7 +2,6 @@ import {
   QueryPartDef,
   QueryPart,
   functionRenderer,
-  suffixRenderer,
   identityRenderer,
   quotedIdentityRenderer,
 } from 'app/core/components/query_part/query_part';
@@ -12,6 +11,7 @@ import _ from 'lodash';
 var index = [];
 var categories = {
   Functions: [],
+  GroupBy: [],
 };
 
 export class PromQuery {
@@ -29,6 +29,7 @@ export class PromQuery {
     this.target.expr = this.target.expr || '';
     this.target.intervalFactor = this.target.intervalFactor || 2;
     this.target.functions = this.target.functions || [];
+    this.target.editorMode = this.target.editorMode || true;
 
     this.templateSrv = templateSrv;
     this.scopedVars = scopedVars;
@@ -80,6 +81,10 @@ function addFunctionStrategy(model, partModel) {
   model.target.functions.push(partModel.part);
 }
 
+function groupByLabelRenderer(part, innerExpr) {
+  return innerExpr + ' by(' + part.params.join(',')  + ')';
+}
+
 register({
   type: 'rate',
   addStrategy: addFunctionStrategy,
@@ -89,6 +94,26 @@ register({
   renderer: functionRenderer,
 });
 
+register({
+  type: 'sum',
+  addStrategy: addFunctionStrategy,
+  category: categories.Functions,
+  params: [],
+  defaultParams: [],
+  renderer: functionRenderer,
+});
+
+register({
+  type: 'by',
+  addStrategy: addFunctionStrategy,
+  category: categories.Functions,
+  params: [
+    {name: "label", type: "string", dynamicLookup: true}
+  ],
+  defaultParams: [],
+  renderer: groupByLabelRenderer,
+});
+
 export function getQueryPartCategories() {
   return categories;
 }

+ 41 - 1
public/app/plugins/datasource/prometheus/query_ctrl.ts

@@ -20,7 +20,7 @@ class PrometheusQueryCtrl extends QueryCtrl {
   linkToPrometheus: any;
 
   /** @ngInject */
-  constructor($scope, $injector, private templateSrv, private uiSegmentSrv) {
+  constructor($scope, $injector, private templateSrv, private uiSegmentSrv, private $rootScope) {
     super($scope, $injector);
 
     this.query = new PromQuery(this.target, templateSrv);
@@ -58,6 +58,39 @@ class PrometheusQueryCtrl extends QueryCtrl {
     this.panelCtrl.refresh();
   }
 
+  getPartOptions(part) {
+    if (part.def.type === 'by') {
+      return this.datasource.metricFindQuery('labels(' + this.target.metric + ')')
+      .then(this.transformToSegments(true))
+      .catch(this.handleQueryError.bind(true));
+    }
+  }
+
+  partUpdated(part) {
+    this.target.expr = this.query.render();
+    this.panelCtrl.refresh();
+  }
+
+  handleQueryError(err) {
+    this.$rootScope.appEvent('alert-error', ['Query failed', err.message]);
+  }
+
+  transformToSegments(addTemplateVars) {
+    return (results) => {
+      var segments = _.map(results, segment => {
+        return this.uiSegmentSrv.newSegment({ value: segment.text, expandable: segment.expandable });
+      });
+
+      if (addTemplateVars) {
+        for (let variable of this.templateSrv.variables) {
+          segments.unshift(this.uiSegmentSrv.newSegment({ type: 'template', value: '/^$' + variable.name + '$/', expandable: true }));
+        }
+      }
+
+      return segments;
+    };
+  }
+
   getMetricOptions() {
     return this.datasource.performSuggestQuery('').then(res => {
       return _.map(res, metric => {
@@ -68,6 +101,13 @@ class PrometheusQueryCtrl extends QueryCtrl {
 
   queryChanged() {
     this.target.metric = this.metricSegment.value;
+    this.target.expr = this.query.render();
+    this.refresh();
+  }
+
+  toggleEditorMode() {
+    this.target.expr = this.query.render(false);
+    this.target.editorMode = !this.target.editorMode;
   }
 
   refreshMetricData() {

+ 37 - 0
public/app/plugins/datasource/prometheus/specs/metric_find_query_specs.ts

@@ -22,18 +22,22 @@ describe('PrometheusMetricFindQuery', function() {
   describe('When performing metricFindQuery', function() {
     var results;
     var response;
+
     it('label_values(resource) should generate label search query', function() {
       response = {
         status: "success",
         data: ["value1", "value2", "value3"]
       };
+
       ctx.$httpBackend.expect('GET', 'proxied/api/v1/label/resource/values').respond(response);
       var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(resource)', ctx.timeSrv);
       pm.process().then(function(data) { results = data; });
       ctx.$httpBackend.flush();
       ctx.$rootScope.$apply();
+
       expect(results.length).to.be(3);
     });
+
     it('label_values(metric, resource) should generate series query', function() {
       response = {
         status: "success",
@@ -43,13 +47,16 @@ describe('PrometheusMetricFindQuery', function() {
           {__name__: "metric", resource: "value3"}
         ]
       };
+
       ctx.$httpBackend.expect('GET', /proxied\/api\/v1\/series\?match\[\]=metric&start=.*&end=.*/).respond(response);
       var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(metric, resource)', ctx.timeSrv);
       pm.process().then(function(data) { results = data; });
       ctx.$httpBackend.flush();
       ctx.$rootScope.$apply();
+
       expect(results.length).to.be(3);
     });
+
     it('label_values(metric, resource) should pass correct time', function() {
       ctx.timeSrv.setTime({ from: moment.utc('2011-01-01'), to: moment.utc('2015-01-01') });
       ctx.$httpBackend.expect('GET',
@@ -59,6 +66,7 @@ describe('PrometheusMetricFindQuery', function() {
       ctx.$httpBackend.flush();
       ctx.$rootScope.$apply();
     });
+
     it('label_values(metric{label1="foo", label2="bar", label3="baz"}, resource) should generate series query', function() {
       response = {
         status: "success",
@@ -68,6 +76,7 @@ describe('PrometheusMetricFindQuery', function() {
           {__name__: "metric", resource: "value3"}
         ]
       };
+
       ctx.$httpBackend.expect('GET', /proxied\/api\/v1\/series\?match\[\]=metric&start=.*&end=.*/).respond(response);
       var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(metric, resource)', ctx.timeSrv);
       pm.process().then(function(data) { results = data; });
@@ -75,18 +84,22 @@ describe('PrometheusMetricFindQuery', function() {
       ctx.$rootScope.$apply();
       expect(results.length).to.be(3);
     });
+
     it('metrics(metric.*) should generate metric name query', function() {
       response = {
         status: "success",
         data: ["metric1","metric2","metric3","nomatch"]
       };
+
       ctx.$httpBackend.expect('GET', 'proxied/api/v1/label/__name__/values').respond(response);
       var pm = new PrometheusMetricFindQuery(ctx.ds, 'metrics(metric.*)', ctx.timeSrv);
       pm.process().then(function(data) { results = data; });
       ctx.$httpBackend.flush();
       ctx.$rootScope.$apply();
+
       expect(results.length).to.be(3);
     });
+
     it('query_result(metric) should generate metric name query', function() {
       response = {
         status: "success",
@@ -98,6 +111,7 @@ describe('PrometheusMetricFindQuery', function() {
           }]
         }
       };
+
       ctx.$httpBackend.expect('GET', /proxied\/api\/v1\/query\?query=metric&time=.*/).respond(response);
       var pm = new PrometheusMetricFindQuery(ctx.ds, 'query_result(metric)', ctx.timeSrv);
       pm.process().then(function(data) { results = data; });
@@ -106,5 +120,28 @@ describe('PrometheusMetricFindQuery', function() {
       expect(results.length).to.be(1);
       expect(results[0].text).to.be('metric{job="testjob"} 3846 1443454528000');
     });
+
+    it('labels(metric) should generate series query', function() {
+      response = {
+        status: "success",
+        data: [
+          {__name__: "metric", resource: "value1", app: "app1"},
+          {__name__: "metric", resource: "value2", app: "app2"},
+          {__name__: "metric", resource: "value3", server: "server1"}
+        ]
+      };
+
+      ctx.$httpBackend.expect('GET', /proxied\/api\/v1\/series\?match\[\]=metric&start=.*&end=.*/).respond(response);
+      var pm = new PrometheusMetricFindQuery(ctx.ds, 'labels(metric)', ctx.timeSrv);
+      pm.process().then(function(data) { results = data; });
+      ctx.$httpBackend.flush();
+      ctx.$rootScope.$apply();
+
+      expect(results.length).to.be(3);
+      expect(results[0].text).to.be('resource');
+      expect(results[1].text).to.be('app');
+      expect(results[2].text).to.be('server');
+    });
+
   });
 });

+ 16 - 1
public/app/plugins/datasource/prometheus/specs/query_specs.ts

@@ -2,7 +2,7 @@ import {describe, beforeEach, it, sinon, expect} from 'test/lib/common';
 
 import {PromQuery} from '../prom_query';
 
-describe.only('PromQuery', function() {
+describe('PromQuery', function() {
   var templateSrv = {replace: val => val};
 
   describe('render series with mesurement only', function() {
@@ -20,4 +20,19 @@ describe.only('PromQuery', function() {
     });
   });
 
+  describe('render series with group by label', function() {
+    it('should generate correct query', function() {
+      var query = new PromQuery({
+        metric: 'cpu',
+        functions: [
+          {type: 'sum', params: []},
+          {type: 'by', params: ['app']},
+        ]
+      }, templateSrv, {});
+
+      var queryText = query.render();
+      expect(queryText).to.be('sum(cpu) by(app)');
+    });
+  });
+
 });