sidekiq 6.5.12 → 7.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +149 -20
  3. data/README.md +42 -34
  4. data/bin/sidekiq +3 -8
  5. data/bin/sidekiqload +204 -118
  6. data/bin/sidekiqmon +3 -0
  7. data/lib/sidekiq/api.rb +114 -128
  8. data/lib/sidekiq/capsule.rb +127 -0
  9. data/lib/sidekiq/cli.rb +56 -74
  10. data/lib/sidekiq/client.rb +65 -36
  11. data/lib/sidekiq/component.rb +4 -1
  12. data/lib/sidekiq/config.rb +282 -0
  13. data/lib/sidekiq/deploy.rb +62 -0
  14. data/lib/sidekiq/embedded.rb +61 -0
  15. data/lib/sidekiq/fetch.rb +11 -14
  16. data/lib/sidekiq/job.rb +371 -10
  17. data/lib/sidekiq/job_logger.rb +2 -2
  18. data/lib/sidekiq/job_retry.rb +33 -15
  19. data/lib/sidekiq/job_util.rb +51 -15
  20. data/lib/sidekiq/launcher.rb +66 -62
  21. data/lib/sidekiq/logger.rb +1 -26
  22. data/lib/sidekiq/manager.rb +9 -11
  23. data/lib/sidekiq/metrics/query.rb +3 -3
  24. data/lib/sidekiq/metrics/shared.rb +8 -7
  25. data/lib/sidekiq/metrics/tracking.rb +20 -18
  26. data/lib/sidekiq/middleware/chain.rb +19 -18
  27. data/lib/sidekiq/middleware/current_attributes.rb +52 -20
  28. data/lib/sidekiq/monitor.rb +16 -3
  29. data/lib/sidekiq/paginator.rb +1 -1
  30. data/lib/sidekiq/processor.rb +46 -51
  31. data/lib/sidekiq/rails.rb +8 -7
  32. data/lib/sidekiq/redis_client_adapter.rb +10 -69
  33. data/lib/sidekiq/redis_connection.rb +12 -111
  34. data/lib/sidekiq/scheduled.rb +21 -22
  35. data/lib/sidekiq/testing.rb +6 -34
  36. data/lib/sidekiq/transaction_aware_client.rb +4 -5
  37. data/lib/sidekiq/version.rb +2 -1
  38. data/lib/sidekiq/web/action.rb +3 -3
  39. data/lib/sidekiq/web/application.rb +76 -11
  40. data/lib/sidekiq/web/csrf_protection.rb +2 -2
  41. data/lib/sidekiq/web/helpers.rb +39 -24
  42. data/lib/sidekiq/web.rb +17 -16
  43. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  44. data/lib/sidekiq.rb +76 -274
  45. data/sidekiq.gemspec +12 -10
  46. data/web/assets/javascripts/application.js +18 -0
  47. data/web/assets/javascripts/base-charts.js +106 -0
  48. data/web/assets/javascripts/dashboard-charts.js +168 -0
  49. data/web/assets/javascripts/dashboard.js +3 -223
  50. data/web/assets/javascripts/metrics.js +117 -115
  51. data/web/assets/stylesheets/application-dark.css +4 -0
  52. data/web/assets/stylesheets/application-rtl.css +2 -91
  53. data/web/assets/stylesheets/application.css +23 -298
  54. data/web/locales/ar.yml +70 -70
  55. data/web/locales/cs.yml +62 -62
  56. data/web/locales/da.yml +60 -53
  57. data/web/locales/de.yml +65 -65
  58. data/web/locales/el.yml +2 -7
  59. data/web/locales/en.yml +78 -70
  60. data/web/locales/es.yml +68 -68
  61. data/web/locales/fa.yml +65 -65
  62. data/web/locales/fr.yml +81 -67
  63. data/web/locales/gd.yml +99 -0
  64. data/web/locales/he.yml +65 -64
  65. data/web/locales/hi.yml +59 -59
  66. data/web/locales/it.yml +53 -53
  67. data/web/locales/ja.yml +67 -69
  68. data/web/locales/ko.yml +52 -52
  69. data/web/locales/lt.yml +66 -66
  70. data/web/locales/nb.yml +61 -61
  71. data/web/locales/nl.yml +52 -52
  72. data/web/locales/pl.yml +45 -45
  73. data/web/locales/pt-br.yml +79 -69
  74. data/web/locales/pt.yml +51 -51
  75. data/web/locales/ru.yml +67 -66
  76. data/web/locales/sv.yml +53 -53
  77. data/web/locales/ta.yml +60 -60
  78. data/web/locales/uk.yml +62 -61
  79. data/web/locales/ur.yml +64 -64
  80. data/web/locales/vi.yml +67 -67
  81. data/web/locales/zh-cn.yml +20 -18
  82. data/web/locales/zh-tw.yml +10 -1
  83. data/web/views/_footer.erb +5 -2
  84. data/web/views/_job_info.erb +18 -2
  85. data/web/views/_metrics_period_select.erb +12 -0
  86. data/web/views/_paging.erb +2 -0
  87. data/web/views/_poll_link.erb +1 -1
  88. data/web/views/busy.erb +39 -28
  89. data/web/views/dashboard.erb +36 -5
  90. data/web/views/filtering.erb +7 -0
  91. data/web/views/metrics.erb +33 -20
  92. data/web/views/metrics_for_job.erb +25 -44
  93. data/web/views/morgue.erb +5 -9
  94. data/web/views/queue.erb +10 -14
  95. data/web/views/queues.erb +3 -1
  96. data/web/views/retries.erb +5 -9
  97. data/web/views/scheduled.erb +12 -13
  98. metadata +44 -39
  99. data/lib/sidekiq/delay.rb +0 -43
  100. data/lib/sidekiq/extensions/action_mailer.rb +0 -48
  101. data/lib/sidekiq/extensions/active_record.rb +0 -43
  102. data/lib/sidekiq/extensions/class_methods.rb +0 -43
  103. data/lib/sidekiq/extensions/generic_proxy.rb +0 -33
  104. data/lib/sidekiq/metrics/deploy.rb +0 -47
  105. data/lib/sidekiq/worker.rb +0 -370
  106. data/web/assets/javascripts/graph.js +0 -16
  107. /data/{LICENSE → LICENSE.txt} +0 -0
