sidekiq 4.1.4 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sidekiq might be problematic. Click here for more details.

Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +0 -6
  3. data/Changes.md +24 -0
  4. data/Ent-Changes.md +24 -1
  5. data/Gemfile +5 -5
  6. data/Pro-Changes.md +11 -0
  7. data/lib/sidekiq.rb +4 -1
  8. data/lib/sidekiq/cli.rb +11 -2
  9. data/lib/sidekiq/launcher.rb +5 -1
  10. data/lib/sidekiq/manager.rb +1 -0
  11. data/lib/sidekiq/processor.rb +28 -25
  12. data/lib/sidekiq/rails.rb +17 -0
  13. data/lib/sidekiq/scheduled.rb +1 -0
  14. data/lib/sidekiq/version.rb +1 -1
  15. data/lib/sidekiq/web.rb +79 -202
  16. data/lib/sidekiq/web/action.rb +99 -0
  17. data/lib/sidekiq/web/application.rb +335 -0
  18. data/lib/sidekiq/{web_helpers.rb → web/helpers.rb} +29 -10
  19. data/lib/sidekiq/web/router.rb +96 -0
  20. data/sidekiq.gemspec +2 -2
  21. data/test/test_cli.rb +14 -2
  22. data/test/test_launcher.rb +12 -2
  23. data/test/test_web.rb +52 -5
  24. data/web/assets/images/{status-sd8051fd480.png → status.png} +0 -0
  25. data/web/assets/javascripts/application.js +5 -0
  26. data/web/assets/stylesheets/application.css +26 -1
  27. data/web/assets/stylesheets/bootstrap.css +4 -8
  28. data/web/locales/de.yml +1 -1
  29. data/web/views/_footer.erb +1 -1
  30. data/web/views/busy.erb +2 -2
  31. data/web/views/dashboard.erb +2 -2
  32. data/web/views/dead.erb +1 -1
  33. data/web/views/layout.erb +3 -3
  34. data/web/views/morgue.erb +2 -2
  35. data/web/views/queue.erb +3 -3
  36. data/web/views/queues.erb +1 -1
  37. data/web/views/retries.erb +2 -2
  38. data/web/views/retry.erb +1 -1
  39. data/web/views/scheduled.erb +2 -2
  40. data/web/views/scheduled_job_info.erb +1 -1
  41. metadata +12 -20
  42. data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
  43. data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
  44. data/web/assets/images/status/active.png +0 -0
  45. data/web/assets/images/status/idle.png +0 -0
  46. data/web/views/_poll_js.erb +0 -5
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ class WebAction
5
+ RACK_SESSION = 'rack.session'.freeze
6
+
7
+ LOCATION = "Location".freeze
8
+
9
+ CONTENT_TYPE = "Content-Type".freeze
10
+ TEXT_HTML = { CONTENT_TYPE => "text/html".freeze, "Cache-Control" => "no-cache" }
11
+ APPLICATION_JSON = { CONTENT_TYPE => "application/json".freeze }
12
+
13
+ attr_accessor :env, :block, :type
14
+
15
+ def settings
16
+ Web.settings
17
+ end
18
+
19
+ def request
20
+ @request ||= ::Rack::Request.new(env)
21
+ end
22
+
23
+ def halt(res)
24
+ throw :halt, res
25
+ end
26
+
27
+ def redirect(location)
28
+ throw :halt, [302, { LOCATION => "#{request.base_url}#{location}" }, []]
29
+ end
30
+
31
+ def params
32
+ indifferent_hash = Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
33
+
34
+ indifferent_hash.merge! request.params
35
+ route_params.each {|k,v| indifferent_hash[k.to_s] = v }
36
+
37
+ indifferent_hash
38
+ end
39
+
40
+ def route_params
41
+ env[WebRouter::ROUTE_PARAMS]
42
+ end
43
+
44
+ def session
45
+ env[RACK_SESSION]
46
+ end
47
+
48
+ def content_type(type)
49
+ @type = type
50
+ end
51
+
52
+ def erb(content, options = {})
53
+ if content.kind_of? Symbol
54
+ unless respond_to?(:"_erb_#{content}")
55
+ src = ERB.new(File.read("#{Web.settings.views}/#{content}.erb")).src
56
+ WebAction.class_eval("def _erb_#{content}\n#{src}\n end")
57
+ end
58
+ end
59
+
60
+ if @_erb
61
+ _erb(content, options[:locals])
62
+ else
63
+ @_erb = true
64
+ content = _erb(content, options[:locals])
65
+
66
+ _render { content }
67
+ end
68
+ end
69
+
70
+ def render(engine, content, options = {})
71
+ raise "Only erb templates are supported" if engine != :erb
72
+
73
+ erb(content, options)
74
+ end
75
+
76
+ def json(payload)
77
+ [200, APPLICATION_JSON, [Sidekiq.dump_json(payload)]]
78
+ end
79
+
80
+ def initialize(env, block)
81
+ @_erb = false
82
+ @env = env
83
+ @block = block
84
+ @@files ||= {}
85
+ end
86
+
87
+ private
88
+
89
+ def _erb(file, locals)
90
+ locals.each {|k, v| define_singleton_method(k){ v } } if locals
91
+
92
+ if file.kind_of?(String)
93
+ ERB.new(file).result(binding)
94
+ else
95
+ send(:"_erb_#{file}")
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,335 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ class WebApplication
5
+ extend WebRouter
6
+
7
+ CONTENT_LENGTH = "Content-Length".freeze
8
+ CONTENT_TYPE = "Content-Type".freeze
9
+ REDIS_KEYS = %w(redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human)
10
+ NOT_FOUND = [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass" }, ["Not Found"]]
11
+
12
+ def initialize(klass)
13
+ @klass = klass
14
+ end
15
+
16
+ def settings
17
+ @klass.settings
18
+ end
19
+
20
+ def self.settings
21
+ Sidekiq::Web.settings
22
+ end
23
+
24
+ def self.tabs
25
+ Sidekiq::Web.tabs
26
+ end
27
+
28
+ def self.set(key, val)
29
+ # nothing, backwards compatibility
30
+ end
31
+
32
+ get "/" do
33
+ @redis_info = redis_info.select{ |k, v| REDIS_KEYS.include? k }
34
+ stats_history = Sidekiq::Stats::History.new((params['days'] || 30).to_i)
35
+ @processed_history = stats_history.processed
36
+ @failed_history = stats_history.failed
37
+
38
+ erb(:dashboard)
39
+ end
40
+
41
+ get "/busy" do
42
+ erb(:busy)
43
+ end
44
+
45
+ post "/busy" do
46
+ if params['identity']
47
+ p = Sidekiq::Process.new('identity' => params['identity'])
48
+ p.quiet! if params['quiet']
49
+ p.stop! if params['stop']
50
+ else
51
+ processes.each do |pro|
52
+ pro.quiet! if params['quiet']
53
+ pro.stop! if params['stop']
54
+ end
55
+ end
56
+
57
+ redirect "#{root_path}busy"
58
+ end
59
+
60
+ get "/queues" do
61
+ @queues = Sidekiq::Queue.all
62
+
63
+ erb(:queues)
64
+ end
65
+
66
+ get "/queues/:name" do
67
+ @name = route_params[:name]
68
+
69
+ halt(404) unless @name
70
+
71
+ @count = (params['count'] || 25).to_i
72
+ @queue = Sidekiq::Queue.new(@name)
73
+ (@current_page, @total_size, @messages) = page("queue:#{@name}", params['page'], @count)
74
+ @messages = @messages.map { |msg| Sidekiq::Job.new(msg, @name) }
75
+
76
+ erb(:queue)
77
+ end
78
+
79
+ post "/queues/:name" do
80
+ Sidekiq::Queue.new(route_params[:name]).clear
81
+
82
+ redirect "#{root_path}queues"
83
+ end
84
+
85
+ post "/queues/:name/delete" do
86
+ name = route_params[:name]
87
+ Sidekiq::Job.new(params['key_val'], name).delete
88
+
89
+ redirect_with_query("#{root_path}queues/#{name}")
90
+ end
91
+
92
+ get '/morgue' do
93
+ @count = (params['count'] || 25).to_i
94
+ (@current_page, @total_size, @dead) = page("dead", params['page'], @count, reverse: true)
95
+ @dead = @dead.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
96
+
97
+ erb(:morgue)
98
+ end
99
+
100
+ get "/morgue/:key" do
101
+ halt(404) unless key = route_params[:key]
102
+
103
+ @dead = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
104
+
105
+ if @dead.nil?
106
+ redirect "#{root_path}morgue"
107
+ else
108
+ erb(:dead)
109
+ end
110
+ end
111
+
112
+ post '/morgue' do
113
+ redirect(request.path) unless params['key']
114
+
115
+ params['key'].each do |key|
116
+ job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
117
+ retry_or_delete_or_kill job, params if job
118
+ end
119
+
120
+ redirect_with_query("#{root_path}morgue")
121
+ end
122
+
123
+ post "/morgue/all/delete" do
124
+ Sidekiq::DeadSet.new.clear
125
+
126
+ redirect "#{root_path}morgue"
127
+ end
128
+
129
+ post "/morgue/all/retry" do
130
+ Sidekiq::DeadSet.new.retry_all
131
+
132
+ redirect "#{root_path}morgue"
133
+ end
134
+
135
+ post "/morgue/:key" do
136
+ halt(404) unless key = route_params[:key]
137
+
138
+ job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
139
+ retry_or_delete_or_kill job, params if job
140
+
141
+ redirect_with_query("#{root_path}morgue")
142
+ end
143
+
144
+ get '/retries' do
145
+ @count = (params['count'] || 25).to_i
146
+ (@current_page, @total_size, @retries) = page("retry", params['page'], @count)
147
+ @retries = @retries.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
148
+
149
+ erb(:retries)
150
+ end
151
+
152
+ get "/retries/:key" do
153
+ @retry = Sidekiq::RetrySet.new.fetch(*parse_params(route_params[:key])).first
154
+
155
+ if @retry.nil?
156
+ redirect "#{root_path}retries"
157
+ else
158
+ erb(:retry)
159
+ end
160
+ end
161
+
162
+ post '/retries' do
163
+ redirect(request.path) unless params['key']
164
+
165
+ params['key'].each do |key|
166
+ job = Sidekiq::RetrySet.new.fetch(*parse_params(key)).first
167
+ retry_or_delete_or_kill job, params if job
168
+ end
169
+
170
+ redirect_with_query("#{root_path}retries")
171
+ end
172
+
173
+ post "/retries/all/delete" do
174
+ Sidekiq::RetrySet.new.clear
175
+
176
+ redirect "#{root_path}retries"
177
+ end
178
+
179
+ post "/retries/all/retry" do
180
+ Sidekiq::RetrySet.new.retry_all
181
+
182
+ redirect "#{root_path}retries"
183
+ end
184
+
185
+ post "/retries/:key" do
186
+ job = Sidekiq::RetrySet.new.fetch(*parse_params(route_params[:key])).first
187
+
188
+ retry_or_delete_or_kill job, params if job
189
+
190
+ redirect_with_query("#{root_path}retries")
191
+ end
192
+
193
+ get '/scheduled' do
194
+ @count = (params['count'] || 25).to_i
195
+ (@current_page, @total_size, @scheduled) = page("schedule", params['page'], @count)
196
+ @scheduled = @scheduled.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
197
+
198
+ erb(:scheduled)
199
+ end
200
+
201
+ get "/scheduled/:key" do
202
+ @job = Sidekiq::ScheduledSet.new.fetch(*parse_params(route_params[:key])).first
203
+
204
+ if @job.nil?
205
+ redirect "#{root_path}scheduled"
206
+ else
207
+ erb(:scheduled_job_info)
208
+ end
209
+ end
210
+
211
+ post '/scheduled' do
212
+ redirect(request.path) unless params['key']
213
+
214
+ params['key'].each do |key|
215
+ job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first
216
+ delete_or_add_queue job, params if job
217
+ end
218
+
219
+ redirect_with_query("#{root_path}scheduled")
220
+ end
221
+
222
+ post "/scheduled/:key" do
223
+ halt(404) unless key = route_params[:key]
224
+
225
+ job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first
226
+ delete_or_add_queue job, params if job
227
+
228
+ redirect_with_query("#{root_path}scheduled")
229
+ end
230
+
231
+ get '/dashboard/stats' do
232
+ redirect "#{root_path}stats"
233
+ end
234
+
235
+ get '/stats' do
236
+ sidekiq_stats = Sidekiq::Stats.new
237
+ redis_stats = redis_info.select { |k, v| REDIS_KEYS.include? k }
238
+
239
+ json(
240
+ sidekiq: {
241
+ processed: sidekiq_stats.processed,
242
+ failed: sidekiq_stats.failed,
243
+ busy: sidekiq_stats.workers_size,
244
+ processes: sidekiq_stats.processes_size,
245
+ enqueued: sidekiq_stats.enqueued,
246
+ scheduled: sidekiq_stats.scheduled_size,
247
+ retries: sidekiq_stats.retry_size,
248
+ dead: sidekiq_stats.dead_size,
249
+ default_latency: sidekiq_stats.default_queue_latency
250
+ },
251
+ redis: redis_stats
252
+ )
253
+ end
254
+
255
+ get '/stats/queues' do
256
+ json Sidekiq::Stats::Queues.new.lengths
257
+ end
258
+
259
+ def call(env)
260
+ action = self.class.match(env)
261
+ return NOT_FOUND unless action
262
+
263
+ resp = catch(:halt) do
264
+ app = @klass
265
+ self.class.run_befores(app, action)
266
+ begin
267
+ resp = action.instance_exec env, &action.block
268
+ ensure
269
+ self.class.run_afters(app, action)
270
+ end
271
+
272
+ resp
273
+ end
274
+
275
+ resp = case resp
276
+ when Array
277
+ resp
278
+ when Fixnum
279
+ [resp, {}, []]
280
+ else
281
+ type_header = case action.type
282
+ when :json
283
+ WebAction::APPLICATION_JSON
284
+ when String
285
+ { WebAction::CONTENT_TYPE => action.type, "Cache-Control" => "no-cache" }
286
+ else
287
+ WebAction::TEXT_HTML
288
+ end
289
+
290
+ [200, type_header, [resp]]
291
+ end
292
+
293
+ resp[1][CONTENT_LENGTH] = resp[2].inject(0) { |l, p| l + p.bytesize }.to_s
294
+
295
+ resp
296
+ end
297
+
298
+ def self.helpers(mod=nil, &block)
299
+ if block_given?
300
+ WebAction.class_eval(&block)
301
+ else
302
+ WebAction.send(:include, mod)
303
+ end
304
+ end
305
+
306
+ def self.before(path=nil, &block)
307
+ befores << [path && Regexp.new("\\A#{path.gsub("*", ".*")}\\z"), block]
308
+ end
309
+
310
+ def self.after(path=nil, &block)
311
+ afters << [path && Regexp.new("\\A#{path.gsub("*", ".*")}\\z"), block]
312
+ end
313
+
314
+ def self.run_befores(app, action)
315
+ run_hooks(befores, app, action)
316
+ end
317
+
318
+ def self.run_afters(app, action)
319
+ run_hooks(afters, app, action)
320
+ end
321
+
322
+ def self.run_hooks(hooks, app, action)
323
+ hooks.select { |p,_| !p || p =~ action.env[WebRouter::PATH_INFO] }.
324
+ each {|_,b| action.instance_exec(action.env, app, &b) }
325
+ end
326
+
327
+ def self.befores
328
+ @befores ||= []
329
+ end
330
+
331
+ def self.afters
332
+ @afters ||= []
333
+ end
334
+ end
335
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require 'uri'
3
+ require 'yaml'
3
4
 
4
5
  module Sidekiq
5
6
  # This is not a public API
@@ -45,21 +46,21 @@ module Sidekiq
45
46
  # <meta .../>
46
47
  # <% end %>
47
48
  #
48
- def add_to_head(&block)
49
+ def add_to_head
49
50
  @head_html ||= []
50
- @head_html << block if block_given?
51
+ @head_html << yield.dup if block_given?
51
52
  end
52
53
 
53
54
  def display_custom_head
54
- return unless defined?(@head_html)
55
- @head_html.map { |block| capture(&block) }.join
55
+ @head_html.join if defined?(@head_html)
56
56
  end
57
57
 
58
- # Simple capture method for erb templates. The origin was
59
- # capture method from sinatra-contrib library.
60
- def capture(&block)
61
- block.call
62
- eval('', block.binding)
58
+ def poll_path
59
+ if current_path != '' && params['poll']
60
+ root_path + current_path
61
+ else
62
+ ""
63
+ end
63
64
  end
64
65
 
65
66
  # Given a browser request Accept-Language header like
@@ -69,7 +70,7 @@ module Sidekiq
69
70
  def locale
70
71
  @locale ||= begin
71
72
  locale = 'en'.freeze
72
- languages = request.env['HTTP_ACCEPT_LANGUAGE'.freeze] || 'en'.freeze
73
+ languages = env['HTTP_ACCEPT_LANGUAGE'.freeze] || 'en'.freeze
73
74
  languages.downcase.split(','.freeze).each do |lang|
74
75
  next if lang == '*'.freeze
75
76
  lang = lang.split(';'.freeze)[0]
@@ -251,5 +252,23 @@ module Sidekiq
251
252
  "#{redis_connection}#{namespace_suffix}"
252
253
  end
253
254
  end
255
+
256
+ def retry_or_delete_or_kill(job, params)
257
+ if params['retry']
258
+ job.retry
259
+ elsif params['delete']
260
+ job.delete
261
+ elsif params['kill']
262
+ job.kill
263
+ end
264
+ end
265
+
266
+ def delete_or_add_queue(job, params)
267
+ if params['delete']
268
+ job.delete
269
+ elsif params['add_to_queue']
270
+ job.add_to_queue
271
+ end
272
+ end
254
273
  end
255
274
  end