sidekiq 6.0.0 → 6.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of sidekiq might be problematic. Click here for more details.

Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +258 -2
  3. data/LICENSE +1 -1
  4. data/README.md +6 -8
  5. data/bin/sidekiq +26 -2
  6. data/bin/sidekiqload +8 -4
  7. data/bin/sidekiqmon +4 -5
  8. data/lib/generators/sidekiq/worker_generator.rb +11 -1
  9. data/lib/sidekiq/api.rb +220 -145
  10. data/lib/sidekiq/cli.rb +64 -27
  11. data/lib/sidekiq/client.rb +31 -14
  12. data/lib/sidekiq/extensions/action_mailer.rb +3 -2
  13. data/lib/sidekiq/extensions/active_record.rb +4 -3
  14. data/lib/sidekiq/extensions/class_methods.rb +5 -4
  15. data/lib/sidekiq/extensions/generic_proxy.rb +3 -1
  16. data/lib/sidekiq/fetch.rb +36 -27
  17. data/lib/sidekiq/job.rb +13 -0
  18. data/lib/sidekiq/job_logger.rb +13 -5
  19. data/lib/sidekiq/job_retry.rb +27 -17
  20. data/lib/sidekiq/launcher.rb +110 -28
  21. data/lib/sidekiq/logger.rb +109 -12
  22. data/lib/sidekiq/manager.rb +4 -4
  23. data/lib/sidekiq/middleware/chain.rb +17 -6
  24. data/lib/sidekiq/middleware/current_attributes.rb +48 -0
  25. data/lib/sidekiq/monitor.rb +3 -18
  26. data/lib/sidekiq/paginator.rb +7 -2
  27. data/lib/sidekiq/processor.rb +22 -24
  28. data/lib/sidekiq/rails.rb +27 -18
  29. data/lib/sidekiq/redis_connection.rb +19 -13
  30. data/lib/sidekiq/scheduled.rb +37 -11
  31. data/lib/sidekiq/sd_notify.rb +149 -0
  32. data/lib/sidekiq/systemd.rb +24 -0
  33. data/lib/sidekiq/testing.rb +14 -4
  34. data/lib/sidekiq/util.rb +28 -2
  35. data/lib/sidekiq/version.rb +1 -1
  36. data/lib/sidekiq/web/action.rb +2 -2
  37. data/lib/sidekiq/web/application.rb +37 -30
  38. data/lib/sidekiq/web/csrf_protection.rb +180 -0
  39. data/lib/sidekiq/web/helpers.rb +51 -33
  40. data/lib/sidekiq/web/router.rb +6 -5
  41. data/lib/sidekiq/web.rb +37 -73
  42. data/lib/sidekiq/worker.rb +78 -14
  43. data/lib/sidekiq.rb +24 -8
  44. data/sidekiq.gemspec +13 -6
  45. data/web/assets/images/apple-touch-icon.png +0 -0
  46. data/web/assets/javascripts/application.js +83 -64
  47. data/web/assets/javascripts/dashboard.js +53 -53
  48. data/web/assets/stylesheets/application-dark.css +147 -0
  49. data/web/assets/stylesheets/application-rtl.css +0 -4
  50. data/web/assets/stylesheets/application.css +43 -230
  51. data/web/locales/ar.yml +8 -2
  52. data/web/locales/de.yml +14 -2
  53. data/web/locales/en.yml +6 -1
  54. data/web/locales/es.yml +18 -2
  55. data/web/locales/fr.yml +10 -3
  56. data/web/locales/ja.yml +5 -0
  57. data/web/locales/lt.yml +83 -0
  58. data/web/locales/pl.yml +4 -4
  59. data/web/locales/ru.yml +4 -0
  60. data/web/locales/vi.yml +83 -0
  61. data/web/views/_footer.erb +1 -1
  62. data/web/views/_job_info.erb +3 -2
  63. data/web/views/_poll_link.erb +2 -5
  64. data/web/views/_summary.erb +7 -7
  65. data/web/views/busy.erb +54 -20
  66. data/web/views/dashboard.erb +22 -14
  67. data/web/views/dead.erb +3 -3
  68. data/web/views/layout.erb +3 -1
  69. data/web/views/morgue.erb +9 -6
  70. data/web/views/queue.erb +19 -10
  71. data/web/views/queues.erb +10 -2
  72. data/web/views/retries.erb +11 -8
  73. data/web/views/retry.erb +3 -3
  74. data/web/views/scheduled.erb +5 -2
  75. metadata +29 -50
  76. data/.circleci/config.yml +0 -61
  77. data/.github/contributing.md +0 -32
  78. data/.github/issue_template.md +0 -11
  79. data/.gitignore +0 -13
  80. data/.standard.yml +0 -20
  81. data/3.0-Upgrade.md +0 -70
  82. data/4.0-Upgrade.md +0 -53
  83. data/5.0-Upgrade.md +0 -56
  84. data/6.0-Upgrade.md +0 -70
  85. data/COMM-LICENSE +0 -97
  86. data/Ent-2.0-Upgrade.md +0 -37
  87. data/Ent-Changes.md +0 -250
  88. data/Gemfile +0 -24
  89. data/Gemfile.lock +0 -196
  90. data/Pro-2.0-Upgrade.md +0 -138
  91. data/Pro-3.0-Upgrade.md +0 -44
  92. data/Pro-4.0-Upgrade.md +0 -35
  93. data/Pro-5.0-Upgrade.md +0 -25
  94. data/Pro-Changes.md +0 -768
  95. data/Rakefile +0 -10
  96. data/code_of_conduct.md +0 -50
