From c77d72b29fb806eb848987f085fa1eebf16e4818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 23 May 2016 18:03:17 +0200 Subject: [PATCH] feat(prometheus): progress on new prometheus query editor, #5117 --- .../prometheus/metric_find_query.js | 128 +++++++++++------- .../prometheus/partials/query.editor.html | 9 +- .../datasource/prometheus/prom_query.ts | 27 +++- .../datasource/prometheus/query_ctrl.ts | 42 +++++- .../specs/metric_find_query_specs.ts | 37 +++++ .../prometheus/specs/query_specs.ts | 17 ++- 6 files changed, 207 insertions(+), 53 deletions(-) diff --git a/public/app/plugins/datasource/prometheus/metric_find_query.js b/public/app/plugins/datasource/prometheus/metric_find_query.js index f449978be81..d3d5376bc19 100644 --- a/public/app/plugins/datasource/prometheus/metric_find_query.js +++ b/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 labelValuesRegex = /^label_values\((?:(.+),\s*)?([a-zA-Z_][a-zA-Z0-9_]+)\)$/; + var metricNamesRegex = /^metrics\((.+)\)$/; + var labelsRegex = /^labels\((.+)\)$/; + var queryResultRegex = /^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 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; + .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 - }; + 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; diff --git a/public/app/plugins/datasource/prometheus/partials/query.editor.html b/public/app/plugins/datasource/prometheus/partials/query.editor.html index a67e3e9b188..359c366cb0f 100644 --- a/public/app/plugins/datasource/prometheus/partials/query.editor.html +++ b/public/app/plugins/datasource/prometheus/partials/query.editor.html @@ -1,5 +1,10 @@ - -
+ + +
+ +
+ +
diff --git a/public/app/plugins/datasource/prometheus/prom_query.ts b/public/app/plugins/datasource/prometheus/prom_query.ts index 83ac4f1615f..d73a8b1cbcf 100644 --- a/public/app/plugins/datasource/prometheus/prom_query.ts +++ b/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; } diff --git a/public/app/plugins/datasource/prometheus/query_ctrl.ts b/public/app/plugins/datasource/prometheus/query_ctrl.ts index 8692d915d30..680b1294df6 100644 --- a/public/app/plugins/datasource/prometheus/query_ctrl.ts +++ b/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() { diff --git a/public/app/plugins/datasource/prometheus/specs/metric_find_query_specs.ts b/public/app/plugins/datasource/prometheus/specs/metric_find_query_specs.ts index 38b50d2ce79..b393f36402c 100644 --- a/public/app/plugins/datasource/prometheus/specs/metric_find_query_specs.ts +++ b/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'); + }); + }); }); diff --git a/public/app/plugins/datasource/prometheus/specs/query_specs.ts b/public/app/plugins/datasource/prometheus/specs/query_specs.ts index 5bb164c306e..d58e0834f89 100644 --- a/public/app/plugins/datasource/prometheus/specs/query_specs.ts +++ b/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)'); + }); + }); + });