gitlab-exporter 10.3.0 → 11.0.1

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: 8e5ca8b32e5bce057dbfd998d16e3bc940440115a7540d44e4ab6a4c616c23b3
4
- data.tar.gz: 616a825e49b9a0aee99f0684ec7df670c3ad1d09a2b182e317037ac30bca031a
3
+ metadata.gz: 4a277e09b79fa17edf12f018a94a78c66cce4cc13cb4507ec6e8c82266ff4e0f
4
+ data.tar.gz: de009b471d90a44f3721e3acad8c6f25cfc3e82d3f0ad72a76e8e7b0bf9e6e45
5
5
  SHA512:
6
- metadata.gz: a346775749dce1e4e08311d42e7988dc470a46f1b52379b982bc11376b70ebf81fa4a8090447743a5624e16ec3377b638204b2d31bd4e41f2c175d8fdbe51336
7
- data.tar.gz: d1fb453077499d6de7ea8fd434e839f390ce3052f4f50fc17c0ee041ed90dcafd83e0e0a9a16ad7a50ce862752701be5a2eecc39b5306458f8feb08b293c3868
6
+ metadata.gz: 88bfdd110c7360730d076f24b96681d5b705ab926fa3925b79850bb499cc3247e3566fc132cdb5ae6877d7e533569515dcd00bd7e7b5d7e8c98c0fc77de8104d
7
+ data.tar.gz: 2658a7a6f6f735782868b43a9c1b68d3f63fd1d5ace3f8d1553bc471c50bf340f03000daded30012fb86f901f1b94cb029788e45ccbaf22f0e831d1be91fb8de
data/.gitlab-ci.yml CHANGED
@@ -6,6 +6,10 @@ include:
6
6
  - template: Security/SAST.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
7
7
  - template: Security/Secret-Detection.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-foss/-/blob/master/lib/gitlab/ci/templates/Security/Secret-Detection.gitlab-ci.yml
8
8
 
9
+ stages:
10
+ - test
11
+ - dast
12
+
9
13
  default:
10
14
  image: ruby:2.7
11
15
  cache:
data/Gemfile.lock CHANGED
@@ -1,15 +1,15 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gitlab-exporter (10.3.0)
5
- connection_pool (~> 2.2.1)
6
- pg (~> 1.1)
7
- puma (~> 5.3.2)
8
- quantile (~> 0.2.0)
9
- redis (~> 4.1.2)
10
- redis-namespace (~> 1.6.0)
11
- sidekiq (~> 5.2.1)
12
- sinatra (~> 2.0.4)
4
+ gitlab-exporter (11.0.1)
5
+ connection_pool (= 2.2.5)
6
+ pg (= 1.2.3)
7
+ puma (= 5.3.2)
8
+ quantile (= 0.2.1)
9
+ redis (= 4.1.4)
10
+ redis-namespace (= 1.6.0)
11
+ sidekiq (= 5.2.9)
12
+ sinatra (= 2.0.8.1)
13
13
 
14
14
  GEM
15
15
  remote: https://rubygems.org/
@@ -78,8 +78,6 @@ probes:
78
78
  opts:
79
79
  - pid_or_pattern: "sidekiq .* \\[.*?\\]"
80
80
  name: sidekiq
81
- - pid_or_pattern: "unicorn.* worker\\[.*?\\]"
82
- name: unicorn
83
81
  - pid_or_pattern: "git-upload-pack --stateless-rpc"
84
82
  name: git_upload_pack
85
83
  quantiles: true
@@ -20,14 +20,14 @@ Gem::Specification.new do |s|
20
20
  s.homepage = "http://gitlab.com"
21
21
  s.license = "MIT"
22
22
 
