From 84c6caabc5b06a8fdb342714e3fed2cd5adc6774 Mon Sep 17 00:00:00 2001 From: Alin Sinpalean Date: Mon, 28 Aug 2017 15:45:51 +0200 Subject: [PATCH 1/6] Prometheus: Fix actual step computation logic when a min_step is specified and the range is longer than min_step * 11000. --- public/app/plugins/datasource/prometheus/datasource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index 36fc1e5de4b..9e416de44c7 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -128,7 +128,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS // Prometheus drop query if range/step > 11000 // calibrate step if it is too big if (step !== 0 && range / step > 11000) { - return Math.ceil(range / 11000); + step = Math.ceil(range / 11000); } return Math.max(step, autoStep); }; From 13eb0c1ece082bb2db7c791ba71eb5f450d7118a Mon Sep 17 00:00:00 2001 From: Alin Sinpalean Date: Thu, 7 Sep 2017 22:14:37 +0200 Subject: [PATCH 2/6] Fix kbn.round_interval for exact intervals. --- public/app/core/utils/kbn.js | 58 ++++++++++++++--------------- public/test/core/utils/kbn_specs.js | 14 +++++++ 2 files changed, 43 insertions(+), 29 deletions(-) diff --git a/public/app/core/utils/kbn.js b/public/app/core/utils/kbn.js index 37888fb10ac..670051e57d1 100644 --- a/public/app/core/utils/kbn.js +++ b/public/app/core/utils/kbn.js @@ -17,88 +17,88 @@ function($, _) { kbn.round_interval = function(interval) { switch (true) { // 0.015s - case (interval <= 15): + case (interval < 15): return 10; // 0.01s // 0.035s - case (interval <= 35): + case (interval < 35): return 20; // 0.02s // 0.075s - case (interval <= 75): + case (interval < 75): return 50; // 0.05s // 0.15s - case (interval <= 150): + case (interval < 150): return 100; // 0.1s // 0.35s - case (interval <= 350): + case (interval < 350): return 200; // 0.2s // 0.75s - case (interval <= 750): + case (interval < 750): return 500; // 0.5s // 1.5s - case (interval <= 1500): + case (interval < 1500): return 1000; // 1s // 3.5s - case (interval <= 3500): + case (interval < 3500): return 2000; // 2s // 7.5s - case (interval <= 7500): + case (interval < 7500): return 5000; // 5s // 12.5s - case (interval <= 12500): + case (interval < 12500): return 10000; // 10s // 17.5s - case (interval <= 17500): + case (interval < 17500): return 15000; // 15s // 25s - case (interval <= 25000): + case (interval < 25000): return 20000; // 20s // 45s - case (interval <= 45000): + case (interval < 45000): return 30000; // 30s // 1.5m - case (interval <= 90000): + case (interval < 90000): return 60000; // 1m // 3.5m - case (interval <= 210000): + case (interval < 210000): return 120000; // 2m // 7.5m - case (interval <= 450000): + case (interval < 450000): return 300000; // 5m // 12.5m - case (interval <= 750000): + case (interval < 750000): return 600000; // 10m // 12.5m - case (interval <= 1050000): + case (interval < 1050000): return 900000; // 15m // 25m - case (interval <= 1500000): + case (interval < 1500000): return 1200000; // 20m // 45m - case (interval <= 2700000): + case (interval < 2700000): return 1800000; // 30m // 1.5h - case (interval <= 5400000): + case (interval < 5400000): return 3600000; // 1h // 2.5h - case (interval <= 9000000): + case (interval < 9000000): return 7200000; // 2h // 4.5h - case (interval <= 16200000): + case (interval < 16200000): return 10800000; // 3h // 9h - case (interval <= 32400000): + case (interval < 32400000): return 21600000; // 6h // 24h - case (interval <= 86400000): + case (interval < 86400000): return 43200000; // 12h // 48h - case (interval <= 172800000): + case (interval < 172800000): return 86400000; // 24h // 1w - case (interval <= 604800000): + case (interval < 604800000): return 86400000; // 24h // 3w - case (interval <= 1814400000): + case (interval < 1814400000): return 604800000; // 1w // 2y case (interval < 3628800000): @@ -134,7 +134,7 @@ function($, _) { return nummilliseconds + 'ms'; } - return 'less then a millisecond'; //'just now' //or other string you like; + return 'less than a millisecond'; //'just now' //or other string you like; }; kbn.to_percent = function(number,outof) { diff --git a/public/test/core/utils/kbn_specs.js b/public/test/core/utils/kbn_specs.js index 0b7f4b4f2bc..31fb25cb578 100644 --- a/public/test/core/utils/kbn_specs.js +++ b/public/test/core/utils/kbn_specs.js @@ -167,6 +167,20 @@ define([ var res = kbn.calculateInterval(range, 900, '>15ms'); expect(res.interval).to.be('15ms'); }); + + it('1d 1 resolution', function() { + var range = { from: dateMath.parse('now-1d'), to: dateMath.parse('now') }; + var res = kbn.calculateInterval(range, 1, null); + expect(res.interval).to.be('1d'); + expect(res.intervalMs).to.be(86400000); + }); + + it('86399s 1 resolution', function() { + var range = { from: dateMath.parse('now-86399s'), to: dateMath.parse('now') }; + var res = kbn.calculateInterval(range, 1, null); + expect(res.interval).to.be('12h'); + expect(res.intervalMs).to.be(43200000); + }); }); describe('hex', function() { From 2a62374a61e8e10d1141914038325eb8e856e46b Mon Sep 17 00:00:00 2001 From: Alin Sinpalean Date: Tue, 12 Sep 2017 16:36:27 +0200 Subject: [PATCH 3/6] Prometheus: Rework the interaction between auto interval (computed based on graph resolution), min interval (where specified, per query) and intervalFactor (AKA resolution, where specified, per query). As a bonus, have and reflect the actual interval (not the auto interval), taking into account min interval and Prometheus' 11k data points limit. --- .../datasource/prometheus/datasource.ts | 40 ++- .../prometheus/specs/datasource_specs.ts | 262 ++++++++++++++++++ 2 files changed, 290 insertions(+), 12 deletions(-) diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index dd071d09fe6..7e0758a253c 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -98,14 +98,29 @@ export class PrometheusDatasource { activeTargets.push(target); var query: any = {}; - query.expr = this.templateSrv.replace(target.expr, options.scopedVars, self.interpolateQueryExpr); - query.requestId = options.panelId + target.refId; - var interval = this.templateSrv.replace(target.interval, options.scopedVars) || options.interval; + var interval = this.intervalSeconds(options.interval); + // Minimum interval ("Min step"), if specified for the query. or same as interval otherwise + var minInterval = this.intervalSeconds(this.templateSrv.replace(target.interval, options.scopedVars) || options.interval); var intervalFactor = target.intervalFactor || 1; - target.step = query.step = this.calculateInterval(interval, intervalFactor); var range = Math.ceil(end - start); - target.step = query.step = this.adjustStep(query.step, this.intervalSeconds(options.interval), range); + // Adjust the interval to take into account any specified minimum plus Prometheus limitations + var adjustedInterval = this.adjustInterval(interval, minInterval, range, intervalFactor); + + var scopedVars = options.scopedVars; + // If the interval was adjusted, make a shallow copy of scopedVars with updated interval vars + if (interval !== adjustedInterval) { + interval = adjustedInterval; + scopedVars = Object.assign({}, options.scopedVars, { + "__interval": {text: interval + "s", value: interval + "s"}, + "__interval_ms": {text: interval * 1000, value: interval * 1000}, + }); + } + target.step = query.step = interval * intervalFactor; + + // Only replace vars in expression after having (possibly) updated interval vars + query.expr = this.templateSrv.replace(target.expr, scopedVars, self.interpolateQueryExpr); + query.requestId = options.panelId + target.refId; queries.push(query); } @@ -140,13 +155,14 @@ export class PrometheusDatasource { }); } - adjustStep(step, autoStep, range) { - // 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); + adjustInterval(interval, minInterval, range, intervalFactor) { + interval = Math.max(interval, minInterval); + // Prometheus will drop queries that might return more than 11000 data points. + // Calibrate interval if it is too small. + if (interval !== 0 && range / intervalFactor / interval > 11000) { + interval = Math.ceil(range / intervalFactor / 11000); } - return Math.max(step, autoStep); + return interval; } performTimeSeriesQuery(query, start, end) { @@ -206,7 +222,7 @@ export class PrometheusDatasource { var end = this.getPrometheusTime(options.range.to, true); var query = { expr: interpolated, - step: this.adjustStep(kbn.interval_to_seconds(step), 0, Math.ceil(end - start)) + 's' + step: this.adjustInterval(kbn.interval_to_seconds(step), 0, Math.ceil(end - start), 1) + 's' }; var self = this; diff --git a/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts b/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts index 9588090c7bb..1e6fff2607f 100644 --- a/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts @@ -195,4 +195,266 @@ describe('PrometheusDatasource', function() { ); }); }); + describe('The "step" query parameter', function() { + var response = { + status: "success", + data: { + resultType: "matrix", + result: [] + } + }; + + beforeEach(function() { + ctx.$httpBackend.flush(); + }); + + it('should be min interval when greater than auto interval', function() { + var query = { + range: { from: moment(1443438674760), to: moment(1443460274760) }, + targets: [{ + expr: 'test', + interval: '10s' + }], + interval: '5s' + }; + var urlExpected = 'proxied/api/v1/query_range?query=test' + + '&start=1443438675&end=1443460275&step=10'; + ctx.$httpBackend.expect('GET', urlExpected).respond(response); + ctx.ds.query(query); + ctx.$httpBackend.verifyNoOutstandingExpectation(); + }); + it('should be auto interval when greater than min interval', function() { + var query = { + range: { from: moment(1443438674760), to: moment(1443460274760) }, + targets: [{ + expr: 'test', + interval: '5s' + }], + interval: '10s' + }; + var urlExpected = 'proxied/api/v1/query_range?query=test' + + '&start=1443438675&end=1443460275&step=10'; + ctx.$httpBackend.expect('GET', urlExpected).respond(response); + ctx.ds.query(query); + ctx.$httpBackend.verifyNoOutstandingExpectation(); + }); + it('should result in querying fewer than 11000 data points', function() { + var query = { + range: { from: moment(1443438674760), to: moment(1443460274760) }, + targets: [{ expr: 'test' }], + interval: '1s' + }; + var urlExpected = 'proxied/api/v1/query_range?query=test' + + '&start=1443438675&end=1443460275&step=2'; + ctx.$httpBackend.expect('GET', urlExpected).respond(response); + ctx.ds.query(query); + ctx.$httpBackend.verifyNoOutstandingExpectation(); + }); + it('should apply intervalFactor to min interval when greater', function() { + var query = { + range: { from: moment(1443438674760), to: moment(1443460274760) }, + targets: [{ + expr: 'test', + interval: '10s', + intervalFactor: 10 + }], + interval: '5s' + }; + var urlExpected = 'proxied/api/v1/query_range?query=test' + + '&start=1443438675&end=1443460275&step=100'; + ctx.$httpBackend.expect('GET', urlExpected).respond(response); + ctx.ds.query(query); + ctx.$httpBackend.verifyNoOutstandingExpectation(); + }); + it('should apply intervalFactor to auto interval when greater', function() { + var query = { + range: { from: moment(1443438674760), to: moment(1443460274760) }, + targets: [{ + expr: 'test', + interval: '5s', + intervalFactor: 10 + }], + interval: '10s' + }; + var urlExpected = 'proxied/api/v1/query_range?query=test' + + '&start=1443438675&end=1443460275&step=100'; + ctx.$httpBackend.expect('GET', urlExpected).respond(response); + ctx.ds.query(query); + ctx.$httpBackend.verifyNoOutstandingExpectation(); + }); + it('should not not be affected by the 11000 data points limit when large enough', function() { + var query = { + // 1 week range + range: { from: moment(1443438674760), to: moment(1444043474760) }, + targets: [{ + expr: 'test', + intervalFactor: 10 + }], + interval: '10s' + }; + var urlExpected = 'proxied/api/v1/query_range?query=test' + + '&start=1443438675&end=1444043475&step=100'; + ctx.$httpBackend.expect('GET', urlExpected).respond(response); + ctx.ds.query(query); + ctx.$httpBackend.verifyNoOutstandingExpectation(); + }); + it('should be determined by the 11000 data points limit when too small', function() { + var query = { + // 1 week range + range: { from: moment(1443438674760), to: moment(1444043474760) }, + targets: [{ + expr: 'test', + intervalFactor: 10 + }], + interval: '5s' + }; + var urlExpected = 'proxied/api/v1/query_range?query=test' + + '&start=1443438675&end=1444043475&step=60'; + ctx.$httpBackend.expect('GET', urlExpected).respond(response); + ctx.ds.query(query); + ctx.$httpBackend.verifyNoOutstandingExpectation(); + }); + }); + describe('The __interval and __interval_ms template variables', function() { + var response = { + status: "success", + data: { + resultType: "matrix", + result: [] + } + }; + + beforeEach(function() { + ctx.$httpBackend.flush(); + }); + + it('should be unchanged when auto interval is greater than min interval', function() { + var query = { + range: { from: moment(1443438674760), to: moment(1443460274760) }, + targets: [{ + expr: 'rate(test[$__interval])', + interval: '5s' + }], + interval: '10s', + scopedVars: { + "__interval": {text: "10s", value: "10s"}, + "__interval_ms": {text: 10 * 1000, value: 10 * 1000}, + } + }; + var urlExpected = 'proxied/api/v1/query_range?query=' + + encodeURIComponent('rate(test[10s])') + + '&start=1443438675&end=1443460275&step=10'; + ctx.$httpBackend.expect('GET', urlExpected).respond(response); + ctx.ds.query(query); + ctx.$httpBackend.verifyNoOutstandingExpectation(); + + expect(query.scopedVars.__interval.text).to.be("10s"); + expect(query.scopedVars.__interval.value).to.be("10s"); + expect(query.scopedVars.__interval_ms.text).to.be(10 * 1000); + expect(query.scopedVars.__interval_ms.value).to.be(10 * 1000); + }); + it('should be min interval when it is greater than auto interval', function() { + var query = { + range: { from: moment(1443438674760), to: moment(1443460274760) }, + targets: [{ + expr: 'rate(test[$__interval])', + interval: '10s' + }], + interval: '5s', + scopedVars: { + "__interval": {text: "5s", value: "5s"}, + "__interval_ms": {text: 5 * 1000, value: 5 * 1000}, + } + }; + var urlExpected = 'proxied/api/v1/query_range?query=' + + encodeURIComponent('rate(test[10s])') + + '&start=1443438675&end=1443460275&step=10'; + ctx.$httpBackend.expect('GET', urlExpected).respond(response); + ctx.ds.query(query); + ctx.$httpBackend.verifyNoOutstandingExpectation(); + + expect(query.scopedVars.__interval.text).to.be("5s"); + expect(query.scopedVars.__interval.value).to.be("5s"); + expect(query.scopedVars.__interval_ms.text).to.be(5 * 1000); + expect(query.scopedVars.__interval_ms.value).to.be(5 * 1000); + }); + it('should ignore intervalFactor', function() { + var query = { + range: { from: moment(1443438674760), to: moment(1443460274760) }, + targets: [{ + expr: 'rate(test[$__interval])', + interval: '5s', + intervalFactor: 10 + }], + interval: '10s', + scopedVars: { + "__interval": {text: "10s", value: "10s"}, + "__interval_ms": {text: 10 * 1000, value: 10 * 1000}, + } + }; + var urlExpected = 'proxied/api/v1/query_range?query=' + + encodeURIComponent('rate(test[10s])') + + '&start=1443438675&end=1443460275&step=100'; + ctx.$httpBackend.expect('GET', urlExpected).respond(response); + ctx.ds.query(query); + ctx.$httpBackend.verifyNoOutstandingExpectation(); + + expect(query.scopedVars.__interval.text).to.be("10s"); + expect(query.scopedVars.__interval.value).to.be("10s"); + expect(query.scopedVars.__interval_ms.text).to.be(10 * 1000); + expect(query.scopedVars.__interval_ms.value).to.be(10 * 1000); + }); + it('should ignore intervalFactor', function() { + var query = { + range: { from: moment(1443438674760), to: moment(1443460274760) }, + targets: [{ + expr: 'rate(test[$__interval])', + interval: '10s', + intervalFactor: 10 + }], + interval: '5s', + scopedVars: { + "__interval": {text: "5s", value: "5s"}, + "__interval_ms": {text: 5 * 1000, value: 5 * 1000}, + } + }; + var urlExpected = 'proxied/api/v1/query_range?query=' + + encodeURIComponent('rate(test[10s])') + + '&start=1443438675&end=1443460275&step=100'; + ctx.$httpBackend.expect('GET', urlExpected).respond(response); + ctx.ds.query(query); + ctx.$httpBackend.verifyNoOutstandingExpectation(); + + expect(query.scopedVars.__interval.text).to.be("5s"); + expect(query.scopedVars.__interval.value).to.be("5s"); + expect(query.scopedVars.__interval_ms.text).to.be(5 * 1000); + expect(query.scopedVars.__interval_ms.value).to.be(5 * 1000); + }); + it('should be determined by the 11000 data points limit, accounting for intervalFactor', function() { + var query = { + // 1 week range + range: { from: moment(1443438674760), to: moment(1444043474760) }, + targets: [{ + expr: 'rate(test[$__interval])', + intervalFactor: 10 + }], + interval: '5s', + scopedVars: { + "__interval": {text: "5s", value: "5s"}, + "__interval_ms": {text: 5 * 1000, value: 5 * 1000}, + } + }; + var urlExpected = 'proxied/api/v1/query_range?query=' + + encodeURIComponent('rate(test[6s])') + + '&start=1443438675&end=1444043475&step=60'; + ctx.$httpBackend.expect('GET', urlExpected).respond(response); + ctx.ds.query(query); + ctx.$httpBackend.verifyNoOutstandingExpectation(); + + expect(query.scopedVars.__interval.text).to.be("5s"); + expect(query.scopedVars.__interval.value).to.be("5s"); + expect(query.scopedVars.__interval_ms.text).to.be(5 * 1000); + expect(query.scopedVars.__interval_ms.value).to.be(5 * 1000); + }); + }); }); From bb8849785a7c6f27759a26d9dbb1ac1ce2a5869b Mon Sep 17 00:00:00 2001 From: Alin Sinpalean Date: Wed, 27 Sep 2017 14:20:51 +0200 Subject: [PATCH 4/6] Have include intervalFactor in its calculation, so always equal to the step query parameter. --- .../datasource/prometheus/datasource.ts | 6 +- .../prometheus/specs/datasource_specs.ts | 69 ++++++++++++++++--- 2 files changed, 64 insertions(+), 11 deletions(-) diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index 85139336dd4..c0bb3079290 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -104,7 +104,7 @@ export class PrometheusDatasource { var minInterval = this.intervalSeconds(this.templateSrv.replace(target.interval, options.scopedVars) || options.interval); var intervalFactor = target.intervalFactor || 1; var range = Math.ceil(end - start); - // Adjust the interval to take into account any specified minimum plus Prometheus limitations + // Adjust the interval to take into account any specified minimum and interval factor plus Prometheus limits var adjustedInterval = this.adjustInterval(interval, minInterval, range, intervalFactor); var scopedVars = options.scopedVars; @@ -116,7 +116,7 @@ export class PrometheusDatasource { "__interval_ms": {text: interval * 1000, value: interval * 1000}, }); } - target.step = query.step = interval * intervalFactor; + target.step = query.step = interval; // Only replace vars in expression after having (possibly) updated interval vars query.expr = this.templateSrv.replace(target.expr, scopedVars, self.interpolateQueryExpr); @@ -163,12 +163,12 @@ export class PrometheusDatasource { } adjustInterval(interval, minInterval, range, intervalFactor) { - interval = Math.max(interval, minInterval); // Prometheus will drop queries that might return more than 11000 data points. // Calibrate interval if it is too small. if (interval !== 0 && range / intervalFactor / interval > 11000) { interval = Math.ceil(range / intervalFactor / 11000); } + interval = Math.max(interval * intervalFactor, minInterval); return interval; } diff --git a/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts b/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts index 0ee59ef1923..a6e7f480f88 100644 --- a/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts @@ -284,6 +284,7 @@ describe('PrometheusDatasource', function() { it('should be min interval when greater than auto interval', function() { var query = { + // 6 hour range range: { from: moment(1443438674760), to: moment(1443460274760) }, targets: [{ expr: 'test', @@ -299,6 +300,7 @@ describe('PrometheusDatasource', function() { }); it('should be auto interval when greater than min interval', function() { var query = { + // 6 hour range range: { from: moment(1443438674760), to: moment(1443460274760) }, targets: [{ expr: 'test', @@ -314,6 +316,7 @@ describe('PrometheusDatasource', function() { }); it('should result in querying fewer than 11000 data points', function() { var query = { + // 6 hour range range: { from: moment(1443438674760), to: moment(1443460274760) }, targets: [{ expr: 'test' }], interval: '1s' @@ -324,8 +327,9 @@ describe('PrometheusDatasource', function() { ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); }); - it('should apply intervalFactor to min interval when greater', function() { + it('should not apply min interval when interval * intervalFactor greater', function() { var query = { + // 6 hour range range: { from: moment(1443438674760), to: moment(1443460274760) }, targets: [{ expr: 'test', @@ -335,13 +339,31 @@ describe('PrometheusDatasource', function() { interval: '5s' }; var urlExpected = 'proxied/api/v1/query_range?query=test' + - '&start=1443438675&end=1443460275&step=100'; + '&start=1443438675&end=1443460275&step=50'; + ctx.$httpBackend.expect('GET', urlExpected).respond(response); + ctx.ds.query(query); + ctx.$httpBackend.verifyNoOutstandingExpectation(); + }); + it('should apply min interval when interval * intervalFactor smaller', function() { + var query = { + // 6 hour range + range: { from: moment(1443438674760), to: moment(1443460274760) }, + targets: [{ + expr: 'test', + interval: '15s', + intervalFactor: 2 + }], + interval: '5s' + }; + var urlExpected = 'proxied/api/v1/query_range?query=test' + + '&start=1443438675&end=1443460275&step=15'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); }); it('should apply intervalFactor to auto interval when greater', function() { var query = { + // 6 hour range range: { from: moment(1443438674760), to: moment(1443460274760) }, targets: [{ expr: 'test', @@ -404,6 +426,7 @@ describe('PrometheusDatasource', function() { it('should be unchanged when auto interval is greater than min interval', function() { var query = { + // 6 hour range range: { from: moment(1443438674760), to: moment(1443460274760) }, targets: [{ expr: 'rate(test[$__interval])', @@ -429,6 +452,7 @@ describe('PrometheusDatasource', function() { }); it('should be min interval when it is greater than auto interval', function() { var query = { + // 6 hour range range: { from: moment(1443438674760), to: moment(1443460274760) }, targets: [{ expr: 'rate(test[$__interval])', @@ -452,8 +476,9 @@ describe('PrometheusDatasource', function() { expect(query.scopedVars.__interval_ms.text).to.be(5 * 1000); expect(query.scopedVars.__interval_ms.value).to.be(5 * 1000); }); - it('should ignore intervalFactor', function() { + it('should account for intervalFactor', function() { var query = { + // 6 hour range range: { from: moment(1443438674760), to: moment(1443460274760) }, targets: [{ expr: 'rate(test[$__interval])', @@ -467,7 +492,7 @@ describe('PrometheusDatasource', function() { } }; var urlExpected = 'proxied/api/v1/query_range?query=' + - encodeURIComponent('rate(test[10s])') + + encodeURIComponent('rate(test[100s])') + '&start=1443438675&end=1443460275&step=100'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); @@ -478,8 +503,9 @@ describe('PrometheusDatasource', function() { expect(query.scopedVars.__interval_ms.text).to.be(10 * 1000); expect(query.scopedVars.__interval_ms.value).to.be(10 * 1000); }); - it('should ignore intervalFactor', function() { + it('should be interval * intervalFactor when greater than min interval', function() { var query = { + // 6 hour range range: { from: moment(1443438674760), to: moment(1443460274760) }, targets: [{ expr: 'rate(test[$__interval])', @@ -493,8 +519,35 @@ describe('PrometheusDatasource', function() { } }; var urlExpected = 'proxied/api/v1/query_range?query=' + - encodeURIComponent('rate(test[10s])') + - '&start=1443438675&end=1443460275&step=100'; + encodeURIComponent('rate(test[50s])') + + '&start=1443438675&end=1443460275&step=50'; + ctx.$httpBackend.expect('GET', urlExpected).respond(response); + ctx.ds.query(query); + ctx.$httpBackend.verifyNoOutstandingExpectation(); + + expect(query.scopedVars.__interval.text).to.be("5s"); + expect(query.scopedVars.__interval.value).to.be("5s"); + expect(query.scopedVars.__interval_ms.text).to.be(5 * 1000); + expect(query.scopedVars.__interval_ms.value).to.be(5 * 1000); + }); + it('should be min interval when greater than interval * intervalFactor', function() { + var query = { + // 6 hour range + range: { from: moment(1443438674760), to: moment(1443460274760) }, + targets: [{ + expr: 'rate(test[$__interval])', + interval: '15s', + intervalFactor: 2 + }], + interval: '5s', + scopedVars: { + "__interval": {text: "5s", value: "5s"}, + "__interval_ms": {text: 5 * 1000, value: 5 * 1000}, + } + }; + var urlExpected = 'proxied/api/v1/query_range?query=' + + encodeURIComponent('rate(test[15s])') + + '&start=1443438675&end=1443460275&step=15'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); ctx.$httpBackend.verifyNoOutstandingExpectation(); @@ -519,7 +572,7 @@ describe('PrometheusDatasource', function() { } }; var urlExpected = 'proxied/api/v1/query_range?query=' + - encodeURIComponent('rate(test[6s])') + + encodeURIComponent('rate(test[60s])') + '&start=1443438675&end=1444043475&step=60'; ctx.$httpBackend.expect('GET', urlExpected).respond(response); ctx.ds.query(query); From 080c46f835fe00054074759ff18a382c4d2e2d7a Mon Sep 17 00:00:00 2001 From: Alin Sinpalean Date: Wed, 4 Oct 2017 15:30:07 +0200 Subject: [PATCH 5/6] Address review comments. --- .../datasource/prometheus/datasource.ts | 78 ++++++++----------- 1 file changed, 31 insertions(+), 47 deletions(-) diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index c0bb3079290..e7b7cf36aa0 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -1,15 +1,12 @@ /// import _ from 'lodash'; -import moment from 'moment'; import kbn from 'app/core/utils/kbn'; import * as dateMath from 'app/core/utils/datemath'; import PrometheusMetricFindQuery from './metric_find_query'; import TableModel from 'app/core/table_model'; -var durationSplitRegexp = /(\d+)(ms|s|m|h|d|w|M|y)/; - function prometheusSpecialRegexEscape(value) { return value.replace(/[\\^$*+?.()|[\]{}]/g, '\\\\$&'); } @@ -83,6 +80,7 @@ export class PrometheusDatasource { var self = this; var start = this.getPrometheusTime(options.range.from, false); var end = this.getPrometheusTime(options.range.to, true); + var range = Math.ceil(end - start); var queries = []; var activeTargets = []; @@ -95,33 +93,7 @@ export class PrometheusDatasource { } activeTargets.push(target); - - var query: any = {}; - query.instant = target.instant; - - var interval = this.intervalSeconds(options.interval); - // Minimum interval ("Min step"), if specified for the query. or same as interval otherwise - var minInterval = this.intervalSeconds(this.templateSrv.replace(target.interval, options.scopedVars) || options.interval); - var intervalFactor = target.intervalFactor || 1; - var range = Math.ceil(end - start); - // Adjust the interval to take into account any specified minimum and interval factor plus Prometheus limits - var adjustedInterval = this.adjustInterval(interval, minInterval, range, intervalFactor); - - var scopedVars = options.scopedVars; - // If the interval was adjusted, make a shallow copy of scopedVars with updated interval vars - if (interval !== adjustedInterval) { - interval = adjustedInterval; - scopedVars = Object.assign({}, options.scopedVars, { - "__interval": {text: interval + "s", value: interval + "s"}, - "__interval_ms": {text: interval * 1000, value: interval * 1000}, - }); - } - target.step = query.step = interval; - - // Only replace vars in expression after having (possibly) updated interval vars - query.expr = this.templateSrv.replace(target.expr, scopedVars, self.interpolateQueryExpr); - query.requestId = options.panelId + target.refId; - queries.push(query); + queries.push(this.createQuery(target, options, range)); } // No valid targets, return the empty result to save a round trip. @@ -162,14 +134,41 @@ export class PrometheusDatasource { }); } + createQuery(target, options, range) { + var query: any = {}; + query.instant = target.instant; + + var interval = kbn.interval_to_seconds(options.interval); + // Minimum interval ("Min step"), if specified for the query. or same as interval otherwise + var minInterval = kbn.interval_to_seconds(this.templateSrv.replace(target.interval, options.scopedVars) || options.interval); + var intervalFactor = target.intervalFactor || 1; + // Adjust the interval to take into account any specified minimum and interval factor plus Prometheus limits + var adjustedInterval = this.adjustInterval(interval, minInterval, range, intervalFactor); + + var scopedVars = options.scopedVars; + // If the interval was adjusted, make a shallow copy of scopedVars with updated interval vars + if (interval !== adjustedInterval) { + interval = adjustedInterval; + scopedVars = Object.assign({}, options.scopedVars, { + "__interval": {text: interval + "s", value: interval + "s"}, + "__interval_ms": {text: interval * 1000, value: interval * 1000}, + }); + } + target.step = query.step = interval; + + // Only replace vars in expression after having (possibly) updated interval vars + query.expr = this.templateSrv.replace(target.expr, scopedVars, this.interpolateQueryExpr); + query.requestId = options.panelId + target.refId; + return query; + } + adjustInterval(interval, minInterval, range, intervalFactor) { // Prometheus will drop queries that might return more than 11000 data points. // Calibrate interval if it is too small. if (interval !== 0 && range / intervalFactor / interval > 11000) { interval = Math.ceil(range / intervalFactor / 11000); } - interval = Math.max(interval * intervalFactor, minInterval); - return interval; + return Math.max(interval * intervalFactor, minInterval); } performTimeSeriesQuery(query, start, end) { @@ -273,21 +272,6 @@ export class PrometheusDatasource { }); } - calculateInterval(interval, intervalFactor) { - return Math.ceil(this.intervalSeconds(interval) * intervalFactor); - } - - intervalSeconds(interval) { - var m = interval.match(durationSplitRegexp); - var dur = moment.duration(parseInt(m[1]), m[2]); - var sec = dur.asSeconds(); - if (sec < 1) { - sec = 1; - } - - return sec; - } - transformMetricData(md, options, start, end) { var dps = [], metricLabel = null; From 9cf7a2d2ed42b746c9730461470a2d2599cfc2ee Mon Sep 17 00:00:00 2001 From: Alin Sinpalean Date: Wed, 4 Oct 2017 16:23:24 +0200 Subject: [PATCH 6/6] Remove apparently unnecessary .flush() calls. --- .../datasource/prometheus/specs/datasource_specs.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts b/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts index a6e7f480f88..702ad8b5990 100644 --- a/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts @@ -278,10 +278,6 @@ describe('PrometheusDatasource', function() { } }; - beforeEach(function() { - ctx.$httpBackend.flush(); - }); - it('should be min interval when greater than auto interval', function() { var query = { // 6 hour range @@ -420,10 +416,6 @@ describe('PrometheusDatasource', function() { } }; - beforeEach(function() { - ctx.$httpBackend.flush(); - }); - it('should be unchanged when auto interval is greater than min interval', function() { var query = { // 6 hour range