sidekiq 6.5.1 → 6.5.12

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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +65 -0
  3. data/bin/sidekiqload +2 -2
  4. data/lib/sidekiq/api.rb +161 -37
  5. data/lib/sidekiq/cli.rb +13 -0
  6. data/lib/sidekiq/client.rb +2 -2
  7. data/lib/sidekiq/component.rb +2 -1
  8. data/lib/sidekiq/fetch.rb +2 -2
  9. data/lib/sidekiq/job_retry.rb +55 -35
  10. data/lib/sidekiq/launcher.rb +6 -4
  11. data/lib/sidekiq/metrics/deploy.rb +47 -0
  12. data/lib/sidekiq/metrics/query.rb +153 -0
  13. data/lib/sidekiq/metrics/shared.rb +94 -0
  14. data/lib/sidekiq/metrics/tracking.rb +134 -0
  15. data/lib/sidekiq/middleware/chain.rb +70 -35
  16. data/lib/sidekiq/middleware/current_attributes.rb +14 -12
  17. data/lib/sidekiq/monitor.rb +1 -1
  18. data/lib/sidekiq/paginator.rb +9 -1
  19. data/lib/sidekiq/processor.rb +9 -3
  20. data/lib/sidekiq/rails.rb +10 -11
  21. data/lib/sidekiq/redis_connection.rb +0 -2
  22. data/lib/sidekiq/scheduled.rb +43 -15
  23. data/lib/sidekiq/version.rb +1 -1
  24. data/lib/sidekiq/web/action.rb +3 -3
  25. data/lib/sidekiq/web/application.rb +21 -5
  26. data/lib/sidekiq/web/helpers.rb +17 -4
  27. data/lib/sidekiq/web.rb +5 -1
  28. data/lib/sidekiq/worker.rb +6 -3
  29. data/lib/sidekiq.rb +9 -1
  30. data/sidekiq.gemspec +2 -2
  31. data/web/assets/javascripts/application.js +2 -1
  32. data/web/assets/javascripts/chart.min.js +13 -0
  33. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  34. data/web/assets/javascripts/dashboard.js +0 -17
  35. data/web/assets/javascripts/graph.js +16 -0
  36. data/web/assets/javascripts/metrics.js +262 -0
  37. data/web/assets/stylesheets/application.css +44 -1
  38. data/web/locales/el.yml +43 -19
  39. data/web/locales/en.yml +7 -0
  40. data/web/locales/ja.yml +7 -0
  41. data/web/locales/zh-cn.yml +36 -11
  42. data/web/locales/zh-tw.yml +32 -7
  43. data/web/views/_nav.erb +1 -1
  44. data/web/views/busy.erb +7 -2
  45. data/web/views/dashboard.erb +1 -0
  46. data/web/views/metrics.erb +69 -0
  47. data/web/views/metrics_for_job.erb +87 -0
  48. data/web/views/queue.erb +5 -1
  49. metadata +29 -8
  50. data/lib/sidekiq/.DS_Store +0 -0
@@ -4,84 +4,98 @@ require "sidekiq/middleware/modules"
4
4
 
5
5
  module Sidekiq
6
6
  # Middleware is code configured to run before/after
7
- # a message is processed. It is patterned after Rack
7
+ # a job is processed. It is patterned after Rack
8
8
  # middleware. Middleware exists for the client side
9
9
  # (pushing jobs onto the queue) as well as the server
10
10
  # side (when jobs are actually processed).
11
11
  #
12
+ # Callers will register middleware Classes and Sidekiq will
13
+ # create new instances of the middleware for every job. This
14
+ # is important so that instance state is not shared accidentally
15
+ # between job executions.
16
+ #
12
17
  # To add middleware for the client:
13
18
  #
14
- # Sidekiq.configure_client do |config|
15
- # config.client_middleware do |chain|
16
- # chain.add MyClientHook
19
+ # Sidekiq.configure_client do |config|
20
+ # config.client_middleware do |chain|
21
+ # chain.add MyClientHook
22
+ # end
17
23
  # end
18
- # end
19
24
  #
20
25
  # To modify middleware for the server, just call
21
26
  # with another block:
22
27
  #
23
- # Sidekiq.configure_server do |config|
24
- # config.server_middleware do |chain|
25
- # chain.add MyServerHook
26
- # chain.remove ActiveRecord
28
+ # Sidekiq.configure_server do |config|
29
+ # config.server_middleware do |chain|
30
+ # chain.add MyServerHook
31
+ # chain.remove ActiveRecord
32
+ # end
27
33
  # end