23
- s.add_runtime_dependency "connection_pool", "~> 2.2.1"
24
- s.add_runtime_dependency "pg", "~> 1.1"
25
- s.add_runtime_dependency "puma", "~> 5.3.2"
26
- s.add_runtime_dependency "quantile", "~> 0.2.0"
27
- s.add_runtime_dependency "redis", "~> 4.1.2"
28
- s.add_runtime_dependency "redis-namespace", "~> 1.6.0"
29
- s.add_runtime_dependency "sidekiq", "~> 5.2.1"
30
- s.add_runtime_dependency "sinatra", "~> 2.0.4"
23
+ s.add_runtime_dependency "connection_pool", "2.2.5"
24
+ s.add_runtime_dependency "pg", "1.2.3"
25
+ s.add_runtime_dependency "puma", "5.3.2"
26
+ s.add_runtime_dependency "quantile", "0.2.1"
27
+ s.add_runtime_dependency "redis", "4.1.4"
28
+ s.add_runtime_dependency "redis-namespace", "1.6.0"
29
+ s.add_runtime_dependency "sidekiq", "5.2.9"
30
+ s.add_runtime_dependency "sinatra", "2.0.8.1"
31
31
 
32
32
  s.add_development_dependency "rspec", "~> 3.7.0"
33
33
  s.add_development_dependency "rspec-expectations", "~> 3.7.0"
@@ -217,7 +217,7 @@ module GitLab
217
217
  opts.on("--pid=123", "Process ID") do |val|
218
218
  @pid = val
219
219
  end
220
- opts.on("--pattern=unicorn", "Process command pattern") do |val|
220
+ opts.on("--pattern=worker", "Process command pattern") do |val|
221
221
  @pattern = val
222
222
  end
223
223
  opts.on("--name=NAME", "Process name to be used in metrics") do |val|
@@ -275,7 +275,7 @@ module GitLab
275
275
  ::GitLab::Exporter::SidekiqProber.new(redis_url: @redis_url)
276
276
  .probe_stats
277
277
  .probe_queues
278
- .probe_jobs
278
+ .probe_jobs_limit
279
279
  .probe_workers
280
280
  .probe_retries
281
281
  .write_to(@target)
@@ -15,6 +15,21 @@ module GitLab
15
15
  @include_timestamp = include_timestamp
16
16
  end
17
17
 
18
+ class << self
19
+ def describe(name, description)
20
+ @metric_descriptions ||= {}
21
+ @metric_descriptions[name] = description
22
+ end
23
+
24
+ def description(name)
25
+ @metric_descriptions && @metric_descriptions[name]
26
+ end
27
+
28
+ def clear_descriptions
29
+ @metric_descriptions = {}
30
+ end
31
+ end
32
+
18
33
  def add(name, value, quantile = false, **labels)
19
34
  fail "value '#{value}' must be a number" unless value.is_a?(Numeric)
20
35
 
@@ -32,6 +47,8 @@ module GitLab
32
47
 
33
48
  buffer = ""
34
49
  @metrics.each do |name, measurements|
50
+ buffer << "# HELP #{name} #{self.class.description(name)}\n" if self.class.description(name)
51
+
35
52
  measurements.each do |measurement|
36
53
  buffer << name.to_s
37
54
  labels = (measurement[:labels] || {}).map { |label, value| "#{label}=\"#{value}\"" }.join(",")
@@ -7,8 +7,12 @@ module GitLab
7
7
  #
8
8
  # It takes the Redis URL Sidekiq is connected to
9
9
  class SidekiqProber
10
- QUEUE_JOB_STATS_SCRIPT = File.read(File.expand_path("#{__FILE__}/../sidekiq_queue_job_stats.lua")).freeze
11
- QUEUE_JOB_STATS_SHA = Digest::SHA1.hexdigest(QUEUE_JOB_STATS_SCRIPT).freeze
10
+ # The maximum depth (from the head) of each queue to probe. Probing the
11
+ # entirety of a very large queue will take longer and run the risk of
12
+ # timing out. But when we have a very large queue, we are most in need of
13
+ # reliable metrics. This trades off completeness for predictability by
14
+ # only taking a limited amount of items from the head of the queue.
15
+ PROBE_JOBS_LIMIT = 1_000
12
16
 
13
17
  POOL_SIZE = 3
14
18
 
@@ -17,6 +21,9 @@ module GitLab
17
21
  # needed to be re-initialized
18
22
  POOL_TIMEOUT = 90
19
23
 
