gitlab-exporter 9.1.0 → 10.0.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: 04f135f6746fff19e960b1ec4c46226541cf5522b36d8f12618bdd1a8dad2998
4
- data.tar.gz: 3dfb6dd9751dab33bd5eba3994f380d22dd1a25cc00a4d5b8071aa97e4e5d894
3
+ metadata.gz: 0f7115d7a7e8604d15ad7734afd3280c22edf9f6911af159f9386f2a9d495701
4
+ data.tar.gz: 7b050b23851b8073da229a5347b0c22afd641a1420859f37f0a0b8a7a4113244
5
5
  SHA512:
6
- metadata.gz: 58fde6e70e4f5f1c10ec821866390867317a3d337aacb51ced2a8f0ab28b01c614909fa6abdec7063b73f4b9a23f6da49928a876a161baf715e165dc30607b2a
7
- data.tar.gz: 8f420a0fb33a6d109a0645bcd1d178d13a57e8d049c52c65e168eaeeeb680a42e491df01e8a4f92fc6460f3f3272638af91a4a25f04cd86e4e24a899da300d2a
6
+ metadata.gz: 848007d860313f8d62239b3f97a9024944aec9ddbfe6ea97472eaae64ddfc6e03cc056fe9466ce719209a6fad2b5a88947ff085f25ed6cf4ecf6421c1db576d7
7
+ data.tar.gz: f751e8af1eea6d30eb00dae65ec41681b50e9481a392b6521fde15c07934d8550dd2c3e3b65ea13ae6cbc0adadfe30986bad418c88172699b3172d20f9f62a75
@@ -1,9 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gitlab-exporter (9.1.0)
4
+ gitlab-exporter (10.0.0)
5
5
  connection_pool (~> 2.2.1)
6
6
  pg (~> 1.1)
7
+ puma (~> 5.1.1)
7
8
  quantile (~> 0.2.0)
8
9
  redis (~> 4.1.2)
9
10
  redis-namespace (~> 1.6.0)
@@ -18,10 +19,13 @@ GEM
18
19
  diff-lcs (1.3)
19
20
  mustermann (1.1.1)
20
21
  ruby2_keywords (~> 0.0.1)
22
+ nio4r (2.5.4)
21
23
  parallel (1.20.1)
22
24
  parser (3.0.0.0)
23
25
  ast (~> 2.4.1)
24
26
  pg (1.2.3)
27
+ puma (5.1.1)
28
+ nio4r (~> 2.0)
25
29
  quantile (0.2.1)
26
30
  rack (2.2.3)
27
31
  rack-protection (2.0.8.1)
@@ -57,7 +61,7 @@ GEM
57
61
  rubocop-ast (1.4.0)
58
62
  parser (>= 2.7.1.5)
59
63
  ruby-progressbar (1.11.0)
60
- ruby2_keywords (0.0.2)
64
+ ruby2_keywords (0.0.4)
61
65
  sidekiq (5.2.9)
62
66
  connection_pool (~> 2.2, >= 2.2.2)
63
67
  rack (~> 2.0)
@@ -2,6 +2,8 @@
2
2
 
3
3
  $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
4
4
 
5
+ require "rubygems"
6
+ require "bundler/setup"
5
7
  require "optparse"
6
8
  require "gitlab_exporter"
7
9
 
@@ -95,12 +95,21 @@ probes:
95
95
  redis_url: "redis://localhost:6379"
96
96
  redis_enable_client: true
97
97
 
98
+ ruby: &ruby
99
+ class_name: RubyProber
100
+ methods:
101
+ - probe_gc
102
+ opts:
103
+ quantiles: false
104
+
98
105
  metrics:
99
106
  multiple: true
100
107
  git_process:
101
108
  <<: *git_process
102
109
  process:
103
110
  <<: *process
111
+ ruby:
112
+ <<: *ruby
104
113
  sidekiq:
105
114
  <<: *sidekiq