@@ -16,12 +16,13 @@ module Sidekiq
16
16
  proc { Sidekiq::VERSION },
17
17
  proc { |me, data| data["tag"] },
18
18
  proc { |me, data| "[#{Processor::WORKER_STATE.size} of #{data["concurrency"]} busy]" },
19
- proc { |me, data| "stopping" if me.stopping? },
19
+ proc { |me, data| "stopping" if me.stopping? }
20
20
  ]
21
21
 
22
22
  attr_accessor :manager, :poller, :fetcher
23
23
 
24
24
  def initialize(options)
25
+ options[:fetch] ||= BasicFetch.new(options)
25
26
  @manager = Sidekiq::Manager.new(options)
26
27
  @poller = Sidekiq::Scheduled::Poller.new
27
28
  @done = false
@@ -56,7 +57,7 @@ module Sidekiq
56
57
 
57
58
  # Requeue everything in case there was a worker who grabbed work while stopped
58
59
  # This call is a no-op in Sidekiq but necessary for Sidekiq Pro.
59
- strategy = (@options[:fetch] || Sidekiq::BasicFetch)
60
+ strategy = @options[:fetch]
60
61
  strategy.bulk_requeue([], @options)
61
62
 
62
63
  clear_heartbeat
@@ -68,10 +69,12 @@ module Sidekiq
68
69
 
69
70
  private unless $TESTING
70
71
 
72
+ BEAT_PAUSE = 5
73
+
71
74
  def start_heartbeat
72
75
  loop do
73
76
  heartbeat
74
- sleep 5
77
+ sleep BEAT_PAUSE
75
78
  end
76
79
  Sidekiq.logger.info("Heartbeat stopping...")
77
80
  end
@@ -83,7 +86,7 @@ module Sidekiq
83
86
  Sidekiq.redis do |conn|
84
87
  conn.pipelined do
85
88
  conn.srem("processes", identity)
86
- conn.del("#{identity}:workers")
89
+ conn.unlink("#{identity}:workers")
87
90
  end
88
91
  end
89
92
  rescue
@@ -96,6 +99,32 @@ module Sidekiq
96
99
 
97
100
  end
98
101
 
