resque-cleaner 0.0.1 → 0.0.2
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.
- data/README.markdown +220 -3
- data/lib/resque-cleaner.rb +1 -0
- data/lib/resque_cleaner.rb +43 -57
- data/test/resque_cleaner_test.rb +9 -9
- metadata +5 -4
data/README.markdown
CHANGED
@@ -1,6 +1,223 @@
|
|
1
|
-
|
1
|
+
ResqueCleaner
|
2
2
|
==============
|
3
3
|
|
4
|
-
|
4
|
+
[github.com/ono/resque-cleaner](https://github.com/ono/resque-cleaner)
|
5
|
+
|
6
|
+
|
7
|
+
Description
|
8
|
+
-----------
|
9
|
+
|
10
|
+
ResqueCleaner is a [Resque](https://github.com/defunkt/resque) plugin which
|
11
|
+
helps you to deal with failed jobs of Resque by:
|
12
|
+
|
13
|
+
* Showing stats of failed jobs
|
14
|
+
* Retrying failed jobs
|
15
|
+
* Removing failed jobs
|
16
|
+
* Filtering failed jobs
|
17
|
+
|
18
|
+
Although ResqueCleaner hasn't integrated with Resque's web-based interface yet,
|
19
|
+
it is pretty easy to use on irb(console).
|
20
|
+
|
21
|
+
|
22
|
+
Installation
|
23
|
+
------------
|
24
|
+
|
25
|
+
Install as a gem:
|
26
|
+
$ gem install resque-cleaner
|
27
|
+
|
28
|
+
|
29
|
+
Usage
|
30
|
+
-----
|
31
|
+
|
32
|
+
**Create Instance**
|
33
|
+
|
34
|
+
> cleaner = Resque::Plugins::ResqueCleaner.new
|
35
|
+
|
36
|
+
**Show Stats**
|
37
|
+
|
38
|
+
Shows stats of failed jobs grouped by date.
|
39
|
+
|
40
|
+
> cleaner.stats_by_date
|
41
|
+
2009/03/13: 6
|
42
|
+
2009/11/13: 14
|
43
|
+
2010/08/13: 22
|
44
|
+
total: 42
|
45
|
+
=> {'2009/03/10' => 6, ...}
|
46
|
+
|
47
|
+
You could also group them by class.
|
48
|
+
|
49
|
+
> cleaner.stats_by_class
|
50
|
+
BadJob: 3
|
51
|
+
HorribleJob: 7
|
52
|
+
total: 10
|
53
|
+
=> {'BadJob' => 3, ...}
|
54
|
+
|
55
|
+
You can get the ones filtered with a block: it targets only jobs which the block
|
56
|
+
evaluetes true. e.g. Show stats only of jobs entried with some arguments.
|
57
|
+
|
58
|
+
> cleaner.stats_by_date{|job| job["payload"]["args"].size > 0}
|
59
|
+
2009/03/13: 3
|
60
|
+
2009/11/13: 7
|
61
|
+
2010/08/13: 11
|
62
|
+
total: 22
|
63
|
+
=> {'2009/03/10' => 3, ...}
|
64
|
+
|
65
|
+
**Retry(Requeue) Jobs**
|
66
|
+
|
67
|
+
You can retry all failed jobs with this method.
|
68
|
+
|
69
|
+
> cleaner.requeue
|
70
|
+
|
71
|
+
Of course, you can filter jobs with a block; it requeues only jobs which the
|
72
|
+
block evaluates true. e.g. Retry only jobs with some arguments.
|
73
|
+
|
74
|
+
> cleaner.requeue{ |job| job["payload"]["args"].size > 0}
|
75
|
+
|
76
|
+
The job hash is extended with a module which defines some useful methods. You
|
77
|
+
can use it in the blcok. e.g. Retry only jobs entried within a day.
|
78
|
+
|
79
|
+
> cleaner.requeue {|j| j.after?(1.day.ago)}
|
80
|
+
|
81
|
+
e.g. Retry EmailJob entried with arguments within 3 days:
|
82
|
+
|
83
|
+
> cleaner.requeue {|j| j["payload"]["args"]>0 && j.after?(3.days.ago) && j.klass?(EmailJob)}
|
84
|
+
|
85
|
+
See Helper Methods bellow for more.
|
86
|
+
|
87
|
+
NOTE:
|
88
|
+
[1.day.ago](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/numeric/time.rb)
|
89
|
+
is not in standard library. It is equivalent to `Time.now - 60*60*24*3`.
|
90
|
+
|
91
|
+
**Clear Jobs**
|
92
|
+
|
93
|
+
You can clear all failed jobs with this method.
|
94
|
+
|
95
|
+
> cleaner.clear
|
96
|
+
|
97
|
+
Like you can do with the retry method, the clear metod takes a block. Here are
|
98
|
+
some examples:
|
99
|
+
|
100
|
+
> cleaner.clear {|j| j.retried?}
|
101
|
+
=> clears all jobs already retried and returns number of the jobs.
|
102
|
+
|
103
|
+
> cleaner.clear {|j| j.queue?(:low) && j.before?('2010-10-10')}
|
104
|
+
=> clears all jobs entried in :low queue before 10th October, 2010.
|
105
|
+
|
106
|
+
> cleaner.clear {|j| j["exception"]=="RuntimeError" && j.queue?(:low)}
|
107
|
+
=> clears all jobs raised RuntimeError and queued :low queue
|
108
|
+
|
109
|
+
**Retry and Clear Jobs**
|
110
|
+
|
111
|
+
You can retry(requeue) and clear failed jobs at the same time; just pass true
|
112
|
+
as an argument. e.g. Retry EmailJob and remove from failed jobs.
|
113
|
+
|
114
|
+
> cleaner.requeue(true) {|j| j.klass?(EmailJob)}
|
115
|
+
|
116
|
+
**Select Jobs**
|
117
|
+
|
118
|
+
You can just select the jobs of course. Here are some examples:
|
119
|
+
|
120
|
+
> cleaner.select {|j| j["exception"]=="RuntimeError"}
|
121
|
+
> cleaner.select {|j| j.after?(2.days.ago)}
|
122
|
+
> cleaner.select #=> returns all jobs
|
123
|
+
|
124
|
+
**Helper Methods**
|
125
|
+
|
126
|
+
Here is a list of methods a job extended.
|
127
|
+
|
128
|
+
retried?: returns true if the job has already been retried.
|
129
|
+
requeued?: alias of retried?.
|
130
|
+
before?(time): returns true if the job failed before the time.
|
131
|
+
after?(time): returns true if the job failed after the time.
|
132
|
+
klass?(klass_or_name): returns true if the class of job matches.
|
133
|
+
queue?(queue_name): returns true if the queue of job matches.
|
134
|
+
|
135
|
+
|
136
|
+
Failed Job
|
137
|
+
-----------
|
138
|
+
|
139
|
+
I show a sample of failed job here; it might help you when you write a block for
|
140
|
+
filtering failed jobs.
|
141
|
+
|
142
|
+
{"failed_at": "2009/03/13 00:00:00",
|
143
|
+
"payload": {"args": ["Johnson"], "class": "BadJob"},
|
144
|
+
"queue": "jobs",
|
145
|
+
"worker": "localhost:7327:jobs,jobs2",
|
146
|
+
"exception": "RuntimeError",
|
147
|
+
"error": "Bad job, Johnson",
|
148
|
+
"backtrace":
|
149
|
+
["./test/test_helper.rb:108:in `perform'",
|
150
|
+
"/opt/local/lib/ruby/gems/1.8/gems/resque-1.10.0/lib/resque/job.rb:133:in `perform'",
|
151
|
+
"/opt/local/lib/ruby/gems/1.8/gems/resque-1.10.0/lib/resque/worker.rb:157:in `perform'",
|
152
|
+
"/opt/local/lib/ruby/gems/1.8/gems/resque-1.10.0/lib/resque/worker.rb:124:in `work'",
|
153
|
+
"....(omitted)....",
|
154
|
+
"./test/test_helper.rb:41",
|
155
|
+
"test/resque_cleaner_test.rb:3"]
|
156
|
+
}
|
157
|
+
|
158
|
+
|
159
|
+
Limiter
|
160
|
+
-------
|
161
|
+
|
162
|
+
ResqueCleaner expects a disaster situation like a huge number of failed jobs are
|
163
|
+
out there. Since ResqueCleaner's filter function is running on your application
|
164
|
+
process but on your Redis, it would not respond ages if you try to deal with all
|
165
|
+
of those jobs.
|
166
|
+
|
167
|
+
ResqueCleaner supposes recent jobs are more important than old jobs. Therefore
|
168
|
+
ResqueCleaner deals with **ONLY LAST X(default=1000)** jobs. In that way, you
|
169
|
+
don't have to worry that your block for filtering might be stuck. You can change
|
170
|
+
its setting through `limiter` attribute. I will show you how it works with a
|
171
|
+
sample situation.
|
172
|
+
|
173
|
+
**Sample Situation**
|
174
|
+
|
175
|
+
* Number of failed jobs: 100,000
|
176
|
+
|
177
|
+
Default limiter is 1000 so that it returns 1000 as a count.
|
178
|
+
|
179
|
+
> cleaner.limiter.count
|
180
|
+
=> 1,000
|
181
|
+
> cleaner.failure.count
|
182
|
+
=> 100,000
|
183
|
+
|
184
|
+
You could know if the limiter is on with on? method.
|
185
|
+
|
186
|
+
> cleaner.limiter.on?
|
187
|
+
=> true
|
188
|
+
|
189
|
+
You can change the maximum number of the limiter with maximum attribute.
|
190
|
+
|
191
|
+
> cleaner.limiter.maxmum = 3000
|
192
|
+
=> 3,000
|
193
|
+
> cleaner.limiter.count
|
194
|
+
=> 3,000
|
195
|
+
> cleaner.limiter.on?
|
196
|
+
=> true
|
197
|
+
|
198
|
+
With limiter, ResqueClener's filtering targets only the last X(3000 in this
|
199
|
+
sampe) failed jobs.
|
200
|
+
|
201
|
+
> cleaner.select.size
|
202
|
+
=> 3,000
|
203
|
+
|
204
|
+
The clear\_stale method deletes all jobs entried prior to the last X(3000 in
|
205
|
+
this sample) failed jobs. This calls Redis API and no iteration occurs on Ruby
|
206
|
+
application; it should be quick even if there are huge number of failed jobs.
|
207
|
+
|
208
|
+
> cleaner.clear_stale
|
209
|
+
> cleaner.failure.count
|
210
|
+
=> 3,000
|
211
|
+
> cleaner.limiter.count
|
212
|
+
=> 3,000
|
213
|
+
> cleaner.limiter.on?
|
214
|
+
=> false
|
215
|
+
|
216
|
+
TODO
|
217
|
+
----
|
218
|
+
|
219
|
+
* Integration with Resque's sinatra based front end.
|
220
|
+
* More stats.
|
221
|
+
|
222
|
+
Any suggestion or idea are welcomed.
|
5
223
|
|
6
|
-
(This README will be update in days.)
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'resque_cleaner'
|
data/lib/resque_cleaner.rb
CHANGED
@@ -35,42 +35,37 @@ module Resque
|
|
35
35
|
|
36
36
|
# Stats by date.
|
37
37
|
def stats_by_date(&block)
|
38
|
-
jobs = select(&block)
|
39
|
-
summary = {}
|
38
|
+
jobs, stats = select(&block), {}
|
40
39
|
jobs.each do |job|
|
41
40
|
date = job["failed_at"][0,10]
|
42
|
-
|
43
|
-
|
41
|
+
stats[date] ||= 0
|
42
|
+
stats[date] += 1
|
44
43
|
end
|
45
44
|
|
46
|
-
if print?
|
47
|
-
|
48
|
-
summary.keys.sort.each do |k|
|
49
|
-
log "%s: %4d" % [k,summary[k]]
|
50
|
-
end
|
51
|
-
log "%10s: %4d" % ["total", @limiter.count]
|
52
|
-
end
|
53
|
-
summary
|
45
|
+
print_stats(stats) if print?
|
46
|
+
stats
|
54
47
|
end
|
55
48
|
|
56
49
|
# Stats by class.
|
57
50
|
def stats_by_class(&block)
|
58
|
-
jobs = select(&block)
|
59
|
-
summary = {}
|
51
|
+
jobs, stats = select(&block), {}
|
60
52
|
jobs.each do |job|
|
61
53
|
klass = job["payload"]["class"]
|
62
|
-
|
63
|
-
|
54
|
+
stats[klass] ||= 0
|
55
|
+
stats[klass] += 1
|
64
56
|
end
|
65
57
|
|
66
|
-
if print?
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
58
|
+
print_stats(stats) if print?
|
59
|
+
stats
|
60
|
+
end
|
61
|
+
|
62
|
+
# Print stats
|
63
|
+
def print_stats(stats)
|
64
|
+
log too_many_message if @limiter.on?
|
65
|
+
stats.keys.sort.each do |k|
|
66
|
+
log "%15s: %4d" % [k,stats[k]]
|
72
67
|
end
|
73
|
-
|
68
|
+
log "%15s: %4d" % ["total", @limiter.count]
|
74
69
|
end
|
75
70
|
|
76
71
|
# Returns every jobs for which block evaluates to true.
|
@@ -132,49 +127,39 @@ module Resque
|
|
132
127
|
c
|
133
128
|
end
|
134
129
|
|
135
|
-
#
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
# cleaner.select &cleaner.proc{|j| j["exception"]=="RunTimeError"}.klass(EmailJob)
|
142
|
-
# #=> Selects all EmailJob failed with RunTimeError.
|
143
|
-
def proc(&block)
|
144
|
-
FilterProc.new(&block)
|
145
|
-
end
|
146
|
-
|
147
|
-
# Provides typical proc you can filter jobs.
|
148
|
-
class FilterProc < Proc
|
149
|
-
def retried
|
150
|
-
FilterProc.new {|job| self.call(job) && job['retried_at'].blank?}
|
130
|
+
# Exntends job(Hash instance) with some helper methods.
|
131
|
+
module FailedJobEx
|
132
|
+
# Returns true if the job has been already retried. Otherwise returns
|
133
|
+
# false.
|
134
|
+
def retried?
|
135
|
+
self['retried_at'].blank?
|
151
136
|
end
|
152
|
-
alias :requeued :retried
|
137
|
+
alias :requeued? :retried?
|
153
138
|
|
154
|
-
|
139
|
+
# Returns true if the job processed(failed) before the given time.
|
140
|
+
# Otherwise returns false.
|
141
|
+
# You can pass Time object or String.
|
142
|
+
def before?(time)
|
155
143
|
time = Time.parse(time) if time.is_a?(String)
|
156
|
-
|
144
|
+
Time.parse(self['failed_at']) < time
|
157
145
|
end
|
158
146
|
|
159
|
-
|
147
|
+
# Returns true if the job processed(failed) after the given time.
|
148
|
+
# Otherwise returns false.
|
149
|
+
# You can pass Time object or String.
|
150
|
+
def after?(time)
|
160
151
|
time = Time.parse(time) if time.is_a?(String)
|
161
|
-
|
162
|
-
end
|
163
|
-
|
164
|
-
def klass(klass_or_name)
|
165
|
-
FilterProc.new {|job| self.call(job) && job["payload"]["class"] == klass_or_name.to_s}
|
152
|
+
Time.parse(self['failed_at']) >= time
|
166
153
|
end
|
167
154
|
|
168
|
-
|
169
|
-
|
155
|
+
# Returns true if the class of the job matches. Otherwise returns false.
|
156
|
+
def klass?(klass_or_name)
|
157
|
+
self["payload"]["class"] == klass_or_name.to_s
|
170
158
|
end
|
171
159
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
else
|
176
|
-
super {|job| true}
|
177
|
-
end
|
160
|
+
# Returns true if the queue of the job matches. Otherwise returns false.
|
161
|
+
def queue?(queue)
|
162
|
+
self["queue"] == queue.to_s
|
178
163
|
end
|
179
164
|
end
|
180
165
|
|
@@ -214,11 +199,12 @@ module Resque
|
|
214
199
|
end
|
215
200
|
end
|
216
201
|
|
217
|
-
#
|
202
|
+
# Wraps Resque's all and returns always array.
|
218
203
|
def all(index=0,count=1)
|
219
204
|
jobs = @cleaner.failure.all( index, count)
|
220
205
|
jobs = [] unless jobs
|
221
206
|
jobs = [jobs] unless jobs.is_a?(Array)
|
207
|
+
jobs.each{|j| j.extend FailedJobEx}
|
222
208
|
jobs
|
223
209
|
end
|
224
210
|
|
data/test/resque_cleaner_test.rb
CHANGED
@@ -104,29 +104,29 @@ context "ResqueCleaner" do
|
|
104
104
|
assert_equal Time.parse(@cleaner.failure_jobs[0]['failed_at']), Time.parse('2010-08-13')
|
105
105
|
end
|
106
106
|
|
107
|
-
test "
|
107
|
+
test "FailedJobEx module extends job and provides some useful methods" do
|
108
108
|
# before 2009-04-01
|
109
|
-
ret = @cleaner.select
|
109
|
+
ret = @cleaner.select {|j| j.before?('2009-04-01')}
|
110
110
|
assert_equal 6, ret.size
|
111
111
|
|
112
112
|
# after 2010-01-01
|
113
|
-
ret = @cleaner.select
|
113
|
+
ret = @cleaner.select {|j| j.after?('2010-01-01')}
|
114
114
|
assert_equal 22, ret.size
|
115
115
|
|
116
116
|
# filter by class
|
117
|
-
ret = @cleaner.select
|
117
|
+
ret = @cleaner.select {|j| j.klass?(BadJobWithSyntaxError)}
|
118
118
|
assert_equal 7, ret.size
|
119
119
|
|
120
120
|
# filter by queue
|
121
|
-
ret = @cleaner.select
|
121
|
+
ret = @cleaner.select {|j| j.queue?(:jobs2)}
|
122
122
|
assert_equal 20, ret.size
|
123
123
|
|
124
|
-
#
|
125
|
-
ret = @cleaner.select
|
124
|
+
# combination
|
125
|
+
ret = @cleaner.select {|j| j.queue?(:jobs2) && j.before?('2009-12-01')}
|
126
126
|
assert_equal 9, ret.size
|
127
127
|
|
128
|
-
#
|
129
|
-
ret = @cleaner.select
|
128
|
+
# combination 2
|
129
|
+
ret = @cleaner.select {|j| j['payload']['args']==['Jason'] && j.queue?(:jobs2)}
|
130
130
|
assert_equal 13, ret.size
|
131
131
|
end
|
132
132
|
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: resque-cleaner
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 2
|
10
|
+
version: 0.0.2
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Tatsuya Ono
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-11-
|
18
|
+
date: 2010-11-19 00:00:00 +00:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -46,6 +46,7 @@ files:
|
|
46
46
|
- README.markdown
|
47
47
|
- Rakefile
|
48
48
|
- LICENSE
|
49
|
+
- lib/resque-cleaner.rb
|
49
50
|
- lib/resque_cleaner.rb
|
50
51
|
- test/redis-test.conf
|
51
52
|
- test/resque_cleaner_test.rb
|