gitlab-exporter 14.5.0 → 15.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '08d5fac10fb60048757b6f0ad48ac3672d34d64d01e096eac72b1e1d2e859ed2'
4
- data.tar.gz: f33f247f22c6d9f1022d33e8d22b01ced782eea825cd22529cabcbe54e9f08f0
3
+ metadata.gz: 73d7d8a42035a6cb634f0c234e6bcd3e723492f2903663d7ecd0ccacde53279c
4
+ data.tar.gz: 012ce7e6313ec24c72c605bf016477c75d6de08ec2c37339e0a12c09c64aaa23
5
5
  SHA512:
6
- metadata.gz: 155b43cb6cb074afd58462dc0c2f2e6bdef31310d3e8e8a9781340b1e07aab321ef2d5f5d5f9a95cd69e710aadb21d32cc7d6a5901d171c92f68259d95124f54
7
- data.tar.gz: 237c25cfe1df48c137ba7f6a513e566f0f4bb0305f43c99e4a94014dc961a2f6deebd923b4adbfcedd8d0cd9ae893841013c290bbb7d127bd61765ecf3452d93
6
+ metadata.gz: dc31a10b7051bb5c76667d817cdd2761cd9ce9b755949567770b390f4362b37722d5658300b63607ff8f2566e577786ddb6e5b8edf3263bbbba3c478d28e08e7
7
+ data.tar.gz: 96b24d081ca6172197f6bdf44b739b3d053f32f2944378bb09609a02e485a053251c752ad49c921202eeee165437284aea73f875cce06a85edc455fb2d4e573d
data/.gitlab-ci.yml CHANGED
@@ -50,9 +50,32 @@ rspec_integration:
50
50
  - bundle exec rspec spec -t integration -f d -c
51
51
  before_script: *before_scripts
52
52
  services:
53
- - redis:latest
53
+ - name: redis:${REDIS_VERSION}
54
+ alias: redis-master
55
+ - name: bitnami/redis-sentinel:${REDIS_SENTINEL_VERSION}
56
+ alias: redis-sentinel
57
+ command:
58
+ - /bin/sh
59
+ - -c
60
+ - |
61
+ echo "Starting Redis Sentinel..."
62
+ cat <<EOF > /opt/bitnami/redis-sentinel/etc/sentinel.conf
63
+ user default off
64
+ user ${REDIS_SENTINEL_USERNAME} on >${REDIS_SENTINEL_PASSWORD} +@all
65
+ sentinel monitor mymaster redis-master 6379 2
66
+ sentinel resolve-hostnames yes
67
+ sentinel down-after-milliseconds mymaster 5000
68
+ EOF
69
+ redis-sentinel /opt/bitnami/redis-sentinel/etc/sentinel.conf
70
+
54
71
  variables:
55
72
  REDIS_URL: "redis://redis"
73
+ REDIS_SENTINEL_URL: "redis://mymaster"
74
+ REDIS_SENTINELS: "redis-sentinel:26379"
75
+ REDIS_VERSION: latest
76
+ REDIS_SENTINEL_VERSION: latest
77
+ REDIS_SENTINEL_USERNAME: "my-sentinel-username"
78
+ REDIS_SENTINEL_PASSWORD: "my-sentinel-password"
56
79
  parallel:
57
80
  matrix:
58
81
  - RUBY_VERSION: ["2.7", "3.0", "3.1", "3.2"]
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gitlab-exporter (14.5.0)
4
+ gitlab-exporter (15.1.0)
5
5
  connection_pool (= 2.2.5)
6
6
  deep_merge (~> 1.2.2)
7
7
  faraday (>= 1.8.0, <= 2.8.1)
data/README.md CHANGED
@@ -19,6 +19,9 @@ metrics.
19
19
  1. Database
20
20
  * [Per-table tuple stats](lib/gitlab_exporter/database/tuple_stats.rb) --
21
21
  `gitlab_database_stat_table_*`
22
+ * [Per-sequence stats](lib/gitlab_exporter/database/pg_sequences.rb) --
23
+ `gitlab_pg_sequences_min_value`, `gitlab_pg_sequences_max_value`, `gitlab_pg_sequences_current_value`,
24
+ `gitlab_pg_sequences_saturation_ratio`
22
25
  * [Row count queries](lib/gitlab_exporter/database/row_count.rb) --
