Skip to content

Commit a2bf3e0

Browse files
committed
Merge branch 'prometheus'
2 parents 318af9b + ee85eb2 commit a2bf3e0

File tree

11 files changed

+664
-0
lines changed

11 files changed

+664
-0
lines changed

docker/blocks/prometheus/Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FROM prom/prometheus
2+
ADD prometheus.yml /etc/prometheus/

docker/blocks/prometheus/fig

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
prometheus:
2+
build: blocks/prometheus
3+
ports:
4+
- "9090:9090"
5+
volumes:
6+
- /var/docker/prometheus:/prometheus-data
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# my global config
2+
global:
3+
scrape_interval: 10s # By default, scrape targets every 15 seconds.
4+
evaluation_interval: 10s # By default, scrape targets every 15 seconds.
5+
# scrape_timeout is set to the global default (10s).
6+
7+
# Attach these extra labels to all timeseries collected by this Prometheus instance.
8+
labels:
9+
monitor: 'codelab-monitor'
10+
11+
# Load and evaluate rules in this file every 'evaluation_interval' seconds.
12+
rule_files:
13+
# - "first.rules"
14+
# - "second.rules"
15+
16+
# A scrape configuration containing exactly one endpoint to scrape:
17+
# Here it's Prometheus itself.
18+
scrape_configs:
19+
# The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
20+
- job_name: 'prometheus'
21+
22+
# Override the global default and scrape targets from this job every 5 seconds.
23+
scrape_interval: 10s
24+
scrape_timeout: 10s
25+
26+
# metrics_path defaults to '/metrics'
27+
# scheme defaults to 'http'.
28+
29+
target_groups:
30+
- targets: ['localhost:9090', '172.17.42.1:9091']
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
define([
2+
'angular',
3+
'lodash',
4+
'kbn',
5+
'moment',
6+
'app/core/utils/datemath',
7+
'./directives',
8+
'./queryCtrl',
9+
],
10+
function (angular, _, kbn, dateMath) {
11+
'use strict';
12+
13+
var module = angular.module('grafana.services');
14+
15+
module.factory('PrometheusDatasource', function($q, backendSrv, templateSrv) {
16+
17+
function PrometheusDatasource(datasource) {
18+
this.type = 'prometheus';
19+
this.editorSrc = 'app/features/prometheus/partials/query.editor.html';
20+
this.name = datasource.name;
21+
this.supportMetrics = true;
22+
23+
var url = datasource.url;
24+
if (url[url.length-1] === '/') {
25+
// remove trailing slash
26+
url = url.substr(0, url.length - 1);
27+
}
28+
this.url = url;
29+
this.basicAuth = datasource.basicAuth;
30+
this.lastErrors = {};
31+
}
32+
33+
PrometheusDatasource.prototype._request = function(method, url) {
34+
var options = {
35+
url: this.url + url,
36+
method: method
37+
};
38+
39+
if (this.basicAuth) {
40+
options.withCredentials = true;
41+
options.headers = {
42+
"Authorization": this.basicAuth
43+
};
44+
}
45+
46+
return backendSrv.datasourceRequest(options);
47+
};
48+
49+
// Called once per panel (graph)
50+
PrometheusDatasource.prototype.query = function(options) {
51+
var start = getPrometheusTime(options.range.from, false);
52+
var end = getPrometheusTime(options.range.to, true);
53+
54+
var queries = [];
55+
_.each(options.targets, _.bind(function(target) {
56+
if (!target.expr || target.hide) {
57+
return;
58+
}
59+
60+
var query = {};
61+
query.expr = templateSrv.replace(target.expr, options.scopedVars);
62+
63+
var interval = target.interval || options.interval;
64+
var intervalFactor = target.intervalFactor || 1;
65+
query.step = this.calculateInterval(interval, intervalFactor);
66+
67+
queries.push(query);
68+
}, this));
69+
70+
// No valid targets, return the empty result to save a round trip.
71+
if (_.isEmpty(queries)) {
72+
var d = $q.defer();
73+
d.resolve({ data: [] });
74+
return d.promise;
75+
}
76+
77+
var allQueryPromise = _.map(queries, _.bind(function(query) {
78+
return this.performTimeSeriesQuery(query, start, end);
79+
}, this));
80+
81+
var self = this;
82+
return $q.all(allQueryPromise)
83+
.then(function(allResponse) {
84+
var result = [];
85+
86+
_.each(allResponse, function(response, index) {
87+
if (response.status === 'error') {
88+
self.lastErrors.query = response.error;
89+
throw response.error;
90+
}
91+
delete self.lastErrors.query;
92+
93+
_.each(response.data.data.result, function(metricData) {
94+
result.push(transformMetricData(metricData, options.targets[index]));
95+
});
96+
});
97+
98+
return { data: result };
99+
});
100+
};
101+
102+
PrometheusDatasource.prototype.performTimeSeriesQuery = function(query, start, end) {
103+
var url = '/api/v1/query_range?query=' + encodeURIComponent(query.expr) + '&start=' + start + '&end=' + end;
104+
105+
var step = query.step;
106+
var range = Math.floor(end - start);
107+
// Prometheus drop query if range/step > 11000
108+
// calibrate step if it is too big
109+
if (step !== 0 && range / step > 11000) {
110+
step = Math.floor(range / 11000);
111+
}
112+
url += '&step=' + step;
113+
114+
return this._request('GET', url);
115+
};
116+
117+
PrometheusDatasource.prototype.performSuggestQuery = function(query) {
118+
var url = '/api/v1/label/__name__/values';
119+
120+
return this._request('GET', url).then(function(result) {
121+
var suggestData = _.filter(result.data.data, function(metricName) {
122+
return metricName.indexOf(query) !== 1;
123+
});
124+
125+
return suggestData;
126+
});
127+
};
128+
129+
PrometheusDatasource.prototype.metricFindQuery = function(query) {
130+
var url;
131+
132+
var metricsQuery = query.match(/^[a-zA-Z_:*][a-zA-Z0-9_:*]*/);
133+
var labelValuesQuery = query.match(/^label_values\((.+)\)/);
134+
135+
if (labelValuesQuery) {
136+
// return label values
137+
url = '/api/v1/label/' + labelValuesQuery[1] + '/values';
138+
139+
return this._request('GET', url).then(function(result) {
140+
return _.map(result.data.data, function(value) {
141+
return {text: value};
142+
});
143+
});
144+
} else if (metricsQuery != null && metricsQuery[0].indexOf('*') >= 0) {
145+
// if query has wildcard character, return metric name list
146+
url = '/api/v1/label/__name__/values';
147+
148+
return this._request('GET', url)
149+
.then(function(result) {
150+
return _.chain(result.data.data)
151+
.filter(function(metricName) {
152+
var r = new RegExp(metricsQuery[0].replace(/\*/g, '.*'));
153+
return r.test(metricName);
154+
})
155+
.map(function(matchedMetricName) {
156+
return {
157+
text: matchedMetricName,
158+
expandable: true
159+
};
160+
})
161+
.value();
162+
});
163+
} else {
164+
// if query contains full metric name, return metric name and label list
165+
url = '/api/v1/query?query=' + encodeURIComponent(query);
166+
167+
return this._request('GET', url)
168+
.then(function(result) {
169+
return _.map(result.data.result, function(metricData) {
170+
return {
171+
text: getOriginalMetricName(metricData.metric),
172+
expandable: true
173+
};
174+
});
175+
});
176+
}
177+
};
178+
179+
PrometheusDatasource.prototype.testDatasource = function() {
180+
return this.metricFindQuery('*').then(function() {
181+
return { status: 'success', message: 'Data source is working', title: 'Success' };
182+
});
183+
};
184+
185+
PrometheusDatasource.prototype.calculateInterval = function(interval, intervalFactor) {
186+
var sec = kbn.interval_to_seconds(interval);
187+
188+
if (sec < 1) {
189+
sec = 1;
190+
}
191+
192+
return sec * intervalFactor;
193+
};
194+
195+
function transformMetricData(md, options) {
196+
var dps = [],
197+
metricLabel = null;
198+
199+
metricLabel = createMetricLabel(md.metric, options);
200+
201+
dps = _.map(md.values, function(value) {
202+
return [parseFloat(value[1]), value[0] * 1000];
203+
});
204+
205+
return { target: metricLabel, datapoints: dps };
206+
}
207+
208+
function createMetricLabel(labelData, options) {
209+
if (_.isUndefined(options) || _.isEmpty(options.legendFormat)) {
210+
return getOriginalMetricName(labelData);
211+
}
212+
213+
var originalSettings = _.templateSettings;
214+
_.templateSettings = {
215+
interpolate: /\{\{(.+?)\}\}/g
216+
};
217+
218+
var template = _.template(templateSrv.replace(options.legendFormat));
219+
var metricName;
220+
try {
221+
metricName = template(labelData);
222+
} catch (e) {
223+
metricName = '{}';
224+
}
225+
226+
_.templateSettings = originalSettings;
227+
228+
return metricName;
229+
}
230+
231+
function getOriginalMetricName(labelData) {
232+
var metricName = labelData.__name__ || '';
233+
delete labelData.__name__;
234+
var labelPart = _.map(_.pairs(labelData), function(label) {
235+
return label[0] + '="' + label[1] + '"';
236+
}).join(',');
237+
return metricName + '{' + labelPart + '}';
238+
}
239+
240+
function getPrometheusTime(date, roundUp) {
241+
if (_.isString(date)) {
242+
if (date === 'now') {
243+
return 'now()';
244+
}
245+
if (date.indexOf('now-') >= 0 && date.indexOf('/') === -1) {
246+
return date.replace('now', 'now()').replace('-', ' - ');
247+
}
248+
date = dateMath.parse(date, roundUp);
249+
}
250+
return (date.valueOf() / 1000).toFixed(0);
251+
}
252+
253+
return PrometheusDatasource;
254+
});
255+
256+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
define([
2+
'angular',
3+
],
4+
function (angular) {
5+
'use strict';
6+
7+
var module = angular.module('grafana.directives');
8+
9+
module.directive('metricQueryEditorPrometheus', function() {
10+
return {controller: 'PrometheusQueryCtrl', templateUrl: 'app/plugins/datasource/prometheus/partials/query.editor.html'};
11+
});
12+
13+
});
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<div ng-include="httpConfigPartialSrc"></div>
2+
3+
<br>
4+

0 commit comments

Comments
 (0)