resqueue 1.0.0

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 (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