sidekiq 6.5.12 → 7.3.9

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 (123) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +340 -20
  3. data/README.md +43 -35
  4. data/bin/multi_queue_bench +271 -0
  5. data/bin/sidekiq +3 -8
  6. data/bin/sidekiqload +213 -118
  7. data/bin/sidekiqmon +3 -0
  8. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +75 -0
  9. data/lib/generators/sidekiq/job_generator.rb +2 -0
  10. data/lib/sidekiq/api.rb +243 -162
  11. data/lib/sidekiq/capsule.rb +132 -0
  12. data/lib/sidekiq/cli.rb +60 -75
  13. data/lib/sidekiq/client.rb +87 -38
  14. data/lib/sidekiq/component.rb +26 -1
  15. data/lib/sidekiq/config.rb +311 -0
  16. data/lib/sidekiq/deploy.rb +64 -0
  17. data/lib/sidekiq/embedded.rb +63 -0
  18. data/lib/sidekiq/fetch.rb +11 -14
  19. data/lib/sidekiq/iterable_job.rb +55 -0
  20. data/lib/sidekiq/job/interrupt_handler.rb +24 -0
  21. data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
  22. data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
  23. data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
  24. data/lib/sidekiq/job/iterable.rb +294 -0
  25. data/lib/sidekiq/job.rb +382 -10
  26. data/lib/sidekiq/job_logger.rb +8 -7
  27. data/lib/sidekiq/job_retry.rb +42 -19
  28. data/lib/sidekiq/job_util.rb +53 -15
  29. data/lib/sidekiq/launcher.rb +71 -65
  30. data/lib/sidekiq/logger.rb +2 -27
  31. data/lib/sidekiq/manager.rb +9 -11
  32. data/lib/sidekiq/metrics/query.rb +9 -4
  33. data/lib/sidekiq/metrics/shared.rb +21 -9
  34. data/lib/sidekiq/metrics/tracking.rb +40 -26
  35. data/lib/sidekiq/middleware/chain.rb +19 -18
  36. data/lib/sidekiq/middleware/current_attributes.rb +85 -20
  37. data/lib/sidekiq/middleware/modules.rb +2 -0
  38. data/lib/sidekiq/monitor.rb +18 -4
  39. data/lib/sidekiq/paginator.rb +8 -2
  40. data/lib/sidekiq/processor.rb +62 -57
  41. data/lib/sidekiq/rails.rb +27 -10
  42. data/lib/sidekiq/redis_client_adapter.rb +31 -71
  43. data/lib/sidekiq/redis_connection.rb +44 -115
  44. data/lib/sidekiq/ring_buffer.rb +2 -0
  45. data/lib/sidekiq/scheduled.rb +22 -23
  46. data/lib/sidekiq/systemd.rb +2 -0
  47. data/lib/sidekiq/testing.rb +37 -46
  48. data/lib/sidekiq/transaction_aware_client.rb +11 -5
  49. data/lib/sidekiq/version.rb +6 -1
  50. data/lib/sidekiq/web/action.rb +29 -7
  51. data/lib/sidekiq/web/application.rb +82 -28
  52. data/lib/sidekiq/web/csrf_protection.rb +10 -7
  53. data/lib/sidekiq/web/helpers.rb +110 -49
  54. data/lib/sidekiq/web/router.rb +5 -2
  55. data/lib/sidekiq/web.rb +70 -17
  56. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  57. data/lib/sidekiq.rb +78 -274
  58. data/sidekiq.gemspec +13 -10
  59. data/web/assets/javascripts/application.js +44 -0
  60. data/web/assets/javascripts/base-charts.js +106 -0
  61. data/web/assets/javascripts/dashboard-charts.js +194 -0
  62. data/web/assets/javascripts/dashboard.js +17 -233
  63. data/web/assets/javascripts/metrics.js +151 -115
  64. data/web/assets/stylesheets/application-dark.css +4 -0
  65. data/web/assets/stylesheets/application-rtl.css +10 -89
  66. data/web/assets/stylesheets/application.css +56 -296
  67. data/web/locales/ar.yml +70 -70
  68. data/web/locales/cs.yml +62 -62
  69. data/web/locales/da.yml +60 -53
  70. data/web/locales/de.yml +65 -65
  71. data/web/locales/el.yml +2 -7
  72. data/web/locales/en.yml +81 -71
  73. data/web/locales/es.yml +68 -68
  74. data/web/locales/fa.yml +65 -65
  75. data/web/locales/fr.yml +80 -67
  76. data/web/locales/gd.yml +98 -0
  77. data/web/locales/he.yml +65 -64
  78. data/web/locales/hi.yml +59 -59
  79. data/web/locales/it.yml +85 -54
  80. data/web/locales/ja.yml +67 -70
  81. data/web/locales/ko.yml +52 -52
  82. data/web/locales/lt.yml +66 -66
  83. data/web/locales/nb.yml +61 -61
  84. data/web/locales/nl.yml +52 -52
  85. data/web/locales/pl.yml +45 -45
  86. data/web/locales/pt-br.yml +78 -69
  87. data/web/locales/pt.yml +51 -51
  88. data/web/locales/ru.yml +67 -66
  89. data/web/locales/sv.yml +53 -53
  90. data/web/locales/ta.yml +60 -60
  91. data/web/locales/tr.yml +100 -0
  92. data/web/locales/uk.yml +85 -61
  93. data/web/locales/ur.yml +64 -64
  94. data/web/locales/vi.yml +67 -67
  95. data/web/locales/zh-cn.yml +20 -19
  96. data/web/locales/zh-tw.yml +10 -2
  97. data/web/views/_footer.erb +16 -2
  98. data/web/views/_job_info.erb +18 -2
  99. data/web/views/_metrics_period_select.erb +12 -0
  100. data/web/views/_paging.erb +2 -0
  101. data/web/views/_poll_link.erb +1 -1
  102. data/web/views/_summary.erb +7 -7
  103. data/web/views/busy.erb +46 -35
  104. data/web/views/dashboard.erb +32 -8
  105. data/web/views/filtering.erb +6 -0
  106. data/web/views/layout.erb +6 -6
  107. data/web/views/metrics.erb +47 -26
  108. data/web/views/metrics_for_job.erb +43 -71
  109. data/web/views/morgue.erb +7 -11
  110. data/web/views/queue.erb +11 -15
  111. data/web/views/queues.erb +9 -3
  112. data/web/views/retries.erb +5 -9
  113. data/web/views/scheduled.erb +12 -13
  114. metadata +66 -41
  115. data/lib/sidekiq/delay.rb +0 -43
  116. data/lib/sidekiq/extensions/action_mailer.rb +0 -48
  117. data/lib/sidekiq/extensions/active_record.rb +0 -43
  118. data/lib/sidekiq/extensions/class_methods.rb +0 -43
  119. data/lib/sidekiq/extensions/generic_proxy.rb +0 -33
  120. data/lib/sidekiq/metrics/deploy.rb +0 -47
  121. data/lib/sidekiq/worker.rb +0 -370
  122. data/web/assets/javascripts/graph.js +0 -16
  123. /data/{LICENSE → LICENSE.txt} +0 -0
