sidekiq 6.5.12 → 7.3.9
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 +4 -4
- data/Changes.md +340 -20
- data/README.md +43 -35
- data/bin/multi_queue_bench +271 -0
- data/bin/sidekiq +3 -8
- data/bin/sidekiqload +213 -118
- data/bin/sidekiqmon +3 -0
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +75 -0
- data/lib/generators/sidekiq/job_generator.rb +2 -0
- data/lib/sidekiq/api.rb +243 -162
- data/lib/sidekiq/capsule.rb +132 -0
- data/lib/sidekiq/cli.rb +60 -75
- data/lib/sidekiq/client.rb +87 -38
- data/lib/sidekiq/component.rb +26 -1
- data/lib/sidekiq/config.rb +311 -0
- data/lib/sidekiq/deploy.rb +64 -0
- data/lib/sidekiq/embedded.rb +63 -0
- data/lib/sidekiq/fetch.rb +11 -14
- data/lib/sidekiq/iterable_job.rb +55 -0
- data/lib/sidekiq/job/interrupt_handler.rb +24 -0
- data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
- data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
- data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
- data/lib/sidekiq/job/iterable.rb +294 -0
- data/lib/sidekiq/job.rb +382 -10
- data/lib/sidekiq/job_logger.rb +8 -7
- data/lib/sidekiq/job_retry.rb +42 -19
- data/lib/sidekiq/job_util.rb +53 -15
- data/lib/sidekiq/launcher.rb +71 -65
- data/lib/sidekiq/logger.rb +2 -27
- data/lib/sidekiq/manager.rb +9 -11
- data/lib/sidekiq/metrics/query.rb +9 -4
- data/lib/sidekiq/metrics/shared.rb +21 -9
- data/lib/sidekiq/metrics/tracking.rb +40 -26
- data/lib/sidekiq/middleware/chain.rb +19 -18
- data/lib/sidekiq/middleware/current_attributes.rb +85 -20
- data/lib/sidekiq/middleware/modules.rb +2 -0
- data/lib/sidekiq/monitor.rb +18 -4
- data/lib/sidekiq/paginator.rb +8 -2
- data/lib/sidekiq/processor.rb +62 -57
- data/lib/sidekiq/rails.rb +27 -10
- data/lib/sidekiq/redis_client_adapter.rb +31 -71
- data/lib/sidekiq/redis_connection.rb +44 -115
- data/lib/sidekiq/ring_buffer.rb +2 -0
- data/lib/sidekiq/scheduled.rb +22 -23
- data/lib/sidekiq/systemd.rb +2 -0
- data/lib/sidekiq/testing.rb +37 -46
- data/lib/sidekiq/transaction_aware_client.rb +11 -5
- data/lib/sidekiq/version.rb +6 -1
- data/lib/sidekiq/web/action.rb +29 -7
- data/lib/sidekiq/web/application.rb +82 -28
- data/lib/sidekiq/web/csrf_protection.rb +10 -7
- data/lib/sidekiq/web/helpers.rb +110 -49
- data/lib/sidekiq/web/router.rb +5 -2
- data/lib/sidekiq/web.rb +70 -17
- data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
- data/lib/sidekiq.rb +78 -274
- data/sidekiq.gemspec +13 -10
- data/web/assets/javascripts/application.js +44 -0
- data/web/assets/javascripts/base-charts.js +106 -0
- data/web/assets/javascripts/dashboard-charts.js +194 -0
- data/web/assets/javascripts/dashboard.js +17 -233
- data/web/assets/javascripts/metrics.js +151 -115
- data/web/assets/stylesheets/application-dark.css +4 -0
- data/web/assets/stylesheets/application-rtl.css +10 -89
- data/web/assets/stylesheets/application.css +56 -296
- data/web/locales/ar.yml +70 -70
- data/web/locales/cs.yml +62 -62
- data/web/locales/da.yml +60 -53
- data/web/locales/de.yml +65 -65
- data/web/locales/el.yml +2 -7
- data/web/locales/en.yml +81 -71
- data/web/locales/es.yml +68 -68
- data/web/locales/fa.yml +65 -65
- data/web/locales/fr.yml +80 -67
- data/web/locales/gd.yml +98 -0
- data/web/locales/he.yml +65 -64
- data/web/locales/hi.yml +59 -59
- data/web/locales/it.yml +85 -54
- data/web/locales/ja.yml +67 -70
- data/web/locales/ko.yml +52 -52
- data/web/locales/lt.yml +66 -66
- data/web/locales/nb.yml +61 -61
- data/web/locales/nl.yml +52 -52
- data/web/locales/pl.yml +45 -45
- data/web/locales/pt-br.yml +78 -69
- data/web/locales/pt.yml +51 -51
- data/web/locales/ru.yml +67 -66
- data/web/locales/sv.yml +53 -53
- data/web/locales/ta.yml +60 -60
- data/web/locales/tr.yml +100 -0
- data/web/locales/uk.yml +85 -61
- data/web/locales/ur.yml +64 -64
- data/web/locales/vi.yml +67 -67
- data/web/locales/zh-cn.yml +20 -19
- data/web/locales/zh-tw.yml +10 -2
- data/web/views/_footer.erb +16 -2
- data/web/views/_job_info.erb +18 -2
- data/web/views/_metrics_period_select.erb +12 -0
- data/web/views/_paging.erb +2 -0
- data/web/views/_poll_link.erb +1 -1
- data/web/views/_summary.erb +7 -7
- data/web/views/busy.erb +46 -35
- data/web/views/dashboard.erb +32 -8
- data/web/views/filtering.erb +6 -0
- data/web/views/layout.erb +6 -6
- data/web/views/metrics.erb +47 -26
- data/web/views/metrics_for_job.erb +43 -71
- data/web/views/morgue.erb +7 -11
- data/web/views/queue.erb +11 -15
- data/web/views/queues.erb +9 -3
- data/web/views/retries.erb +5 -9
- data/web/views/scheduled.erb +12 -13
- metadata +66 -41
- data/lib/sidekiq/delay.rb +0 -43
- data/lib/sidekiq/extensions/action_mailer.rb +0 -48
- data/lib/sidekiq/extensions/active_record.rb +0 -43
- data/lib/sidekiq/extensions/class_methods.rb +0 -43
- data/lib/sidekiq/extensions/generic_proxy.rb +0 -33
- data/lib/sidekiq/metrics/deploy.rb +0 -47
- data/lib/sidekiq/worker.rb +0 -370
- data/web/assets/javascripts/graph.js +0 -16
- /data/{LICENSE → LICENSE.txt} +0 -0
data/lib/sidekiq/launcher.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "sidekiq/manager"
|
4
|
-
require "sidekiq/
|
4
|
+
require "sidekiq/capsule"
|
5
5
|
require "sidekiq/scheduled"
|
6
6
|
require "sidekiq/ring_buffer"
|
7
7
|
|
8
8
|
module Sidekiq
|
9
|
-
# The Launcher starts the
|
9
|
+
# The Launcher starts the Capsule Managers, the Poller thread and provides the process heartbeat.
|
10
10
|
class Launcher
|
11
11
|
include Sidekiq::Component
|
12
12
|
|
@@ -16,48 +16,56 @@ module Sidekiq
|
|
16
16
|
proc { "sidekiq" },
|
17
17
|
proc { Sidekiq::VERSION },
|
18
18
|
proc { |me, data| data["tag"] },
|
19
|
-
proc { |me, data| "[#{Processor::WORK_STATE.size} of #{
|
19
|
+
proc { |me, data| "[#{Processor::WORK_STATE.size} of #{me.config.total_concurrency} busy]" },
|
20
20
|
proc { |me, data| "stopping" if me.stopping? }
|
21
21
|
]
|
22
22
|
|
23
|
-
attr_accessor :
|
23
|
+
attr_accessor :managers, :poller
|
24
24
|
|
25
|
-
def initialize(
|
26
|
-
@config =
|
27
|
-
|
28
|
-
@
|
29
|
-
|
25
|
+
def initialize(config, embedded: false)
|
26
|
+
@config = config
|
27
|
+
@embedded = embedded
|
28
|
+
@managers = config.capsules.values.map do |cap|
|
29
|
+
Sidekiq::Manager.new(cap)
|
30
|
+
end
|
31
|
+
@poller = Sidekiq::Scheduled::Poller.new(@config)
|
30
32
|
@done = false
|
31
33
|
end
|
32
34
|
|
33
|
-
|
34
|
-
|
35
|
+
# Start this Sidekiq instance. If an embedding process already
|
36
|
+
# has a heartbeat thread, caller can use `async_beat: false`
|
37
|
+
# and instead have thread call Launcher#heartbeat every N seconds.
|
38
|
+
def run(async_beat: true)
|
39
|
+
logger.debug { @config.merge!({}) }
|
40
|
+
Sidekiq.freeze!
|
41
|
+
@thread = safe_thread("heartbeat", &method(:start_heartbeat)) if async_beat
|
35
42
|
@poller.start
|
36
|
-
@
|
43
|
+
@managers.each(&:start)
|
37
44
|
end
|
38
45
|
|
39
46
|
# Stops this instance from processing any more jobs,
|
40
|
-
#
|
41
47
|
def quiet
|
48
|
+
return if @done
|
49
|
+
|
42
50
|
@done = true
|
43
|
-
@
|
51
|
+
@managers.each(&:quiet)
|
44
52
|
@poller.terminate
|
53
|
+
fire_event(:quiet, reverse: true)
|
45
54
|
end
|
46
55
|
|
47
56
|
# Shuts down this Sidekiq instance. Waits up to the deadline for all jobs to complete.
|
48
57
|
def stop
|
49
58
|
deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + @config[:timeout]
|
50
59
|
|
51
|
-
|
52
|
-
@
|
53
|
-
|
54
|
-
|
55
|
-
|
60
|
+
quiet
|
61
|
+
stoppers = @managers.map do |mgr|
|
62
|
+
Thread.new do
|
63
|
+
mgr.stop(deadline)
|
64
|
+
end
|
65
|
+
end
|
56
66
|
|
57
|
-
|
58
|
-
|
59
|
-
strategy = @config[:fetch]
|
60
|
-
strategy.bulk_requeue([], @config)
|
67
|
+
fire_event(:shutdown, reverse: true)
|
68
|
+
stoppers.each(&:join)
|
61
69
|
|
62
70
|
clear_heartbeat
|
63
71
|
end
|
@@ -66,18 +74,30 @@ module Sidekiq
|
|
66
74
|
@done
|
67
75
|
end
|
68
76
|
|
77
|
+
# If embedding Sidekiq, you can have the process heartbeat
|
78
|
+
# call this method to regularly heartbeat rather than creating
|
79
|
+
# a separate thread.
|
80
|
+
def heartbeat
|
81
|
+
❤
|
82
|
+
end
|
83
|
+
|
69
84
|
private unless $TESTING
|
70
85
|
|
71
|
-
BEAT_PAUSE =
|
86
|
+
BEAT_PAUSE = 10
|
72
87
|
|
73
88
|
def start_heartbeat
|
74
89
|
loop do
|
75
|
-
|
90
|
+
beat
|
76
91
|
sleep BEAT_PAUSE
|
77
92
|
end
|
78
93
|
logger.info("Heartbeat stopping...")
|
79
94
|
end
|
80
95
|
|
96
|
+
def beat
|
97
|
+
$0 = PROCTITLES.map { |proc| proc.call(self, to_data) }.compact.join(" ") unless @embedded
|
98
|
+
❤
|
99
|
+
end
|
100
|
+
|
81
101
|
def clear_heartbeat
|
82
102
|
flush_stats
|
83
103
|
|
@@ -94,12 +114,6 @@ module Sidekiq
|
|
94
114
|
# best effort, ignore network errors
|
95
115
|
end
|
96
116
|
|
97
|
-
def heartbeat
|
98
|
-
$0 = PROCTITLES.map { |proc| proc.call(self, to_data) }.compact.join(" ")
|
99
|
-
|
100
|
-
❤
|
101
|
-
end
|
102
|
-
|
103
117
|
def flush_stats
|
104
118
|
fails = Processor::FAILURE.reset
|
105
119
|
procd = Processor::PROCESSED.reset
|
@@ -107,7 +121,7 @@ module Sidekiq
|
|
107
121
|
|
108
122
|
nowdate = Time.now.utc.strftime("%Y-%m-%d")
|
109
123
|
begin
|
110
|
-
|
124
|
+
redis do |conn|
|
111
125
|
conn.pipelined do |pipeline|
|
112
126
|
pipeline.incrby("stat:processed", procd)
|
113
127
|
pipeline.incrby("stat:processed:#{nowdate}", procd)
|
@@ -119,9 +133,7 @@ module Sidekiq
|
|
119
133
|
end
|
120
134
|
end
|
121
135
|
rescue => ex
|
122
|
-
|
123
|
-
# try to handle the exception
|
124
|
-
Sidekiq.logger.warn("Unable to flush stats: #{ex}")
|
136
|
+
logger.warn("Unable to flush stats: #{ex}")
|
125
137
|
end
|
126
138
|
end
|
127
139
|
|
@@ -130,31 +142,20 @@ module Sidekiq
|
|
130
142
|
fails = procd = 0
|
131
143
|
|
132
144
|
begin
|
133
|
-
|
134
|
-
procd = Processor::PROCESSED.reset
|
135
|
-
curstate = Processor::WORK_STATE.dup
|
145
|
+
flush_stats
|
136
146
|
|
137
|
-
|
147
|
+
curstate = Processor::WORK_STATE.dup
|
148
|
+
curstate.transform_values! { |val| Sidekiq.dump_json(val) }
|
138
149
|
|
139
150
|
redis do |conn|
|
140
|
-
conn.multi do |transaction|
|
141
|
-
transaction.incrby("stat:processed", procd)
|
142
|
-
transaction.incrby("stat:processed:#{nowdate}", procd)
|
143
|
-
transaction.expire("stat:processed:#{nowdate}", STATS_TTL)
|
144
|
-
|
145
|
-
transaction.incrby("stat:failed", fails)
|
146
|
-
transaction.incrby("stat:failed:#{nowdate}", fails)
|
147
|
-
transaction.expire("stat:failed:#{nowdate}", STATS_TTL)
|
148
|
-
end
|
149
|
-
|
150
151
|
# work is the current set of executing jobs
|
151
152
|
work_key = "#{key}:work"
|
152
|
-
conn.
|
153
|
+
conn.multi do |transaction|
|
153
154
|
transaction.unlink(work_key)
|
154
|
-
curstate.
|
155
|
-
transaction.hset(work_key,
|
155
|
+
if curstate.size > 0
|
156
|
+
transaction.hset(work_key, curstate)
|
157
|
+
transaction.expire(work_key, 60)
|
156
158
|
end
|
157
|
-
transaction.expire(work_key, 60)
|
158
159
|
end
|
159
160
|
end
|
160
161
|
|
@@ -163,11 +164,11 @@ module Sidekiq
|
|
163
164
|
fails = procd = 0
|
164
165
|
kb = memory_usage(::Process.pid)
|
165
166
|
|
166
|
-
_, exists, _, _,
|
167
|
+
_, exists, _, _, signal = redis { |conn|
|
167
168
|
conn.multi { |transaction|
|
168
169
|
transaction.sadd("processes", [key])
|
169
|
-
transaction.exists
|
170
|
-
transaction.
|
170
|
+
transaction.exists(key)
|
171
|
+
transaction.hset(key, "info", to_json,
|
171
172
|
"busy", curstate.size,
|
172
173
|
"beat", Time.now.to_f,
|
173
174
|
"rtt_us", rtt,
|
@@ -179,12 +180,10 @@ module Sidekiq
|
|
179
180
|
}
|
180
181
|
|
181
182
|
# first heartbeat or recovering from an outage and need to reestablish our heartbeat
|
182
|
-
fire_event(:heartbeat) unless exists
|
183
|
+
fire_event(:heartbeat) unless exists > 0
|
183
184
|
fire_event(:beat, oneshot: false)
|
184
185
|
|
185
|
-
|
186
|
-
|
187
|
-
::Process.kill(msg, ::Process.pid)
|
186
|
+
::Process.kill(signal, ::Process.pid) if signal && !@embedded
|
188
187
|
rescue => e
|
189
188
|
# ignore all redis/network issues
|
190
189
|
logger.error("heartbeat: #{e}")
|
@@ -218,7 +217,7 @@ module Sidekiq
|
|
218
217
|
Last RTT readings were #{RTT_READINGS.buffer.inspect}, ideally these should be < 1000.
|
219
218
|
Ensure Redis is running in the same AZ or datacenter as Sidekiq.
|
220
219
|
If these values are close to 100,000, that means your Sidekiq process may be
|
221
|
-
CPU-saturated; reduce your concurrency and/or see https://github.com/
|
220
|
+
CPU-saturated; reduce your concurrency and/or see https://github.com/sidekiq/sidekiq/discussions/5039
|
222
221
|
EOM
|
223
222
|
RTT_READINGS.reset
|
224
223
|
end
|
@@ -251,13 +250,20 @@ module Sidekiq
|
|
251
250
|
"started_at" => Time.now.to_f,
|
252
251
|
"pid" => ::Process.pid,
|
253
252
|
"tag" => @config[:tag] || "",
|
254
|
-
"concurrency" => @config
|
255
|
-
"queues" => @config
|
256
|
-
"
|
257
|
-
"
|
253
|
+
"concurrency" => @config.total_concurrency,
|
254
|
+
"queues" => @config.capsules.values.flat_map { |cap| cap.queues }.uniq,
|
255
|
+
"weights" => to_weights,
|
256
|
+
"labels" => @config[:labels].to_a,
|
257
|
+
"identity" => identity,
|
258
|
+
"version" => Sidekiq::VERSION,
|
259
|
+
"embedded" => @embedded
|
258
260
|
}
|
259
261
|
end
|
260
262
|
|
263
|
+
def to_weights
|
264
|
+
@config.capsules.values.map(&:weights)
|
265
|
+
end
|
266
|
+
|
261
267
|
def to_json
|
262
268
|
# this data changes infrequently so dump it to a string
|
263
269
|
# now so we don't need to dump it every heartbeat.
|
data/lib/sidekiq/logger.rb
CHANGED
@@ -31,12 +31,12 @@ module Sidekiq
|
|
31
31
|
"fatal" => 4
|
32
32
|
}
|
33
33
|
LEVELS.default_proc = proc do |_, level|
|
34
|
-
|
34
|
+
puts("Invalid log level: #{level.inspect}")
|
35
35
|
nil
|
36
36
|
end
|
37
37
|
|
38
38
|
LEVELS.each do |level, numeric_level|
|
39
|
-
define_method("#{level}?") do
|
39
|
+
define_method(:"#{level}?") do
|
40
40
|
local_level.nil? ? super() : local_level <= numeric_level
|
41
41
|
end
|
42
42
|
end
|
@@ -70,36 +70,11 @@ module Sidekiq
|
|
70
70
|
ensure
|
71
71
|
self.local_level = old_local_level
|
72
72
|
end
|
73
|
-
|
74
|
-
# Redefined to check severity against #level, and thus the thread-local level, rather than +@level+.
|
75
|
-
# FIXME: Remove when the minimum Ruby version supports overriding Logger#level.
|
76
|
-
def add(severity, message = nil, progname = nil, &block)
|
77
|
-
severity ||= ::Logger::UNKNOWN
|
78
|
-
progname ||= @progname
|
79
|
-
|
80
|
-
return true if @logdev.nil? || severity < level
|
81
|
-
|
82
|
-
if message.nil?
|
83
|
-
if block
|
84
|
-
message = yield
|
85
|
-
else
|
86
|
-
message = progname
|
87
|
-
progname = @progname
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
@logdev.write format_message(format_severity(severity), Time.now, progname, message)
|
92
|
-
end
|
93
73
|
end
|
94
74
|
|
95
75
|
class Logger < ::Logger
|
96
76
|
include LoggingUtils
|
97
77
|
|
98
|
-
def initialize(*args, **kwargs)
|
99
|
-
super
|
100
|
-
self.formatter = Sidekiq.log_formatter
|
101
|
-
end
|
102
|
-
|
103
78
|
module Formatters
|
104
79
|
class Base < ::Logger::Formatter
|
105
80
|
def tid
|
data/lib/sidekiq/manager.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "sidekiq/processor"
|
4
|
-
require "sidekiq/fetch"
|
5
4
|
require "set"
|
6
5
|
|
7
6
|
module Sidekiq
|
@@ -23,19 +22,19 @@ module Sidekiq
|
|
23
22
|
include Sidekiq::Component
|
24
23
|
|
25
24
|
attr_reader :workers
|
25
|
+
attr_reader :capsule
|
26
26
|
|
27
|
-
def initialize(
|
28
|
-
@config =
|
29
|
-
|
30
|
-
@count = options[:concurrency] || 10
|
27
|
+
def initialize(capsule)
|
28
|
+
@config = @capsule = capsule
|
29
|
+
@count = capsule.concurrency
|
31
30
|
raise ArgumentError, "Concurrency of #{@count} is not supported" if @count < 1
|
32
31
|
|
33
32
|
@done = false
|
34
33
|
@workers = Set.new
|
34
|
+
@plock = Mutex.new
|
35
35
|
@count.times do
|
36
36
|
@workers << Processor.new(@config, &method(:processor_result))
|
37
37
|
end
|
38
|
-
@plock = Mutex.new
|
39
38
|
end
|
40
39
|
|
41
40
|
def start
|
@@ -46,14 +45,12 @@ module Sidekiq
|
|
46
45
|
return if @done
|
47
46
|
@done = true
|
48
47
|
|
49
|
-
logger.info { "Terminating quiet threads" }
|
48
|
+
logger.info { "Terminating quiet threads for #{capsule.name} capsule" }
|
50
49
|
@workers.each(&:terminate)
|
51
|
-
fire_event(:quiet, reverse: true)
|
52
50
|
end
|
53
51
|
|
54
52
|
def stop(deadline)
|
55
53
|
quiet
|
56
|
-
fire_event(:shutdown, reverse: true)
|
57
54
|
|
58
55
|
# some of the shutdown events can be async,
|
59
56
|
# we don't have any way to know when they're done but
|
@@ -66,6 +63,8 @@ module Sidekiq
|
|
66
63
|
return if @workers.empty?
|
67
64
|
|
68
65
|
hard_shutdown
|
66
|
+
ensure
|
67
|
+
capsule.stop
|
69
68
|
end
|
70
69
|
|
71
70
|
def processor_result(processor, reason = nil)
|
@@ -105,8 +104,7 @@ module Sidekiq
|
|
105
104
|
# contract says that jobs are run AT LEAST once. Process termination
|
106
105
|
# is delayed until we're certain the jobs are back in Redis because
|
107
106
|
# it is worse to lose a job than to run it twice.
|
108
|
-
|
109
|
-
strategy.bulk_requeue(jobs, @config)
|
107
|
+
capsule.fetcher.bulk_requeue(jobs)
|
110
108
|
end
|
111
109
|
|
112
110
|
cleanup.each do |processor|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "sidekiq"
|
2
4
|
require "date"
|
3
5
|
require "set"
|
@@ -13,14 +15,15 @@ module Sidekiq
|
|
13
15
|
# NB: all metrics and times/dates are UTC only. We specifically do not
|
14
16
|
# support timezones.
|
15
17
|
class Query
|
16
|
-
def initialize(pool:
|
18
|
+
def initialize(pool: nil, now: Time.now)
|
17
19
|
@time = now.utc
|
18
|
-
@pool = pool
|
20
|
+
@pool = pool || Sidekiq.default_configuration.redis_pool
|
19
21
|
@klass = nil
|
20
22
|
end
|
21
23
|
|
22
24
|
# Get metric data for all jobs from the last hour
|
23
|
-
|
25
|
+
# +class_filter+: return only results for classes matching filter
|
26
|
+
def top_jobs(class_filter: nil, minutes: 60)
|
24
27
|
result = Result.new
|
25
28
|
|
26
29
|
time = @time
|
@@ -39,6 +42,7 @@ module Sidekiq
|
|
39
42
|
redis_results.each do |hash|
|
40
43
|
hash.each do |k, v|
|
41
44
|
kls, metric = k.split("|")
|
45
|
+
next if class_filter && !class_filter.match?(kls)
|
42
46
|
result.job_results[kls].add_metric metric, time, v.to_i
|
43
47
|
end
|
44
48
|
time -= 60
|
@@ -70,7 +74,7 @@ module Sidekiq
|
|
70
74
|
result.job_results[klass].add_metric "ms", time, ms.to_i if ms
|
71
75
|
result.job_results[klass].add_metric "p", time, p.to_i if p
|
72
76
|
result.job_results[klass].add_metric "f", time, f.to_i if f
|
73
|
-
result.job_results[klass].add_hist time, Histogram.new(klass).fetch(conn, time)
|
77
|
+
result.job_results[klass].add_hist time, Histogram.new(klass).fetch(conn, time).reverse
|
74
78
|
time -= 60
|
75
79
|
end
|
76
80
|
end
|
@@ -117,6 +121,7 @@ module Sidekiq
|
|
117
121
|
|
118
122
|
def total_avg(metric = "ms")
|
119
123
|
completed = totals["p"] - totals["f"]
|
124
|
+
return 0 if completed.zero?
|
120
125
|
totals[metric].to_f / completed
|
121
126
|
end
|
122
127
|
|
@@ -1,9 +1,21 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Sidekiq
|
4
4
|
module Metrics
|
5
|
-
|
6
|
-
|
5
|
+
class Counter
|
6
|
+
def initialize
|
7
|
+
@value = 0
|
8
|
+
@lock = Mutex.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def increment
|
12
|
+
@lock.synchronize { @value += 1 }
|
13
|
+
end
|
14
|
+
|
15
|
+
def value
|
16
|
+
@lock.synchronize { @value }
|
17
|
+
end
|
18
|
+
end
|
7
19
|
|
8
20
|
# Implements space-efficient but statistically useful histogram storage.
|
9
21
|
# A precise time histogram stores every time. Instead we break times into a set of
|
@@ -28,8 +40,8 @@ module Sidekiq
|
|
28
40
|
1100, 1700, 2500, 3800, 5750,
|
29
41
|
8500, 13000, 20000, 30000, 45000,
|
30
42
|
65000, 100000, 150000, 225000, 335000,
|
31
|
-
|
32
|
-
]
|
43
|
+
1e20 # the "maybe your job is too long" bucket
|
44
|
+
].freeze
|
33
45
|
LABELS = [
|
34
46
|
"20ms", "30ms", "45ms", "65ms", "100ms",
|
35
47
|
"150ms", "225ms", "335ms", "500ms", "750ms",
|
@@ -37,8 +49,7 @@ module Sidekiq
|
|
37
49
|
"8.5s", "13s", "20s", "30s", "45s",
|
38
50
|
"65s", "100s", "150s", "225s", "335s",
|
39
51
|
"Slow"
|
40
|
-
]
|
41
|
-
|
52
|
+
].freeze
|
42
53
|
FETCH = "GET u16 #0 GET u16 #1 GET u16 #2 GET u16 #3 \
|
43
54
|
GET u16 #4 GET u16 #5 GET u16 #6 GET u16 #7 \
|
44
55
|
GET u16 #8 GET u16 #9 GET u16 #10 GET u16 #11 \
|
@@ -46,6 +57,7 @@ module Sidekiq
|
|
46
57
|
GET u16 #16 GET u16 #17 GET u16 #18 GET u16 #19 \
|
47
58
|
GET u16 #20 GET u16 #21 GET u16 #22 GET u16 #23 \
|
48
59
|
GET u16 #24 GET u16 #25".split
|
60
|
+
HISTOGRAM_TTL = 8 * 60 * 60
|
49
61
|
|
50
62
|
def each
|
51
63
|
buckets.each { |counter| yield counter.value }
|
@@ -72,7 +84,7 @@ module Sidekiq
|
|
72
84
|
def fetch(conn, now = Time.now)
|
73
85
|
window = now.utc.strftime("%d-%H:%-M")
|
74
86
|
key = "#{@klass}-#{window}"
|
75
|
-
conn.
|
87
|
+
conn.bitfield_ro(key, *FETCH)
|
76
88
|
end
|
77
89
|
|
78
90
|
def persist(conn, now = Time.now)
|
@@ -86,7 +98,7 @@ module Sidekiq
|
|
86
98
|
end
|
87
99
|
|
88
100
|
conn.bitfield(*cmd) if cmd.size > 3
|
89
|
-
conn.expire(key,
|
101
|
+
conn.expire(key, HISTOGRAM_TTL)
|
90
102
|
key
|
91
103
|
end
|
92
104
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "time"
|
2
4
|
require "sidekiq"
|
3
5
|
require "sidekiq/metrics/shared"
|
@@ -29,11 +31,11 @@ module Sidekiq
|
|
29
31
|
# We don't track time for failed jobs as they can have very unpredictable
|
30
32
|
# execution times. more important to know average time for successful jobs so we
|
31
33
|
# can better recognize when a perf regression is introduced.
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
track_time(klass, time_ms)
|
35
|
+
rescue JobRetry::Skip
|
36
|
+
# This is raised when iterable job is interrupted.
|
37
|
+
track_time(klass, time_ms)
|
38
|
+
raise
|
37
39
|
rescue Exception
|
38
40
|
@lock.synchronize {
|
39
41
|
@jobs["#{klass}|f"] += 1
|
@@ -48,8 +50,8 @@ module Sidekiq
|
|
48
50
|
end
|
49
51
|
end
|
50
52
|
|
51
|
-
LONG_TERM = 90 * 24 * 60 * 60
|
52
|
-
MID_TERM = 7 * 24 * 60 * 60
|
53
|
+
# LONG_TERM = 90 * 24 * 60 * 60
|
54
|
+
# MID_TERM = 7 * 24 * 60 * 60
|
53
55
|
SHORT_TERM = 8 * 60 * 60
|
54
56
|
|
55
57
|
def flush(time = Time.now)
|
@@ -59,12 +61,13 @@ module Sidekiq
|
|
59
61
|
return if procd == 0 && fails == 0
|
60
62
|
|
61
63
|
now = time.utc
|
62
|
-
nowdate = now.strftime("%Y%m%d")
|
63
|
-
nowhour = now.strftime("%Y%m%d|%-H")
|
64
|
+
# nowdate = now.strftime("%Y%m%d")
|
65
|
+
# nowhour = now.strftime("%Y%m%d|%-H")
|
64
66
|
nowmin = now.strftime("%Y%m%d|%-H:%-M")
|
65
67
|
count = 0
|
66
68
|
|
67
69
|
redis do |conn|
|
70
|
+
# persist fine-grained histogram data
|
68
71
|
if grams.size > 0
|
69
72
|
conn.pipelined do |pipe|
|
70
73
|
grams.each do |_, gram|
|
@@ -73,15 +76,16 @@ module Sidekiq
|
|
73
76
|
end
|
74
77
|
end
|
75
78
|
|
79
|
+
# persist coarse grained execution count + execution millis.
|
80
|
+
# note as of today we don't use or do anything with the
|
81
|
+
# daily or hourly rollups.
|
76
82
|
[
|
77
|
-
["j", jobs, nowdate, LONG_TERM],
|
78
|
-
["j", jobs, nowhour, MID_TERM],
|
83
|
+
# ["j", jobs, nowdate, LONG_TERM],
|
84
|
+
# ["j", jobs, nowhour, MID_TERM],
|
79
85
|
["j", jobs, nowmin, SHORT_TERM]
|
80
86
|
].each do |prefix, data, bucket, ttl|
|
81
|
-
# Quietly seed the new 7.0 stats format so migration is painless.
|
82
87
|
conn.pipelined do |xa|
|
83
88
|
stats = "#{prefix}|#{bucket}"
|
84
|
-
# logger.debug "Flushing metrics #{stats}"
|
85
89
|
data.each_pair do |key, value|
|
86
90
|
xa.hincrby stats, key, value
|
87
91
|
count += 1
|
@@ -89,22 +93,34 @@ module Sidekiq
|
|
89
93
|
xa.expire(stats, ttl)
|
90
94
|
end
|
91
95
|
end
|
92
|
-
logger.
|
96
|
+
logger.debug "Flushed #{count} metrics"
|
93
97
|
count
|
94
98
|
end
|
95
99
|
end
|
96
100
|
|
97
101
|
private
|
98
102
|
|
103
|
+
def track_time(klass, time_ms)
|
104
|
+
@lock.synchronize {
|
105
|
+
@grams[klass].record_time(time_ms)
|
106
|
+
@jobs["#{klass}|ms"] += time_ms
|
107
|
+
@totals["ms"] += time_ms
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
99
111
|
def reset
|
100
112
|
@lock.synchronize {
|
101
113
|
array = [@totals, @jobs, @grams]
|
102
|
-
|
103
|
-
@jobs = Hash.new(0)
|
104
|
-
@grams = Hash.new { |hash, key| hash[key] = Histogram.new(key) }
|
114
|
+
reset_instance_variables
|
105
115
|
array
|
106
116
|
}
|
107
117
|
end
|
118
|
+
|
119
|
+
def reset_instance_variables
|
120
|
+
@totals = Hash.new(0)
|
121
|
+
@jobs = Hash.new(0)
|
122
|
+
@grams = Hash.new { |hash, key| hash[key] = Histogram.new(key) }
|
123
|
+
end
|
108
124
|
end
|
109
125
|
|
110
126
|
class Middleware
|
@@ -121,14 +137,12 @@ module Sidekiq
|
|
121
137
|
end
|
122
138
|
end
|
123
139
|
|
124
|
-
|
125
|
-
Sidekiq.
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
exec.flush
|
132
|
-
end
|
140
|
+
Sidekiq.configure_server do |config|
|
141
|
+
exec = Sidekiq::Metrics::ExecutionTracker.new(config)
|
142
|
+
config.server_middleware do |chain|
|
143
|
+
chain.add Sidekiq::Metrics::Middleware, exec
|
144
|
+
end
|
145
|
+
config.on(:beat) do
|
146
|
+
exec.flush
|
133
147
|
end
|
134
148
|
end
|