resque-cleaner 0.1.1 → 0.2.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.
- data/CHANGELOG.md +6 -2
- data/README.markdown +17 -13
- data/lib/resque_cleaner/server/public/cleaner.css +61 -0
- data/lib/resque_cleaner/server/views/_limiter.erb +13 -0
- data/lib/resque_cleaner/server/views/_paginate.erb +50 -0
- data/lib/resque_cleaner/server/views/cleaner.erb +43 -0
- data/lib/resque_cleaner/server/views/cleaner_exec.erb +8 -0
- data/lib/resque_cleaner/server/views/cleaner_list.erb +169 -0
- data/lib/resque_cleaner/server.rb +205 -0
- data/lib/resque_cleaner.rb +4 -1
- data/test/resque_cleaner_test.rb +0 -13
- data/test/resque_web_test.rb +44 -0
- data/test/test_helper.rb +17 -0
- metadata +12 -8
data/CHANGELOG.md
CHANGED
data/README.markdown
CHANGED
@@ -8,16 +8,13 @@ Description
|
|
8
8
|
-----------
|
9
9
|
|
10
10
|
ResqueCleaner is a [Resque](https://github.com/defunkt/resque) plugin which
|
11
|
-
|
11
|
+
aims to help you to clean up failed jobs on Resque by:
|
12
12
|
|
13
13
|
* Showing stats of failed jobs
|
14
14
|
* Retrying failed jobs
|
15
15
|
* Removing failed jobs
|
16
16
|
* Filtering failed jobs
|
17
17
|
|
18
|
-
Although ResqueCleaner has not integrated with Resque's web-based interface yet,
|
19
|
-
it is pretty easy to use on irb(console).
|
20
|
-
|
21
18
|
|
22
19
|
Installation
|
23
20
|
------------
|
@@ -27,8 +24,22 @@ Install as a gem:
|
|
27
24
|
$ gem install resque-cleaner
|
28
25
|
|
29
26
|
|
30
|
-
|
31
|
-
|
27
|
+
Resque-Web integration
|
28
|
+
----------------------
|
29
|
+
|
30
|
+

|
31
|
+

|
32
|
+
|
33
|
+
You have to load ResqueCleaner to enable the Cleaner tab.
|
34
|
+
|
35
|
+
require 'resque-cleaner'
|
36
|
+
|
37
|
+
Console
|
38
|
+
-------
|
39
|
+
|
40
|
+
Hopefully a situation of your failed jobs is simple enough to get figured out through
|
41
|
+
the web interface. But, if not, a powerful filtering feature of ResqueCleaner may help
|
42
|
+
you to understand what is going on with your console(irb).
|
32
43
|
|
33
44
|
**Create Instance**
|
34
45
|
|
@@ -234,11 +245,4 @@ application; it should be quick even if there are huge number of failed jobs.
|
|
234
245
|
> cleaner.limiter.on?
|
235
246
|
=> false
|
236
247
|
|
237
|
-
TODO
|
238
|
-
----
|
239
|
-
|
240
|
-
* Integration with Resque's sinatra based front end.
|
241
|
-
* More stats.
|
242
|
-
|
243
|
-
Any suggestion or idea are welcomed.
|
244
248
|
|
@@ -0,0 +1,61 @@
|
|
1
|
+
|
2
|
+
#main .cleaner .title h1 { float: left }
|
3
|
+
|
4
|
+
#main .cleaner .sub_header {color: #888; font-size: 80%; font-weight: bold}
|
5
|
+
|
6
|
+
#main .cleaner .control_panel {
|
7
|
+
margin: 5px 0 10px; padding: 5px; background: #eee; display: inline-block;
|
8
|
+
-webkit-border-radius:5px; border:1px solid #ccc;
|
9
|
+
}
|
10
|
+
|
11
|
+
#main .cleaner #exec { color: black; }
|
12
|
+
#main .cleaner #exec select { margin-right: 20px }
|
13
|
+
#main .cleaner #exec a { margin-right: 5px}
|
14
|
+
#main .cleaner #exec a.disabled { color: #aaa}
|
15
|
+
|
16
|
+
#main .cleaner table.class_list { width: auto }
|
17
|
+
#main .cleaner td.number { text-align: right }
|
18
|
+
#main .cleaner tr.total { border-top: 3px double #ccc}
|
19
|
+
|
20
|
+
#main .cleaner form { float: none; margin: 0}
|
21
|
+
|
22
|
+
#main .cleaner .warning { margin: 30px 0; padding: 10px; background: #fdd; display: inline-block; }
|
23
|
+
|
24
|
+
#main .clearfix:after {
|
25
|
+
content: ".";
|
26
|
+
display: block;
|
27
|
+
height: 0;
|
28
|
+
clear: both;
|
29
|
+
visibility: hidden;
|
30
|
+
}
|
31
|
+
#main .cleaner ul.failed {
|
32
|
+
margin-bottom: 10px
|
33
|
+
}
|
34
|
+
|
35
|
+
#main .cleaner .list_info{
|
36
|
+
color: #999; text-align: center;
|
37
|
+
}
|
38
|
+
#main .cleaner .list_info a{
|
39
|
+
color: #666
|
40
|
+
}
|
41
|
+
#main .cleaner .list_info .list_summary{
|
42
|
+
vertical-align: baseline;
|
43
|
+
display: inline-block;
|
44
|
+
margin: 0; padding:0;
|
45
|
+
}
|
46
|
+
#main .cleaner ul.pagination {
|
47
|
+
display: inline-block;
|
48
|
+
vertical-align: baseline;
|
49
|
+
margin:0; padding:0;
|
50
|
+
}
|
51
|
+
#main .cleaner ul.pagination li {
|
52
|
+
list-style:none; float: left; padding: 0 3px;
|
53
|
+
}
|
54
|
+
#main .cleaner ul.pagination li.current {
|
55
|
+
color: #333; font-weight: bold;
|
56
|
+
}
|
57
|
+
#main .cleaner ul.pagination li.summary{
|
58
|
+
padding-right: 10px
|
59
|
+
}
|
60
|
+
|
61
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<div class="warning">
|
2
|
+
<p>
|
3
|
+
There are more than <%= @cleaner.limiter.maximum%> jobs. ResqueCleaner handles only recent <%= @cleaner.limiter.maximum %> jobs. See the Limiter section on <a href="https://github.com/ono/resque-cleaner" target="_blank">README</a> for more detail.
|
4
|
+
</p>
|
5
|
+
<p>
|
6
|
+
<form method="post" action="cleaner_stale">
|
7
|
+
<input type="hidden" name="action" value="clear_stale" />
|
8
|
+
<input type="submit" onclick="return confirm('Are you sure?')" value="Clear all jobs older than <%= @cleaner.limiter.count %>th job."/>
|
9
|
+
(<%= @cleaner.failure.count - @cleaner.limiter.maximum %> jobs will be cleared)
|
10
|
+
</form>
|
11
|
+
</p>
|
12
|
+
</div>
|
13
|
+
|
@@ -0,0 +1,50 @@
|
|
1
|
+
<div class="list_info clearfix">
|
2
|
+
|
3
|
+
<ul class="pagination">
|
4
|
+
<li class="summary">
|
5
|
+
<%= @paginate.first_index%> - <%= @paginate.last_index%> / <%= @paginate.total_size%>
|
6
|
+
</li>
|
7
|
+
<% if @paginate.max_page>1 %>
|
8
|
+
<% if @paginate.first_page? %>
|
9
|
+
<li class="prev off"><<newer</li>
|
10
|
+
<% else %>
|
11
|
+
<li class="prev"><a href="<%= @paginate.page_url(:prev)%>"><<newer</a></li>
|
12
|
+
<% end %>
|
13
|
+
|
14
|
+
<% 1.upto(6<@paginate.max_page ? 6 : @paginate.max_page) do |pg| %>
|
15
|
+
<% if @paginate.page==pg %>
|
16
|
+
<li class="current"><%=pg%></li>
|
17
|
+
<% else %>
|
18
|
+
<li><a href="<%= @paginate.page_url(pg)%>"><%=pg%></a></li>
|
19
|
+
<% end %>
|
20
|
+
<% end %>
|
21
|
+
|
22
|
+
<% if @paginate.max_page > 6 %>
|
23
|
+
<% if @paginate.max_page!=@paginate.page %>
|
24
|
+
<% if @paginate.page>7 %>
|
25
|
+
<li class="off">...</li>
|
26
|
+
<% end %>
|
27
|
+
<% if @paginate.page>6 %>
|
28
|
+
<li class="current"><%=@paginate.page%></li>
|
29
|
+
<% end %>
|
30
|
+
<% end %>
|
31
|
+
<% if @paginate.max_page>7 && @paginate.page+1!=@paginate.max_page %>
|
32
|
+
<li class="off">...</li>
|
33
|
+
<% end %>
|
34
|
+
|
35
|
+
<% if @paginate.max_page==@paginate.page %>
|
36
|
+
<li class="current"><%=@paginate.max_page%></li>
|
37
|
+
<% else %>
|
38
|
+
<li><a href="<%= @paginate.page_url(@paginate.max_page)%>"><%=@paginate.max_page%></a></li>
|
39
|
+
<% end %>
|
40
|
+
<% end %>
|
41
|
+
|
42
|
+
<% if @paginate.last_page? %>
|
43
|
+
<li class="next off">older>></li>
|
44
|
+
<% else %>
|
45
|
+
<li class="next"><a href="<%= @paginate.page_url(:next)%>">older>></a></li>
|
46
|
+
<% end %>
|
47
|
+
<% end %>
|
48
|
+
</ul>
|
49
|
+
</div>
|
50
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
<link href="/cleaner/public/cleaner.css" media="screen" rel="stylesheet" type="text/css">
|
2
|
+
|
3
|
+
<div class="cleaner">
|
4
|
+
<div class="title clearfix">
|
5
|
+
<h1>Job Classes Failed</h1>
|
6
|
+
</div>
|
7
|
+
|
8
|
+
<table class="class_list">
|
9
|
+
<tr>
|
10
|
+
<th>Class</th>
|
11
|
+
<th>Failed</th>
|
12
|
+
<th>In last 1 hour</th>
|
13
|
+
<th>In last 3 hours</th>
|
14
|
+
<th>In last 24 hours</th>
|
15
|
+
<th>In last 3 days</th>
|
16
|
+
<th>In last 7 days</th>
|
17
|
+
</tr>
|
18
|
+
<% @stats.each do |klass,count| %>
|
19
|
+
<tr>
|
20
|
+
<td><%= klass %></td>
|
21
|
+
<td class="number"><a href="/cleaner_list?c=<%=klass%>"><%= count["total"] %></a></td>
|
22
|
+
<td class="number"><a href="/cleaner_list?c=<%=klass%>&f=1"><%= count["1h"] %></a></td>
|
23
|
+
<td class="number"><a href="/cleaner_list?c=<%=klass%>&f=3"><%= count["3h"] %></a></td>
|
24
|
+
<td class="number"><a href="/cleaner_list?c=<%=klass%>&f=24"><%= count["1d"] %></a></td>
|
25
|
+
<td class="number"><a href="/cleaner_list?c=<%=klass%>&f=72"><%= count["3d"] %></a></td>
|
26
|
+
<td class="number"><a href="/cleaner_list?c=<%=klass%>&f=168"><%= count["7d"] %></a></td>
|
27
|
+
</tr>
|
28
|
+
<% end %>
|
29
|
+
<tr class="total">
|
30
|
+
<td>Total</td>
|
31
|
+
<td class="number"><a href="/cleaner_list"><%= @total["total"] %></a></td>
|
32
|
+
<td class="number"><a href="/cleaner_list?f=1"><%= @total["1h"] %></a></td>
|
33
|
+
<td class="number"><a href="/cleaner_list?f=3"><%= @total["3h"] %></a></td>
|
34
|
+
<td class="number"><a href="/cleaner_list?f=24"><%= @total["1d"] %></a></td>
|
35
|
+
<td class="number"><a href="/cleaner_list?f=72"><%= @total["3d"] %></a></td>
|
36
|
+
<td class="number"><a href="/cleaner_list?f=168"><%= @total["7d"] %></a></td>
|
37
|
+
</tr>
|
38
|
+
</table>
|
39
|
+
|
40
|
+
<% if @cleaner.limiter.on? %>
|
41
|
+
<%= erb File.read(ResqueCleaner::Server.erb_path("_limiter.erb")) %>
|
42
|
+
<% end %>
|
43
|
+
</div>
|
@@ -0,0 +1,169 @@
|
|
1
|
+
<link href="/cleaner/public/cleaner.css" media="screen" rel="stylesheet" type="text/css">
|
2
|
+
|
3
|
+
<!-- Many code was copied from failed.erb of the original resque -->
|
4
|
+
<div class="cleaner">
|
5
|
+
<div class="title clearfix">
|
6
|
+
<h1>Failed Jobs</h1>
|
7
|
+
</div>
|
8
|
+
|
9
|
+
<div class="clearfix">
|
10
|
+
<div class="control_panel sub_header">
|
11
|
+
<form method="get">
|
12
|
+
<span class="class_filter">
|
13
|
+
Class: <%= class_filter("filter_class","c",@klasses,@klass)%>
|
14
|
+
</span>
|
15
|
+
<span class="time_filter">
|
16
|
+
From: <%= time_filter("filter_from","f",@from)%>
|
17
|
+
</span>
|
18
|
+
<span class="time_filter">
|
19
|
+
To: <%= time_filter("filter_to","t",@to)%>
|
20
|
+
</span>
|
21
|
+
<input type="submit" value="Filter" />
|
22
|
+
</form>
|
23
|
+
</div>
|
24
|
+
</div>
|
25
|
+
|
26
|
+
<% if @count > 0 %>
|
27
|
+
<div class="clearfix">
|
28
|
+
<div class="control_panel sub_header">
|
29
|
+
<form method="post" id="exec" action="/cleaner_exec">
|
30
|
+
<input type="hidden" name="c" value="<%=@klass%>" />
|
31
|
+
<input type="hidden" name="f" value="<%=@from%>" />
|
32
|
+
<input type="hidden" name="t" value="<%=@to%>" />
|
33
|
+
<input type="hidden" name="p" value="<%=@paginate.page%>" />
|
34
|
+
<select id="form_action" name="action">
|
35
|
+
<option id="default_option" value="" selected="selected">-- Select Action --</option>
|
36
|
+
<option value="clear">Clear</option>
|
37
|
+
<option value="retry_and_clear">Retry and Clear</option>
|
38
|
+
<option value="retry">Retry</option>
|
39
|
+
</select>
|
40
|
+
<a href="#" id="select_all">select all</a>
|
41
|
+
<a href="#" id="reset_all">reset</a>
|
42
|
+
|
43
|
+
<% if @paginate.max_page > 1 %>
|
44
|
+
<input type="checkbox" name="select_all_pages" value="1" id="select_all_pages" />Select all <%=@count%> jobs
|
45
|
+
<% end %>
|
46
|
+
<input type="hidden" name="sha1" id="sha1_list" />
|
47
|
+
|
48
|
+
</form>
|
49
|
+
</div>
|
50
|
+
</div>
|
51
|
+
<% end %>
|
52
|
+
|
53
|
+
<% start = 0 %>
|
54
|
+
<% failed = @paginate.paginated_jobs%>
|
55
|
+
<% index = 0 %>
|
56
|
+
|
57
|
+
<% if @paginate.max_page > 0 %>
|
58
|
+
<%= erb File.read(ResqueCleaner::Server.erb_path("_paginate.erb")) %>
|
59
|
+
<ul class='failed'>
|
60
|
+
<%for job in failed%>
|
61
|
+
<% index += 1 %>
|
62
|
+
<li>
|
63
|
+
<dl>
|
64
|
+
<% if job.nil? %>
|
65
|
+
<dt>Error</dt>
|
66
|
+
<dd>Job <%= index%> could not be parsed; perhaps it contains invalid JSON?</dd>
|
67
|
+
<% else %>
|
68
|
+
<dt>
|
69
|
+
<input type="checkbox" id="<%=Digest::SHA1.hexdigest job.to_json %>" />
|
70
|
+
</dt>
|
71
|
+
<dd> </dd>
|
72
|
+
<dt>Worker</dt>
|
73
|
+
<dd>
|
74
|
+
<a href="<%= u(:workers, job['worker']) %>"><%= job['worker'].split(':')[0...2].join(':') %></a> on <b class='queue-tag'><%= job['queue'] %></b > at <b><span class="time"><%= job['failed_at'] %></span></b>
|
75
|
+
<% if job['retried_at'] %>
|
76
|
+
<div class='retried'>
|
77
|
+
Retried <b><span class="time"><%= job['retried_at'] %></span></b>
|
78
|
+
</div>
|
79
|
+
<% end %>
|
80
|
+
</dd>
|
81
|
+
<dt>Class</dt>
|
82
|
+
<dd><code><%= job['payload'] ? job['payload']['class'] : 'nil' %></code></dd>
|
83
|
+
<dt>Arguments</dt>
|
84
|
+
<dd><pre><%=h job['payload'] ? show_args(job['payload']['args']) : 'nil' %></pre></dd>
|
85
|
+
<dt>Exception</dt>
|
86
|
+
<dd><code><%= job['exception'] %></code></dd>
|
87
|
+
<dt>Error</dt>
|
88
|
+
<dd class='error'>
|
89
|
+
<% if job['backtrace'] %>
|
90
|
+
<a href="#" class="backtrace"><%= h(job['error']) %></a>
|
91
|
+
<pre style='display:none'><%=h job['backtrace'].join("\n") %></pre>
|
92
|
+
<% else %>
|
93
|
+
<%=h job['error'] %>
|
94
|
+
<% end %>
|
95
|
+
</dd>
|
96
|
+
<% end %>
|
97
|
+
</dl>
|
98
|
+
<div class='r'>
|
99
|
+
</div>
|
100
|
+
</li>
|
101
|
+
<%end%>
|
102
|
+
</ul>
|
103
|
+
<%= erb File.read(ResqueCleaner::Server.erb_path("_paginate.erb")) %>
|
104
|
+
<% else %>
|
105
|
+
Clean!
|
106
|
+
<% end %>
|
107
|
+
</div>
|
108
|
+
|
109
|
+
<script>
|
110
|
+
$(document).ready(function(){
|
111
|
+
$('#select_all_pages').click(function() {
|
112
|
+
updateCheckboxStaus();
|
113
|
+
});
|
114
|
+
|
115
|
+
$('#select_all').click(function() {
|
116
|
+
if (!$(this).hasClass('disabled')) {
|
117
|
+
$('.failed input').attr('checked','checked');
|
118
|
+
}
|
119
|
+
return false;
|
120
|
+
});
|
121
|
+
$('#reset_all').click(function() {
|
122
|
+
if (!$(this).hasClass('disabled')) {
|
123
|
+
$('.failed input').removeAttr('checked');
|
124
|
+
}
|
125
|
+
return false;
|
126
|
+
});
|
127
|
+
|
128
|
+
$('#form_action').change( function() {
|
129
|
+
if ($('#form_action option:selected').val()=='') return;
|
130
|
+
|
131
|
+
if ($('#select_all_pages:checked, .failed input:checked').length==0) {
|
132
|
+
alert('Please select jobs.');
|
133
|
+
$('#default_option').attr('selected','selected');
|
134
|
+
return false;
|
135
|
+
}
|
136
|
+
|
137
|
+
if (!confirm('Do you really want to proceed?')) {
|
138
|
+
$('#default_option').attr('selected','selected');
|
139
|
+
return false;
|
140
|
+
}
|
141
|
+
|
142
|
+
if ($('#select_all_pages:checked').length==0) {
|
143
|
+
setSha1();
|
144
|
+
}
|
145
|
+
$('#exec').submit();
|
146
|
+
});
|
147
|
+
});
|
148
|
+
|
149
|
+
function setSha1() {
|
150
|
+
var sha1 = "";
|
151
|
+
$('.failed input:checked').each( function() {
|
152
|
+
if (sha1.length>0) sha1 += ",";
|
153
|
+
sha1 += $(this).attr("id");
|
154
|
+
});
|
155
|
+
|
156
|
+
$('#sha1_list').val(sha1);
|
157
|
+
}
|
158
|
+
|
159
|
+
function updateCheckboxStaus() {
|
160
|
+
if ($('#select_all_pages:checked').length==1) {
|
161
|
+
$('#exec a').addClass('disabled');
|
162
|
+
$('.failed input').attr('disabled','disabled');
|
163
|
+
} else {
|
164
|
+
$('#exec a').removeClass('disabled');
|
165
|
+
$('.failed input').removeAttr('disabled');
|
166
|
+
}
|
167
|
+
};
|
168
|
+
|
169
|
+
</script>
|
@@ -0,0 +1,205 @@
|
|
1
|
+
# Extends Resque Web Based UI.
|
2
|
+
# Structure has been borrowed from ResqueScheduler.
|
3
|
+
module ResqueCleaner
|
4
|
+
module Server
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'yajl/json_gem'
|
8
|
+
rescue Exception
|
9
|
+
require 'json'
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.erb_path(filename)
|
13
|
+
File.join(File.dirname(__FILE__), 'server', 'views', filename)
|
14
|
+
end
|
15
|
+
def self.public_path(filename)
|
16
|
+
File.join(File.dirname(__FILE__), 'server', 'public', filename)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Pagination helpr for list page.
|
20
|
+
class Paginate
|
21
|
+
attr_accessor :page_size, :page, :jobs, :url
|
22
|
+
def initialize(jobs, url, page=1, page_size=20)
|
23
|
+
@jobs = jobs
|
24
|
+
@url = url
|
25
|
+
@page = (!page || page < 1) ? 1 : page
|
26
|
+
@page_size = 20
|
27
|
+
end
|
28
|
+
|
29
|
+
def first_index
|
30
|
+
@page_size * (@page-1)
|
31
|
+
end
|
32
|
+
|
33
|
+
def last_index
|
34
|
+
last = first_index + @page_size - 1
|
35
|
+
last > @jobs.size-1 ? @jobs.size-1 : last
|
36
|
+
end
|
37
|
+
|
38
|
+
def paginated_jobs
|
39
|
+
@jobs[first_index,@page_size]
|
40
|
+
end
|
41
|
+
|
42
|
+
def first_page?
|
43
|
+
@page <= 1
|
44
|
+
end
|
45
|
+
|
46
|
+
def last_page?
|
47
|
+
@page >= max_page
|
48
|
+
end
|
49
|
+
|
50
|
+
def page_url(page)
|
51
|
+
u = @url
|
52
|
+
u += @url.include?("?") ? "&" : "?"
|
53
|
+
if page.is_a?(Symbol)
|
54
|
+
page = @page - 1 if page==:prev
|
55
|
+
page = @page + 1 if page==:next
|
56
|
+
end
|
57
|
+
u += "p=#{page}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def total_size
|
61
|
+
@jobs.size
|
62
|
+
end
|
63
|
+
|
64
|
+
def max_page
|
65
|
+
((total_size-1) / @page_size) + 1
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.included(base)
|
70
|
+
require 'digest/sha1'
|
71
|
+
base.class_eval do
|
72
|
+
helpers do
|
73
|
+
def time_filter(id, name, value)
|
74
|
+
html = "<select id=\"#{id}\" name=\"#{name}\">"
|
75
|
+
html += "<option value=\"\">-</option>"
|
76
|
+
[1, 3, 6, 12, 24].each do |h|
|
77
|
+
selected = h.to_s == value ? 'selected="selected"' : ''
|
78
|
+
html += "<option #{selected} value=\"#{h}\">#{h} #{h==1 ? "hour" : "hours"} ago</option>"
|
79
|
+
end
|
80
|
+
[3, 7, 14, 28].each do |d|
|
81
|
+
selected = (d*24).to_s == value ? 'selected="selected"' : ''
|
82
|
+
html += "<option #{selected} value=\"#{d*24}\">#{d} days ago</option>"
|
83
|
+
end
|
84
|
+
html += "</select>"
|
85
|
+
end
|
86
|
+
|
87
|
+
def class_filter(id, name, klasses, value)
|
88
|
+
html = "<select id=\"#{id}\" name=\"#{name}\">"
|
89
|
+
html += "<option value=\"\">-</option>"
|
90
|
+
klasses.each do |k|
|
91
|
+
selected = k == value ? 'selected="selected"' : ''
|
92
|
+
html += "<option #{selected} value=\"#{k}\">#{k}</option>"
|
93
|
+
end
|
94
|
+
html += "</select>"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
get "/cleaner" do
|
99
|
+
load_cleaner_filter
|
100
|
+
|
101
|
+
@jobs = cleaner.select
|
102
|
+
@stats, @total = {}, {"total" => 0, "1h" => 0, "3h" => 0, "1d" => 0, "3d" => 0, "7d" => 0}
|
103
|
+
@jobs.each do |job|
|
104
|
+
klass = job["payload"]["class"]
|
105
|
+
failed_at = Time.parse job["failed_at"]
|
106
|
+
|
107
|
+
@stats[klass] ||= {"total" => 0, "1h" => 0, "3h" => 0, "1d" => 0, "3d" => 0, "7d" => 0}
|
108
|
+
items = [@stats[klass],@total]
|
109
|
+
|
110
|
+
items.each{|a| a["total"] += 1}
|
111
|
+
items.each{|a| a["1h"] += 1} if failed_at >= hours_ago(1)
|
112
|
+
items.each{|a| a["3h"] += 1} if failed_at >= hours_ago(3)
|
113
|
+
items.each{|a| a["1d"] += 1} if failed_at >= hours_ago(24)
|
114
|
+
items.each{|a| a["3d"] += 1} if failed_at >= hours_ago(24*3)
|
115
|
+
items.each{|a| a["7d"] += 1} if failed_at >= hours_ago(24*7)
|
116
|
+
end
|
117
|
+
|
118
|
+
erb File.read(ResqueCleaner::Server.erb_path('cleaner.erb'))
|
119
|
+
end
|
120
|
+
|
121
|
+
get "/cleaner_list" do
|
122
|
+
load_cleaner_filter
|
123
|
+
|
124
|
+
block = lambda{|j|
|
125
|
+
(!@from || j.after?(hours_ago(@from))) &&
|
126
|
+
(!@to || j.before?(hours_ago(@to))) &&
|
127
|
+
(!@klass || j.klass?(@klass))
|
128
|
+
}
|
129
|
+
|
130
|
+
@failed = cleaner.select(&block).reverse
|
131
|
+
|
132
|
+
url = "cleaner_list?c=#{@klass}&f=#{@from}&t=#{@to}"
|
133
|
+
@paginate = Paginate.new(@failed, url, params[:p].to_i)
|
134
|
+
|
135
|
+
@klasses = cleaner.stats_by_class.keys
|
136
|
+
@count = cleaner.select(&block).size
|
137
|
+
|
138
|
+
erb File.read(ResqueCleaner::Server.erb_path('cleaner_list.erb'))
|
139
|
+
end
|
140
|
+
|
141
|
+
post "/cleaner_exec" do
|
142
|
+
load_cleaner_filter
|
143
|
+
|
144
|
+
@sha1 = nil
|
145
|
+
if params[:select_all_pages]!="1"
|
146
|
+
@sha1 = {}
|
147
|
+
params[:sha1].split(",").each do |s|
|
148
|
+
@sha1[s] = true
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
block = lambda{|j|
|
153
|
+
(!@from || j.after?(hours_ago(@from))) &&
|
154
|
+
(!@to || j.before?(hours_ago(@to))) &&
|
155
|
+
(!@klass || j.klass?(@klass))
|
156
|
+
(!@sha1 || @sha1[Digest::SHA1.hexdigest(j.to_json)])
|
157
|
+
}
|
158
|
+
|
159
|
+
count =
|
160
|
+
case params[:action]
|
161
|
+
when "clear" then cleaner.clear(&block)
|
162
|
+
when "retry_and_clear" then cleaner.requeue(true,&block)
|
163
|
+
when "retry" then cleaner.requeue(false,{},&block)
|
164
|
+
end
|
165
|
+
|
166
|
+
@msg = "processed #{count} jobs."
|
167
|
+
@url = "cleaner_list?c=#{@klass}&f=#{@from}&t=#{@to}"
|
168
|
+
erb File.read(ResqueCleaner::Server.erb_path('cleaner_exec.erb'))
|
169
|
+
end
|
170
|
+
|
171
|
+
post "/cleaner_stale" do
|
172
|
+
cleaner.clear_stale
|
173
|
+
redirect "/cleaner"
|
174
|
+
end
|
175
|
+
|
176
|
+
get /cleaner\/public\/([a-z]+\.[a-z]+)/ do
|
177
|
+
send_file ResqueCleaner::Server.public_path(params[:captures].first)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
182
|
+
|
183
|
+
def cleaner
|
184
|
+
@cleaner ||= Resque::Plugins::ResqueCleaner.new
|
185
|
+
@cleaner.print_message = false
|
186
|
+
@cleaner
|
187
|
+
end
|
188
|
+
|
189
|
+
def load_cleaner_filter
|
190
|
+
@from = params[:f]=="" ? nil : params[:f]
|
191
|
+
@to = params[:t]=="" ? nil : params[:t]
|
192
|
+
@klass = params[:c]=="" ? nil : params[:c]
|
193
|
+
end
|
194
|
+
|
195
|
+
def hours_ago(h)
|
196
|
+
Time.now - h.to_i*60*60
|
197
|
+
end
|
198
|
+
Resque::Server.tabs << 'Cleaner'
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
Resque::Server.class_eval do
|
203
|
+
include ResqueCleaner::Server
|
204
|
+
end
|
205
|
+
|
data/lib/resque_cleaner.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
require 'time'
|
2
|
+
require 'resque'
|
3
|
+
require 'resque/server'
|
4
|
+
|
2
5
|
module Resque
|
3
6
|
module Plugins
|
4
7
|
# ResqueCleaner class provides useful functionalities to retry or clean
|
@@ -258,5 +261,5 @@ module Resque
|
|
258
261
|
end
|
259
262
|
end
|
260
263
|
|
261
|
-
|
264
|
+
require 'resque_cleaner/server'
|
262
265
|
|
data/test/resque_cleaner_test.rb
CHANGED
@@ -1,19 +1,6 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/test_helper')
|
2
2
|
require 'time'
|
3
3
|
context "ResqueCleaner" do
|
4
|
-
def create_and_process_jobs(queue,worker,num,date,job,*args)
|
5
|
-
Timecop.freeze(date) do
|
6
|
-
num.times do
|
7
|
-
Resque::Job.create(queue, job, *args)
|
8
|
-
end
|
9
|
-
worker.work(0)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
def queue_size(*queues)
|
14
|
-
queues.inject(0){|sum,queue| sum + Resque.size(queue).to_i}
|
15
|
-
end
|
16
|
-
|
17
4
|
setup do
|
18
5
|
Resque.redis.flushall
|
19
6
|
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/test_helper')
|
2
|
+
|
3
|
+
# Pull in the server test_helper from resque
|
4
|
+
require 'resque/server/test_helper.rb'
|
5
|
+
|
6
|
+
def setup_some_failed_jobs
|
7
|
+
Resque.redis.flushall
|
8
|
+
|
9
|
+
@worker = Resque::Worker.new(:jobs,:jobs2)
|
10
|
+
|
11
|
+
10.times {|i|
|
12
|
+
create_and_process_jobs :jobs, @worker, 1, Time.now, BadJob, "test_#{i}"
|
13
|
+
}
|
14
|
+
|
15
|
+
@cleaner = Resque::Plugins::ResqueCleaner.new
|
16
|
+
@cleaner.print_message = false
|
17
|
+
end
|
18
|
+
|
19
|
+
context "resque-web" do
|
20
|
+
setup do
|
21
|
+
setup_some_failed_jobs
|
22
|
+
end
|
23
|
+
|
24
|
+
test "#cleaner should respond with success" do
|
25
|
+
get "/cleaner_list"
|
26
|
+
assert last_response.ok?, last_response.errors
|
27
|
+
end
|
28
|
+
|
29
|
+
test "#cleaner_list should respond with success" do
|
30
|
+
get "/cleaner_list"
|
31
|
+
assert last_response.ok?, last_response.errors
|
32
|
+
end
|
33
|
+
|
34
|
+
test '#cleaner_list shows the failed jobs' do
|
35
|
+
get "/cleaner_list"
|
36
|
+
assert last_response.body.include?('BadJob')
|
37
|
+
end
|
38
|
+
|
39
|
+
test '#cleaner_exec clears job' do
|
40
|
+
post "/cleaner_exec", :action => "clear", :sha1 => Digest::SHA1.hexdigest(@cleaner.select[0].to_json)
|
41
|
+
assert_equal 9, @cleaner.select.size
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
data/test/test_helper.rb
CHANGED
@@ -120,3 +120,20 @@ class BadJobWithSyntaxError
|
|
120
120
|
raise SyntaxError, "Extra Bad job!"
|
121
121
|
end
|
122
122
|
end
|
123
|
+
|
124
|
+
#
|
125
|
+
# helper methods
|
126
|
+
#
|
127
|
+
|
128
|
+
def create_and_process_jobs(queue,worker,num,date,job,*args)
|
129
|
+
Timecop.freeze(date) do
|
130
|
+
num.times do
|
131
|
+
Resque::Job.create(queue, job, *args)
|
132
|
+
end
|
133
|
+
worker.work(0)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def queue_size(*queues)
|
138
|
+
queues.inject(0){|sum,queue| sum + Resque.size(queue).to_i}
|
139
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: resque-cleaner
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash: 25
|
5
4
|
prerelease: false
|
6
5
|
segments:
|
7
6
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
7
|
+
- 2
|
8
|
+
- 0
|
9
|
+
version: 0.2.0
|
11
10
|
platform: ruby
|
12
11
|
authors:
|
13
12
|
- Tatsuya Ono
|
@@ -15,7 +14,7 @@ autorequire:
|
|
15
14
|
bindir: bin
|
16
15
|
cert_chain: []
|
17
16
|
|
18
|
-
date:
|
17
|
+
date: 2011-04-06 00:00:00 +01:00
|
19
18
|
default_executable:
|
20
19
|
dependencies:
|
21
20
|
- !ruby/object:Gem::Dependency
|
@@ -26,7 +25,6 @@ dependencies:
|
|
26
25
|
requirements:
|
27
26
|
- - ~>
|
28
27
|
- !ruby/object:Gem::Version
|
29
|
-
hash: 15
|
30
28
|
segments:
|
31
29
|
- 1
|
32
30
|
- 0
|
@@ -49,9 +47,17 @@ files:
|
|
49
47
|
- Rakefile
|
50
48
|
- LICENSE
|
51
49
|
- lib/resque-cleaner.rb
|
50
|
+
- lib/resque_cleaner/server/public/cleaner.css
|
51
|
+
- lib/resque_cleaner/server/views/_limiter.erb
|
52
|
+
- lib/resque_cleaner/server/views/_paginate.erb
|
53
|
+
- lib/resque_cleaner/server/views/cleaner.erb
|
54
|
+
- lib/resque_cleaner/server/views/cleaner_exec.erb
|
55
|
+
- lib/resque_cleaner/server/views/cleaner_list.erb
|
56
|
+
- lib/resque_cleaner/server.rb
|
52
57
|
- lib/resque_cleaner.rb
|
53
58
|
- test/redis-test.conf
|
54
59
|
- test/resque_cleaner_test.rb
|
60
|
+
- test/resque_web_test.rb
|
55
61
|
- test/test_helper.rb
|
56
62
|
has_rdoc: true
|
57
63
|
homepage: http://github.com/ono/resque-cleaner
|
@@ -67,7 +73,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
67
73
|
requirements:
|
68
74
|
- - ">="
|
69
75
|
- !ruby/object:Gem::Version
|
70
|
-
hash: 3
|
71
76
|
segments:
|
72
77
|
- 0
|
73
78
|
version: "0"
|
@@ -76,7 +81,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
76
81
|
requirements:
|
77
82
|
- - ">="
|
78
83
|
- !ruby/object:Gem::Version
|
79
|
-
hash: 3
|
80
84
|
segments:
|
81
85
|
- 0
|
82
86
|
version: "0"
|