sidekiq 6.2.2 → 8.1.5

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.
Files changed (181) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +726 -11
  3. data/LICENSE.txt +9 -0
  4. data/README.md +70 -39
  5. data/bin/kiq +17 -0
  6. data/bin/lint-herb +13 -0
  7. data/bin/multi_queue_bench +271 -0
  8. data/bin/sidekiq +4 -9
  9. data/bin/sidekiqload +214 -115
  10. data/bin/sidekiqmon +4 -1
  11. data/bin/webload +69 -0
  12. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +124 -0
  13. data/lib/generators/sidekiq/job_generator.rb +71 -0
  14. data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +3 -3
  15. data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
  16. data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
  17. data/lib/sidekiq/api.rb +729 -264
  18. data/lib/sidekiq/capsule.rb +135 -0
  19. data/lib/sidekiq/cli.rb +124 -100
  20. data/lib/sidekiq/client.rb +153 -106
  21. data/lib/sidekiq/component.rb +132 -0
  22. data/lib/sidekiq/config.rb +320 -0
  23. data/lib/sidekiq/deploy.rb +64 -0
  24. data/lib/sidekiq/embedded.rb +64 -0
  25. data/lib/sidekiq/fetch.rb +27 -26
  26. data/lib/sidekiq/iterable_job.rb +56 -0
  27. data/lib/sidekiq/job/interrupt_handler.rb +24 -0
  28. data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
  29. data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
  30. data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
  31. data/lib/sidekiq/job/iterable.rb +322 -0
  32. data/lib/sidekiq/job.rb +397 -5
  33. data/lib/sidekiq/job_logger.rb +23 -32
  34. data/lib/sidekiq/job_retry.rb +141 -68
  35. data/lib/sidekiq/job_util.rb +113 -0
  36. data/lib/sidekiq/launcher.rb +122 -98
  37. data/lib/sidekiq/loader.rb +57 -0
  38. data/lib/sidekiq/logger.rb +27 -106
  39. data/lib/sidekiq/manager.rb +41 -43
  40. data/lib/sidekiq/metrics/query.rb +184 -0
  41. data/lib/sidekiq/metrics/shared.rb +109 -0
  42. data/lib/sidekiq/metrics/tracking.rb +153 -0
  43. data/lib/sidekiq/middleware/chain.rb +96 -51
  44. data/lib/sidekiq/middleware/current_attributes.rb +120 -0
  45. data/lib/sidekiq/middleware/i18n.rb +8 -4
  46. data/lib/sidekiq/middleware/modules.rb +23 -0
  47. data/lib/sidekiq/monitor.rb +16 -6
  48. data/lib/sidekiq/paginator.rb +37 -10
  49. data/lib/sidekiq/processor.rb +105 -87
  50. data/lib/sidekiq/profiler.rb +73 -0
  51. data/lib/sidekiq/rails.rb +49 -36
  52. data/lib/sidekiq/redis_client_adapter.rb +117 -0
  53. data/lib/sidekiq/redis_connection.rb +55 -86
  54. data/lib/sidekiq/ring_buffer.rb +32 -0
  55. data/lib/sidekiq/scheduled.rb +106 -50
  56. data/lib/sidekiq/systemd.rb +2 -0
  57. data/lib/sidekiq/test_api.rb +331 -0
  58. data/lib/sidekiq/testing/inline.rb +2 -30
  59. data/lib/sidekiq/testing.rb +2 -342
  60. data/lib/sidekiq/transaction_aware_client.rb +59 -0
  61. data/lib/sidekiq/tui/controls.rb +53 -0
  62. data/lib/sidekiq/tui/filtering.rb +53 -0
  63. data/lib/sidekiq/tui/tabs/base_tab.rb +204 -0
  64. data/lib/sidekiq/tui/tabs/busy.rb +118 -0
  65. data/lib/sidekiq/tui/tabs/dead.rb +19 -0
  66. data/lib/sidekiq/tui/tabs/home.rb +144 -0
  67. data/lib/sidekiq/tui/tabs/metrics.rb +131 -0
  68. data/lib/sidekiq/tui/tabs/queues.rb +95 -0
  69. data/lib/sidekiq/tui/tabs/retries.rb +19 -0
  70. data/lib/sidekiq/tui/tabs/scheduled.rb +19 -0
  71. data/lib/sidekiq/tui/tabs/set_tab.rb +96 -0
  72. data/lib/sidekiq/tui/tabs.rb +15 -0
  73. data/lib/sidekiq/tui.rb +382 -0
  74. data/lib/sidekiq/version.rb +6 -1
  75. data/lib/sidekiq/web/action.rb +149 -64
  76. data/lib/sidekiq/web/application.rb +376 -268
  77. data/lib/sidekiq/web/config.rb +117 -0
  78. data/lib/sidekiq/web/helpers.rb +213 -87
  79. data/lib/sidekiq/web/router.rb +61 -74
  80. data/lib/sidekiq/web.rb +71 -100
  81. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  82. data/lib/sidekiq.rb +95 -196
  83. data/sidekiq.gemspec +14 -11
  84. data/web/assets/images/logo.png +0 -0
  85. data/web/assets/images/status.png +0 -0
  86. data/web/assets/javascripts/application.js +171 -57
  87. data/web/assets/javascripts/base-charts.js +120 -0
  88. data/web/assets/javascripts/chart.min.js +13 -0
  89. data/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
  90. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  91. data/web/assets/javascripts/dashboard-charts.js +194 -0
  92. data/web/assets/javascripts/dashboard.js +41 -274
  93. data/web/assets/javascripts/metrics.js +280 -0
  94. data/web/assets/stylesheets/style.css +776 -0
  95. data/web/locales/ar.yml +72 -70
  96. data/web/locales/cs.yml +64 -62
  97. data/web/locales/da.yml +62 -53
  98. data/web/locales/de.yml +67 -65
  99. data/web/locales/el.yml +45 -24
  100. data/web/locales/en.yml +93 -69
  101. data/web/locales/es.yml +91 -68
  102. data/web/locales/fa.yml +67 -65
  103. data/web/locales/fr.yml +82 -67
  104. data/web/locales/gd.yml +110 -0
  105. data/web/locales/he.yml +67 -64
  106. data/web/locales/hi.yml +61 -59
  107. data/web/locales/it.yml +94 -54
  108. data/web/locales/ja.yml +74 -68
  109. data/web/locales/ko.yml +54 -52
  110. data/web/locales/lt.yml +68 -66
  111. data/web/locales/nb.yml +63 -61
  112. data/web/locales/nl.yml +54 -52
  113. data/web/locales/pl.yml +47 -45
  114. data/web/locales/{pt-br.yml → pt-BR.yml} +85 -56
  115. data/web/locales/pt.yml +53 -51
  116. data/web/locales/ru.yml +69 -66
  117. data/web/locales/sv.yml +55 -53
  118. data/web/locales/ta.yml +62 -60
  119. data/web/locales/tr.yml +102 -0
  120. data/web/locales/uk.yml +87 -61
  121. data/web/locales/ur.yml +66 -64
  122. data/web/locales/vi.yml +69 -67
  123. data/web/locales/zh-CN.yml +107 -0
  124. data/web/locales/{zh-tw.yml → zh-TW.yml} +44 -9
  125. data/web/views/_footer.html.erb +32 -0
  126. data/web/views/_job_info.html.erb +115 -0
  127. data/web/views/_metrics_period_select.html.erb +15 -0
  128. data/web/views/_nav.html.erb +45 -0
  129. data/web/views/_paging.html.erb +26 -0
  130. data/web/views/_poll_link.html.erb +4 -0
  131. data/web/views/_summary.html.erb +40 -0
  132. data/web/views/busy.html.erb +151 -0
  133. data/web/views/dashboard.html.erb +104 -0
  134. data/web/views/dead.html.erb +38 -0
  135. data/web/views/filtering.html.erb +6 -0
  136. data/web/views/layout.html.erb +26 -0
  137. data/web/views/metrics.html.erb +85 -0
  138. data/web/views/metrics_for_job.html.erb +58 -0
  139. data/web/views/morgue.html.erb +69 -0
  140. data/web/views/profiles.html.erb +43 -0
  141. data/web/views/queue.html.erb +57 -0
  142. data/web/views/queues.html.erb +46 -0
  143. data/web/views/retries.html.erb +77 -0
  144. data/web/views/retry.html.erb +39 -0
  145. data/web/views/scheduled.html.erb +64 -0
  146. data/web/views/{scheduled_job_info.erb → scheduled_job_info.html.erb} +3 -3
  147. metadata +130 -61
  148. data/LICENSE +0 -9
  149. data/lib/generators/sidekiq/worker_generator.rb +0 -57
  150. data/lib/sidekiq/delay.rb +0 -41
  151. data/lib/sidekiq/exception_handler.rb +0 -27
  152. data/lib/sidekiq/extensions/action_mailer.rb +0 -48
  153. data/lib/sidekiq/extensions/active_record.rb +0 -43
  154. data/lib/sidekiq/extensions/class_methods.rb +0 -43
  155. data/lib/sidekiq/extensions/generic_proxy.rb +0 -33
  156. data/lib/sidekiq/util.rb +0 -95
  157. data/lib/sidekiq/web/csrf_protection.rb +0 -180
  158. data/lib/sidekiq/worker.rb +0 -244
  159. data/web/assets/stylesheets/application-dark.css +0 -147
  160. data/web/assets/stylesheets/application-rtl.css +0 -246
  161. data/web/assets/stylesheets/application.css +0 -1053
  162. data/web/assets/stylesheets/bootstrap-rtl.min.css +0 -9
  163. data/web/assets/stylesheets/bootstrap.css +0 -5
  164. data/web/locales/zh-cn.yml +0 -68
  165. data/web/views/_footer.erb +0 -20
  166. data/web/views/_job_info.erb +0 -89
  167. data/web/views/_nav.erb +0 -52
  168. data/web/views/_paging.erb +0 -23
  169. data/web/views/_poll_link.erb +0 -7
  170. data/web/views/_status.erb +0 -4
  171. data/web/views/_summary.erb +0 -40
  172. data/web/views/busy.erb +0 -132
  173. data/web/views/dashboard.erb +0 -83
  174. data/web/views/dead.erb +0 -34
  175. data/web/views/layout.erb +0 -42
  176. data/web/views/morgue.erb +0 -78
  177. data/web/views/queue.erb +0 -55
  178. data/web/views/queues.erb +0 -38
  179. data/web/views/retries.erb +0 -83
  180. data/web/views/retry.erb +0 -34
  181. data/web/views/scheduled.erb +0 -57
