resqueue 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/HISTORY.md +488 -0
  3. data/LICENSE +20 -0
  4. data/README.markdown +920 -0
  5. data/Rakefile +57 -0
  6. data/bin/resque +81 -0
  7. data/bin/resque-web +31 -0
  8. data/lib/resque.rb +578 -0
  9. data/lib/resque/data_store.rb +326 -0
  10. data/lib/resque/errors.rb +21 -0
  11. data/lib/resque/failure.rb +119 -0
  12. data/lib/resque/failure/airbrake.rb +33 -0
  13. data/lib/resque/failure/base.rb +73 -0
  14. data/lib/resque/failure/multiple.rb +68 -0
  15. data/lib/resque/failure/redis.rb +128 -0
  16. data/lib/resque/failure/redis_multi_queue.rb +104 -0
  17. data/lib/resque/helpers.rb +48 -0
  18. data/lib/resque/job.rb +296 -0
  19. data/lib/resque/log_formatters/quiet_formatter.rb +7 -0
  20. data/lib/resque/log_formatters/verbose_formatter.rb +7 -0
  21. data/lib/resque/log_formatters/very_verbose_formatter.rb +8 -0
  22. data/lib/resque/logging.rb +18 -0
  23. data/lib/resque/plugin.rb +78 -0
  24. data/lib/resque/server.rb +299 -0
  25. data/lib/resque/server/helpers.rb +64 -0
  26. data/lib/resque/server/public/favicon.ico +0 -0
  27. data/lib/resque/server/public/idle.png +0 -0
  28. data/lib/resque/server/public/jquery-1.12.4.min.js +5 -0
  29. data/lib/resque/server/public/jquery.relatize_date.js +95 -0
  30. data/lib/resque/server/public/poll.png +0 -0
  31. data/lib/resque/server/public/ranger.js +78 -0
  32. data/lib/resque/server/public/reset.css +44 -0
  33. data/lib/resque/server/public/style.css +91 -0
  34. data/lib/resque/server/public/working.png +0 -0
  35. data/lib/resque/server/test_helper.rb +19 -0
  36. data/lib/resque/server/views/error.erb +1 -0
  37. data/lib/resque/server/views/failed.erb +29 -0
  38. data/lib/resque/server/views/failed_job.erb +50 -0
  39. data/lib/resque/server/views/failed_queues_overview.erb +24 -0
  40. data/lib/resque/server/views/key_sets.erb +17 -0
  41. data/lib/resque/server/views/key_string.erb +11 -0
  42. data/lib/resque/server/views/layout.erb +44 -0
  43. data/lib/resque/server/views/next_more.erb +22 -0
  44. data/lib/resque/server/views/overview.erb +4 -0
  45. data/lib/resque/server/views/queues.erb +58 -0
  46. data/lib/resque/server/views/stats.erb +62 -0
  47. data/lib/resque/server/views/workers.erb +111 -0
  48. data/lib/resque/server/views/working.erb +72 -0
  49. data/lib/resque/stat.rb +58 -0
  50. data/lib/resque/tasks.rb +72 -0
  51. data/lib/resque/thread_signal.rb +45 -0
  52. data/lib/resque/vendor/utf8_util.rb +26 -0
  53. data/lib/resque/vendor/utf8_util/utf8_util_18.rb +91 -0
  54. data/lib/resque/vendor/utf8_util/utf8_util_19.rb +6 -0
  55. data/lib/resque/version.rb +3 -0
  56. data/lib/resque/worker.rb +892 -0
  57. data/lib/resqueue.rb +4 -0
  58. data/lib/tasks/redis.rake +161 -0
  59. data/lib/tasks/resque.rake +2 -0
  60. metadata +197 -0
