resque 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of resque might be problematic. Click here for more details.

data/HISTORY.md CHANGED
@@ -1,3 +1,10 @@
1
- ## 0.1.0 (2009-11-02)
1
+ ## 1.1.0 (2009-11-04)
2
+
3
+ * Bugfix: Broken ERB tag in failure UI
4
+ * Bugfix: Save the worker's ID, not the worker itself, in the failure module
5
+ * Redesigned the sinatra web interface
6
+ * Added option to clear failed jobs
7
+
8
+ ## 1.0.0 (2009-11-03)
2
9
 
3
10
  * First release.
data/README.markdown CHANGED
@@ -365,7 +365,7 @@ The Front End
365
365
  Resque comes with a Sinatra-based front end for seeing what's up with
366
366
  your queue.
367
367
 
368
- ![The Front End](http://img.skitch.com/20091102-rpekt191w28xfhwyussru44nsw.png)
368
+ ![The Front End](http://img.skitch.com/20091104-tqh5pgkwgbskjbk7qbtmpesnyw.jpg)
369
369
 
370
370
  ## Standalone
371
371
 
@@ -619,6 +619,69 @@ processed in the background.
619
619
  Try it out by looking at the README, found at `examples/demo/README.markdown`.
620
620
 
621
621
 
622
+ Monitoring
623
+ ----------
624
+
625
+ If you're using god to monitor Resque, we have provided example
626
+ configs in `examples/god/`. One is for starting / stopping workers,
627
+ the other is for killing workers that have been running too long.
628
+
629
+
630
+ Development
631
+ -----------
632
+
633
+ Want to hack on Resque?
634
+
635
+ First clone the repo and run the tests:
636
+
637
+ git clone git://github.com/defunkt/resque.git
638
+ cd resque
639
+ rake test
640
+
641
+ If the tests do not pass make sure you have Redis installed
642
+ correctly (though we make an effort to tell you if we feel this is the
643
+ case). The tests attempt to start an isolated instance of Redis to
644
+ run against.
645
+
646
+ Also make sure you've installed all the depenedencies correctly. For
647
+ example, try loading the `redis-namespace` gem after you've installed
648
+ it:
649
+
650
+ $ irb
651
+ >> require 'rubygems'
652
+ => true
653
+ >> require 'redis/namespace'
654
+ => true
655
+
656
+ If you get an error requiring any of the dependencies, you may have
657
+ failed to install them or be seeing load path issues.
658
+
659
+ Feel free to ping the mailing list with your problem and we'll try to
660
+ sort it out.
661
+
662
+
663
+ Contributing
664
+ ------------
665
+
666
+ Once you've made your great commits:
667
+
668
+ 1. [Fork](fk) Resque
669
+ 2. Create a topic branch - `git checkout -b my_branch`
670
+ 3. Push to your branch - `git push origin my_branch`
671
+ 4. Create an [Issue](is) with a link to your branch
672
+ 5. That's it!
673
+
674
+
675
+ Mailing List
676
+ ------------
677
+
678
+ To join the list simply send an email to <resque@librelist.com>. This
679
+ will subscribe you and send you information about your subscription,
680
+ include unsubscribe information.
681
+
682
+ The archive can be found at <http://librelist.com/browser/>.
683
+
684
+
622
685
  Meta
623
686
  ----
624
687
 
@@ -636,3 +699,5 @@ Author
636
699
  Chris Wanstrath :: chris@ozmm.org :: @defunkt
637
700
 
638
701
  [0]: http://github.com/blog/542-introducing-resque
702
+ [fk]: http://help.github.com/forking/
703
+ [is]: http://github.com/defunkt/resque/issues
data/examples/demo/app.rb CHANGED
@@ -1,4 +1,4 @@
1
- require 'sinatra'
1
+ require 'sinatra/base'
2
2
  require 'resque'
3
3
  require 'job'
4
4
 
@@ -0,0 +1,52 @@
1
+ rails_env = ENV['RAILS_ENV'] || "production"
2
+ rails_root = ENV['RAILS_ROOT'] || "/data/github/current"
3
+ num_workers = rails_env == 'production' ? 5 : 2
4
+
5
+ num_workers.times do |num|
6
+ God.watch do |w|
7
+ w.name = "resque-#{num}"
8
+ w.group = 'resque'
9
+ w.interval = 30.seconds
10
+ w.start = "env QUEUE=critical,high,low /usr/bin/rake -f #{rails_root}/Rakefile #{rails_env} resque:work"
11
+
12
+ w.uid = 'git'
13
+ w.gid = 'git'
14
+
15
+ # retart if memory gets too high
16
+ w.transition(:up, :restart) do |on|
17
+ on.condition(:memory_usage) do |c|
18
+ c.above = 350.megabytes
19
+ c.times = 2
20
+ end
21
+ end
22
+
23
+ # determine the state on startup
24
+ w.transition(:init, { true => :up, false => :start }) do |on|
25
+ on.condition(:process_running) do |c|
26
+ c.running = true
27
+ end
28
+ end
29
+
30
+ # determine when process has finished starting
31
+ w.transition([:start, :restart], :up) do |on|
32
+ on.condition(:process_running) do |c|
33
+ c.running = true
34
+ c.interval = 5.seconds
35
+ end
36
+
37
+ # failsafe
38
+ on.condition(:tries) do |c|
39
+ c.times = 5
40
+ c.transition = :start
41
+ c.interval = 5.seconds
42
+ end
43
+ end
44
+
45
+ # start if process is not running
46
+ w.transition(:up, :start) do |on|
47
+ on.condition(:process_running) do |c|
48
+ c.running = false
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,26 @@
1
+ # This will ride alongside god and kill any rogue stale worker
2
+ # processes. Their sacrifice is for the greater good.
3
+
4
+ WORKER_TIMEOUT = 60 * 10 # 10 minutes
5
+
6
+ Thread.new do
7
+ loop do
8
+ begin
9
+ `ps -e -o pid,command | grep [r]esque`.split("\n").each do |line|
10
+ parts = line.split(' ')
11
+ next if parts[-2] != "at"
12
+ started = parts[-1].to_i
13
+ elapsed = Time.now - Time.at(started)
14
+
15
+ if elapsed >= WORKER_TIMEOUT
16
+ ::Process.kill('USR1', parts[0].to_i)
17
+ end
18
+ end
19
+ rescue
20
+ # don't die because of stupid exceptions
21
+ nil
22
+ end
23
+
24
+ sleep 30
25
+ end
26
+ end
@@ -53,5 +53,11 @@ module Resque
53
53
  def self.url
54
54
  backend.url
55
55
  end
56
+
57
+ # Clear all failure jobs
58
+ def self.clear
59
+ backend.clear
60
+ end
61
+
56
62
  end
57
63
  end
@@ -44,6 +44,10 @@ module Resque
44
44
  # A URL where someone can go to view failures.
45
45
  def self.url
46
46
  end
47
+
48
+ # Clear all failure objects
49
+ def self.clear
50
+ end
47
51
 
48
52
  # Logging!
49
53
  def log(message)
@@ -9,7 +9,7 @@ module Resque
9
9
  :payload => payload,
10
10
  :error => exception.to_s,
11
11
  :backtrace => exception.backtrace,
12
- :worker => worker,
12
+ :worker => worker.to_s,
13
13
  :queue => queue
14
14
  }
15
15
  data = Resque.encode(data)
@@ -23,6 +23,11 @@ module Resque
23
23
  def self.all(start = 0, count = 1)
24
24
  Resque.list_range(:failed, start, count)
25
25
  end
26
+
27
+ def self.clear
28
+ Resque.redis.delete('resque:failed')
29
+ end
30
+
26
31
  end
27
32
  end
28
33
  end
data/lib/resque/server.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'sinatra/base'
2
2
  require 'erb'
3
3
  require 'resque'
4
+ require 'resque/version'
4
5
 
5
6
  module Resque
6
7
  class Server < Sinatra::Base
@@ -80,13 +81,23 @@ module Resque
80
81
  ensure
81
82
  @partial = false
82
83
  end
84
+
85
+ def poll
86
+ if @polling
87
+ text = "Last Updated: #{Time.now.strftime("%H:%M:%S")}"
88
+ else
89
+ text = "<a href='#{url(request.path_info)}.poll' rel='poll'>Live Poll</a>"
90
+ end
91
+ "<p class='poll'>#{text}</p>"
92
+ end
93
+
83
94
  end
84
95
 
85
- def show(page)
96
+ def show(page, layout = true)
86
97
  begin
87
- erb page.to_sym, {}, :resque => Resque
98
+ erb page.to_sym, {:layout => layout}, :resque => Resque
88
99
  rescue Errno::ECONNREFUSED
89
- erb :error, {}, :error => "Can't connect to Redis! (#{Resque.redis.server})"
100
+ erb :error, {:layout => false}, :error => "Can't connect to Redis! (#{Resque.redis.server})"
90
101
  end
91
102
  end
92
103
 
@@ -104,6 +115,14 @@ module Resque
104
115
  show page
105
116
  end
106
117
  end
118
+
119
+ %w( overview workers ).each do |page|
120
+ get "/#{page}.poll" do
121
+ content_type "text/plain"
122
+ @polling = true
123
+ show(page.to_sym, false).gsub(/\s{1,}/, ' ')
124
+ end
125
+ end
107
126
 
108
127
  get "/failed" do
109
128
  if Resque::Failure.url
@@ -112,9 +131,10 @@ module Resque
112
131
  show :failed
113
132
  end
114
133
  end
115
-
116
- get "/failed/:id" do
117
- show :failed
134
+
135
+ post "/failed/clear" do
136
+ Resque::Failure.clear
137
+ redirect u('failed')
118
138
  end
119
139
 
120
140
  get "/stats" do
Binary file
Binary file
@@ -4,4 +4,18 @@ $(function() {
4
4
  $(this).next().toggle()
5
5
  return false
6
6
  })
7
+
8
+ $('a[rel=poll]').click(function() {
9
+ var href = $(this).attr('href')
10
+ $(this).parent().text('Starting...')
11
+ $("#main").addClass('polling')
12
+ setInterval(function() {
13
+ $.ajax({dataType:'text', type:'get', url:href, success:function(data) {
14
+ $('#main').html(data)
15
+ $('#main .time').relatizeDate()
16
+ }})
17
+ }, 2 * 1000)
18
+ return false
19
+ })
20
+
7
21
  })
