gitlab-exporter 14.5.0 → 15.1.0

Sign up to get free protection for your applications and to get access to all the features.
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