28
- # end
29
34
  #
30
35
  # To insert immediately preceding another entry:
31
36
  #
32
- # Sidekiq.configure_client do |config|
33
- # config.client_middleware do |chain|
34
- # chain.insert_before ActiveRecord, MyClientHook
37
+ # Sidekiq.configure_client do |config|
38
+ # config.client_middleware do |chain|
39
+ # chain.insert_before ActiveRecord, MyClientHook
40
+ # end
35
41
  # end
36
- # end
37
42
  #
38
43
  # To insert immediately after another entry:
39
44
  #
40
- # Sidekiq.configure_client do |config|
41
- # config.client_middleware do |chain|
42
- # chain.insert_after ActiveRecord, MyClientHook
45
+ # Sidekiq.configure_client do |config|
46
+ # config.client_middleware do |chain|
47
+ # chain.insert_after ActiveRecord, MyClientHook
48
+ # end
43
49
  # end
44
- # end
45
50
  #
46
51
  # This is an example of a minimal server middleware:
47
52
  #
48
- # class MyServerHook
49
- # include Sidekiq::ServerMiddleware
50
- # def call(job_instance, msg, queue)
51
- # logger.info "Before job"
52
- # redis {|conn| conn.get("foo") } # do something in Redis
53
- # yield
54
- # logger.info "After job"
53
+ # class MyServerHook
54
+ # include Sidekiq::ServerMiddleware
55
+ #
56
+ # def call(job_instance, msg, queue)
57
+ # logger.info "Before job"
58
+ # redis {|conn| conn.get("foo") } # do something in Redis
59
+ # yield
60
+ # logger.info "After job"
61
+ # end
55
62
  # end
56
- # end
57
63
  #
58
64
  # This is an example of a minimal client middleware, note
59
65
  # the method must return the result or the job will not push
60
66
  # to Redis:
61
67
  #
62
- # class MyClientHook
63
- # include Sidekiq::ClientMiddleware
64
- # def call(job_class, msg, queue, redis_pool)
65
- # logger.info "Before push"
66
- # result = yield
67
- # logger.info "After push"
68
- # result
68
+ # class MyClientHook
69
+ # include Sidekiq::ClientMiddleware
70
+ #
71
+ # def call(job_class, msg, queue, redis_pool)
72
+ # logger.info "Before push"
73
+ # result = yield
74
+ # logger.info "After push"
75
+ # result
76
+ # end
69
77
  # end
70
- # end
71
78
  #
72
79
  module Middleware
73
80
  class Chain
74
81
  include Enumerable
75
82
 
83
+ # A unique instance of the middleware chain is created for
84
+ # each job executed in order to be thread-safe.
85
+ # @param copy [Sidekiq::Middleware::Chain] New instance of Chain
86
+ # @returns nil
76
87
  def initialize_copy(copy)
77
88
  copy.instance_variable_set(:@entries, entries.dup)
89
+ nil
78
90
  end
79
91
 
92
+ # Iterate through each middleware in the chain
80
93
  def each(&block)
81
94
  entries.each(&block)
82
95
  end
83
96
 
84
- def initialize(config = nil)
97
+ # @api private
98
+ def initialize(config = nil) # :nodoc:
85
99
  @config = config
86
100
  @entries = nil
87
101
  yield self if block_given?
@@ -91,20 +105,33 @@ module Sidekiq
91
105
  @entries ||= []
92
106
  end
93
107
 
108
+ # Remove all middleware matching the given Class
109
+ # @param klass [Class]
94
110
  def remove(klass)
95
111
  entries.delete_if { |entry| entry.klass == klass }
96
112
  end
97
113
 
114
+ # Add the given middleware to the end of the chain.
115
+ # Sidekiq will call `klass.new(*args)` to create a clean
116
+ # copy of your middleware for every job executed.
117
+ #
118
+ # chain.add(Statsd::Metrics, { collector: "localhost:8125" })
119
+ #
120
+ # @param klass [Class] Your middleware class
121
+ # @param *args [Array<Object>] Set of arguments to pass to every instance of your middleware
98
122
  def add(klass, *args)
99
123
  remove(klass)
100
124
  entries << Entry.new(@config, klass, *args)
101
125
  end
102
126
 
