Commit f4e90df4 by Carl Bergquist

Merge pull request #3472 from mtanda/prometheus_query_template

(prometheus) templating by query result
parents dea2234b 2cce057d
......@@ -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).
......
......@@ -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)) {
......
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;
});
......@@ -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=' +
......
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');
});
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment