sidekiq-prometheus-exporter 0.2.1 → 0.3.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: f39ddc3bdda39080a7b6a0b1051fb1dc90ad11be5a970b0c6f955ef50d99af66
4
- data.tar.gz: b5abf1281d9d7afdc53a452f3a5eebb480cc6b7f9e631930f02bf9be52acf8c8
3
+ metadata.gz: 992a82a14617db8daeecd9c7b100f0a96f1503ada37ad80a509121ce940a6e38
4
+ data.tar.gz: 06c79dfc4a3652acccd1bffdecff30d46987dfc0a728058b6a8d692e70b59904
5
5
  SHA512:
6
- metadata.gz: 9db770f2ff366515f412235c4cad0abdc0a7e6331420d82e9a6e24446afce711daaa65449350ec1e23eb1128a47131065472d91a6643e1f20628de6249f55009
7
- data.tar.gz: 4605aa418c294b245afae7e7201ecf0e558e778acac024f16ce173675ca70db4ae1a7a2858e43bcc26b26c91c10f0361025edf9127bc4c6a4d43515c079c7be6
6
+ metadata.gz: 9bf9f6c91f5f5596d27bce500312545ae92366b71b0efff62b57883fabf12aa3b02557357b7cff2faa9fec890756722df842471d425b0219df1920cf3ea37490
7
+ data.tar.gz: e464afd62399b31b5a3970881a8e6ea73a5f0cca4544429689ba699a275b3b5a18e95fdd7093053a85a946c8e555e06603d7ab71f999d72771a93754451ad0d3
data/.rubocop.yml CHANGED
@@ -128,5 +128,11 @@ RSpec/NestedGroups:
128
128
  RSpec/ScatteredSetup:
129
129
  Enabled: true
130
130
 
131
+ RSpec/SpecFilePathSuffix:
132
+ Enabled: false
133
+
134
+ RSpec/SpecFilePathFormat:
135
+ Enabled: false
136
+
131
137
  Rake/Desc:
132
138
  Enabled: false
data/Appraisals CHANGED
@@ -15,16 +15,19 @@ appraise 'sidekiq-5.x' do
15
15
  end
16
16
 
17
17
  appraise 'sidekiq-6.x' do
18
+ gem 'base64'
18
19
  gem 'redis', '~> 4.1'
19
20
  gem 'sidekiq', '~> 6.0'
20
21
  end
21
22
 
22
23
  appraise 'sidekiq-7.x' do
23
- gem 'redis', '~> 5.0'
24
24
  gem 'sidekiq', '~> 7.0'
25
25
  end
26
26
 
27
+ appraise 'sidekiq-8.x' do
28
+ gem 'sidekiq', '~> 8.0'
29
+ end
30
+
27
31
  appraise 'sidekiq-latest' do
28
- gem 'redis', '>= 4', github: 'redis/redis-rb'
29
- gem 'sidekiq', '>= 7', github: 'mperham/sidekiq'
32
+ gem 'sidekiq', '>= 8', github: 'mperham/sidekiq'
30
33
  end
data/Gemfile CHANGED
@@ -9,7 +9,7 @@ gem 'appraisal', '~> 2.2'
9
9
  gem 'bundler', '~> 2.1'
10
10
  gem 'pry', '~> 0.14'
11
11
  gem 'pry-byebug', '~> 3.6'
12
- gem 'rack-test', '~> 1.1'
12
+ gem 'rack-test', '>= 1.1'
13
13
  gem 'rake', '~> 13.0'
14
14
  gem 'rspec', '~> 3.0'