102
+ def self.flush_stats
103
+ fails = Processor::FAILURE.reset
104
+ procd = Processor::PROCESSED.reset
105
+ return if fails + procd == 0
106
+
107
+ nowdate = Time.now.utc.strftime("%Y-%m-%d")
108
+ begin
109
+ Sidekiq.redis do |conn|
110
+ conn.pipelined do
111
+ conn.incrby("stat:processed", procd)
112
+ conn.incrby("stat:processed:#{nowdate}", procd)
113
+ conn.expire("stat:processed:#{nowdate}", STATS_TTL)
114
+
115
+ conn.incrby("stat:failed", fails)
116
+ conn.incrby("stat:failed:#{nowdate}", fails)
117
+ conn.expire("stat:failed:#{nowdate}", STATS_TTL)
118
+ end
119
+ end
120
+ rescue => ex
121
+ # we're exiting the process, things might be shut down so don't
122
+ # try to handle the exception
123
+ Sidekiq.logger.warn("Unable to flush stats: #{ex}")
124
+ end
125
+ end
126
+ at_exit(&method(:flush_stats))
127
+
99
128
  def ❤
100
129
  key = identity
101
130
  fails = procd = 0
@@ -118,7 +147,7 @@ module Sidekiq
118
147
  conn.incrby("stat:failed:#{nowdate}", fails)
119
148
  conn.expire("stat:failed:#{nowdate}", STATS_TTL)
120
149
 
121
- conn.del(workers_key)
150
+ conn.unlink(workers_key)
122
151
  curstate.each_pair do |tid, hash|
123
152
  conn.hset(workers_key, tid, Sidekiq.dump_json(hash))
124
153
  end
@@ -126,18 +155,24 @@ module Sidekiq
126
155
  end
127
156
  end
128
157
 
158
+ rtt = check_rtt
159
+
129
160
  fails = procd = 0
161
+ kb = memory_usage(::Process.pid)
130
162
 