data/bin/sidekiqload CHANGED
@@ -1,32 +1,63 @@
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"
27
+ require "ruby-prof" if ENV["PROFILE"]
8
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"
13
-
14
- if ENV["SIDEKIQ_REDIS_CLIENT"]
15
- Sidekiq::RedisConnection.adapter = :redis_client
31
+ latency = Integer(ENV["LATENCY"] || 1)
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
+ }])
16
44
  end
17
45
 
18
- Sidekiq.configure_server do |config|
19
- config.options[:concurrency] = 10
20
- config.redis = {db: 13, port: 6380}
21
- # config.redis = { db: 13, port: 6380, driver: :hiredis}
22
- config.options[:queues] << "default"
23
- config.logger.level = Logger::ERROR
24
- config.average_scheduled_poll_interval = 2
25
- config.reliable! if defined?(Sidekiq::Pro)
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
51
+
52
+ class LoadJob < ActiveJob::Base
53
+ def perform(idx, ts = nil)
54
+ puts(Time.now.to_f - ts) if !ts.nil?
55
+ end
56
+ end
26
57
  end
27
58
 
28
59
  class LoadWorker
29
- include Sidekiq::Worker
60
+ include Sidekiq::Job
30
61
  sidekiq_options retry: 1
31
62
  sidekiq_retry_in do |x|
32
63
  1
@@ -38,124 +69,179 @@ class LoadWorker
38
69
  end
39
70
  end
40
71
 
41
- # brew tap shopify/shopify
42
- # brew install toxiproxy
43
- # run `toxiproxy-server` in a separate terminal window.
44
- require "toxiproxy"
45
- # simulate a non-localhost network for realer-world conditions.
46
- # adding 1ms of network latency has an ENORMOUS impact on benchmarks
47
- Toxiproxy.populate([{
48
- name: "redis",
49
- listen: "127.0.0.1:6380",
50
- upstream: "127.0.0.1:6379"
51
- }])
52
-
53
- self_read, self_write = IO.pipe
54
- %w[INT TERM TSTP TTIN].each do |sig|
55
- trap sig do
56
- self_write.puts(sig)
57
- end
58
- rescue ArgumentError
59
- puts "Signal #{sig} not supported"
72
+ def Process.rss
73
+ `ps -o rss= -p #{Process.pid}`.chomp.to_i
60
74
  end
