ultra-fast-tool 0.0.1
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 +7 -0
- data/sidekiq-8.1.6/Changes.md +2515 -0
- data/sidekiq-8.1.6/LICENSE.txt +9 -0
- data/sidekiq-8.1.6/README.md +124 -0
- data/sidekiq-8.1.6/bin/kiq +17 -0
- data/sidekiq-8.1.6/bin/lint-herb +13 -0
- data/sidekiq-8.1.6/bin/multi_queue_bench +271 -0
- data/sidekiq-8.1.6/bin/sidekiq +37 -0
- data/sidekiq-8.1.6/bin/sidekiqload +256 -0
- data/sidekiq-8.1.6/bin/sidekiqmon +11 -0
- data/sidekiq-8.1.6/bin/webload +69 -0
- data/sidekiq-8.1.6/lib/active_job/queue_adapters/sidekiq_adapter.rb +124 -0
- data/sidekiq-8.1.6/lib/generators/sidekiq/job_generator.rb +71 -0
- data/sidekiq-8.1.6/lib/generators/sidekiq/templates/job.rb.erb +9 -0
- data/sidekiq-8.1.6/lib/generators/sidekiq/templates/job_spec.rb.erb +6 -0
- data/sidekiq-8.1.6/lib/generators/sidekiq/templates/job_test.rb.erb +8 -0
- data/sidekiq-8.1.6/lib/sidekiq/api.rb +1466 -0
- data/sidekiq-8.1.6/lib/sidekiq/capsule.rb +135 -0
- data/sidekiq-8.1.6/lib/sidekiq/cli.rb +433 -0
- data/sidekiq-8.1.6/lib/sidekiq/client.rb +306 -0
- data/sidekiq-8.1.6/lib/sidekiq/component.rb +132 -0
- data/sidekiq-8.1.6/lib/sidekiq/config.rb +316 -0
- data/sidekiq-8.1.6/lib/sidekiq/deploy.rb +64 -0
- data/sidekiq-8.1.6/lib/sidekiq/embedded.rb +64 -0
- data/sidekiq-8.1.6/lib/sidekiq/fetch.rb +89 -0
- data/sidekiq-8.1.6/lib/sidekiq/iterable_job.rb +56 -0
- data/sidekiq-8.1.6/lib/sidekiq/job/interrupt_handler.rb +24 -0
- data/sidekiq-8.1.6/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
- data/sidekiq-8.1.6/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
- data/sidekiq-8.1.6/lib/sidekiq/job/iterable/enumerators.rb +135 -0
- data/sidekiq-8.1.6/lib/sidekiq/job/iterable.rb +322 -0
- data/sidekiq-8.1.6/lib/sidekiq/job.rb +400 -0
- data/sidekiq-8.1.6/lib/sidekiq/job_logger.rb +54 -0
- data/sidekiq-8.1.6/lib/sidekiq/job_retry.rb +332 -0
- data/sidekiq-8.1.6/lib/sidekiq/job_util.rb +113 -0
- data/sidekiq-8.1.6/lib/sidekiq/launcher.rb +283 -0
- data/sidekiq-8.1.6/lib/sidekiq/loader.rb +57 -0
- data/sidekiq-8.1.6/lib/sidekiq/logger.rb +87 -0
- data/sidekiq-8.1.6/lib/sidekiq/manager.rb +134 -0
- data/sidekiq-8.1.6/lib/sidekiq/metrics/query.rb +184 -0
- data/sidekiq-8.1.6/lib/sidekiq/metrics/shared.rb +109 -0
- data/sidekiq-8.1.6/lib/sidekiq/metrics/tracking.rb +153 -0
- data/sidekiq-8.1.6/lib/sidekiq/middleware/chain.rb +207 -0
- data/sidekiq-8.1.6/lib/sidekiq/middleware/current_attributes.rb +120 -0
- data/sidekiq-8.1.6/lib/sidekiq/middleware/i18n.rb +44 -0
- data/sidekiq-8.1.6/lib/sidekiq/middleware/modules.rb +23 -0
- data/sidekiq-8.1.6/lib/sidekiq/monitor.rb +143 -0
- data/sidekiq-8.1.6/lib/sidekiq/paginator.rb +74 -0
- data/sidekiq-8.1.6/lib/sidekiq/processor.rb +298 -0
- data/sidekiq-8.1.6/lib/sidekiq/profiler.rb +73 -0
- data/sidekiq-8.1.6/lib/sidekiq/rails.rb +63 -0
- data/sidekiq-8.1.6/lib/sidekiq/redis_client_adapter.rb +117 -0
- data/sidekiq-8.1.6/lib/sidekiq/redis_connection.rb +115 -0
- data/sidekiq-8.1.6/lib/sidekiq/ring_buffer.rb +32 -0
- data/sidekiq-8.1.6/lib/sidekiq/scheduled.rb +235 -0
- data/sidekiq-8.1.6/lib/sidekiq/sd_notify.rb +149 -0
- data/sidekiq-8.1.6/lib/sidekiq/systemd.rb +26 -0
- data/sidekiq-8.1.6/lib/sidekiq/test_api.rb +331 -0
- data/sidekiq-8.1.6/lib/sidekiq/testing/inline.rb +2 -0
- data/sidekiq-8.1.6/lib/sidekiq/testing.rb +3 -0
- data/sidekiq-8.1.6/lib/sidekiq/transaction_aware_client.rb +59 -0
- data/sidekiq-8.1.6/lib/sidekiq/tui/controls.rb +53 -0
- data/sidekiq-8.1.6/lib/sidekiq/tui/filtering.rb +53 -0
- data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/base_tab.rb +204 -0
- data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/busy.rb +118 -0
- data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/dead.rb +19 -0
- data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/home.rb +144 -0
- data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/metrics.rb +131 -0
- data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/queues.rb +95 -0
- data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/retries.rb +19 -0
- data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/scheduled.rb +19 -0
- data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/set_tab.rb +96 -0
- data/sidekiq-8.1.6/lib/sidekiq/tui/tabs.rb +15 -0
- data/sidekiq-8.1.6/lib/sidekiq/tui.rb +382 -0
- data/sidekiq-8.1.6/lib/sidekiq/version.rb +10 -0
- data/sidekiq-8.1.6/lib/sidekiq/web/action.rb +178 -0
- data/sidekiq-8.1.6/lib/sidekiq/web/application.rb +480 -0
- data/sidekiq-8.1.6/lib/sidekiq/web/config.rb +117 -0
- data/sidekiq-8.1.6/lib/sidekiq/web/helpers.rb +474 -0
- data/sidekiq-8.1.6/lib/sidekiq/web/router.rb +91 -0
- data/sidekiq-8.1.6/lib/sidekiq/web.rb +140 -0
- data/sidekiq-8.1.6/lib/sidekiq/worker_compatibility_alias.rb +13 -0
- data/sidekiq-8.1.6/lib/sidekiq.rb +161 -0
- data/sidekiq-8.1.6/sidekiq.gemspec +31 -0
- data/sidekiq-8.1.6/web/assets/images/apple-touch-icon.png +0 -0
- data/sidekiq-8.1.6/web/assets/images/favicon.ico +0 -0
- data/sidekiq-8.1.6/web/assets/images/logo.png +0 -0
- data/sidekiq-8.1.6/web/assets/images/status.png +0 -0
- data/sidekiq-8.1.6/web/assets/javascripts/application.js +204 -0
- data/sidekiq-8.1.6/web/assets/javascripts/base-charts.js +120 -0
- data/sidekiq-8.1.6/web/assets/javascripts/chart.min.js +13 -0
- data/sidekiq-8.1.6/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
- data/sidekiq-8.1.6/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
- data/sidekiq-8.1.6/web/assets/javascripts/dashboard-charts.js +194 -0
- data/sidekiq-8.1.6/web/assets/javascripts/dashboard.js +63 -0
- data/sidekiq-8.1.6/web/assets/javascripts/metrics.js +280 -0
- data/sidekiq-8.1.6/web/assets/stylesheets/style.css +776 -0
- data/sidekiq-8.1.6/web/locales/ar.yml +89 -0
- data/sidekiq-8.1.6/web/locales/cs.yml +80 -0
- data/sidekiq-8.1.6/web/locales/da.yml +77 -0
- data/sidekiq-8.1.6/web/locales/de.yml +83 -0
- data/sidekiq-8.1.6/web/locales/el.yml +89 -0
- data/sidekiq-8.1.6/web/locales/en.yml +110 -0
- data/sidekiq-8.1.6/web/locales/es.yml +109 -0
- data/sidekiq-8.1.6/web/locales/fa.yml +82 -0
- data/sidekiq-8.1.6/web/locales/fr.yml +100 -0
- data/sidekiq-8.1.6/web/locales/gd.yml +110 -0
- data/sidekiq-8.1.6/web/locales/he.yml +82 -0
- data/sidekiq-8.1.6/web/locales/hi.yml +77 -0
- data/sidekiq-8.1.6/web/locales/it.yml +109 -0
- data/sidekiq-8.1.6/web/locales/ja.yml +92 -0
- data/sidekiq-8.1.6/web/locales/ko.yml +70 -0
- data/sidekiq-8.1.6/web/locales/lt.yml +85 -0
- data/sidekiq-8.1.6/web/locales/nb.yml +79 -0
- data/sidekiq-8.1.6/web/locales/nl.yml +70 -0
- data/sidekiq-8.1.6/web/locales/pl.yml +61 -0
- data/sidekiq-8.1.6/web/locales/pt-BR.yml +97 -0
- data/sidekiq-8.1.6/web/locales/pt.yml +69 -0
- data/sidekiq-8.1.6/web/locales/ru.yml +85 -0
- data/sidekiq-8.1.6/web/locales/sv.yml +70 -0
- data/sidekiq-8.1.6/web/locales/ta.yml +77 -0
- data/sidekiq-8.1.6/web/locales/tr.yml +102 -0
- data/sidekiq-8.1.6/web/locales/uk.yml +102 -0
- data/sidekiq-8.1.6/web/locales/ur.yml +82 -0
- data/sidekiq-8.1.6/web/locales/vi.yml +85 -0
- data/sidekiq-8.1.6/web/locales/zh-CN.yml +107 -0
- data/sidekiq-8.1.6/web/locales/zh-TW.yml +103 -0
- data/sidekiq-8.1.6/web/views/_footer.html.erb +32 -0
- data/sidekiq-8.1.6/web/views/_job_info.html.erb +115 -0
- data/sidekiq-8.1.6/web/views/_metrics_period_select.html.erb +15 -0
- data/sidekiq-8.1.6/web/views/_nav.html.erb +45 -0
- data/sidekiq-8.1.6/web/views/_paging.html.erb +26 -0
- data/sidekiq-8.1.6/web/views/_poll_link.html.erb +4 -0
- data/sidekiq-8.1.6/web/views/_summary.html.erb +40 -0
- data/sidekiq-8.1.6/web/views/busy.html.erb +155 -0
- data/sidekiq-8.1.6/web/views/dashboard.html.erb +104 -0
- data/sidekiq-8.1.6/web/views/dead.html.erb +38 -0
- data/sidekiq-8.1.6/web/views/filtering.html.erb +6 -0
- data/sidekiq-8.1.6/web/views/layout.html.erb +26 -0
- data/sidekiq-8.1.6/web/views/metrics.html.erb +85 -0
- data/sidekiq-8.1.6/web/views/metrics_for_job.html.erb +58 -0
- data/sidekiq-8.1.6/web/views/morgue.html.erb +69 -0
- data/sidekiq-8.1.6/web/views/profiles.html.erb +43 -0
- data/sidekiq-8.1.6/web/views/queue.html.erb +57 -0
- data/sidekiq-8.1.6/web/views/queues.html.erb +46 -0
- data/sidekiq-8.1.6/web/views/retries.html.erb +77 -0
- data/sidekiq-8.1.6/web/views/retry.html.erb +39 -0
- data/sidekiq-8.1.6/web/views/scheduled.html.erb +64 -0
- data/sidekiq-8.1.6/web/views/scheduled_job_info.html.erb +8 -0
- data/ultra-fast-tool.gemspec +11 -0
- metadata +189 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_job/arguments"
|
|
4
|
+
require "active_support/current_attributes"
|
|
5
|
+
|
|
6
|
+
module Sidekiq
|
|
7
|
+
##
|
|
8
|
+
# Automatically save and load any current attributes in the execution context
|
|
9
|
+
# so context attributes "flow" from Rails actions into any associated jobs.
|
|
10
|
+
# This can be useful for multi-tenancy, i18n locale, timezone, any implicit
|
|
11
|
+
# per-request attribute. See +ActiveSupport::CurrentAttributes+.
|
|
12
|
+
#
|
|
13
|
+
# For multiple current attributes, pass an array of current attributes.
|
|
14
|
+
#
|
|
15
|
+
# @example
|
|
16
|
+
#
|
|
17
|
+
# # in your initializer
|
|
18
|
+
# require "sidekiq/middleware/current_attributes"
|
|
19
|
+
# Sidekiq::CurrentAttributes.persist("Myapp::Current")
|
|
20
|
+
# # or multiple current attributes
|
|
21
|
+
# Sidekiq::CurrentAttributes.persist(["Myapp::Current", "Myapp::OtherCurrent"])
|
|
22
|
+
#
|
|
23
|
+
module CurrentAttributes
|
|
24
|
+
Serializer = ::ActiveJob::Arguments
|
|
25
|
+
|
|
26
|
+
class Save
|
|
27
|
+
include Sidekiq::ClientMiddleware
|
|
28
|
+
|
|
29
|
+
def initialize(cattrs)
|
|
30
|
+
@cattrs = cattrs
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def call(_, job, _, _)
|
|
34
|
+
@cattrs.each do |(key, strklass)|
|
|
35
|
+
if !job.has_key?(key)
|
|
36
|
+
attrs = strklass.constantize.attributes
|
|
37
|
+
# Retries can push the job N times, we don't
|
|
38
|
+
# want retries to reset cattr. #5692, #5090
|
|
39
|
+
job[key] = Serializer.serialize(attrs) if attrs.any?
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
yield
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class Load
|
|
47
|
+
include Sidekiq::ServerMiddleware
|
|
48
|
+
|
|
49
|
+
def initialize(cattrs)
|
|
50
|
+
@cattrs = cattrs
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def call(_, job, *, &block)
|
|
54
|
+
klass_attrs = {}
|
|
55
|
+
|
|
56
|
+
@cattrs.each do |(key, strklass)|
|
|
57
|
+
next unless job.has_key?(key)
|
|
58
|
+
|
|
59
|
+
klass_attrs[strklass.constantize] = Serializer.deserialize(job[key]).to_h
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
wrap(klass_attrs.to_a, &block)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def wrap(klass_attrs, &block)
|
|
68
|
+
klass, attrs = klass_attrs.shift
|
|
69
|
+
return block.call unless klass
|
|
70
|
+
|
|
71
|
+
retried = false
|
|
72
|
+
|
|
73
|
+
begin
|
|
74
|
+
set_succeeded = false
|
|
75
|
+
klass.set(attrs) do
|
|
76
|
+
set_succeeded = true
|
|
77
|
+
wrap(klass_attrs, &block)
|
|
78
|
+
end
|
|
79
|
+
rescue NoMethodError
|
|
80
|
+
# Don't retry if the no method error didn't come from current attributes
|
|
81
|
+
raise if retried || set_succeeded
|
|
82
|
+
|
|
83
|
+
# It is possible that the `CurrentAttributes` definition
|
|
84
|
+
# was changed before the job started processing.
|
|
85
|
+
attrs = attrs.select { |attr| klass.respond_to?(attr) }
|
|
86
|
+
retried = true
|
|
87
|
+
retry
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
class << self
|
|
93
|
+
def persist(klass_or_array, config = Sidekiq.default_configuration)
|
|
94
|
+
cattrs = build_cattrs_hash(klass_or_array)
|
|
95
|
+
|
|
96
|
+
config.client_middleware.prepend Load, cattrs
|
|
97
|
+
config.client_middleware.add Save, cattrs
|
|
98
|
+
config.server_middleware.prepend Load, cattrs
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
def build_cattrs_hash(klass_or_array)
|
|
104
|
+
if klass_or_array.is_a?(Array)
|
|
105
|
+
{}.tap do |hash|
|
|
106
|
+
klass_or_array.each_with_index do |klass, index|
|
|
107
|
+
hash[key_at(index)] = klass.to_s
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
else
|
|
111
|
+
{key_at(0) => klass_or_array.to_s}
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def key_at(index)
|
|
116
|
+
(index == 0) ? "cattr" : "cattr_#{index}"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# Simple middleware to save the current locale and restore it when the job executes.
|
|
5
|
+
# Use it by requiring it in your initializer:
|
|
6
|
+
#
|
|
7
|
+
# require 'sidekiq/middleware/i18n'
|
|
8
|
+
#
|
|
9
|
+
module Sidekiq::Middleware::I18n
|
|
10
|
+
# Get the current locale and store it in the message
|
|
11
|
+
# to be sent to Sidekiq.
|
|
12
|
+
class Client
|
|
13
|
+
include Sidekiq::ClientMiddleware
|
|
14
|
+
|
|
15
|
+
def call(_jobclass, job, _queue, _redis)
|
|
16
|
+
job["locale"] ||= I18n.locale
|
|
17
|
+
yield
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Pull the msg locale out and set the current thread to use it.
|
|
22
|
+
class Server
|
|
23
|
+
include Sidekiq::ServerMiddleware
|
|
24
|
+
|
|
25
|
+
def call(_jobclass, job, _queue, &block)
|
|
26
|
+
I18n.with_locale(job.fetch("locale", I18n.default_locale), &block)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
Sidekiq.configure_client do |config|
|
|
32
|
+
config.client_middleware do |chain|
|
|
33
|
+
chain.add Sidekiq::Middleware::I18n::Client
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
Sidekiq.configure_server do |config|
|
|
38
|
+
config.client_middleware do |chain|
|
|
39
|
+
chain.add Sidekiq::Middleware::I18n::Client
|
|
40
|
+
end
|
|
41
|
+
config.server_middleware do |chain|
|
|
42
|
+
chain.add Sidekiq::Middleware::I18n::Server
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sidekiq
|
|
4
|
+
# Server-side middleware must import this Module in order
|
|
5
|
+
# to get access to server resources during `call`.
|
|
6
|
+
module ServerMiddleware
|
|
7
|
+
attr_accessor :config
|
|
8
|
+
def redis_pool
|
|
9
|
+
config.redis_pool
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def logger
|
|
13
|
+
config.logger
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def redis(&block)
|
|
17
|
+
config.redis(&block)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# no difference for now
|
|
22
|
+
ClientMiddleware = ServerMiddleware
|
|
23
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "sidekiq/api"
|
|
6
|
+
|
|
7
|
+
class Sidekiq::Monitor
|
|
8
|
+
class Status
|
|
9
|
+
VALID_SECTIONS = %w[all version overview processes queues]
|
|
10
|
+
COL_PAD = 2
|
|
11
|
+
|
|
12
|
+
def display(section = nil)
|
|
13
|
+
section ||= "all"
|
|
14
|
+
unless VALID_SECTIONS.include? section
|
|
15
|
+
puts "I don't know how to check the status of '#{section}'!"
|
|
16
|
+
puts "Try one of these: #{VALID_SECTIONS.join(", ")}"
|
|
17
|
+
return
|
|
18
|
+
end
|
|
19
|
+
send(section)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def all
|
|
23
|
+
version
|
|
24
|
+
puts
|
|
25
|
+
overview
|
|
26
|
+
puts
|
|
27
|
+
processes
|
|
28
|
+
puts
|
|
29
|
+
queues
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def version
|
|
33
|
+
puts "Sidekiq #{Sidekiq::VERSION}"
|
|
34
|
+
puts Time.now.utc
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def overview
|
|
38
|
+
puts "---- Overview ----"
|
|
39
|
+
puts " Processed: #{delimit stats.processed}"
|
|
40
|
+
puts " Failed: #{delimit stats.failed}"
|
|
41
|
+
puts " Busy: #{delimit stats.workers_size}"
|
|
42
|
+
puts " Enqueued: #{delimit stats.enqueued}"
|
|
43
|
+
puts " Retries: #{delimit stats.retry_size}"
|
|
44
|
+
puts " Scheduled: #{delimit stats.scheduled_size}"
|
|
45
|
+
puts " Dead: #{delimit stats.dead_size}"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def processes
|
|
49
|
+
puts "---- Processes (#{process_set.size}) ----"
|
|
50
|
+
process_set.each_with_index do |process, index|
|
|
51
|
+
# Keep compatibility with legacy versions since we don't want to break sidekiqmon during rolling upgrades or downgrades.
|
|
52
|
+
queues =
|
|
53
|
+
if process["capsules"] # 8.0.6+
|
|
54
|
+
process["capsules"].values.map { |x| x["weights"].keys.join(", ") }
|
|
55
|
+
elsif process["weights"]
|
|
56
|
+
process["weights"].sort_by { |queue| queue[0] }.map { |capsule| capsule.map { |name, weight| (weight > 0) ? "#{name}: #{weight}" : name }.join(", ") }
|
|
57
|
+
else
|
|
58
|
+
process["queues"].sort
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
puts "#{process["identity"]} #{tags_for(process)}"
|
|
62
|
+
puts " Started: #{Time.at(process["started_at"])} (#{time_ago(process["started_at"])})"
|
|
63
|
+
puts " Threads: #{process["concurrency"]} (#{process["busy"]} busy)"
|
|
64
|
+
puts " Queues: #{split_multiline(queues, pad: 11)}"
|
|
65
|
+
puts " Version: #{process["version"] || "Unknown"}" if process["version"] != Sidekiq::VERSION
|
|
66
|
+
puts "" unless (index + 1) == process_set.size
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def queues
|
|
71
|
+
puts "---- Queues (#{queue_data.size}) ----"
|
|
72
|
+
columns = {
|
|
73
|
+
name: [:ljust, (["name"] + queue_data.map(&:name)).map(&:length).max + COL_PAD],
|
|
74
|
+
size: [:rjust, (["size"] + queue_data.map(&:size)).map(&:length).max + COL_PAD],
|
|
75
|
+
latency: [:rjust, (["latency"] + queue_data.map(&:latency)).map(&:length).max + COL_PAD]
|
|
76
|
+
}
|
|
77
|
+
columns.each { |col, (dir, width)| print col.to_s.upcase.public_send(dir, width) }
|
|
78
|
+
puts
|
|
79
|
+
queue_data.each do |q|
|
|
80
|
+
columns.each do |col, (dir, width)|
|
|
81
|
+
print q.send(col).public_send(dir, width)
|
|
82
|
+
end
|
|
83
|
+
puts
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def delimit(number)
|
|
90
|
+
number.to_s.reverse.scan(/.{1,3}/).join(",").reverse
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def split_multiline(values, opts = {})
|
|
94
|
+
return "none" unless values
|
|
95
|
+
pad = opts[:pad] || 0
|
|
96
|
+
max_length = opts[:max_length] || (80 - pad)
|
|
97
|
+
out = []
|
|
98
|
+
line = +""
|
|
99
|
+
values.each do |value|
|
|
100
|
+
if (line.length + value.length) > max_length
|
|
101
|
+
out << line
|
|
102
|
+
line = " " * pad
|
|
103
|
+
end
|
|
104
|
+
line << value + "; "
|
|
105
|
+
end
|
|
106
|
+
out << line[0..-3]
|
|
107
|
+
out.join("\n")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def tags_for(process)
|
|
111
|
+
tags = [
|
|
112
|
+
process["tag"],
|
|
113
|
+
process["labels"],
|
|
114
|
+
((process["quiet"] == "true") ? "quiet" : nil)
|
|
115
|
+
].flatten.compact
|
|
116
|
+
tags.any? ? "[#{tags.join("] [")}]" : nil
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def time_ago(timestamp)
|
|
120
|
+
seconds = Time.now - Time.at(timestamp)
|
|
121
|
+
return "just now" if seconds < 60
|
|
122
|
+
return "a minute ago" if seconds < 120
|
|
123
|
+
return "#{seconds.floor / 60} minutes ago" if seconds < 3600
|
|
124
|
+
return "an hour ago" if seconds < 7200
|
|
125
|
+
"#{seconds.floor / 60 / 60} hours ago"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
QUEUE_STRUCT = Struct.new(:name, :size, :latency)
|
|
129
|
+
def queue_data
|
|
130
|
+
@queue_data ||= Sidekiq::Queue.all.map { |q|
|
|
131
|
+
QUEUE_STRUCT.new(q.name, q.size.to_s, sprintf("%#.2f", q.latency))
|
|
132
|
+
}
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def process_set
|
|
136
|
+
@process_set ||= Sidekiq::ProcessSet.new
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def stats
|
|
140
|
+
@stats ||= Sidekiq::Stats.new
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sidekiq
|
|
4
|
+
module Paginator
|
|
5
|
+
TYPE_CACHE = {
|
|
6
|
+
"dead" => "zset",
|
|
7
|
+
"retry" => "zset",
|
|
8
|
+
"schedule" => "zset"
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
def page(key, pageidx = 1, page_size = 25, opts = nil)
|
|
12
|
+
current_page = (pageidx.to_i < 1) ? 1 : pageidx.to_i
|
|
13
|
+
pageidx = current_page - 1
|
|
14
|
+
total_size = 0
|
|
15
|
+
items = []
|
|
16
|
+
starting = pageidx * page_size
|
|
17
|
+
ending = starting + page_size - 1
|
|
18
|
+
|
|
19
|
+
Sidekiq.redis do |conn|
|
|
20
|
+
# horrible, think you can make this cleaner?
|
|
21
|
+
type = TYPE_CACHE[key]
|
|
22
|
+
if type
|
|
23
|
+
elsif key.start_with?("queue:")
|
|
24
|
+
type = TYPE_CACHE[key] = "list"
|
|
25
|
+
else
|
|
26
|
+
type = conn.type(key)
|
|
27
|
+
TYPE_CACHE[key] = type unless type == "none"
|
|
28
|
+
end
|
|
29
|
+
rev = opts && opts[:reverse]
|
|
30
|
+
|
|
31
|
+
case type
|
|
32
|
+
when "zset"
|
|
33
|
+
total_size, items = conn.multi { |transaction|
|
|
34
|
+
transaction.zcard(key)
|
|
35
|
+
if rev
|
|
36
|
+
transaction.zrange(key, starting, ending, "REV", "withscores")
|
|
37
|
+
else
|
|
38
|
+
transaction.zrange(key, starting, ending, "withscores")
|
|
39
|
+
end
|
|
40
|
+
}
|
|
41
|
+
[current_page, total_size, items]
|
|
42
|
+
when "list"
|
|
43
|
+
total_size, items = conn.multi { |transaction|
|
|
44
|
+
transaction.llen(key)
|
|
45
|
+
if rev
|
|
46
|
+
transaction.lrange(key, -ending - 1, -starting - 1)
|
|
47
|
+
else
|
|
48
|
+
transaction.lrange(key, starting, ending)
|
|
49
|
+
end
|
|
50
|
+
}
|
|
51
|
+
items.reverse! if rev
|
|
52
|
+
[current_page, total_size, items]
|
|
53
|
+
when "none"
|
|
54
|
+
[1, 0, []]
|
|
55
|
+
else
|
|
56
|
+
raise "can't page a #{type}"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def page_items(items, pageidx = 1, page_size = 25)
|
|
62
|
+
current_page = (pageidx.to_i < 1) ? 1 : pageidx.to_i
|
|
63
|
+
pageidx = current_page - 1
|
|
64
|
+
starting = pageidx * page_size
|
|
65
|
+
items = items.to_a
|
|
66
|
+
total_size = items.size
|
|
67
|
+
if starting > total_size
|
|
68
|
+
starting = 0
|
|
69
|
+
current_page = 1
|
|
70
|
+
end
|
|
71
|
+
[current_page, total_size, items[starting, page_size]]
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sidekiq/fetch"
|
|
4
|
+
require "sidekiq/job_logger"
|
|
5
|
+
require "sidekiq/job_retry"
|
|
6
|
+
require "sidekiq/profiler"
|
|
7
|
+
|
|
8
|
+
module Sidekiq
|
|
9
|
+
##
|
|
10
|
+
# The Processor is a standalone thread which:
|
|
11
|
+
#
|
|
12
|
+
# 1. fetches a job from Redis
|
|
13
|
+
# 2. executes the job
|
|
14
|
+
# a. instantiate the job class
|
|
15
|
+
# b. run the middleware chain
|
|
16
|
+
# c. call #perform
|
|
17
|
+
#
|
|
18
|
+
# A Processor can exit due to shutdown or due to
|
|
19
|
+
# an error during job execution.
|
|
20
|
+
#
|
|
21
|
+
# If an error occurs in the job execution, the
|
|
22
|
+
# Processor calls the Manager to create a new one
|
|
23
|
+
# to replace itself and exits.
|
|
24
|
+
#
|
|
25
|
+
class Processor
|
|
26
|
+
include Sidekiq::Component
|
|
27
|
+
|
|
28
|
+
attr_reader :thread
|
|
29
|
+
attr_reader :job
|
|
30
|
+
attr_reader :capsule
|
|
31
|
+
|
|
32
|
+
def initialize(capsule, &block)
|
|
33
|
+
@config = @capsule = capsule
|
|
34
|
+
@callback = block
|
|
35
|
+
@down = false
|
|
36
|
+
@done = false
|
|
37
|
+
@job = nil
|
|
38
|
+
@thread = nil
|
|
39
|
+
@reloader = Sidekiq.default_configuration[:reloader]
|
|
40
|
+
@job_logger = (capsule.config[:job_logger] || Sidekiq::JobLogger).new(capsule.config)
|
|
41
|
+
@retrier = Sidekiq::JobRetry.new(capsule)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def terminate(wait = false)
|
|
45
|
+
@done = true
|
|
46
|
+
return unless @thread
|
|
47
|
+
@thread.value if wait
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def kill(wait = false)
|
|
51
|
+
@done = true
|
|
52
|
+
return unless @thread
|
|
53
|
+
# unlike the other actors, terminate does not wait
|
|
54
|
+
# for the thread to finish because we don't know how
|
|
55
|
+
# long the job will take to finish. Instead we
|
|
56
|
+
# provide a `kill` method to call after the shutdown
|
|
57
|
+
# timeout passes.
|
|
58
|
+
@thread.raise ::Sidekiq::Shutdown
|
|
59
|
+
@thread.value if wait
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def stopping?
|
|
63
|
+
@done
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def start
|
|
67
|
+
@thread ||= safe_thread("#{config.name}/processor", &method(:run))
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def run
|
|
73
|
+
# By setting this thread-local, Sidekiq.redis will access +Sidekiq::Capsule#redis_pool+
|
|
74
|
+
# instead of the global pool in +Sidekiq::Config#redis_pool+.
|
|
75
|
+
Thread.current[:sidekiq_capsule] = @capsule
|
|
76
|
+
|
|
77
|
+
process_one until @done
|
|
78
|
+
@callback.call(self)
|
|
79
|
+
rescue Sidekiq::Shutdown
|
|
80
|
+
@callback.call(self)
|
|
81
|
+
rescue Exception => ex
|
|
82
|
+
@callback.call(self, ex)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def process_one(&block)
|
|
86
|
+
@job = fetch
|
|
87
|
+
process(@job) if @job
|
|
88
|
+
@job = nil
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def get_one
|
|
92
|
+
uow = capsule.fetcher.retrieve_work
|
|
93
|
+
if @down
|
|
94
|
+
logger.info { "Redis is online, #{::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @down} sec downtime" }
|
|
95
|
+
@down = nil
|
|
96
|
+
end
|
|
97
|
+
uow
|
|
98
|
+
rescue Sidekiq::Shutdown
|
|
99
|
+
rescue => ex
|
|
100
|
+
handle_fetch_exception(ex)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def fetch
|
|
104
|
+
j = get_one
|
|
105
|
+
if j && @done
|
|
106
|
+
j.requeue
|
|
107
|
+
nil
|
|
108
|
+
else
|
|
109
|
+
j
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def handle_fetch_exception(ex)
|
|
114
|
+
unless @down
|
|
115
|
+
@down = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
|
116
|
+
handle_exception(ex)
|
|
117
|
+
end
|
|
118
|
+
sleep(1)
|
|
119
|
+
nil
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def profile(job, &block)
|
|
123
|
+
return yield unless job["profile"]
|
|
124
|
+
Sidekiq::Profiler.new(config).call(job, &block)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def dispatch(job_hash, queue, jobstr)
|
|
128
|
+
# since middleware can mutate the job hash
|
|
129
|
+
# we need to clone it to report the original
|
|
130
|
+
# job structure to the Web UI
|
|
131
|
+
# or to push back to redis when retrying.
|
|
132
|
+
# To avoid costly and, most of the time, useless cloning here,
|
|
133
|
+
# we pass original String of JSON to respected methods
|
|
134
|
+
# to re-parse it there if we need access to the original, untouched job
|
|
135
|
+
|
|
136
|
+
@job_logger.prepare(job_hash) do
|
|
137
|
+
@retrier.global(jobstr, queue) do
|
|
138
|
+
@job_logger.call(job_hash, queue) do
|
|
139
|
+
stats(jobstr, queue) do
|
|
140
|
+
profile(job_hash) do
|
|
141
|
+
# Rails 5 requires a Reloader to wrap code execution. In order to
|
|
142
|
+
# constantize the worker and instantiate an instance, we have to call
|
|
143
|
+
# the Reloader. It handles code loading, db connection management, etc.
|
|
144
|
+
# Effectively this block denotes a "unit of work" to Rails.
|
|
145
|
+
@reloader.call do
|
|
146
|
+
klass = Object.const_get(job_hash["class"])
|
|
147
|
+
instance = klass.new
|
|
148
|
+
instance.jid = job_hash["jid"]
|
|
149
|
+
instance._context = self
|
|
150
|
+
@retrier.local(instance, jobstr, queue) do
|
|
151
|
+
yield instance
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
IGNORE_SHUTDOWN_INTERRUPTS = {Sidekiq::Shutdown => :never}
|
|
162
|
+
private_constant :IGNORE_SHUTDOWN_INTERRUPTS
|
|
163
|
+
ALLOW_SHUTDOWN_INTERRUPTS = {Sidekiq::Shutdown => :immediate}
|
|
164
|
+
private_constant :ALLOW_SHUTDOWN_INTERRUPTS
|
|
165
|
+
|
|
166
|
+
def process(uow)
|
|
167
|
+
jobstr = uow.job
|
|
168
|
+
queue = uow.queue_name
|
|
169
|
+
|
|
170
|
+
# Treat malformed JSON as a special case: job goes straight to the morgue.
|
|
171
|
+
job_hash = nil
|
|
172
|
+
begin
|
|
173
|
+
job_hash = Sidekiq.load_json(jobstr)
|
|
174
|
+
rescue => ex
|
|
175
|
+
now = Time.now.to_f
|
|
176
|
+
redis do |conn|
|
|
177
|
+
conn.multi do |xa|
|
|
178
|
+
xa.zadd("dead", now.to_s, jobstr)
|
|
179
|
+
xa.zremrangebyscore("dead", "-inf", now - @capsule.config[:dead_timeout_in_seconds])
|
|
180
|
+
xa.zremrangebyrank("dead", 0, - @capsule.config[:dead_max_jobs])
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
handle_exception(ex, {context: "Invalid JSON for job", jobstr: jobstr})
|
|
184
|
+
return uow.acknowledge
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
ack = false
|
|
188
|
+
Thread.handle_interrupt(IGNORE_SHUTDOWN_INTERRUPTS) do
|
|
189
|
+
Thread.handle_interrupt(ALLOW_SHUTDOWN_INTERRUPTS) do
|
|
190
|
+
dispatch(job_hash, queue, jobstr) do |instance|
|
|
191
|
+
config.server_middleware.invoke(instance, job_hash, queue) do
|
|
192
|
+
execute_job(instance, job_hash["args"])
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
ack = true
|
|
196
|
+
rescue Sidekiq::Shutdown
|
|
197
|
+
# Had to force kill this job because it didn't finish
|
|
198
|
+
# within the timeout. Don't acknowledge the work since
|
|
199
|
+
# we didn't properly finish it.
|
|
200
|
+
rescue Sidekiq::JobRetry::Skip => s
|
|
201
|
+
# Skip means we handled this error elsewhere. We don't
|
|
202
|
+
# need to log or report the error.
|
|
203
|
+
ack = true
|
|
204
|
+
raise s
|
|
205
|
+
rescue Sidekiq::JobRetry::Handled => h
|
|
206
|
+
# this is the common case: job raised error and Sidekiq::JobRetry::Handled
|
|
207
|
+
# signals that we created a retry successfully. We can acknowledge the job.
|
|
208
|
+
ack = true
|
|
209
|
+
e = h.cause || h
|
|
210
|
+
handle_exception(e, {context: "Job raised exception", job: job_hash})
|
|
211
|
+
raise e
|
|
212
|
+
rescue Exception => ex
|
|
213
|
+
# Unexpected error! This is very bad and indicates an exception that got past
|
|
214
|
+
# the retry subsystem (e.g. network partition). We won't acknowledge the job
|
|
215
|
+
# so it can be rescued when using Sidekiq Pro.
|
|
216
|
+
handle_exception(ex, {context: "Internal exception!", job: job_hash, jobstr: jobstr})
|
|
217
|
+
raise ex
|
|
218
|
+
end
|
|
219
|
+
ensure
|
|
220
|
+
if ack
|
|
221
|
+
uow.acknowledge
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def execute_job(instance, cloned_args)
|
|
227
|
+
instance.perform(*cloned_args)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Ruby doesn't provide atomic counters out of the box so we'll
|
|
231
|
+
# implement something simple ourselves.
|
|
232
|
+
# https://bugs.ruby-lang.org/issues/14706
|
|
233
|
+
class Counter
|
|
234
|
+
def initialize
|
|
235
|
+
@value = 0
|
|
236
|
+
@lock = Mutex.new
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def incr(amount = 1)
|
|
240
|
+
@lock.synchronize { @value += amount }
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def reset
|
|
244
|
+
@lock.synchronize {
|
|
245
|
+
val = @value
|
|
246
|
+
@value = 0
|
|
247
|
+
val
|
|
248
|
+
}
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# jruby's Hash implementation is not threadsafe, so we wrap it in a mutex here
|
|
253
|
+
class SharedWorkState
|
|
254
|
+
def initialize
|
|
255
|
+
@work_state = {}
|
|
256
|
+
@lock = Mutex.new
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def set(tid, hash)
|
|
260
|
+
@lock.synchronize { @work_state[tid] = hash }
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def delete(tid)
|
|
264
|
+
@lock.synchronize { @work_state.delete(tid) }
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def dup
|
|
268
|
+
@lock.synchronize { @work_state.dup }
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def size
|
|
272
|
+
@lock.synchronize { @work_state.size }
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def clear
|
|
276
|
+
@lock.synchronize { @work_state.clear }
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
PROCESSED = Counter.new
|
|
281
|
+
FAILURE = Counter.new
|
|
282
|
+
WORK_STATE = SharedWorkState.new
|
|
283
|
+
|
|
284
|
+
def stats(jobstr, queue)
|
|
285
|
+
WORK_STATE.set(tid, {queue: queue, payload: jobstr, run_at: Time.now.to_i})
|
|
286
|
+
|
|
287
|
+
begin
|
|
288
|
+
yield
|
|
289
|
+
rescue Exception
|
|
290
|
+
FAILURE.incr
|
|
291
|
+
raise
|
|
292
|
+
ensure
|
|
293
|
+
WORK_STATE.delete(tid)
|
|
294
|
+
PROCESSED.incr
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
end
|