24
+ PrometheusMetrics.describe("sidekiq_enqueued_jobs",
25
+ "Total number of jobs enqueued by class name. Only inspects the first #{PROBE_JOBS_LIMIT} jobs per queue.") # rubocop:disable Layout/LineLength
26
+
20
27
  def self.connection_pool
21
28
  @@connection_pool ||= Hash.new do |h, connection_hash| # rubocop:disable Style/ClassVars
22
29
  config = connection_hash.merge(pool_timeout: POOL_TIMEOUT, size: POOL_SIZE)
@@ -63,17 +70,30 @@ module GitLab
63
70
  end
64
71
 
65
72
  def probe_jobs
73
+ puts "[REMOVED] probe_jobs is now considered obsolete and does not emit any metrics,"\
74
+ " please use probe_jobs_limit instead"
75
+
76
+ self
77
+ end
78
+
79
+ # Count worker classes present in Sidekiq queues. This only looks at the
80
+ # first PROBE_JOBS_LIMIT jobs in each queue. This means that we run a
81
+ # single LRANGE command for each queue, which does not block other
82
+ # commands. For queues over PROBE_JOBS_LIMIT in size, this means that we
83
+ # will not have completely accurate statistics, but the probe performance
84
+ # will also not degrade as the queue gets larger.
85
+ def probe_jobs_limit
66
86
  with_sidekiq do
67
- job_stats = {}
87
+ job_stats = Hash.new(0)
68
88
 
69
89
  Sidekiq::Queue.all.each do |queue|
70
90
  Sidekiq.redis do |conn|
71
- stats = conn.evalsha(QUEUE_JOB_STATS_SHA, ["queue:#{queue.name}"])
72
- job_stats.merge!(stats.to_h)
91
+ conn.lrange("queue:#{queue.name}", 0, PROBE_JOBS_LIMIT).each do |job|
92
+ job_class = Sidekiq.load_json(job)["class"]
93
+
94
+ job_stats[job_class] += 1
95
+ end
73
96
  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
77
97
  end
78
98
 
79
99
  job_stats.each do |class_name, count|
@@ -169,14 +189,9 @@ module GitLab
169
189
  def connected?
170
190
  return @connected unless @connected.nil?
171
191
 
172
- # This is also a good "connected check"
173
192
  Sidekiq.redis do |conn|
174
- # Using administrative commands on conn directly (which is a Redis::Namespace)
175
- # will be removed in redis-namespace 2.0.
176
- conn.redis.script(:load, QUEUE_JOB_STATS_SCRIPT) unless conn.redis.script(:exists, QUEUE_JOB_STATS_SHA)
193
+ @connected = (conn.ping == "PONG")
177
194
  end
178
-
179
- @connected = true
180
195
  rescue Redis::BaseConnectionError => e
181
196
  @logger&.error "Error connecting to the Redis: #{e}"
182
197
  @connected = false
@@ -1,5 +1,5 @@
1
1
  module GitLab
2
2
  module Exporter
3
- VERSION = "10.3.0".freeze
3
+ VERSION = "11.0.1".freeze
4
4
  end
5
5
  end
@@ -23,4 +23,21 @@ describe GitLab::Exporter::PrometheusMetrics do
23
23
  subject.add("mymetric", "invalid", mylabel: "x", myotherlabel: "y").to_s
24
24
  }.to raise_error(RuntimeError)
25
25
  end
26
+
27
+ it "supports described metrics" do
28
+ time = Time.now
29
+
30
+ allow(Time).to receive(:now).and_return(time)
31
+
32
+ described_class.describe("mymetric", "description")
33
+ described_class.describe("missingmetric", "otherdescription")
34
+ subject.add("mymetric", 1.3, mylabel: "x", myotherlabel: "y")
35
+
36
+ expect(subject.to_s).to eq(<<~METRICS)
37
+ # HELP mymetric description
38
+ mymetric{mylabel="x",myotherlabel="y"} 1.3 #{(time.to_f * 1000).to_i}
39
+ METRICS
40
+
41
+ described_class.clear_descriptions
42
+ end
26
43
  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: 10.3.0