106
115
  ci_builds:
@@ -22,6 +22,7 @@ Gem::Specification.new do |s|
22
22
 
23
23
  s.add_runtime_dependency "connection_pool", "~> 2.2.1"
24
24
  s.add_runtime_dependency "pg", "~> 1.1"
25
+ s.add_runtime_dependency "puma", "~> 5.1.1"
25
26
  s.add_runtime_dependency "quantile", "~> 0.2.0"
26
27
  s.add_runtime_dependency "redis", "~> 4.1.2"
27
28
  s.add_runtime_dependency "redis-namespace", "~> 1.6.0"
@@ -14,5 +14,6 @@ module GitLab
14
14
  autoload :WebExporter, "gitlab_exporter/web_exporter"
15
15
  autoload :Prober, "gitlab_exporter/prober"
16
16
  autoload :SidekiqProber, "gitlab_exporter/sidekiq"
17
+ autoload :RubyProber, "gitlab_exporter/ruby"
17
18
  end
18
19
  end
@@ -8,14 +8,39 @@ module GitLab
8
8
  #
9
9
  # It takes a connection string (e.g. "dbname=test port=5432")
10
10
  class Base
11
+ POOL_SIZE = 3
12
+
13
+ # This timeout is configured to higher interval than scrapping
14
+ # of Prometheus to ensure that connection is kept instead of
15
+ # needed to be re-initialized
16
+ POOL_TIMEOUT = 90
17
+
11
18
  def self.connection_pool
12
- @connection_pool ||= Hash.new do |h, connection_string|
13
- h[connection_string] = ConnectionPool.new(size: 3, timeout: 5) do
14
- PG.connect(connection_string)
19
+ @@connection_pool ||= Hash.new do |h, connection_string| # rubocop:disable Style/ClassVars
20
+ h[connection_string] = ConnectionPool.new(size: POOL_SIZE, timeout: POOL_TIMEOUT) do
21
+ PG.connect(connection_string).tap do |conn|
22
+ configure_type_map_for_results(conn)
23
+ end
15
24
  end
16
25
  end
17
26
  end
18
27
 
28
+ def self.configure_type_map_for_results(conn)
29
+ tm = PG::BasicTypeMapForResults.new(conn)
30
+
31
+ # Remove warning message:
32
+ # Warning: no type cast defined for type "name" with oid 19.
33
+ # Please cast this type explicitly to TEXT to be safe for future changes.
34
+ # Warning: no type cast defined for type "regproc" with oid 24.
35
+ # Please cast this type explicitly to TEXT to be safe for future changes.
36
+ [{ "type": "text", "oid": 19 }, { "type": "int4", "oid": 24 }].each do |value|
37
+ old_coder = tm.coders.find { |c| c.name == value[:type] }
38
+ tm.add_coder(old_coder.dup.tap { |c| c.oid = value[:oid] })
39
+ end
40
+
41
+ conn.type_map_for_results = tm
42
+ end
43
+
19
44
  def initialize(args, logger: nil)
20
45
  @connection_string = args[:connection_string]
21
46
  @logger = logger
@@ -175,30 +175,12 @@ module GitLab
175
175
 
176
176
  def execute(query)
177
177
  with_connection_pool do |conn|
178
- conn.exec(query).map_types!(type_map_for_results(conn))
178
+ conn.exec(query)
179
179
  end
180
180
  rescue PG::UndefinedTable, PG::UndefinedColumn
181
181
  nil
182
182
  end
183
183
 