127
+ # Identical to {#add} except the middleware is added to the front of the chain.
103
128
  def prepend(klass, *args)
104
129
  remove(klass)
105
130
  entries.insert(0, Entry.new(@config, klass, *args))
106
131
  end
107
132
 
133
+ # Inserts +newklass+ before +oldklass+ in the chain.
134
+ # Useful if one middleware must run before another middleware.
108
135
  def insert_before(oldklass, newklass, *args)
109
136
  i = entries.index { |entry| entry.klass == newklass }
110
137
  new_entry = i.nil? ? Entry.new(@config, newklass, *args) : entries.delete_at(i)
@@ -112,6 +139,8 @@ module Sidekiq
112
139
  entries.insert(i, new_entry)
113
140
  end
114
141
 
142
+ # Inserts +newklass+ after +oldklass+ in the chain.
143
+ # Useful if one middleware must run after another middleware.
115
144
  def insert_after(oldklass, newklass, *args)
116
145
  i = entries.index { |entry| entry.klass == newklass }
117
146
  new_entry = i.nil? ? Entry.new(@config, newklass, *args) : entries.delete_at(i)
@@ -119,10 +148,12 @@ module Sidekiq
119
148
  entries.insert(i + 1, new_entry)
120
149
  end
121
150
 
151
+ # @return [Boolean] if the given class is already in the chain
122
152
  def exists?(klass)
123
153
  any? { |entry| entry.klass == klass }
124
154
  end
125
155
 
156
+ # @return [Boolean] if the chain contains no middleware
126
157
  def empty?
127
158
  @entries.nil? || @entries.empty?
128
159
  end
@@ -135,6 +166,8 @@ module Sidekiq
135
166
  entries.clear
136
167
  end
137
168
 
169
+ # Used by Sidekiq to execute the middleware at runtime
170
+ # @api private
138
171
  def invoke(*args)
139
172
  return yield if empty?
140
173
 
@@ -152,6 +185,8 @@ module Sidekiq
152
185
 
153
186
  private
154
187
 
188
+ # Represents each link in the middleware chain
189
+ # @api private
155
190
  class Entry
156
191
  attr_reader :klass
157
192
 
@@ -11,22 +11,24 @@ module Sidekiq
11
11
  #
12
12
  # # in your initializer
13
13
  # require "sidekiq/middleware/current_attributes"
14
- # Sidekiq::CurrentAttributes.persist(Myapp::Current)
14
+ # Sidekiq::CurrentAttributes.persist("Myapp::Current")
15
15
  #
16
16
  module CurrentAttributes
17
17
  class Save
18
18
  include Sidekiq::ClientMiddleware
19
19
 
20
20
  def initialize(cattr)
21
- @klass = cattr
21
+ @strklass = cattr
22
22
  end
23
23
 
24
24
  def call(_, job, _, _)
25
- attrs = @klass.attributes
26
- if job.has_key?("cattr")
27
- job["cattr"].merge!(attrs)
28
- else
29
- job["cattr"] = attrs
25
+ attrs = @strklass.constantize.attributes
26
+ if attrs.any?
27
+ if job.has_key?("cattr")
28
+ job["cattr"].merge!(attrs)
29
+ else
30
+ job["cattr"] = attrs
31
+ end
30
32
  end
31
33
  yield
32
34
  end
@@ -36,12 +38,12 @@ module Sidekiq
36
38
  include Sidekiq::ServerMiddleware
37
39
 
38
40
  def initialize(cattr)
39
- @klass = cattr
41
+ @strklass = cattr
40
42
  end
41
43
 
42
44
  def call(_, job, _, &block)
43
45
  if job.has_key?("cattr")
44
- @klass.set(job["cattr"], &block)
46
+ @strklass.constantize.set(job["cattr"], &block)
45
47
  else
46
48
  yield
47
49
  end
@@ -50,11 +52,11 @@ module Sidekiq
50
52
 
51
53
  def self.persist(klass)
52
54
  Sidekiq.configure_client do |config|
53
- config.client_middleware.add Save, klass
55
+ config.client_middleware.add Save, klass.to_s
54
56
  end
55
57
  Sidekiq.configure_server do |config|
56
- config.client_middleware.add Save, klass
57
- config.server_middleware.add Load, klass
58
+ config.client_middleware.add Save, klass.to_s
59
+ config.server_middleware.add Load, klass.to_s
58
60
  end
59
61
  end
60
62
  end
