From 552d38861ad77bf6a64b1e61a91e36fcd26d057c Mon Sep 17 00:00:00 2001
From: Peter Leitzen <pleitzen@gitlab.com>
Date: Thu, 4 Apr 2019 15:38:37 +0000
Subject: [PATCH] Automatically set Prometheus step interval

By computing the step interval passed to the query_range Prometheus API
call we improve the performance on the Prometheus server and GitLab by
reducing the amount of data points sent back and prevent Prometheus
from sending errors when requesting longer intervals.
---
 ...matically-set-prometheus-step-interval.yml |  5 +++
 lib/gitlab/prometheus_client.rb               | 32 ++++++++++++++++---
 spec/lib/gitlab/prometheus_client_spec.rb     | 28 ++++++++++++++++
 spec/support/helpers/prometheus_helpers.rb    | 10 ++++--
 4 files changed, 67 insertions(+), 8 deletions(-)
 create mode 100644 changelogs/unreleased/58839-automatically-set-prometheus-step-interval.yml

diff --git a/changelogs/unreleased/58839-automatically-set-prometheus-step-interval.yml b/changelogs/unreleased/58839-automatically-set-prometheus-step-interval.yml
new file mode 100644
index 0000000000000..2c6edf45ae217
--- /dev/null
+++ b/changelogs/unreleased/58839-automatically-set-prometheus-step-interval.yml
@@ -0,0 +1,5 @@
+---
+title: Automatically set Prometheus step interval
+merge_request: 26441
+author:
+type: changed
diff --git a/lib/gitlab/prometheus_client.rb b/lib/gitlab/prometheus_client.rb
index 45828c77a33b5..b4de7cd2bce3e 100644
--- a/lib/gitlab/prometheus_client.rb
+++ b/lib/gitlab/prometheus_client.rb
@@ -6,6 +6,14 @@ class PrometheusClient
     Error = Class.new(StandardError)
     QueryError = Class.new(Gitlab::PrometheusClient::Error)
 
+    # Target number of data points for `query_range`.
+    # Please don't exceed the limit of 11000 data points
+    # See https://github.com/prometheus/prometheus/blob/91306bdf24f5395e2601773316945a478b4b263d/web/api/v1/api.go#L347
+    QUERY_RANGE_DATA_POINTS = 600
+
+    # Minimal value of the `step` parameter for `query_range` in seconds.
+    QUERY_RANGE_MIN_STEP = 60
+
     attr_reader :rest_client, :headers
 
     def initialize(rest_client)
@@ -23,12 +31,18 @@ def query(query, time: Time.now)
     end
 
     def query_range(query, start: 8.hours.ago, stop: Time.now)
+      start = start.to_f
+      stop = stop.to_f
+      step = self.class.compute_step(start, stop)
+
       get_result('matrix') do
-        json_api_get('query_range',
-                     query: query,
-                     start: start.to_f,
-                     end: stop.to_f,
-                     step: 1.minute.to_i)
+        json_api_get(
+          'query_range',
+          query: query,
+          start: start,
+          end: stop,
+          step: step
+        )
       end
     end
 
@@ -40,6 +54,14 @@ def series(*matches, start: 8.hours.ago, stop: Time.now)
       json_api_get('series', 'match': matches, start: start.to_f, end: stop.to_f)
     end
 
+    def self.compute_step(start, stop)
+      diff = stop - start
+
+      step = (diff / QUERY_RANGE_DATA_POINTS).ceil
+
+      [QUERY_RANGE_MIN_STEP, step].max
+    end
+
     private
 
     def json_api_get(type, args = {})
diff --git a/spec/lib/gitlab/prometheus_client_spec.rb b/spec/lib/gitlab/prometheus_client_spec.rb
index 4c3b8deefb913..2517ee71f24b3 100644
--- a/spec/lib/gitlab/prometheus_client_spec.rb
+++ b/spec/lib/gitlab/prometheus_client_spec.rb
@@ -230,4 +230,32 @@
       let(:execute_query) { subject.query_range(prometheus_query) }
     end
   end
+
+  describe '.compute_step' do
+    using RSpec::Parameterized::TableSyntax
+
+    let(:now) { Time.now.utc }
+
+    subject { described_class.compute_step(start, stop) }
+
+    where(:time_interval_in_seconds, :step) do
+      0               | 60
+      10.hours        | 60
+      10.hours + 1    | 61
+      # frontend options
+      30.minutes      | 60
+      3.hours         | 60
+      8.hours         | 60
+      1.day           | 144
+      3.days          | 432
+      1.week          | 1008
+    end
+
+    with_them do
+      let(:start) { now - time_interval_in_seconds }
+      let(:stop) { now }
+
+      it { is_expected.to eq(step) }
+    end
+  end
 end
diff --git a/spec/support/helpers/prometheus_helpers.rb b/spec/support/helpers/prometheus_helpers.rb
index ce1f9fce10dd5..08d1d7a605915 100644
--- a/spec/support/helpers/prometheus_helpers.rb
+++ b/spec/support/helpers/prometheus_helpers.rb
@@ -25,12 +25,16 @@ def prometheus_query_with_time_url(prometheus_query, time)
     "https://prometheus.example.com/api/v1/query?#{query}"
   end
 
-  def prometheus_query_range_url(prometheus_query, start: 8.hours.ago, stop: Time.now.to_f)
+  def prometheus_query_range_url(prometheus_query, start: 8.hours.ago, stop: Time.now, step: nil)
+    start = start.to_f
+    stop = stop.to_f
+    step ||= Gitlab::PrometheusClient.compute_step(start, stop)
+
     query = {
       query: prometheus_query,
-      start: start.to_f,
+      start: start,
       end: stop,
-      step: 1.minute.to_i
+      step: step
     }.to_query
 
     "https://prometheus.example.com/api/v1/query_range?#{query}"
-- 
GitLab