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.
- checksums.yaml +4 -4
- data/Changes.md +65 -0
- data/bin/sidekiqload +2 -2
- data/lib/sidekiq/api.rb +161 -37
- data/lib/sidekiq/cli.rb +13 -0
- data/lib/sidekiq/client.rb +2 -2
- data/lib/sidekiq/component.rb +2 -1
- data/lib/sidekiq/fetch.rb +2 -2
- data/lib/sidekiq/job_retry.rb +55 -35
- data/lib/sidekiq/launcher.rb +6 -4
- data/lib/sidekiq/metrics/deploy.rb +47 -0
- data/lib/sidekiq/metrics/query.rb +153 -0
- data/lib/sidekiq/metrics/shared.rb +94 -0
- data/lib/sidekiq/metrics/tracking.rb +134 -0
- data/lib/sidekiq/middleware/chain.rb +70 -35
- data/lib/sidekiq/middleware/current_attributes.rb +14 -12
- data/lib/sidekiq/monitor.rb +1 -1
- data/lib/sidekiq/paginator.rb +9 -1
- data/lib/sidekiq/processor.rb +9 -3
- data/lib/sidekiq/rails.rb +10 -11
- data/lib/sidekiq/redis_connection.rb +0 -2
- data/lib/sidekiq/scheduled.rb +43 -15
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/action.rb +3 -3
- data/lib/sidekiq/web/application.rb +21 -5
- data/lib/sidekiq/web/helpers.rb +17 -4
- data/lib/sidekiq/web.rb +5 -1
- data/lib/sidekiq/worker.rb +6 -3
- data/lib/sidekiq.rb +9 -1
- data/sidekiq.gemspec +2 -2
- data/web/assets/javascripts/application.js +2 -1
- data/web/assets/javascripts/chart.min.js +13 -0
- data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
- data/web/assets/javascripts/dashboard.js +0 -17
- data/web/assets/javascripts/graph.js +16 -0
- data/web/assets/javascripts/metrics.js +262 -0
- data/web/assets/stylesheets/application.css +44 -1
- data/web/locales/el.yml +43 -19
- data/web/locales/en.yml +7 -0
- data/web/locales/ja.yml +7 -0
- data/web/locales/zh-cn.yml +36 -11
- data/web/locales/zh-tw.yml +32 -7
- data/web/views/_nav.erb +1 -1
- data/web/views/busy.erb +7 -2
- data/web/views/dashboard.erb +1 -0
- data/web/views/metrics.erb +69 -0
- data/web/views/metrics_for_job.erb +87 -0
- data/web/views/queue.erb +5 -1
- metadata +29 -8
- 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
|
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
|
-
#
|
15
|
-
#
|
16
|
-
#
|
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
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
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
|
-
#
|
33
|
-
#
|
34
|
-
#
|
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
|
-
#
|
41
|
-
#
|
42
|
-
#
|
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
|
-
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
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
|
-
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
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
|
-
|
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
|
-
@
|
21
|
+
@strklass = cattr
|
22
22
|
end
|
23
23
|
|
24
24
|
def call(_, job, _, _)
|
25
|
-
attrs = @
|
26
|
-
if
|
27
|
-
job
|
28
|
-
|
29
|
-
|
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
|
-
@
|
41
|
+
@strklass = cattr
|
40
42
|
end
|
41
43
|
|
42
44
|
def call(_, job, _, &block)
|
43
45
|
if job.has_key?("cattr")
|
44
|
-
@
|
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
|
data/lib/sidekiq/monitor.rb
CHANGED
@@ -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
|
data/lib/sidekiq/paginator.rb
CHANGED
@@ -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
|
data/lib/sidekiq/processor.rb
CHANGED
@@ -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
|
-
|
156
|
-
|
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
|
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
|
data/lib/sidekiq/scheduled.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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]
|
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
|
-
|
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.
|
198
|
-
#
|
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
|
data/lib/sidekiq/version.rb
CHANGED
data/lib/sidekiq/web/action.rb
CHANGED
@@ -15,11 +15,11 @@ module Sidekiq
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def halt(res)
|
18
|
-
throw :halt, [res, {"
|
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, {"
|
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, {"
|
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, {"
|
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
|
-
"
|
320
|
-
"
|
321
|
-
"
|
322
|
-
"
|
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]]
|
data/lib/sidekiq/web/helpers.rb
CHANGED
@@ -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.
|
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" ? "↑" : "↓"
|
140
|
+
(params[:direction] == "asc") ? "↑" : "↓"
|
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, {"
|
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"],
|
data/lib/sidekiq/worker.rb
CHANGED
@@ -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
|
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
|