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 @@ -
+
-
+
+
+
+
+
    +
  • + +
  • +
  • + group by time +
  • +
  • + +
  • +
  • + +
  • +
+
+
+ + +
+
+
-
-
- - 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'); + }); + + }); + });