140 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			Ruby
		
	
	
	
			
		
		
	
	
			140 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			Ruby
		
	
	
	
# frozen_string_literal: true
 | 
						|
 | 
						|
module Gitlab
 | 
						|
  # Helper methods to interact with Prometheus network services & resources
 | 
						|
  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)
 | 
						|
      @rest_client = rest_client
 | 
						|
    end
 | 
						|
 | 
						|
    def ping
 | 
						|
      json_api_get('query', query: '1')
 | 
						|
    end
 | 
						|
 | 
						|
    def proxy(type, args)
 | 
						|
      path = api_path(type)
 | 
						|
      get(path, args)
 | 
						|
    rescue RestClient::ExceptionWithResponse => ex
 | 
						|
      if ex.response
 | 
						|
        ex.response
 | 
						|
      else
 | 
						|
        raise PrometheusClient::Error, "Network connection error"
 | 
						|
      end
 | 
						|
    rescue RestClient::Exception
 | 
						|
      raise PrometheusClient::Error, "Network connection error"
 | 
						|
    end
 | 
						|
 | 
						|
    def query(query, time: Time.now)
 | 
						|
      get_result('vector') do
 | 
						|
        json_api_get('query', query: query, time: time.to_f)
 | 
						|
      end
 | 
						|
    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,
 | 
						|
          end: stop,
 | 
						|
          step: step
 | 
						|
        )
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    def label_values(name = '__name__')
 | 
						|
      json_api_get("label/#{name}/values")
 | 
						|
    end
 | 
						|
 | 
						|
    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 api_path(type)
 | 
						|
      ['api', 'v1', type].join('/')
 | 
						|
    end
 | 
						|
 | 
						|
    def json_api_get(type, args = {})
 | 
						|
      path = api_path(type)
 | 
						|
      response = get(path, args)
 | 
						|
      handle_response(response)
 | 
						|
    rescue RestClient::ExceptionWithResponse => ex
 | 
						|
      if ex.response
 | 
						|
        handle_exception_response(ex.response)
 | 
						|
      else
 | 
						|
        raise PrometheusClient::Error, "Network connection error"
 | 
						|
      end
 | 
						|
    rescue RestClient::Exception
 | 
						|
      raise PrometheusClient::Error, "Network connection error"
 | 
						|
    end
 | 
						|
 | 
						|
    def get(path, args)
 | 
						|
      rest_client[path].get(params: args)
 | 
						|
    rescue SocketError
 | 
						|
      raise PrometheusClient::Error, "Can't connect to #{rest_client.url}"
 | 
						|
    rescue OpenSSL::SSL::SSLError
 | 
						|
      raise PrometheusClient::Error, "#{rest_client.url} contains invalid SSL data"
 | 
						|
    rescue Errno::ECONNREFUSED
 | 
						|
      raise PrometheusClient::Error, 'Connection refused'
 | 
						|
    end
 | 
						|
 | 
						|
    def handle_response(response)
 | 
						|
      json_data = parse_json(response.body)
 | 
						|
      if response.code == 200 && json_data['status'] == 'success'
 | 
						|
        json_data['data'] || {}
 | 
						|
      else
 | 
						|
        raise PrometheusClient::Error, "#{response.code} - #{response.body}"
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    def handle_exception_response(response)
 | 
						|
      if response.code == 200 && response['status'] == 'success'
 | 
						|
        response['data'] || {}
 | 
						|
      elsif response.code == 400
 | 
						|
        json_data = parse_json(response.body)
 | 
						|
        raise PrometheusClient::QueryError, json_data['error'] || 'Bad data received'
 | 
						|
      else
 | 
						|
        raise PrometheusClient::Error, "#{response.code} - #{response.body}"
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    def get_result(expected_type)
 | 
						|
      data = yield
 | 
						|
      data['result'] if data['resultType'] == expected_type
 | 
						|
    end
 | 
						|
 | 
						|
    def parse_json(response_body)
 | 
						|
      JSON.parse(response_body)
 | 
						|
    rescue JSON::ParserError
 | 
						|
      raise PrometheusClient::Error, 'Parsing response failed'
 | 
						|
    end
 | 
						|
  end
 | 
						|
end
 |