131
163
  _, exists, _, _, msg = Sidekiq.redis { |conn|
132
- res = conn.multi {
164
+ conn.multi {
133
165
  conn.sadd("processes", key)
134
- conn.exists(key)
135
- conn.hmset(key, "info", to_json, "busy", curstate.size, "beat", Time.now.to_f, "quiet", @done)
166
+ conn.exists?(key)
167
+ conn.hmset(key, "info", to_json,
168
+ "busy", curstate.size,
169
+ "beat", Time.now.to_f,
170
+ "rtt_us", rtt,
171
+ "quiet", @done,
172
+ "rss", kb)
136
173
  conn.expire(key, 60)
137
174
  conn.rpop("#{key}-signals")
138
175
  }
139
-
140
- res
141
176
  }
142
177
 
143
178
  # first heartbeat or recovering from an outage and need to reestablish our heartbeat
@@ -148,34 +183,81 @@ module Sidekiq
148
183
  ::Process.kill(msg, ::Process.pid)
149
184
  rescue => e
150
185
  # ignore all redis/network issues
151
- logger.error("heartbeat: #{e.message}")
186
+ logger.error("heartbeat: #{e}")
152
187
  # don't lose the counts if there was a network issue
153
188
  Processor::PROCESSED.incr(procd)
154
189
  Processor::FAILURE.incr(fails)
155
190
  end
156
191
  end
157
192
 
158
- def to_data
159
- @data ||= begin
160
- {
161
- "hostname" => hostname,
162
- "started_at" => Time.now.to_f,
163
- "pid" => ::Process.pid,
164
- "tag" => @options[:tag] || "",
165
- "concurrency" => @options[:concurrency],
166
- "queues" => @options[:queues].uniq,
167
- "labels" => @options[:labels],
168
- "identity" => identity,
169
- }
193
+ # We run the heartbeat every five seconds.
194
+ # Capture five samples of RTT, log a warning if each sample
195
+ # is above our warning threshold.
196
+ RTT_READINGS = RingBuffer.new(5)
197
+ RTT_WARNING_LEVEL = 50_000
198
+
199
+ def check_rtt
200
+ a = b = 0
201
+ Sidekiq.redis do |x|
202
+ a = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
203
+ x.ping
204
+ b = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
205
+ end
206
+ rtt = b - a
207
+ RTT_READINGS << rtt
208
+ # Ideal RTT for Redis is < 1000µs
209
+ # Workable is < 10,000µs
210
+ # Log a warning if it's a disaster.
211
+ if RTT_READINGS.all? { |x| x > RTT_WARNING_LEVEL }
212
+ Sidekiq.logger.warn <<~EOM
213
+ Your Redis network connection is performing extremely poorly.
214
+ Last RTT readings were #{RTT_READINGS.buffer.inspect}, ideally these should be < 1000.
215
+ Ensure Redis is running in the same AZ or datacenter as Sidekiq.
216
+ If these values are close to 100,000, that means your Sidekiq process may be
217
+ CPU overloaded; see https://github.com/mperham/sidekiq/discussions/5039
218
+ EOM
219
+ RTT_READINGS.reset
170
220
  end
221
+ rtt
222
+ end
223
+
224
+ MEMORY_GRABBER = case RUBY_PLATFORM
225
+ when /linux/
226
+ ->(pid) {
227
+ IO.readlines("/proc/#{$$}/status").each do |line|
228
+ next unless line.start_with?("VmRSS:")
229
+ break line.split[1].to_i
230
+ end
231
+ }
232
+ when /darwin|bsd/
233
+ ->(pid) {
234
+ `ps -o pid,rss -p #{pid}`.lines.last.split.last.to_i
235
+ }
236
+ else
237
+ ->(pid) { 0 }
238
+ end
239
+
240
+ def memory_usage(pid)
241
+ MEMORY_GRABBER.call(pid)
242
+ end
243
+
244
+ def to_data
245
+ @data ||= {
246
+ "hostname" => hostname,
247
+ "started_at" => Time.now.to_f,
248
+ "pid" => ::Process.pid,
249
+ "tag" => @options[:tag] || "",
250
+ "concurrency" => @options[:concurrency],
251
+ "queues" => @options[:queues].uniq,
252
+ "labels" => @options[:labels],
253
+ "identity" => identity
254
+ }
171
255
  end
172
256
 
173
257
  def to_json
174
- @json ||= begin
175
- # this data changes infrequently so dump it to a string
176
- # now so we don't need to dump it every heartbeat.
177
- Sidekiq.dump_json(to_data)
178
- end
258
+ # this data changes infrequently so dump it to a string
259
+ # now so we don't need to dump it every heartbeat.
260
+ @json ||= Sidekiq.dump_json(to_data)
179
261
  end
180
262
  end
181
263
  end
@@ -4,22 +4,110 @@ require "logger"
4
4
  require "time"
5
5
 
6
6
  module Sidekiq
7
- class Logger < ::Logger
8
- def initialize(*args)
9
- super
7
+ module Context
8
+ def self.with(hash)
9
+ orig_context = current.dup
10
+ current.merge!(hash)
11
+ yield
12
+ ensure
13
+ Thread.current[:sidekiq_context] = orig_context
14
+ end
10
15
 
11
- self.formatter = Sidekiq.log_formatter
16
+ def self.current
17
+ Thread.current[:sidekiq_context] ||= {}
18
+ end
19
+ end
20
+
21
+ module LoggingUtils
22
+ LEVELS = {
23
+ "debug" => 0,
24
+ "info" => 1,
25
+ "warn" => 2,
26
+ "error" => 3,
27
+ "fatal" => 4
28
+ }
29
+ LEVELS.default_proc = proc do |_, level|
30
+ Sidekiq.logger.warn("Invalid log level: #{level.inspect}")
31
+ nil
32
+ end
33
+
34
+ def debug?
35
+ level <= 0
36
+ end
37
+
38
+ def info?
39
+ level <= 1
40
+ end
41
+
42
+ def warn?
43
+ level <= 2
44
+ end
45
+
46
+ def error?
47
+ level <= 3
48
+ end
49
+
50
+ def fatal?
51
+ level <= 4
12
52
  end
13
53
 
14
- def with_context(hash)
15
- ctx.merge!(hash)
54
+ def local_level
55
+ Thread.current[:sidekiq_log_level]
56
+ end
57
+
58
+ def local_level=(level)
59
+ case level
60
+ when Integer
61
+ Thread.current[:sidekiq_log_level] = level
62
+ when Symbol, String
63
+ Thread.current[:sidekiq_log_level] = LEVELS[level.to_s]
64
+ when nil
65
+ Thread.current[:sidekiq_log_level] = nil
66
+ else
67
+ raise ArgumentError, "Invalid log level: #{level.inspect}"
68
+ end
69
+ end
70
+
71
+ def level
72
+ local_level || super
73
+ end
74
+
75
+ # Change the thread-local level for the duration of the given block.
76
+ def log_at(level)
77
+ old_local_level = local_level
78
+ self.local_level = level
16
79
  yield
17
80
  ensure
18
- hash.keys.each { |key| ctx.delete(key) }
81
+ self.local_level = old_local_level
19
82
  end
20
83
 
21
- def ctx
22
- Thread.current[:sidekiq_context] ||= {}
84
+ # Redefined to check severity against #level, and thus the thread-local level, rather than +@level+.
85
+ # FIXME: Remove when the minimum Ruby version supports overriding Logger#level.
86
+ def add(severity, message = nil, progname = nil, &block)
87
+ severity ||= ::Logger::UNKNOWN
88
+ progname ||= @progname
89
+
90
+ return true if @logdev.nil? || severity < level
91
+
92
+ if message.nil?
93
+ if block
94
+ message = yield
95
+ else
96
+ message = progname
97
+ progname = @progname
98
+ end
99
+ end
100
+
101
+ @logdev.write format_message(format_severity(severity), Time.now, progname, message)
102
+ end
103
+ end
104
+
105
+ class Logger < ::Logger
106
+ include LoggingUtils
107
+
108
+ def initialize(*args, **kwargs)
109
+ super
110
+ self.formatter = Sidekiq.log_formatter
23
111
  end
24
112
 
25
113
  module Formatters
@@ -29,11 +117,20 @@ module Sidekiq
29
117
  end
30
118
 
31
119
  def ctx
32
- Thread.current[:sidekiq_context] ||= {}
120
+ Sidekiq::Context.current
33
121
  end
34
122
 
35
123
  def format_context
36
- " " + ctx.compact.map { |k, v| "#{k}=#{v}" }.join(" ") if ctx.any?
124
+ if ctx.any?
125
+ " " + ctx.compact.map { |k, v|
126
+ case v
127
+ when Array
128
+ "#{k}=#{v.join(",")}"
129
+ else
130
+ "#{k}=#{v}"
131
+ end
132
+ }.join(" ")
133
+ end
37
134
  end
38
135
  end
39
136
 
@@ -56,7 +153,7 @@ module Sidekiq
56
153
  pid: ::Process.pid,
57
154
  tid: tid,
58
155
  lvl: severity,
59
- msg: message,
156
+ msg: message
60
157
  }
