sidekiq 6.5.12 → 7.3.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changes.md +327 -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 +88 -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 +2 -2
- data/lib/sidekiq/processor.rb +62 -57
- data/lib/sidekiq/rails.rb +17 -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 +104 -43
- 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 +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 +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 +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 +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 +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 +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 +6 -10
- 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/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
|