Commit 63b50ab9 by Torkel Ödegaard

Merge branch 'master' into external-plugins

parents 69daede5 c0beef75
......@@ -8,7 +8,7 @@ page_keywords: grafana, performance, documentation
## Graphite
Graphite 0.9.13 adds a much needed feature to the JSON rendering API
Graphite 0.9.14 adds a much needed feature to the JSON rendering API
that is very important for Grafana. If you are experiencing slow load &
rendering times for large time ranges then it is most likely caused by
running Graphite 0.9.12 or lower.
......@@ -17,6 +17,6 @@ The latest version of Graphite adds a `maxDataPoints` parameter to the
JSON render API. Without this feature Graphite can return hundreds of
thousands of data points per graph, which can hang your browser. Be sure
to upgrade to
[0.9.13](http://graphite.readthedocs.org/en/latest/releases/0_9_13.html).
[0.9.14](http://graphite.readthedocs.org/en/latest/releases/0_9_14.html).
......@@ -101,6 +101,7 @@ class TimeSeries {
var nullAsZero = fillStyle === 'null as zero';
var currentTime;
var currentValue;
var nonNulls = 0;
for (var i = 0; i < this.datapoints.length; i++) {
currentValue = this.datapoints[i][0];
......@@ -117,6 +118,7 @@ class TimeSeries {
if (_.isNumber(currentValue)) {
this.stats.total += currentValue;
this.allIsNull = false;
nonNulls++;
}
if (currentValue > this.stats.max) {
......@@ -139,7 +141,7 @@ class TimeSeries {
if (this.stats.min === Number.MAX_VALUE) { this.stats.min = null; }
if (result.length) {
this.stats.avg = (this.stats.total / result.length);
this.stats.avg = (this.stats.total / nonNulls);
this.stats.current = result[result.length-1][1];
if (this.stats.current === null && result.length > 1) {
this.stats.current = result[result.length-2][1];
......
......@@ -96,6 +96,13 @@ function (angular, _) {
}
};
$scope.duplicate = function(variable) {
$scope.current = angular.copy(variable);
$scope.variables.push($scope.current);
$scope.current.name = 'copy_of_'+variable.name;
$scope.updateSubmenuVisibility();
};
$scope.update = function() {
if ($scope.isValid()) {
$scope.runQuery().then(function() {
......
......@@ -39,7 +39,7 @@
<div ng-if="mode === 'list'">
<div class="editor-row row">
<div class="span8">
<div style="max-width: 1024px">
<div ng-if="variables.length === 0">
<em>No template variables defined</em>
</div>
......@@ -59,6 +59,11 @@
Edit
</a>
</td>
<td style="width: 1%">
<a ng-click="duplicate(variable)" class="btn btn-inverse btn-small">
Duplicate
</a>
</td>
<td style="width: 1%"><i ng-click="_.move(variables,$index,$index-1)" ng-hide="$first" class="pointer fa fa-arrow-up"></i></td>
<td style="width: 1%"><i ng-click="_.move(variables,$index,$index+1)" ng-hide="$last" class="pointer fa fa-arrow-down"></i></td>
<td style="width: 1%">
......
......@@ -263,7 +263,7 @@ function (angular, _) {
})
.each(function(dp) {
var timestamp = new Date(dp.Timestamp).getTime();
if (lastTimestamp && (timestamp - lastTimestamp) > periodMs * 2) {
if (lastTimestamp && (timestamp - lastTimestamp) > periodMs) {
dps.push([null, lastTimestamp + periodMs]);
}
lastTimestamp = timestamp;
......
......@@ -55,7 +55,7 @@ describe('CloudWatchDatasource', function() {
},
{
Average: 5,
Timestamp: 'Wed Dec 31 1969 16:20:00 GMT-0800 (PST)'
Timestamp: 'Wed Dec 31 1969 16:15:00 GMT-0800 (PST)'
}
],
Label: 'CPUUtilization'
......
......@@ -10,7 +10,7 @@ function (angular) {
ElasticQueryBuilder.prototype.getRangeFilter = function() {
var filter = {};
filter[this.timeField] = {"gte": "$timeFrom", "lte": "$timeTo"};
filter[this.timeField] = {"gte": "$timeFrom", "lte": "$timeTo", "format": "epoch_millis"};
return filter;
};
......@@ -127,6 +127,7 @@ function (angular) {
"interval": this.getInterval(aggDef),
"field": this.timeField,
"min_doc_count": 0,
"format": "epoch_millis",
"extended_bounds": { "min": "$timeFrom", "max": "$timeTo" }
};
break;
......
......@@ -48,6 +48,7 @@ function (angular, _, moment, dateMath) {
var end = getPrometheusTime(options.range.to, true);
var queries = [];
options = _.clone(options);
_.each(options.targets, _.bind(function(target) {
if (!target.expr || target.hide) {
return;
......@@ -58,7 +59,13 @@ function (angular, _, moment, dateMath) {
var interval = target.interval || options.interval;
var intervalFactor = target.intervalFactor || 1;
query.step = this.calculateInterval(interval, intervalFactor);
target.step = query.step = this.calculateInterval(interval, intervalFactor);
var range = Math.ceil(end - start);
// Prometheus drop query if range/step > 11000
// calibrate step if it is too big
if (query.step !== 0 && range / query.step > 11000) {
target.step = query.step = Math.ceil(range / 11000);
}
queries.push(query);
}, this));
......@@ -96,17 +103,7 @@ function (angular, _, moment, dateMath) {
};
PrometheusDatasource.prototype.performTimeSeriesQuery = function(query, start, end) {
var url = '/api/v1/query_range?query=' + encodeURIComponent(query.expr) + '&start=' + start + '&end=' + end;
var step = query.step;
var range = Math.ceil(end - start);
// Prometheus drop query if range/step > 11000
// calibrate step if it is too big
if (step !== 0 && range / step > 11000) {
step = Math.ceil(range / 11000);
}
url += '&step=' + step;
var url = '/api/v1/query_range?query=' + encodeURIComponent(query.expr) + '&start=' + start + '&end=' + end + '&step=' + query.step;
return this._request('GET', url);
};
......@@ -212,7 +209,7 @@ function (angular, _, moment, dateMath) {
sec = 1;
}
return Math.ceil(sec * intervalFactor) + 's';
return Math.ceil(sec * intervalFactor);
};
function transformMetricData(md, options) {
......@@ -221,8 +218,20 @@ function (angular, _, moment, dateMath) {
metricLabel = createMetricLabel(md.metric, options);
dps = _.map(md.values, function(value) {
return [parseFloat(value[1]), value[0] * 1000];
var stepMs = parseInt(options.step) * 1000;
var lastTimestamp = null;
_.each(md.values, function(value) {
var dp_value = parseFloat(value[1]);
if (_.isNaN(dp_value)) {
dp_value = null;
}
var timestamp = value[0] * 1000;
if (lastTimestamp && (timestamp - lastTimestamp) > stepMs) {
dps.push([null, lastTimestamp + stepMs]);
}
lastTimestamp = timestamp;
dps.push([dp_value, timestamp]);
});
return { target: metricLabel, datapoints: dps };
......
......@@ -19,7 +19,7 @@ describe('PrometheusDatasource', function() {
var results;
var urlExpected = 'proxied/api/v1/query_range?query=' +
encodeURIComponent('test{job="testjob"}') +
'&start=1443438675&end=1443460275&step=60s';
'&start=1443438675&end=1443460275&step=60';
var query = {
range: { from: moment(1443438674760), to: moment(1443460274760) },
targets: [{ expr: 'test{job="testjob"}' }],
......
......@@ -43,6 +43,17 @@ define([
expect(series.stats.max).to.be(-4);
});
it('average value should ignore nulls', function() {
series = new TimeSeries(testData);
series.getFlotPairs('null', yAxisFormats);
expect(series.stats.avg).to.be(6.333333333333333);
});
it('with null as zero style, average value should treat nulls as 0', function() {
series = new TimeSeries(testData);
series.getFlotPairs('null as zero', yAxisFormats);
expect(series.stats.avg).to.be(4.75);
});
});
describe('series overrides', function() {
......
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