@@ -0,0 +1,271 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #
4
+ # bin/bench is a helpful script to load test and
5
+ # performance tune Sidekiq's core. It's a configurable script,
6
+ # which accepts the following parameters as ENV variables.
7
+ #
8
+ # QUEUES
9
+ # Number of queues to consume from. Default is 8
10
+ #
11
+ # PROCESSES
12
+ # The number of processes this benchmark will create. Each process, consumes
13
+ # from one of the available queues. When processes are more than the number of
14
+ # queues, they are distributed to processes in round robin. Default is 8
15
+ #
16
+ # ELEMENTS
17
+ # Number of jobs to push to each queue. Default is 1000
18
+ #
19
+ # ITERATIONS
20
+ # Each queue pushes ITERATIONS times ELEMENTS jobs. Default is 1000
21
+ #
22
+ # PORT
23
+ # The port of the Dragonfly instance. Default is 6379
24
+ #
25
+ # IP
26
+ # The ip of the Dragonfly instance. Default is 127.0.0.1
27
+ #
28
+ # Example Usage:
29
+ #
30
+ # > RUBY_YJIT_ENABLE=1 THREADS=10 PROCESSES=8 QUEUES=8 bin/multi_queue_bench
31
+ #
32
+ # None of this script is considered a public API and may change over time.
33
+ #
34
+
35
+ # Quiet some warnings we see when running in warning mode:
36
+ # RUBYOPT=-w bundle exec sidekiq
37
+ $TESTING = false
38
+ puts RUBY_DESCRIPTION
39
+
40
+ require "bundler/setup"
41
+ Bundler.require(:default, :load_test)
42
+
43
+ class LoadWorker
44
+ include Sidekiq::Job
45
+ sidekiq_options retry: 1
46
+ sidekiq_retry_in do |x|
47
+ 1
48
+ end
49
+
50
+ def perform(idx, ts = nil)
51
+ puts(Time.now.to_f - ts) if !ts.nil?
52
+ # raise idx.to_s if idx % 100 == 1
53
+ end
54
+ end
55
+
56
+ def Process.rss
57
+ `ps -o rss= -p #{Process.pid}`.chomp.to_i
58
+ end
59
+
60
+ $iterations = ENV["ITERATIONS"] ? Integer(ENV["ITERATIONS"]) : 1_000
61
+ $elements = ENV["ELEMENTS"] ? Integer(ENV["ELEMENTS"]) : 1_000
62
+ $port = ENV["PORT"] ? Integer(ENV["PORT"]) : 6379
63
+ $ip = ENV["IP"] ? String(ENV["IP"]) : "127.0.0.1"
64
+
65
+ class Loader
66
+ def initialize
67
+ @iter = $iterations
68
+ @count = $elements
69
+ end
70
+
71
+ def configure(queue)
72
+ @x = Sidekiq.configure_embed do |config|
73
+ config.redis = {db: 0, host: $ip, port: $port}
74
+ config.concurrency = Integer(ENV.fetch("THREADS", "30"))
75
+ config.queues = queue
76
+ config.logger.level = Logger::WARN
77
+ config.average_scheduled_poll_interval = 2
78
+ config.reliable! if defined?(Sidekiq::Pro)
79
+ end
80
+
81
+ @self_read, @self_write = IO.pipe
82
+ %w[INT TERM TSTP TTIN].each do |sig|
83
+ trap sig do
84
+ @self_write.puts(sig)
85
+ end
86
+ rescue ArgumentError
87
+ puts "Signal #{sig} not supported"
88
+ end
89
+ end
90
+
91
+ def handle_signal(sig)
92
+ launcher = @x
93
+ Sidekiq.logger.debug "Got #{sig} signal"
94
+ case sig
95
+ when "INT"
96
+ # Handle Ctrl-C in JRuby like MRI
97
+ # http://jira.codehaus.org/browse/JRUBY-4637
98
+ raise Interrupt
99
+ when "TERM"
100
+ # Heroku sends TERM and then waits 30 seconds for process to exit.
101
+ raise Interrupt
102
+ when "TSTP"
103
+ Sidekiq.logger.info "Received TSTP, no longer accepting new work"
104
+ launcher.quiet
105
+ when "TTIN"
106
+ Thread.list.each do |thread|
107
+ Sidekiq.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread["label"]}"
108
+ if thread.backtrace
109
+ Sidekiq.logger.warn thread.backtrace.join("\n")
110
+ else
111
+ Sidekiq.logger.warn "<no backtrace available>"
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ def setup(queue)
118
+ Sidekiq.logger.error("Setup RSS: #{Process.rss}")
119
+ Sidekiq.logger.error("Pushing work to queue: #{queue}")
120
+ start = Time.now
121
+ @iter.times do
122
+ arr = Array.new(@count) { |idx| [idx] }
123
+ # Sidekiq always prepends "queue:" to the queue name,
124
+ # that's why we pass 'q1', 'q2', etc instead of 'queue:q1'
125
+ Sidekiq::Client.push_bulk("class" => LoadWorker, "args" => arr, "queue" => queue)
126
+ $stdout.write "."
127
+ end
128
+ puts "Done"
129
+ end
130
+
131
+ def monitor_single(queue)
132
+ q = "queue:#{queue}"
133
+ @monitor_single = Thread.new do
134
+ GC.start
135
+ loop do
136
+ sleep 0.2
137
+ total = Sidekiq.redis do |conn|
138
+ conn.llen q
139
+ end
140
+
141
+ if total == 0
142
+ sleep 0.1
143
+ @x.stop
144
+ Process.kill("INT", $$)
145
+ break
146
+ end
147
+
148
+ end
149
+ end
150
+ end
151
+
152
+ def monitor_all(queues)
153
+ @monitor_all = Thread.new do
154
+ GC.start
155
+ loop do
156
+ sleep 0.2
157
+ qsize = 0
158
+ queues.each do |q|
159
+ tmp = Sidekiq.redis do |conn|
160
+ conn.llen q
161
+ end
162
+ qsize = qsize + tmp
163
+ end
164
+ total = qsize
165
+
166
+ if total == 0
167
+ ending = Time.now - @start
168
+ size = @iter * @count * queues.length()
169
+ Sidekiq.logger.error("Done, #{size} jobs in #{ending} sec, #{(size / ending).to_i} jobs/sec")
170
+ Sidekiq.logger.error("Ending RSS: #{Process.rss}")
171
+
172
+ sleep 0.1
173
+ @x.stop
174
+ Process.kill("INT", $$)
175
+ break
176
+ end
177
+ end
178
+ end
179
+ end
180
+
181
+ def run(queues, queue, monitor_all_queues)
182
+ Sidekiq.logger.warn("Consuming from #{queue}")
183
+ if monitor_all_queues
184
+ monitor_all(queues)
185
+ else
186
+ monitor_single(queue)
187
+ end
188
+
189
+ @start = Time.now
190
+ @x.run
191
+
192
+ while (readable_io = IO.select([@self_read]))
193
+ signal = readable_io.first[0].gets.strip
194
+ handle_signal(signal)
195
+ end
196
+ # normal
197
+ rescue Interrupt
198
+ rescue => e
199
+ raise e if $DEBUG
200
+ warn e.message
201
+ warn e.backtrace.join("\n")
202
+ exit 1
203
+ ensure
204
+ @x.stop
205
+ end
206
+ end
207
+
208
+ def setup(queue)
209
+ ll = Loader.new
210
+ ll.configure(queue)
211
+ ll.setup(queue)
212
+ end
213
+
214
+ def consume(queues, queue, monitor_all_queues)
215
+ ll = Loader.new
216
+ ll.configure(queue)
217
+ ll.run(queues, queue, monitor_all_queues)
218
+ end
219
+
220
+ # We assign one queue to each sidekiq process
221
+ def run(number_of_processes, total_queues)
222
+ read_stream, write_stream = IO.pipe
223
+
224
+ queues = []
225
+ (0..total_queues-1).each do |idx|
226
+ queues.push("queue:q#{idx}")
227
+ end
228
+
229
+ Sidekiq.logger.info("Queues are: #{queues}")
230
+
231
+ # Produce
232
+ start = Time.now
233
+ (0..total_queues-1).each do |idx|
234
+ Process.fork do
235
+ queue_num = "q#{idx}"
236
+ setup(queue_num)
237
+ end
238
+ end
239
+
240
+ queue_sz = $iterations * $elements * total_queues
241
+ Process.waitall
242
+
243
+ ending = Time.now - start
244
+ #Sidekiq.logger.info("Pushed #{queue_sz} in #{ending} secs")
245
+
246
+ # Consume
247
+ (0..number_of_processes-1).each do |idx|
248
+ Process.fork do
249
+ # First process only consumes from it's own queue but monitors all queues.
250
+ # It works as a synchronization point. Once all processes finish
251
+ # (that is, when all queues are emptied) it prints the the stats.
252
+ if idx == 0
253
+ queue = "q#{idx}"
254
+ consume(queues, queue, true)
255
+ else
256
+ queue = "q#{idx % total_queues}"
257
+ consume(queues, queue, false)
258
+ end
259
+ end
260
+ end
261
+
262
+ Process.waitall
263
+ write_stream.close
264
+ results = read_stream.read
265
+ read_stream.close
266
+ end
267
+
268
+ $total_processes = ENV["PROCESSES"] ? Integer(ENV["PROCESSES"]) : 8;
269
+ $total_queues = ENV["QUEUES"] ? Integer(ENV["QUEUES"]) : 8;
270
+
271
+ run($total_processes, $total_queues)
data/bin/sidekiq CHANGED
@@ -10,7 +10,7 @@ def integrate_with_systemd
10
10
  return unless ENV["NOTIFY_SOCKET"]