@@ -101,7 +101,7 @@ class Sidekiq::Monitor
101
101
  tags = [
102
102
  process["tag"],
103
103
  process["labels"],
104
- (process["quiet"] == "true" ? "quiet" : nil)
104
+ ((process["quiet"] == "true") ? "quiet" : nil)
105
105
  ].flatten.compact
106
106
  tags.any? ? "[#{tags.join("] [")}]" : nil
107
107
  end
@@ -3,7 +3,7 @@
3
3
  module Sidekiq
4
4
  module Paginator
5
5
  def page(key, pageidx = 1, page_size = 25, opts = nil)
6
- current_page = pageidx.to_i < 1 ? 1 : pageidx.to_i
6
+ current_page = (pageidx.to_i < 1) ? 1 : pageidx.to_i
7
7
  pageidx = current_page - 1
8
8
  total_size = 0
9
9
  items = []
@@ -43,5 +43,13 @@ module Sidekiq
43
43
  end
44
44
  end
45
45
  end
46
+
47
+ def page_items(items, pageidx = 1, page_size = 25)
48
+ current_page = (pageidx.to_i < 1) ? 1 : pageidx.to_i
49
+ pageidx = current_page - 1
50
+ starting = pageidx * page_size
51
+ items = items.to_a
52
+ [current_page, items.size, items[starting, page_size]]
53
+ end
46
54
  end
47
55
  end
@@ -152,8 +152,14 @@ module Sidekiq
152
152
  job_hash = Sidekiq.load_json(jobstr)
153
153
  rescue => ex
154
154
  handle_exception(ex, {context: "Invalid JSON for job", jobstr: jobstr})
155
- # we can't notify because the job isn't a valid hash payload.
156
- DeadSet.new.kill(jobstr, notify_failure: false)
155
+ now = Time.now.to_f
156
+ config.redis do |conn|
157
+ conn.multi do |xa|
158
+ xa.zadd("dead", now.to_s, jobstr)
159
+ xa.zremrangebyscore("dead", "-inf", now - config[:dead_timeout_in_seconds])
160
+ xa.zremrangebyrank("dead", 0, - config[:dead_max_jobs])
161
+ end
162
+ end
157
163
  return uow.acknowledge
158
164
  end
159
165
 
@@ -174,7 +180,7 @@ module Sidekiq
174
180
  # signals that we created a retry successfully. We can acknowlege the job.
175
181
  ack = true
176
182
  e = h.cause || h
177
- handle_exception(e, {context: "Job raised exception", job: job_hash, jobstr: jobstr})
183
+ handle_exception(e, {context: "Job raised exception", job: job_hash})
178
184
  raise e
179
185
  rescue Exception => ex
180
186
  # Unexpected error! This is very bad and indicates an exception that got past
data/lib/sidekiq/rails.rb CHANGED
@@ -37,17 +37,6 @@ module Sidekiq
37
37
  end
38
38
  end
39
39
 
40
- initializer "sidekiq.rails_logger" do
41
- Sidekiq.configure_server do |config|
42
- # This is the integration code necessary so that if a job uses `Rails.logger.info "Hello"`,
43
- # it will appear in the Sidekiq console with all of the job context. See #5021 and
44
- # https://github.com/rails/rails/blob/b5f2b550f69a99336482739000c58e4e04e033aa/railties/lib/rails/commands/server/server_command.rb#L82-L84
45
- unless ::Rails.logger == config.logger || ::ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout)
46
- ::Rails.logger.extend(::ActiveSupport::Logger.broadcast(config.logger))
47
- end
48
- end
49
- end
50
-
51
40
  config.before_configuration do
52
41
  dep = ActiveSupport::Deprecation.new("7.0", "Sidekiq")
53
42
  dep.deprecate_methods(Sidekiq.singleton_class,
@@ -62,6 +51,16 @@ module Sidekiq
62
51
  config.after_initialize do
63
52
  Sidekiq.configure_server do |config|
64
53
  config[:reloader] = Sidekiq::Rails::Reloader.new
54
+
55
+ # This is the integration code necessary so that if a job uses `Rails.logger.info "Hello"`,
56
+ # it will appear in the Sidekiq console with all of the job context.
57
+ unless ::Rails.logger == config.logger || ::ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout)
58
+ if ::Rails::VERSION::STRING < "7.1"
59
+ ::Rails.logger.extend(::ActiveSupport::Logger.broadcast(config.logger))
60
+ else
61
+ ::Rails.logger.broadcast_to(config.logger)
62
+ end
63
+ end
65
64
  end
