module Gitlab module Git module Storage class Health attr_reader :storage_name, :info def self.pattern_for_storage(storage_name) "#{Gitlab::Git::Storage::REDIS_KEY_PREFIX}#{storage_name}:*" end def self.for_all_storages storage_names = Gitlab.config.repositories.storages.keys results_per_storage = nil Gitlab::Git::Storage.redis.with do |redis| keys_per_storage = all_keys_for_storages(storage_names, redis) results_per_storage = load_for_keys(keys_per_storage, redis) end results_per_storage.map do |name, info| info.each { |i| i[:failure_count] = i[:failure_count].value.to_i } new(name, info) end end private_class_method def self.all_keys_for_storages(storage_names, redis) keys_per_storage = {} redis.pipelined do storage_names.each do |storage_name| pattern = pattern_for_storage(storage_name) matched_keys = redis.scan_each(match: pattern) keys_per_storage[storage_name] = matched_keys end end # We need to make sure each lazy-loaded `Enumerator` for matched keys # is loaded into an array. # # Otherwise it would be loaded in the second `Redis#pipelined` block # within `.load_for_keys`. In this pipelined call, the active # Redis-client changes again, so the values would not be available # until the end of that pipelined-block. keys_per_storage.each do |storage_name, key_future| keys_per_storage[storage_name] = key_future.to_a end end private_class_method def self.load_for_keys(keys_per_storage, redis) info_for_keys = {} redis.pipelined do keys_per_storage.each do |storage_name, keys_future| info_for_storage = keys_future.map do |key| { name: key, failure_count: redis.hget(key, :failure_count) } end info_for_keys[storage_name] = info_for_storage end end info_for_keys end def self.for_failing_storages for_all_storages.select(&:failing?) end def initialize(storage_name, info) @storage_name = storage_name @info = info end def failing_info @failing_info ||= info.select { |info_for_host| info_for_host[:failure_count] > 0 } end def failing? failing_info.any? end def failing_on_hosts @failing_on_hosts ||= failing_info.map do |info_for_host| info_for_host[:name].split(':').last end end def failing_circuit_breakers @failing_circuit_breakers ||= failing_on_hosts.map do |hostname| CircuitBreaker.build(storage_name, hostname) end end def total_failures @total_failures ||= failing_info.sum { |info_for_host| info_for_host[:failure_count] } end end end end end