@@ -14,7 +14,6 @@ table, caption, tbody, tfoot, thead, tr, th, td {
14
14
  font-style: normal;
15
15
  font-size: 100%;
16
16
  font-family: inherit;
17
- vertical-align: baseline;
18
17
  }
19
18
 
20
19
  :focus {
@@ -23,8 +22,6 @@ table, caption, tbody, tfoot, thead, tr, th, td {
23
22
 
24
23
  body {
25
24
  line-height: 1;
26
- color: black;
27
- background: white;
28
25
  }
29
26
 
30
27
  ul {
@@ -1,67 +1,75 @@
1
- body {
2
- margin: 0;
3
- /* font: normal 12px/1.5em "Helvetica Neue", Helvetica, Arial, sans-serif; */
4
- }
5
- h1, h2, h3, h4, h5 {
6
- color: #333;
7
- font: normal 12px/1.5em "Helvetica Neue", Helvetica, Arial, sans-serif;
8
- text-shadow: 1px 1px #ddd;
9
- }
10
- h1 {
11
- font-size: 1.5em;
12
- }
13
- h2 {
14
- font-size: 1.0em;
15
- }
16
- h3 {
17
- font-size: 0.7em;
18
- }
19
- table {
20
- margin-bottom: 50px;
21
- border-collapse: collapse;
22
- }
23
- th, td {
24
- padding: 4px;
25
- border-bottom: 1px solid #eee;
1
+ html { background:#efefef; font-family:Arial, Verdana, sans-serif; font-size:13px; }
2
+ body { padding:0; margin:0; }
26
3
 
27
- }
28
- td {
29
- padding: 5px 15px 5px 15px;
30
- }
31
- th {
32
- color: #666;
33
- font-family: Helvetica, Arial, sans-serif;
34
- font-weight: bold;
35
- text-align: left;
36
- font-size: .9em;
37
- padding: 0 15px 5px 15px;
38
- }
39
- a {
40
- color: #59768A;
41
- text-decoration: none;
42
- font-size: .9em;
43
- }
44
- a:hover {
45
- color: #000;
46
- background-color: #e0f3ff;
47
- }
48
- a.queue {
49
- text-transform: uppercase;
50
- }
51
- code {
52
- font-size: 1.2em;
53
- }
4
+ .header { background:#000; padding:8px 5% 0 5%; border-bottom:1px solid #444;border-bottom:5px solid #ce1212;}
5
+ .header h1 { color:#333; font-size:90%; font-weight:bold; margin-bottom:6px;}
6
+ .header ul li { display:inline;}
7
+ .header ul li a { color:#fff; text-decoration:none; margin-right:10px; display:inline-block; padding:8px; -webkit-border-top-right-radius:6px; -webkit-border-top-left-radius:6px; }
8
+ .header ul li a:hover { background:#333;}
9
+ .header ul li.current a { background:#ce1212; font-weight:bold; color:#fff;}
54
10
 
55
- #logo {
56
- float: right;
57
- font-family: Collegiate, "Helvetica Neue", Helvetica, Arial, sans-serif;
58
- text-shadow: #0c0c0c 4px 4px 8px;
59
- font-size: 1.6em;
60
- padding-right: 20px;
61
- letter-spacing: 3px;
62
- color: #333;
63
- }
11
+ .subnav { padding:2px 5% 7px 5%; background:#ce1212; font-size:90%;}
12
+ .subnav li { display:inline;}
13
+ .subnav li a { color:#fff; text-decoration:none; margin-right:10px; display:inline-block; background:#dd5b5b; padding:5px; -webkit-border-radius:3px; -moz-border-radius:3px;}
14
+ .subnav li.current a { background:#fff; font-weight:bold; color:#ce1212;}
15
+ .subnav li a:active { background:#b00909;}
64
16
 
65
- #main {
66
- margin: 10px;
67
- }
17
+ #main { padding:10px 5%; background:#fff; overflow:hidden; }
18
+ #main .logo { float:right; margin:10px;}
19
+ #main span.hl { background:#efefef; padding:2px;}
20
+ #main h1 { margin:10px 0; font-size:190%; font-weight:bold; color:#ce1212;}
21
+ #main h2 { margin:10px 0; font-size:130%;}
22
+ #main table { width:100%; margin:10px 0;}
23
+ #main table tr td, #main table tr th { border:1px solid #ccc; padding:6px;}
24
+ #main table tr th { background:#efefef; color:#888; font-size:80%; font-weight:bold;}
25
+ #main table tr td.no-data { text-align:center; padding:40px 0; color:#999; font-style:italic; font-size:130%;}
26
+ #main a { color:#111;}
27
+ #main p { margin:5px 0;}
28
+ #main p.intro { margin-bottom:15px; font-size:85%; color:#999; margin-top:0; line-height:1.3;}
29
+ #main h1.wi { margin-bottom:5px;}
30
+ #main p.sub { font-size:95%; color:#999;}
31
+
32
+ #main table.queues { width:40%;}
33
+ #main table.queues td.queue { font-weight:bold; width:50%;}
34
+ #main table.queues tr.failed td { background:#ffecec; border-top:2px solid #d37474; font-size:90%; color:#d37474;}
35
+ #main table.queues tr.failed td a{ color:#d37474;}
36
+
37
+ #main table.jobs td.class { font-family:Monaco, "Courier New", monospace; font-size:90%; width:50%;}
38
+ #main table.jobs td.args{ width:50%;}
39
+
40
+ #main table.workers td.icon {width:1%; background:#efefef;text-align:center;}
41
+ #main table.workers td.where { width:25%;}
42
+ #main table.workers td.queues { width:35%;}
43
+ #main .queue-tag { background:#b1d2e9; padding:2px; margin:0 3px; font-size:80%; text-decoration:none; text-transform:uppercase; font-weight:bold; color:#3274a2; -webkit-border-radius:4px; -moz-border-radius:4px;}
44
+ #main table.workers td.queues.queue { width:10%;}
45
+ #main table.workers td.process { width:35%;}
46
+ #main table.workers td.process span.waiting { color:#999; font-size:90%;}
47
+ #main table.workers td.process small { font-size:80%; margin-left:5px;}
48
+ #main table.workers td.process code { font-family:Monaco, "Courier New", monospace; font-size:90%;}
49
+ #main table.workers td.process small a { color:#999;}
50
+ #main.polling table.workers tr.working td { background:#f4ffe4; color:#7ac312;}
51
+ #main.polling table.workers tr.working td.where a { color:#7ac312;}
52
+ #main.polling table.workers tr.working td.process code { font-weight:bold;}
53
+
54
+
55
+ #main table.stats th { font-size:100%; width:40%; color:#000;}
56
+ #main hr { border:0; border-top:5px solid #efefef; margin:15px 0;}
57
+
58
+ #footer { padding:10px 5%; background:#efefef; color:#999; font-size:85%; line-height:1.5; border-top:5px solid #ccc; padding-top:10px;}
59
+ #footer p a { color:#999;}
60
+
61
+ #main p.poll { background:url(poll.png) no-repeat 0 2px; padding:3px 0; padding-left:23px; float:right; font-size:85%; }
62
+
63
+ #main ul.failed {}
64
+ #main ul.failed li {background:-webkit-gradient(linear, left top, left bottom, from(#efefef), to(#fff)) #efefef; margin-top:10px; padding:10px; overflow:hidden; -webkit-border-radius:5px; border:1px solid #ccc; }
65
+ #main ul.failed li dl dt {font-size:80%; color:#999; width:60px; float:left; padding-top:1px; text-align:right;}
66
+ #main ul.failed li dl dd {margin-bottom:10px; margin-left:70px;}
67
+ #main ul.failed li dl dd code, #main ul.failed li dl dd pre { font-family:Monaco, "Courier New", monospace; font-size:90%;}
68
+ #main ul.failed li dl dd.error a {font-family:Monaco, "Courier New", monospace; font-size:90%; }
69
+ #main ul.failed li dl dd.error pre { margin-top:3px; line-height:1.3;}
70
+
71
+ #main p.pagination { background:#efefef; padding:10px; overflow:hidden;}
72
+ #main p.pagination a.less { float:left;}
73
+ #main p.pagination a.more { float:right;}
74
+
75
+ #main form.clear-failed {float:right; margin-top:-10px;}
Binary file
@@ -1 +1 @@
1
- <h1><%= error %></h1>
1
+ <h1 style="font-size:110%;font-family:Arial, sans-serif;"><%= error %></h1>
@@ -1,29 +1,35 @@
1
- <h1><%= size = Resque::Failure.count %> Jobs Failed</h1>
2
- <h2>Showing <%= start = params[:start].to_i %> to <%= start + 20 %></h2>
3
- <table>
4
- <tr>
5
- <th>Queue</th>
6
- <th>Worker</th>
7
- <th>Failed</th>
8
- <th>Class</th>
9
- <th>Args</th>
10
- <th>Error</th>
11
- </tr>
12
- <% for job in Resque::Failure.all(start, 20) %>
13
- <tr>
14
- <td><%= job['queue'] %></td>
15
- <td><a href="<= url(:workers, job['worker']) %>"><%= job['worker'].split(':')[0...2].join(':') %></a></td>
16
- <td><span class="time"><%= job['failed_at'] %></span></td>
17
- <td><%= job['payload']['class'] %></td>
18
- <td>
19
- <a href="#" class="backtrace"><%= Array(job['payload']['args']).size %></a>
20
- <pre style="display:none;"><%=h show_args(job['payload']['args']) %></pre>
21
- </td>
22
- <td>
23
- <a href="#" class="backtrace"><%= h(job['error']) %></a>
24
- <pre style="display:none;"><%=h job['backtrace'].join("\n") %></pre>
25
- </td>
26
- </tr>
27
- <% end %>
28
- <%= partial :next_more, :start => start, :size => size %>
29
- </table>
1
+ <%start = params[:start].to_i %>
2
+ <%failed = Resque::Failure.all(start, 20)%>
3
+
4
+ <h1>Failed Jobs</h1>
5
+ <%unless failed.empty?%>
6
+ <form method="POST" action="<%=u 'failed/clear'%>" class='clear-failed'>
7
+ <input type='submit' name='' value='Clear Failed Jobs' />
8
+ </form>
9
+ <%end%>
10
+
11
+ <p class='sub'>Showing <%=start%> to <%= start + 20 %> of <b><%= size = Resque::Failure.count %></b> jobs</p>
12
+
13
+ <ul class='failed'>
14
+ <%for job in failed%>
15
+ <li>
16
+ <dl>
17
+ <dt>Worker</dt>
18
+ <dd><a href="<%= url(: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></dd>
19
+ <dt>Class</dt>
20
+ <dd><code><%= job['payload']['class'] %></code></dd>
21
+ <dt>Arguments</dt>
22
+ <dd><pre><%=h show_args(job['payload']['args']) %></pre></dd>
23
+ <dt>Error</dt>
24
+ <dd class='error'>
25
+ <a href="#" class="backtrace"><%= h(job['error']) %></a>
26
+ <pre style='display:none'><%=h job['backtrace'].join("\n") %></pre>
27
+ </dd>
28
+ </dl>
29
+ <div class='r'>
30
+ </div>
31
+ </li>
32
+ <%end%>
33
+ </ul>
34
+
35
+ <%= partial :next_more, :start => start, :size => size %>
@@ -2,20 +2,15 @@
2
2
  <html>
3
3
  <head>
4
4
  <title>Resque.</title>
5
+ <link href="<%=u 'reset.css' %>" media="screen" rel="stylesheet" type="text/css">
5
6
  <link href="<%=u 'style.css' %>" media="screen" rel="stylesheet" type="text/css">
6
- <link href="<%=u 'tabs.css' %>" media="screen" rel="stylesheet" type="text/css">
7
7
  <script src="<%=u 'jquery-1.3.2.min.js' %>" type="text/javascript"</script>
8
8
  <script src="<%=u 'jquery.relatize_date.js' %>" type="text/javascript"</script>
9
9
  <script src="<%=u 'ranger.js' %>" type="text/javascript"></script>
10
10
  </head>
11
11
  <body>
12
-
13
- <div class="navigation" id="top">
14
- <div class="tabs">
15
- <div id="logo">
16
- Resque
17
- </div>
18
- <ul>
12
+ <div class="header">
13
+ <ul class='nav'>
19
14
  <%= tab "Overview" %>
20
15
  <%= tab "Working" %>
21
16
  <%= tab "Failed" %>
@@ -24,20 +19,23 @@
24
19
  <%= tab "Stats" %>
25
20
  </ul>
26
21
  </div>
22
+
27
23
  <% if @subtabs %>
28
- <div class="tabs">
29
- <ul>
30
- <% for subtab in @subtabs %>
31
- <li <%= class_if_current "#{current_section}/#{subtab}" %>><a href="<%= current_section %>/<%= subtab %>"><span><%= subtab %></span></a></li>
32
- <% end %>
33
- </ul>
34
- </div>
24
+ <ul class='subnav'>
25
+ <% for subtab in @subtabs %>
26
+ <li <%= class_if_current "#{current_section}/#{subtab}" %>><a href="<%= current_section %>/<%= subtab %>"><span><%= subtab %></span></a></li>
27
+ <% end %>
28
+ </ul>
35
29
  <% end %>
36
- </div>
37
-
30
+
38
31
  <div id="main">
39
32
  <%= yield %>
40
33
  </div>
41
34
 
35
+ <div id="footer">
36
+ <p>Powered by <a href="http://github.com/defunkt/resque">Resque</a> v<%=Resque::Version%></p>
37
+ <p>Connected to Redis on <%=Resque.redis.server%></p>
38
+ </div>
39
+
42
40
  </body>
43
41
  </html>
@@ -1,12 +1,10 @@
1
- <tr>
2
- <td>
3
- <% if start - 20 >= 0 %>
4
- <a href="/<%= current_page %>?start=<%= start - 20 %>">&laquo; less</a>
5
- <% end %>
6
- </td>
7
- <td>
8
- <% if start + 20 <= size %>
9
- <a href="/<%= current_page %>?start=<%= start + 20 %>">more &raquo;</a>
10
- <% end %>
11
- </td>
12
- </tr>
1
+ <%if start - 20 >= 0 || start + 20 <= size%>
2
+ <p class='pagination'>
3
+ <% if start - 20 >= 0 %>
4
+ <a href="<%= current_page %>?start=<%= start - 20 %>" class='less'>&laquo; less</a>
5
+ <% end %>
6
+ <% if start + 20 <= size %>
7
+ <a href="<%= current_page %>?start=<%= start + 20 %>" class='more'>more &raquo;</a>
8
+ <% end %>
9
+ </p>
10
+ <%end%>
@@ -1,2 +1,4 @@
1
1
  <%= partial :queues %>
2
+ <hr />
2
3
  <%= partial :working %>
4
+ <%= poll %>
@@ -2,38 +2,44 @@
2
2
 
3
3
  <% if queue = params[:id] %>
4
4
 
5
- <h1><%= size = resque.size(queue) %> Pending Jobs in '<%= queue.upcase %>'</h1>
6
- <h2>Showing <%= start = params[:start].to_i %> to <%= start + 20 %></h2>
7
- <table>
5
+ <h1>Pending jobs on <span class='hl'><%= queue %></span></h1>
6
+ <p class='sub'>Showing <%= start = params[:start].to_i %> to <%= start + 20 %> of <b><%=size = resque.size(queue)%></b> jobs</p>
7
+ <table class='jobs'>
8
8
  <tr>
9
9
  <th>Class</th>
10
10
  <th>Args</th>
11
11
  </tr>
12
- <% for job in resque.peek(queue, start, 20) %>
12
+ <% for job in (jobs = resque.peek(queue, start, 20)) %>
13
13
  <tr>
14
- <td><%= job['class'] %></td>
15
- <td><%=h job['args'].inspect %></td>
14
+ <td class='class'><%= job['class'] %></td>
15
+ <td class='args'><%=h job['args'].inspect %></td>
16
+ </tr>
17
+ <% end %>
18
+ <% if jobs.empty? %>
19
+ <tr>
20
+ <td class='no-data' colspan='2'>There are no pending jobs in this queue</td>
16
21
  </tr>
17
22
  <% end %>
18
- <%= partial :next_more, :start => start, :size => size %>
19
23
  </table>
24
+ <%= partial :next_more, :start => start, :size => size %>
20
25
  <% else %>
21
26
 
22
- <h1>Queues</h1>
23
- <table>
27
+ <h1 class='wi'>Queues</h1>
28
+ <p class='intro'>The list below contains all the registered queues with the number of jobs currently in the queue. Select a queue from above to view all jobs currently pending on the queue.</p>
29
+ <table class='queues'>
24
30
  <tr>
25
31
  <th>Name</th>
26
- <th>Pending</th>
32
+ <th>Jobs</th>
27
33
  </tr>
28
34
  <% for queue in resque.queues.sort_by { |q| q.to_s } %>
29
35
  <tr>
30
- <td><a class="queue" href="<%= url "queues/#{queue}" %>"><%= queue %></a></td>
31
- <td><%= resque.size queue %></td>
36
+ <td class='queue'><a class="queue" href="<%= url "queues/#{queue}" %>"><%= queue %></a></td>
37
+ <td class='size'><%= resque.size queue %></td>
32
38
  </tr>
33
39
  <% end %>
34
- <tr>
35
- <td><a class="queue" href="<%= url :failed %>">failed</a></td>
36
- <td><%= Resque::Failure.count %></td>
40
+ <tr class='failed'>
41
+ <td class='queue failed'><a class="queue" href="<%= url :failed %>">failed</a></td>
42
+ <td class='size'><%= Resque::Failure.count %></td>
37
43
  </tr>
38
44
  </table>
39
45
 
@@ -7,12 +7,12 @@
7
7
  <% elsif params[:id] == "resque" %>
8
8
 
9
9
  <h1><%= resque %></h1>
10
- <table>
10
+ <table class='stats'>
11
11
  <% for key, value in resque.info.to_a.sort_by { |i| i[0].to_s } %>
12
12
  <tr>
13
- <td>
13
+ <th>
14
14
  <%= key %>
15
- </td>
15
+ </th>
16
16
  <td>
17
17
  <%= value %>
18
18
  </td>
@@ -22,13 +22,13 @@
22
22
 
23
23
  <% elsif params[:id] == 'redis' %>
24
24
 
25
- <h1><%= resque.redis %></h1>
26
- <table>
25
+ <h1><%= resque.redis.server %></h1>
26
+ <table class='stats'>
27
27
  <% for key, value in resque.redis.info.to_a.sort_by { |i| i[0].to_s } %>
28
28
  <tr>
29
- <td>
29
+ <th>
30
30
  <%= key %>
31
- </td>
31
+ </th>
32
32
  <td>
33
33
  <%= value %>
34
34
  </td>
@@ -39,8 +39,8 @@
39
39
  <% elsif params[:id] == 'keys' %>
40
40
 
41
41
  <h1>Keys owned by <%= resque %></h1>
42
- <h2>(All keys are actually prefixed with "resque:")</h2>
43
- <table>
42
+ <p class='sub'>(All keys are actually prefixed with "resque:")</p>
43
+ <table class='stats'>
44
44
  <tr>
45
45
  <th>key</th>
46
46
  <th>type</th>
@@ -48,9 +48,9 @@
48
48
  </tr>
49
49
  <% for key in resque.keys.sort %>
50
50
  <tr>
51
- <td>
51
+ <th>
52
52
  <a href="/stats/keys/<%= key %>"><%= key %></a>
53
- </td>
53
+ </th>
54
54
  <td><%= resque.redis.type key %></td>
55
55
  <td><%= redis_get_size key %></td>
56
56
  </tr>
@@ -1,7 +1,7 @@
1
1
  <% if params[:id] && worker = Resque::Worker.find(params[:id]) %>
2
2
 
3
3
  <h1>Worker <%= worker %></h1>
4
- <table>
4
+ <table class='workers'>
5
5
  <tr>
6
6
  <th>&nbsp;</th>
7
7
  <th>Host</th>
@@ -13,22 +13,22 @@
13
13
  <th>Processing</th>
14
14
  </tr>
15
15
  <tr>
16
- <td><img src="<%=u state = worker.state %>.png" alt="<%= state %>" title="<%= state %>"></td>
16
+ <td class='icon'><img src="<%=u state = worker.state %>.png" alt="<%= state %>" title="<%= state %>"></td>
17
17
 
18
18
  <% host, pid, queues = worker.to_s.split(':') %>
19
19
  <td><%= host %></td>
20
20
  <td><%= pid %></td>
21
21
  <td><span class="time"><%= worker.started %></a></td>
22
- <td><%= queues.split(',').map { |q| '<a class="queue" href="' + u("/queues/#{q}") + '">' + q + '</a>'}.join(', ') %></td>
22
+ <td class='queues'><%= queues.split(',').map { |q| '<a class="queue-tag" href="' + u("/queues/#{q}") + '">' + q + '</a>'}.join('') %></td>
23
23
  <td><%= worker.processed %></td>
24
24
  <td><%= worker.failed %></td>
25
- <td>
25
+ <td class='process'>
26
26
  <% data = worker.processing || {} %>
27
27
  <% if data['queue'] %>
28
28
  <code><%= data['payload']['class'] %></code>
29
29
  <small><a class="queue time" href="<%=u "/working/#{worker}" %>"><%= data['run_at'] %></a></small>
30
30
  <% else %>
31
- Waiting for more
31
+ <span class='waiting'>Waiting for a job...</span>
32
32
  <% end %>
33
33
  </td>
34
34
  </tr>
@@ -40,33 +40,39 @@
40
40
 
41
41
  <% else %>
42
42
 
43
- <h1><%= resque.workers.size %> Workers</h1>
44
- <table>
43
+ <h1 class='wi'><%= resque.workers.size %> Workers</h1>
44
+ <p class='intro'>The workers listed below are all registered as active on your system.</p>
45
+ <table class='workers'>
45
46
  <tr>
46
47
  <th>&nbsp;</th>
47
48
  <th>Where</th>
48
49
  <th>Queues</th>
49
50
  <th>Processing</th>
50
51
  </tr>
51
- <% for worker in resque.workers.sort_by { |w| w.to_s } %>
52
- <tr>
53
- <td><img src="<%=u state = worker.state %>.png" alt="<%= state %>" title="<%= state %>"></td>
52
+ <% for worker in (workers = resque.workers.sort_by { |w| w.to_s }) %>
53
+ <tr class="<%=state = worker.state%>">
54
+ <td class='icon'><img src="<%=u state %>.png" alt="<%= state %>" title="<%= state %>"></td>
54
55
 
55
56
  <% host, pid, queues = worker.to_s.split(':') %>
56
- <td><a href="/workers/<%= worker %>"><%= host %>:<%= pid %></a></td>
57
- <td><%= queues.split(',').map { |q| '<a class="queue" href="' + u("/queues/#{q}") + '">' + q + '</a>'}.join(', ') %></td>
57
+ <td class='where'><a href="<%=u "workers/#{worker}"%>"><%= host %>:<%= pid %></a></td>
58
+ <td class='queues'><%= queues.split(',').map { |q| '<a class="queue-tag" href="' + u("/queues/#{q}") + '">' + q + '</a>'}.join('') %></td>
58
59
 
59
- <td>
60
+ <td class='process'>
60
61
  <% data = worker.processing || {} %>
61
62
  <% if data['queue'] %>
62
63
  <code><%= data['payload']['class'] %></code>
63
64
  <small><a class="queue time" href="<%=u "/working/#{worker}" %>"><%= data['run_at'] %></a></small>
64
65
  <% else %>
65
- Waiting for more
66
+ <span class='waiting'>Waiting for a job...</span>
66
67
  <% end %>
67
68
  </td>
68
69
  </tr>
69
70
  <% end %>
71
+ <% if workers.empty? %>
72
+ <tr>
73
+ <td colspan='4' class='no-data'>There are no registered workers</td>
74
+ </tr>
75
+ <% end %>
70
76
  </table>
71
-
77
+ <%=poll%>
72
78
  <% end %>
@@ -28,8 +28,9 @@
28
28
  <% else %>
29
29
 
30
30
  <% workers = resque.working %>
31
- <h1><%= workers.size %> of <%= resque.workers.size %> Workers Working</h1>
32
- <table>
31
+ <h1 class='wi'><%= workers.size %> of <%= resque.workers.size %> Workers Working</h1>
32
+ <p class='intro'>The list below contains all workers which are currently running a job.</p>
33
+ <table class='workers'>
33
34
  <tr>
34
35
  <th>&nbsp;</th>
35
36
  <th>Where</th>
@@ -38,25 +39,25 @@
38
39
  </tr>
39
40
  <% if workers.empty? %>
40
41
  <tr>
41
- <td colspan="4">Not a one.</td>
42
+ <td colspan="4" class='no-data'>Nothing is happening right now...</td>
42
43
  </tr>
43
44
  <% end %>
44
45
 
45
46
  <% for worker in workers.sort_by { |w| w.job['run_at'] ? w.job['run_at'] : '' } %>
46
47
  <% job = worker.job %>
47
48
  <tr>
48
- <td><img src="<%=u state = worker.state %>.png" alt="<%= state %>" title="<%= state %>"></td>
49
+ <td class='icon'><img src="<%=u state = worker.state %>.png" alt="<%= state %>" title="<%= state %>"></td>
49
50
  <% host, pid, queues = worker.to_s.split(':') %>
50
- <td><a href="<%=u "/workers/#{worker}" %>"><%= host %>:<%= pid %></a></td>
51
- <td>
52
- <a class="queue" href="<%=u "/queues/#{job['queue']}" %>"><%= job['queue'] %></a>
51
+ <td class='where'><a href="<%=u "/workers/#{worker}" %>"><%= host %>:<%= pid %></a></td>
52
+ <td class='queues queue'>
53
+ <a class="queue-tag" href="<%=u "/queues/#{job['queue']}" %>"><%= job['queue'] %></a>
53
54
  </td>
54
- <td>
55
+ <td class='process'>
55
56
  <% if job['queue'] %>
56
57
  <code><%= job['payload']['class'] %></code>
57
58
  <small><a class="queue time" href="<%=u "/working/#{worker}" %>"><%= job['run_at'] %></a></small>
58
59
  <% else %>
59
- Waiting for more
60
+ <span class='waiting'>Waiting for a job...</span>
60
61
  <% end %>
61
62
  </td>
62
63
  </tr>
@@ -1,3 +1,3 @@
1
1
  module Resque
2
- Version = '1.0.0'
2
+ Version = '1.1.0'
3
3
  end
data/test/worker_test.rb CHANGED
@@ -22,6 +22,14 @@ context "Resque::Worker" do
22
22
  assert_equal 10, Resque::Failure.all(0, 20).size
23
23
  end
24
24
 
25
+ test "can clear failed jobs" do
26
+ Resque::Job.create(:jobs, BadJob)
27
+ @worker.work(0)
28
+ assert_equal 1, Resque::Failure.count
29
+ Resque::Failure.clear
30
+ assert_equal 0, Resque::Failure.count
31
+ end
32
+
25
33
  test "catches exceptional jobs" do
26
34
  Resque::Job.create(:jobs, BadJob)
27
35
  Resque::Job.create(:jobs, BadJob)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resque
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Wanstrath
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-03 00:00:00 -08:00
12
+ date: 2009-11-04 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -29,7 +29,6 @@ files:
29
29
  - LICENSE
30
30
  - README.markdown
31
31
  - Rakefile
32
- - TODO.md
33
32
  - bin/resque
34
33
  - bin/resque-web
35
34
  - config.ru
@@ -40,7 +39,8 @@ files:
40
39
  - examples/demo/app.rb
41
40
  - examples/demo/config.ru
42
41
  - examples/demo/job.rb
43
- - examples/existing_classes_as_jobs.rb
42
+ - examples/god/resque.god
43
+ - examples/god/stale.god
44
44
  - examples/instance.rb
45
45
  - examples/simple.rb
46
46
  - init.rb
@@ -56,13 +56,10 @@ files:
56
56
  - lib/resque/server/public/idle.png
57
57
  - lib/resque/server/public/jquery-1.3.2.min.js
58
58
  - lib/resque/server/public/jquery.relatize_date.js
59
- - lib/resque/server/public/nav-bg.png
59
+ - lib/resque/server/public/poll.png
60
60
  - lib/resque/server/public/ranger.js
61
61
  - lib/resque/server/public/reset.css
62
62
  - lib/resque/server/public/style.css
63
- - lib/resque/server/public/tab_b.gif
64
- - lib/resque/server/public/tab_r.gif
65
- - lib/resque/server/public/tabs.css
66
63
  - lib/resque/server/public/working.png
67
64
  - lib/resque/server/views/error.erb
68
65
  - lib/resque/server/views/failed.erb
@@ -119,6 +116,5 @@ test_files:
119
116
  - examples/async_helper.rb
120
117
  - examples/demo/app.rb
121
118
  - examples/demo/job.rb
122
- - examples/existing_classes_as_jobs.rb
123
119
  - examples/instance.rb
124
120
  - examples/simple.rb
data/TODO.md DELETED
@@ -1,60 +0,0 @@
1
- Little Stuff
2
- -----------
3
-
4
- [ ] Show stale workers in red with a warning icon in the Sinatra app
5
-
6
-
7
- Big Stuff
8
- ---------
9
-
10
- ### async
11
-
12
- I want the `async` helper to be first class. Something like this:
13
-
14
- class Repository < ActiveRecord::Base
15
- include Resque::AsyncHelper
16
- end
17
-
18
- This adds an `async` instance method and a `perform` class method.
19
-
20
- The `async` method looks like this:
21
-
22
- def async(method, *args)
23
- Resque.enqueue(self.class, id, method, *args)
24
- end
25
-
26
- The `perform` method looks like this:
27
-
28
- def self.perform(id, method, *args)
29
- find(id).send(method, *args)
30
- end
31
-
32
- Of course, you can define your own `self.perform` and have it still
33
- work beautifully. We might override ours in GitHub to look like this:
34
-
35
- def self.perform(id, method, *args)
36
- return unless repo = cached_by_id(id)
37
- repo.send(method, *args)
38
- end
39
-
40
- Then: `@repo.async(:update_disk_usage)` issues a job equivalent to:
41
-
42
- Resque.enqueue(Repository, 44, :update_disk_usage)
43
-
44
- Booyah.
45
-
46
-
47
- ### gem install
48
-
49
- `gem install resque` should pull in yajl, redis, sinatra, rake, and rack
50
-
51
- Do it like Unicorn does it.
52
-
53
- ### Parent / Child => Master / Workers
54
-
55
- On an ideal setup (REE + Linux) you'll have 2N Resque processes
56
- running at any time: N parents and N children.
57
-
58
- We can cut this number down to N+1 by moving from a parent / child
59
- architecture to a master / workers architecture.
60
-
@@ -1,3 +0,0 @@
1
- # Let's say this is a class in your app, already
2
-
3
- # TODO: example
Binary file
Binary file
Binary file
@@ -1,189 +0,0 @@
1
- /* tabs styles, based on http://www.alistapart.com/articles/slidingdoors */
2
-
3
- DIV.tabs
4
- {
5
- float : left;
6
- width : 100%;
7
- background : url("tab_b.gif") repeat-x bottom;
8
- margin-bottom : 4px;
9
- }
10
-
11
- DIV.tabs UL
12
- {
13
- margin : 0px;
14
- padding-left : 10px;
15
- list-style : none;
16
- }
17
-
18
- DIV.tabs LI, DIV.tabs FORM
19
- {
20
- display : inline;
21
- margin : 0px;
22
- padding : 0px;
23
- }
24
-
25
- DIV.tabs FORM
26
- {
27
- float : right;
28
- }
29
-
30
- DIV.tabs A
31
- {
32
- float : left;
33
- background : url("tab_r.gif") no-repeat right top;
34
- border-bottom : 1px solid #84B0C7;
35
- font-size : 80%;
36
- font-weight : bold;
37
- text-decoration : none;
38
- }
39
-
40
- DIV.tabs A:hover
41
- {
42
- background-position: 100% -150px;
43
- }
44
-
45
- DIV.tabs A:link, DIV.tabs A:visited,
46
- DIV.tabs A:active, DIV.tabs A:hover
47
- {
48
- color: #1A419D;
49
- }
50
-
51
- DIV.tabs SPAN
52
- {
53
- float : left;
54
- display : block;
55
- background : url("tab_l.gif") no-repeat left top;
56
- padding : 5px 9px;
57
- white-space : nowrap;
58
- }
59
-
60
- DIV.tabs INPUT
61
- {
62
- float : right;
63
- display : inline;
64
- font-size : 1em;
65
- }
66
-
67
- DIV.tabs TD
68
- {
69
- font-size : 80%;
70
- font-weight : bold;
71
- text-decoration : none;
72
- }
73
-
74
- /* Commented Backslash Hack hides rule from IE5-Mac \*/
75
- DIV.tabs SPAN {float : none;}
76
- /* End IE5-Mac hack */
77
-
78
- DIV.tabs A:hover SPAN
79
- {
80
- background-position: 0% -150px;
81
- }
82
-
83
- DIV.tabs LI.current A
84
- {
85
- background-position: 100% -150px;
86
- border-width : 0px;
87
- }
88
-
89
- DIV.tabs LI.current SPAN
90
- {
91
- background-position: 0% -150px;
92
- padding-bottom : 6px;
93
- }
94
-
95
- DIV.navpath
96
- {
97
- background : none;
98
- border : none;
99
- border-bottom : 1px solid #84B0C7;
100
- text-align : center;
101
- margin : 2px;
102
- padding : 2px;
103
- }
104
- div.tabs {
105
- background: url(nav-bg.png) repeat-x !important;
106
- margin: 0 !important;
107
- margin-bottom: 0 !important;
108
- padding: 8px 0;
109
- float: none;
110
- border-bottom: 1px solid #A4B0BC;
111
- font-size: 100%;
112
- }
113
-
114
- div.tabs li {
115
- line-height: 100%;
116
- display: block;
117
- float: left;
118
- margin-right: 10px;
119
- }
120
-
121
- div.tabs a {
122
- background: none;
123
- font-size: 100%;
124
- padding: 5px 8px;
125
- text-shadow: 1px 1px #eff4f7;
126
- }
127
-
128
- div.tabs span {
129
- background: none;
130
- padding: 0;
131
- }
132
-
133
- div.tabs li.current a {
134
- background: #bdcad4;
135
- -webkit-border-radius: 4px;
136
- -moz-border-radius: 4px;
137
- -o-border-radius: 4px;
138
- border-radius: 4px;
139
- text-shadow: none;
140
- }
141
-
142
- div.tabs li.current span {
143
- padding: 0;
144
- }
145
-
146
- div.tabs a:link, div.tabs a:visited, div.tabs a:active, div.tabs a:hover
147
- {
148
- color: #546e80;
149
- border: none;
150
- }
151
-
152
- div.tabs ul:after {
153
- content: ".";
154
- display: block;
155
- clear: both;
156
- height: 0;
157
- visibility: hidden;
158
- }
159
-
160
- div.tabs + div.tabs {
161
- background: #f2f6f9 !important;
162
- border-bottom: 1px solid #e2ecf3 !important;
163
- }
164
-
165
- div.navpath {
166
- line-height: 100%;
167
- text-align: left;
168
- background: #f2f6f9 !important;
169
- border-bottom: 1px solid #e2ecf3 !important;
170
- padding: 0;
171
- margin: 0;
172
- }
173
-
174
- div.navpath:after {
175
- content: ".";
176
- display: block;
177
- visibility: hidden;
178
- clear: both;
179
- padding: 0;
180
- margin: 0;
181
- height: 0;
182
- }
183
-
184
- div.navpath a {
185
- display: block;
186
- float: left;
187
- padding: 8px 20px;
188
- color: #8c99a3;
189
- }