mirror of
https://github.com/grafana/grafana.git
synced 2025-01-28 17:24:59 -06:00
Merge branch 'prometheus' of github.com:grafana/grafana
Conflicts: pkg/models/datasource.go public/app/plugins/datasource/prometheus/datasource.js public/app/plugins/datasource/prometheus/partials/query.editor.html public/app/plugins/datasource/prometheus/queryCtrl.js
This commit is contained in:
commit
106fe4854f
@ -69,6 +69,7 @@ pages:
|
||||
- ['datasources/influxdb.md', 'Data Sources', 'InfluxDB']
|
||||
- ['datasources/opentsdb.md', 'Data Sources', 'OpenTSDB']
|
||||
- ['datasources/kairosdb.md', 'Data Sources', 'KairosDB']
|
||||
- ['datasources/prometheus.md', 'Data Sources', 'Prometheus']
|
||||
|
||||
- ['tutorials/index.md', 'Tutorials', 'Tutorials']
|
||||
- ['tutorials/hubot_howto.md', 'Tutorials', 'How To integrate Hubot and Grafana']
|
||||
|
@ -1,7 +1,7 @@
|
||||
----
|
||||
page_title: Data Source Overview
|
||||
page_description: Data Source Overview
|
||||
page_keywords: grafana, graphite, influxDB, KairosDB, OpenTSDB, documentation
|
||||
page_keywords: grafana, graphite, influxDB, KairosDB, OpenTSDB, Prometheus, documentation
|
||||
---
|
||||
|
||||
# Data Source Overview
|
||||
@ -18,5 +18,6 @@ The following datasources are officially supported:
|
||||
* [InfluxDB](/datasources/influxdb/)
|
||||
* [OpenTSDB](/datasources/opentsdb/)
|
||||
* [KairosDB](/datasources/kairosdb)
|
||||
* [Prometheus](/datasources/prometheus)
|
||||
|
||||
Grafana can query any Elasticsearch index for annotation events, but at this time, it's not supported for metric queries. Learn more about [annotations](/reference/annotations/#elasticsearch-annotations)
|
||||
|
65
docs/sources/datasources/prometheus.md
Normal file
65
docs/sources/datasources/prometheus.md
Normal file
@ -0,0 +1,65 @@
|
||||
----
|
||||
page_title: Prometheus query guide
|
||||
page_description: Prometheus query guide
|
||||
page_keywords: grafana, prometheus, metrics, query, documentation
|
||||
---
|
||||
|
||||
# Prometheus
|
||||
Grafana includes support for Prometheus Datasources. While the process of adding the datasource is similar to adding a Graphite or OpenTSDB datasource type, Prometheus does have a few different options for building queries.
|
||||
|
||||
## Adding the data source to Grafana
|
||||
![](/img/v2/add_Prometheus.png)
|
||||
|
||||
1. Open the side menu by clicking the the Grafana icon in the top header.
|
||||
2. In the side menu under the `Dashboards` link you should find a link named `Data Sources`.
|
||||
|
||||
> NOTE: If this link is missing in the side menu it means that your current user does not have the `Admin` role for the current organization.
|
||||
|
||||
3. Click the `Add new` link in the top header.
|
||||
4. Select `Prometheus` from the dropdown.
|
||||
|
||||
Name | Description
|
||||
------------ | -------------
|
||||
Name | The data source name, important that this is the same as in Grafana v1.x if you plan to import old dashboards.
|
||||
Default | Default data source means that it will be pre-selected for new panels.
|
||||
Url | The http protocol, ip and port of you Prometheus server (default port is usually 9090)
|
||||
Access | Proxy = access via Grafana backend, Direct = access directory from browser.
|
||||
Basic Auth | Enable basic authentication to the Prometheus datasource.
|
||||
User | Name of your Prometheus user
|
||||
Password | Database user's password
|
||||
|
||||
> Proxy access means that the Grafana backend will proxy all requests from the browser, and send them on to the Data Source. This is useful because it can eliminate CORS (Cross Origin Site Resource) issues, as well as eliminate the need to disseminate authentication details to the Data Source to the brower.
|
||||
|
||||
> Direct access is still supported because in some cases it may be useful to access a Data Source directly depending on the use case and topology of Grafana, the user, and the Data Source.
|
||||
|
||||
## Query editor
|
||||
Open a graph in edit mode by click the title.
|
||||
|
||||
![](/img/v2/prometheus_editor.png)
|
||||
|
||||
For details on Prometheus metric queries check out the Prometheus documentation
|
||||
- [Query Metrics - Prometheus documentation](http://prometheus.io/docs/querying/basics/).
|
||||
|
||||
## Templated queries
|
||||
Prometheus Datasource Plugin provides the following functions in `Variables values query` field in Templating Editor to query `metric names` and `labels names` on the Prometheus server.
|
||||
|
||||
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.
|
||||
|
||||
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).
|
||||
|
||||
You can create a template variable in Grafana and have that variable filled with values from any Prometheus metric exploration query.
|
||||
You can then use this variable in your Prometheus metric queries.
|
||||
|
||||
For example you can have a variable that contains all values for label `hostname` if you specify a query like this
|
||||
in the templating edit view.
|
||||
```sql
|
||||
label_values(hostname)
|
||||
```
|
||||
|
||||
You can also use raw queries & regular expressions to extract anything you might need.
|
||||
|
||||
![](/img/v2/prometheus_templating.png)
|
@ -10,7 +10,7 @@ It provides a powerful and elegant way to create, share, and explore data and da
|
||||
|
||||
Grafana is most commonly used for Internet infrastructure and application analytics, but many use it in other domains including industrial sensors, home automation, weather, and process control.
|
||||
|
||||
Grafana features pluggable panels and data sources allowing easy extensibility. There is currently rich support for [Graphite](http://graphite.readthedocs.org/en/latest/), [InfluxDB](http://influxdb.org) and [OpenTSDB](http://opentsdb.net). There is also experimental support for [KairosDB](https://github.com/kairosdb/kairosdb), and SQL is on the roadmap. Grafana has a variety of panels, including a fully featured graph panel with rich visualization options.
|
||||
Grafana features pluggable panels and data sources allowing easy extensibility. There is currently rich support for [Graphite](http://graphite.readthedocs.org/en/latest/), [InfluxDB](http://influxdb.org) and [OpenTSDB](http://opentsdb.net). There is also experimental support for [KairosDB](https://github.com/kairosdb/kairosdb), [Prometheus](http://prometheus.io/), and SQL is on the roadmap. Grafana has a variety of panels, including a fully featured graph panel with rich visualization options.
|
||||
|
||||
Version 2.0 was released in April 2015: Grafana now ships with its own backend server that brings [many changes and features](../guides/whats-new-in-v2/).
|
||||
Version 2.1 was released in July 2015 and added [even more features and enhancements](../guides/whats-new-in-v2-1/).
|
||||
|
@ -81,6 +81,11 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
|
||||
dsMap["index"] = ds.Database
|
||||
}
|
||||
|
||||
if ds.Type == m.DS_PROMETHEUS {
|
||||
// add unproxied server URL for link to Prometheus web UI
|
||||
dsMap["directUrl"] = ds.Url
|
||||
}
|
||||
|
||||
datasources[ds.Name] = dsMap
|
||||
}
|
||||
|
||||
|
@ -1,17 +1,18 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash',
|
||||
'kbn',
|
||||
'moment',
|
||||
'app/core/utils/datemath',
|
||||
'./directives',
|
||||
'./query_ctrl',
|
||||
],
|
||||
function (angular, _, kbn, dateMath) {
|
||||
function (angular, _, moment, dateMath) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.services');
|
||||
|
||||
var durationSplitRegexp = /(\d+)(ms|s|m|h|d|w|M|y)/;
|
||||
|
||||
module.factory('PrometheusDatasource', function($q, backendSrv, templateSrv) {
|
||||
|
||||
function PrometheusDatasource(datasource) {
|
||||
@ -19,12 +20,7 @@ function (angular, _, kbn, dateMath) {
|
||||
this.editorSrc = 'app/features/prometheus/partials/query.editor.html';
|
||||
this.name = datasource.name;
|
||||
this.supportMetrics = true;
|
||||
|
||||
var url = datasource.url;
|
||||
if (url[url.length-1] === '/') {
|
||||
url = url.substr(0, url.length - 1);
|
||||
}
|
||||
this.url = url;
|
||||
this.url = datasource.url;
|
||||
this.basicAuth = datasource.basicAuth;
|
||||
this.lastErrors = {};
|
||||
}
|
||||
@ -126,29 +122,62 @@ function (angular, _, kbn, dateMath) {
|
||||
};
|
||||
|
||||
PrometheusDatasource.prototype.metricFindQuery = function(query) {
|
||||
if (!query) { return $q.when([]); }
|
||||
|
||||
var interpolated;
|
||||
try {
|
||||
interpolated = templateSrv.replace(query);
|
||||
}
|
||||
catch (err) {
|
||||
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';
|
||||
|
||||
var metricsQuery = query.match(/^[a-zA-Z_:*][a-zA-Z0-9_:*]*/);
|
||||
var labelValuesQuery = query.match(/^label_values\((.+)\)/);
|
||||
|
||||
if (labelValuesQuery) {
|
||||
// return label values
|
||||
url = '/api/v1/label/' + labelValuesQuery[1] + '/values';
|
||||
|
||||
return this._request('GET', url).then(function(result) {
|
||||
return _.map(result.data.data, function(value) {
|
||||
return {text: value};
|
||||
return this._request('GET', url).then(function(result) {
|
||||
return _.map(result.data.data, function(value) {
|
||||
return {text: value};
|
||||
});
|
||||
});
|
||||
});
|
||||
} else if (metricsQuery != null && metricsQuery[0].indexOf('*') >= 0) {
|
||||
// if query has wildcard character, return metric name list
|
||||
} else {
|
||||
var metric_query = 'count(' + label_values_query[1] + ') by (' +
|
||||
label_values_query[2] + ')';
|
||||
url = '/api/v1/query?query=' + encodeURIComponent(metric_query) +
|
||||
'&time=' + (moment().valueOf() / 1000);
|
||||
|
||||
return this._request('GET', url)
|
||||
.then(function(result) {
|
||||
if (result.data.data.result.length === 0 ||
|
||||
_.keys(result.data.data.result[0].metric).length === 0) {
|
||||
return [];
|
||||
}
|
||||
return _.map(result.data.data.result, function(metricValue) {
|
||||
return {
|
||||
text: metricValue.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(metricsQuery[0].replace(/\*/g, '.*'));
|
||||
var r = new RegExp(metric_names_query[1]);
|
||||
return r.test(metricName);
|
||||
})
|
||||
.map(function(matchedMetricName) {
|
||||
@ -161,11 +190,12 @@ function (angular, _, kbn, dateMath) {
|
||||
});
|
||||
} else {
|
||||
// if query contains full metric name, return metric name and label list
|
||||
url = '/api/v1/query?query=' + encodeURIComponent(query);
|
||||
url = '/api/v1/query?query=' + encodeURIComponent(interpolated) +
|
||||
'&time=' + (moment().valueOf() / 1000);
|
||||
|
||||
return this._request('GET', url)
|
||||
.then(function(result) {
|
||||
return _.map(result.data.result, function(metricData) {
|
||||
return _.map(result.data.data.result, function(metricData) {
|
||||
return {
|
||||
text: getOriginalMetricName(metricData.metric),
|
||||
expandable: true
|
||||
@ -176,19 +206,20 @@ function (angular, _, kbn, dateMath) {
|
||||
};
|
||||
|
||||
PrometheusDatasource.prototype.testDatasource = function() {
|
||||
return this.metricFindQuery('*').then(function() {
|
||||
return this.metricFindQuery('metrics(.*)').then(function() {
|
||||
return { status: 'success', message: 'Data source is working', title: 'Success' };
|
||||
});
|
||||
};
|
||||
|
||||
PrometheusDatasource.prototype.calculateInterval = function(interval, intervalFactor) {
|
||||
var sec = kbn.interval_to_seconds(interval);
|
||||
|
||||
var m = interval.match(durationSplitRegexp);
|
||||
var dur = moment.duration(parseInt(m[1]), m[2]);
|
||||
var sec = dur.asSeconds();
|
||||
if (sec < 1) {
|
||||
sec = 1;
|
||||
}
|
||||
|
||||
return sec * intervalFactor;
|
||||
return Math.floor(sec * intervalFactor) + 's';
|
||||
};
|
||||
|
||||
function transformMetricData(md, options) {
|
||||
@ -238,12 +269,6 @@ function (angular, _, kbn, dateMath) {
|
||||
|
||||
function getPrometheusTime(date, roundUp) {
|
||||
if (_.isString(date)) {
|
||||
if (date === 'now') {
|
||||
return 'now()';
|
||||
}
|
||||
if (date.indexOf('now-') >= 0 && date.indexOf('/') === -1) {
|
||||
return date.replace('now', 'now()').replace('-', ' - ');
|
||||
}
|
||||
date = dateMath.parse(date, roundUp);
|
||||
}
|
||||
return (date.valueOf() / 1000).toFixed(0);
|
||||
|
@ -62,7 +62,6 @@
|
||||
placeholder="metric name"
|
||||
data-min-length=0 data-items=100>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
@ -74,15 +73,10 @@
|
||||
Legend format
|
||||
</li>
|
||||
<li>
|
||||
<input type="text"
|
||||
class="tight-form-input input-xxlarge"
|
||||
ng-model="target.legendFormat"
|
||||
spellcheck='false'
|
||||
placeholder="legend format"
|
||||
data-min-length=0 data-items=1000
|
||||
ng-model-onblur
|
||||
ng-change="refreshMetricData()"
|
||||
/>
|
||||
<input type="text" class="tight-form-input input-xxlarge" ng-model="target.legendFormat"
|
||||
spellcheck='false' placeholder="legend format" data-min-length=0 data-items=1000
|
||||
ng-model-onblur ng-change="refreshMetricData()">
|
||||
</input>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@ -95,9 +89,7 @@
|
||||
Step
|
||||
</li>
|
||||
<li>
|
||||
<input type="text"
|
||||
class="input-mini tight-form-input"
|
||||
ng-model="target.interval"
|
||||
<input type="text" class="input-mini tight-form-input" ng-model="target.interval"
|
||||
bs-tooltip="'Leave blank for auto handling based on time range and panel width'"
|
||||
data-placement="right"
|
||||
spellcheck='false'
|
||||
@ -106,14 +98,14 @@
|
||||
ng-model-onblur
|
||||
ng-change="refreshMetricData()"
|
||||
/>
|
||||
</input>
|
||||
</li>
|
||||
|
||||
<li class="tight-form-item">
|
||||
Resolution
|
||||
</li>
|
||||
<li>
|
||||
<select ng-model="target.intervalFactor"
|
||||
class="tight-form-input input-mini"
|
||||
<select ng-model="target.intervalFactor" class="tight-form-input input-mini"
|
||||
ng-options="r.factor as r.label for r in resolutions"
|
||||
ng-change="refreshMetricData()">
|
||||
</select>
|
||||
|
@ -0,0 +1,93 @@
|
||||
///<amd-dependency path="app/plugins/datasource/prometheus/datasource" />
|
||||
///<amd-dependency path="test/specs/helpers" name="helpers" />
|
||||
|
||||
import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common';
|
||||
import moment = require('moment');
|
||||
declare var helpers: any;
|
||||
|
||||
describe('PrometheusDatasource', function() {
|
||||
var ctx = new helpers.ServiceTestContext();
|
||||
beforeEach(angularMocks.module('grafana.services'));
|
||||
beforeEach(ctx.createService('PrometheusDatasource'));
|
||||
beforeEach(function() {
|
||||
ctx.ds = new ctx.service({ url: 'proxied', directUrl: 'direct', user: 'test', password: 'mupp' });
|
||||
});
|
||||
describe('When querying prometheus with one target using query editor target spec', function() {
|
||||
var results;
|
||||
var urlExpected = 'proxied/api/v1/query_range?query=' +
|
||||
encodeURIComponent('test{job="testjob"}') +
|
||||
'&start=1443438675&end=1443460275&step=60s';
|
||||
var query = {
|
||||
range: { from: moment(1443438674760), to: moment(1443460274760) },
|
||||
targets: [{ expr: 'test{job="testjob"}' }],
|
||||
interval: '60s'
|
||||
};
|
||||
var response = {
|
||||
status: "success",
|
||||
data: {
|
||||
resultType: "matrix",
|
||||
result: [{
|
||||
metric: {"__name__": "test", job: "testjob"},
|
||||
values: [[1443454528, "3846"]]
|
||||
}]
|
||||
}
|
||||
};
|
||||
beforeEach(function() {
|
||||
ctx.$httpBackend.expect('GET', urlExpected).respond(response);
|
||||
ctx.ds.query(query).then(function(data) { results = data; });
|
||||
ctx.$httpBackend.flush();
|
||||
});
|
||||
it('should generate the correct query', function() {
|
||||
ctx.$httpBackend.verifyNoOutstandingExpectation();
|
||||
});
|
||||
it('should return series list', function() {
|
||||
expect(results.data.length).to.be(1);
|
||||
expect(results.data[0].target).to.be('test{job="testjob"}');
|
||||
});
|
||||
});
|
||||
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 count metric query', function() {
|
||||
response = {
|
||||
status: "success",
|
||||
data: {
|
||||
resultType: "vector",
|
||||
result: [
|
||||
{metric: {resource: "value1"}, value: []},
|
||||
{metric: {resource: "value2"}, value: []},
|
||||
{metric: {resource: "value3"}, value: []}
|
||||
]
|
||||
}
|
||||
};
|
||||
ctx.$httpBackend.expect('GET', /proxied\/api\/v1\/query\?query=count\(metric\)%20by%20\(resource\)&time=.*/).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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,61 +0,0 @@
|
||||
define([
|
||||
'./helpers',
|
||||
'moment',
|
||||
'app/plugins/datasource/prometheus/datasource',
|
||||
'app/services/backendSrv',
|
||||
'app/services/alertSrv'
|
||||
], function(helpers, moment) {
|
||||
'use strict';
|
||||
|
||||
describe('PrometheusDatasource', function() {
|
||||
var ctx = new helpers.ServiceTestContext();
|
||||
|
||||
beforeEach(module('grafana.services'));
|
||||
beforeEach(ctx.providePhase(['templateSrv']));
|
||||
beforeEach(ctx.createService('PrometheusDatasource'));
|
||||
beforeEach(function() {
|
||||
ctx.ds = new ctx.service({ url: '', user: 'test', password: 'mupp' });
|
||||
});
|
||||
|
||||
describe('When querying prometheus with one target using query editor target spec', function() {
|
||||
var results;
|
||||
var urlExpected = '/api/v1/query_range?query=' +
|
||||
encodeURIComponent('test{job="testjob"}') +
|
||||
'&start=1443438675&end=1443460275&step=60';
|
||||
var query = {
|
||||
range: { from: moment(1443438674760), to: moment(1443460274760) },
|
||||
targets: [{ expr: 'test{job="testjob"}' }],
|
||||
interval: '60s'
|
||||
};
|
||||
|
||||
var response = {
|
||||
"status":"success",
|
||||
"data":{
|
||||
"resultType":"matrix",
|
||||
"result":[{
|
||||
"metric":{"__name__":"test", "job":"testjob"},
|
||||
"values":[[1443454528,"3846"]]
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
ctx.$httpBackend.expect('GET', urlExpected).respond(response);
|
||||
ctx.ds.query(query).then(function(data) { results = data; });
|
||||
ctx.$httpBackend.flush();
|
||||
});
|
||||
|
||||
it('should generate the correct query', function() {
|
||||
ctx.$httpBackend.verifyNoOutstandingExpectation();
|
||||
});
|
||||
|
||||
it('should return series list', function() {
|
||||
expect(results.data.length).to.be(1);
|
||||
expect(results.data[0].target).to.be('test{job="testjob"}');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user