1# frozen_string_literal: true
2
3module Gitlab
4  module Metrics
5    module Samplers
6      class DatabaseSampler < BaseSampler
7        DEFAULT_SAMPLING_INTERVAL_SECONDS = 5
8
9        METRIC_PREFIX = 'gitlab_database_connection_pool_'
10
11        METRIC_DESCRIPTIONS = {
12          size: 'Total connection pool capacity',
13          connections: 'Current connections in the pool',
14          busy: 'Connections in use where the owner is still alive',
15          dead: 'Connections in use where the owner is not alive',
16          idle: 'Connections not in use',
17          waiting: 'Threads currently waiting on this queue'
18        }.freeze
19
20        def metrics
21          @metrics ||= init_metrics
22        end
23
24        def sample
25          host_stats.each do |host_stat|
26            METRIC_DESCRIPTIONS.each_key do |metric|
27              metrics[metric].set(host_stat[:labels], host_stat[:stats][metric])
28            end
29          end
30        end
31
32        private
33
34        def init_metrics
35          METRIC_DESCRIPTIONS.to_h do |name, description|
36            [name, ::Gitlab::Metrics.gauge(:"#{METRIC_PREFIX}#{name}", description)]
37          end
38        end
39
40        def host_stats
41          connection_class_stats + replica_host_stats
42        end
43
44        def connection_class_stats
45          Gitlab::Database.database_base_models.each_value.with_object([]) do |base_model, stats|
46            next unless base_model.connected?
47
48            stats << { labels: labels_for_class(base_model), stats: base_model.connection_pool.stat }
49          end
50        end
51
52        def replica_host_stats
53          Gitlab::Database::LoadBalancing.each_load_balancer.with_object([]) do |load_balancer, stats|
54            next if load_balancer.primary_only?
55
56            load_balancer.host_list.hosts.each do |host|
57              stats << { labels: labels_for_replica_host(load_balancer, host), stats: host.connection.pool.stat }
58            end
59          end
60        end
61
62        def labels_for_class(klass)
63          {
64            host: klass.connection_db_config.host,
65            port: klass.connection_db_config.configuration_hash[:port],
66            class: klass.to_s,
67            db_config_name: klass.connection_db_config.name
68          }
69        end
70
71        def labels_for_replica_host(load_balancer, host)
72          {
73            host: host.host,
74            port: host.port,
75            class: load_balancer.configuration.primary_connection_specification_name,
76            db_config_name: Gitlab::Database.db_config_name(host.connection)
77          }
78        end
79      end
80    end
81  end
82end
83
84Gitlab::Metrics::Samplers::DatabaseSampler.prepend_mod_with('Gitlab::Metrics::Samplers::DatabaseSampler')
85