sidekiq 6.5.1 → 6.5.12

Sign up to get free protection for your applications and to get access to all the features.
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