23
26
  `gitlab_database_rows`
24
27
  * [CI builds](lib/gitlab_exporter/database/ci_builds.rb) --
@@ -41,6 +41,10 @@ probes:
41
41
  class_name: Database::BloatProber
42
42
  <<: *db_common
43
43
 
44
+ pg_sequences:
45
+ class_name: Database::PgSequencesProber
46
+ <<: *db_common
47
+
44
48
  # We can group multiple probes under a single endpoint by setting the `multiple` key to `true`, followed
45
49
  # by probe definitions as usual.
46
50
  database:
@@ -98,6 +102,14 @@ probes:
98
102
  # redis_username: 'redis-username'
99
103
  # redis_password: 'redis-password'
100
104
  redis_enable_client: true
105
+ # Uncomment if Redis Sentinels are needed
106
+ # redis_sentinels:
107
+ # - host: 'localhost'
108
+ # port: 26380
109
+ # - host: 'localhost'
110
+ # port: 26381
111
+ # redis_sentinel_username: 'redis-sentinel-username'
112
+ # redis_sentinel_password: 'redis-sentinel-password'
101
113
 
102
114
  ruby: &ruby
103
115
  class_name: RubyProber
@@ -297,12 +297,24 @@ module GitLab
297
297
  @target = File.open(@target, "a") if @target.is_a?(String)
298
298
  end
299
299
 
300
- def options(args)
300
+ def options(args) # rubocop:disable Metrics/MethodLength
301
301
  args.options do |opts|
302
302
  opts.banner = "Usage: #{EXECUTABLE_NAME} #{COMMAND_NAME} [options]"
303
303
  opts.on("--redis-url=\"redis://localhost:6379\"", "Redis URL") do |val|
304
304
  @redis_url = val
305
305
  end
306
+ opts.on("--redis-sentinel-username=\"my-sentinel-username\"", "Redis Sentinel username") do |val|
307
+ @redis_sentinel_username = val
308
+ end
309
+ opts.on("--redis-sentinel-password=\"my-sentinel-password\"", "Redis Sentinel password") do |val|
310
+ @redis_sentinel_password = val
311
+ end
312
+ opts.on("--redis-sentinels=HOST1:PORT1,HOST2:PORT2", Array, "List of Redis Sentinels") do |val|
313
+ @redis_sentinels = val.map { |item|
314
+ host, port = item.split(":")
315
+ { host: host, port: port.to_i }
316
+ }
317
+ end
306
318
  end
307
319
  end
308
320
 
@@ -313,13 +325,19 @@ module GitLab
313
325
  def run
314
326
  validate!
315
327
 
316
- ::GitLab::Exporter::SidekiqProber.new(redis_url: @redis_url)
317
- .probe_stats
318
- .probe_queues
319
- .probe_jobs_limit
320
- .probe_workers
321
- .probe_retries
322
- .write_to(@target)
328
+ prober = ::GitLab::Exporter::SidekiqProber.new(redis_url: @redis_url,
329
+ redis_sentinels: @redis_sentinels,
330
+ redis_sentinel_username: @redis_sentinel_username,
331
+ redis_sentinel_password: @redis_sentinel_password,
332
+ logger: Logger.new(STDERR))
333
+
334
+ prober
335
+ .probe_stats
336
+ .probe_queues
337
+ .probe_jobs_limit
338
+ .probe_workers
339
+ .probe_retries
340
+ .write_to(@target)
323
341
  end
324
342
 
325
343
  private
@@ -62,6 +62,10 @@ module GitLab
62
62
  conn.reset
63
63
  raise e
64
64
  end
65
+ rescue PG::ConnectionBad => e
66
+ @logger.error "Bad connection to the database, resetting pool: #{e}" if @logger
67
+ connection_pool.reload(&:close)
68
+ raise e
65
69
  rescue PG::Error => e
66
70
  @logger.error "Error connecting to the database: #{e}" if @logger
67
71
  raise e