61
158
  c = ctx
62
159
  hash["ctx"] = c unless c.empty?
@@ -35,7 +35,7 @@ module Sidekiq
35
35
  @done = false
36
36
  @workers = Set.new
37
37
  @count.times do
38
- @workers << Processor.new(self)
38
+ @workers << Processor.new(self, options)
39
39
  end
40
40
  @plock = Mutex.new
41
41
  end
@@ -56,7 +56,7 @@ module Sidekiq
56
56
  end
57
57
 
58
58
  # hack for quicker development / testing environment #2774
59
- PAUSE_TIME = STDOUT.tty? ? 0.1 : 0.5
59
+ PAUSE_TIME = $stdout.tty? ? 0.1 : 0.5
60
60
 
61
61
  def stop(deadline)
62
62
  quiet
@@ -90,7 +90,7 @@ module Sidekiq
90
90
  @plock.synchronize do
91
91
  @workers.delete(processor)
92
92
  unless @done
93
- p = Processor.new(self)
93
+ p = Processor.new(self, options)
94
94
  @workers << p
95
95
  p.start
96
96
  end
@@ -123,7 +123,7 @@ module Sidekiq
123
123
  # contract says that jobs are run AT LEAST once. Process termination
124
124
  # is delayed until we're certain the jobs are back in Redis because
