瀏覽代碼

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.

Alin Sinpalean 8 年之前
父節點
當前提交
2a62374a61

+ 28 - 12
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;

+ 262 - 0
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);
+    });
+  });
 });