data/bin/sidekiqload CHANGED
@@ -1,87 +1,80 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ #
4
+ # bin/sidekiqload is a helpful script to load test and
5
+ # performance tune Sidekiq's core. It creates 500,000 no-op
6
+ # jobs and executes them as fast as possible.
7
+ # Example Usage:
8
+ #
9
+ # > RUBY_YJIT_ENABLE=1 LATENCY=0 THREADS=10 bin/sidekiqload
10
+ # Result: Done, 500000 jobs in 20.264945 sec, 24673 jobs/sec
11
+ #
12
+ # Use LATENCY=1 to get a more real world network setup
13
+ # but you'll need to setup and start toxiproxy as noted below.
14
+ #
15
+ # Use AJ=1 to test ActiveJob instead of plain old Sidekiq::Jobs so
16
+ # you can see the runtime performance difference between the two APIs.
17
+ #
18
+ # None of this script is considered a public API and may change over time.
19
+ #
20
+
3
21
  # Quiet some warnings we see when running in warning mode:
4
22
  # RUBYOPT=-w bundle exec sidekiq
5
23
  $TESTING = false
24
+ puts RUBY_DESCRIPTION
25
+ puts(%w[THREADS LATENCY AJ PROFILE].map { |x| "#{x}: #{ENV[x] || "nil"}" }.join(", "))
6
26
 
