gitlab-exporter 10.2.0 → 11.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: b54a7fd5cab81e9ffb2fe44bdff47d707c2a1cb2d31aa606246fd66ed3b15adf
4
- data.tar.gz: f0d096e1a97a28f81ff360bad1925aa9abb764724387ec92f843a5184fead7b0
3
+ metadata.gz: 8f22f9c58b0d64fdec6134fa8b59cfd474f1333ebe59a8a94c620749fc4be33c
4
+ data.tar.gz: 0dc26d62d7aab597a13aab640844a42774a85c2ce6459f9eeeef7441b81f3d19
5
5
  SHA512:
6
- metadata.gz: ab3fe334be8aeab22baf7ddddd2d4d6e650d6a8d30e290c98ef440205bf522c80338f311796bd1a2003a780a55ae8d5e9bd1fd71a51c3c61ed394a297fae700a
7
- data.tar.gz: 874907c793cb4bf1947cc40a4fbf0bbfb70eb95a5769971688f64285346b01bf87ec45bd367cb44aec0c58fb3d1bcd369500d46e0f498a8a1fc0c73757d9c08f
6
+ metadata.gz: 56a1c9f3a267b32ead646a1c6a95a8d81744498d67613e0c6dbb849a15e624d96215800a1688504e08c8b25a2b1222a4b86f2e3b1cb7b898ff892f6a445dcdbd
7
+ data.tar.gz: e08257d194617fd5467526403a8629aeab1d46719bb34c57b6aaaa77cb94b138b139544771b071bfccc6ec47e8440d1241ae3adb8fceb226694366d4e89a058f
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.2.0)
5
- connection_pool (~> 2.2.1)
6
- pg (~> 1.1)
7
- puma (~> 5.1.1)
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.0)
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/
@@ -24,7 +24,7 @@ GEM
24
24
  parser (3.0.0.0)
25
25
  ast (~> 2.4.1)
26
26
  pg (1.2.3)
27
- puma (5.1.1)
27
+ puma (5.3.2)
28
28
  nio4r (~> 2.0)
29
29
  quantile (0.2.1)
30
30
  rack (2.2.3)
@@ -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.1.1"
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|
@@ -1,5 +1,5 @@
1
1
  module GitLab
2
2
  module Exporter
3
- VERSION = "10.2.0".freeze
3
+ VERSION = "11.0.0".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.2.0
4
+ version: 11.0.0
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
- version: 5.1.1
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
- version: 5.1.1
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