1# frozen_string_literal: true
2
3module PrometheusAdapter
4  extend ActiveSupport::Concern
5
6  # We should choose more conservative timeouts, but some queries we run are now busting our
7  # default timeouts, which are stricter. We should make those queries faster instead.
8  # See https://gitlab.com/gitlab-org/gitlab/-/issues/232786
9  DEFAULT_PROMETHEUS_REQUEST_TIMEOUT_SEC = 60.seconds
10
11  included do
12    include ReactiveCaching
13
14    self.reactive_cache_lease_timeout = 30.seconds
15    self.reactive_cache_refresh_interval = 30.seconds
16    self.reactive_cache_lifetime = 1.minute
17    self.reactive_cache_work_type = :external_dependency
18
19    def prometheus_client
20      raise NotImplementedError
21    end
22
23    def prometheus_client_default_options
24      {
25        timeout: DEFAULT_PROMETHEUS_REQUEST_TIMEOUT_SEC
26      }
27    end
28
29    # Overridden in app/models/clusters/applications/prometheus.rb
30    def managed_prometheus?
31      false
32    end
33
34    # This is a light-weight check if a prometheus client is properly configured.
35    def configured?
36      raise NotImplementedError
37    end
38
39    # This is a heavy-weight check if a prometheus is properly configured and accessible from GitLab.
40    # This actually sends a request to an external service and often it could take a long time,
41    # Please consider using `configured?` instead if the process is running on Puma threads.
42    def can_query?
43      prometheus_client.present?
44    end
45
46    def query(query_name, *args)
47      return unless can_query?
48
49      query_class = query_klass_for(query_name)
50      query_args = build_query_args(*args)
51
52      with_reactive_cache(query_class.name, *query_args, &query_class.method(:transform_reactive_result))
53    end
54
55    # Cache metrics for specific environment
56    def calculate_reactive_cache(query_class_name, *args)
57      return unless prometheus_client
58
59      data = Object.const_get(query_class_name, false).new(prometheus_client).query(*args)
60      {
61        success: true,
62        data: data,
63        last_update: Time.current.utc
64      }
65    rescue Gitlab::PrometheusClient::Error => err
66      { success: false, result: err.message }
67    end
68
69    def query_klass_for(query_name)
70      Gitlab::Prometheus::Queries.const_get("#{query_name.to_s.classify}Query", false)
71    end
72
73    def build_query_args(*args)
74      args.map { |arg| arg.respond_to?(:id) ? arg.id : arg }
75    end
76
77    def clear_prometheus_reactive_cache!(query_name, *args)
78      query_class = query_klass_for(query_name)
79      query_args = build_query_args(*args)
80
81      clear_reactive_cache!(query_class.name, *query_args)
82    end
83  end
84end
85