7
- #require 'ruby-prof'
8
- require 'bundler/setup'
27
+ require "ruby-prof" if ENV["PROFILE"]
28
+ require "bundler/setup"
9
29
  Bundler.require(:default, :load_test)
10
30
 
11
- require_relative '../lib/sidekiq/cli'
12
- require_relative '../lib/sidekiq/launcher'
31
+ latency = Integer(ENV["LATENCY"] || 0)
32
+ if latency > 0
33
+ # brew tap shopify/shopify
34
+ # brew install toxiproxy
35
+ # run `toxiproxy-server` in a separate terminal window.
36
+ require "toxiproxy"
37
+ # simulate a non-localhost network for realer-world conditions.
38
+ # adding 1ms of network latency has an ENORMOUS impact on benchmarks
39
+ Toxiproxy.populate([{
40
+ name: "redis",
41
+ listen: "127.0.0.1:6380",
42
+ upstream: "127.0.0.1:6379"
43
+ }])
44
+ end
13
45
 
14
- include Sidekiq::Util
46
+ if ENV["AJ"]
47
+ require "active_job"
48
+ puts "Using ActiveJob #{ActiveJob::VERSION::STRING}"
49
+ ActiveJob::Base.queue_adapter = :sidekiq
50
+ ActiveJob::Base.logger.level = Logger::WARN
15
51
 