66
65
  end
67
66
  end
@@ -46,8 +46,6 @@ module Sidekiq
46
46
  opts.delete(:network_timeout)
47
47
  end
48
48
 
49
- opts[:driver] ||= Redis::Connection.drivers.last || "ruby"
50
-
51
49
  # Issue #3303, redis-rb will silently retry an operation.
52
50
  # This can lead to duplicate jobs if Sidekiq::Client's LPUSH
53
51
  # is performed twice but I believe this is much, much rarer
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "sidekiq"
4
- require "sidekiq/api"
5
4
  require "sidekiq/component"
6
5
 
7
6
  module Sidekiq
@@ -148,13 +147,16 @@ module Sidekiq
148
147
  # As we run more processes, the scheduling interval average will approach an even spread
149
148
  # between 0 and poll interval so we don't need this artifical boost.
150
149
  #
151
- if process_count < 10
150
+ count = process_count
151
+ interval = poll_interval_average(count)
152
+
153
+ if count < 10
152
154
  # For small clusters, calculate a random interval that is ±50% the desired average.
153
- poll_interval_average * rand + poll_interval_average.to_f / 2
155
+ interval * rand + interval.to_f / 2
154
156
  else
155
157
  # With 10+ processes, we should have enough randomness to get decent polling
156
158
  # across the entire timespan
157
- poll_interval_average * rand
159
+ interval * rand
158
160
  end
159
161
  end
160
162
 
@@ -171,31 +173,52 @@ module Sidekiq
171
173
  # the same time: the thundering herd problem.
172
174
  #
173
175
  # We only do this if poll_interval_average is unset (the default).
174
- def poll_interval_average
175
- @config[:poll_interval_average] ||= scaled_poll_interval
176
+ def poll_interval_average(count)
177
+ @config[:poll_interval_average] || scaled_poll_interval(count)
176
178
  end
177
179
 
178
180
  # Calculates an average poll interval based on the number of known Sidekiq processes.
179
181
  # This minimizes a single point of failure by dispersing check-ins but without taxing
180
182
  # Redis if you run many Sidekiq processes.
181
- def scaled_poll_interval
183
+ def scaled_poll_interval(process_count)
182
184
  process_count * @config[:average_scheduled_poll_interval]
183
185
  end
184
186
 
185
187
  def process_count
186
- # The work buried within Sidekiq::ProcessSet#cleanup can be
187
- # expensive at scale. Cut it down by 90% with this counter.
188
- # NB: This method is only called by the scheduler thread so we
189
- # don't need to worry about the thread safety of +=.
190
- pcount = Sidekiq::ProcessSet.new(@count_calls % 10 == 0).size
188
+ pcount = Sidekiq.redis { |conn| conn.scard("processes") }
191
189
  pcount = 1 if pcount == 0
192
- @count_calls += 1
193
190
  pcount
194
191
  end
195
192
 
193
+ # A copy of Sidekiq::ProcessSet#cleanup because server
194
+ # should never depend on sidekiq/api.
195
+ def cleanup
196
+ # dont run cleanup more than once per minute
197
+ return 0 unless Sidekiq.redis { |conn| conn.set("process_cleanup", "1", nx: true, ex: 60) }
198
+
199
+ count = 0
200
+ Sidekiq.redis do |conn|
201
+ procs = conn.sscan_each("processes").to_a
202
+ heartbeats = conn.pipelined { |pipeline|
203
+ procs.each do |key|
204
+ pipeline.hget(key, "info")
205
+ end
206
+ }
207
+
208
+ # the hash named key has an expiry of 60 seconds.
209
+ # if it's not found, that means the process has not reported
210
+ # in to Redis and probably died.
211
+ to_prune = procs.select.with_index { |proc, i|
212
+ heartbeats[i].nil?
213
+ }
214
+ count = conn.srem("processes", to_prune) unless to_prune.empty?
215
+ end
216
+ count
217
+ end
218
+
196
219
  def initial_wait
197
- # Have all processes sleep between 5-15 seconds. 10 seconds
198
- # to give time for the heartbeat to register (if the poll interval is going to be calculated by the number
220
+ # Have all processes sleep between 5-15 seconds. 10 seconds to give time for
221
+ # the heartbeat to register (if the poll interval is going to be calculated by the number
199
222
  # of workers), and 5 random seconds to ensure they don't all hit Redis at the same time.
