From 2cce057df1e876f90afde775a83ed73646ee6667 Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Wed, 9 Dec 2015 11:22:00 +0900 Subject: [PATCH] (prometheus) add query result template query --- docs/sources/datasources/prometheus.md | 1 + .../datasource/prometheus/datasource.ts | 97 +++----------- .../prometheus/metric_find_query.d.ts | 2 + .../prometheus/metric_find_query.js | 125 ++++++++++++++++++ .../prometheus/specs/datasource_specs.ts | 41 ------ .../specs/metric_find_query_specs.ts | 83 ++++++++++++ 6 files changed, 230 insertions(+), 119 deletions(-) create mode 100644 public/app/plugins/datasource/prometheus/metric_find_query.d.ts create mode 100644 public/app/plugins/datasource/prometheus/metric_find_query.js create mode 100644 public/app/plugins/datasource/prometheus/specs/metric_find_query_specs.ts diff --git a/docs/sources/datasources/prometheus.md b/docs/sources/datasources/prometheus.md index a7959a41c51..9ad435270c4 100644 --- a/docs/sources/datasources/prometheus.md +++ b/docs/sources/datasources/prometheus.md @@ -48,6 +48,7 @@ Name | Description `label_values(label)` | Returns a list of label values for the `label` in every metric. `label_values(metric, label)` | Returns a list of label values for the `label` in the specified metric. `metrics(metric)` | Returns a list of metrics matching the specified `metric` regex. +`query_result(query)` | Returns a list of Prometheus query result for the `query`. For details of `metric names` & `label names`, and `label values`, please refer to the [Prometheus documentation](http://prometheus.io/docs/concepts/data_model/#metric-names-and-labels). diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index 4cfd174978c..6c4cbc216ae 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -5,6 +5,7 @@ import _ from 'lodash'; import moment from 'moment'; import * as dateMath from 'app/core/utils/datemath'; +import PrometheusMetricFindQuery from './metric_find_query'; var durationSplitRegexp = /(\d+)(ms|s|m|h|d|w|M|y)/; @@ -90,7 +91,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS delete self.lastErrors.query; _.each(response.data.data.result, function(metricData) { - result.push(transformMetricData(metricData, options.targets[index], start, end)); + result.push(self.transformMetricData(metricData, options.targets[index], start, end)); }); }); @@ -123,69 +124,8 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS return $q.reject(err); } - var label_values_regex = /^label_values\(([^,]+)(?:,\s*(.+))?\)$/; - var metric_names_regex = /^metrics\((.+)\)$/; - - var url; - var label_values_query = interpolated.match(label_values_regex); - if (label_values_query) { - if (!label_values_query[2]) { - // return label values globally - url = '/api/v1/label/' + label_values_query[1] + '/values'; - - return this._request('GET', url).then(function(result) { - return _.map(result.data.data, function(value) { - return {text: value}; - }); - }); - } else { - url = '/api/v1/series?match[]=' + encodeURIComponent(label_values_query[1]); - - return this._request('GET', url) - .then(function(result) { - return _.map(result.data.data, function(metric) { - return { - text: metric[label_values_query[2]], - expandable: true - }; - }); - }); - } - } - - var metric_names_query = interpolated.match(metric_names_regex); - if (metric_names_query) { - url = '/api/v1/label/__name__/values'; - - return this._request('GET', url) - .then(function(result) { - return _.chain(result.data.data) - .filter(function(metricName) { - var r = new RegExp(metric_names_query[1]); - return r.test(metricName); - }) - .map(function(matchedMetricName) { - return { - text: matchedMetricName, - expandable: true - }; - }) - .value(); - }); - } else { - // if query contains full metric name, return metric name and label list - url = '/api/v1/series?match[]=' + encodeURIComponent(interpolated); - - return this._request('GET', url) - .then(function(result) { - return _.map(result.data.data, function(metric) { - return { - text: getOriginalMetricName(metric), - expandable: true - }; - }); - }); - } + var metricFindQuery = new PrometheusMetricFindQuery(this, interpolated); + return metricFindQuery.process(); }; this.annotationQuery = function(options) { @@ -210,6 +150,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS }; var start = getPrometheusTime(options.range.from, false); var end = getPrometheusTime(options.range.to, true); + var self = this; return this.performTimeSeriesQuery(query, start, end).then(function(results) { var eventList = []; tagKeys = tagKeys.split(','); @@ -225,9 +166,9 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS var event = { annotation: annotation, time: Math.floor(value[0]) * 1000, - title: renderTemplate(titleFormat, series.metric), + title: self.renderTemplate(titleFormat, series.metric), tags: tags, - text: renderTemplate(textFormat, series.metric) + text: self.renderTemplate(textFormat, series.metric) }; eventList.push(event); @@ -245,7 +186,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS }); }; - PrometheusDatasource.prototype.calculateInterval = function(interval, intervalFactor) { + this.calculateInterval = function(interval, intervalFactor) { var m = interval.match(durationSplitRegexp); var dur = moment.duration(parseInt(m[1]), m[2]); var sec = dur.asSeconds(); @@ -256,11 +197,11 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS return Math.ceil(sec * intervalFactor); }; - function transformMetricData(md, options, start, end) { + this.transformMetricData = function(md, options, start, end) { var dps = [], metricLabel = null; - metricLabel = createMetricLabel(md.metric, options); + metricLabel = this.createMetricLabel(md.metric, options); var stepMs = parseInt(options.step) * 1000; var baseTimestamp = start * 1000; @@ -284,17 +225,17 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS } return { target: metricLabel, datapoints: dps }; - } + }; - function createMetricLabel(labelData, options) { + this.createMetricLabel = function(labelData, options) { if (_.isUndefined(options) || _.isEmpty(options.legendFormat)) { - return getOriginalMetricName(labelData); + return this.getOriginalMetricName(labelData); } - return renderTemplate(options.legendFormat, labelData) || '{}'; - } + return this.renderTemplate(options.legendFormat, labelData) || '{}'; + }; - function renderTemplate(format, data) { + this.renderTemplate = function(format, data) { var originalSettings = _.templateSettings; _.templateSettings = { interpolate: /\{\{(.+?)\}\}/g @@ -311,16 +252,16 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS _.templateSettings = originalSettings; return result; - } + }; - function getOriginalMetricName(labelData) { + this.getOriginalMetricName = function(labelData) { var metricName = labelData.__name__ || ''; delete labelData.__name__; var labelPart = _.map(_.pairs(labelData), function(label) { return label[0] + '="' + label[1] + '"'; }).join(','); return metricName + '{' + labelPart + '}'; - } + }; function getPrometheusTime(date, roundUp): number { if (_.isString(date)) { diff --git a/public/app/plugins/datasource/prometheus/metric_find_query.d.ts b/public/app/plugins/datasource/prometheus/metric_find_query.d.ts new file mode 100644 index 00000000000..c3318b8e133 --- /dev/null +++ b/public/app/plugins/datasource/prometheus/metric_find_query.d.ts @@ -0,0 +1,2 @@ +declare var test: any; +export default test; diff --git a/public/app/plugins/datasource/prometheus/metric_find_query.js b/public/app/plugins/datasource/prometheus/metric_find_query.js new file mode 100644 index 00000000000..65ea92e05b1 --- /dev/null +++ b/public/app/plugins/datasource/prometheus/metric_find_query.js @@ -0,0 +1,125 @@ +define([ + 'lodash', + 'moment', +], +function (_, moment) { + 'use strict'; + + function PrometheusMetricFindQuery(datasource, query) { + this.datasource = datasource; + this.query = query; + } + + PrometheusMetricFindQuery.prototype.process = function() { + var label_values_regex = /^label_values\(([^,]+)(?:,\s*(.+))?\)$/; + 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[2]) { + return this.labelValuesQuery(label_values_query[2], label_values_query[1]); + } else { + return this.labelValuesQuery(label_values_query[1], null); + } + } + + var metric_names_query = this.query.match(metric_names_regex); + if (metric_names_query) { + return this.metricNameQuery(metric_names_query[1]); + } + + var query_result_query = this.query.match(query_result_regex); + if (query_result_query) { + return this.queryResultQuery(query_result_query[1]); + } + + // if query contains full metric name, return metric name and label list + return this.metricNameAndLabelsQuery(this.query); + }; + + PrometheusMetricFindQuery.prototype.labelValuesQuery = function(label, metric) { + var url; + + if (!metric) { + // return label values globally + url = '/api/v1/label/' + label + '/values'; + + return this.datasource._request('GET', url).then(function(result) { + return _.map(result.data.data, function(value) { + return {text: value}; + }); + }); + } else { + url = '/api/v1/series?match[]=' + encodeURIComponent(metric); + + return this.datasource._request('GET', url) + .then(function(result) { + return _.map(result.data.data, function(metric) { + return { + text: metric[label], + expandable: true + }; + }); + }); + } + }; + + 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(); + }); + }; + + PrometheusMetricFindQuery.prototype.queryResultQuery = function(query) { + var url = '/api/v1/query?query=' + encodeURIComponent(query) + '&time=' + (moment().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 + }; + }); + }); + }; + + PrometheusMetricFindQuery.prototype.metricNameAndLabelsQuery = function(query) { + var url = '/api/v1/series?match[]=' + encodeURIComponent(query); + + 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 + }; + }); + }); + }; + + return PrometheusMetricFindQuery; +}); diff --git a/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts b/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts index 4a1b4121dba..1958c6be4cf 100644 --- a/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts @@ -116,47 +116,6 @@ describe('PrometheusDatasource', function() { expect(results.data[1].datapoints[3][0]).to.be(null); }); }); - 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); - ctx.ds.metricFindQuery('label_values(resource)').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", - data: [ - {__name__: "metric", resource: "value1"}, - {__name__: "metric", resource: "value2"}, - {__name__: "metric", resource: "value3"} - ] - }; - ctx.$httpBackend.expect('GET', 'proxied/api/v1/series?match[]=metric').respond(response); - ctx.ds.metricFindQuery('label_values(metric, resource)').then(function(data) { results = data; }); - ctx.$httpBackend.flush(); - 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); - ctx.ds.metricFindQuery('metrics(metric.*)').then(function(data) { results = data; }); - ctx.$httpBackend.flush(); - ctx.$rootScope.$apply(); - expect(results.length).to.be(3); - }); - }); describe('When performing annotationQuery', function() { var results; var urlExpected = 'proxied/api/v1/query_range?query=' + 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 new file mode 100644 index 00000000000..dfc6b597598 --- /dev/null +++ b/public/app/plugins/datasource/prometheus/specs/metric_find_query_specs.ts @@ -0,0 +1,83 @@ +import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; +import moment from 'moment'; +import helpers from 'test/specs/helpers'; +import {PrometheusDatasource} from '../datasource'; +import PrometheusMetricFindQuery from '../metric_find_query'; + +describe('PrometheusMetricFindQuery', function() { + + var ctx = new helpers.ServiceTestContext(); + var instanceSettings = {url: 'proxied', directUrl: 'direct', user: 'test', password: 'mupp' }; + beforeEach(angularMocks.module('grafana.core')); + beforeEach(angularMocks.module('grafana.services')); + beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) { + ctx.$q = $q; + ctx.$httpBackend = $httpBackend; + ctx.$rootScope = $rootScope; + ctx.ds = $injector.instantiate(PrometheusDatasource, {instanceSettings: instanceSettings}); + })); + + 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)'); + 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", + data: [ + {__name__: "metric", resource: "value1"}, + {__name__: "metric", resource: "value2"}, + {__name__: "metric", resource: "value3"} + ] + }; + ctx.$httpBackend.expect('GET', 'proxied/api/v1/series?match[]=metric').respond(response); + var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(metric, resource)'); + pm.process().then(function(data) { results = data; }); + ctx.$httpBackend.flush(); + 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.*)'); + 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", + data: { + resultType: "vector", + result: [{ + metric: {"__name__": "metric", job: "testjob"}, + value: [1443454528.000, "3846"] + }] + } + }; + ctx.$httpBackend.expect('GET', /proxied\/api\/v1\/query\?query=metric&time=.*/).respond(response); + var pm = new PrometheusMetricFindQuery(ctx.ds, 'query_result(metric)'); + pm.process().then(function(data) { results = data; }); + ctx.$httpBackend.flush(); + ctx.$rootScope.$apply(); + expect(results.length).to.be(1); + expect(results[0].text).to.be('metric{job="testjob"} 3846 1443454528000'); + }); + }); +});