@@ -0,0 +1,60 @@
1
+ module GitLab
2
+ module Exporter
3
+ module Database
4
+ # A helper class to collect sequences metrics. Mainly used to monitor Cells sequences.
5
+ class PgSequencesCollector < Base
6
+ QUERY = <<~SQL.freeze
7
+ SELECT
8
+ schemaname,
9
+ sequencename,
10
+ CONCAT(schemaname, '.', sequencename) AS fully_qualified_sequencename,
11
+ start_value,
12
+ min_value,
13
+ max_value,
14
+ last_value AS current_value
15
+ FROM pg_catalog.pg_sequences;
16
+ SQL
17
+
18
+ def run
19
+ with_connection_pool do |conn|
20
+ conn.exec(QUERY).each.with_object({}) do |row, stats|
21
+ stats[row.delete("fully_qualified_sequencename")] = row
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ # The prober which is called when gathering metrics
28
+ class PgSequencesProber
29
+ METRIC_NAME = "gitlab_pg_sequences".freeze
30
+ METRIC_KEYS = %w[min_value max_value current_value].freeze
31
+
32
+ def initialize(metrics: PrometheusMetrics.new, **opts)
33
+ @metrics = metrics
34
+ @collector = PgSequencesCollector.new(**opts)
35
+ end
36
+
37
+ def probe_db
38
+ result = @collector.run
39
+
40
+ result.each do |fully_qualified_sequencename, sequence_info|
41
+ METRIC_KEYS.each do |key|
42
+ value = sequence_info.fetch(key).to_f
43
+ tags = {
44
+ schemaname: sequence_info.fetch("schemaname"),
45
+ sequencename: sequence_info.fetch("sequencename"),
46
+ fully_qualified_sequencename: fully_qualified_sequencename
47
+ }
48
+
49
+ @metrics.add("#{METRIC_NAME}_#{key}", value, **tags)
50
+ end
51
+ end
52
+
53
+ self
54
+ rescue PG::ConnectionBad
55
+ self
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -8,6 +8,7 @@ module GitLab
8
8
  autoload :RowCountProber, "gitlab_exporter/database/row_count"
9
9
  autoload :BloatProber, "gitlab_exporter/database/bloat"
10
10
  autoload :RemoteMirrorsProber, "gitlab_exporter/database/remote_mirrors"
11
+ autoload :PgSequencesProber, "gitlab_exporter/database/pg_sequences"
11
12
  end
12
13
  end
13
14
  end
@@ -7,7 +7,7 @@ module GitLab
7
7
  # A prober for Sidekiq queues
8
8
  #
9
9
  # It takes the Redis URL Sidekiq is connected to
10
- class SidekiqProber
10
+ class SidekiqProber # rubocop:disable Metrics/ClassLength
11
11
  # The maximum depth (from the head) of each queue to probe. Probing the
12
12
  # entirety of a very large queue will take longer and run the risk of
13
13
  # timing out. But when we have a very large queue, we are most in need of
@@ -195,6 +195,7 @@ module GitLab
195
195
  def redis_options
196
196
  options = {
197
197
  url: @opts[:redis_url],
198
+ sentinels: redis_sentinel_options,
198
199
  connect_timeout: 1,
199
200
  reconnect_attempts: 0
200
201
  }
@@ -207,6 +208,19 @@ module GitLab
207
208
  options
208
209
  end
209
210
 
211
+ def redis_sentinel_options
212
+ sentinels = @opts[:redis_sentinels]
213
+
214
+ return sentinels unless sentinels.is_a?(Array)
215
+
216
+ sentinels.each do |sentinel_config|
217
+ sentinel_config[:username] = @opts[:redis_sentinel_username] if @opts.key?(:redis_sentinel_username)
218
+ sentinel_config[:password] = @opts[:redis_sentinel_password] if @opts.key?(:redis_sentinel_password)
219
+ end
220
+
221
+ sentinels
222
+ end
223
+
210
224
  def redis_enable_client?
211
225
  return true if @opts[:redis_enable_client].nil?
212
226
 
@@ -221,6 +235,8 @@ module GitLab
221
235
  end
222
236
  rescue Redis::BaseConnectionError => e
223
237
  @logger&.error "Error connecting to the Redis: #{e}"
238
+ pool = self.class.connection_pool[redis_options]
239
+ pool.reload(&:close)
224
240
  @connected = false
225
241
  end
226
242
  end
@@ -1,5 +1,5 @@
1
1
  module GitLab
2
2
  module Exporter
3
- VERSION = "14.5.0".freeze
3
+ VERSION = "15.1.0".freeze
4
4
  end
5
5
  end