@@ -0,0 +1,296 @@
1
+ module Resque
2
+ # A Resque::Job represents a unit of work. Each job lives on a
3
+ # single queue and has an associated payload object. The payload
4
+ # is a hash with two attributes: `class` and `args`. The `class` is
5
+ # the name of the Ruby class which should be used to run the
6
+ # job. The `args` are an array of arguments which should be passed
7
+ # to the Ruby class's `perform` class-level method.
8
+ #
9
+ # You can manually run a job using this code:
10
+ #
11
+ # job = Resque::Job.reserve(:high)
12
+ # klass = Resque::Job.constantize(job.payload['class'])
13
+ # klass.perform(*job.payload['args'])
14
+ class Job
15
+ include Helpers
16
+ extend Helpers
17
+ def redis
18
+ Resque.redis
19
+ end
20
+ alias :data_store :redis
21
+
22
+ def self.redis
23
+ Resque.redis
24
+ end
25
+
26
+ def self.data_store
27
+ self.redis
28
+ end
29
+
30
+ # Given a Ruby object, returns a string suitable for storage in a
31
+ # queue.
32
+ def encode(object)
33
+ Resque.encode(object)
34
+ end
35
+
36
+ # Given a string, returns a Ruby object.
37
+ def decode(object)
38
+ Resque.decode(object)
39
+ end
40
+
41
+ # Given a Ruby object, returns a string suitable for storage in a
42
+ # queue.
43
+ def self.encode(object)
44
+ Resque.encode(object)
45
+ end
46
+
47
+ # Given a string, returns a Ruby object.
48
+ def self.decode(object)
49
+ Resque.decode(object)
50
+ end
51
+
52
+ # Given a word with dashes, returns a camel cased version of it.
53
+ def classify(dashed_word)
54
+ Resque.classify(dashed_word)
55
+ end
56
+
57
+ # Tries to find a constant with the name specified in the argument string
58
+ def constantize(camel_cased_word)
59
+ Resque.constantize(camel_cased_word)
60
+ end
61
+
62
+ # Raise Resque::Job::DontPerform from a before_perform hook to
63
+ # abort the job.
64
+ DontPerform = Class.new(StandardError)
65
+
66
+ # The worker object which is currently processing this job.
67
+ attr_accessor :worker
68
+
69
+ # The name of the queue from which this job was pulled (or is to be
70
+ # placed)
71
+ attr_reader :queue
72
+
73
+ # This job's associated payload object.
74
+ attr_reader :payload
75
+
76
+ def initialize(queue, payload)
77
+ @queue = queue
78
+ @payload = payload
79
+ @failure_hooks_ran = false
80
+ end
81
+
82
+ # Creates a job by placing it on a queue. Expects a string queue
83
+ # name, a string class name, and an optional array of arguments to
84
+ # pass to the class' `perform` method.
85
+ #
86
+ # Raises an exception if no queue or class is given.
87
+ def self.create(queue, klass, *args)
88
+ Resque.validate(klass, queue)
89
+
90
+ if Resque.inline?
91
+ # Instantiating a Resque::Job and calling perform on it so callbacks run
92
+ # decode(encode(args)) to ensure that args are normalized in the same manner as a non-inline job
93
+ new(:inline, {'class' => klass, 'args' => decode(encode(args))}).perform
94
+ else
95
+ Resque.push(queue, :class => klass.to_s, :args => args)
96
+ end
97
+ end
98
+
99
+ # Removes a job from a queue. Expects a string queue name, a
100
+ # string class name, and, optionally, args.
101
+ #
102
+ # Returns the number of jobs destroyed.
103
+ #
104
+ # If no args are provided, it will remove all jobs of the class
105
+ # provided.
106
+ #
107
+ # That is, for these two jobs:
108
+ #
109
+ # { 'class' => 'UpdateGraph', 'args' => ['defunkt'] }
110
+ # { 'class' => 'UpdateGraph', 'args' => ['mojombo'] }
111
+ #
112
+ # The following call will remove both:
113
+ #
114
+ # Resque::Job.destroy(queue, 'UpdateGraph')
115
+ #
116
+ # Whereas specifying args will only remove the 2nd job:
117
+ #
118
+ # Resque::Job.destroy(queue, 'UpdateGraph', 'mojombo')
119
+ #
120
+ # This method can be potentially very slow and memory intensive,
121
+ # depending on the size of your queue, as it loads all jobs into
122
+ # a Ruby array before processing.
123
+ def self.destroy(queue, klass, *args)
124
+ klass = klass.to_s
125
+ destroyed = 0
126
+
127
+ if args.empty?
128
+ data_store.everything_in_queue(queue).each do |string|
129
+ if decode(string)['class'] == klass
130
+ destroyed += data_store.remove_from_queue(queue,string).to_i
131
+ end
132
+ end
133
+ else
134
+ destroyed += data_store.remove_from_queue(queue, encode(:class => klass, :args => args))
135
+ end
136
+
137
+ destroyed
138
+ end
139
+
140
+ # Given a string queue name, returns an instance of Resque::Job
141
+ # if any jobs are available. If not, returns nil.
142
+ def self.reserve(queue)
143
+ return unless payload = Resque.pop(queue)
144
+ new(queue, payload)
145
+ end
146
+
147
+ # Attempts to perform the work represented by this job instance.
148
+ # Calls #perform on the class given in the payload with the
149
+ # arguments given in the payload.
150
+ def perform
151
+ job = payload_class
152
+ job_args = args || []
153
+ job_was_performed = false
154
+
155
+ begin
156
+ # Execute before_perform hook. Abort the job gracefully if
157
+ # Resque::DontPerform is raised.
158
+ begin
159
+ before_hooks.each do |hook|
160
+ job.send(hook, *job_args)
161
+ end
162
+ rescue DontPerform
163
+ return false
164
+ end
165
+
166
+ # Execute the job. Do it in an around_perform hook if available.
167
+ if around_hooks.empty?
168
+ job.perform(*job_args)
169
+ job_was_performed = true
170
+ else
171
+ # We want to nest all around_perform plugins, with the last one
172
+ # finally calling perform
173
+ stack = around_hooks.reverse.inject(nil) do |last_hook, hook|
174
+ if last_hook
175
+ lambda do
176
+ job.send(hook, *job_args) { last_hook.call }
177
+ end
178
+ else
179
+ lambda do
180
+ job.send(hook, *job_args) do
181
+ result = job.perform(*job_args)
182
+ job_was_performed = true
183
+ result
184
+ end
185
+ end
186
+ end
187
+ end
188
+ stack.call
189
+ end
190
+
191
+ # Execute after_perform hook
192
+ after_hooks.each do |hook|
193
+ job.send(hook, *job_args)
194
+ end
195
+
196
+ # Return true if the job was performed
197
+ return job_was_performed
198
+
199
+ # If an exception occurs during the job execution, look for an
200
+ # on_failure hook then re-raise.
201
+ rescue Object => e
202
+ run_failure_hooks(e)
203
+ raise e
204
+ end
205
+ end
206
+
207
+ # Returns the actual class constant represented in this job's payload.
208
+ def payload_class
209
+ @payload_class ||= constantize(@payload['class'])
210
+ end
211
+
212
+ # Returns the payload class as a string without raising NameError
213
+ def payload_class_name
214
+ payload_class.to_s
215
+ rescue NameError
216
+ 'No Name'
217
+ end
218
+
219
+ def has_payload_class?
220
+ payload_class != Object
221
+ rescue NameError
222
+ false
223
+ end
224
+
225
+ # Returns an array of args represented in this job's payload.
226
+ def args
227
+ @payload['args']
228
+ end
229
+
230
+ # Given an exception object, hands off the needed parameters to
231
+ # the Failure module.
232
+ def fail(exception)
233
+ begin
234
+ run_failure_hooks(exception)
235
+ rescue Exception => e
236
+ raise e
237
+ ensure
238
+ Failure.create \
239
+ :payload => payload,
240
+ :exception => exception,
241
+ :worker => worker,
242
+ :queue => queue
243
+ end
244
+ end
245
+
246
+ # Creates an identical job, essentially placing this job back on
247
+ # the queue.
248
+ def recreate
249
+ self.class.create(queue, payload_class, *args)
250
+ end
251
+
252
+ # String representation
253
+ def inspect
254
+ obj = @payload
255
+ "(Job{%s} | %s | %s)" % [ @queue, obj['class'], obj['args'].inspect ]
256
+ end
257
+
258
+ # Equality
259
+ def ==(other)
260
+ queue == other.queue &&
261
+ payload_class == other.payload_class &&
262
+ args == other.args
263
+ end
264
+
265
+ def before_hooks
266
+ @before_hooks ||= Plugin.before_hooks(payload_class)
267
+ end
268
+
269
+ def around_hooks
270
+ @around_hooks ||= Plugin.around_hooks(payload_class)
271
+ end
272
+
273
+ def after_hooks
274
+ @after_hooks ||= Plugin.after_hooks(payload_class)
275
+ end
276
+
277
+ def failure_hooks
278
+ @failure_hooks ||= Plugin.failure_hooks(payload_class)
279
+ end
280
+
281
+ def run_failure_hooks(exception)
282
+ begin
283
+ job_args = args || []
284
+ if has_payload_class?
285
+ failure_hooks.each { |hook| payload_class.send(hook, exception, *job_args) } unless @failure_hooks_ran
286
+ end
287
+ rescue Exception => e
288
+ error_message = "Additional error (#{e.class}: #{e}) occurred in running failure hooks for job #{inspect}\n" \
289
+ "Original error that caused job failure was #{e.class}: #{exception.class}: #{exception.message}"
290
+ raise RuntimeError.new(error_message)
291
+ ensure
292
+ @failure_hooks_ran = true
293
+ end
294
+ end
295
+ end
296
+ end
@@ -0,0 +1,7 @@
1
+ module Resque
2
+ class QuietFormatter
3
+ def call(serverity, datetime, progname, msg)
4
+ ""
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Resque
2
+ class VerboseFormatter
3
+ def call(serverity, datetime, progname, msg)
4
+ "*** #{msg}\n"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ module Resque
2
+ class VeryVerboseFormatter
3
+ def call(serverity, datetime, progname, msg)
4
+ time = Time.now.strftime('%H:%M:%S %Y-%m-%d')
5
+ "** [#{time}] #$$: #{msg}\n"
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,18 @@
1
+ module Resque
2
+ # Include this module in classes you wish to have logging facilities
3
+ module Logging
4
+ module_function
5
+
6
+ # Thunk to the logger's own log method (if configured)
7
+ def self.log(severity, message)
8
+ Resque.logger.__send__(severity, message) if Resque.logger
9
+ end
10
+
11
+ # Log level aliases
12
+ def debug(message); Logging.log :debug, message; end
13
+ def info(message); Logging.log :info, message; end
14
+ def warn(message); Logging.log :warn, message; end
15
+ def error(message); Logging.log :error, message; end
16
+ def fatal(message); Logging.log :fatal, message; end
17
+ end
18
+ end
@@ -0,0 +1,78 @@
1
+ module Resque
2
+ module Plugin
3
+ extend self
4
+
5
+ LintError = Class.new(RuntimeError)
6
+
7
+ # Ensure that your plugin conforms to good hook naming conventions.
8
+ #
9
+ # Resque::Plugin.lint(MyResquePlugin)
10
+ def lint(plugin)
11
+ hooks = before_hooks(plugin) + around_hooks(plugin) + after_hooks(plugin)
12
+
13
+ hooks.each do |hook|
14
+ if hook.to_s.end_with?("perform")
15
+ raise LintError, "#{plugin}.#{hook} is not namespaced"
16
+ end
17
+ end
18
+
19
+ failure_hooks(plugin).each do |hook|
20
+ if hook.to_s.end_with?("failure")
21
+ raise LintError, "#{plugin}.#{hook} is not namespaced"
22
+ end
23
+ end
24
+ end
25
+
26
+ @job_methods = {}
27
+ def job_methods(job)
28
+ @job_methods[job] ||= job.methods.collect{|m| m.to_s}
29
+ end
30
+
31
+ # Given an object, and a method prefix, returns a list of methods prefixed
32
+ # with that name (hook names).
33
+ def get_hook_names(job, hook_method_prefix)
34
+ methods = (job.respond_to?(:hooks) && job.hooks) || job_methods(job)
35
+ methods.select{|m| m.start_with?(hook_method_prefix)}.sort
36
+ end
37
+
38
+ # Given an object, returns a list `before_perform` hook names.
39
+ def before_hooks(job)
40
+ get_hook_names(job, 'before_perform')
41
+ end
42
+
43
+ # Given an object, returns a list `around_perform` hook names.
44
+ def around_hooks(job)
45
+ get_hook_names(job, 'around_perform')
46
+ end
47
+
48
+ # Given an object, returns a list `after_perform` hook names.
49
+ def after_hooks(job)
50
+ get_hook_names(job, 'after_perform')
51
+ end
52
+
53
+ # Given an object, returns a list `on_failure` hook names.
54
+ def failure_hooks(job)
55
+ get_hook_names(job, 'on_failure')
56
+ end
57
+
58
+ # Given an object, returns a list `after_enqueue` hook names.
59
+ def after_enqueue_hooks(job)
60
+ get_hook_names(job, 'after_enqueue')
61
+ end
62
+
63
+ # Given an object, returns a list `before_enqueue` hook names.
64
+ def before_enqueue_hooks(job)
65
+ get_hook_names(job, 'before_enqueue')
66
+ end
67
+
68
+ # Given an object, returns a list `after_dequeue` hook names.
69
+ def after_dequeue_hooks(job)
70
+ get_hook_names(job, 'after_dequeue')
71
+ end
72
+
73
+ # Given an object, returns a list `before_dequeue` hook names.
74
+ def before_dequeue_hooks(job)
75
+ get_hook_names(job, 'before_dequeue')
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,299 @@
1
+ require 'sinatra/base'
2
+ require 'tilt/erb'
3
+ require 'resque'
4
+ require 'resque/version'
5
+ require 'time'
6
+ require 'yaml'
7
+
8
+ if defined? Encoding
9
+ Encoding.default_external = Encoding::UTF_8
10
+ end
11
+
12
+ module Resque
13
+ class Server < Sinatra::Base
14
+ require 'resque/server/helpers'
15
+
16
+ dir = File.dirname(File.expand_path(__FILE__))
17
+
18
+ set :views, "#{dir}/server/views"
19
+
20
+ if respond_to? :public_folder
21
+ set :public_folder, "#{dir}/server/public"
22
+ else
23
+ set :public, "#{dir}/server/public"
24
+ end
25
+
26
+ set :static, true
27
+
28
+ helpers do
29
+ include Rack::Utils
30
+ alias_method :h, :escape_html
31
+
32
+ def current_section
33
+ url_path request.path_info.sub('/','').split('/')[0].downcase
34
+ end
35
+
36
+ def current_page
37
+ url_path request.path_info.sub('/','')
38
+ end
39
+
40
+ def url_path(*path_parts)
41
+ [ url_prefix, path_prefix, path_parts ].join("/").squeeze('/')
42
+ end
43
+ alias_method :u, :url_path
44
+
45
+ def redirect_url_path(*path_parts)
46
+ [ path_prefix, path_parts ].join("/").squeeze('/')
47
+ end
48
+
49
+ def path_prefix
50
+ request.env['SCRIPT_NAME']
51
+ end
52
+
53
+ def class_if_current(path = '')
54
+ 'class="current"' if current_page[0, path.size] == path
55
+ end
56
+
57
+ def tab(name)
58
+ dname = name.to_s.downcase
59
+ path = url_path(dname)
60
+ "<li #{class_if_current(path)}><a href='#{path}'>#{name}</a></li>"
61
+ end
62
+
63
+ def tabs
64
+ Resque::Server.tabs
65
+ end
66
+
67
+ def url_prefix
68
+ Resque::Server.url_prefix
69
+ end
70
+
71
+ def redis_get_size(key)
72
+ case Resque.redis.type(key)
73
+ when 'none'
74
+ []
75
+ when 'list'
76
+ Resque.redis.llen(key)
77
+ when 'set'
78
+ Resque.redis.scard(key)
79
+ when 'string'
80
+ Resque.redis.get(key).length
81
+ when 'zset'
82
+ Resque.redis.zcard(key)
83
+ end
84
+ end
85
+
86
+ def redis_get_value_as_array(key, start=0)
87
+ case Resque.redis.type(key)
88
+ when 'none'
89
+ []
90
+ when 'list'
91
+ Resque.redis.lrange(key, start, start + 20)
92
+ when 'set'
93
+ Resque.redis.smembers(key)[start..(start + 20)]
94
+ when 'string'
95
+ [Resque.redis.get(key)]
96
+ when 'zset'
97
+ Resque.redis.zrange(key, start, start + 20)
98
+ end
99
+ end
100
+
101
+ def show_args(args)
102
+ Array(args).map do |a|
103
+ a.to_yaml
104
+ end.join("\n")
105
+ end
106
+
107
+ def worker_hosts
108
+ @worker_hosts ||= worker_hosts!
109
+ end
110
+
111
+ def worker_hosts!
112
+ hosts = Hash.new { [] }
113
+
114
+ Resque.workers.each do |worker|
115
+ host, _ = worker.to_s.split(':')
116
+ hosts[host] += [worker.to_s]
117
+ end
118
+
119
+ hosts
120
+ end
121
+
122
+ def partial?
123
+ @partial
124
+ end
125
+
126
+ def partial(template, local_vars = {})
127
+ @partial = true
128
+ erb(template.to_sym, {:layout => false}, local_vars)
129
+ ensure
130
+ @partial = false
131
+ end
132
+
133
+ def poll
134
+ if @polling
135
+ text = "Last Updated: #{Time.now.strftime("%H:%M:%S")}"
136
+ else
137
+ text = "<a href='#{u(request.path_info)}.poll' rel='poll'>Live Poll</a>"
138
+ end
139
+ "<p class='poll'>#{text}</p>"
140
+ end
141
+
142
+ end
143
+
144
+ def show(page, layout = true)
145
+ response["Cache-Control"] = "max-age=0, private, must-revalidate"
146
+ begin
147
+ erb page.to_sym, {:layout => layout}, :resque => Resque
148
+ rescue Errno::ECONNREFUSED
149
+ erb :error, {:layout => false}, :error => "Can't connect to Redis! (#{Resque.redis_id})"
150
+ end
151
+ end
152
+
153
+ def show_for_polling(page)
154
+ content_type "text/html"
155
+ @polling = true
156
+ show(page.to_sym, false).gsub(/\s{1,}/, ' ')
157
+ end
158
+
159
+ # to make things easier on ourselves
160
+ get "/?" do
161
+ redirect redirect_url_path(:overview)
162
+ end
163
+
164
+ %w( overview workers ).each do |page|
165
+ get "/#{page}.poll/?" do
166
+ show_for_polling(page)
167
+ end
168
+
169
+ get "/#{page}/:id.poll/?" do
170
+ show_for_polling(page)
171
+ end
172
+ end
173
+
174
+ %w( overview queues working workers key ).each do |page|
175
+ get "/#{page}/?" do
176
+ show page
177
+ end
178
+
179
+ get "/#{page}/:id/?" do
180
+ show page
181
+ end
182
+ end
183
+
184
+ post "/queues/:id/remove" do
185
+ Resque.remove_queue(params[:id])
186
+ redirect u('queues')
187
+ end
188
+
189
+ get "/failed/?" do
190
+ if Resque::Failure.url
191
+ redirect Resque::Failure.url
192
+ else
193
+ show :failed
194
+ end
195
+ end
196
+
197
+ get "/failed/:queue" do
198
+ if Resque::Failure.url
199
+ redirect Resque::Failure.url
200
+ else
201
+ show :failed
202
+ end
203
+ end
204
+
205
+ post "/failed/clear" do
206
+ Resque::Failure.clear
207
+ redirect u('failed')
208
+ end
209
+
210
+ post "/failed/:queue/clear" do
211
+ Resque::Failure.clear params[:queue]
212
+ redirect u('failed')
213
+ end
214
+
215
+ post "/failed/requeue/all" do
216
+ Resque::Failure.requeue_all
217
+ redirect u('failed')
218
+ end
219
+
220
+ post "/failed/:queue/requeue/all" do
221
+ Resque::Failure.requeue_queue Resque::Failure.job_queue_name(params[:queue])
222
+ redirect redirect_url_path("/failed/#{params[:queue]}")
223
+ end
224
+
225
+ get "/failed/requeue/:index/?" do
226
+ Resque::Failure.requeue(params[:index])
227
+ if request.xhr?
228
+ return Resque::Failure.all(params[:index])['retried_at']
229
+ else
230
+ redirect u('failed')
231
+ end
232
+ end
233
+
234
+ get "/failed/:queue/requeue/:index/?" do
235
+ Resque::Failure.requeue(params[:index], params[:queue])
236
+ if request.xhr?
237
+ return Resque::Failure.all(params[:index],1,params[:queue])['retried_at']
238
+ else
239
+ redirect url_path("/failed/#{params[:queue]}")
240
+ end
241
+ end
242
+
243
+ get "/failed/remove/:index/?" do
244
+ Resque::Failure.remove(params[:index])
245
+ redirect u('failed')
246
+ end
247
+
248
+ get "/failed/:queue/remove/:index/?" do
249
+ Resque::Failure.remove(params[:index], params[:queue])
250
+ redirect url_path("/failed/#{params[:queue]}")
251
+ end
252
+
253
+ get "/stats/?" do
254
+ redirect redirect_url_path("/stats/resque")
255
+ end
256
+
257
+ get "/stats/:id/?" do
258
+ show :stats
259
+ end
260
+
261
+ get "/stats/keys/:key/?" do
262
+ show :stats
263
+ end
264
+
265
+ get "/stats.txt/?" do
266
+ info = Resque.info
267
+
268
+ stats = []
269
+ stats << "resque.pending=#{info[:pending]}"
270
+ stats << "resque.processed+=#{info[:processed]}"
271
+ stats << "resque.failed+=#{info[:failed]}"
272
+ stats << "resque.workers=#{info[:workers]}"
273
+ stats << "resque.working=#{info[:working]}"
274
+
275
+ Resque.queues.each do |queue|
276
+ stats << "queues.#{queue}=#{Resque.size(queue)}"
277
+ end
278
+
279
+ content_type 'text/html'
280
+ stats.join "\n"
281
+ end
282
+
283
+ def resque
284
+ Resque
285
+ end
286
+
287
+ def self.tabs
288
+ @tabs ||= ["Overview", "Working", "Failed", "Queues", "Workers", "Stats"]
289
+ end
290
+
291
+ def self.url_prefix=(url_prefix)
292
+ @url_prefix = url_prefix
293
+ end
294
+
295
+ def self.url_prefix
296
+ (@url_prefix.nil? || @url_prefix.empty?) ? '' : @url_prefix + '/'
297
+ end
298
+ end
299
+ end