11
11
 
12
12
  Sidekiq.configure_server do |config|
13
- Sidekiq.logger.info "Enabling systemd notification integration"
13
+ config.logger.info "Enabling systemd notification integration"
14
14
  require "sidekiq/sd_notify"
15
15
  config.on(:startup) do
16
16
  Sidekiq::SdNotify.ready
@@ -31,12 +31,7 @@ begin
31
31
  cli.run
32
32
  rescue => e
33
33
  raise e if $DEBUG
34
- if Sidekiq.error_handlers.length == 0
35
- warn e.message
36
- warn e.backtrace.join("\n")
37
- else
38
- cli.handle_exception e
39
- end
40
-
34
+ warn e.message
35
+ warn e.backtrace.join("\n")
41
36
  exit 1
42
37
  end
data/bin/sidekiqload CHANGED
@@ -1,161 +1,256 @@
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(string, idx, hash, 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
61
+ $count = 0
62
+ $lock = Mutex.new
63
+
30
64
  sidekiq_options retry: 1
31
65
  sidekiq_retry_in do |x|
32
66
  1
33
67
  end
34
68
 
35
- def perform(idx, ts = nil)
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}")
74
+ end
75
+ end
36
76
  puts(Time.now.to_f - ts) if !ts.nil?
37
77
  # raise idx.to_s if idx % 100 == 1
