sidekiq 7.1.6 → 7.2.4
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 +68 -0
- data/README.md +2 -2
- data/bin/multi_queue_bench +271 -0
- data/lib/sidekiq/api.rb +77 -11
- data/lib/sidekiq/cli.rb +3 -1
- data/lib/sidekiq/config.rb +4 -4
- data/lib/sidekiq/deploy.rb +2 -2
- data/lib/sidekiq/job.rb +1 -1
- data/lib/sidekiq/job_retry.rb +3 -3
- data/lib/sidekiq/launcher.rb +6 -4
- data/lib/sidekiq/logger.rb +1 -1
- data/lib/sidekiq/metrics/query.rb +4 -1
- data/lib/sidekiq/metrics/tracking.rb +7 -3
- data/lib/sidekiq/middleware/current_attributes.rb +1 -1
- data/lib/sidekiq/paginator.rb +2 -2
- data/lib/sidekiq/processor.rb +1 -1
- data/lib/sidekiq/rails.rb +7 -3
- data/lib/sidekiq/redis_client_adapter.rb +16 -0
- data/lib/sidekiq/redis_connection.rb +3 -6
- data/lib/sidekiq/scheduled.rb +2 -2
- data/lib/sidekiq/testing.rb +9 -3
- data/lib/sidekiq/transaction_aware_client.rb +7 -0
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/action.rb +5 -0
- data/lib/sidekiq/web/application.rb +32 -4
- data/lib/sidekiq/web/csrf_protection.rb +8 -5
- data/lib/sidekiq/web/helpers.rb +14 -15
- data/sidekiq.gemspec +1 -1
- data/web/assets/javascripts/application.js +21 -0
- data/web/assets/javascripts/dashboard-charts.js +14 -0
- data/web/assets/javascripts/dashboard.js +7 -9
- data/web/assets/javascripts/metrics.js +34 -0
- data/web/assets/stylesheets/application-rtl.css +10 -0
- data/web/assets/stylesheets/application.css +22 -0
- data/web/views/_footer.erb +13 -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 +23 -33
- data/web/views/filtering.erb +4 -4
- data/web/views/metrics.erb +36 -27
- data/web/views/metrics_for_job.erb +26 -35
- data/web/views/queues.erb +6 -2
- metadata +5 -4
@@ -20,7 +20,8 @@ module Sidekiq
|
|
20
20
|
end
|
21
21
|
|
22
22
|
# Get metric data for all jobs from the last hour
|
23
|
-
|
23
|
+
# +class_filter+: return only results for classes matching filter
|
24
|
+
def top_jobs(class_filter: nil, minutes: 60)
|
24
25
|
result = Result.new
|
25
26
|
|
26
27
|
time = @time
|
@@ -39,6 +40,7 @@ module Sidekiq
|
|
39
40
|
redis_results.each do |hash|
|
40
41
|
hash.each do |k, v|
|
41
42
|
kls, metric = k.split("|")
|
43
|
+
next if class_filter && !class_filter.match?(kls)
|
42
44
|
result.job_results[kls].add_metric metric, time, v.to_i
|
43
45
|
end
|
44
46
|
time -= 60
|
@@ -117,6 +119,7 @@ module Sidekiq
|
|
117
119
|
|
118
120
|
def total_avg(metric = "ms")
|
119
121
|
completed = totals["p"] - totals["f"]
|
122
|
+
return 0 if completed.zero?
|
120
123
|
totals[metric].to_f / completed
|
121
124
|
end
|
122
125
|
|
@@ -103,12 +103,16 @@ module Sidekiq
|
|
103
103
|
def reset
|
104
104
|
@lock.synchronize {
|
105
105
|
array = [@totals, @jobs, @grams]
|
106
|
-
|
107
|
-
@jobs = Hash.new(0)
|
108
|
-
@grams = Hash.new { |hash, key| hash[key] = Histogram.new(key) }
|
106
|
+
reset_instance_variables
|
109
107
|
array
|
110
108
|
}
|
111
109
|
end
|
110
|
+
|
111
|
+
def reset_instance_variables
|
112
|
+
@totals = Hash.new(0)
|
113
|
+
@jobs = Hash.new(0)
|
114
|
+
@grams = Hash.new { |hash, key| hash[key] = Histogram.new(key) }
|
115
|
+
end
|
112
116
|
end
|
113
117
|
|
114
118
|
class Middleware
|
data/lib/sidekiq/paginator.rb
CHANGED
@@ -19,9 +19,9 @@ module Sidekiq
|
|
19
19
|
total_size, items = conn.multi { |transaction|
|
20
20
|
transaction.zcard(key)
|
21
21
|
if rev
|
22
|
-
transaction.zrange(key, starting, ending, "REV", withscores
|
22
|
+
transaction.zrange(key, starting, ending, "REV", "withscores")
|
23
23
|
else
|
24
|
-
transaction.zrange(key, starting, ending, withscores
|
24
|
+
transaction.zrange(key, starting, ending, "withscores")
|
25
25
|
end
|
26
26
|
}
|
27
27
|
[current_page, total_size, items]
|
data/lib/sidekiq/processor.rb
CHANGED
@@ -187,7 +187,7 @@ module Sidekiq
|
|
187
187
|
# we didn't properly finish it.
|
188
188
|
rescue Sidekiq::JobRetry::Handled => h
|
189
189
|
# this is the common case: job raised error and Sidekiq::JobRetry::Handled
|
190
|
-
# signals that we created a retry successfully. We can
|
190
|
+
# signals that we created a retry successfully. We can acknowledge the job.
|
191
191
|
ack = true
|
192
192
|
e = h.cause || h
|
193
193
|
handle_exception(e, {context: "Job raised exception", job: job_hash})
|
data/lib/sidekiq/rails.rb
CHANGED
@@ -20,6 +20,10 @@ module Sidekiq
|
|
20
20
|
def inspect
|
21
21
|
"#<Sidekiq::Rails::Reloader @app=#{@app.class.name}>"
|
22
22
|
end
|
23
|
+
|
24
|
+
def to_hash
|
25
|
+
{app: @app.class.name}
|
26
|
+
end
|
23
27
|
end
|
24
28
|
|
25
29
|
# By including the Options module, we allow AJs to directly control sidekiq features
|
@@ -56,10 +60,10 @@ module Sidekiq
|
|
56
60
|
# This is the integration code necessary so that if a job uses `Rails.logger.info "Hello"`,
|
57
61
|
# it will appear in the Sidekiq console with all of the job context.
|
58
62
|
unless ::Rails.logger == config.logger || ::ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout)
|
59
|
-
if ::Rails
|
60
|
-
::Rails.logger.extend(::ActiveSupport::Logger.broadcast(config.logger))
|
61
|
-
else
|
63
|
+
if ::Rails.logger.respond_to?(:broadcast_to)
|
62
64
|
::Rails.logger.broadcast_to(config.logger)
|
65
|
+
else
|
66
|
+
::Rails.logger.extend(::ActiveSupport::Logger.broadcast(config.logger))
|
63
67
|
end
|
64
68
|
end
|
65
69
|
end
|
@@ -21,6 +21,22 @@ module Sidekiq
|
|
21
21
|
@client.call("EVALSHA", sha, keys.size, *keys, *argv)
|
22
22
|
end
|
23
23
|
|
24
|
+
# this is the set of Redis commands used by Sidekiq. Not guaranteed
|
25
|
+
# to be comprehensive, we use this as a performance enhancement to
|
26
|
+
# avoid calling method_missing on most commands
|
27
|
+
USED_COMMANDS = %w[bitfield bitfield_ro del exists expire flushdb
|
28
|
+
get hdel hget hgetall hincrby hlen hmget hset hsetnx incr incrby
|
29
|
+
lindex llen lmove lpop lpush lrange lrem mget mset ping pttl
|
30
|
+
publish rpop rpush sadd scard script set sismember smembers
|
31
|
+
srem ttl type unlink zadd zcard zincrby zrange zrem
|
32
|
+
zremrangebyrank zremrangebyscore]
|
33
|
+
|
34
|
+
USED_COMMANDS.each do |name|
|
35
|
+
define_method(name) do |*args, **kwargs|
|
36
|
+
@client.call(name, *args, **kwargs)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
24
40
|
private
|
25
41
|
|
26
42
|
# this allows us to use methods like `conn.hmset(...)` instead of having to use
|
@@ -39,9 +39,8 @@ module Sidekiq
|
|
39
39
|
uri.password = redacted
|
40
40
|
scrubbed_options[:url] = uri.to_s
|
41
41
|
end
|
42
|
-
if scrubbed_options[:password]
|
43
|
-
|
44
|
-
end
|
42
|
+
scrubbed_options[:password] = redacted if scrubbed_options[:password]
|
43
|
+
scrubbed_options[:sentinel_password] = redacted if scrubbed_options[:sentinel_password]
|
45
44
|
scrubbed_options[:sentinels]&.each do |sentinel|
|
46
45
|
sentinel[:password] = redacted if sentinel[:password]
|
47
46
|
end
|
@@ -67,9 +66,7 @@ module Sidekiq
|
|
67
66
|
EOM
|
68
67
|
end
|
69
68
|
|
70
|
-
ENV[
|
71
|
-
p || "REDIS_URL"
|
72
|
-
]
|
69
|
+
ENV[p.to_s] || ENV["REDIS_URL"]
|
73
70
|
end
|
74
71
|
end
|
75
72
|
end
|
data/lib/sidekiq/scheduled.rb
CHANGED
@@ -144,7 +144,7 @@ module Sidekiq
|
|
144
144
|
# In the example above, each process should schedule every 10 seconds on average. We special
|
145
145
|
# case smaller clusters to add 50% so they would sleep somewhere between 5 and 15 seconds.
|
146
146
|
# As we run more processes, the scheduling interval average will approach an even spread
|
147
|
-
# between 0 and poll interval so we don't need this
|
147
|
+
# between 0 and poll interval so we don't need this artificial boost.
|
148
148
|
#
|
149
149
|
count = process_count
|
150
150
|
interval = poll_interval_average(count)
|
@@ -193,7 +193,7 @@ module Sidekiq
|
|
193
193
|
# should never depend on sidekiq/api.
|
194
194
|
def cleanup
|
195
195
|
# dont run cleanup more than once per minute
|
196
|
-
return 0 unless redis { |conn| conn.set("process_cleanup", "1",
|
196
|
+
return 0 unless redis { |conn| conn.set("process_cleanup", "1", "NX", "EX", "60") }
|
197
197
|
|
198
198
|
count = 0
|
199
199
|
redis do |conn|
|
data/lib/sidekiq/testing.rb
CHANGED
@@ -5,6 +5,7 @@ require "sidekiq"
|
|
5
5
|
|
6
6
|
module Sidekiq
|
7
7
|
class Testing
|
8
|
+
class TestModeAlreadySetError < RuntimeError; end
|
8
9
|
class << self
|
9
10
|
attr_accessor :__global_test_mode
|
10
11
|
|
@@ -12,8 +13,13 @@ module Sidekiq
|
|
12
13
|
# all threads. Calling with a block only affects the current Thread.
|
13
14
|
def __set_test_mode(mode)
|
14
15
|
if block_given?
|
16
|
+
# Reentrant testing modes will lead to a rat's nest of code which is
|
17
|
+
# hard to reason about. You can set the testing mode once globally and
|
18
|
+
# you can override that global setting once per-thread.
|
19
|
+
raise TestModeAlreadySetError, "Nesting test modes is not supported" if __local_test_mode
|
20
|
+
|
21
|
+
self.__local_test_mode = mode
|
15
22
|
begin
|
16
|
-
self.__local_test_mode = mode
|
17
23
|
yield
|
18
24
|
ensure
|
19
25
|
self.__local_test_mode = nil
|
@@ -106,7 +112,7 @@ module Sidekiq
|
|
106
112
|
# The Queues class is only for testing the fake queue implementation.
|
107
113
|
# There are 2 data structures involved in tandem. This is due to the
|
108
114
|
# Rspec syntax of change(HardJob.jobs, :size). It keeps a reference
|
109
|
-
# to the array. Because the array was
|
115
|
+
# to the array. Because the array was derived from a filter of the total
|
110
116
|
# jobs enqueued, it appeared as though the array didn't change.
|
111
117
|
#
|
112
118
|
# To solve this, we'll keep 2 hashes containing the jobs. One with keys based
|
@@ -272,7 +278,7 @@ module Sidekiq
|
|
272
278
|
def perform_one
|
273
279
|
raise(EmptyQueueError, "perform_one called with empty job queue") if jobs.empty?
|
274
280
|
next_job = jobs.first
|
275
|
-
Queues.delete_for(next_job["jid"], queue, to_s)
|
281
|
+
Queues.delete_for(next_job["jid"], next_job["queue"], to_s)
|
276
282
|
process_job(next_job)
|
277
283
|
end
|
278
284
|
|
@@ -9,7 +9,14 @@ module Sidekiq
|
|
9
9
|
@redis_client = Client.new(pool: pool, config: config)
|
10
10
|
end
|
11
11
|
|
12
|
+
def batching?
|
13
|
+
Thread.current[:sidekiq_batch]
|
14
|
+
end
|
15
|
+
|
12
16
|
def push(item)
|
17
|
+
# 6160 we can't support both Sidekiq::Batch and transactions.
|
18
|
+
return @redis_client.push(item) if batching?
|
19
|
+
|
13
20
|
# pre-allocate the JID so we can return it immediately and
|
14
21
|
# save it to the database as part of the transaction.
|
15
22
|
item["jid"] ||= SecureRandom.hex(12)
|
data/lib/sidekiq/version.rb
CHANGED
data/lib/sidekiq/web/action.rb
CHANGED
@@ -22,6 +22,11 @@ module Sidekiq
|
|
22
22
|
throw :halt, [302, {Web::LOCATION => "#{request.base_url}#{location}"}, []]
|
23
23
|
end
|
24
24
|
|
25
|
+
def reload_page
|
26
|
+
current_location = request.referer.gsub(request.base_url, "")
|
27
|
+
redirect current_location
|
28
|
+
end
|
29
|
+
|
25
30
|
def params
|
26
31
|
indifferent_hash = Hash.new { |hash, key| hash[key.to_s] if Symbol === key }
|
27
32
|
|
@@ -15,7 +15,7 @@ module Sidekiq
|
|
15
15
|
"manifest-src 'self'",
|
16
16
|
"media-src 'self'",
|
17
17
|
"object-src 'none'",
|
18
|
-
"script-src 'self' https: http:
|
18
|
+
"script-src 'self' https: http:",
|
19
19
|
"style-src 'self' https: http: 'unsafe-inline'",
|
20
20
|
"worker-src 'self'",
|
21
21
|
"base-uri 'self'"
|
@@ -49,9 +49,9 @@ module Sidekiq
|
|
49
49
|
|
50
50
|
head "/" do
|
51
51
|
# HEAD / is the cheapest heartbeat possible,
|
52
|
-
# it hits Redis to ensure connectivity
|
53
|
-
|
54
|
-
""
|
52
|
+
# it hits Redis to ensure connectivity and returns
|
53
|
+
# the size of the default queue
|
54
|
+
Sidekiq.redis { |c| c.llen("queue:default") }.to_s
|
55
55
|
end
|
56
56
|
|
57
57
|
get "/" do
|
@@ -330,6 +330,22 @@ module Sidekiq
|
|
330
330
|
|
331
331
|
########
|
332
332
|
# Filtering
|
333
|
+
|
334
|
+
get "/filter/metrics" do
|
335
|
+
redirect "#{root_path}metrics"
|
336
|
+
end
|
337
|
+
|
338
|
+
post "/filter/metrics" do
|
339
|
+
x = params[:substr]
|
340
|
+
q = Sidekiq::Metrics::Query.new
|
341
|
+
@period = h((params[:period] || "")[0..1])
|
342
|
+
@periods = METRICS_PERIODS
|
343
|
+
minutes = @periods.fetch(@period, @periods.values.first)
|
344
|
+
@query_result = q.top_jobs(minutes: minutes, class_filter: Regexp.new(Regexp.escape(x), Regexp::IGNORECASE))
|
345
|
+
|
346
|
+
erb :metrics
|
347
|
+
end
|
348
|
+
|
333
349
|
get "/filter/retries" do
|
334
350
|
x = params[:substr]
|
335
351
|
return redirect "#{root_path}retries" unless x && x != ""
|
@@ -378,6 +394,18 @@ module Sidekiq
|
|
378
394
|
erb :morgue
|
379
395
|
end
|
380
396
|
|
397
|
+
post "/change_locale" do
|
398
|
+
locale = params["locale"]
|
399
|
+
|
400
|
+
match = available_locales.find { |available|
|
401
|
+
locale == available
|
402
|
+
}
|
403
|
+
|
404
|
+
session[:locale] = match if match
|
405
|
+
|
406
|
+
reload_page
|
407
|
+
end
|
408
|
+
|
381
409
|
def call(env)
|
382
410
|
action = self.class.match(env)
|
383
411
|
return [404, {Rack::CONTENT_TYPE => "text/plain", Web::X_CASCADE => "pass"}, ["Not Found"]] unless action
|
@@ -27,7 +27,6 @@
|
|
27
27
|
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
28
28
|
|
29
29
|
require "securerandom"
|
30
|
-
require "base64"
|
31
30
|
require "rack/request"
|
32
31
|
|
33
32
|
module Sidekiq
|
@@ -57,7 +56,7 @@ module Sidekiq
|
|
57
56
|
end
|
58
57
|
|
59
58
|
def logger(env)
|
60
|
-
@logger ||=
|
59
|
+
@logger ||= env["rack.logger"] || ::Logger.new(env["rack.errors"])
|
61
60
|
end
|
62
61
|
|
63
62
|
def deny(env)
|
@@ -116,7 +115,7 @@ module Sidekiq
|
|
116
115
|
sess = session(env)
|
117
116
|
localtoken = sess[:csrf]
|
118
117
|
|
119
|
-
# Checks that Rack::Session::Cookie
|
118
|
+
# Checks that Rack::Session::Cookie actually contains the csrf token
|
120
119
|
return false if localtoken.nil?
|
121
120
|
|
122
121
|
# Rotate the session token after every use
|
@@ -143,7 +142,7 @@ module Sidekiq
|
|
143
142
|
one_time_pad = SecureRandom.random_bytes(token.length)
|
144
143
|
encrypted_token = xor_byte_strings(one_time_pad, token)
|
145
144
|
masked_token = one_time_pad + encrypted_token
|
146
|
-
|
145
|
+
encode_token(masked_token)
|
147
146
|
end
|
148
147
|
|
149
148
|
# Essentially the inverse of +mask_token+.
|
@@ -168,8 +167,12 @@ module Sidekiq
|
|
168
167
|
::Rack::Utils.secure_compare(token.to_s, decode_token(local).to_s)
|
169
168
|
end
|
170
169
|
|
170
|
+
def encode_token(token)
|
171
|
+
[token].pack("m0").tr("+/", "-_")
|
172
|
+
end
|
173
|
+
|
171
174
|
def decode_token(token)
|
172
|
-
|
175
|
+
token.tr("-_", "+/").unpack1("m0")
|
173
176
|
end
|
174
177
|
|
175
178
|
def xor_byte_strings(s1, s2)
|
data/lib/sidekiq/web/helpers.rb
CHANGED
@@ -21,6 +21,10 @@ module Sidekiq
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
+
def to_json(x)
|
25
|
+
Sidekiq.dump_json(x)
|
26
|
+
end
|
27
|
+
|
24
28
|
def singularize(str, count)
|
25
29
|
if count == 1 && str.respond_to?(:singularize) # rails
|
26
30
|
str.singularize
|
@@ -117,6 +121,10 @@ module Sidekiq
|
|
117
121
|
#
|
118
122
|
# Inspiration taken from https://github.com/iain/http_accept_language/blob/master/lib/http_accept_language/parser.rb
|
119
123
|
def locale
|
124
|
+
# session[:locale] is set via the locale selector from the footer
|
125
|
+
# defined?(session) && session are used to avoid exceptions when running tests
|
126
|
+
return session[:locale] if defined?(session) && session&.[](:locale)
|
127
|
+
|
120
128
|
@locale ||= begin
|
121
129
|
matched_locale = user_preferred_languages.map { |preferred|
|
122
130
|
preferred_language = preferred.split("-", 2).first
|
@@ -292,23 +300,13 @@ module Sidekiq
|
|
292
300
|
elsif rss_kb < 10_000_000
|
293
301
|
"#{number_with_delimiter((rss_kb / 1024.0).to_i)} MB"
|
294
302
|
else
|
295
|
-
"#{number_with_delimiter((rss_kb / (1024.0 * 1024.0))
|
303
|
+
"#{number_with_delimiter((rss_kb / (1024.0 * 1024.0)), precision: 1)} GB"
|
296
304
|
end
|
297
305
|
end
|
298
306
|
|
299
|
-
def number_with_delimiter(number)
|
300
|
-
|
301
|
-
|
302
|
-
begin
|
303
|
-
Float(number)
|
304
|
-
rescue ArgumentError, TypeError
|
305
|
-
return number
|
306
|
-
end
|
307
|
-
|
308
|
-
options = {delimiter: ",", separator: "."}
|
309
|
-
parts = number.to_s.to_str.split(".")
|
310
|
-
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
|
311
|
-
parts.join(options[:separator])
|
307
|
+
def number_with_delimiter(number, options = {})
|
308
|
+
precision = options[:precision] || 0
|
309
|
+
%(<span data-nwp="#{precision}">#{number.round(precision)}</span>)
|
312
310
|
end
|
313
311
|
|
314
312
|
def h(text)
|
@@ -346,7 +344,8 @@ module Sidekiq
|
|
346
344
|
end
|
347
345
|
|
348
346
|
def pollable?
|
349
|
-
|
347
|
+
# there's no point to refreshing the metrics pages every N seconds
|
348
|
+
!(current_path == "" || current_path.index("metrics"))
|
350
349
|
end
|
351
350
|
|
352
351
|
def retry_or_delete_or_kill(job, params)
|
data/sidekiq.gemspec
CHANGED
@@ -23,7 +23,7 @@ Gem::Specification.new do |gem|
|
|
23
23
|
"rubygems_mfa_required" => "true"
|
24
24
|
}
|
25
25
|
|
26
|
-
gem.add_dependency "redis-client", ">= 0.
|
26
|
+
gem.add_dependency "redis-client", ">= 0.19.0"
|
27
27
|
gem.add_dependency "connection_pool", ">= 2.3.0"
|
28
28
|
gem.add_dependency "rack", ">= 2.2.4"
|
29
29
|
gem.add_dependency "concurrent-ruby", "< 2"
|
@@ -33,6 +33,7 @@ function addListeners() {
|
|
33
33
|
|
34
34
|
addShiftClickListeners()
|
35
35
|
updateFuzzyTimes();
|
36
|
+
updateNumbers();
|
36
37
|
setLivePollFromUrl();
|
37
38
|
|
38
39
|
var buttons = document.querySelectorAll(".live-poll");
|
@@ -46,6 +47,8 @@ function addListeners() {
|
|
46
47
|
scheduleLivePoll();
|
47
48
|
}
|
48
49
|
}
|
50
|
+
|
51
|
+
document.getElementById("locale-select").addEventListener("change", updateLocale);
|
49
52
|
}
|
50
53
|
|
51
54
|
function addPollingListeners(_event) {
|
@@ -102,6 +105,20 @@ function updateFuzzyTimes() {
|
|
102
105
|
t.cancel();
|
103
106
|
}
|
104
107
|
|
108
|
+
function updateNumbers() {
|
109
|
+
document.querySelectorAll("[data-nwp]").forEach(node => {
|
110
|
+
let number = parseFloat(node.textContent);
|
111
|
+
let precision = parseInt(node.dataset["nwp"] || 0);
|
112
|
+
if (typeof number === "number") {
|
113
|
+
let formatted = number.toLocaleString(undefined, {
|
114
|
+
minimumFractionDigits: precision,
|
115
|
+
maximumFractionDigits: precision,
|
116
|
+
});
|
117
|
+
node.textContent = formatted;
|
118
|
+
}
|
119
|
+
});
|
120
|
+
}
|
121
|
+
|
105
122
|
function setLivePollFromUrl() {
|
106
123
|
var url_params = new URL(window.location.href).searchParams
|
107
124
|
|
@@ -160,3 +177,7 @@ function replacePage(text) {
|
|
160
177
|
function showError(error) {
|
161
178
|
console.error(error)
|
162
179
|
}
|
180
|
+
|
181
|
+
function updateLocale(event) {
|
182
|
+
event.target.form.submit();
|
183
|
+
};
|
@@ -86,6 +86,7 @@ class RealtimeChart extends DashboardChart {
|
|
86
86
|
updateStatsSummary(this.stats.sidekiq);
|
87
87
|
updateRedisStats(this.stats.redis);
|
88
88
|
updateFooterUTCTime(this.stats.server_utc_time);
|
89
|
+
updateNumbers();
|
89
90
|
pulseBeacon();
|
90
91
|
|
91
92
|
this.stats = stats;
|
@@ -166,3 +167,16 @@ class RealtimeChart extends DashboardChart {
|
|
166
167
|
};
|
167
168
|
}
|
168
169
|
}
|
170
|
+
|
171
|
+
var rc = document.getElementById("realtime-chart")
|
172
|
+
if (rc != null) {
|
173
|
+
var rtc = new RealtimeChart(rc, JSON.parse(rc.textContent))
|
174
|
+
rtc.registerLegend(document.getElementById("realtime-legend"))
|
175
|
+
window.realtimeChart = rtc
|
176
|
+
}
|
177
|
+
|
178
|
+
var hc = document.getElementById("history-chart")
|
179
|
+
if (hc != null) {
|
180
|
+
var htc = new DashboardChart(hc, JSON.parse(hc.textContent))
|
181
|
+
window.historyChart = htc
|
182
|
+
}
|
@@ -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) {
|
@@ -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
|
+
}
|
@@ -370,6 +370,15 @@ img.smallogo {
|
|
370
370
|
.stat p {
|
371
371
|
font-size: 0.9em;
|
372
372
|
}
|
373
|
+
|
374
|
+
.num {
|
375
|
+
font-family: monospace;
|
376
|
+
}
|
377
|
+
|
378
|
+
td.num {
|
379
|
+
text-align: right;
|
380
|
+
}
|
381
|
+
|
373
382
|
@media (max-width: 767px) {
|
374
383
|
.stats-container {
|
375
384
|
display: block;
|
@@ -722,3 +731,16 @@ div.interval-slider input {
|
|
722
731
|
canvas {
|
723
732
|
margin: 20px 0 30px;
|
724
733
|
}
|
734
|
+
|
735
|
+
#locale-select {
|
736
|
+
float: left;
|
737
|
+
margin: 8px 15px;
|
738
|
+
}
|
739
|
+
|
740
|
+
@media (max-width: 767px) {
|
741
|
+
#locale-select {
|
742
|
+
float: none;
|
743
|
+
width: auto;
|
744
|
+
margin: 15px auto;
|
745
|
+
}
|
746
|
+
}
|
data/web/views/_footer.erb
CHANGED
@@ -15,7 +15,19 @@
|
|
15
15
|
<p class="navbar-text"><a rel=help href="https://github.com/sidekiq/sidekiq/wiki">docs</a></p>
|
16
16
|
</li>
|
17
17
|
<li>
|
18
|
-
<
|
18
|
+
<form id="locale-form" class="form-inline" action="<%= root_path %>change_locale" method="post">
|
19
|
+
<%= csrf_tag %>
|
20
|
+
<label class="sr-only" for="locale">Language</label>
|
21
|
+
<select id="locale-select" class="form-control" name="locale">
|
22
|
+
<% available_locales.each do |locale_option| %>
|
23
|
+
<% if locale_option == locale %>
|
24
|
+
<option selected value="<%= locale_option %>"><%= locale_option %></option>
|
25
|
+
<% else %>
|
26
|
+
<option value="<%= locale_option %>"><%= locale_option %></option>
|
27
|
+
<% end %>
|
28
|
+
<% end %>
|
29
|
+
</select>
|
30
|
+
</form>
|
19
31
|
</li>
|
20
32
|
</ul>
|
21
33
|
</div>
|