4
+ version: 11.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pablo Carranza
@@ -14,114 +14,114 @@ dependencies:
14
14
  name: connection_pool
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 2.2.1
19
+ version: 2.2.5
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 2.2.1
26
+ version: 2.2.5
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: pg
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: '1.1'
33
+ version: 1.2.3
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: '1.1'
40
+ version: 1.2.3
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: puma
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - '='
46
46
  - !ruby/object:Gem::Version
47
47
  version: 5.3.2
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - '='
53
53
  - !ruby/object:Gem::Version
54
54
  version: 5.3.2
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: quantile
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - '='
60
60
  - !ruby/object:Gem::Version
61
- version: 0.2.0
61
+ version: 0.2.1
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - '='
67
67
  - !ruby/object:Gem::Version
68
- version: 0.2.0
68
+ version: 0.2.1
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: redis
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - "~>"
73
+ - - '='
74
74
  - !ruby/object:Gem::Version
75
- version: 4.1.2
75
+ version: 4.1.4
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - "~>"
80
+ - - '='
81
81
  - !ruby/object:Gem::Version
82
- version: 4.1.2
82
+ version: 4.1.4
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: redis-namespace
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - "~>"
87
+ - - '='
88
88
  - !ruby/object:Gem::Version
89
89
  version: 1.6.0
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - "~>"
94
+ - - '='
95
95
  - !ruby/object:Gem::Version
96
96
  version: 1.6.0
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: sidekiq
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - "~>"
101
+ - - '='
102
102
  - !ruby/object:Gem::Version
103
- version: 5.2.1
103
+ version: 5.2.9
104
104
  type: :runtime
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - "~>"
108
+ - - '='
109
109
  - !ruby/object:Gem::Version
110
- version: 5.2.1
110
+ version: 5.2.9
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: sinatra
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
- - - "~>"
115
+ - - '='
116
116
  - !ruby/object:Gem::Version
117
- version: 2.0.4
117
+ version: 2.0.8.1
118
118
  type: :runtime
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
- - - "~>"
122
+ - - '='
123
123
  - !ruby/object:Gem::Version
124
- version: 2.0.4
124
+ version: 2.0.8.1
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: rspec
127
127
  requirement: !ruby/object:Gem::Requirement
@@ -189,7 +189,6 @@ files:
189
189
  - lib/gitlab_exporter/prometheus.rb
190
190
  - lib/gitlab_exporter/ruby.rb
191
191
  - lib/gitlab_exporter/sidekiq.rb
192
- - lib/gitlab_exporter/sidekiq_queue_job_stats.lua
193
192
  - lib/gitlab_exporter/util.rb
194
193
  - lib/gitlab_exporter/version.rb
195
194
  - lib/gitlab_exporter/web_exporter.rb
@@ -1,42 +0,0 @@
1
- --
2
- -- Adapted from https://github.com/mperham/sidekiq/blob/2f9258e4fe77991c526f7a65c92bcf792eef8338/lib/sidekiq/api.rb#L231
3
- --
4
- local queue_name = KEYS[1]
5
- local initial_size = redis.call('llen', queue_name)
6
- local deleted_size = 0
7
- local page = 0
8
- local page_size = 2000
9
- local temp_job_stats = {}
10
- local final_job_stats = {}
11
-
12
- while true do
13
- local range_start = page * page_size - deleted_size
14
- local range_end = range_start + page_size - 1
15
- local entries = redis.call('lrange', queue_name, range_start, range_end)
16
-
17
- if #entries == 0 then
18
- break
19
- end
20
-
21
- page = page + 1
22
-
23
- for index, entry in next, entries do
24
- local class = cjson.decode(entry)['class']
25
- if class ~= nil then
26
- if temp_job_stats[class] ~= nil then
27
- temp_job_stats[class] = temp_job_stats[class] + 1
28
- else
29
- temp_job_stats[class] = 1
30
- end
31
- end
32
- end
33
-
34
- deleted_size = initial_size - redis.call('llen', queue_name)
35
- end
36
-
37
- for class, count in next, temp_job_stats do
38
- local stat_entry = {class, count}
39
- table.insert(final_job_stats, stat_entry)
40
- end
41
-
42
- return final_job_stats