debian-mirror-gitlab/lib/gitlab/prometheus_client.rb

196 lines
5.9 KiB
Ruby
Raw Permalink Normal View History

2018-12-13 13:39:08 +05:30
# frozen_string_literal: true
2017-08-17 22:00:37 +05:30
module Gitlab
# Helper methods to interact with Prometheus network services & resources
2017-09-10 17:25:29 +05:30
class PrometheusClient
2019-10-12 21:52:04 +05:30
include Gitlab::Utils::StrongMemoize
2018-03-27 19:54:05 +05:30
Error = Class.new(StandardError)
2020-07-28 23:09:34 +05:30
ConnectionError = Class.new(Gitlab::PrometheusClient::Error)
UnexpectedResponseError = Class.new(Gitlab::PrometheusClient::Error)
2018-03-27 19:54:05 +05:30
QueryError = Class.new(Gitlab::PrometheusClient::Error)
2020-04-22 19:07:51 +05:30
HEALTHY_RESPONSE = "Prometheus is Healthy.\n"
2018-03-27 19:54:05 +05:30
2019-07-07 11:18:12 +05:30
# 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
2019-10-12 21:52:04 +05:30
# Key translation between RestClient and Gitlab::HTTP (HTTParty)
RESTCLIENT_GITLAB_HTTP_KEYMAP = {
ssl_cert_store: :cert_store
}.freeze
2017-08-17 22:00:37 +05:30
2019-10-12 21:52:04 +05:30
attr_reader :api_url, :options
private :api_url, :options
def initialize(api_url, options = {})
@api_url = api_url.chomp('/')
@options = options
2017-08-17 22:00:37 +05:30
end
def ping
json_api_get('query', query: '1')
end
2020-04-22 19:07:51 +05:30
def healthy?
response_body = handle_management_api_response(get(health_url, {}))
# From Prometheus docs: This endpoint always returns 200 and should be used to check Prometheus health.
response_body == HEALTHY_RESPONSE
end
2020-11-24 15:15:51 +05:30
def ready?
response = get(ready_url, {})
# From Prometheus docs: This endpoint returns 200 when Prometheus is ready to serve traffic (i.e. respond to queries).
response.code == 200
2021-06-08 01:23:25 +05:30
rescue StandardError => e
2020-11-24 15:15:51 +05:30
raise PrometheusClient::UnexpectedResponseError, "#{e.message}"
end
2019-07-07 11:18:12 +05:30
def proxy(type, args)
path = api_path(type)
get(path, args)
2019-10-12 21:52:04 +05:30
rescue Gitlab::HTTP::ResponseError => ex
2020-07-28 23:09:34 +05:30
raise PrometheusClient::ConnectionError, "Network connection error" unless ex.response && ex.response.try(:code)
2019-10-12 21:52:04 +05:30
2020-04-22 19:07:51 +05:30
handle_querying_api_response(ex.response)
2019-07-07 11:18:12 +05:30
end
2017-08-17 22:00:37 +05:30
def query(query, time: Time.now)
get_result('vector') do
2017-09-10 17:25:29 +05:30
json_api_get('query', query: query, time: time.to_f)
2017-08-17 22:00:37 +05:30
end
end
2020-04-22 19:07:51 +05:30
def query_range(query, start_time: 8.hours.ago, end_time: Time.now)
start_time = start_time.to_f
end_time = end_time.to_f
step = self.class.compute_step(start_time, end_time)
2019-07-07 11:18:12 +05:30
2017-08-17 22:00:37 +05:30
get_result('matrix') do
2019-07-07 11:18:12 +05:30
json_api_get(
'query_range',
query: query,
2020-04-22 19:07:51 +05:30
start: start_time,
end: end_time,
2019-07-07 11:18:12 +05:30
step: step
)
2017-08-17 22:00:37 +05:30
end
end
2020-06-23 00:09:42 +05:30
# Queries Prometheus with the given aggregate query and groups the results by mapping
# metric labels to their respective values.
#
# @return [Hash] mapping labels to their aggregate numeric values, or the empty hash if no results were found
2020-10-24 23:57:45 +05:30
def aggregate(aggregate_query, time: Time.now, transform_value: :to_f)
2020-06-23 00:09:42 +05:30
response = query(aggregate_query, time: time)
response.to_h do |result|
key = block_given? ? yield(result['metric']) : result['metric']
_timestamp, value = result['value']
2020-10-24 23:57:45 +05:30
[key, value.public_send(transform_value)] # rubocop:disable GitlabSecurity/PublicSend
2020-06-23 00:09:42 +05:30
end
end
2017-09-10 17:25:29 +05:30
def label_values(name = '__name__')
json_api_get("label/#{name}/values")
end
2020-04-22 19:07:51 +05:30
def series(*matches, start_time: 8.hours.ago, end_time: Time.now)
json_api_get('series', 'match': matches, start: start_time.to_f, end: end_time.to_f)
2017-09-10 17:25:29 +05:30
end
2020-04-22 19:07:51 +05:30
def self.compute_step(start_time, end_time)
diff = end_time - start_time
2019-07-07 11:18:12 +05:30
step = (diff / QUERY_RANGE_DATA_POINTS).ceil
[QUERY_RANGE_MIN_STEP, step].max
end
2020-04-22 19:07:51 +05:30
def health_url
2020-11-24 15:15:51 +05:30
"#{api_url}/-/healthy"
end
def ready_url
"#{api_url}/-/ready"
2020-04-22 19:07:51 +05:30
end
2017-08-17 22:00:37 +05:30
private
2019-07-07 11:18:12 +05:30
def api_path(type)
2019-10-12 21:52:04 +05:30
[api_url, 'api', 'v1', type].join('/')
2017-08-17 22:00:37 +05:30
end
2019-07-07 11:18:12 +05:30
def json_api_get(type, args = {})
path = api_path(type)
response = get(path, args)
2020-04-22 19:07:51 +05:30
handle_querying_api_response(response)
2019-10-12 21:52:04 +05:30
rescue Gitlab::HTTP::ResponseError => ex
2020-07-28 23:09:34 +05:30
raise PrometheusClient::ConnectionError, "Network connection error" unless ex.response && ex.response.try(:code)
2019-10-12 21:52:04 +05:30
2020-04-22 19:07:51 +05:30
handle_querying_api_response(ex.response)
2019-10-12 21:52:04 +05:30
end
def gitlab_http_key(key)
RESTCLIENT_GITLAB_HTTP_KEYMAP[key] || key
end
def mapped_options
2021-04-29 21:17:54 +05:30
options.keys.to_h { |k| [gitlab_http_key(k), options[k]] }
2019-10-12 21:52:04 +05:30
end
def http_options
strong_memoize(:http_options) do
{ follow_redirects: false }.merge(mapped_options)
2018-03-27 19:54:05 +05:30
end
2017-08-17 22:00:37 +05:30
end
2019-07-07 11:18:12 +05:30
def get(path, args)
2023-03-04 22:38:38 +05:30
Gitlab::HTTP.get(path, { query: args }.merge(http_options))
2021-12-11 22:18:48 +05:30
rescue *Gitlab::HTTP::HTTP_ERRORS => e
raise PrometheusClient::ConnectionError, e.message
2019-07-07 11:18:12 +05:30
end
2020-04-22 19:07:51 +05:30
def handle_management_api_response(response)
if response.code == 200
response.body
else
2020-07-28 23:09:34 +05:30
raise PrometheusClient::UnexpectedResponseError, "#{response.code} - #{response.body}"
2020-04-22 19:07:51 +05:30
end
end
def handle_querying_api_response(response)
2019-10-12 21:52:04 +05:30
response_code = response.try(:code)
response_body = response.try(:body)
2020-07-28 23:09:34 +05:30
raise PrometheusClient::UnexpectedResponseError, "#{response_code} - #{response_body}" unless response_code
2019-10-12 21:52:04 +05:30
json_data = parse_json(response_body) if [200, 400].include?(response_code)
2018-03-17 18:26:18 +05:30
2019-10-12 21:52:04 +05:30
case response_code
when 200
json_data['data'] if response['status'] == 'success'
when 400
2018-03-27 19:54:05 +05:30
raise PrometheusClient::QueryError, json_data['error'] || 'Bad data received'
2017-08-17 22:00:37 +05:30
else
2020-07-28 23:09:34 +05:30
raise PrometheusClient::UnexpectedResponseError, "#{response_code} - #{response_body}"
2017-08-17 22:00:37 +05:30
end
end
def get_result(expected_type)
data = yield
data['result'] if data['resultType'] == expected_type
end
2019-07-07 11:18:12 +05:30
def parse_json(response_body)
2020-05-24 23:13:21 +05:30
Gitlab::Json.parse(response_body, legacy_mode: true)
2019-07-07 11:18:12 +05:30
rescue JSON::ParserError
2020-07-28 23:09:34 +05:30
raise PrometheusClient::UnexpectedResponseError, 'Parsing response failed'
2019-07-07 11:18:12 +05:30
end
2017-08-17 22:00:37 +05:30
end
end