38
78
  end
39
79
  end
40
80
 
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"
81
+ def Process.rss
82
+ `ps -o rss= -p #{Process.pid}`.chomp.to_i
60
83
  end
61
84
 
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>"
85
+ class Loader
86
+ def initialize
87
+ @iter = ENV["GC"] ? 10 : 500
88
+ @count = Integer(ENV["COUNT"] || 1_000)
89
+ @latency = Integer(ENV["LATENCY"] || 1)
90
+ end
91
+
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)
83
107
  end
108
+ rescue ArgumentError
109
+ puts "Signal #{sig} not supported"
84
110
  end
85
111
  end
86
- end
87
112
 
88
- def Process.rss
89
- `ps -o rss= -p #{Process.pid}`.chomp.to_i
90
- end
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
137
+ end
91
138
 
92
- iter = 10
93
- count = 10_000
139
+ def setup
140
+ Sidekiq.logger.error("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
94
157
 
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"
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.error("Done, #{size} jobs in #{ending} sec, #{(size / ending).to_i} jobs/sec")
171
+ Sidekiq.logger.error("Ending RSS: #{Process.rss}")
172
+ Sidekiq.logger.error("Now here's the latency for three jobs")
100
173
 
101
- start = Time.now
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
102
183
 
103
- Monitoring = Thread.new do
104
- while true
105
- sleep 0.2
106
- qsize = Sidekiq.redis do |conn|
107
- conn.llen "queue:default"
184
+ sleep 0.1
185
+ @x.stop
186
+ Process.kill("INT", $$)
187
+ break
188
+ end
189
+ end
108
190
  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)
191
+ end
192
+
193
+ def with_latency(latency, &block)
194
+ Sidekiq.logger.error "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
121
199
  end
122
200
  end
123
- end
124
201
 
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
202
+ def run(name)
203
+ Sidekiq.logger.warn("Starting #{name}")
204
+ monitor
205
+
206
+ if ENV["PROFILE"]
207
+ RubyProf.exclude_threads = [@monitor]
208
+ RubyProf.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)
222
+ end
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
131
233
  end
132
- end
133
234
 
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)
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 = RubyProf.stop
240
+ printer = RubyProf::GraphHtmlPrinter.new(result)
241
+ printer.print(File.new("output.html", "w"), min_percent: 1)
148
242
  end
149
243
  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
161
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
@@ -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