16
- Sidekiq.configure_server do |config|
17
- config.options[:concurrency] = 10
18
- config.redis = { db: 13, port: 6380, driver: :hiredis }
19
- config.options[:queues] << 'default'
20
- config.logger.level = Logger::ERROR
21
- config.average_scheduled_poll_interval = 2
22
- config.reliable! if defined?(Sidekiq::Pro)
52
+ class LoadJob < ActiveJob::Base
53
+ def perform(string, idx, hash, ts = nil)
54
+ puts(Time.now.to_f - ts) if !ts.nil?
55
+ end
56
+ end
23
57
  end
24
58
 
25
59
  class LoadWorker
26
- include Sidekiq::Worker
60
+ include Sidekiq::Job
61
+ $count = 0
62
+ $lock = Mutex.new
63
+
27
64
  sidekiq_options retry: 1
28
65
  sidekiq_retry_in do |x|
29
66
  1
30
67
  end
31
68
 
32
- def perform(idx, ts=nil)
33
- puts(Time.now.to_f - ts) if ts != nil
34
- #raise idx.to_s if idx % 100 == 1
35
- end
36
- end
37
-
38
- # brew tap shopify/shopify
39
- # brew install toxiproxy
40
- # gem install toxiproxy
41
- # run `toxiproxy-server` in a separate terminal window.
42
- require 'toxiproxy'
43
- # simulate a non-localhost network for realer-world conditions.
44
- # adding 1ms of network latency has an ENORMOUS impact on benchmarks
45
- Toxiproxy.populate([{
46
- "name": "redis",
47
- "listen": "127.0.0.1:6380",
48
- "upstream": "127.0.0.1:6379"
49
- }])
50
-
51
- self_read, self_write = IO.pipe
52
- %w(INT TERM TSTP TTIN).each do |sig|
53
- begin
54
- trap sig do
55
- self_write.puts(sig)
56
- end
57
- rescue ArgumentError
58
- puts "Signal #{sig} not supported"
59
- end
60
- end
61
-
62
- Sidekiq.redis {|c| c.flushdb}
63
- def handle_signal(launcher, sig)
64
- Sidekiq.logger.debug "Got #{sig} signal"
65
- case sig
66
- when 'INT'
67
- # Handle Ctrl-C in JRuby like MRI
68
- # http://jira.codehaus.org/browse/JRUBY-4637
69
- raise Interrupt
70
- when 'TERM'
71
- # Heroku sends TERM and then waits 30 seconds for process to exit.
72
- raise Interrupt
73
- when 'TSTP'
74
- Sidekiq.logger.info "Received TSTP, no longer accepting new work"
75
- launcher.quiet
76
- when 'TTIN'
77
- Thread.list.each do |thread|
78
- Sidekiq.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread['label']}"
79
- if thread.backtrace
80
- Sidekiq.logger.warn thread.backtrace.join("\n")
81
- else
82
- Sidekiq.logger.warn "<no backtrace available>"
69
+ def perform(string, idx, hash, ts = nil)
70
+ $lock.synchronize do
71
+ $count += 1
72
+ if $count % 100_000 == 0
73
+ logger.warn("#{Time.now} Done #{$count}")
83
74
  end
84
75
  end
76
+ puts(Time.now.to_f - ts) if !ts.nil?
77
+ # raise idx.to_s if idx % 100 == 1
85
78
  end
86
79
  end
87
80
 
@@ -89,69 +82,175 @@ def Process.rss
89
82
  `ps -o rss= -p #{Process.pid}`.chomp.to_i
90
83
  end
91
84
 
92
- iter = 10
93
- count = 10_000
85
+ class Loader
86
+ def initialize
87
+ @iter = ENV["GC"] ? 10 : 500
88
+ @count = Integer(ENV["COUNT"] || 1_000)
89
+ @latency = Integer(ENV["LATENCY"] || 0)
90
+ end
94
91
 
95
- iter.times do
96
- arr = Array.new(count) do
97
- []
92
+ def configure
93
+ @x = Sidekiq.configure_embed do |config|
94
+ config.redis = {db: 13, port: ((@latency > 0) ? 6380 : 6379)}
95
+ config.concurrency = Integer(ENV.fetch("THREADS", "10"))
96
+ # config.redis = { db: 13, port: 6380, driver: :hiredis}
97
+ config.queues = %w[default]
98
+ config.logger.level = Logger::WARN
99
+ config.average_scheduled_poll_interval = 2
100
+ config.reliable! if defined?(Sidekiq::Pro)
101
+ end
102
+
103
+ @self_read, @self_write = IO.pipe
104
+ %w[INT TERM TSTP TTIN].each do |sig|
105
+ trap sig do
106
+ @self_write.puts(sig)
107
+ end
108
+ rescue ArgumentError
109
+ puts "Signal #{sig} not supported"
110
+ end
98
111
  end
