diff --git a/docs/sources/installation/performance.md b/docs/sources/installation/performance.md index 51ab747bcd2..f09686687ca 100644 --- a/docs/sources/installation/performance.md +++ b/docs/sources/installation/performance.md @@ -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). diff --git a/public/app/core/time_series.ts b/public/app/core/time_series.ts index 282af99e33a..0fb2b83b7e0 100644 --- a/public/app/core/time_series.ts +++ b/public/app/core/time_series.ts @@ -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]; diff --git a/public/app/features/templating/editorCtrl.js b/public/app/features/templating/editorCtrl.js index 2f30129eedb..852de52655d 100644 --- a/public/app/features/templating/editorCtrl.js +++ b/public/app/features/templating/editorCtrl.js @@ -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() { diff --git a/public/app/features/templating/partials/editor.html b/public/app/features/templating/partials/editor.html index 133886971bd..484495b3951 100644 --- a/public/app/features/templating/partials/editor.html +++ b/public/app/features/templating/partials/editor.html @@ -39,7 +39,7 @@
-
+
No template variables defined
@@ -59,6 +59,11 @@ Edit + + + Duplicate + + diff --git a/public/app/plugins/datasource/cloudwatch/datasource.js b/public/app/plugins/datasource/cloudwatch/datasource.js index 91b00f8f84a..79f4a6a00c7 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.js +++ b/public/app/plugins/datasource/cloudwatch/datasource.js @@ -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; diff --git a/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts b/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts index 95526e38489..39d48e273b3 100644 --- a/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts @@ -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' diff --git a/public/app/plugins/datasource/elasticsearch/query_builder.js b/public/app/plugins/datasource/elasticsearch/query_builder.js index 129c52d3f02..34156397ed0 100644 --- a/public/app/plugins/datasource/elasticsearch/query_builder.js +++ b/public/app/plugins/datasource/elasticsearch/query_builder.js @@ -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; diff --git a/public/app/plugins/datasource/prometheus/datasource.js b/public/app/plugins/datasource/prometheus/datasource.js index c86fa2806d3..2c9279cee96 100644 --- a/public/app/plugins/datasource/prometheus/datasource.js +++ b/public/app/plugins/datasource/prometheus/datasource.js @@ -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 }; diff --git a/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts b/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts index 2ac4992edef..58034d09cac 100644 --- a/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts @@ -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"}' }], diff --git a/public/test/core/time_series_specs.js b/public/test/core/time_series_specs.js index 22d85775a05..5d6950ad2b6 100644 --- a/public/test/core/time_series_specs.js +++ b/public/test/core/time_series_specs.js @@ -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() {