@@ -290,6 +290,22 @@ describe GitLab::Exporter::Database do
290
290
  expect(output).to match(/^ci_stale_builds 0.0$/m)
291
291
  end
292
292
  end
293
+
294
+ context "when PG connection shuts down" do
295
+ before do
296
+ allow(connection).to receive(:exec).and_raise(PG::ConnectionBad)
297
+ end
298
+
299
+ it "responds with Prometheus metrics" do
300
+ expect(connection_pool).to receive(:reload)
301
+
302
+ prober.probe_db
303
+ prober.write_to(writer)
304
+ output = writer.string
305
+
306
+ expect(output).to be_empty
307
+ end
308
+ end
293
309
  end
294
310
 
295
311
  context "when executed on EE" do
@@ -0,0 +1,133 @@
1
+ require "spec_helper"
2
+ require "gitlab_exporter/database/pg_sequences"
3
+
4
+ describe GitLab::Exporter::Database::PgSequencesCollector do
5
+ subject { described_class.new(connection_string: "") }
6
+
7
+ let(:connection_pool) { double("connection pool") }
8
+ let(:connection) { double("connection", exec: query_result) }
9
+ let(:type) { :btree }
10
+ let(:query_result) do
11
+ [
12
+ {
13
+ "schemaname" => "public",
14
+ "sequencename" => "seq_one",
15
+ "fully_qualified_sequencename" => "public.seq_one",
16
+ "min_value" => 1,
17
+ "max_value" => 9_223_372_036_854_775_807,
18
+ "current_value" => 6_223_372_036_854_745_121,
19
+ "saturation_ratio" => 0.6747e2
20
+ },
21
+ {
22
+ "schemaname" => "public",
23
+ "sequencename" => "seq_two",
24
+ "fully_qualified_sequencename" => "public.seq_two",
25
+ "min_value" => 1,
26
+ "max_value" => 9_223_372_036_854_775_807,
27
+ "current_value" => nil,
28
+ "saturation_ratio" => 0.0
29
+ }
30
+ ]
31
+ end
32
+
33
+ before do
34
+ allow_any_instance_of(described_class).to receive(:connection_pool).and_return(connection_pool)
35
+ allow(connection_pool).to receive(:with).and_yield(connection)
36
+ end
37
+
38
+ it "converts query results into a hash" do
39
+ expect(subject.run).to(
40
+ eq(
41
+ "public.seq_one" => {
42
+ "schemaname" => "public",
43
+ "sequencename" => "seq_one",
44
+ "min_value" => 1,
45
+ "max_value" => 9_223_372_036_854_775_807,
46
+ "current_value" => 6_223_372_036_854_745_121,
47
+ "saturation_ratio" => 0.6747e2
48
+ },
49
+ "public.seq_two" => {
50
+ "schemaname" => "public",
51
+ "sequencename" => "seq_two",
52
+ "min_value" => 1,
53
+ "max_value" => 9_223_372_036_854_775_807,
54
+ "current_value" => nil,
55
+ "saturation_ratio" => 0.0
56
+ }
57
+ )
58
+ )
59
+ end
60
+ end
61
+
62
+ describe GitLab::Exporter::Database::PgSequencesProber do
63
+ subject(:prober) { described_class.new(metrics: metrics, connection_string: "") }
64
+
65
+ let(:metrics) { double("PrometheusMetrics", add: nil) }
66
+ let(:collector) { double("PgSequencesCollector", run: data) }
67
+ let(:data) do
68
+ {
69
+ "public.seq_one" => {
70
+ "schemaname" => "public",
71
+ "sequencename" => "seq_one",
72
+ "min_value" => 1,
73
+ "max_value" => 10,
74
+ "current_value" => 2,
75
+ "saturation_ratio" => 0.2
76
+ }
77
+ }
78
+ end
79
+
80
+ describe "#probe_db" do
81
+ before do
82
+ allow(GitLab::Exporter::Database::PgSequencesCollector).to receive(:new).and_return(collector)
83
+ end
84
+
85
+ it "properly invokes the collector" do
86
+ expect(collector).to receive(:run)
87
+
88
+ prober.probe_db
89
+ end
90
+
91
+ it "adds min_value metric" do
92
+ expect(metrics).to(
93
+ receive(:add).with(
94
+ "gitlab_pg_sequences_min_value",
95
+ 1.0,
96
+ schemaname: "public",
97
+ sequencename: "seq_one",
98
+ fully_qualified_sequencename: "public.seq_one"
99
+ )
100
+ )
101
+
102
+ prober.probe_db
103
+ end
104
+
105
+ it "adds max_value metric" do
106
+ expect(metrics).to(
107
+ receive(:add).with(
108
+ "gitlab_pg_sequences_max_value",
109
+ 10.0,
110
+ schemaname: "public",
111
+ sequencename: "seq_one",
112
+ fully_qualified_sequencename: "public.seq_one"
113
+ )
114
+ )
115
+
116
+ prober.probe_db
117
+ end
118
+
119
+ it "adds current_value metric" do
120
+ expect(metrics).to(
121
+ receive(:add).with(
122
+ "gitlab_pg_sequences_current_value",
123
+ 2.0,
124
+ schemaname: "public",
125
+ sequencename: "seq_one",
126
+ fully_qualified_sequencename: "public.seq_one"
127
+ )
128
+ )
129
+
130
+ prober.probe_db
131
+ end
132
+ end
133
+ end
@@ -6,6 +6,13 @@ module GitLab
6
6
  module CLI