99
- count.times do |idx|
100
- arr[idx][0] = idx
112
+
113
+ def handle_signal(sig)
114
+ launcher = @x
115
+ Sidekiq.logger.debug "Got #{sig} signal"
116
+ case sig
117
+ when "INT"
118
+ # Handle Ctrl-C in JRuby like MRI
119
+ # http://jira.codehaus.org/browse/JRUBY-4637
120
+ raise Interrupt
121
+ when "TERM"
122
+ # Heroku sends TERM and then waits 30 seconds for process to exit.
123
+ raise Interrupt
124
+ when "TSTP"
125
+ Sidekiq.logger.info "Received TSTP, no longer accepting new work"
126
+ launcher.quiet
127
+ when "TTIN"
128
+ Thread.list.each do |thread|
129
+ Sidekiq.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread["label"]}"
130
+ if thread.backtrace
131
+ Sidekiq.logger.warn thread.backtrace.join("\n")
132
+ else
133
+ Sidekiq.logger.warn "<no backtrace available>"
134
+ end
135
+ end
136
+ end
101
137
  end
102
- Sidekiq::Client.push_bulk('class' => LoadWorker, 'args' => arr)
103
- end
104
- Sidekiq.logger.error "Created #{count*iter} jobs"
105
138
 
106
- start = Time.now
139
+ def setup
140
+ Sidekiq.logger.warn("Setup RSS: #{Process.rss}")
141
+ Sidekiq.redis { |c| c.flushdb }
142
+ start = Time.now
143
+ if ENV["AJ"]
144
+ @iter.times do
145
+ ActiveJob.perform_all_later(@count.times.map do |idx|
146
+ LoadJob.new("mike", idx, {mike: "bob"})
147
+ end)
148
+ end
149
+ else
150
+ @iter.times do
151
+ arr = Array.new(@count) { |idx| ["string", idx, {"mike" => "bob"}] }
152
+ Sidekiq::Client.push_bulk("class" => LoadWorker, "args" => arr)
153
+ end
154
+ end
155
+ Sidekiq.logger.warn "Created #{@count * @iter} jobs in #{Time.now - start} sec"
156
+ end
157
+
158
+ def monitor
159
+ @monitor = Thread.new do
160
+ GC.start
161
+ loop do
162
+ sleep 0.2
163
+ qsize = Sidekiq.redis do |conn|
164
+ conn.llen "queue:default"
165
+ end
166
+ total = qsize
167
+ if total == 0
168
+ ending = Time.now - @start
169
+ size = @iter * @count
170
+ Sidekiq.logger.warn("Done, #{size} jobs in #{ending} sec, #{(size / ending).to_i} jobs/sec")
171
+ Sidekiq.logger.warn("Ending RSS: #{Process.rss}")
172
+ Sidekiq.logger.warn("Now here's the latency for three jobs")
173
+
174
+ if ENV["AJ"]
175
+ LoadJob.perform_later("", 1, {}, Time.now.to_f)
176
+ LoadJob.perform_later("", 2, {}, Time.now.to_f)
177
+ LoadJob.perform_later("", 3, {}, Time.now.to_f)
178
+ else
179
+ LoadWorker.perform_async("", 1, {}, Time.now.to_f)
180
+ LoadWorker.perform_async("", 2, {}, Time.now.to_f)
181
+ LoadWorker.perform_async("", 3, {}, Time.now.to_f)
182
+ end
107
183
 
108
- Monitoring = Thread.new do
109
- watchdog("monitor thread") do
110
- while true
111
- sleep 0.2
112
- qsize = Sidekiq.redis do |conn|
113
- conn.llen "queue:default"
184
+ sleep 0.1
185
+ @x.stop
186
+ Process.kill("INT", $$)
187
+ break
188
+ end
114
189
  end
115
- total = qsize
116
- #Sidekiq.logger.error("RSS: #{Process.rss} Pending: #{total}")
117
- if total == 0
118
- Sidekiq.logger.error("Done, #{iter * count} jobs in #{Time.now - start} sec")
119
- Sidekiq.logger.error("Now here's the latency for three jobs")
190
+ end
191
+ end
192
+
193
+ def with_latency(latency, &block)
194
+ Sidekiq.logger.warn "Simulating #{latency}ms of latency between Sidekiq and redis"
195
+ if latency > 0
196
+ Toxiproxy[:redis].downstream(:latency, latency: latency).apply(&block)
197
+ else
198
+ yield
199
+ end
200
+ end
120
201
 
