Skip to content

Commit

Permalink
Merge pull request grafana#3472 from mtanda/prometheus_query_template
Browse files Browse the repository at this point in the history
(prometheus) templating by query result
  • Loading branch information
bergquist committed Feb 9, 2016
2 parents dea2234 + 2cce057 commit f4e90df
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 119 deletions.
1 change: 1 addition & 0 deletions docs/sources/datasources/prometheus.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down
97 changes: 19 additions & 78 deletions public/app/plugins/datasource/prometheus/datasource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)/;

Expand Down Expand Up @@ -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));
});
});

Expand Down Expand Up @@ -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) {
Expand All @@ -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(',');
Expand All @@ -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);
Expand All @@ -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();
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
declare var test: any;
export default test;
125 changes: 125 additions & 0 deletions public/app/plugins/datasource/prometheus/metric_find_query.js
Original file line number Diff line number Diff line change
@@ -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;
});
41 changes: 0 additions & 41 deletions public/app/plugins/datasource/prometheus/specs/datasource_specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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=' +
Expand Down
Loading

0 comments on commit f4e90df

Please sign in to comment.