7
7
  describe SidekiqRunner, :integration do
8
8
  let(:redis_url) { ENV.fetch("REDIS_URL", "redis://localhost:6379") }
9
+ let(:redis_sentinel_url) { ENV.fetch("REDIS_SENTINEL_URL", "redis://mymaster:6379") }
10
+ let(:redis_sentinel_username) { ENV.fetch("REDIS_SENTINEL_USERNAME", "my-sentinel-user") }
11
+ let(:redis_sentinel_password) { ENV.fetch("REDIS_SENTINEL_PASSWORD", "my-sentinel-password") }
12
+ let(:redis_sentinels) do
13
+ sentinels = ENV.fetch("REDIS_SENTINELS", "localhost:26379")
14
+ sentinels.split(" ")
15
+ end
9
16
  let(:io) { StringIO.new }
10
17
 
11
18
  it "can properly reach out to redis" do
@@ -14,6 +21,16 @@ module GitLab
14
21
 
15
22
  expect { runner.run }.not_to raise_error
16
23
  end
24
+
25
+ it "can properly reach out to redis-sentinel" do
26
+ args = CLIArgs.new([io], options: { /^--redis-url/ => redis_sentinel_url,
27
+ /^--redis-sentinel-username/ => redis_sentinel_username,
28
+ /^--redis-sentinel-password/ => redis_sentinel_password,
29
+ /^--redis-sentinels/ => redis_sentinels })
30
+ runner = SidekiqRunner.new(args)
31
+
32
+ expect { runner.run }.not_to raise_error
33
+ end
17
34
  end
18
35
  end
19
36
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitlab-exporter
3
3
  version: !ruby/object:Gem::Version
4
- version: 14.5.0
4
+ version: 15.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pablo Carranza
@@ -227,6 +227,7 @@ files:
227
227
  - lib/gitlab_exporter/database/bloat_btree.sql
228
228
  - lib/gitlab_exporter/database/bloat_table.sql
229
229
  - lib/gitlab_exporter/database/ci_builds.rb
230
+ - lib/gitlab_exporter/database/pg_sequences.rb
230
231
  - lib/gitlab_exporter/database/remote_mirrors.rb
231
232
  - lib/gitlab_exporter/database/row_count.rb
232
233
  - lib/gitlab_exporter/database/tuple_stats.rb
@@ -247,6 +248,7 @@ files:
247
248
  - spec/cli_spec.rb
248
249
  - spec/database/bloat_spec.rb
249
250
  - spec/database/ci_builds_spec.rb
251
+ - spec/database/pg_sequences_spec.rb
250
252
  - spec/database/row_count_spec.rb
251
253
  - spec/elasticsearch_spec.rb
252
254
  - spec/fixtures/config.yml
@@ -286,6 +288,7 @@ test_files:
286
288
  - spec/cli_spec.rb
287
289
  - spec/database/bloat_spec.rb
288
290
  - spec/database/ci_builds_spec.rb
291
+ - spec/database/pg_sequences_spec.rb
289
292
  - spec/database/row_count_spec.rb
290
293
  - spec/elasticsearch_spec.rb
291
294
  - spec/fixtures/config.yml