121
- LoadWorker.perform_async(1, Time.now.to_f)
122
- LoadWorker.perform_async(2, Time.now.to_f)
123
- LoadWorker.perform_async(3, Time.now.to_f)
202
+ def run(name)
203
+ Sidekiq.logger.warn("Starting #{name}")
204
+ monitor
124
205
 
125
- sleep 0.2
126
- exit(0)
206
+ if ENV["PROFILE"]
207
+ $profile = RubyProf::Profile.new(exclude_threads: [@monitor])
208
+ $profile.start
209
+ elsif ENV["GC"]
210
+ GC.start
211
+ GC.compact
212
+ GC.disable
213
+ Sidekiq.logger.error("GC Start RSS: #{Process.rss}")
214
+ end
215
+ @start = Time.now
216
+ with_latency(@latency) do
217
+ @x.run
218
+
219
+ while (readable_io = IO.select([@self_read]))
220
+ signal = readable_io.first[0].gets.strip
221
+ handle_signal(signal)
127
222
  end
128
223
  end
224
+ # normal
225
+ rescue Interrupt
226
+ rescue => e
227
+ raise e if $DEBUG
228
+ warn e.message
229
+ warn e.backtrace.join("\n")
230
+ exit 1
231
+ ensure
232
+ @x.stop
129
233
  end
130
- end
131
234
 
132
- begin
133
- #RubyProf::exclude_threads = [ Monitoring ]
134
- #RubyProf.start
135
- fire_event(:startup)
136
- Sidekiq.logger.error "Simulating 1ms of latency between Sidekiq and redis"
137
- Toxiproxy[:redis].downstream(:latency, latency: 1).apply do
138
- launcher = Sidekiq::Launcher.new(Sidekiq.options)
139
- launcher.run
140
-
141
- while readable_io = IO.select([self_read])
142
- signal = readable_io.first[0].gets.strip
143
- handle_signal(launcher, signal)
235
+ def done
236
+ Sidekiq.logger.error("GC End RSS: #{Process.rss}") if ENV["GC"]
237
+ if ENV["PROFILE"]
238
+ Sidekiq.logger.error("Profiling...")
239
+ result = $profile.stop
240
+ printer = RubyProf::GraphHtmlPrinter.new(result)
241
+ printer.print(File.new("output.html", "w"), min_percent: 1)
144
242
  end
145
243
  end
146
- rescue SystemExit => e
147
- #Sidekiq.logger.error("Profiling...")
148
- #result = RubyProf.stop
149
- #printer = RubyProf::GraphHtmlPrinter.new(result)
150
- #printer.print(File.new("output.html", "w"), :min_percent => 1)
151
- # normal
152
- rescue => e
153
- raise e if $DEBUG
154
- STDERR.puts e.message
155
- STDERR.puts e.backtrace.join("\n")
156
- exit 1
157
244
  end
245
+
246
+ ll = Loader.new
247
+ ll.configure
248
+
249
+ if ENV["WARM"]
250
+ ll.setup
251
+ ll.run("warmup")
252
+ end
253
+
254
+ ll.setup
255
+ ll.run("load")
256
+ ll.done
data/bin/sidekiqmon CHANGED
@@ -1,6 +1,9 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'sidekiq/monitor'
3
+ require "sidekiq/monitor"
4
+
5
+ # disable the Redis connection pool logging
6
+ Sidekiq.default_configuration.logger.level = :warn
4
7
 
5
8
  section = "all"
6
9
  section = ARGV[0] if ARGV.size == 1
