sidekiq 7.0.9 → 7.3.10
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 +263 -0
- data/README.md +5 -5
- data/bin/multi_queue_bench +271 -0
- data/bin/sidekiqload +41 -14
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +75 -0
- data/lib/generators/sidekiq/job_generator.rb +2 -0
- data/lib/sidekiq/api.rb +170 -52
- data/lib/sidekiq/capsule.rb +8 -3
- data/lib/sidekiq/cli.rb +6 -2
- data/lib/sidekiq/client.rb +58 -22
- data/lib/sidekiq/component.rb +23 -1
- data/lib/sidekiq/config.rb +52 -11
- data/lib/sidekiq/deploy.rb +4 -2
- data/lib/sidekiq/embedded.rb +2 -0
- data/lib/sidekiq/fetch.rb +2 -1
- data/lib/sidekiq/iterable_job.rb +55 -0
- data/lib/sidekiq/job/interrupt_handler.rb +24 -0
- data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
- data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
- data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
- data/lib/sidekiq/job/iterable.rb +294 -0
- data/lib/sidekiq/job.rb +15 -8
- data/lib/sidekiq/job_logger.rb +7 -6
- data/lib/sidekiq/job_retry.rb +30 -8
- data/lib/sidekiq/job_util.rb +6 -2
- data/lib/sidekiq/launcher.rb +8 -6
- data/lib/sidekiq/logger.rb +1 -1
- data/lib/sidekiq/metrics/query.rb +6 -1
- data/lib/sidekiq/metrics/shared.rb +16 -5
- data/lib/sidekiq/metrics/tracking.rb +20 -8
- data/lib/sidekiq/middleware/current_attributes.rb +88 -16
- data/lib/sidekiq/middleware/i18n.rb +2 -0
- data/lib/sidekiq/middleware/modules.rb +2 -0
- data/lib/sidekiq/monitor.rb +2 -1
- data/lib/sidekiq/paginator.rb +8 -2
- data/lib/sidekiq/processor.rb +44 -33
- data/lib/sidekiq/rails.rb +28 -7
- data/lib/sidekiq/redis_client_adapter.rb +30 -31
- data/lib/sidekiq/redis_connection.rb +49 -9
- data/lib/sidekiq/ring_buffer.rb +3 -0
- data/lib/sidekiq/scheduled.rb +3 -3
- data/lib/sidekiq/systemd.rb +2 -0
- data/lib/sidekiq/testing.rb +32 -13
- data/lib/sidekiq/transaction_aware_client.rb +20 -5
- data/lib/sidekiq/version.rb +5 -1
- data/lib/sidekiq/web/action.rb +29 -7
- data/lib/sidekiq/web/application.rb +64 -25
- data/lib/sidekiq/web/csrf_protection.rb +9 -6
- data/lib/sidekiq/web/helpers.rb +95 -35
- data/lib/sidekiq/web/router.rb +5 -2
- data/lib/sidekiq/web.rb +67 -3
- data/lib/sidekiq.rb +5 -3
- data/sidekiq.gemspec +5 -13
- data/web/assets/javascripts/application.js +27 -0
- data/web/assets/javascripts/dashboard-charts.js +40 -12
- data/web/assets/javascripts/dashboard.js +14 -10
- data/web/assets/javascripts/metrics.js +34 -0
- data/web/assets/stylesheets/application-rtl.css +10 -0
- data/web/assets/stylesheets/application.css +38 -3
- data/web/locales/en.yml +5 -1
- data/web/locales/fr.yml +13 -0
- data/web/locales/gd.yml +0 -1
- data/web/locales/it.yml +32 -1
- data/web/locales/ja.yml +0 -1
- data/web/locales/pt-br.yml +20 -1
- data/web/locales/tr.yml +100 -0
- data/web/locales/uk.yml +24 -1
- data/web/locales/zh-cn.yml +0 -1
- data/web/locales/zh-tw.yml +0 -1
- data/web/views/_footer.erb +12 -1
- data/web/views/_job_info.erb +1 -1
- data/web/views/_metrics_period_select.erb +1 -1
- data/web/views/_summary.erb +7 -7
- data/web/views/busy.erb +7 -7
- data/web/views/dashboard.erb +29 -36
- data/web/views/filtering.erb +6 -0
- data/web/views/layout.erb +6 -6
- data/web/views/metrics.erb +38 -30
- data/web/views/metrics_for_job.erb +30 -39
- data/web/views/morgue.erb +2 -2
- data/web/views/queue.erb +1 -1
- data/web/views/queues.erb +6 -2
- metadata +52 -21
data/lib/sidekiq/web/helpers.rb
CHANGED
|
@@ -6,14 +6,57 @@ require "yaml"
|
|
|
6
6
|
require "cgi"
|
|
7
7
|
|
|
8
8
|
module Sidekiq
|
|
9
|
-
#
|
|
9
|
+
# These methods are available to pages within the Web UI and UI extensions.
|
|
10
|
+
# They are not public APIs for applications to use.
|
|
10
11
|
module WebHelpers
|
|
12
|
+
def style_tag(location, **kwargs)
|
|
13
|
+
global = location.match?(/:\/\//)
|
|
14
|
+
location = root_path + location if !global && !location.start_with?(root_path)
|
|
15
|
+
attrs = {
|
|
16
|
+
type: "text/css",
|
|
17
|
+
media: "screen",
|
|
18
|
+
rel: "stylesheet",
|
|
19
|
+
nonce: csp_nonce,
|
|
20
|
+
href: location
|
|
21
|
+
}
|
|
22
|
+
html_tag(:link, attrs.merge(kwargs))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def script_tag(location, **kwargs)
|
|
26
|
+
global = location.match?(/:\/\//)
|
|
27
|
+
location = root_path + location if !global && !location.start_with?(root_path)
|
|
28
|
+
attrs = {
|
|
29
|
+
type: "text/javascript",
|
|
30
|
+
nonce: csp_nonce,
|
|
31
|
+
src: location
|
|
32
|
+
}
|
|
33
|
+
html_tag(:script, attrs.merge(kwargs)) {}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# NB: keys and values are not escaped; do not allow user input
|
|
37
|
+
# in the attributes
|
|
38
|
+
private def html_tag(tagname, attrs)
|
|
39
|
+
s = +"<#{tagname}"
|
|
40
|
+
attrs.each_pair do |k, v|
|
|
41
|
+
next unless v
|
|
42
|
+
s << " #{k}=\"#{v}\""
|
|
43
|
+
end
|
|
44
|
+
if block_given?
|
|
45
|
+
s << ">"
|
|
46
|
+
yield s
|
|
47
|
+
s << "</#{tagname}>"
|
|
48
|
+
else
|
|
49
|
+
s << " />"
|
|
50
|
+
end
|
|
51
|
+
s
|
|
52
|
+
end
|
|
53
|
+
|
|
11
54
|
def strings(lang)
|
|
12
|
-
|
|
55
|
+
@@strings ||= {}
|
|
13
56
|
|
|
14
57
|
# Allow sidekiq-web extensions to add locale paths
|
|
15
58
|
# so extensions can be localized
|
|
16
|
-
|
|
59
|
+
@@strings[lang] ||= settings.locales.each_with_object({}) do |path, global|
|
|
17
60
|
find_locale_files(lang).each do |file|
|
|
18
61
|
strs = YAML.safe_load(File.read(file))
|
|
19
62
|
global.merge!(strs[lang])
|
|
@@ -21,6 +64,10 @@ module Sidekiq
|
|
|
21
64
|
end
|
|
22
65
|
end
|
|
23
66
|
|
|
67
|
+
def to_json(x)
|
|
68
|
+
Sidekiq.dump_json(x)
|
|
69
|
+
end
|
|
70
|
+
|
|
24
71
|
def singularize(str, count)
|
|
25
72
|
if count == 1 && str.respond_to?(:singularize) # rails
|
|
26
73
|
str.singularize
|
|
@@ -30,27 +77,48 @@ module Sidekiq
|
|
|
30
77
|
end
|
|
31
78
|
|
|
32
79
|
def clear_caches
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
80
|
+
@@strings = nil
|
|
81
|
+
@@locale_files = nil
|
|
82
|
+
@@available_locales = nil
|
|
36
83
|
end
|
|
37
84
|
|
|
38
85
|
def locale_files
|
|
39
|
-
|
|
86
|
+
@@locale_files ||= settings.locales.flat_map { |path|
|
|
40
87
|
Dir["#{path}/*.yml"]
|
|
41
88
|
}
|
|
42
89
|
end
|
|
43
90
|
|
|
44
91
|
def available_locales
|
|
45
|
-
|
|
92
|
+
@@available_locales ||= Set.new(locale_files.map { |path| File.basename(path, ".yml") })
|
|
46
93
|
end
|
|
47
94
|
|
|
48
95
|
def find_locale_files(lang)
|
|
49
96
|
locale_files.select { |file| file =~ /\/#{lang}\.yml$/ }
|
|
50
97
|
end
|
|
51
98
|
|
|
52
|
-
|
|
53
|
-
|
|
99
|
+
def search(jobset, substr)
|
|
100
|
+
resultset = jobset.scan(substr).to_a
|
|
101
|
+
@current_page = 1
|
|
102
|
+
@count = @total_size = resultset.size
|
|
103
|
+
resultset
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def filtering(which)
|
|
107
|
+
erb(:filtering, locals: {which: which})
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def filter_link(jid, within = "retries")
|
|
111
|
+
if within.nil?
|
|
112
|
+
::Rack::Utils.escape_html(jid)
|
|
113
|
+
else
|
|
114
|
+
"<a href='#{root_path}#{within}?substr=#{jid}'>#{::Rack::Utils.escape_html(jid)}</a>"
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def display_tags(job, within = "retries")
|
|
119
|
+
job.tags.map { |tag|
|
|
120
|
+
"<span class='label label-info jobtag'>#{filter_link(tag, within)}</span>"
|
|
121
|
+
}.join(" ")
|
|
54
122
|
end
|
|
55
123
|
|
|
56
124
|
# This view helper provide ability display you html code in
|
|
@@ -96,7 +164,10 @@ module Sidekiq
|
|
|
96
164
|
#
|
|
97
165
|
# Inspiration taken from https://github.com/iain/http_accept_language/blob/master/lib/http_accept_language/parser.rb
|
|
98
166
|
def locale
|
|
99
|
-
|
|
167
|
+
# session[:locale] is set via the locale selector from the footer
|
|
168
|
+
@locale ||= if (l = session&.fetch(:locale, nil)) && available_locales.include?(l)
|
|
169
|
+
l
|
|
170
|
+
else
|
|
100
171
|
matched_locale = user_preferred_languages.map { |preferred|
|
|
101
172
|
preferred_language = preferred.split("-", 2).first
|
|
102
173
|
|
|
@@ -111,16 +182,10 @@ module Sidekiq
|
|
|
111
182
|
end
|
|
112
183
|
end
|
|
113
184
|
|
|
114
|
-
# within is used by Sidekiq Pro
|
|
115
|
-
def display_tags(job, within = nil)
|
|
116
|
-
job.tags.map { |tag|
|
|
117
|
-
"<span class='label label-info jobtag'>#{::Rack::Utils.escape_html(tag)}</span>"
|
|
118
|
-
}.join(" ")
|
|
119
|
-
end
|
|
120
|
-
|
|
121
185
|
# sidekiq/sidekiq#3243
|
|
122
186
|
def unfiltered?
|
|
123
|
-
|
|
187
|
+
s = url_params("substr")
|
|
188
|
+
yield unless s && s.size > 0
|
|
124
189
|
end
|
|
125
190
|
|
|
126
191
|
def get_locale
|
|
@@ -245,6 +310,10 @@ module Sidekiq
|
|
|
245
310
|
"<input type='hidden' name='authenticity_token' value='#{env[:csrf_token]}'/>"
|
|
246
311
|
end
|
|
247
312
|
|
|
313
|
+
def csp_nonce
|
|
314
|
+
env[:csp_nonce]
|
|
315
|
+
end
|
|
316
|
+
|
|
248
317
|
def to_display(arg)
|
|
249
318
|
arg.inspect
|
|
250
319
|
rescue
|
|
@@ -278,27 +347,17 @@ module Sidekiq
|
|
|
278
347
|
elsif rss_kb < 10_000_000
|
|
279
348
|
"#{number_with_delimiter((rss_kb / 1024.0).to_i)} MB"
|
|
280
349
|
else
|
|
281
|
-
"#{number_with_delimiter(
|
|
350
|
+
"#{number_with_delimiter(rss_kb / (1024.0 * 1024.0), precision: 1)} GB"
|
|
282
351
|
end
|
|
283
352
|
end
|
|
284
353
|
|
|
285
|
-
def number_with_delimiter(number)
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
begin
|
|
289
|
-
Float(number)
|
|
290
|
-
rescue ArgumentError, TypeError
|
|
291
|
-
return number
|
|
292
|
-
end
|
|
293
|
-
|
|
294
|
-
options = {delimiter: ",", separator: "."}
|
|
295
|
-
parts = number.to_s.to_str.split(".")
|
|
296
|
-
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
|
|
297
|
-
parts.join(options[:separator])
|
|
354
|
+
def number_with_delimiter(number, options = {})
|
|
355
|
+
precision = options[:precision] || 0
|
|
356
|
+
%(<span data-nwp="#{precision}">#{number.round(precision)}</span>)
|
|
298
357
|
end
|
|
299
358
|
|
|
300
359
|
def h(text)
|
|
301
|
-
::Rack::Utils.escape_html(text)
|
|
360
|
+
::Rack::Utils.escape_html(text.to_s)
|
|
302
361
|
rescue ArgumentError => e
|
|
303
362
|
raise unless e.message.eql?("invalid byte sequence in UTF-8")
|
|
304
363
|
text.encode!("UTF-16", "UTF-8", invalid: :replace, replace: "").encode!("UTF-8", "UTF-16")
|
|
@@ -332,7 +391,8 @@ module Sidekiq
|
|
|
332
391
|
end
|
|
333
392
|
|
|
334
393
|
def pollable?
|
|
335
|
-
|
|
394
|
+
# there's no point to refreshing the metrics pages every N seconds
|
|
395
|
+
!(current_path == "" || current_path.index("metrics"))
|
|
336
396
|
end
|
|
337
397
|
|
|
338
398
|
def retry_or_delete_or_kill(job, params)
|
data/lib/sidekiq/web/router.rb
CHANGED
|
@@ -39,10 +39,13 @@ module Sidekiq
|
|
|
39
39
|
route(DELETE, path, &block)
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
def route(
|
|
42
|
+
def route(*methods, path, &block)
|
|
43
43
|
@routes ||= {GET => [], POST => [], PUT => [], PATCH => [], DELETE => [], HEAD => []}
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
methods.each do |method|
|
|
46
|
+
method = method.to_s.upcase
|
|
47
|
+
@routes[method] << WebRoute.new(method, path, block)
|
|
48
|
+
end
|
|
46
49
|
end
|
|
47
50
|
|
|
48
51
|
def match(env)
|
data/lib/sidekiq/web.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "erb"
|
|
4
|
+
require "securerandom"
|
|
4
5
|
|
|
5
6
|
require "sidekiq"
|
|
6
7
|
require "sidekiq/api"
|
|
@@ -34,7 +35,26 @@ module Sidekiq
|
|
|
34
35
|
"Metrics" => "metrics"
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
if Gem::Version.new(Rack::RELEASE) < Gem::Version.new("3")
|
|
39
|
+
CONTENT_LANGUAGE = "Content-Language"
|
|
40
|
+
CONTENT_SECURITY_POLICY = "Content-Security-Policy"
|
|
41
|
+
LOCATION = "Location"
|
|
42
|
+
X_CASCADE = "X-Cascade"
|
|
43
|
+
X_CONTENT_TYPE_OPTIONS = "X-Content-Type-Options"
|
|
44
|
+
else
|
|
45
|
+
CONTENT_LANGUAGE = "content-language"
|
|
46
|
+
CONTENT_SECURITY_POLICY = "content-security-policy"
|
|
47
|
+
LOCATION = "location"
|
|
48
|
+
X_CASCADE = "x-cascade"
|
|
49
|
+
X_CONTENT_TYPE_OPTIONS = "x-content-type-options"
|
|
50
|
+
end
|
|
51
|
+
|
|
37
52
|
class << self
|
|
53
|
+
# Forward compatibility with 8.0
|
|
54
|
+
def configure
|
|
55
|
+
yield self
|
|
56
|
+
end
|
|
57
|
+
|
|
38
58
|
def settings
|
|
39
59
|
self
|
|
40
60
|
end
|
|
@@ -102,6 +122,7 @@ module Sidekiq
|
|
|
102
122
|
end
|
|
103
123
|
|
|
104
124
|
def call(env)
|
|
125
|
+
env[:csp_nonce] = SecureRandom.base64(16)
|
|
105
126
|
app.call(env)
|
|
106
127
|
end
|
|
107
128
|
|
|
@@ -126,7 +147,50 @@ module Sidekiq
|
|
|
126
147
|
send(:"#{attribute}=", value)
|
|
127
148
|
end
|
|
128
149
|
|
|
129
|
-
|
|
150
|
+
# Register a class as a Sidekiq Web UI extension. The class should
|
|
151
|
+
# provide one or more tabs which map to an index route. Options:
|
|
152
|
+
#
|
|
153
|
+
# @param extension [Class] Class which contains the HTTP actions, required
|
|
154
|
+
# @param name [String] the name of the extension, used to namespace assets
|
|
155
|
+
# @param tab [String | Array] labels(s) of the UI tabs
|
|
156
|
+
# @param index [String | Array] index route(s) for each tab
|
|
157
|
+
# @param root_dir [String] directory location to find assets, locales and views, typically `web/` within the gemfile
|
|
158
|
+
# @param asset_paths [Array] one or more directories under {root}/assets/{name} to be publicly served, e.g. ["js", "css", "img"]
|
|
159
|
+
# @param cache_for [Integer] amount of time to cache assets, default one day
|
|
160
|
+
#
|
|
161
|
+
# TODO name, tab and index will be mandatory in 8.0
|
|
162
|
+
#
|
|
163
|
+
# Web extensions will have a root `web/` directory with `locales/`, `assets/`
|
|
164
|
+
# and `views/` subdirectories.
|
|
165
|
+
def self.register(extension, name: nil, tab: nil, index: nil, root_dir: nil, cache_for: 86400, asset_paths: nil)
|
|
166
|
+
tab = Array(tab)
|
|
167
|
+
index = Array(index)
|
|
168
|
+
tab.zip(index).each do |tab, index|
|
|
169
|
+
tabs[tab] = index
|
|
170
|
+
end
|
|
171
|
+
if root_dir
|
|
172
|
+
locdir = File.join(root_dir, "locales")
|
|
173
|
+
locales << locdir if File.directory?(locdir)
|
|
174
|
+
|
|
175
|
+
if asset_paths && name
|
|
176
|
+
# if you have {root}/assets/{name}/js/scripts.js
|
|
177
|
+
# and {root}/assets/{name}/css/styles.css
|
|
178
|
+
# you would pass in:
|
|
179
|
+
# asset_paths: ["js", "css"]
|
|
180
|
+
# See script_tag and style_tag in web/helpers.rb
|
|
181
|
+
assdir = File.join(root_dir, "assets")
|
|
182
|
+
assurls = Array(asset_paths).map { |x| "/#{name}/#{x}" }
|
|
183
|
+
assetprops = {
|
|
184
|
+
urls: assurls,
|
|
185
|
+
root: assdir,
|
|
186
|
+
cascade: true
|
|
187
|
+
}
|
|
188
|
+
assetprops[:header_rules] = [[:all, {Rack::CACHE_CONTROL => "private, max-age=#{cache_for.to_i}"}]] if cache_for
|
|
189
|
+
middlewares << [[Rack::Static, assetprops], nil]
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
yield self if block_given?
|
|
130
194
|
extension.registered(WebApplication)
|
|
131
195
|
end
|
|
132
196
|
|
|
@@ -137,7 +201,7 @@ module Sidekiq
|
|
|
137
201
|
m = middlewares
|
|
138
202
|
|
|
139
203
|
rules = []
|
|
140
|
-
rules = [[:all, {
|
|
204
|
+
rules = [[:all, {Rack::CACHE_CONTROL => "private, max-age=86400"}]] unless ENV["SIDEKIQ_WEB_TESTING"]
|
|
141
205
|
|
|
142
206
|
::Rack::Builder.new do
|
|
143
207
|
use Rack::Static, urls: ["/stylesheets", "/images", "/javascripts"],
|
|
@@ -154,7 +218,7 @@ module Sidekiq
|
|
|
154
218
|
Sidekiq::WebApplication.helpers WebHelpers
|
|
155
219
|
Sidekiq::WebApplication.helpers Sidekiq::Paginator
|
|
156
220
|
|
|
157
|
-
Sidekiq::WebAction.class_eval <<-RUBY,
|
|
221
|
+
Sidekiq::WebAction.class_eval <<-RUBY, Web::LAYOUT, -1 # standard:disable Style/EvalWithLocation
|
|
158
222
|
def _render
|
|
159
223
|
#{ERB.new(File.read(Web::LAYOUT)).src}
|
|
160
224
|
end
|
data/lib/sidekiq.rb
CHANGED
|
@@ -32,6 +32,7 @@ require "sidekiq/logger"
|
|
|
32
32
|
require "sidekiq/client"
|
|
33
33
|
require "sidekiq/transaction_aware_client"
|
|
34
34
|
require "sidekiq/job"
|
|
35
|
+
require "sidekiq/iterable_job"
|
|
35
36
|
require "sidekiq/worker_compatibility_alias"
|
|
36
37
|
require "sidekiq/redis_client_adapter"
|
|
37
38
|
|
|
@@ -101,18 +102,19 @@ module Sidekiq
|
|
|
101
102
|
def self.freeze!
|
|
102
103
|
@frozen = true
|
|
103
104
|
@config_blocks = nil
|
|
105
|
+
default_configuration.freeze!
|
|
104
106
|
end
|
|
105
107
|
|
|
106
108
|
# Creates a Sidekiq::Config instance that is more tuned for embedding
|
|
107
109
|
# within an arbitrary Ruby process. Notably it reduces concurrency by
|
|
108
110
|
# default so there is less contention for CPU time with other threads.
|
|
109
111
|
#
|
|
110
|
-
#
|
|
112
|
+
# instance = Sidekiq.configure_embed do |config|
|
|
111
113
|
# config.queues = %w[critical default low]
|
|
112
114
|
# end
|
|
113
|
-
#
|
|
115
|
+
# instance.run
|
|
114
116
|
# sleep 10
|
|
115
|
-
#
|
|
117
|
+
# instance.stop
|
|
116
118
|
#
|
|
117
119
|
# NB: it is really easy to overload a Ruby process with threads due to the GIL.
|
|
118
120
|
# I do not recommend setting concurrency higher than 2-3.
|
data/sidekiq.gemspec
CHANGED
|
@@ -23,17 +23,9 @@ Gem::Specification.new do |gem|
|
|
|
23
23
|
"rubygems_mfa_required" => "true"
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
gem.add_dependency "redis-client", ">= 0.
|
|
27
|
-
gem.add_dependency "connection_pool", ">= 2.3.0"
|
|
28
|
-
gem.add_dependency "rack", ">= 2.2.4"
|
|
29
|
-
gem.add_dependency "
|
|
30
|
-
gem.
|
|
31
|
-
|
|
32
|
-
Welcome to Sidekiq 7.0!
|
|
33
|
-
|
|
34
|
-
1. Use `gem 'sidekiq', '<7'` in your Gemfile if you don't want this new version.
|
|
35
|
-
2. Read the release notes at https://github.com/sidekiq/sidekiq/blob/main/docs/7.0-Upgrade.md
|
|
36
|
-
3. If you have problems, search for open/closed issues at https://github.com/sidekiq/sidekiq/issues/
|
|
37
|
-
|
|
38
|
-
EOM
|
|
26
|
+
gem.add_dependency "redis-client", ">= 0.23.0", "<1"
|
|
27
|
+
gem.add_dependency "connection_pool", ">= 2.3.0", "<3"
|
|
28
|
+
gem.add_dependency "rack", ">= 2.2.4", "<3.3"
|
|
29
|
+
gem.add_dependency "logger"
|
|
30
|
+
gem.add_dependency "base64"
|
|
39
31
|
end
|
|
@@ -33,6 +33,8 @@ function addListeners() {
|
|
|
33
33
|
|
|
34
34
|
addShiftClickListeners()
|
|
35
35
|
updateFuzzyTimes();
|
|
36
|
+
updateNumbers();
|
|
37
|
+
updateProgressBars();
|
|
36
38
|
setLivePollFromUrl();
|
|
37
39
|
|
|
38
40
|
var buttons = document.querySelectorAll(".live-poll");
|
|
@@ -46,6 +48,8 @@ function addListeners() {
|
|
|
46
48
|
scheduleLivePoll();
|
|
47
49
|
}
|
|
48
50
|
}
|
|
51
|
+
|
|
52
|
+
document.getElementById("locale-select").addEventListener("change", updateLocale);
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
function addPollingListeners(_event) {
|
|
@@ -102,6 +106,20 @@ function updateFuzzyTimes() {
|
|
|
102
106
|
t.cancel();
|
|
103
107
|
}
|
|
104
108
|
|
|
109
|
+
function updateNumbers() {
|
|
110
|
+
document.querySelectorAll("[data-nwp]").forEach(node => {
|
|
111
|
+
let number = parseFloat(node.textContent);
|
|
112
|
+
let precision = parseInt(node.dataset["nwp"] || 0);
|
|
113
|
+
if (typeof number === "number") {
|
|
114
|
+
let formatted = number.toLocaleString(undefined, {
|
|
115
|
+
minimumFractionDigits: precision,
|
|
116
|
+
maximumFractionDigits: precision,
|
|
117
|
+
});
|
|
118
|
+
node.textContent = formatted;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
105
123
|
function setLivePollFromUrl() {
|
|
106
124
|
var url_params = new URL(window.location.href).searchParams
|
|
107
125
|
|
|
@@ -140,6 +158,7 @@ function checkResponse(resp) {
|
|
|
140
158
|
|
|
141
159
|
function scheduleLivePoll() {
|
|
142
160
|
let ti = parseInt(localStorage.sidekiqTimeInterval) || 5000;
|
|
161
|
+
if (ti < 2000) { ti = 2000 }
|
|
143
162
|
livePollTimer = setTimeout(livePollCallback, ti);
|
|
144
163
|
}
|
|
145
164
|
|
|
@@ -159,3 +178,11 @@ function replacePage(text) {
|
|
|
159
178
|
function showError(error) {
|
|
160
179
|
console.error(error)
|
|
161
180
|
}
|
|
181
|
+
|
|
182
|
+
function updateLocale(event) {
|
|
183
|
+
event.target.form.submit();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function updateProgressBars() {
|
|
187
|
+
document.querySelectorAll('.progress-bar').forEach(bar => { bar.style.width = bar.dataset.width + "%"})
|
|
188
|
+
}
|
|
@@ -57,7 +57,9 @@ class DashboardChart extends BaseChart {
|
|
|
57
57
|
class RealtimeChart extends DashboardChart {
|
|
58
58
|
constructor(el, options) {
|
|
59
59
|
super(el, options);
|
|
60
|
-
|
|
60
|
+
let d = parseInt(localStorage.sidekiqTimeInterval) || 5000;
|
|
61
|
+
if (d < 2000) { d = 2000; }
|
|
62
|
+
this.delay = d
|
|
61
63
|
this.startPolling();
|
|
62
64
|
document.addEventListener("interval:update", this.handleUpdate.bind(this));
|
|
63
65
|
}
|
|
@@ -81,9 +83,12 @@ class RealtimeChart extends DashboardChart {
|
|
|
81
83
|
this.chart.data.datasets[1].data.push(failed);
|
|
82
84
|
this.chart.update();
|
|
83
85
|
|
|
86
|
+
updateScreenReaderDashboardValues(processed, failed);
|
|
87
|
+
|
|
84
88
|
updateStatsSummary(this.stats.sidekiq);
|
|
85
89
|
updateRedisStats(this.stats.redis);
|
|
86
90
|
updateFooterUTCTime(this.stats.server_utc_time);
|
|
91
|
+
updateNumbers();
|
|
87
92
|
pulseBeacon();
|
|
88
93
|
|
|
89
94
|
this.stats = stats;
|
|
@@ -105,17 +110,27 @@ class RealtimeChart extends DashboardChart {
|
|
|
105
110
|
}
|
|
106
111
|
|
|
107
112
|
renderLegend(dp) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
113
|
+
const entry1 = this.legendEntry(dp[0]);
|
|
114
|
+
const entry2 = this.legendEntry(dp[1]);
|
|
115
|
+
const time = document.createElement("span");
|
|
116
|
+
time.classList.add("time");
|
|
117
|
+
time.innerText = dp[0].label;
|
|
118
|
+
|
|
119
|
+
this.legend.replaceChildren(entry1, entry2, time)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
legendEntry(dp) {
|
|
123
|
+
const wrapper = document.createElement("span");
|
|
124
|
+
|
|
125
|
+
const swatch = document.createElement("span");
|
|
126
|
+
swatch.classList.add("swatch");
|
|
127
|
+
swatch.style.backgroundColor = dp.dataset.borderColor;
|
|
128
|
+
wrapper.appendChild(swatch)
|
|
129
|
+
|
|
130
|
+
const label = document.createElement("span");
|
|
131
|
+
label.innerText = `${dp.dataset.label}: ${dp.formattedValue}`;
|
|
132
|
+
wrapper.appendChild(label)
|
|
133
|
+
return wrapper;
|
|
119
134
|
}
|
|
120
135
|
|
|
121
136
|
renderCursor(dp) {
|
|
@@ -164,3 +179,16 @@ class RealtimeChart extends DashboardChart {
|
|
|
164
179
|
};
|
|
165
180
|
}
|
|
166
181
|
}
|
|
182
|
+
|
|
183
|
+
var rc = document.getElementById("realtime-chart")
|
|
184
|
+
if (rc != null) {
|
|
185
|
+
var rtc = new RealtimeChart(rc, JSON.parse(rc.textContent))
|
|
186
|
+
rtc.registerLegend(document.getElementById("realtime-legend"))
|
|
187
|
+
window.realtimeChart = rtc
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
var hc = document.getElementById("history-chart")
|
|
191
|
+
if (hc != null) {
|
|
192
|
+
var htc = new DashboardChart(hc, JSON.parse(hc.textContent))
|
|
193
|
+
window.historyChart = htc
|
|
194
|
+
}
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
Sidekiq = {};
|
|
2
2
|
|
|
3
|
-
var nf = new Intl.NumberFormat();
|
|
4
|
-
|
|
5
3
|
var updateStatsSummary = function(data) {
|
|
6
|
-
document.getElementById("txtProcessed").innerText =
|
|
7
|
-
document.getElementById("txtFailed").innerText =
|
|
8
|
-
document.getElementById("txtBusy").innerText =
|
|
9
|
-
document.getElementById("txtScheduled").innerText =
|
|
10
|
-
document.getElementById("txtRetries").innerText =
|
|
11
|
-
document.getElementById("txtEnqueued").innerText =
|
|
12
|
-
document.getElementById("txtDead").innerText =
|
|
4
|
+
document.getElementById("txtProcessed").innerText = data.processed;
|
|
5
|
+
document.getElementById("txtFailed").innerText = data.failed;
|
|
6
|
+
document.getElementById("txtBusy").innerText = data.busy;
|
|
7
|
+
document.getElementById("txtScheduled").innerText = data.scheduled;
|
|
8
|
+
document.getElementById("txtRetries").innerText = data.retries;
|
|
9
|
+
document.getElementById("txtEnqueued").innerText = data.enqueued;
|
|
10
|
+
document.getElementById("txtDead").innerText = data.dead;
|
|
13
11
|
}
|
|
14
12
|
|
|
15
13
|
var updateRedisStats = function(data) {
|
|
@@ -30,7 +28,7 @@ var pulseBeacon = function() {
|
|
|
30
28
|
}
|
|
31
29
|
|
|
32
30
|
var setSliderLabel = function(val) {
|
|
33
|
-
document.getElementById('sldr-text').innerText = Math.round(parseFloat(val) / 1000) + '
|
|
31
|
+
document.getElementById('sldr-text').innerText = Math.round(parseFloat(val) / 1000) + ' s';
|
|
34
32
|
}
|
|
35
33
|
|
|
36
34
|
var ready = (callback) => {
|
|
@@ -38,6 +36,12 @@ var ready = (callback) => {
|
|
|
38
36
|
else document.addEventListener("DOMContentLoaded", callback);
|
|
39
37
|
}
|
|
40
38
|
|
|
39
|
+
var updateScreenReaderDashboardValues = function(processed, failed) {
|
|
40
|
+
let lastDashboardUpdateSpan = document.getElementById("sr-last-dashboard-update");
|
|
41
|
+
var updateText = document.getElementById("sr-last-dashboard-update-template").innerText;
|
|
42
|
+
lastDashboardUpdateSpan.innerText = updateText.replace("PROCESSED_COUNT", processed).replace("FAILED_COUNT", failed);
|
|
43
|
+
}
|
|
44
|
+
|
|
41
45
|
ready(() => {
|
|
42
46
|
var sldr = document.getElementById('sldr');
|
|
43
47
|
if (typeof localStorage.sidekiqTimeInterval !== 'undefined') {
|
|
@@ -262,3 +262,37 @@ class HistBubbleChart extends BaseChart {
|
|
|
262
262
|
};
|
|
263
263
|
}
|
|
264
264
|
}
|
|
265
|
+
|
|
266
|
+
var ch = document.getElementById("job-metrics-overview-chart");
|
|
267
|
+
if (ch != null) {
|
|
268
|
+
var jm = new JobMetricsOverviewChart(ch, JSON.parse(ch.textContent));
|
|
269
|
+
document.querySelectorAll(".metrics-swatch-wrapper > input[type=checkbox]").forEach((imp) => {
|
|
270
|
+
jm.registerSwatch(imp.id)
|
|
271
|
+
});
|
|
272
|
+
window.jobMetricsChart = jm;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
var htc = document.getElementById("hist-totals-chart");
|
|
276
|
+
if (htc != null) {
|
|
277
|
+
var tc = new HistTotalsChart(htc, JSON.parse(htc.textContent));
|
|
278
|
+
window.histTotalsChart = tc
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
var hbc = document.getElementById("hist-bubble-chart");
|
|
282
|
+
if (hbc != null) {
|
|
283
|
+
var bc = new HistBubbleChart(hbc, JSON.parse(hbc.textContent));
|
|
284
|
+
window.histBubbleChart = bc
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
var form = document.getElementById("metrics-form")
|
|
288
|
+
document.querySelectorAll("#period-selector").forEach(node => {
|
|
289
|
+
node.addEventListener("input", debounce(() => form.submit()))
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
function debounce(func, timeout = 300) {
|
|
293
|
+
let timer;
|
|
294
|
+
return (...args) => {
|
|
295
|
+
clearTimeout(timer);
|
|
296
|
+
timer = setTimeout(() => { func.apply(this, args); }, timeout);
|
|
297
|
+
};
|
|
298
|
+
}
|