184
- def type_map_for_results(conn)
185
- @type_map_for_results ||= begin
186
- tm = PG::BasicTypeMapForResults.new(conn)
187
-
188
- # Remove warning message:
189
- # Warning: no type cast defined for type "name" with oid 19.
190
- # Please cast this type explicitly to TEXT to be safe for future changes.
191
- # Warning: no type cast defined for type "regproc" with oid 24.
192
- # Please cast this type explicitly to TEXT to be safe for future changes.
193
- [{ "type": "text", "oid": 19 }, { "type": "int4", "oid": 24 }].each do |value|
194
- old_coder = tm.coders.find { |c| c.name == value[:type] }
195
- tm.add_coder(old_coder.dup.tap { |c| c.oid = value[:oid] })
196
- end
197
-
198
- tm
199
- end
200
- end
201
-
202
184
  # Not private so I can test it without meta programming tricks
203
185
  def construct_query(query)
204
186
  query_string = "SELECT COUNT(*)"
@@ -93,6 +93,10 @@ module GitLab
93
93
  end
94
94
 
95
95
  def probe_git # rubocop:disable Metrics/MethodLength
96
+ puts "[DEPRECATED] probe_git and GitProcessProber are now considered obsolete"\
97
+ " and will be removed in future major versions,"\
98
+ " please use git metrics produced by Gitaly instead"
99
+
96
100
  counts = Hash.new(0)
97
101
 
98
102
  Utils.pgrep("^git ").each do |pid|
@@ -71,6 +71,10 @@ module GitLab
71
71
  end
72
72
 
73
73
  def probe_stat
74
+ puts "[DEPRECATED] probe_stat and ProcessProber are now considered obsolete"\
75
+ " and will be removed in future major versions,"\
76
+ " please use metrics produced by application processes instead"
77
+
74
78
  @pids.each do |pid|
75
79
  stats = ProcessStats.new(pid)
76
80
  next unless stats.valid?
@@ -88,12 +92,20 @@ module GitLab
88
92
  end
89
93
 
90
94
  def probe_count
95
+ puts "[DEPRECATED] probe_count and ProcessProber are now considered obsolete"\
96
+ " and will be removed in future major versions,"\
97
+ " please use metrics produced by application processes instead"
98
+
91
99
  @metrics.add("process_count", @pids.count, name: @name.downcase)
92
100
 
93
101
  self
94
102
  end
95
103
 
96
104
  def probe_smaps
105
+ puts "[DEPRECATED] probe_smaps and ProcessProber are now considered obsolete"\
106
+ " and will be removed in future major versions,"\
107
+ " please use metrics produced by application processes instead"
108
+
97
109
  @pids.each do |pid|
98
110
  stats = ::GitLab::Exporter::MemStats::Aggregator.new(pid)
99
111
 
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitLab
4
+ module Exporter
5
+ # Probes a current process GC for info then writes metrics to a target
6
+ class RubyProber
7
+ def initialize(options, metrics: PrometheusMetrics.new, logger: nil) # rubocop:disable Lint/UnusedMethodArgument
8
+ @metrics = metrics
9
+ @use_quantiles = options.fetch(:quantiles, false)
10
+ end
11
+
12
+ def probe_gc
13
+ GC.stat.each do |stat, value|
14
+ @metrics.add("ruby_gc_stat_#{stat}", value.to_i, @use_quantiles)
15
+ end
16
+
17
+ self
18
+ end
19
+ end
20
+ end
21
+ end
@@ -10,100 +10,109 @@ module GitLab
10
10
  QUEUE_JOB_STATS_SCRIPT = File.read(File.expand_path("#{__FILE__}/../sidekiq_queue_job_stats.lua")).freeze
11
11
  QUEUE_JOB_STATS_SHA = Digest::SHA1.hexdigest(QUEUE_JOB_STATS_SCRIPT).freeze
12
12
 
13
+ POOL_SIZE = 3
14
+
15
+ # This timeout is configured to higher interval than scrapping
16
+ # of Prometheus to ensure that connection is kept instead of
17
+ # needed to be re-initialized
18
+ POOL_TIMEOUT = 90
19
+
20
+ def self.connection_pool
21
+ @@connection_pool ||= Hash.new do |h, connection_hash| # rubocop:disable Style/ClassVars
22
+ config = connection_hash.merge(pool_timeout: POOL_TIMEOUT, size: POOL_SIZE)
23
+
24
+ h[connection_hash] = Sidekiq::RedisConnection.create(config)
25
+ end
26
+ end
27
+
13
28
  def initialize(opts, metrics: PrometheusMetrics.new, logger: nil)