125
125
  # it is worse to lose a job than to run it twice.
126
- strategy = (@options[:fetch] || Sidekiq::BasicFetch)
126
+ strategy = @options[:fetch]
127
127
  strategy.bulk_requeue(jobs, @options)
128
128
  end
129
129
 
@@ -67,7 +67,6 @@ module Sidekiq
67
67
  module Middleware
68
68
  class Chain
69
69
  include Enumerable
70
- attr_reader :entries
71
70
 
72
71
  def initialize_copy(copy)
73
72
  copy.instance_variable_set(:@entries, entries.dup)
@@ -78,21 +77,25 @@ module Sidekiq
78
77
  end
79
78
 
80
79
  def initialize
81
- @entries = []
80
+ @entries = nil
82
81
  yield self if block_given?
83
82
  end
84
83
 
84
+ def entries
85
+ @entries ||= []
86
+ end
87
+
85
88
  def remove(klass)
86
89
  entries.delete_if { |entry| entry.klass == klass }
87
90
  end
88
91
 
89
92
  def add(klass, *args)
90
- remove(klass) if exists?(klass)
93
+ remove(klass)
91
94
  entries << Entry.new(klass, *args)
92
95
  end
93
96
 
94
97
  def prepend(klass, *args)
95
- remove(klass) if exists?(klass)
98
+ remove(klass)
96
99
  entries.insert(0, Entry.new(klass, *args))
97
100
  end
98
101
 
@@ -114,6 +117,10 @@ module Sidekiq
114
117
  any? { |entry| entry.klass == klass }
115
118
  end
116
119
 
120
+ def empty?
121
+ @entries.nil? || @entries.empty?
122
+ end
123
+
117
124
  def retrieve
118
125
  map(&:make_new)
119
126
  end
@@ -123,8 +130,10 @@ module Sidekiq
123
130
  end
124
131
 
125
132
  def invoke(*args)
126
- chain = retrieve.dup
127
- traverse_chain = lambda do
133
+ return yield if empty?
134
+
135
+ chain = retrieve
136
+ traverse_chain = proc do
128
137
  if chain.empty?
129
138
  yield
130
139
  else
@@ -135,6 +144,8 @@ module Sidekiq
135
144
  end
136
145
  end
137
146
 
147
+ private
148
+
138
149
  class Entry
139
150
  attr_reader :klass
140
151
 
@@ -0,0 +1,48 @@
1
+ require "active_support/current_attributes"
2
+
3
+ module Sidekiq
4
+ ##
5
+ # Automatically save and load any current attributes in the execution context
6
+ # so context attributes "flow" from Rails actions into any associated jobs.
7
+ # This can be useful for multi-tenancy, i18n locale, timezone, any implicit
8
+ # per-request attribute. See +ActiveSupport::CurrentAttributes+.
9
+ #
10
+ # @example
11
+ #
12
+ # # in your initializer
13
+ # require "sidekiq/middleware/current_attributes"
14
+ # Sidekiq::CurrentAttributes.persist(Myapp::Current)
15
+ #
16
+ module CurrentAttributes
17
+ class Save
18
+ def initialize(with:)
19
+ @klass = with
20
+ end
21
+
22
+ def call(_, job, _, _)
23
+ job["ctx"] = @klass.attributes
24
+ yield
25
+ end
26
+ end
27
+
28
+ class Load
29
+ def initialize(with:)
30
+ @klass = with
31
+ end
32
+
33
+ def call(_, job, _, &block)
34
+ @klass.set(job["ctx"], &block)
35
+ end
36
+ end
37
+
38
+ def self.persist(klass)
39
+ Sidekiq.configure_client do |config|
40
+ config.client_middleware.add Save, with: klass
41
+ end
42
+ Sidekiq.configure_server do |config|
43
+ config.client_middleware.add Save, with: klass
44
+ config.server_middleware.add Load, with: klass
45
+ end
46
+ end
47
+ end
48
+ end
@@ -4,21 +4,6 @@ require "fileutils"
4
4
  require "sidekiq/api"