15
15
  gem 'rubocop', '~> 1.22'
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  ![CI status](https://github.com/Strech/sidekiq-prometheus-exporter/workflows/CI/badge.svg)
2
2
  [![Maintainability](https://api.codeclimate.com/v1/badges/bb1b30cd7aca8ecc9413/maintainability)](https://codeclimate.com/github/Strech/sidekiq-prometheus-exporter/maintainability)
3
3
 
4
+ [Sidekiq Enterprise]: https://sidekiq.org/products/enterprise.html
5
+
4
6
  # Sidekiq Prometheus Exporter
5
7
 
6
8
  > — Hey! Sidekiq dashboard stats looks like a Prometheus metrics!?
@@ -31,6 +33,7 @@ _(starting Sidekiq `v4.1.0`)_
31
33
  | sidekiq_workers | gauge | The number of workers across all the processes |
32
34
  | sidekiq_processes | gauge | The number of processes |
33
35
  | sidekiq_host_processes | gauge | The number of processes running on the host (labels: `host`, `quiet`) |
36
+ | sidekiq_host_processes_memory_usage_bytes | gauge | The amount of real memory (resident set) used by the processes running on the host (labels: `host`) |
34
37
  | sidekiq_busy_workers | gauge | The number of workers performing the job |
35
38
  | sidekiq_enqueued_jobs | gauge | The number of enqueued jobs |
36
39
  | sidekiq_scheduled_jobs | gauge | The number of jobs scheduled for a future execution |
@@ -42,6 +45,7 @@ _(starting Sidekiq `v4.1.0`)_
42
45
  | sidekiq_queue_workers | gauge | The number of workers serving the queue (labels: `name`) |
43
46
  | sidekiq_queue_processes | gauge | The number of processes serving the queue (labels: `name`) |
44
47
  | sidekiq_queue_busy_workers | gauge | The number of workers performing the job for the queue (labels: `name`) |
48
+ | sidekiq_leader_lifetime_seconds | gauge | The number of seconds since cluster leader has been created (available only on [Sidekiq Enterprise]) |
45
49
 
46
50
  <details>
47
51
  <summary>Click to expand for all available contribs</summary>
@@ -67,7 +71,7 @@ _(starting Sidekiq `v4.1.0`)_
67
71
  Add this line to your application's Gemfile:
68
72
 
69
73
  ```ruby
70
- gem 'sidekiq-prometheus-exporter', '~> 0.1'
74
+ gem 'sidekiq-prometheus-exporter', '~> 0.3'
71
75
  ```
72
76
 
73
77
  And then execute:
@@ -79,7 +83,7 @@ $ bundle
79
83
  Or install it yourself as:
80
84
 
81
85
  ```console
82
- $ gem install sidekiq-prometheus-exporter -v '~> 0.1'
86
+ $ gem install sidekiq-prometheus-exporter -v '~> 0.3'
83
87
  ```
84
88
 
85
89
  ## Rack application
data/Rakefile CHANGED
@@ -1,7 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/gem_tasks'
2
4
  require 'rspec/core/rake_task'
3
5
 
4
- RSpec::Core::RakeTask.new(:spec)
6
+ RSpec::Core::RakeTask.new(:spec) do |t, args|
7
+ require 'sidekiq/version'
8
+ directory =
9
+ case Gem::Version.new(Sidekiq::VERSION).segments[0]
10
+ when 7 then 'sidekiq-7.x'
11
+ when 8 then 'sidekiq-8.x'
12
+ else 'sidekiq'
13
+ end
14
+
15
+ t.pattern = "spec/#{directory}/**/*_spec.rb"
16
+ t.rspec_opts = args.to_a.join(' ')
17
+ end
5
18
 
6
19
  task default: :spec
7
20
 
@@ -7,17 +7,31 @@ module Sidekiq
7
7
  module Prometheus
8
8
  module Exporter
9
9
  class Standard
10
- TEMPLATE = ERB.new(File.read(File.expand_path('templates/standard.erb', __dir__)))
10
+ UNKNOWN_IDENTITY = 'unknown-identity'.freeze
11
+ BYTES_IN_KILOBYTE = 1024
12
+ OMIT_NEWLINES_MODE = '<>'.freeze
13
+ TEMPLATE = ERB.new(
14
+ File.read(File.expand_path('templates/standard.erb', __dir__)),
15
+ trim_mode: OMIT_NEWLINES_MODE
16
+ )
11
17
 
12
- QueueStats = Struct.new(:name, :size, :latency)
13
- QueueWorkersStats = Struct.new(:total_workers, :busy_workers, :processes)
14
- WorkersStats = Struct.new(:total_workers, :by_queue, :by_host)
18
+ HostStats = Struct.new(:memory_usage, :by_quiet, keyword_init: true)
19
+ QueueStats = Struct.new(:name, :size, :latency, keyword_init: true)
20
+ QueueWorkersStats = Struct.new(
21
+ :total_workers, :busy_workers, :processes, keyword_init: true
22
+ )
23
+ WorkersStats = Struct.new(
24
+ :total_workers, :by_queue, :by_host, :leader_lifetime, keyword_init: true
25
+ )
15
26
 
16
27
  def self.available?
17
28
  true
18
29
  end
19
30
 
20
31
  def initialize
32
+ # Version dependent metrics
33
+ @show_memory_usage = false
34
+
21
35
  @overview_stats = Sidekiq::Stats.new
22
36
  @queues_stats = queues_stats
23
37
  @workers_stats = workers_stats
@@ -25,46 +39,84 @@ module Sidekiq
25
39
  end
26
40
 
27
41
  def to_s
28
- TEMPLATE.result(binding).chomp!
42
+ TEMPLATE.result(binding)
29
43
  end
30
44
 
31
45
  private
32
46
 
33
47
  def queues_stats
34
48
  Sidekiq::Queue.all.map do |queue|
35
- QueueStats.new(queue.name, queue.size, queue.latency)
49
+ QueueStats.new(name: queue.name, size: queue.size, latency: queue.latency)
36
50
  end
37
51
  end
38
52
 
39
53
  def workers_stats
40
- workers_stats = WorkersStats.new(0, {}, {})
54
+ processes = Sidekiq::ProcessSet.new
55
+ workers_stats = WorkersStats.new(total_workers: 0, by_queue: {}, by_host: {})
41
56
 
42
- Sidekiq::ProcessSet.new.each_with_object(workers_stats) do |process, stats|
43
- stats.total_workers += process['concurrency'].to_i
57
+ # NOTE: available only on enterprise starting v5.0.1
58
+ leader_identity =
59
+ processes.respond_to?(:leader) ? processes.leader : UNKNOWN_IDENTITY
44
60
 
45
- stats.by_host[process['hostname']] ||= Hash.new(0)
46
- stats.by_host[process['hostname']][process['quiet']] += 1
61
+ processes.each_with_object(workers_stats) do |process, stats|
62
+ stats.total_workers += process['concurrency'].to_i
47
63
 
48
- process['queues'].each do |queue|
49
- stats.by_queue[queue] ||= QueueWorkersStats.new(0, 0, 0)
50
- stats.by_queue[queue].processes += 1
51
- stats.by_queue[queue].busy_workers += process['busy'].to_i
52
- stats.by_queue[queue].total_workers += process['concurrency'].to_i
64
+ if process['identity'] == leader_identity
65
+ stats.leader_lifetime = Time.now.utc.to_i - process['started_at']
53
66
  end
67
+
68
+ collect_process_stats_by_host(process, stats.by_host)
69
+ collect_process_stats_by_queue(process, stats.by_queue)
54
70
  end
55
71
  end
56
72
 
73
+ def collect_process_stats_by_host(process, stats)
74
+ stats[process['hostname']] ||= HostStats.new(memory_usage: 0, by_quiet: Hash.new(0))
75
+ stats[process['hostname']].by_quiet[process['quiet']] += 1
76
+ # NOTE: available only starting v6.2.0
77
+ @show_memory_usage ||= true if process['rss']
78
+ stats[process['hostname']].memory_usage += kilobytes_to_bytes(process['rss'].to_i)
79
+ end
80
+
81
+ def collect_process_stats_by_queue(process, stats)
82
+ process['queues'].each do |queue|
83
+ stats[queue] ||= QueueWorkersStats.new(
84
+ total_workers: 0, busy_workers: 0, processes: 0
85
+ )
86
+
87
+ stats[queue].processes += 1
88
+ stats[queue].busy_workers += process['busy'].to_i
89
+ stats[queue].total_workers += process['concurrency'].to_i
90
+ end
91
+ end
92
+
93
+ def kilobytes_to_bytes(kilobytes)
94
+ kilobytes * BYTES_IN_KILOBYTE
95
+ end
96
+
57
97
  def max_processing_times
98
+ return new_max_processing_times if Sidekiq.const_defined?(:Work)
99
+
58
100
  now = Time.now.to_i
59
101
 
60
102
  Sidekiq::Workers.new
61
- .map { |_, _, execution| execution }
62
- .group_by { |execution| execution['queue'] }
63
- .each_with_object({}) do |(queue, executions), memo|
64
- oldest_execution = executions.min_by { |execution| execution['run_at'] }
65
- memo[queue] = now - oldest_execution['run_at']
103
+ .each_with_object({}) { |(_, _, work), memo| (memo[work['queue']] ||= []).push(work) }
104
+ .transform_values! do |works|
105
+ oldest_work = works.min_by { |work| work['run_at'] }
106
+ now - oldest_work['run_at']
66
107
  end
67
108
  end
109
+
110
+ # TODO: Internally Sidekiq::Work stores `run_at` as a timestamp.
111
+ # It would be great to use it instead of invoking `Time.at` call.
112
+ # See: https://github.com/sidekiq/sidekiq/blob/v8.0.0/lib/sidekiq/api.rb#L1253-L1255
113
+ def new_max_processing_times
114
+ now = Time.now
115
+
116
+ Sidekiq::Workers.new
117
+ .each_with_object({}) { |(_, _, work), memo| (memo[work.queue] ||= []).push(work) }
118
+ .transform_values! { |works| now - works.min_by(&:run_at).run_at }
119
+ end
68
120
  end
69
121
  end
70
122
  end
@@ -16,8 +16,20 @@ sidekiq_processes <%= format('%d', @overview_stats.processes_size) %>
16
16
 
17
17
  # HELP sidekiq_host_processes The number of processes running on the host.
18
18
  # TYPE sidekiq_host_processes gauge
19
- <% @workers_stats.by_host.each do |host, stats| %><% stats.each do |quiet, count| %>sidekiq_host_processes{host="<%= host %>",quiet="<%= quiet %>"} <%= format('%i', count) %>
20
- <% end %><% end %>
19
+ <% @workers_stats.by_host.each do |host, stats| %>
20
+ <% stats.by_quiet.each do |quiet, count| %>
21
+ sidekiq_host_processes{host="<%= host %>",quiet="<%= quiet %>"} <%= format('%i', count) %>
22
+ <% end %>
23
+ <% end %>
24
+
25
+ <% if @show_memory_usage %>
26
+ # HELP sidekiq_host_processes_memory_usage_bytes The real memory (resident set) size of the processes running on the host.
27
+ # TYPE sidekiq_host_processes_memory_usage_bytes gauge
28
+ <% @workers_stats.by_host.each do |host, stats| %>
29
+ sidekiq_host_processes_memory_usage_bytes{host="<%= host %>"} <%= format('%i', stats.memory_usage) %>
30
+ <% end %>
31
+
32
+ <% end %>
21
33
  # HELP sidekiq_busy_workers The number of workers performing the job.
22
34
  # TYPE sidekiq_busy_workers gauge
23
35
  sidekiq_busy_workers <%= format('%d', @overview_stats.workers_size) %>
@@ -40,25 +52,42 @@ sidekiq_dead_jobs <%= format('%d', @overview_stats.dead_size) %>
40
52
 
41
53
  # HELP sidekiq_queue_latency_seconds The number of seconds between oldest job being pushed to the queue and current time.
42
54
  # TYPE sidekiq_queue_latency_seconds gauge
43
- <% @queues_stats.each do |queue| %>sidekiq_queue_latency_seconds{name="<%= queue.name %>"} <%= format('%.3f', queue.latency) %>
55
+ <% @queues_stats.each do |queue| %>
56
+ sidekiq_queue_latency_seconds{name="<%= queue.name %>"} <%= format('%.3f', queue.latency) %>
44
57
  <% end %>
58
+
45
59
  # HELP sidekiq_queue_enqueued_jobs The number of enqueued jobs in the queue.
46
60
  # TYPE sidekiq_queue_enqueued_jobs gauge
47
- <% @queues_stats.each do |queue| %>sidekiq_queue_enqueued_jobs{name="<%= queue.name %>"} <%= format('%d', queue.size) %>
61
+ <% @queues_stats.each do |queue| %>
62
+ sidekiq_queue_enqueued_jobs{name="<%= queue.name %>"} <%= format('%d', queue.size) %>
48
63
  <% end %>
64
+
49
65
  # HELP sidekiq_queue_max_processing_time_seconds The number of seconds between oldest job of the queue being executed and current time.
50
66
  # TYPE sidekiq_queue_max_processing_time_seconds gauge
51
- <% @max_processing_times.each do |queue, max_processing_time| %>sidekiq_queue_max_processing_time_seconds{name="<%= queue %>"} <%= format('%i', max_processing_time) %>
67
+ <% @max_processing_times.each do |queue, max_processing_time| %>
68
+ sidekiq_queue_max_processing_time_seconds{name="<%= queue %>"} <%= format('%i', max_processing_time) %>
52
69
  <% end %>
70
+
53
71
  # HELP sidekiq_queue_workers The number of workers serving the queue.
54
72
  # TYPE sidekiq_queue_workers gauge
55
- <% @workers_stats.by_queue.each do |queue, stats| %>sidekiq_queue_workers{name="<%= queue %>"} <%= format('%i', stats.total_workers) %>
73
+ <% @workers_stats.by_queue.each do |queue, stats| %>
74
+ sidekiq_queue_workers{name="<%= queue %>"} <%= format('%i', stats.total_workers) %>
56
75
  <% end %>
76
+
57
77
  # HELP sidekiq_queue_processes The number of processes serving the queue.
58
78
  # TYPE sidekiq_queue_processes gauge
59
- <% @workers_stats.by_queue.each do |queue, stats| %>sidekiq_queue_processes{name="<%= queue %>"} <%= format('%i', stats.processes) %>
79
+ <% @workers_stats.by_queue.each do |queue, stats| %>
80
+ sidekiq_queue_processes{name="<%= queue %>"} <%= format('%i', stats.processes) %>
60
81
  <% end %>
82
+
61
83
  # HELP sidekiq_queue_busy_workers The number of workers performing the job for the queue.
62
84
  # TYPE sidekiq_queue_busy_workers gauge
63
- <% @workers_stats.by_queue.each do |queue, stats| %>sidekiq_queue_busy_workers{name="<%= queue %>"} <%= format('%i', stats.busy_workers) %>
85
+ <% @workers_stats.by_queue.each do |queue, stats| %>
86
+ sidekiq_queue_busy_workers{name="<%= queue %>"} <%= format('%i', stats.busy_workers) %>
87
+ <% end %>
88
+ <% if @workers_stats.leader_lifetime %>
89
+
90
+ # HELP sidekiq_leader_lifetime_seconds The number of seconds since the leader process has started.
91
+ # TYPE sidekiq_leader_lifetime_seconds gauge
92
+ sidekiq_leader_lifetime_seconds <%= format('%d', @workers_stats.leader_lifetime) %>
64
93
  <% end %>
@@ -5,7 +5,7 @@ module Sidekiq
5
5
  module Exporter
6
6
  # NOTE: Every version update dropds Docker patch version to 0
7
7
  # and every adjustment in Docker setup bumps it to +1
8
- VERSION = '0.2.1'.freeze
8
+ VERSION = '0.3.0'.freeze
9
9
  DOCKER_PATCH_VERSION = '0'.freeze
10
10
  end
11
11
  end
@@ -29,7 +29,7 @@ Gem::Specification.new do |spec|
29
29
  spec.executables = spec.files.grep(%r{^exe/}) { |file| File.basename(file) }
30
30
  spec.require_paths = %w(lib)
31
31
 
32
- spec.required_ruby_version = '>= 2.3'
32
+ spec.required_ruby_version = '>= 2.5'
33
33
  spec.add_dependency 'rack', '>= 1.6.0'
34
34
  spec.add_dependency 'sidekiq', '>= 4.1.0'
35
35
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-prometheus-exporter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergey Fedorov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-11-04 00:00:00.000000000 Z
11
+ date: 2025-04-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -78,14 +78,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
78
78
  requirements:
79
79
  - - ">="
80
80
  - !ruby/object:Gem::Version
81
- version: '2.3'
81
+ version: '2.5'
82
82
  required_rubygems_version: !ruby/object:Gem::Requirement
83
83
  requirements:
84
84
  - - ">="
85
85
  - !ruby/object:Gem::Version
86
86
  version: '0'
87
87
  requirements: []
88
- rubygems_version: 3.3.26
88
+ rubygems_version: 3.5.3
89
89
  signing_key:
90
90
  specification_version: 4
91
91
  summary: Prometheus exporter for the Sidekiq