resqueue 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|