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