From 2a62374a61e8e10d1141914038325eb8e856e46b Mon Sep 17 00:00:00 2001 From: Alin Sinpalean Date: Tue, 12 Sep 2017 16:36:27 +0200 Subject: [PATCH] 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); + }); + }); });