diff --git a/src/app/components/kbn.js b/src/app/components/kbn.js
index b18ad567b54..35e5c2f67a7 100644
--- a/src/app/components/kbn.js
+++ b/src/app/components/kbn.js
@@ -8,25 +8,6 @@ function($, _, moment) {
var kbn = {};
- /**
- * Calculate a graph interval
- *
- * from:: Date object containing the start time
- * to:: Date object containing the finish time
- * size:: Calculate to approximately this many bars
- * user_interval:: User specified histogram interval
- *
- */
- kbn.calculate_interval = function(from,to,size,user_interval) {
- if(_.isObject(from)) {
- from = from.valueOf();
- }
- if(_.isObject(to)) {
- to = to.valueOf();
- }
- return user_interval === 0 ? kbn.round_interval((to - from)/size) : user_interval;
- };
-
kbn.round_interval = function(interval) {
switch (true) {
// 0.5s
@@ -131,6 +112,28 @@ function($, _, moment) {
s: 1
};
+ kbn.calculateInterval = function(range, resolution, userInterval) {
+ var lowLimitMs = 1; // 1 millisecond default low limit
+ var intervalMs, lowLimitInterval;
+
+ if (userInterval) {
+ if (userInterval[0] === '>') {
+ lowLimitInterval = userInterval.slice(1);
+ lowLimitMs = kbn.interval_to_ms(lowLimitInterval);
+ }
+ else {
+ return userInterval;
+ }
+ }
+
+ intervalMs = kbn.round_interval((range.to.valueOf() - range.from.valueOf()) / resolution);
+ if (lowLimitMs > intervalMs) {
+ intervalMs = lowLimitMs;
+ }
+
+ return kbn.secondsToHms(intervalMs / 1000);
+ };
+
kbn.describe_interval = function (string) {
var matches = string.match(kbn.interval_regex);
if (!matches || !_.has(kbn.intervals_in_seconds, matches[2])) {
diff --git a/src/app/controllers/influxTargetCtrl.js b/src/app/controllers/influxTargetCtrl.js
index 6b27be78a0c..b8101ab9577 100644
--- a/src/app/controllers/influxTargetCtrl.js
+++ b/src/app/controllers/influxTargetCtrl.js
@@ -16,13 +16,19 @@ function (angular) {
target.function = target.function || 'mean';
target.column = target.column || 'value';
+ // backward compatible correction of schema
if (target.condition_value) {
- target.condition_expression = target.condition_key + ' ' + target.condition_op + ' ' + target.condition_value;
+ target.condition = target.condition_key + ' ' + target.condition_op + ' ' + target.condition_value;
delete target.condition_key;
delete target.condition_op;
delete target.condition_value;
}
+ if (target.groupby_field_add === false) {
+ target.groupby_field = '';
+ delete target.groupby_field_add;
+ }
+
$scope.rawQuery = false;
$scope.functions = [
diff --git a/src/app/panels/graph/module.js b/src/app/panels/graph/module.js
index 09866d055b6..aa6667ee154 100644
--- a/src/app/panels/graph/module.js
+++ b/src/app/panels/graph/module.js
@@ -182,13 +182,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
$scope.range = timeSrv.timeRange();
$scope.rangeUnparsed = timeSrv.timeRange(false);
$scope.resolution = Math.ceil($(window).width() * ($scope.panel.span / 12));
- $scope.interval = '10m';
-
- if ($scope.range) {
- $scope.interval = kbn.secondsToHms(
- kbn.calculate_interval($scope.range.from, $scope.range.to, $scope.resolution, 0) / 1000
- );
- }
+ $scope.interval = kbn.calculateInterval($scope.range, $scope.resolution, $scope.panel.interval);
};
$scope.get_data = function() {
@@ -355,6 +349,14 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
$scope.render();
};
+ $scope.toggleEditorHelp = function(index) {
+ if ($scope.editorHelpIndex === index) {
+ $scope.editorHelpIndex = null;
+ return;
+ }
+ $scope.editorHelpIndex = index;
+ };
+
panelSrv.init($scope);
});
diff --git a/src/app/partials/influxdb/editor.html b/src/app/partials/influxdb/editor.html
index 995ce612d21..afe7b59d503 100644
--- a/src/app/partials/influxdb/editor.html
+++ b/src/app/partials/influxdb/editor.html
@@ -1,4 +1,4 @@
-
+
-
+
-
-
+
@@ -53,13 +51,12 @@
ng-model-onblur
ng-blur="get_data()">
-
+
+
+
-
-
-
- alias patterns:
-
-
- - $s = series name
- - $g = group by
- - $[0-9] part of series name for series names seperated by dots.
-
-
+
+
+
+ Alias patterns:
+
+ - $s = series name
+ - $g = group by
+ - $[0-9] part of series name for series names seperated by dots.
+
+
+
+
+ Stacking and fill:
+
+ - When stacking is enabled it important that points align
+ - If there are missing points for one series it can cause gaps or missing bars
+ - You must use fill(0), and select a group by time low limit
+ - Use the group by time option below your queries and specify for example >10s if your metrics are written every 10 seconds
+ - This will insert zeros for series that are missing measurements and will make stacking work properly
+
+
+
+
+ Group by time:
+
+ - Group by time is important, otherwise the query could return many thousands of datapoints that will slow down Grafana
+ - Leave the group by time field empty for each query and it will be calculated based on time range and pixel width of the graph
+ - If you use fill(0) or fill(null) set a low limit for the auto group by time interval
+ - The low limit can only be set in the group by time option below your queries
+ - You set a low limit by adding a greater sign before the interval
+ - Example: >60s if you write metrics to InfluxDB every 60 seconds
+
+
+
+
+
+
diff --git a/src/app/services/influxdb/influxQueryBuilder.js b/src/app/services/influxdb/influxQueryBuilder.js
index af5022ff897..a72f18fdd4a 100644
--- a/src/app/services/influxdb/influxQueryBuilder.js
+++ b/src/app/services/influxdb/influxQueryBuilder.js
@@ -22,24 +22,28 @@ function () {
seriesName = '"' + seriesName+ '"';
}
- if (target.groupby_field_add) {
+ if (target.groupby_field) {
query += target.groupby_field + ', ';
}
query += target.function + '(' + target.column + ')';
query += ' from ' + seriesName + ' where [[$timeFilter]]';
- if (target.condition_filter) {
- query += ' and ' + target.condition_expression;
+ if (target.condition) {
+ query += ' and ' + target.condition;
}
query += ' group by time([[$interval]])';
- if (target.groupby_field_add) {
+ if (target.groupby_field) {
query += ', ' + target.groupby_field;
this.groupByField = target.groupby_field;
}
+ if (target.fill) {
+ query += ' fill(' + target.fill + ')';
+ }
+
query += " order asc";
target.query = query;
diff --git a/src/app/services/influxdb/influxdbDatasource.js b/src/app/services/influxdb/influxdbDatasource.js
index d2a375521a5..ce54f9b172a 100644
--- a/src/app/services/influxdb/influxdbDatasource.js
+++ b/src/app/services/influxdb/influxdbDatasource.js
@@ -61,6 +61,10 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
});
};
+ InfluxDatasource.prototype._getGroupByTimeInterval = function(target, options) {
+ return target.interval || options.interval;
+ };
+
InfluxDatasource.prototype.annotationQuery = function(annotation, rangeUnparsed) {
var timeFilter = getTimeFilter({ range: rangeUnparsed });
var query = _.template(annotation.query, { timeFilter: timeFilter, "$timeFilter": timeFilter }, this.templateSettings);
diff --git a/src/css/less/grafana.less b/src/css/less/grafana.less
index a410da93d85..5f0ba02e9af 100644
--- a/src/css/less/grafana.less
+++ b/src/css/less/grafana.less
@@ -135,7 +135,6 @@
list-style: none;
margin: 0;
margin-right: 90px;
- margin-left: 30px;
>li {
float: left;
}
@@ -143,9 +142,6 @@
.grafana-metric-options {
margin-top: 35px;
- .grafana-segment-list {
- margin-left: 0;
- }
}
// fix for fixed positioned panel & scrolling
@@ -183,6 +179,15 @@
&.annotation-segment {
padding: 8px 15px;
}
+
+}
+
+.grafana-target-segment-icon {
+ i {
+ width: 15px;
+ text-align: center;
+ display: inline-block;
+ }
}
.grafana-target-function {
@@ -208,15 +213,6 @@ input[type=text].grafana-function-param-input {
padding: 0;
}
-.grafana-target-controls-left {
- list-style: none;
- float: left;
- margin: 0px;
- li {
- display: inline-block;
- }
-}
-
.grafana-target-controls {
float: right;
list-style: none;
diff --git a/src/test/specs/graph-ctrl-specs.js b/src/test/specs/graph-ctrl-specs.js
index c01353feb62..e1ddcbc571e 100644
--- a/src/test/specs/graph-ctrl-specs.js
+++ b/src/test/specs/graph-ctrl-specs.js
@@ -36,8 +36,8 @@ define([
var data = ctx.scope.render.getCall(0).args[0];
expect(data.length).to.be(2);
});
-
});
+
});
});
diff --git a/src/test/specs/influxQueryBuilder-specs.js b/src/test/specs/influxQueryBuilder-specs.js
index f5218187bdc..3003db956a0 100644
--- a/src/test/specs/influxQueryBuilder-specs.js
+++ b/src/test/specs/influxQueryBuilder-specs.js
@@ -10,9 +10,7 @@ define([
series: 'google.test',
column: 'value',
function: 'mean',
- condition_filter: true,
- condition_expression: "code=1",
- groupby_field_add: true,
+ condition: "code=1",
groupby_field: 'code'
});
@@ -29,6 +27,23 @@ define([
});
+ describe('series with fill and minimum group by time', function() {
+ var builder = new InfluxQueryBuilder({
+ series: 'google.test',
+ column: 'value',
+ function: 'mean',
+ fill: '0',
+ });
+
+ var query = builder.build();
+
+ it('should generate correct query', function() {
+ expect(query).to.be('select mean(value) from "google.test" where [[$timeFilter]] ' +
+ 'group by time([[$interval]]) fill(0) order asc');
+ });
+
+ });
+
describe('old style raw query', function() {
var builder = new InfluxQueryBuilder({
query: 'select host, mean(value) from asd.asd where time > now() - 1h group by time(1s), code order asc',
diff --git a/src/test/specs/influxdb-datasource-specs.js b/src/test/specs/influxdb-datasource-specs.js
index 9b531065115..7bbbe72a22d 100644
--- a/src/test/specs/influxdb-datasource-specs.js
+++ b/src/test/specs/influxdb-datasource-specs.js
@@ -10,6 +10,9 @@ define([
beforeEach(module('grafana.services'));
beforeEach(ctx.providePhase());
beforeEach(ctx.createService('InfluxDatasource'));
+ beforeEach(function() {
+ ctx.ds = new ctx.service({ urls: [''], user: 'test', password: 'mupp' });
+ });
describe('When querying influxdb with one target using query editor target spec', function() {
var results;
@@ -28,10 +31,8 @@ define([
}];
beforeEach(function() {
- var ds = new ctx.service({ urls: [''], user: 'test', password: 'mupp' });
-
ctx.$httpBackend.expect('GET', urlExpected).respond(response);
- ds.query(query).then(function(data) { results = data; });
+ ctx.ds.query(query).then(function(data) { results = data; });
ctx.$httpBackend.flush();
});
@@ -58,10 +59,8 @@ define([
var response = [];
beforeEach(function() {
- var ds = new ctx.service({ urls: [''], user: 'test', password: 'mupp' });
-
ctx.$httpBackend.expect('GET', urlExpected).respond(response);
- ds.query(query).then(function(data) { results = data; });
+ ctx.ds.query(query).then(function(data) { results = data; });
ctx.$httpBackend.flush();
});
@@ -71,6 +70,18 @@ define([
});
+ describe('When calculating group by time interval', function() {
+ it('if blank should use auto interval', function() {
+ var result = ctx.ds._getGroupByTimeInterval({}, { interval:'0.1s' });
+ expect(result).to.be('0.1s');
+ });
+
+ it('if target interval specified should use that interval', function() {
+ var result = ctx.ds._getGroupByTimeInterval({interval: '10s'}, { interval:'0.1s' });
+ expect(result).to.be('10s');
+ });
+
+ });
});
});
diff --git a/src/test/specs/kbn-format-specs.js b/src/test/specs/kbn-format-specs.js
index 0faa4728f2d..a44475556b0 100644
--- a/src/test/specs/kbn-format-specs.js
+++ b/src/test/specs/kbn-format-specs.js
@@ -41,7 +41,6 @@ define([
});
describe('nanosecond formatting', function () {
-
it('should translate 25 to 25 ns', function () {
var str = kbn.nanosFormat(25, 2);
expect(str).to.be("25 ns");
@@ -68,4 +67,38 @@ define([
});
});
+
+ describe('calculateInterval', function() {
+ it('1h 100 resultion', function() {
+ var range = { from: kbn.parseDate('now-1h'), to: kbn.parseDate('now') };
+ var str = kbn.calculateInterval(range, 100, null);
+ expect(str).to.be('30s');
+ });
+
+ it('10m 1600 resolution', function() {
+ var range = { from: kbn.parseDate('now-10m'), to: kbn.parseDate('now') };
+ var str = kbn.calculateInterval(range, 1600, null);
+ expect(str).to.be('0.1s');
+ });
+
+ it('fixed user interval', function() {
+ var range = { from: kbn.parseDate('now-10m'), to: kbn.parseDate('now') };
+ var str = kbn.calculateInterval(range, 1600, '10s');
+ expect(str).to.be('10s');
+ });
+
+ it('short time range and user low limit', function() {
+ var range = { from: kbn.parseDate('now-10m'), to: kbn.parseDate('now') };
+ var str = kbn.calculateInterval(range, 1600, '>10s');
+ expect(str).to.be('10s');
+ });
+
+ it('large time range and user low limit', function() {
+ var range = { from: kbn.parseDate('now-14d'), to: kbn.parseDate('now') };
+ var str = kbn.calculateInterval(range, 1000, '>10s');
+ expect(str).to.be('30m');
+ });
+
+ });
+
});