sidekiq 6.5.12 → 7.3.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changes.md +303 -20
- data/README.md +43 -35
- data/bin/multi_queue_bench +271 -0
- data/bin/sidekiq +3 -8
- data/bin/sidekiqload +204 -118
- data/bin/sidekiqmon +3 -0
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +88 -0
- data/lib/generators/sidekiq/job_generator.rb +2 -0
- data/lib/sidekiq/api.rb +196 -138
- 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 +4 -1
- data/lib/sidekiq/config.rb +305 -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 +23 -12
- 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 +70 -20
- data/lib/sidekiq/middleware/modules.rb +2 -0
- data/lib/sidekiq/monitor.rb +18 -4
- data/lib/sidekiq/paginator.rb +2 -2
- data/lib/sidekiq/processor.rb +62 -57
- data/lib/sidekiq/rails.rb +21 -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 +15 -5
- data/lib/sidekiq/web/application.rb +89 -17
- data/lib/sidekiq/web/csrf_protection.rb +10 -7
- data/lib/sidekiq/web/helpers.rb +102 -42
- data/lib/sidekiq/web/router.rb +5 -2
- data/lib/sidekiq/web.rb +65 -17
- data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
- data/lib/sidekiq.rb +78 -274
- data/sidekiq.gemspec +12 -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 +192 -0
- data/web/assets/javascripts/dashboard.js +11 -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 +53 -298
- 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 +78 -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 +53 -53
- 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 +17 -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 +28 -7
- data/web/views/filtering.erb +7 -0
- data/web/views/layout.erb +6 -6
- data/web/views/metrics.erb +48 -26
- data/web/views/metrics_for_job.erb +43 -71
- data/web/views/morgue.erb +5 -9
- data/web/views/queue.erb +10 -14
- data/web/views/queues.erb +9 -3
- data/web/views/retries.erb +5 -9
- data/web/views/scheduled.erb +12 -13
- metadata +53 -39
- 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/job_util.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "securerandom"
|
2
4
|
require "time"
|
3
5
|
|
@@ -9,26 +11,32 @@ module Sidekiq
|
|
9
11
|
|
10
12
|
def validate(item)
|
11
13
|
raise(ArgumentError, "Job must be a Hash with 'class' and 'args' keys: `#{item}`") unless item.is_a?(Hash) && item.key?("class") && item.key?("args")
|
12
|
-
raise(ArgumentError, "Job args must be an Array: `#{item}`") unless item["args"].is_a?(Array)
|
14
|
+
raise(ArgumentError, "Job args must be an Array: `#{item}`") unless item["args"].is_a?(Array) || item["args"].is_a?(Enumerator::Lazy)
|
13
15
|
raise(ArgumentError, "Job class must be either a Class or String representation of the class name: `#{item}`") unless item["class"].is_a?(Class) || item["class"].is_a?(String)
|
14
16
|
raise(ArgumentError, "Job 'at' must be a Numeric timestamp: `#{item}`") if item.key?("at") && !item["at"].is_a?(Numeric)
|
15
17
|
raise(ArgumentError, "Job tags must be an Array: `#{item}`") if item["tags"] && !item["tags"].is_a?(Array)
|
18
|
+
raise(ArgumentError, "retry_for must be a relative amount of time, e.g. 48.hours `#{item}`") if item["retry_for"] && item["retry_for"] > 1_000_000_000
|
16
19
|
end
|
17
20
|
|
18
21
|
def verify_json(item)
|
19
22
|
job_class = item["wrapped"] || item["class"]
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
23
|
+
args = item["args"]
|
24
|
+
mode = Sidekiq::Config::DEFAULTS[:on_complex_arguments]
|
25
|
+
|
26
|
+
if mode == :raise || mode == :warn
|
27
|
+
if (unsafe_item = json_unsafe?(args))
|
28
|
+
msg = <<~EOM
|
29
|
+
Job arguments to #{job_class} must be native JSON types, but #{unsafe_item.inspect} is a #{unsafe_item.class}.
|
30
|
+
See https://github.com/sidekiq/sidekiq/wiki/Best-Practices
|
31
|
+
To disable this error, add `Sidekiq.strict_args!(false)` to your initializer.
|
32
|
+
EOM
|
33
|
+
|
34
|
+
if mode == :raise
|
35
|
+
raise(ArgumentError, msg)
|
36
|
+
else
|
37
|
+
warn(msg)
|
38
|
+
end
|
39
|
+
end
|
32
40
|
end
|
33
41
|
end
|
34
42
|
|
@@ -49,6 +57,7 @@ module Sidekiq
|
|
49
57
|
item["jid"] ||= SecureRandom.hex(12)
|
50
58
|
item["class"] = item["class"].to_s
|
51
59
|
item["queue"] = item["queue"].to_s
|
60
|
+
item["retry_for"] = item["retry_for"].to_i if item["retry_for"]
|
52
61
|
item["created_at"] ||= Time.now.to_f
|
53
62
|
item
|
54
63
|
end
|
@@ -64,8 +73,37 @@ module Sidekiq
|
|
64
73
|
|
65
74
|
private
|
66
75
|
|
67
|
-
|
68
|
-
|
76
|
+
RECURSIVE_JSON_UNSAFE = {
|
77
|
+
Integer => ->(val) {},
|
78
|
+
Float => ->(val) {},
|
79
|
+
TrueClass => ->(val) {},
|
80
|
+
FalseClass => ->(val) {},
|
81
|
+
NilClass => ->(val) {},
|
82
|
+
String => ->(val) {},
|
83
|
+
Array => ->(val) {
|
84
|
+
val.each do |e|
|
85
|
+
unsafe_item = RECURSIVE_JSON_UNSAFE[e.class].call(e)
|
86
|
+
return unsafe_item unless unsafe_item.nil?
|
87
|
+
end
|
88
|
+
nil
|
89
|
+
},
|
90
|
+
Hash => ->(val) {
|
91
|
+
val.each do |k, v|
|
92
|
+
return k unless String === k
|
93
|
+
|
94
|
+
unsafe_item = RECURSIVE_JSON_UNSAFE[v.class].call(v)
|
95
|
+
return unsafe_item unless unsafe_item.nil?
|
96
|
+
end
|
97
|
+
nil
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
RECURSIVE_JSON_UNSAFE.default = ->(val) { val }
|
102
|
+
RECURSIVE_JSON_UNSAFE.compare_by_identity
|
103
|
+
private_constant :RECURSIVE_JSON_UNSAFE
|
104
|
+
|
105
|
+
def json_unsafe?(item)
|
106
|
+
RECURSIVE_JSON_UNSAFE[item.class].call(item)
|
69
107
|
end
|
70
108
|
end
|
71
109
|
end
|
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
|