61
75
 
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>"
76
+ class Loader
77
+ def initialize
78
+ @iter = ENV["GC"] ? 10 : 500
79
+ @count = Integer(ENV["COUNT"] || 1_000)
80
+ @latency = Integer(ENV["LATENCY"] || 1)
81
+ end
82
+
83
+ def configure
84
+ @x = Sidekiq.configure_embed do |config|
85
+ config.redis = {db: 13, port: ((@latency > 0) ? 6380 : 6379)}
86
+ config.concurrency = Integer(ENV.fetch("THREADS", "10"))
87
+ # config.redis = { db: 13, port: 6380, driver: :hiredis}
88
+ config.queues = %w[default]
89
+ config.logger.level = Logger::WARN
90
+ config.average_scheduled_poll_interval = 2
91
+ config.reliable! if defined?(Sidekiq::Pro)
92
+ end
93
+
94
+ @self_read, @self_write = IO.pipe
95
+ %w[INT TERM TSTP TTIN].each do |sig|
96
+ trap sig do
97
+ @self_write.puts(sig)
83
98
  end
99
+ rescue ArgumentError
100
+ puts "Signal #{sig} not supported"
84
101
  end
85
102
  end
86
- end
87
103
 
88
- def Process.rss
89
- `ps -o rss= -p #{Process.pid}`.chomp.to_i
90
- end
104
+ def handle_signal(sig)
105
+ launcher = @x
106
+ Sidekiq.logger.debug "Got #{sig} signal"
107
+ case sig
108
+ when "INT"
109
+ # Handle Ctrl-C in JRuby like MRI
110
+ # http://jira.codehaus.org/browse/JRUBY-4637
111
+ raise Interrupt
112
+ when "TERM"
113
+ # Heroku sends TERM and then waits 30 seconds for process to exit.
114
+ raise Interrupt
115
+ when "TSTP"
116
+ Sidekiq.logger.info "Received TSTP, no longer accepting new work"
117
+ launcher.quiet
118
+ when "TTIN"
119
+ Thread.list.each do |thread|
120
+ Sidekiq.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread["label"]}"
121
+ if thread.backtrace
122
+ Sidekiq.logger.warn thread.backtrace.join("\n")
123
+ else
124
+ Sidekiq.logger.warn "<no backtrace available>"
125
+ end
126
+ end
127
+ end
128
+ end
91
129
 
92
- iter = 10
93
- count = 10_000
130
+ def setup
131
+ Sidekiq.logger.error("Setup RSS: #{Process.rss}")
132
+ Sidekiq.redis { |c| c.flushdb }
133
+ start = Time.now
134
+ if ENV["AJ"]
135
+ @iter.times do
136
+ @count.times do |idx|
137
+ LoadJob.perform_later(idx)
138
+ end
139
+ end
140
+ else
141
+ @iter.times do
142
+ arr = Array.new(@count) { |idx| [idx] }
143
+ Sidekiq::Client.push_bulk("class" => LoadWorker, "args" => arr)
144
+ end
145
+ end
146
+ Sidekiq.logger.warn "Created #{@count * @iter} jobs in #{Time.now - start} sec"
147
+ end
94
148
 
95
- iter.times do
96
- arr = Array.new(count) { |idx| [idx] }
97
- Sidekiq::Client.push_bulk("class" => LoadWorker, "args" => arr)
98
- end
99
- Sidekiq.logger.error "Created #{count * iter} jobs"
149
+ def monitor
150
+ @monitor = Thread.new do
151
+ GC.start
152
+ loop do
153
+ sleep 0.2
154
+ qsize = Sidekiq.redis do |conn|
155
+ conn.llen "queue:default"
156
+ end
157
+ total = qsize
158
+ if total == 0
159
+ ending = Time.now - @start
160
+ size = @iter * @count
161
+ Sidekiq.logger.error("Done, #{size} jobs in #{ending} sec, #{(size / ending).to_i} jobs/sec")
162
+ Sidekiq.logger.error("Ending RSS: #{Process.rss}")
163
+ Sidekiq.logger.error("Now here's the latency for three jobs")
164
+
165
+ if ENV["AJ"]
166
+ LoadJob.perform_later(1, Time.now.to_f)
167
+ LoadJob.perform_later(2, Time.now.to_f)
168
+ LoadJob.perform_later(3, Time.now.to_f)
169
+ else
170
+ LoadWorker.perform_async(1, Time.now.to_f)
171
+ LoadWorker.perform_async(2, Time.now.to_f)
172
+ LoadWorker.perform_async(3, Time.now.to_f)
173
+ end
174
+
175
+ sleep 0.1
176
+ @x.stop
177
+ Process.kill("INT", $$)
178
+ break
179
+ end
180
+ end
181
+ end
182
+ end
100
183
 