data/bin/webload ADDED
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler'
4
+ Bundler.setup
5
+
6
+ # This skeleton allows you to run Sidekiq::Web page rendering
7
+ # through Vernier for tuning.
8
+ require "sidekiq/web"
9
+ require "rack/test"
10
+ require "vernier"
11
+
12
+ Sidekiq::Web.configure do |config|
13
+ config.middlewares.clear # remove csrf
14
+ end
15
+
16
+ class SomeJob
17
+ include Sidekiq::Job
18
+ end
19
+
20
+ class BenchWeb
21
+ include Rack::Test::Methods
22
+
23
+ def app
24
+ Sidekiq::Web.new
25
+ end
26
+
27
+ def warmup(page = "/scheduled")
28
+ # Sidekiq.redis {|c| c.flushdb }
29
+
30
+ # 100.times do |idx|
31
+ # SomeJob.perform_at(idx, 1, 3, "mike", {"foo" => "bar"})
32
+ # end
33
+
34
+ 100.times do
35
+ get page
36
+ end
37
+ end
38
+
39
+ def load(page = "/scheduled", count = 10_000)
40
+ profile do
41
+ count.times do
42
+ get page
43
+ raise last_response.inspect unless last_response.status == 200
44
+ end
45
+ end
46
+ end
47
+
48
+ def profile(&)
49
+ if ENV["PROF"]
50
+ Vernier.profile(out: "profile.json.gz", &)
51
+ else
52
+ yield
53
+ end
54
+ end
55
+ end
56
+
57
+ def timer(name="block", count = 10_000)
58
+ a = Time.now
59
+ yield count
60
+ b = Time.now
61
+ puts "#{name} in #{b - a} sec"
62
+ end
63
+
64
+ page = "/busy"
65
+ b = BenchWeb.new
66
+ b.warmup(page)
67
+ timer(page) do |count|
68
+ b.load(page, count)
69
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ gem "activejob", ">= 7.0"
5
+ require "active_job"
6
+
7
+ module Sidekiq
8
+ module ActiveJob
9
+ # @api private
10
+ class Wrapper
11
+ include Sidekiq::Job
12
+
13
+ def perform(job_data)
14
+ ::ActiveJob::Base.execute(job_data.merge("provider_job_id" => jid))
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ ActiveSupport.on_load(:active_job) do
21
+ # By including the Options module, we allow AJs to directly control sidekiq features
22
+ # via the *sidekiq_options* class method and, for instance, not use AJ's retry system.
23
+ # AJ retries don't show up in the Sidekiq UI Retries tab, don't save any error data, can't be
24
+ # manually retried, don't automatically die, etc.
25
+ #
26
+ # class SomeJob < ActiveJob::Base
27
+ # queue_as :default
28
+ # sidekiq_options retry: 3, backtrace: 10
29
+ # def perform
30
+ # end
31
+ # end
32
+ include Sidekiq::Job::Options unless respond_to?(:sidekiq_options)
33
+ end
34
+
35
+ # Patch the ActiveJob module
36
+ module ActiveJob
37
+ module QueueAdapters
38
+ # Explicitly remove the implementation existing in older Rails.
39
+ remove_const(:SidekiqAdapter) if const_defined?(:SidekiqAdapter)
40
+
41
+ # Sidekiq adapter for Active Job
42
+ #
43
+ # To use Sidekiq set the queue_adapter config to +:sidekiq+.
44
+ #
45
+ # Rails.application.config.active_job.queue_adapter = :sidekiq
46
+ parent = const_defined?(:AbstractAdapter) ? AbstractAdapter : Object
47
+ class SidekiqAdapter < parent
48
+ @@stopping = false
49
+
50
+ callback = -> { @@stopping = true }
51
+
52
+ Sidekiq.configure_client { |config| config.on(:quiet, &callback) }
53
+ Sidekiq.configure_server { |config| config.on(:quiet, &callback) }
54
+
55
+ # Defines whether enqueuing should happen implicitly to after commit when called
56
+ # from inside a transaction.
57
+ # @api private
58
+ def enqueue_after_transaction_commit?
59
+ true
60
+ end
61
+
62
+ # @api private
63
+ def enqueue(job)
64
+ # NB: Active Job only serializes keys it recognizes. We
65
+ # cannot set arbitrary key/values here.
66
+ options = {wrapped: job.class, queue: job.queue_name}
67
+ options[:profile] = job.profile if job.respond_to?(:profile) && !job.profile.nil?
68
+
69
+ wrapper = Sidekiq::ActiveJob::Wrapper.set(options)
70
+ job.provider_job_id = wrapper.perform_async(job.serialize)
71
+ end
72
+
73
+ # @api private
74
+ def enqueue_at(job, timestamp)
75
+ options = {wrapped: job.class, queue: job.queue_name}
76
+ options[:profile] = job.profile if job.respond_to?(:profile) && !job.profile.nil?
77
+
78
+ job.provider_job_id = Sidekiq::ActiveJob::Wrapper.set(options).perform_at(timestamp, job.serialize)
79
+ end
80
+
81
+ # @api private
82
+ def enqueue_all(jobs)
83
+ enqueued_count = 0
84
+ jobs.group_by(&:class).each do |job_class, same_class_jobs|
85
+ same_class_jobs.group_by(&:queue_name).each do |queue, same_class_and_queue_jobs|
86
+ immediate_jobs, scheduled_jobs = same_class_and_queue_jobs.partition { |job| job.scheduled_at.nil? }
87
+
88
+ if immediate_jobs.any?
89
+ jids = Sidekiq::Client.push_bulk(
90
+ "class" => Sidekiq::ActiveJob::Wrapper,
91
+ "wrapped" => job_class,
92
+ "queue" => queue,
93
+ "args" => immediate_jobs.map { |job| [job.serialize] }
94
+ )
95
+ enqueued_count += jids.compact.size
96
+ end
97
+
98
+ if scheduled_jobs.any?
99
+ jids = Sidekiq::Client.push_bulk(
100
+ "class" => Sidekiq::ActiveJob::Wrapper,
101
+ "wrapped" => job_class,
102
+ "queue" => queue,
103
+ "args" => scheduled_jobs.map { |job| [job.serialize] },
104
+ "at" => scheduled_jobs.map { |job| job.scheduled_at&.to_f }
105
+ )
106
+ enqueued_count += jids.compact.size
107
+ end
108
+ end
109
+ end
110
+ enqueued_count
111
+ end
112
+
113
+ # @api private
114
+ def stopping? = !!@@stopping
115
+
116
+ # Defines a class alias for backwards compatibility with enqueued Active Job jobs.
117
+ # @api private
118
+ JobWrapper = Sidekiq::ActiveJob::Wrapper
119
+ end
120
+ end
121
+ end
122
+ rescue Gem::LoadError
123
+ # ActiveJob not available or version requirement not met
124
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/named_base"
4
+
5
+ module Sidekiq
6
+ module Generators # :nodoc:
7
+ class JobGenerator < ::Rails::Generators::NamedBase # :nodoc:
8
+ desc "This generator creates a Sidekiq Job in app/sidekiq and a corresponding test"
9
+
10
+ check_class_collision suffix: "Job"
11
+
12
+ def self.default_generator_root
13
+ File.dirname(__FILE__)
14
+ end
15
+
16
+ def create_job_file
17
+ template(
18
+ "job.rb.erb",
19
+ File.join(jobs_directory, class_path, "#{file_name}_job.rb")
20
+ )
21
+ end
22
+
23
+ def create_test_file
24
+ return unless test_framework
25
+
26
+ if test_framework == :rspec
27
+ create_job_spec
28
+ else
29
+ create_job_test
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def create_job_spec
36
+ template_file = File.join(
37
+ "spec",
38
+ jobs_directory.gsub("app/", ""),
39
+ class_path,
40
+ "#{file_name}_job_spec.rb"
41
+ )
42
+ template "job_spec.rb.erb", template_file
43
+ end
44
+
45
+ def create_job_test
46
+ template_file = File.join(
47
+ "test",
48
+ jobs_directory.gsub("app/", ""),
49
+ class_path,
50
+ "#{file_name}_job_test.rb"
51
+ )
52
+ template "job_test.rb.erb", template_file
53
+ end
54
+
55
+ def file_name
56
+ @_file_name ||= super.sub(/_?job\z/i, "")
57
+ end
58
+
59
+ def test_framework
60
+ ::Rails.application.config.generators.options[:rails][:test_framework]
61
+ end
62
+
63
+ # Can be set in an initializer or in application configuration
64
+ # with Rails.application.config.generators.options[:sidekiq][:jobs_directory] = "app/jobs"
65
+ # to change the directory that the job files are generated in to.
66
+ def jobs_directory
67
+ ::Rails.application.config.generators.options[:sidekiq].fetch(:jobs_directory, "app/sidekiq")
68
+ end
69
+ end
70
+ end
71
+ end
@@ -1,9 +1,9 @@
1
1
  <% module_namespacing do -%>
2
- class <%= class_name %>Worker
3
- include Sidekiq::Worker
2
+ class <%= class_name %>Job
3
+ include Sidekiq::Job
4
4
 
5
5
  def perform(*args)
6
6
  # Do something
7
7
  end
8
8
  end
9
- <% end -%>
9
+ <% end -%>
@@ -1,6 +1,6 @@
1
1
  require 'rails_helper'
2
2
  <% module_namespacing do -%>
3
- RSpec.describe <%= class_name %>Worker, type: :worker do
3
+ RSpec.describe <%= class_name %>Job, type: :job do
4
4
  pending "add some examples to (or delete) #{__FILE__}"
5
5
  end
6
6
  <% end -%>
@@ -1,6 +1,6 @@
1
1
  require 'test_helper'
2
2
  <% module_namespacing do -%>
3
- class <%= class_name %>WorkerTest < Minitest::Test
3
+ class <%= class_name %>JobTest < Minitest::Test
4
4
  def test_example
5
5
  skip "add some examples to (or delete) #{__FILE__}"
6
6
  end