14
29
  @opts = opts
15
30
  @metrics = metrics
16
31
  @logger = logger
17
-
18
- Sidekiq.configure_client do |config|
19
- config.redis = redis_options
20
- end
21
-
22
- ensure_queue_job_stats_script_loaded
23
32
  end
24
33
 
25
34
  def probe_stats
26
- return self unless connected?
27
-
28
- stats = Sidekiq::Stats.new
29
-
30
- @metrics.add("sidekiq_jobs_processed_total", stats.processed)
31
- @metrics.add("sidekiq_jobs_failed_total", stats.failed)
32
- @metrics.add("sidekiq_jobs_enqueued_size", stats.enqueued)
33
- @metrics.add("sidekiq_jobs_scheduled_size", stats.scheduled_size)
34
- @metrics.add("sidekiq_jobs_retry_size", stats.retry_size)
35
- @metrics.add("sidekiq_jobs_dead_size", stats.dead_size)
36
-
37
- @metrics.add("sidekiq_default_queue_latency_seconds", stats.default_queue_latency)
38
- @metrics.add("sidekiq_processes_size", stats.processes_size)
39
- @metrics.add("sidekiq_workers_size", stats.workers_size)
35
+ with_sidekiq do
36
+ stats = Sidekiq::Stats.new
37
+
38
+ @metrics.add("sidekiq_jobs_processed_total", stats.processed)
39
+ @metrics.add("sidekiq_jobs_failed_total", stats.failed)
40
+ @metrics.add("sidekiq_jobs_enqueued_size", stats.enqueued)
41
+ @metrics.add("sidekiq_jobs_scheduled_size", stats.scheduled_size)
42
+ @metrics.add("sidekiq_jobs_retry_size", stats.retry_size)
43
+ @metrics.add("sidekiq_jobs_dead_size", stats.dead_size)
44
+
45
+ @metrics.add("sidekiq_default_queue_latency_seconds", stats.default_queue_latency)
46
+ @metrics.add("sidekiq_processes_size", stats.processes_size)
47
+ @metrics.add("sidekiq_workers_size", stats.workers_size)
48
+ end
40
49
 
41
50
  self
42
51
  end
43
52
 
44
53
  def probe_queues
45
- return self unless connected?
46
-
47
- Sidekiq::Queue.all.each do |queue|
48
- @metrics.add("sidekiq_queue_size", queue.size, name: queue.name)
49
- @metrics.add("sidekiq_queue_latency_seconds", queue.latency, name: queue.name)
50
- @metrics.add("sidekiq_queue_paused", queue.paused? ? 1 : 0, name: queue.name)
54
+ with_sidekiq do
55
+ Sidekiq::Queue.all.each do |queue|
56
+ @metrics.add("sidekiq_queue_size", queue.size, name: queue.name)
57
+ @metrics.add("sidekiq_queue_latency_seconds", queue.latency, name: queue.name)
58
+ @metrics.add("sidekiq_queue_paused", queue.paused? ? 1 : 0, name: queue.name)
59
+ end
51
60
  end
52
61
 
53
62
  self
54
63
  end
55
64
 
56
65
  def probe_jobs
57
- return self unless connected?
58
-
59
- job_stats = {}
60
-
61
- Sidekiq::Queue.all.each do |queue|
62
- Sidekiq.redis do |conn|
63
- stats = conn.evalsha(QUEUE_JOB_STATS_SHA, ["queue:#{queue.name}"])
64
- job_stats.merge!(stats.to_h)
66
+ with_sidekiq do
67
+ job_stats = {}
68
+
69
+ Sidekiq::Queue.all.each do |queue|
70
+ Sidekiq.redis do |conn|
71
+ stats = conn.evalsha(QUEUE_JOB_STATS_SHA, ["queue:#{queue.name}"])
72
+ job_stats.merge!(stats.to_h)
73
+ end
74
+ rescue Redis::CommandError # Could happen if the script exceeded the maximum run time (5 seconds by default)
75
+ # FIXME: Should we call SCRIPT KILL?
76
+ return self
65
77
  end
