mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
(prometheus) add query result template query
This commit is contained in:
parent
dea2234b14
commit
2cce057df1
@ -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)) {
|
||||
|
2
public/app/plugins/datasource/prometheus/metric_find_query.d.ts
vendored
Normal file
2
public/app/plugins/datasource/prometheus/metric_find_query.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
declare var test: any;
|
||||
export default test;
|
125
public/app/plugins/datasource/prometheus/metric_find_query.js
Normal file
125
public/app/plugins/datasource/prometheus/metric_find_query.js
Normal file
@ -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;
|
||||
});
|
@ -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=' +
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user