200
223
  total = 0
201
224
  total += INITIAL_WAIT unless @config[:poll_interval_average]
@@ -203,6 +226,11 @@ module Sidekiq
203
226
 
204
227
  @sleeper.pop(total)
205
228
  rescue Timeout::Error
229
+ ensure
230
+ # periodically clean out the `processes` set in Redis which can collect
231
+ # references to dead processes over time. The process count affects how
232
+ # often we scan for scheduled jobs.
233
+ cleanup
206
234
  end
207
235
  end
208
236
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sidekiq
4
- VERSION = "6.5.1"
4
+ VERSION = "6.5.12"
5
5
  end
@@ -15,11 +15,11 @@ module Sidekiq
15
15
  end
16
16
 
17
17
  def halt(res)
18
- throw :halt, [res, {"Content-Type" => "text/plain"}, [res.to_s]]
18
+ throw :halt, [res, {"content-type" => "text/plain"}, [res.to_s]]
19
19
  end
20
20
 
21
21
  def redirect(location)
22
- throw :halt, [302, {"Location" => "#{request.base_url}#{location}"}, []]
22
+ throw :halt, [302, {"location" => "#{request.base_url}#{location}"}, []]
23
23
  end
24
24
 
25
25
  def params
@@ -68,7 +68,7 @@ module Sidekiq
68
68
  end
69
69
 
70
70
  def json(payload)
71
- [200, {"Content-Type" => "application/json", "Cache-Control" => "private, no-store"}, [Sidekiq.dump_json(payload)]]
71
+ [200, {"content-type" => "application/json", "cache-control" => "private, no-store"}, [Sidekiq.dump_json(payload)]]
72
72
  end
73
73
 
74
74
  def initialize(env, block)
@@ -60,7 +60,23 @@ module Sidekiq
60
60
  erb(:dashboard)
61
61
  end
62
62
 
63
+ get "/metrics" do
64
+ q = Sidekiq::Metrics::Query.new
65
+ @query_result = q.top_jobs
66
+ erb(:metrics)
67
+ end
68
+
69
+ get "/metrics/:name" do
70
+ @name = route_params[:name]
71
+ q = Sidekiq::Metrics::Query.new
72
+ @query_result = q.for_job(@name)
73
+ erb(:metrics_for_job)
74
+ end
75
+
63
76
  get "/busy" do
77
+ @count = (params["count"] || 100).to_i
78
+ (@current_page, @total_size, @workset) = page_items(workset, params["page"], @count)
79
+
64
80
  erb(:busy)
65
81
  end
66
82
 
@@ -299,7 +315,7 @@ module Sidekiq
299
315
 
300
316
  def call(env)
301
317
  action = self.class.match(env)