66
- rescue Redis::CommandError # Could happen if the script exceeded the maximum run time (5 seconds by default)
67
- # FIXME: Should we call SCRIPT KILL?
68
- return self
69
- end
70
78
 
71
- job_stats.each do |class_name, count|
72
- @metrics.add("sidekiq_enqueued_jobs", count, name: class_name)
79
+ job_stats.each do |class_name, count|
80
+ @metrics.add("sidekiq_enqueued_jobs", count, name: class_name)
81
+ end
73
82
  end
74
83
 
75
84
  self
76
85
  end
77
86
 
78
87
  def probe_workers
79
- return self unless connected?
80
-
81
- worker_stats = Hash.new(0)
88
+ with_sidekiq do
89
+ worker_stats = Hash.new(0)
82
90
 
83
- Sidekiq::Workers.new.map do |_pid, _tid, work|
84
- job_klass = work["payload"]["class"]
91
+ Sidekiq::Workers.new.map do |_pid, _tid, work|
92
+ job_klass = work["payload"]["class"]
85
93
 
86
- worker_stats[job_klass] += 1
87
- end
94
+ worker_stats[job_klass] += 1
95
+ end
88
96
 
89
- worker_stats.each do |class_name, count|
90
- @metrics.add("sidekiq_running_jobs", count, name: class_name)
97
+ worker_stats.each do |class_name, count|
98
+ @metrics.add("sidekiq_running_jobs", count, name: class_name)
99
+ end
91
100
  end
92
101
 
93
102
  self
94
103
  end
95
104
 
96
105
  def probe_retries
97
- return self unless connected?
106
+ with_sidekiq do
107
+ retry_stats = Hash.new(0)
98
108
 
99
- retry_stats = Hash.new(0)
100
-
101
- Sidekiq::RetrySet.new.map do |job|
102
- retry_stats[job.klass] += 1
103
- end
109
+ Sidekiq::RetrySet.new.map do |job|
110
+ retry_stats[job.klass] += 1
111
+ end
104
112
 
105
- retry_stats.each do |class_name, count|
106
- @metrics.add("sidekiq_to_be_retried_jobs", count, name: class_name)
113
+ retry_stats.each do |class_name, count|
114
+ @metrics.add("sidekiq_to_be_retried_jobs", count, name: class_name)
115
+ end
107
116
  end
108
117
 
109
118
  self
@@ -113,9 +122,9 @@ module GitLab
113
122
  puts "[DEPRECATED] probe_dead is now considered obsolete and will be removed in future major versions,"\
114
123
  " please use probe_stats instead"
115
124
 
116
- return self unless connected?
117
-
118
- @metrics.add("sidekiq_dead_jobs", Sidekiq::Stats.new.dead_size)
125
+ with_sidekiq do
126
+ @metrics.add("sidekiq_dead_jobs", Sidekiq::Stats.new.dead_size)
127
+ end
119
128
 
120
129
  self
121
130
  end
@@ -126,6 +135,19 @@ module GitLab
126
135
 
127
136
  private
128
137
 
138
+ def with_sidekiq
139
+ # TODO: this is not concurrent safe as we change global context
140
+ # It means that we are unable to use many different sidekiq's
141
+ # which is not a problem as of now
142
+ Sidekiq.configure_client do |config|
143
+ config.redis = self.class.connection_pool[redis_options]
144
+ end
145
+
146
+ return unless connected?
147
+
148
+ yield
149
+ end
150
+
129
151
  def redis_options
