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 CHANGED
@@ -1,6 +1,223 @@
1
- Resque-Cleaner
1
+ ResqueCleaner
2
2
  ==============
3
3
 
4
- Resque-Cleaner is a Resque plugin which helps you to deal with failure jobs.
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'
@@ -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
- summary[date] ||= 0
43
- summary[date] += 1
41
+ stats[date] ||= 0
42
+ stats[date] += 1
44
43
  end
45
44
 
46
- if print?
47
- log too_many_message if @limiter.on?
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
- summary[klass] ||= 0
63
- summary[klass] += 1
54
+ stats[klass] ||= 0
55
+ stats[klass] += 1
64
56
  end
65
57
 
66
- if print?
67
- log too_many_message if @limiter.on?
68
- summary.keys.sort.each do |k|
69
- log "%15s: %4d" % [k,summary[k]]
70
- end
71
- log "%15s: %4d" % ["total", @limiter.count]
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
- summary
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
- # Returns Proc which you can add a useful condition easily.
136
- # e.g.
137
- # cleaner.clear &cleaner.proc.retried
138
- # #=> Clears all jobs retried.
139
- # cleaner.select &cleaner.proc.after(10.days.ago).klass(EmailJob)
140
- # #=> Selects all EmailJob failed within 10 days.
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
- def before(time)
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
- FilterProc.new {|job| self.call(job) && Time.parse(job['failed_at']) <= time}
144
+ Time.parse(self['failed_at']) < time
157
145
  end
158
146
 
159
- def after(time)
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
- FilterProc.new {|job| self.call(job) && Time.parse(job['failed_at']) >= time}
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
- def queue(queue)
169
- FilterProc.new {|job| self.call(job) && job["queue"] == queue.to_s}
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
- def self.new(&block)
173
- if block
174
- super
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
- # wraps Resque's all and returns always array.
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
 
@@ -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 "#proc gives you handy proc definitions" do
107
+ test "FailedJobEx module extends job and provides some useful methods" do
108
108
  # before 2009-04-01
109
- ret = @cleaner.select &@cleaner.proc.before('2009-04-01')
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 &@cleaner.proc.after('2010-01-01')
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 &@cleaner.proc.klass(BadJobWithSyntaxError)
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 &@cleaner.proc.queue(:jobs2)
121
+ ret = @cleaner.select {|j| j.queue?(:jobs2)}
122
122
  assert_equal 20, ret.size
123
123
 
124
- # you can chain
125
- ret = @cleaner.select &@cleaner.proc.queue(:jobs2).before('2009-12-01')
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
- # you can chain with your custom block
129
- ret = @cleaner.select &@cleaner.proc{|j| j['payload']['args']==['Jason']}.queue(:jobs2)
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: 29
4
+ hash: 27
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 1
10
- version: 0.0.1
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 00:00:00 +00:00
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