5
5
 
6
6
  class Sidekiq::Monitor
7
- CMD = File.basename($PROGRAM_NAME)
8
-
9
- attr_reader :stage
10
-
11
- def self.print_usage
12
- puts "#{CMD} - monitor Sidekiq from the command line."
13
- puts
14
- puts "Usage: #{CMD} status <section>"
15
- puts
16
- puts " <section> (optional) view a specific section of the status output"
17
- puts " Valid sections are: #{Sidekiq::Monitor::Status::VALID_SECTIONS.join(", ")}"
18
- puts
19
- puts "Set REDIS_URL to the location of your Redis server if not monitoring localhost."
20
- end
21
-
22
7
  class Status
23
8
  VALID_SECTIONS = %w[all version overview processes queues]
24
9
  COL_PAD = 2
@@ -47,7 +32,7 @@ class Sidekiq::Monitor
47
32
 
48
33
  def version
49
34
  puts "Sidekiq #{Sidekiq::VERSION}"
50
- puts Time.now
35
+ puts Time.now.utc
51
36
  end
52
37
 
53
38
  def overview
@@ -77,7 +62,7 @@ class Sidekiq::Monitor
77
62
  columns = {
78
63
  name: [:ljust, (["name"] + queue_data.map(&:name)).map(&:length).max + COL_PAD],
79
64
  size: [:rjust, (["size"] + queue_data.map(&:size)).map(&:length).max + COL_PAD],
80
- latency: [:rjust, (["latency"] + queue_data.map(&:latency)).map(&:length).max + COL_PAD],
65
+ latency: [:rjust, (["latency"] + queue_data.map(&:latency)).map(&:length).max + COL_PAD]
81
66
  }
82
67
  columns.each { |col, (dir, width)| print col.to_s.upcase.public_send(dir, width) }
83
68
  puts
@@ -116,7 +101,7 @@ class Sidekiq::Monitor
116
101
  tags = [
117
102
  process["tag"],
118
103
  process["labels"],
119
- (process["quiet"] == "true" ? "quiet" : nil),
104
+ (process["quiet"] == "true" ? "quiet" : nil)
120
105
  ].flatten.compact
121
106
  tags.any? ? "[#{tags.join("] [")}]" : nil
122
107
  end
@@ -12,10 +12,10 @@ module Sidekiq
12
12
 
13
13
  Sidekiq.redis do |conn|
14
14
  type = conn.type(key)
15
+ rev = opts && opts[:reverse]
15
16
 
16
17
  case type
17
18
  when "zset"
18
- rev = opts && opts[:reverse]
19
19
  total_size, items = conn.multi {
20
20
  conn.zcard(key)
21
21
  if rev
@@ -28,8 +28,13 @@ module Sidekiq
28
28
  when "list"
29
29
  total_size, items = conn.multi {
30
30
  conn.llen(key)
31
- conn.lrange(key, starting, ending)
31
+ if rev
32
+ conn.lrange(key, -ending - 1, -starting - 1)
33
+ else
34
+ conn.lrange(key, starting, ending)
35
+ end
32
36
  }
37
+ items.reverse! if rev
33
38
  [current_page, total_size, items]
34
39
  when "none"
35
40
  [1, 0, []]