130
152
  options = {
131
153
  url: @opts[:redis_url],
@@ -145,26 +167,19 @@ module GitLab
145
167
  end
146
168
 
147
169
  def connected?
148
- @connected ||= begin
149
- Sidekiq.redis do |conn|
150
- conn.get("foo")
151
- end
152
- true
153
- end
154
- rescue Redis::CannotConnectError, Redis::TimeoutError
155
- # Maybe we're trying connecting to a slave
156
- end
157
-
158
- def ensure_queue_job_stats_script_loaded
159
- return unless connected?
170
+ return @connected unless @connected.nil?
160
171
 
172
+ # This is also a good "connected check"
161
173
  Sidekiq.redis do |conn|
162
174
  # Using administrative commands on conn directly (which is a Redis::Namespace)
163
175
  # will be removed in redis-namespace 2.0.
164
- next if conn.redis.script(:exists, QUEUE_JOB_STATS_SHA)
165
-
166
- conn.redis.script(:load, QUEUE_JOB_STATS_SCRIPT)
176
+ conn.redis.script(:load, QUEUE_JOB_STATS_SCRIPT) unless conn.redis.script(:exists, QUEUE_JOB_STATS_SHA)
167
177
  end
178
+
179
+ @connected = true
180
+ rescue Redis::BaseConnectionError => e
181
+ @logger&.error "Error connecting to the Redis: #{e}"
182
+ @connected = false
168
183
  end
169
184
  end
170
185
  end
@@ -1,5 +1,5 @@
1
1
  module GitLab
2
2
  module Exporter
3
- VERSION = "9.1.0".freeze
3
+ VERSION = "10.0.0".freeze
4
4
  end
5
5
  end
@@ -0,0 +1,25 @@
1
+ require "spec_helper"
2
+ require "gitlab_exporter/ruby"
3
+ require "gitlab_exporter/prober"
4
+
5
+ describe GitLab::Exporter::RubyProber do
6
+ let(:prober) { GitLab::Exporter::Prober.new(options) }
7
+
8
+ let(:options) do
9
+ {
10
+ ruby: {
11
+ class_name: described_class.to_s,
12
+ methods: %w[probe_gc],
13
+ opts: { quantiles: false }
14
+ }
15
+ }
16
+ end
17
+
18
+ it "probes and returns GC stats" do
19
+ prober.probe_all
20
+
21
+ output = StringIO.new
22
+ prober.write_to(output)
23
+ expect(output.string).to match(/ruby_gc_stat_count \d+ \d+/)
24
+ end
25
+ 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: 9.1.0
4
+ version: 10.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pablo Carranza
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: puma
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 5.1.1
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 5.1.1
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: quantile
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -173,6 +187,7 @@ files:
173
187
  - lib/gitlab_exporter/prober.rb
174
188
  - lib/gitlab_exporter/process.rb
175
189
  - lib/gitlab_exporter/prometheus.rb
190
+ - lib/gitlab_exporter/ruby.rb
176
191
  - lib/gitlab_exporter/sidekiq.rb
177
192
  - lib/gitlab_exporter/sidekiq_queue_job_stats.lua
178
193
  - lib/gitlab_exporter/util.rb
@@ -187,6 +202,7 @@ files:
187
202
  - spec/git_spec.rb
188
203
  - spec/memstats_spec.rb
189
204
  - spec/prometheus_metrics_spec.rb
205
+ - spec/ruby_spec.rb
190
206
  - spec/spec_helper.rb
191
207
  - spec/util_spec.rb
192
208
  homepage: http://gitlab.com
@@ -222,5 +238,6 @@ test_files:
222
238
  - spec/git_spec.rb
223
239
  - spec/memstats_spec.rb
224
240
  - spec/prometheus_metrics_spec.rb
241
+ - spec/ruby_spec.rb
225
242
  - spec/spec_helper.rb
226
243
  - spec/util_spec.rb