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.
- checksums.yaml +7 -0
- data/HISTORY.md +488 -0
- data/LICENSE +20 -0
- data/README.markdown +920 -0
- data/Rakefile +57 -0
- data/bin/resque +81 -0
- data/bin/resque-web +31 -0
- data/lib/resque.rb +578 -0
- data/lib/resque/data_store.rb +326 -0
- data/lib/resque/errors.rb +21 -0
- data/lib/resque/failure.rb +119 -0
- data/lib/resque/failure/airbrake.rb +33 -0
- data/lib/resque/failure/base.rb +73 -0
- data/lib/resque/failure/multiple.rb +68 -0
- data/lib/resque/failure/redis.rb +128 -0
- data/lib/resque/failure/redis_multi_queue.rb +104 -0
- data/lib/resque/helpers.rb +48 -0
- data/lib/resque/job.rb +296 -0
- data/lib/resque/log_formatters/quiet_formatter.rb +7 -0
- data/lib/resque/log_formatters/verbose_formatter.rb +7 -0
- data/lib/resque/log_formatters/very_verbose_formatter.rb +8 -0
- data/lib/resque/logging.rb +18 -0
- data/lib/resque/plugin.rb +78 -0
- data/lib/resque/server.rb +299 -0
- data/lib/resque/server/helpers.rb +64 -0
- data/lib/resque/server/public/favicon.ico +0 -0
- data/lib/resque/server/public/idle.png +0 -0
- data/lib/resque/server/public/jquery-1.12.4.min.js +5 -0
- data/lib/resque/server/public/jquery.relatize_date.js +95 -0
- data/lib/resque/server/public/poll.png +0 -0
- data/lib/resque/server/public/ranger.js +78 -0
- data/lib/resque/server/public/reset.css +44 -0
- data/lib/resque/server/public/style.css +91 -0
- data/lib/resque/server/public/working.png +0 -0
- data/lib/resque/server/test_helper.rb +19 -0
- data/lib/resque/server/views/error.erb +1 -0
- data/lib/resque/server/views/failed.erb +29 -0
- data/lib/resque/server/views/failed_job.erb +50 -0
- data/lib/resque/server/views/failed_queues_overview.erb +24 -0
- data/lib/resque/server/views/key_sets.erb +17 -0
- data/lib/resque/server/views/key_string.erb +11 -0
- data/lib/resque/server/views/layout.erb +44 -0
- data/lib/resque/server/views/next_more.erb +22 -0
- data/lib/resque/server/views/overview.erb +4 -0
- data/lib/resque/server/views/queues.erb +58 -0
- data/lib/resque/server/views/stats.erb +62 -0
- data/lib/resque/server/views/workers.erb +111 -0
- data/lib/resque/server/views/working.erb +72 -0
- data/lib/resque/stat.rb +58 -0
- data/lib/resque/tasks.rb +72 -0
- data/lib/resque/thread_signal.rb +45 -0
- data/lib/resque/vendor/utf8_util.rb +26 -0
- data/lib/resque/vendor/utf8_util/utf8_util_18.rb +91 -0
- data/lib/resque/vendor/utf8_util/utf8_util_19.rb +6 -0
- data/lib/resque/version.rb +3 -0
- data/lib/resque/worker.rb +892 -0
- data/lib/resqueue.rb +4 -0
- data/lib/tasks/redis.rake +161 -0
- data/lib/tasks/resque.rake +2 -0
- metadata +197 -0
data/lib/resque/job.rb
ADDED
@@ -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,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
|