sidekiq 5.1.3 → 5.2.10
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sidekiq might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/.circleci/config.yml +61 -0
- data/.gitignore +2 -0
- data/.travis.yml +2 -5
- data/COMM-LICENSE +11 -9
- data/Changes.md +80 -0
- data/Ent-Changes.md +22 -0
- data/Gemfile +16 -5
- data/Pro-Changes.md +30 -0
- data/README.md +1 -1
- data/Rakefile +2 -1
- data/bin/sidekiqctl +13 -92
- data/bin/sidekiqload +1 -1
- data/lib/sidekiq/api.rb +50 -15
- data/lib/sidekiq/cli.rb +64 -58
- data/lib/sidekiq/client.rb +4 -3
- data/lib/sidekiq/ctl.rb +221 -0
- data/lib/sidekiq/job_logger.rb +2 -2
- data/lib/sidekiq/job_retry.rb +33 -12
- data/lib/sidekiq/launcher.rb +15 -8
- data/lib/sidekiq/manager.rb +3 -3
- data/lib/sidekiq/middleware/server/active_record.rb +1 -1
- data/lib/sidekiq/processor.rb +76 -25
- data/lib/sidekiq/rails.rb +2 -1
- data/lib/sidekiq/redis_connection.rb +20 -1
- data/lib/sidekiq/scheduled.rb +32 -3
- data/lib/sidekiq/testing.rb +4 -4
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/action.rb +1 -1
- data/lib/sidekiq/web/application.rb +26 -1
- data/lib/sidekiq/web/helpers.rb +13 -6
- data/lib/sidekiq/worker.rb +24 -8
- data/lib/sidekiq.rb +5 -3
- data/sidekiq.gemspec +5 -12
- data/web/assets/javascripts/application.js +0 -0
- data/web/assets/javascripts/dashboard.js +15 -5
- data/web/assets/stylesheets/application.css +35 -2
- data/web/assets/stylesheets/bootstrap.css +2 -2
- data/web/locales/ar.yml +1 -0
- data/web/locales/en.yml +1 -0
- data/web/locales/es.yml +3 -3
- data/web/views/_nav.erb +3 -17
- data/web/views/layout.erb +1 -1
- data/web/views/queue.erb +1 -0
- data/web/views/queues.erb +1 -1
- data/web/views/retries.erb +4 -0
- metadata +19 -86
data/lib/sidekiq/api.rb
CHANGED
@@ -2,7 +2,23 @@
|
|
2
2
|
require 'sidekiq'
|
3
3
|
|
4
4
|
module Sidekiq
|
5
|
+
|
6
|
+
module RedisScanner
|
7
|
+
def sscan(conn, key)
|
8
|
+
cursor = '0'
|
9
|
+
result = []
|
10
|
+
loop do
|
11
|
+
cursor, values = conn.sscan(key, cursor)
|
12
|
+
result.push(*values)
|
13
|
+
break if cursor == '0'
|
14
|
+
end
|
15
|
+
result
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
5
19
|
class Stats
|
20
|
+
include RedisScanner
|
21
|
+
|
6
22
|
def initialize
|
7
23
|
fetch_stats!
|
8
24
|
end
|
@@ -57,19 +73,25 @@ module Sidekiq
|
|
57
73
|
conn.zcard('dead')
|
58
74
|
conn.scard('processes')
|
59
75
|
conn.lrange('queue:default', -1, -1)
|
60
|
-
conn.smembers('processes')
|
61
|
-
conn.smembers('queues')
|
62
76
|
end
|
63
77
|
end
|
64
78
|
|
79
|
+
processes = Sidekiq.redis do |conn|
|
80
|
+
sscan(conn, 'processes')
|
81
|
+
end
|
82
|
+
|
83
|
+
queues = Sidekiq.redis do |conn|
|
84
|
+
sscan(conn, 'queues')
|
85
|
+
end
|
86
|
+
|
65
87
|
pipe2_res = Sidekiq.redis do |conn|
|
66
88
|
conn.pipelined do
|
67
|
-
|
68
|
-
|
89
|
+
processes.each {|key| conn.hget(key, 'busy') }
|
90
|
+
queues.each {|queue| conn.llen("queue:#{queue}") }
|
69
91
|
end
|
70
92
|
end
|
71
93
|
|
72
|
-
s =
|
94
|
+
s = processes.size
|
73
95
|
workers_size = pipe2_res[0...s].map(&:to_i).inject(0, &:+)
|
74
96
|
enqueued = pipe2_res[s..-1].map(&:to_i).inject(0, &:+)
|
75
97
|
|
@@ -116,9 +138,11 @@ module Sidekiq
|
|
116
138
|
end
|
117
139
|
|
118
140
|
class Queues
|
141
|
+
include RedisScanner
|
142
|
+
|
119
143
|
def lengths
|
120
144
|
Sidekiq.redis do |conn|
|
121
|
-
queues = conn
|
145
|
+
queues = sscan(conn, 'queues')
|
122
146
|
|
123
147
|
lengths = conn.pipelined do
|
124
148
|
queues.each do |queue|
|
@@ -140,6 +164,8 @@ module Sidekiq
|
|
140
164
|
|
141
165
|
class History
|
142
166
|
def initialize(days_previous, start_date = nil)
|
167
|
+
#we only store five years of data in Redis
|
168
|
+
raise ArgumentError if days_previous < 1 || days_previous > (5 * 365)
|
143
169
|
@days_previous = days_previous
|
144
170
|
@start_date = start_date || Time.now.utc.to_date
|
145
171
|
end
|
@@ -198,18 +224,19 @@ module Sidekiq
|
|
198
224
|
#
|
199
225
|
class Queue
|
200
226
|
include Enumerable
|
227
|
+
extend RedisScanner
|
201
228
|
|
202
229
|
##
|
203
230
|
# Return all known queues within Redis.
|
204
231
|
#
|
205
232
|
def self.all
|
206
|
-
Sidekiq.redis { |c| c
|
233
|
+
Sidekiq.redis { |c| sscan(c, 'queues') }.sort.map { |q| Sidekiq::Queue.new(q) }
|
207
234
|
end
|
208
235
|
|
209
236
|
attr_reader :name
|
210
237
|
|
211
238
|
def initialize(name="default")
|
212
|
-
@name = name
|
239
|
+
@name = name.to_s
|
213
240
|
@rname = "queue:#{name}"
|
214
241
|
end
|
215
242
|
|
@@ -645,6 +672,12 @@ module Sidekiq
|
|
645
672
|
each(&:retry)
|
646
673
|
end
|
647
674
|
end
|
675
|
+
|
676
|
+
def kill_all
|
677
|
+
while size > 0
|
678
|
+
each(&:kill)
|
679
|
+
end
|
680
|
+
end
|
648
681
|
end
|
649
682
|
|
650
683
|
##
|
@@ -700,17 +733,18 @@ module Sidekiq
|
|
700
733
|
#
|
701
734
|
class ProcessSet
|
702
735
|
include Enumerable
|
736
|
+
include RedisScanner
|
703
737
|
|
704
738
|
def initialize(clean_plz=true)
|
705
|
-
|
739
|
+
cleanup if clean_plz
|
706
740
|
end
|
707
741
|
|
708
742
|
# Cleans up dead processes recorded in Redis.
|
709
743
|
# Returns the number of processes cleaned.
|
710
|
-
def
|
744
|
+
def cleanup
|
711
745
|
count = 0
|
712
746
|
Sidekiq.redis do |conn|
|
713
|
-
procs = conn
|
747
|
+
procs = sscan(conn, 'processes').sort
|
714
748
|
heartbeats = conn.pipelined do
|
715
749
|
procs.each do |key|
|
716
750
|
conn.hget(key, 'info')
|
@@ -730,7 +764,7 @@ module Sidekiq
|
|
730
764
|
end
|
731
765
|
|
732
766
|
def each
|
733
|
-
procs = Sidekiq.redis { |conn| conn
|
767
|
+
procs = Sidekiq.redis { |conn| sscan(conn, 'processes') }.sort
|
734
768
|
|
735
769
|
Sidekiq.redis do |conn|
|
736
770
|
# We're making a tradeoff here between consuming more memory instead of
|
@@ -865,13 +899,14 @@ module Sidekiq
|
|
865
899
|
#
|
866
900
|
class Workers
|
867
901
|
include Enumerable
|
902
|
+
include RedisScanner
|
868
903
|
|
869
904
|
def each
|
870
905
|
Sidekiq.redis do |conn|
|
871
|
-
procs = conn
|
906
|
+
procs = sscan(conn, 'processes')
|
872
907
|
procs.sort.each do |key|
|
873
908
|
valid, workers = conn.pipelined do
|
874
|
-
conn.exists(key)
|
909
|
+
conn.exists?(key)
|
875
910
|
conn.hgetall("#{key}:workers")
|
876
911
|
end
|
877
912
|
next unless valid
|
@@ -890,7 +925,7 @@ module Sidekiq
|
|
890
925
|
# which can easily get out of sync with crashy processes.
|
891
926
|
def size
|
892
927
|
Sidekiq.redis do |conn|
|
893
|
-
procs = conn
|
928
|
+
procs = sscan(conn, 'processes')
|
894
929
|
if procs.empty?
|
895
930
|
0
|
896
931
|
else
|
data/lib/sidekiq/cli.rb
CHANGED
@@ -9,6 +9,7 @@ require 'fileutils'
|
|
9
9
|
|
10
10
|
require 'sidekiq'
|
11
11
|
require 'sidekiq/util'
|
12
|
+
require 'sidekiq/launcher'
|
12
13
|
|
13
14
|
module Sidekiq
|
14
15
|
class CLI
|
@@ -23,23 +24,13 @@ module Sidekiq
|
|
23
24
|
proc { |me, data| "stopping" if me.stopping? },
|
24
25
|
]
|
25
26
|
|
26
|
-
# Used for CLI testing
|
27
|
-
attr_accessor :code
|
28
27
|
attr_accessor :launcher
|
29
28
|
attr_accessor :environment
|
30
29
|
|
31
|
-
def
|
32
|
-
@code = nil
|
33
|
-
end
|
34
|
-
|
35
|
-
def parse(args=ARGV)
|
36
|
-
@code = nil
|
37
|
-
|
30
|
+
def parse(args = ARGV)
|
38
31
|
setup_options(args)
|
39
32
|
initialize_logger
|
40
33
|
validate!
|
41
|
-
daemonize
|
42
|
-
write_pid
|
43
34
|
end
|
44
35
|
|
45
36
|
def jruby?
|
@@ -50,8 +41,10 @@ module Sidekiq
|
|
50
41
|
# global process state irreversibly. PRs which improve the
|
51
42
|
# test coverage of Sidekiq::CLI are welcomed.
|
52
43
|
def run
|
44
|
+
daemonize if options[:daemon]
|
45
|
+
write_pid
|
53
46
|
boot_system
|
54
|
-
print_banner
|
47
|
+
print_banner if environment == 'development' && $stdout.tty?
|
55
48
|
|
56
49
|
self_read, self_write = IO.pipe
|
57
50
|
sigs = %w(INT TERM TTIN TSTP)
|
@@ -79,6 +72,13 @@ module Sidekiq
|
|
79
72
|
# fire startup and start multithreading.
|
80
73
|
ver = Sidekiq.redis_info['redis_version']
|
81
74
|
raise "You are using Redis v#{ver}, Sidekiq requires Redis v2.8.0 or greater" if ver < '2.8'
|
75
|
+
logger.warn "Sidekiq 6.0 will require Redis 4.0+, you are using Redis v#{ver}" if ver < '4'
|
76
|
+
|
77
|
+
# Since the user can pass us a connection pool explicitly in the initializer, we
|
78
|
+
# need to verify the size is large enough or else Sidekiq's performance is dramatically slowed.
|
79
|
+
cursize = Sidekiq.redis_pool.size
|
80
|
+
needed = Sidekiq.options[:concurrency] + 2
|
81
|
+
raise "Your pool of #{cursize} Redis connections is too small, please increase the size to at least #{needed}" if cursize < needed
|
82
82
|
|
83
83
|
# cache process identity
|
84
84
|
Sidekiq.options[:identity] = identity
|
@@ -93,11 +93,14 @@ module Sidekiq
|
|
93
93
|
logger.debug { "Client Middleware: #{Sidekiq.client_middleware.map(&:klass).join(', ')}" }
|
94
94
|
logger.debug { "Server Middleware: #{Sidekiq.server_middleware.map(&:klass).join(', ')}" }
|
95
95
|
|
96
|
+
launch(self_read)
|
97
|
+
end
|
98
|
+
|
99
|
+
def launch(self_read)
|
96
100
|
if !options[:daemon]
|
97
101
|
logger.info 'Starting processing, hit Ctrl-C to stop'
|
98
102
|
end
|
99
103
|
|
100
|
-
require 'sidekiq/launcher'
|
101
104
|
@launcher = Sidekiq::Launcher.new(options)
|
102
105
|
|
103
106
|
begin
|
@@ -179,23 +182,15 @@ module Sidekiq
|
|
179
182
|
private
|
180
183
|
|
181
184
|
def print_banner
|
182
|
-
#
|
183
|
-
|
184
|
-
|
185
|
-
puts Sidekiq::CLI.banner
|
186
|
-
puts "\e[0m"
|
187
|
-
end
|
185
|
+
puts "\e[#{31}m"
|
186
|
+
puts Sidekiq::CLI.banner
|
187
|
+
puts "\e[0m"
|
188
188
|
end
|
189
189
|
|
190
190
|
def daemonize
|
191
|
-
return unless options[:daemon]
|
192
|
-
|
193
191
|
raise ArgumentError, "You really should set a logfile if you're going to daemonize" unless options[:logfile]
|
194
|
-
files_to_reopen = []
|
195
|
-
ObjectSpace.each_object(File) do |file|
|
196
|
-
files_to_reopen << file unless file.closed?
|
197
|
-
end
|
198
192
|
|
193
|
+
files_to_reopen = ObjectSpace.each_object(File).reject { |f| f.closed? }
|
199
194
|
::Process.daemon(true, true)
|
200
195
|
|
201
196
|
files_to_reopen.each do |file|
|
@@ -233,15 +228,38 @@ module Sidekiq
|
|
233
228
|
alias_method :☠, :exit
|
234
229
|
|
235
230
|
def setup_options(args)
|
231
|
+
# parse CLI options
|
236
232
|
opts = parse_options(args)
|
233
|
+
|
237
234
|
set_environment opts[:environment]
|
238
235
|
|
239
|
-
|
240
|
-
|
236
|
+
# check config file presence
|
237
|
+
if opts[:config_file]
|
238
|
+
if opts[:config_file] && !File.exist?(opts[:config_file])
|
239
|
+
raise ArgumentError, "No such file #{opts[:config_file]}"
|
240
|
+
end
|
241
|
+
else
|
242
|
+
config_dir = if File.directory?(opts[:require].to_s)
|
243
|
+
File.join(opts[:require], 'config')
|
244
|
+
else
|
245
|
+
File.join(options[:require], 'config')
|
246
|
+
end
|
241
247
|
|
248
|
+
%w[sidekiq.yml sidekiq.yml.erb].each do |config_file|
|
249
|
+
path = File.join(config_dir, config_file)
|
250
|
+
opts[:config_file] ||= path if File.exist?(path)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# parse config file options
|
255
|
+
opts = parse_config(opts[:config_file]).merge(opts) if opts[:config_file]
|
256
|
+
|
257
|
+
# set defaults
|
258
|
+
opts[:queues] = Array(opts[:queues]) << 'default' if opts[:queues].nil? || opts[:queues].empty?
|
242
259
|
opts[:strict] = true if opts[:strict].nil?
|
243
|
-
opts[:concurrency] = Integer(ENV["RAILS_MAX_THREADS"]) if
|
260
|
+
opts[:concurrency] = Integer(ENV["RAILS_MAX_THREADS"]) if opts[:concurrency].nil? && ENV["RAILS_MAX_THREADS"]
|
244
261
|
|
262
|
+
# merge with defaults
|
245
263
|
options.merge!(opts)
|
246
264
|
end
|
247
265
|
|
@@ -252,8 +270,6 @@ module Sidekiq
|
|
252
270
|
def boot_system
|
253
271
|
ENV['RACK_ENV'] = ENV['RAILS_ENV'] = environment
|
254
272
|
|
255
|
-
raise ArgumentError, "#{options[:require]} does not exist" unless File.exist?(options[:require])
|
256
|
-
|
257
273
|
if File.directory?(options[:require])
|
258
274
|
require 'rails'
|
259
275
|
if ::Rails::VERSION::MAJOR < 4
|
@@ -273,10 +289,7 @@ module Sidekiq
|
|
273
289
|
end
|
274
290
|
options[:tag] ||= default_tag
|
275
291
|
else
|
276
|
-
|
277
|
-
"./#{options[:require]} or /path/to/#{options[:require]}"
|
278
|
-
|
279
|
-
require(options[:require]) || raise(ArgumentError, not_required_message)
|
292
|
+
require options[:require]
|
280
293
|
end
|
281
294
|
end
|
282
295
|
|
@@ -292,8 +305,6 @@ module Sidekiq
|
|
292
305
|
end
|
293
306
|
|
294
307
|
def validate!
|
295
|
-
options[:queues] << 'default' if options[:queues].empty?
|
296
|
-
|
297
308
|
if !File.exist?(options[:require]) ||
|
298
309
|
(File.directory?(options[:require]) && !File.exist?("#{options[:require]}/config/application.rb"))
|
299
310
|
logger.info "=================================================================="
|
@@ -319,6 +330,7 @@ module Sidekiq
|
|
319
330
|
|
320
331
|
o.on '-d', '--daemon', "Daemonize process" do |arg|
|
321
332
|
opts[:daemon] = arg
|
333
|
+
puts "WARNING: Daemonization mode will be removed in Sidekiq 6.0, see #4045. Please use a proper process supervisor to start and manage your services"
|
322
334
|
end
|
323
335
|
|
324
336
|
o.on '-e', '--environment ENV', "Application environment" do |arg|
|
@@ -358,10 +370,12 @@ module Sidekiq
|
|
358
370
|
|
359
371
|
o.on '-L', '--logfile PATH', "path to writable logfile" do |arg|
|
360
372
|
opts[:logfile] = arg
|
373
|
+
puts "WARNING: Logfile redirection will be removed in Sidekiq 6.0, see #4045. Sidekiq will only log to STDOUT"
|
361
374
|
end
|
362
375
|
|
363
376
|
o.on '-P', '--pidfile PATH', "path to pidfile" do |arg|
|
364
377
|
opts[:pidfile] = arg
|
378
|
+
puts "WARNING: PID file creation will be removed in Sidekiq 6.0, see #4045. Please use a proper process supervisor to start and manage your services"
|
365
379
|
end
|
366
380
|
|
367
381
|
o.on '-V', '--version', "Print version and exit" do |arg|
|
@@ -375,11 +389,8 @@ module Sidekiq
|
|
375
389
|
logger.info @parser
|
376
390
|
die 1
|
377
391
|
end
|
378
|
-
@parser.parse!(argv)
|
379
392
|
|
380
|
-
|
381
|
-
opts[:config_file] ||= filename if File.exist?(filename)
|
382
|
-
end
|
393
|
+
@parser.parse!(argv)
|
383
394
|
|
384
395
|
opts
|
385
396
|
end
|
@@ -399,23 +410,18 @@ module Sidekiq
|
|
399
410
|
end
|
400
411
|
end
|
401
412
|
|
402
|
-
def parse_config(
|
403
|
-
opts = {}
|
404
|
-
if File.exist?(cfile)
|
405
|
-
opts = YAML.load(ERB.new(IO.read(cfile)).result) || opts
|
406
|
-
|
407
|
-
if opts.respond_to? :deep_symbolize_keys!
|
408
|
-
opts.deep_symbolize_keys!
|
409
|
-
else
|
410
|
-
symbolize_keys_deep!(opts)
|
411
|
-
end
|
413
|
+
def parse_config(path)
|
414
|
+
opts = YAML.load(ERB.new(File.read(path)).result) || {}
|
412
415
|
|
413
|
-
|
414
|
-
|
416
|
+
if opts.respond_to? :deep_symbolize_keys!
|
417
|
+
opts.deep_symbolize_keys!
|
415
418
|
else
|
416
|
-
|
417
|
-
# can be deployed by cap with just the defaults.
|
419
|
+
symbolize_keys_deep!(opts)
|
418
420
|
end
|
421
|
+
|
422
|
+
opts = opts.merge(opts.delete(environment.to_sym) || {})
|
423
|
+
parse_queues(opts, opts.delete(:queues) || [])
|
424
|
+
|
419
425
|
ns = opts.delete(:namespace)
|
420
426
|
if ns
|
421
427
|
# logger hasn't been initialized yet, puts is all we have.
|
@@ -429,10 +435,10 @@ module Sidekiq
|
|
429
435
|
queues_and_weights.each { |queue_and_weight| parse_queue(opts, *queue_and_weight) }
|
430
436
|
end
|
431
437
|
|
432
|
-
def parse_queue(opts,
|
433
|
-
[
|
434
|
-
|
435
|
-
|
438
|
+
def parse_queue(opts, queue, weight = nil)
|
439
|
+
opts[:queues] ||= []
|
440
|
+
raise ArgumentError, "queues: #{queue} cannot be defined twice" if opts[:queues].include?(queue)
|
441
|
+
[weight.to_i, 1].max.times { opts[:queues] << queue }
|
436
442
|
opts[:strict] = false if weight.to_i > 0
|
437
443
|
end
|
438
444
|
end
|
data/lib/sidekiq/client.rb
CHANGED
@@ -77,9 +77,10 @@ module Sidekiq
|
|
77
77
|
end
|
78
78
|
|
79
79
|
##
|
80
|
-
# Push a large number of jobs to Redis.
|
81
|
-
#
|
82
|
-
#
|
80
|
+
# Push a large number of jobs to Redis. This method cuts out the redis
|
81
|
+
# network round trip latency. I wouldn't recommend pushing more than
|
82
|
+
# 1000 per call but YMMV based on network quality, size of job args, etc.
|
83
|
+
# A large number of jobs can cause a bit of Redis command processing latency.
|
83
84
|
#
|
84
85
|
# Takes the same arguments as #push except that args is expected to be
|
85
86
|
# an Array of Arrays. All other keys are duplicated for each job. Each job
|
data/lib/sidekiq/ctl.rb
ADDED
@@ -0,0 +1,221 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'sidekiq/api'
|
5
|
+
|
6
|
+
class Sidekiq::Ctl
|
7
|
+
DEFAULT_KILL_TIMEOUT = 10
|
8
|
+
CMD = File.basename($0)
|
9
|
+
|
10
|
+
attr_reader :stage, :pidfile, :kill_timeout
|
11
|
+
|
12
|
+
def self.print_usage
|
13
|
+
puts "#{CMD} - control Sidekiq from the command line."
|
14
|
+
puts
|
15
|
+
puts "Usage: #{CMD} quiet <pidfile> <kill_timeout>"
|
16
|
+
puts " #{CMD} stop <pidfile> <kill_timeout>"
|
17
|
+
puts " #{CMD} status <section>"
|
18
|
+
puts
|
19
|
+
puts " <pidfile> is path to a pidfile"
|
20
|
+
puts " <kill_timeout> is number of seconds to wait until Sidekiq exits"
|
21
|
+
puts " (default: #{Sidekiq::Ctl::DEFAULT_KILL_TIMEOUT}), after which Sidekiq will be KILL'd"
|
22
|
+
puts
|
23
|
+
puts " <section> (optional) view a specific section of the status output"
|
24
|
+
puts " Valid sections are: #{Sidekiq::Ctl::Status::VALID_SECTIONS.join(', ')}"
|
25
|
+
puts
|
26
|
+
puts "Be sure to set the kill_timeout LONGER than Sidekiq's -t timeout. If you want"
|
27
|
+
puts "to wait 60 seconds for jobs to finish, use `sidekiq -t 60` and `sidekiqctl stop"
|
28
|
+
puts " path_to_pidfile 61`"
|
29
|
+
puts
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(stage, pidfile, timeout)
|
33
|
+
@stage = stage
|
34
|
+
@pidfile = pidfile
|
35
|
+
@kill_timeout = timeout
|
36
|
+
|
37
|
+
done('No pidfile given', :error) if !pidfile
|
38
|
+
done("Pidfile #{pidfile} does not exist", :warn) if !File.exist?(pidfile)
|
39
|
+
done('Invalid pidfile content', :error) if pid == 0
|
40
|
+
|
41
|
+
fetch_process
|
42
|
+
|
43
|
+
begin
|
44
|
+
send(stage)
|
45
|
+
rescue NoMethodError
|
46
|
+
done "Invalid command: #{stage}", :error
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def fetch_process
|
51
|
+
Process.kill(0, pid)
|
52
|
+
rescue Errno::ESRCH
|
53
|
+
done "Process doesn't exist", :error
|
54
|
+
# We were not allowed to send a signal, but the process must have existed
|
55
|
+
# when Process.kill() was called.
|
56
|
+
rescue Errno::EPERM
|
57
|
+
return pid
|
58
|
+
end
|
59
|
+
|
60
|
+
def done(msg, error = nil)
|
61
|
+
puts msg
|
62
|
+
exit(exit_signal(error))
|
63
|
+
end
|
64
|
+
|
65
|
+
def exit_signal(error)
|
66
|
+
(error == :error) ? 1 : 0
|
67
|
+
end
|
68
|
+
|
69
|
+
def pid
|
70
|
+
@pid ||= File.read(pidfile).to_i
|
71
|
+
end
|
72
|
+
|
73
|
+
def quiet
|
74
|
+
`kill -TSTP #{pid}`
|
75
|
+
end
|
76
|
+
|
77
|
+
def stop
|
78
|
+
`kill -TERM #{pid}`
|
79
|
+
kill_timeout.times do
|
80
|
+
begin
|
81
|
+
Process.kill(0, pid)
|
82
|
+
rescue Errno::ESRCH
|
83
|
+
FileUtils.rm_f pidfile
|
84
|
+
done 'Sidekiq shut down gracefully.'
|
85
|
+
rescue Errno::EPERM
|
86
|
+
done 'Not permitted to shut down Sidekiq.'
|
87
|
+
end
|
88
|
+
sleep 1
|
89
|
+
end
|
90
|
+
`kill -9 #{pid}`
|
91
|
+
FileUtils.rm_f pidfile
|
92
|
+
done 'Sidekiq shut down forcefully.'
|
93
|
+
end
|
94
|
+
alias_method :shutdown, :stop
|
95
|
+
|
96
|
+
class Status
|
97
|
+
VALID_SECTIONS = %w[all version overview processes queues]
|
98
|
+
def display(section = nil)
|
99
|
+
section ||= 'all'
|
100
|
+
unless VALID_SECTIONS.include? section
|
101
|
+
puts "I don't know how to check the status of '#{section}'!"
|
102
|
+
puts "Try one of these: #{VALID_SECTIONS.join(', ')}"
|
103
|
+
return
|
104
|
+
end
|
105
|
+
send(section)
|
106
|
+
rescue StandardError => e
|
107
|
+
puts "Couldn't get status: #{e}"
|
108
|
+
end
|
109
|
+
|
110
|
+
def all
|
111
|
+
version
|
112
|
+
puts
|
113
|
+
overview
|
114
|
+
puts
|
115
|
+
processes
|
116
|
+
puts
|
117
|
+
queues
|
118
|
+
end
|
119
|
+
|
120
|
+
def version
|
121
|
+
puts "Sidekiq #{Sidekiq::VERSION}"
|
122
|
+
puts Time.now
|
123
|
+
end
|
124
|
+
|
125
|
+
def overview
|
126
|
+
puts '---- Overview ----'
|
127
|
+
puts " Processed: #{delimit stats.processed}"
|
128
|
+
puts " Failed: #{delimit stats.failed}"
|
129
|
+
puts " Busy: #{delimit stats.workers_size}"
|
130
|
+
puts " Enqueued: #{delimit stats.enqueued}"
|
131
|
+
puts " Retries: #{delimit stats.retry_size}"
|
132
|
+
puts " Scheduled: #{delimit stats.scheduled_size}"
|
133
|
+
puts " Dead: #{delimit stats.dead_size}"
|
134
|
+
end
|
135
|
+
|
136
|
+
def processes
|
137
|
+
puts "---- Processes (#{process_set.size}) ----"
|
138
|
+
process_set.each_with_index do |process, index|
|
139
|
+
puts "#{process['identity']} #{tags_for(process)}"
|
140
|
+
puts " Started: #{Time.at(process['started_at'])} (#{time_ago(process['started_at'])})"
|
141
|
+
puts " Threads: #{process['concurrency']} (#{process['busy']} busy)"
|
142
|
+
puts " Queues: #{split_multiline(process['queues'].sort, pad: 11)}"
|
143
|
+
puts '' unless (index+1) == process_set.size
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
COL_PAD = 2
|
148
|
+
def queues
|
149
|
+
puts "---- Queues (#{queue_data.size}) ----"
|
150
|
+
columns = {
|
151
|
+
name: [:ljust, (['name'] + queue_data.map(&:name)).map(&:length).max + COL_PAD],
|
152
|
+
size: [:rjust, (['size'] + queue_data.map(&:size)).map(&:length).max + COL_PAD],
|
153
|
+
latency: [:rjust, (['latency'] + queue_data.map(&:latency)).map(&:length).max + COL_PAD]
|
154
|
+
}
|
155
|
+
columns.each { |col, (dir, width)| print col.to_s.upcase.public_send(dir, width) }
|
156
|
+
puts
|
157
|
+
queue_data.each do |q|
|
158
|
+
columns.each do |col, (dir, width)|
|
159
|
+
print q.send(col).public_send(dir, width)
|
160
|
+
end
|
161
|
+
puts
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
|
167
|
+
def delimit(number)
|
168
|
+
number.to_s.reverse.scan(/.{1,3}/).join(',').reverse
|
169
|
+
end
|
170
|
+
|
171
|
+
def split_multiline(values, opts = {})
|
172
|
+
return 'none' unless values
|
173
|
+
pad = opts[:pad] || 0
|
174
|
+
max_length = opts[:max_length] || (80 - pad)
|
175
|
+
out = []
|
176
|
+
line = ''
|
177
|
+
values.each do |value|
|
178
|
+
if (line.length + value.length) > max_length
|
179
|
+
out << line
|
180
|
+
line = ' ' * pad
|
181
|
+
end
|
182
|
+
line << value + ', '
|
183
|
+
end
|
184
|
+
out << line[0..-3]
|
185
|
+
out.join("\n")
|
186
|
+
end
|
187
|
+
|
188
|
+
def tags_for(process)
|
189
|
+
tags = [
|
190
|
+
process['tag'],
|
191
|
+
process['labels'],
|
192
|
+
(process['quiet'] == 'true' ? 'quiet' : nil)
|
193
|
+
].flatten.compact
|
194
|
+
tags.any? ? "[#{tags.join('] [')}]" : nil
|
195
|
+
end
|
196
|
+
|
197
|
+
def time_ago(timestamp)
|
198
|
+
seconds = Time.now - Time.at(timestamp)
|
199
|
+
return 'just now' if seconds < 60
|
200
|
+
return 'a minute ago' if seconds < 120
|
201
|
+
return "#{seconds.floor / 60} minutes ago" if seconds < 3600
|
202
|
+
return 'an hour ago' if seconds < 7200
|
203
|
+
"#{seconds.floor / 60 / 60} hours ago"
|
204
|
+
end
|
205
|
+
|
206
|
+
QUEUE_STRUCT = Struct.new(:name, :size, :latency)
|
207
|
+
def queue_data
|
208
|
+
@queue_data ||= Sidekiq::Queue.all.map do |q|
|
209
|
+
QUEUE_STRUCT.new(q.name, q.size.to_s, sprintf('%#.2f', q.latency))
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def process_set
|
214
|
+
@process_set ||= Sidekiq::ProcessSet.new
|
215
|
+
end
|
216
|
+
|
217
|
+
def stats
|
218
|
+
@stats ||= Sidekiq::Stats.new
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
data/lib/sidekiq/job_logger.rb
CHANGED
@@ -3,7 +3,7 @@ module Sidekiq
|
|
3
3
|
class JobLogger
|
4
4
|
|
5
5
|
def call(item, queue)
|
6
|
-
start =
|
6
|
+
start = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
7
7
|
logger.info("start")
|
8
8
|
yield
|
9
9
|
logger.info("done: #{elapsed(start)} sec")
|
@@ -15,7 +15,7 @@ module Sidekiq
|
|
15
15
|
private
|
16
16
|
|
17
17
|
def elapsed(start)
|
18
|
-
(
|
18
|
+
(::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start).round(3)
|
19
19
|
end
|
20
20
|
|
21
21
|
def logger
|