101
- start = Time.now
184
+ def with_latency(latency, &block)
185
+ Sidekiq.logger.error "Simulating #{latency}ms of latency between Sidekiq and redis"
186
+ if latency > 0
187
+ Toxiproxy[:redis].downstream(:latency, latency: latency).apply(&block)
188
+ else
189
+ yield
190
+ end
191
+ end
102
192
 
103
- Monitoring = Thread.new do
104
- while true
105
- sleep 0.2
106
- qsize = Sidekiq.redis do |conn|
107
- conn.llen "queue:default"
193
+ def run(name)
194
+ Sidekiq.logger.warn("Starting #{name}")
195
+ monitor
196
+
197
+ if ENV["PROFILE"]
198
+ RubyProf.exclude_threads = [@monitor]
199
+ RubyProf.start
200
+ elsif ENV["GC"]
201
+ GC.start
202
+ GC.compact
203
+ GC.disable
204
+ Sidekiq.logger.error("GC Start RSS: #{Process.rss}")
108
205
  end
109
- total = qsize
110
- # Sidekiq.logger.error("RSS: #{Process.rss} Pending: #{total}")
111
- if total == 0
112
- Sidekiq.logger.error("Done, #{iter * count} jobs in #{Time.now - start} sec")
113
- Sidekiq.logger.error("Now here's the latency for three jobs")
114
-
115
- LoadWorker.perform_async(1, Time.now.to_f)
116
- LoadWorker.perform_async(2, Time.now.to_f)
117
- LoadWorker.perform_async(3, Time.now.to_f)
118
-
119
- sleep 0.2
120
- exit(0)
206
+ @start = Time.now
207
+ with_latency(@latency) do
208
+ @x.run
209
+
210
+ while (readable_io = IO.select([@self_read]))
211
+ signal = readable_io.first[0].gets.strip
212
+ handle_signal(signal)
213
+ end
121
214
  end
215
+ # normal
216
+ rescue Interrupt
217
+ rescue => e
218
+ raise e if $DEBUG
219
+ warn e.message
220
+ warn e.backtrace.join("\n")
221
+ exit 1
222
+ ensure
223
+ @x.stop
122
224
  end
123
- end
124
225
 
125
- def with_latency(latency, &block)
126
- Sidekiq.logger.error "Simulating #{latency}ms of latency between Sidekiq and redis"
127
- if latency > 0
128
- Toxiproxy[:redis].downstream(:latency, latency: latency).apply(&block)
129
- else
130
- yield
226
+ def done
227
+ Sidekiq.logger.error("GC End RSS: #{Process.rss}") if ENV["GC"]
228
+ if ENV["PROFILE"]
229
+ Sidekiq.logger.error("Profiling...")
230
+ result = RubyProf.stop
231
+ printer = RubyProf::GraphHtmlPrinter.new(result)
232
+ printer.print(File.new("output.html", "w"), min_percent: 1)
233
+ end
131
234
  end
132
235
  end
133
236
 
134
- begin
135
- # RubyProf::exclude_threads = [ Monitoring ]
136
- # RubyProf.start
137
- events = Sidekiq.options[:lifecycle_events][:startup]
138
- events.each(&:call)
139
- events.clear
140
-
141
- with_latency(Integer(ENV.fetch("LATENCY", "1"))) do
142
- launcher = Sidekiq::Launcher.new(Sidekiq)
143
- launcher.run
144
-
145
- while readable_io = IO.select([self_read])
146
- signal = readable_io.first[0].gets.strip
147
- handle_signal(launcher, signal)
148
- end
149
- end
150
- rescue SystemExit => e
151
- # Sidekiq.logger.error("Profiling...")
152
- # result = RubyProf.stop
153
- # printer = RubyProf::GraphHtmlPrinter.new(result)
154
- # printer.print(File.new("output.html", "w"), :min_percent => 1)
155
- # normal
156
- rescue => e
157
- raise e if $DEBUG
158
- warn e.message
159
- warn e.backtrace.join("\n")
160
- exit 1
237
+ ll = Loader.new
238
+ ll.configure
239
+
240
+ if ENV["WARM"]
241
+ ll.setup
242
+ ll.run("warmup")
161
243
  end
244
+
245
+ ll.setup
246
+ ll.run("load")
247
+ ll.done
data/bin/sidekiqmon CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  require "sidekiq/monitor"
4
4
 
5
+ # disable the Redis connection pool logging
6
+ Sidekiq.default_configuration.logger.level = :warn
7
+
5
8
  section = "all"
6
9
  section = ARGV[0] if ARGV.size == 1
7
10