sidekiq 7.3.9 → 8.1.7
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 +225 -0
- data/README.md +34 -14
- data/bin/kiq +17 -0
- data/bin/lint-herb +13 -0
- data/bin/sidekiqload +12 -10
- data/bin/webload +69 -0
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +107 -58
- data/lib/generators/sidekiq/job_generator.rb +15 -3
- data/lib/generators/sidekiq/templates/job.rb.erb +1 -1
- data/lib/sidekiq/api.rb +310 -83
- data/lib/sidekiq/capsule.rb +10 -7
- data/lib/sidekiq/cli.rb +31 -24
- data/lib/sidekiq/client.rb +36 -18
- data/lib/sidekiq/component.rb +45 -3
- data/lib/sidekiq/config.rb +35 -30
- data/lib/sidekiq/embedded.rb +2 -1
- data/lib/sidekiq/fetch.rb +1 -0
- data/lib/sidekiq/iterable_job.rb +1 -0
- data/lib/sidekiq/job/iterable.rb +46 -18
- data/lib/sidekiq/job.rb +18 -3
- data/lib/sidekiq/job_logger.rb +8 -6
- data/lib/sidekiq/job_retry.rb +45 -18
- data/lib/sidekiq/job_util.rb +5 -1
- data/lib/sidekiq/launcher.rb +22 -12
- data/lib/sidekiq/loader.rb +57 -0
- data/lib/sidekiq/logger.rb +25 -69
- data/lib/sidekiq/manager.rb +2 -2
- data/lib/sidekiq/metrics/query.rb +77 -46
- data/lib/sidekiq/metrics/shared.rb +8 -5
- data/lib/sidekiq/metrics/tracking.rb +12 -7
- data/lib/sidekiq/middleware/current_attributes.rb +11 -19
- data/lib/sidekiq/middleware/i18n.rb +2 -0
- data/lib/sidekiq/monitor.rb +4 -8
- data/lib/sidekiq/paginator.rb +18 -2
- data/lib/sidekiq/processor.rb +26 -18
- data/lib/sidekiq/profiler.rb +73 -0
- data/lib/sidekiq/rails.rb +46 -67
- data/lib/sidekiq/redis_client_adapter.rb +6 -3
- data/lib/sidekiq/redis_connection.rb +16 -5
- data/lib/sidekiq/ring_buffer.rb +1 -0
- data/lib/sidekiq/scheduled.rb +6 -7
- data/lib/sidekiq/test_api.rb +331 -0
- data/lib/sidekiq/testing/inline.rb +2 -30
- data/lib/sidekiq/testing.rb +2 -333
- data/lib/sidekiq/transaction_aware_client.rb +13 -5
- data/lib/sidekiq/tui/controls.rb +53 -0
- data/lib/sidekiq/tui/filtering.rb +53 -0
- data/lib/sidekiq/tui/tabs/base_tab.rb +230 -0
- data/lib/sidekiq/tui/tabs/busy.rb +104 -0
- data/lib/sidekiq/tui/tabs/dead.rb +14 -0
- data/lib/sidekiq/tui/tabs/home.rb +115 -0
- data/lib/sidekiq/tui/tabs/metrics.rb +114 -0
- data/lib/sidekiq/tui/tabs/queues.rb +83 -0
- data/lib/sidekiq/tui/tabs/retries.rb +14 -0
- data/lib/sidekiq/tui/tabs/scheduled.rb +14 -0
- data/lib/sidekiq/tui/tabs/set_tab.rb +91 -0
- data/lib/sidekiq/tui/tabs.rb +15 -0
- data/lib/sidekiq/tui.rb +390 -0
- data/lib/sidekiq/version.rb +2 -2
- data/lib/sidekiq/web/action.rb +146 -83
- data/lib/sidekiq/web/application.rb +372 -328
- data/lib/sidekiq/web/config.rb +117 -0
- data/lib/sidekiq/web/helpers.rb +102 -44
- data/lib/sidekiq/web/router.rb +61 -77
- data/lib/sidekiq/web.rb +65 -151
- data/lib/sidekiq.rb +13 -1
- data/sidekiq.gemspec +9 -8
- data/web/assets/images/logo.png +0 -0
- data/web/assets/images/status.png +0 -0
- data/web/assets/javascripts/application.js +43 -27
- data/web/assets/javascripts/base-charts.js +30 -16
- data/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
- data/web/assets/javascripts/dashboard.js +1 -1
- data/web/assets/javascripts/metrics.js +16 -34
- data/web/assets/stylesheets/style.css +776 -0
- data/web/locales/ar.yml +3 -1
- data/web/locales/cs.yml +2 -0
- data/web/locales/da.yml +2 -0
- data/web/locales/de.yml +2 -0
- data/web/locales/el.yml +2 -0
- data/web/locales/en.yml +8 -0
- data/web/locales/es.yml +25 -2
- data/web/locales/fa.yml +3 -1
- data/web/locales/fr.yml +3 -1
- data/web/locales/gd.yml +13 -1
- data/web/locales/he.yml +3 -1
- data/web/locales/hi.yml +36 -0
- data/web/locales/it.yml +9 -0
- data/web/locales/ja.yml +2 -0
- data/web/locales/ko.yml +2 -0
- data/web/locales/lt.yml +2 -0
- data/web/locales/nb.yml +2 -0
- data/web/locales/nl.yml +2 -0
- data/web/locales/pl.yml +2 -0
- data/web/locales/{pt-br.yml → pt-BR.yml} +3 -1
- data/web/locales/pt.yml +2 -0
- data/web/locales/ru.yml +2 -0
- data/web/locales/sv.yml +2 -0
- data/web/locales/ta.yml +83 -47
- data/web/locales/tr.yml +2 -0
- data/web/locales/uk.yml +7 -5
- data/web/locales/ur.yml +3 -1
- data/web/locales/vi.yml +2 -0
- data/web/locales/{zh-cn.yml → zh-CN.yml} +86 -73
- data/web/locales/{zh-tw.yml → zh-TW.yml} +3 -1
- data/web/views/_footer.html.erb +32 -0
- data/web/views/_job_info.html.erb +115 -0
- data/web/views/_metrics_period_select.html.erb +15 -0
- data/web/views/_nav.html.erb +45 -0
- data/web/views/_paging.html.erb +26 -0
- data/web/views/_poll_link.html.erb +4 -0
- data/web/views/{_summary.erb → _summary.html.erb} +16 -16
- data/web/views/busy.html.erb +155 -0
- data/web/views/dashboard.html.erb +104 -0
- data/web/views/dead.html.erb +38 -0
- data/web/views/filtering.html.erb +6 -0
- data/web/views/layout.html.erb +26 -0
- data/web/views/metrics.html.erb +85 -0
- data/web/views/metrics_for_job.html.erb +58 -0
- data/web/views/morgue.html.erb +69 -0
- data/web/views/profiles.html.erb +43 -0
- data/web/views/queue.html.erb +57 -0
- data/web/views/queues.html.erb +46 -0
- data/web/views/retries.html.erb +77 -0
- data/web/views/retry.html.erb +39 -0
- data/web/views/scheduled.html.erb +64 -0
- data/web/views/{scheduled_job_info.erb → scheduled_job_info.html.erb} +3 -3
- metadata +65 -47
- data/lib/sidekiq/web/csrf_protection.rb +0 -183
- data/web/assets/stylesheets/application-dark.css +0 -147
- data/web/assets/stylesheets/application-rtl.css +0 -163
- data/web/assets/stylesheets/application.css +0 -759
- data/web/assets/stylesheets/bootstrap-rtl.min.css +0 -9
- data/web/assets/stylesheets/bootstrap.css +0 -5
- data/web/views/_footer.erb +0 -34
- data/web/views/_job_info.erb +0 -105
- data/web/views/_metrics_period_select.erb +0 -12
- data/web/views/_nav.erb +0 -52
- data/web/views/_paging.erb +0 -25
- data/web/views/_poll_link.erb +0 -4
- data/web/views/_status.erb +0 -4
- data/web/views/busy.erb +0 -148
- data/web/views/dashboard.erb +0 -108
- data/web/views/dead.erb +0 -34
- data/web/views/filtering.erb +0 -6
- data/web/views/layout.erb +0 -42
- data/web/views/metrics.erb +0 -90
- data/web/views/metrics_for_job.erb +0 -59
- data/web/views/morgue.erb +0 -74
- data/web/views/queue.erb +0 -55
- data/web/views/queues.erb +0 -44
- data/web/views/retries.erb +0 -79
- data/web/views/retry.erb +0 -34
- data/web/views/scheduled.erb +0 -56
data/lib/sidekiq/api.rb
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "sidekiq"
|
|
4
|
-
|
|
5
3
|
require "zlib"
|
|
6
|
-
require "set"
|
|
7
4
|
|
|
5
|
+
require "sidekiq"
|
|
8
6
|
require "sidekiq/metrics/query"
|
|
9
7
|
|
|
10
8
|
#
|
|
@@ -19,12 +17,37 @@ require "sidekiq/metrics/query"
|
|
|
19
17
|
#
|
|
20
18
|
|
|
21
19
|
module Sidekiq
|
|
20
|
+
module ApiUtils
|
|
21
|
+
# @api private
|
|
22
|
+
# Calculate the latency in seconds for a job based on its enqueued timestamp
|
|
23
|
+
# @param job [Hash] the job hash
|
|
24
|
+
# @return [Float] latency in seconds
|
|
25
|
+
def calculate_latency(job)
|
|
26
|
+
timestamp = job["enqueued_at"] || job["created_at"]
|
|
27
|
+
return 0.0 unless timestamp
|
|
28
|
+
|
|
29
|
+
if timestamp.is_a?(Float)
|
|
30
|
+
# old format
|
|
31
|
+
Time.now.to_f - timestamp
|
|
32
|
+
else
|
|
33
|
+
now = ::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond)
|
|
34
|
+
(now - timestamp) / 1000.0
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
22
39
|
# Retrieve runtime statistics from Redis regarding
|
|
23
40
|
# this Sidekiq cluster.
|
|
24
41
|
#
|
|
25
42
|
# stat = Sidekiq::Stats.new
|
|
26
43
|
# stat.processed
|
|
27
44
|
class Stats
|
|
45
|
+
QueueSummary = Data.define(:name, :size, :latency, :paused) do
|
|
46
|
+
alias_method :paused?, :paused
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
include ApiUtils
|
|
50
|
+
|
|
28
51
|
def initialize
|
|
29
52
|
fetch_stats_fast!
|
|
30
53
|
end
|
|
@@ -65,6 +88,7 @@ module Sidekiq
|
|
|
65
88
|
stat :default_queue_latency
|
|
66
89
|
end
|
|
67
90
|
|
|
91
|
+
# @return [Hash{String => Integer}] a hash of queue names to their lengths
|
|
68
92
|
def queues
|
|
69
93
|
Sidekiq.redis do |conn|
|
|
70
94
|
queues = conn.sscan("queues").to_a
|
|
@@ -80,6 +104,41 @@ module Sidekiq
|
|
|
80
104
|
end
|
|
81
105
|
end
|
|
82
106
|
|
|
107
|
+
# More detailed information about each queue: name, size, latency, paused status
|
|
108
|
+
# @return [Array<QueueSummary>]
|
|
109
|
+
def queue_summaries
|
|
110
|
+
Sidekiq.redis do |conn|
|
|
111
|
+
queues = conn.sscan("queues").to_a
|
|
112
|
+
return [] if queues.empty?
|
|
113
|
+
|
|
114
|
+
results = conn.pipelined { |pipeline|
|
|
115
|
+
queues.each do |queue|
|
|
116
|
+
pipeline.llen("queue:#{queue}")
|
|
117
|
+
pipeline.lindex("queue:#{queue}", -1)
|
|
118
|
+
pipeline.sismember("paused", queue)
|
|
119
|
+
end
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
queue_summaries = []
|
|
123
|
+
queues.each_with_index do |name, idx|
|
|
124
|
+
size = results[idx * 3]
|
|
125
|
+
last_item = results[idx * 3 + 1]
|
|
126
|
+
paused = results[idx * 3 + 2] > 0
|
|
127
|
+
|
|
128
|
+
latency = if last_item
|
|
129
|
+
job = Sidekiq.load_json(last_item)
|
|
130
|
+
calculate_latency(job)
|
|
131
|
+
else
|
|
132
|
+
0.0
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
queue_summaries << QueueSummary.new(name:, size:, latency:, paused:)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
queue_summaries.sort_by { |qd| -qd.size }
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
83
142
|
# O(1) redis calls
|
|
84
143
|
# @api private
|
|
85
144
|
def fetch_stats_fast!
|
|
@@ -101,11 +160,10 @@ module Sidekiq
|
|
|
101
160
|
rescue
|
|
102
161
|
{}
|
|
103
162
|
end
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
now - thence
|
|
163
|
+
|
|
164
|
+
calculate_latency(job)
|
|
107
165
|
else
|
|
108
|
-
0
|
|
166
|
+
0.0
|
|
109
167
|
end
|
|
110
168
|
|
|
111
169
|
@stats = {
|
|
@@ -176,7 +234,7 @@ module Sidekiq
|
|
|
176
234
|
end
|
|
177
235
|
|
|
178
236
|
class History
|
|
179
|
-
def initialize(days_previous, start_date = nil
|
|
237
|
+
def initialize(days_previous, start_date = nil)
|
|
180
238
|
# we only store five years of data in Redis
|
|
181
239
|
raise ArgumentError if days_previous < 1 || days_previous > (5 * 365)
|
|
182
240
|
@days_previous = days_previous
|
|
@@ -226,6 +284,7 @@ module Sidekiq
|
|
|
226
284
|
# end
|
|
227
285
|
class Queue
|
|
228
286
|
include Enumerable
|
|
287
|
+
include ApiUtils
|
|
229
288
|
|
|
230
289
|
##
|
|
231
290
|
# Fetch all known queues within Redis.
|
|
@@ -236,6 +295,7 @@ module Sidekiq
|
|
|
236
295
|
end
|
|
237
296
|
|
|
238
297
|
attr_reader :name
|
|
298
|
+
alias_method :id, :name
|
|
239
299
|
|
|
240
300
|
# @param name [String] the name of the queue
|
|
241
301
|
def initialize(name = "default")
|
|
@@ -265,11 +325,10 @@ module Sidekiq
|
|
|
265
325
|
entry = Sidekiq.redis { |conn|
|
|
266
326
|
conn.lindex(@rname, -1)
|
|
267
327
|
}
|
|
268
|
-
return 0 unless entry
|
|
328
|
+
return 0.0 unless entry
|
|
329
|
+
|
|
269
330
|
job = Sidekiq.load_json(entry)
|
|
270
|
-
|
|
271
|
-
thence = job["enqueued_at"] || now
|
|
272
|
-
now - thence
|
|
331
|
+
calculate_latency(job)
|
|
273
332
|
end
|
|
274
333
|
|
|
275
334
|
def each
|
|
@@ -332,6 +391,8 @@ module Sidekiq
|
|
|
332
391
|
# The job should be considered immutable but may be
|
|
333
392
|
# removed from the queue via JobRecord#delete.
|
|
334
393
|
class JobRecord
|
|
394
|
+
include ApiUtils
|
|
395
|
+
|
|
335
396
|
# the parsed Hash of job data
|
|
336
397
|
# @!attribute [r] Item
|
|
337
398
|
attr_reader :item
|
|
@@ -417,16 +478,34 @@ module Sidekiq
|
|
|
417
478
|
self["jid"]
|
|
418
479
|
end
|
|
419
480
|
|
|
481
|
+
def iterable_state
|
|
482
|
+
@iterable_state ||= Sidekiq::IterableJobQuery.new(jid)[jid]
|
|
483
|
+
end
|
|
484
|
+
|
|
420
485
|
def bid
|
|
421
486
|
self["bid"]
|
|
422
487
|
end
|
|
423
488
|
|
|
489
|
+
def failed_at
|
|
490
|
+
if self["failed_at"]
|
|
491
|
+
time_from_timestamp(self["failed_at"])
|
|
492
|
+
end
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
def retried_at
|
|
496
|
+
if self["retried_at"]
|
|
497
|
+
time_from_timestamp(self["retried_at"])
|
|
498
|
+
end
|
|
499
|
+
end
|
|
500
|
+
|
|
424
501
|
def enqueued_at
|
|
425
|
-
|
|
502
|
+
if self["enqueued_at"]
|
|
503
|
+
time_from_timestamp(self["enqueued_at"])
|
|
504
|
+
end
|
|
426
505
|
end
|
|
427
506
|
|
|
428
507
|
def created_at
|
|
429
|
-
|
|
508
|
+
time_from_timestamp(self["created_at"] || self["enqueued_at"] || 0)
|
|
430
509
|
end
|
|
431
510
|
|
|
432
511
|
def tags
|
|
@@ -444,8 +523,7 @@ module Sidekiq
|
|
|
444
523
|
end
|
|
445
524
|
|
|
446
525
|
def latency
|
|
447
|
-
|
|
448
|
-
now - (@item["enqueued_at"] || @item["created_at"] || now)
|
|
526
|
+
calculate_latency(@item)
|
|
449
527
|
end
|
|
450
528
|
|
|
451
529
|
# Remove this job from the queue
|
|
@@ -494,6 +572,15 @@ module Sidekiq
|
|
|
494
572
|
uncompressed = Zlib::Inflate.inflate(strict_base64_decoded)
|
|
495
573
|
Sidekiq.load_json(uncompressed)
|
|
496
574
|
end
|
|
575
|
+
|
|
576
|
+
def time_from_timestamp(timestamp)
|
|
577
|
+
if timestamp.is_a?(Float)
|
|
578
|
+
# old format, timestamps were stored as fractional seconds since the epoch
|
|
579
|
+
Time.at(timestamp).utc
|
|
580
|
+
else
|
|
581
|
+
Time.at(timestamp / 1000, timestamp % 1000, :millisecond)
|
|
582
|
+
end
|
|
583
|
+
end
|
|
497
584
|
end
|
|
498
585
|
|
|
499
586
|
# Represents a job within a Redis sorted set where the score
|
|
@@ -501,17 +588,24 @@ module Sidekiq
|
|
|
501
588
|
# could be the scheduled time for it to run (e.g. scheduled set),
|
|
502
589
|
# or the expiration date after which the entry should be deleted (e.g. dead set).
|
|
503
590
|
class SortedEntry < JobRecord
|
|
504
|
-
attr_reader :score
|
|
505
591
|
attr_reader :parent
|
|
506
592
|
|
|
507
593
|
# :nodoc:
|
|
508
594
|
# @api private
|
|
509
595
|
def initialize(parent, score, item)
|
|
510
596
|
super(item)
|
|
511
|
-
@score =
|
|
597
|
+
@score = score
|
|
512
598
|
@parent = parent
|
|
513
599
|
end
|
|
514
600
|
|
|
601
|
+
def score
|
|
602
|
+
Float(@score)
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
def id
|
|
606
|
+
"#{@score}|#{item["jid"]}"
|
|
607
|
+
end
|
|
608
|
+
|
|
515
609
|
# The timestamp associated with this entry
|
|
516
610
|
def at
|
|
517
611
|
Time.at(score).utc
|
|
@@ -522,7 +616,7 @@ module Sidekiq
|
|
|
522
616
|
if @value
|
|
523
617
|
@parent.delete_by_value(@parent.name, @value)
|
|
524
618
|
else
|
|
525
|
-
@parent.delete_by_jid(score, jid)
|
|
619
|
+
@parent.delete_by_jid(@score, jid)
|
|
526
620
|
end
|
|
527
621
|
end
|
|
528
622
|
|
|
@@ -531,8 +625,9 @@ module Sidekiq
|
|
|
531
625
|
# @param at [Time] the new timestamp for this job
|
|
532
626
|
def reschedule(at)
|
|
533
627
|
Sidekiq.redis do |conn|
|
|
534
|
-
conn.zincrby(@parent.name, at.to_f -
|
|
628
|
+
conn.zincrby(@parent.name, at.to_f - score, Sidekiq.dump_json(@item))
|
|
535
629
|
end
|
|
630
|
+
@score = at.to_f
|
|
536
631
|
end
|
|
537
632
|
|
|
538
633
|
# Enqueue this job from the scheduled or dead set so it will
|
|
@@ -567,38 +662,8 @@ module Sidekiq
|
|
|
567
662
|
|
|
568
663
|
private
|
|
569
664
|
|
|
570
|
-
def remove_job
|
|
571
|
-
|
|
572
|
-
results = conn.multi { |transaction|
|
|
573
|
-
transaction.zrange(parent.name, score, score, "BYSCORE")
|
|
574
|
-
transaction.zremrangebyscore(parent.name, score, score)
|
|
575
|
-
}.first
|
|
576
|
-
|
|
577
|
-
if results.size == 1
|
|
578
|
-
yield results.first
|
|
579
|
-
else
|
|
580
|
-
# multiple jobs with the same score
|
|
581
|
-
# find the one with the right JID and push it
|
|
582
|
-
matched, nonmatched = results.partition { |message|
|
|
583
|
-
if message.index(jid)
|
|
584
|
-
msg = Sidekiq.load_json(message)
|
|
585
|
-
msg["jid"] == jid
|
|
586
|
-
else
|
|
587
|
-
false
|
|
588
|
-
end
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
msg = matched.first
|
|
592
|
-
yield msg if msg
|
|
593
|
-
|
|
594
|
-
# push the rest back onto the sorted set
|
|
595
|
-
conn.multi do |transaction|
|
|
596
|
-
nonmatched.each do |message|
|
|
597
|
-
transaction.zadd(parent.name, score.to_f.to_s, message)
|
|
598
|
-
end
|
|
599
|
-
end
|
|
600
|
-
end
|
|
601
|
-
end
|
|
665
|
+
def remove_job(&)
|
|
666
|
+
parent.remove_job(self, &)
|
|
602
667
|
end
|
|
603
668
|
end
|
|
604
669
|
|
|
@@ -767,6 +832,46 @@ module Sidekiq
|
|
|
767
832
|
nil
|
|
768
833
|
end
|
|
769
834
|
|
|
835
|
+
def remove_job(entry)
|
|
836
|
+
score = entry.score
|
|
837
|
+
jid = entry.jid
|
|
838
|
+
Sidekiq.redis do |conn|
|
|
839
|
+
results = conn.multi { |transaction|
|
|
840
|
+
transaction.zrange(name, score, score, "BYSCORE")
|
|
841
|
+
transaction.zremrangebyscore(name, score, score)
|
|
842
|
+
}.first
|
|
843
|
+
|
|
844
|
+
if results.size == 1
|
|
845
|
+
yield results.first
|
|
846
|
+
@_size -= 1
|
|
847
|
+
else
|
|
848
|
+
# multiple jobs with the same score
|
|
849
|
+
# find the one with the right JID and push it
|
|
850
|
+
matched, nonmatched = results.partition { |message|
|
|
851
|
+
if message.index(jid)
|
|
852
|
+
msg = Sidekiq.load_json(message)
|
|
853
|
+
msg["jid"] == jid
|
|
854
|
+
else
|
|
855
|
+
false
|
|
856
|
+
end
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
msg = matched.first
|
|
860
|
+
if msg
|
|
861
|
+
yield msg
|
|
862
|
+
@_size -= 1
|
|
863
|
+
end
|
|
864
|
+
|
|
865
|
+
# push the rest back onto the sorted set
|
|
866
|
+
conn.multi do |transaction|
|
|
867
|
+
nonmatched.each do |message|
|
|
868
|
+
transaction.zadd(name, score.to_f.to_s, message)
|
|
869
|
+
end
|
|
870
|
+
end
|
|
871
|
+
end
|
|
872
|
+
end
|
|
873
|
+
end
|
|
874
|
+
|
|
770
875
|
# :nodoc:
|
|
771
876
|
# @api private
|
|
772
877
|
def delete_by_value(name, value)
|
|
@@ -940,19 +1045,20 @@ module Sidekiq
|
|
|
940
1045
|
# you'll be happier this way
|
|
941
1046
|
conn.pipelined do |pipeline|
|
|
942
1047
|
procs.each do |key|
|
|
943
|
-
pipeline.hmget(key, "info", "busy", "beat", "quiet", "rss", "rtt_us")
|
|
1048
|
+
pipeline.hmget(key, "info", "concurrency", "busy", "beat", "quiet", "rss", "rtt_us")
|
|
944
1049
|
end
|
|
945
1050
|
end
|
|
946
1051
|
}
|
|
947
1052
|
|
|
948
|
-
result.each do |info, busy, beat, quiet, rss, rtt_us|
|
|
1053
|
+
result.each do |info, concurrency, busy, beat, quiet, rss, rtt_us|
|
|
949
1054
|
# If a process is stopped between when we query Redis for `procs` and
|
|
950
1055
|
# when we query for `result`, we will have an item in `result` that is
|
|
951
1056
|
# composed of `nil` values.
|
|
952
1057
|
next if info.nil?
|
|
953
1058
|
|
|
954
1059
|
hash = Sidekiq.load_json(info)
|
|
955
|
-
yield Process.new(hash.merge("
|
|
1060
|
+
yield Process.new(hash.merge("concurrency" => concurrency.to_i,
|
|
1061
|
+
"busy" => busy.to_i,
|
|
956
1062
|
"beat" => beat.to_f,
|
|
957
1063
|
"quiet" => quiet,
|
|
958
1064
|
"rss" => rss.to_i,
|
|
@@ -1007,9 +1113,9 @@ module Sidekiq
|
|
|
1007
1113
|
# 'started_at' => <process start time>,
|
|
1008
1114
|
# 'pid' => 12345,
|
|
1009
1115
|
# 'tag' => 'myapp'
|
|
1010
|
-
# 'concurrency' =>
|
|
1011
|
-
# '
|
|
1012
|
-
# 'busy' =>
|
|
1116
|
+
# 'concurrency' => 5,
|
|
1117
|
+
# 'capsules' => {"default" => {"mode" => "weighted", "concurrency" => 5, "weights" => {"default" => 2, "low" => 1}}},
|
|
1118
|
+
# 'busy' => 3,
|
|
1013
1119
|
# 'beat' => <last heartbeat>,
|
|
1014
1120
|
# 'identity' => <unique string identifying the process>,
|
|
1015
1121
|
# 'embedded' => true,
|
|
@@ -1036,13 +1142,37 @@ module Sidekiq
|
|
|
1036
1142
|
def identity
|
|
1037
1143
|
self["identity"]
|
|
1038
1144
|
end
|
|
1145
|
+
alias_method :id, :identity
|
|
1039
1146
|
|
|
1147
|
+
# deprecated, use capsules below
|
|
1040
1148
|
def queues
|
|
1041
|
-
|
|
1149
|
+
# Backwards compatibility with <8.0.8
|
|
1150
|
+
if !self["capsules"]
|
|
1151
|
+
self["queues"]
|
|
1152
|
+
else
|
|
1153
|
+
capsules.values.flat_map { |x| x["weights"].keys }.uniq
|
|
1154
|
+
end
|
|
1042
1155
|
end
|
|
1043
1156
|
|
|
1157
|
+
# deprecated, use capsules below
|
|
1044
1158
|
def weights
|
|
1045
|
-
|
|
1159
|
+
# Backwards compatibility with <8.0.8
|
|
1160
|
+
if !self["capsules"]
|
|
1161
|
+
self["weights"]
|
|
1162
|
+
else
|
|
1163
|
+
hash = {}
|
|
1164
|
+
capsules.values.each do |cap|
|
|
1165
|
+
# Note: will lose data if two capsules are processing the same named queue
|
|
1166
|
+
cap["weights"].each_pair do |queue, weight|
|
|
1167
|
+
hash[queue] = weight
|
|
1168
|
+
end
|
|
1169
|
+
end
|
|
1170
|
+
hash
|
|
1171
|
+
end
|
|
1172
|
+
end
|
|
1173
|
+
|
|
1174
|
+
def capsules
|
|
1175
|
+
self["capsules"]
|
|
1046
1176
|
end
|
|
1047
1177
|
|
|
1048
1178
|
def version
|
|
@@ -1086,6 +1216,10 @@ module Sidekiq
|
|
|
1086
1216
|
self["quiet"] == "true"
|
|
1087
1217
|
end
|
|
1088
1218
|
|
|
1219
|
+
def leader?
|
|
1220
|
+
Sidekiq.redis { |c| c.get("dear-leader") == identity }
|
|
1221
|
+
end
|
|
1222
|
+
|
|
1089
1223
|
private
|
|
1090
1224
|
|
|
1091
1225
|
def signal(sig)
|
|
@@ -1114,9 +1248,8 @@ module Sidekiq
|
|
|
1114
1248
|
# works.each do |process_id, thread_id, work|
|
|
1115
1249
|
# # process_id is a unique identifier per Sidekiq process
|
|
1116
1250
|
# # thread_id is a unique identifier per thread
|
|
1117
|
-
# # work is a
|
|
1118
|
-
# #
|
|
1119
|
-
# # run_at is an epoch Integer.
|
|
1251
|
+
# # work is a `Sidekiq::Work` instance that has the following accessor methods.
|
|
1252
|
+
# # [work.queue, work.run_at, work.payload]
|
|
1120
1253
|
# end
|
|
1121
1254
|
#
|
|
1122
1255
|
class WorkSet
|
|
@@ -1142,7 +1275,7 @@ module Sidekiq
|
|
|
1142
1275
|
end
|
|
1143
1276
|
end
|
|
1144
1277
|
|
|
1145
|
-
results.sort_by { |(_, _,
|
|
1278
|
+
results.sort_by { |(_, _, work)| work.run_at }.each(&block)
|
|
1146
1279
|
end
|
|
1147
1280
|
|
|
1148
1281
|
# Note that #size is only as accurate as Sidekiq's heartbeat,
|
|
@@ -1172,13 +1305,14 @@ module Sidekiq
|
|
|
1172
1305
|
#
|
|
1173
1306
|
# @param jid [String] the job identifier
|
|
1174
1307
|
# @return [Sidekiq::Work] the work or nil
|
|
1175
|
-
def
|
|
1308
|
+
def find_work(jid)
|
|
1176
1309
|
each do |_process_id, _thread_id, work|
|
|
1177
1310
|
job = work.job
|
|
1178
1311
|
return work if job.jid == jid
|
|
1179
1312
|
end
|
|
1180
1313
|
nil
|
|
1181
1314
|
end
|
|
1315
|
+
alias_method :find_work_by_jid, :find_work
|
|
1182
1316
|
end
|
|
1183
1317
|
|
|
1184
1318
|
# Sidekiq::Work represents a job which is currently executing.
|
|
@@ -1208,33 +1342,126 @@ module Sidekiq
|
|
|
1208
1342
|
def payload
|
|
1209
1343
|
@hsh["payload"]
|
|
1210
1344
|
end
|
|
1345
|
+
end
|
|
1211
1346
|
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
warn("Direct access to `Sidekiq::Work` attributes is deprecated, please use `#payload`, `#queue`, `#run_at` or `#job` instead", **kwargs)
|
|
1347
|
+
# Since "worker" is a nebulous term, we've deprecated the use of this class name.
|
|
1348
|
+
# Is "worker" a process, a type of job, a thread? Undefined!
|
|
1349
|
+
# WorkSet better describes the data.
|
|
1350
|
+
Workers = WorkSet
|
|
1217
1351
|
|
|
1218
|
-
|
|
1352
|
+
class ProfileSet
|
|
1353
|
+
include Enumerable
|
|
1354
|
+
|
|
1355
|
+
# This is a point in time/snapshot API, you'll need to instantiate a new instance
|
|
1356
|
+
# if you want to fetch newer records.
|
|
1357
|
+
def initialize
|
|
1358
|
+
@records = Sidekiq.redis do |c|
|
|
1359
|
+
# This throws away expired profiles
|
|
1360
|
+
c.zremrangebyscore("profiles", "-inf", Time.now.to_f.to_s)
|
|
1361
|
+
# retreive records, newest to oldest
|
|
1362
|
+
c.zrange("profiles", "+inf", 0, "byscore", "rev")
|
|
1363
|
+
end
|
|
1219
1364
|
end
|
|
1220
1365
|
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
def raw(name)
|
|
1224
|
-
@hsh[name]
|
|
1366
|
+
def size
|
|
1367
|
+
@records.size
|
|
1225
1368
|
end
|
|
1226
1369
|
|
|
1227
|
-
def
|
|
1228
|
-
|
|
1370
|
+
def each(&block)
|
|
1371
|
+
fetch_keys = %w[started_at jid type token size elapsed].freeze
|
|
1372
|
+
arrays = Sidekiq.redis do |c|
|
|
1373
|
+
c.pipelined do |p|
|
|
1374
|
+
@records.each do |key|
|
|
1375
|
+
p.hmget(key, *fetch_keys)
|
|
1376
|
+
end
|
|
1377
|
+
end
|
|
1378
|
+
end
|
|
1379
|
+
|
|
1380
|
+
arrays.compact.map { |arr| ProfileRecord.new(arr) }.each(&block)
|
|
1381
|
+
end
|
|
1382
|
+
end
|
|
1383
|
+
|
|
1384
|
+
class ProfileRecord
|
|
1385
|
+
attr_reader :started_at, :jid, :type, :token, :size, :elapsed
|
|
1386
|
+
|
|
1387
|
+
def initialize(arr)
|
|
1388
|
+
# Must be same order as fetch_keys above
|
|
1389
|
+
@started_at = Time.at(Integer(arr[0]))
|
|
1390
|
+
@jid = arr[1]
|
|
1391
|
+
@type = arr[2]
|
|
1392
|
+
@token = arr[3]
|
|
1393
|
+
@size = Integer(arr[4])
|
|
1394
|
+
@elapsed = Float(arr[5])
|
|
1229
1395
|
end
|
|
1230
1396
|
|
|
1231
|
-
def
|
|
1232
|
-
|
|
1397
|
+
def key
|
|
1398
|
+
"#{token}-#{jid}"
|
|
1399
|
+
end
|
|
1400
|
+
|
|
1401
|
+
def data
|
|
1402
|
+
Sidekiq.redis { |c| c.hget(key, "data") }
|
|
1233
1403
|
end
|
|
1234
1404
|
end
|
|
1235
1405
|
|
|
1236
|
-
#
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1406
|
+
# Persisted iteration state from Redis for jobs using Sidekiq::IterableJob.
|
|
1407
|
+
class IterableJobQuery
|
|
1408
|
+
def initialize(jids)
|
|
1409
|
+
@cache = bulk_fetch(jids)
|
|
1410
|
+
end
|
|
1411
|
+
|
|
1412
|
+
def [](jid)
|
|
1413
|
+
@cache[jid]
|
|
1414
|
+
end
|
|
1415
|
+
|
|
1416
|
+
private
|
|
1417
|
+
|
|
1418
|
+
# Bulk-fetch iteration state for multiple JIDs in a single Redis pipeline.
|
|
1419
|
+
# Returns a Hash of { jid => State } for JIDs that have iteration state.
|
|
1420
|
+
def bulk_fetch(jids)
|
|
1421
|
+
raise ArgumentError unless jids
|
|
1422
|
+
jids_to_fetch = Array(jids).compact.uniq
|
|
1423
|
+
return {} if jids_to_fetch.empty?
|
|
1424
|
+
|
|
1425
|
+
results = Sidekiq.redis do |conn|
|
|
1426
|
+
conn.pipelined do |pipe|
|
|
1427
|
+
jids_to_fetch.each { |jid| pipe.hgetall("it-#{jid}") }
|
|
1428
|
+
end
|
|
1429
|
+
end
|
|
1430
|
+
|
|
1431
|
+
# TODO Requires Ruby 4
|
|
1432
|
+
# states = ::Hash.new(capacity: jids_to_fetch.size)
|
|
1433
|
+
states = {}
|
|
1434
|
+
jids_to_fetch.each_with_index do |jid, i|
|
|
1435
|
+
raw = results[i]
|
|
1436
|
+
next if raw.nil? || raw.empty?
|
|
1437
|
+
|
|
1438
|
+
states[jid] = State.new(jid, raw)
|
|
1439
|
+
end
|
|
1440
|
+
states
|
|
1441
|
+
end
|
|
1442
|
+
|
|
1443
|
+
State = Struct.new(:jid, :raw) do
|
|
1444
|
+
def executions
|
|
1445
|
+
raw["ex"].to_i
|
|
1446
|
+
end
|
|
1447
|
+
|
|
1448
|
+
def runtime
|
|
1449
|
+
raw["rt"].to_f
|
|
1450
|
+
end
|
|
1451
|
+
|
|
1452
|
+
def cursor
|
|
1453
|
+
@cursor ||= begin
|
|
1454
|
+
Sidekiq.load_json(raw["c"])
|
|
1455
|
+
rescue JSON::ParserError
|
|
1456
|
+
raw["c"]
|
|
1457
|
+
end
|
|
1458
|
+
end
|
|
1459
|
+
|
|
1460
|
+
def cancelled
|
|
1461
|
+
raw["cancelled"]&.to_i
|
|
1462
|
+
end
|
|
1463
|
+
end
|
|
1464
|
+
end
|
|
1240
1465
|
end
|
|
1466
|
+
|
|
1467
|
+
Sidekiq.loader.run_load_hooks(:api)
|
data/lib/sidekiq/capsule.rb
CHANGED
|
@@ -11,12 +11,12 @@ module Sidekiq
|
|
|
11
11
|
# This capsule will pull jobs from the "single" queue and process
|
|
12
12
|
# the jobs with one thread, meaning the jobs will be processed serially.
|
|
13
13
|
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
14
|
+
# Sidekiq.configure_server do |config|
|
|
15
|
+
# config.capsule("single-threaded") do |cap|
|
|
16
|
+
# cap.concurrency = 1
|
|
17
|
+
# cap.queues = %w(single)
|
|
18
|
+
# end
|
|
18
19
|
# end
|
|
19
|
-
# end
|
|
20
20
|
class Capsule
|
|
21
21
|
include Sidekiq::Component
|
|
22
22
|
extend Forwardable
|
|
@@ -27,7 +27,7 @@ module Sidekiq
|
|
|
27
27
|
attr_reader :mode
|
|
28
28
|
attr_reader :weights
|
|
29
29
|
|
|
30
|
-
def_delegators :@config, :[], :[]=, :fetch, :key?, :has_key?, :merge!, :dig
|
|
30
|
+
def_delegators :@config, :[], :[]=, :fetch, :key?, :has_key?, :merge!, :dig, :thread_priority
|
|
31
31
|
|
|
32
32
|
def initialize(name, config)
|
|
33
33
|
@name = name
|
|
@@ -38,6 +38,10 @@ module Sidekiq
|
|
|
38
38
|
@mode = :strict
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
+
def to_h
|
|
42
|
+
{concurrency: concurrency, mode: mode, weights: weights}
|
|
43
|
+
end
|
|
44
|
+
|
|
41
45
|
def fetcher
|
|
42
46
|
@fetcher ||= begin
|
|
43
47
|
instance = (config[:fetch_class] || Sidekiq::BasicFetch).new(self)
|
|
@@ -47,7 +51,6 @@ module Sidekiq
|
|
|
47
51
|
end
|
|
48
52
|
|
|
49
53
|
def stop
|
|
50
|
-
fetcher&.bulk_requeue([])
|
|
51
54
|
end
|
|
52
55
|
|
|
53
56
|
# Sidekiq checks queues in three modes:
|