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