302
- return [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass"}, ["Not Found"]] unless action
318
+ return [404, {"content-type" => "text/plain", "x-cascade" => "pass"}, ["Not Found"]] unless action
303
319
 
304
320
  app = @klass
305
321
  resp = catch(:halt) do
@@ -316,10 +332,10 @@ module Sidekiq
316
332
  else
317
333
  # rendered content goes here
318
334
  headers = {
319
- "Content-Type" => "text/html",
320
- "Cache-Control" => "private, no-store",
321
- "Content-Language" => action.locale,
322
- "Content-Security-Policy" => CSP_HEADER
335
+ "content-type" => "text/html",
336
+ "cache-control" => "private, no-store",
337
+ "content-language" => action.locale,
338
+ "content-security-policy" => CSP_HEADER
323
339
  }
324
340
  # we'll let Rack calculate Content-Length for us.
325
341
  [200, headers, [resp]]
@@ -15,7 +15,7 @@ module Sidekiq
15
15
  # so extensions can be localized
16
16
  @strings[lang] ||= settings.locales.each_with_object({}) do |path, global|
17
17
  find_locale_files(lang).each do |file|
18
- strs = YAML.load(File.open(file))
18
+ strs = YAML.safe_load(File.open(file))
19
19
  global.merge!(strs[lang])
20
20
  end
21
21
  end
@@ -137,7 +137,7 @@ module Sidekiq
137
137
  end
138
138
 
139
139
  def sort_direction_label
140
- params[:direction] == "asc" ? "&uarr;" : "&darr;"
140
+ (params[:direction] == "asc") ? "&uarr;" : "&darr;"
141
141
  end
142
142
 
143
143
  def workset
@@ -148,6 +148,19 @@ module Sidekiq
148
148
  @processes ||= Sidekiq::ProcessSet.new
149
149
  end
150
150
 
151
+ # Sorts processes by hostname following the natural sort order
152
+ def sorted_processes
153
+ @sorted_processes ||= begin
154
+ return processes unless processes.all? { |p| p["hostname"] }
155
+
156
+ processes.to_a.sort_by do |process|
157
+ # Kudos to `shurikk` on StackOverflow
158
+ # https://stackoverflow.com/a/15170063/575547
159
+ process["hostname"].split(/(\d+)/).map { |a| /\d+/.match?(a) ? a.to_i : a }
160
+ end
161
+ end
162
+ end
163
+
151
164
  def stats
152
165
  @stats ||= Sidekiq::Stats.new
153
166
  end
@@ -175,7 +188,7 @@ module Sidekiq
175
188
  end
176
189
 
177
190
  def current_status
178
- workset.size == 0 ? "idle" : "active"
191
+ (workset.size == 0) ? "idle" : "active"
179
192
  end
180
193
 
181
194
  def relative_time(time)
@@ -208,7 +221,7 @@ module Sidekiq
208
221
  end
209
222
 
210
223
  def truncate(text, truncate_after_chars = 2000)
211
- truncate_after_chars && text.size > truncate_after_chars ? "#{text[0..truncate_after_chars]}..." : text
224
+ (truncate_after_chars && text.size > truncate_after_chars) ? "#{text[0..truncate_after_chars]}..." : text
212
225
  end
213
226
 
214
227
  def display_args(args, truncate_after_chars = 2000)
data/lib/sidekiq/web.rb CHANGED
@@ -33,6 +33,10 @@ module Sidekiq
33
33
  "Dead" => "morgue"
34
34
  }
35
35
 
36
+ if ENV["SIDEKIQ_METRICS_BETA"] == "1"
37
+ DEFAULT_TABS["Metrics"] = "metrics"
38
+ end
39
+
36
40
  class << self
37
41
  def settings
38
42
  self
@@ -144,7 +148,7 @@ module Sidekiq
144
148
  m = middlewares
145
149
 
146
150
  rules = []
147
- rules = [[:all, {"Cache-Control" => "public, max-age=86400"}]] unless ENV["SIDEKIQ_WEB_TESTING"]
151
+ rules = [[:all, {"cache-control" => "public, max-age=86400"}]] unless ENV["SIDEKIQ_WEB_TESTING"]
148
152
 
149
153
  ::Rack::Builder.new do
150
154
  use Rack::Static, urls: ["/stylesheets", "/images", "/javascripts"],
@@ -257,7 +257,7 @@ module Sidekiq
257
257
  def at(interval)
258
258
  int = interval.to_f
259
259
  now = Time.now.to_f
260
- ts = (int < 1_000_000_000 ? now + int : int)
260
+ ts = ((int < 1_000_000_000) ? now + int : int)
261
261
  # Optimization to enqueue something now that is scheduled to go out now or in the past
262
262
  @opts["at"] = ts if ts > now
263
263
  self
@@ -324,7 +324,7 @@ module Sidekiq
324
324
  def perform_in(interval, *args)
325
325
  int = interval.to_f
326
326
  now = Time.now.to_f
327
- ts = (int < 1_000_000_000 ? now + int : int)
327
+ ts = ((int < 1_000_000_000) ? now + int : int)
328
328
 
329
329
  item = {"class" => self, "args" => args}
330
330
 
@@ -340,7 +340,7 @@ module Sidekiq
340
340
  # Legal options:
341
341
  #
342
342
  # queue - use a named queue for this Worker, default 'default'
343
- # retry - enable the RetryJobs middleware for this Worker, *true* to use the default
343
+ # retry - enable retries via JobRetry, *true* to use the default
344
344
  # or *Integer* count
345
345
  # backtrace - whether to save any error backtrace in the retry payload to display in web UI,
346
346
  # can be true, false or an integer number of lines to save, default *false*
@@ -348,6 +348,9 @@ module Sidekiq
348
348
  #
349
349
  # In practice, any option is allowed. This is the main mechanism to configure the
350
350
  # options for a specific job.
351
+ #
352
+ # These options will be saved into the serialized job when enqueued by
353
+ # the client.
351
354
  def sidekiq_options(opts = {})
352
355
  super
353
356
  end