howler 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/.gemrc +1 -0
  2. data/.gitignore +5 -0
  3. data/.rspec +2 -0
  4. data/.rvmrc +1 -0
  5. data/Gemfile +2 -0
  6. data/Gemfile.lock +68 -0
  7. data/LICENSE +19 -0
  8. data/README.md +104 -0
  9. data/bin/howler +3 -0
  10. data/howler.gemspec +24 -0
  11. data/lib/howler.rb +34 -0
  12. data/lib/howler/async.rb +15 -0
  13. data/lib/howler/config.ru +4 -0
  14. data/lib/howler/exceptions.rb +5 -0
  15. data/lib/howler/exceptions/error.rb +25 -0
  16. data/lib/howler/exceptions/failed.rb +9 -0
  17. data/lib/howler/exceptions/notify.rb +15 -0
  18. data/lib/howler/exceptions/retry.rb +12 -0
  19. data/lib/howler/manager.rb +123 -0
  20. data/lib/howler/message.rb +15 -0
  21. data/lib/howler/queue.rb +138 -0
  22. data/lib/howler/runner.rb +34 -0
  23. data/lib/howler/support/config.rb +33 -0
  24. data/lib/howler/support/logger.rb +57 -0
  25. data/lib/howler/support/util.rb +23 -0
  26. data/lib/howler/support/version.rb +3 -0
  27. data/lib/howler/web.rb +47 -0
  28. data/lib/howler/web/public/application.css +24 -0
  29. data/lib/howler/web/public/bootstrap.css +3990 -0
  30. data/lib/howler/web/public/bootstrap.min.css +689 -0
  31. data/lib/howler/web/public/queues.css +19 -0
  32. data/lib/howler/web/views/failed_messages.erb +27 -0
  33. data/lib/howler/web/views/html.erb +10 -0
  34. data/lib/howler/web/views/index.erb +11 -0
  35. data/lib/howler/web/views/navigation.erb +25 -0
  36. data/lib/howler/web/views/notification_messages.erb +24 -0
  37. data/lib/howler/web/views/notifications.erb +15 -0
  38. data/lib/howler/web/views/pending_messages.erb +24 -0
  39. data/lib/howler/web/views/processed_messages.erb +28 -0
  40. data/lib/howler/web/views/queue.erb +36 -0
  41. data/lib/howler/web/views/queue_table.erb +27 -0
  42. data/lib/howler/web/views/queues.erb +15 -0
  43. data/lib/howler/worker.rb +17 -0
  44. data/spec/models/async_spec.rb +76 -0
  45. data/spec/models/exceptions/failed_spec.rb +15 -0
  46. data/spec/models/exceptions/message_spec.rb +53 -0
  47. data/spec/models/exceptions/notify_spec.rb +26 -0
  48. data/spec/models/exceptions/retry_spec.rb +49 -0
  49. data/spec/models/howler_spec.rb +69 -0
  50. data/spec/models/manager_spec.rb +397 -0
  51. data/spec/models/message_spec.rb +78 -0
  52. data/spec/models/queue_spec.rb +539 -0
  53. data/spec/models/runner_spec.rb +109 -0
  54. data/spec/models/support/config_spec.rb +56 -0
  55. data/spec/models/support/logger_spec.rb +147 -0
  56. data/spec/models/support/util_spec.rb +44 -0
  57. data/spec/models/worker_spec.rb +54 -0
  58. data/spec/requests/web_spec.rb +220 -0
  59. data/spec/spec_helper.rb +93 -0
  60. metadata +265 -0
@@ -0,0 +1,19 @@
1
+ .queue .title {
2
+ margin-top: 80px;
3
+ margin-left: 30px;
4
+ }
5
+
6
+ .table_title {
7
+ margin-left: 30px;
8
+ line-height: 22px;
9
+ font-size: 20px;
10
+ font-weight: bold;
11
+ }
12
+
13
+ .table {
14
+ margin: 30px;
15
+ }
16
+
17
+ td.args{
18
+ max-width: 175px;
19
+ }
@@ -0,0 +1,27 @@
1
+ <div style="margin-top: 30px;" class="message_list">
2
+ <div class="table_title">Failed Messages</div>
3
+ <div class="table stable-striped table-bordered">
4
+ <table>
5
+ <thead>
6
+ <tr>
7
+ <th>Message</th>
8
+ <th>Created At</th>
9
+ <th>Failed At</th>
10
+ <th>Cause</th>
11
+ <th>Status</th>
12
+ </tr>
13
+ </thead>
14
+ <% @failed.each do |message| %>
15
+ <tr>
16
+ <td><%= message['class'] %>.<%= message['method'] %>(<%= process_args(message['args']) %>)</td>
17
+ <td><%= Howler::Util.at(message['created_at']) %></td>
18
+ <td class="failed_at"><%= Howler::Util.at(message['failed_at']) %></td>
19
+ <td><%= message['cause'] %></td>
20
+ <td><%= message['status'] %></td>
21
+ </tr>
22
+ <% end %>
23
+ <tbody>
24
+ </tbody>
25
+ </table>
26
+ </div>
27
+ </div>
@@ -0,0 +1,10 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Howler</title>
6
+ <meta name="description" content="Monitoring Panel for Howler.">
7
+ <link href="/application.css" rel="stylesheet">
8
+ <link href="/queues.css" rel="stylesheet">
9
+ <link href="/bootstrap.min.css" rel="stylesheet">
10
+ </head>
@@ -0,0 +1,11 @@
1
+ <%= erb(:html) %>
2
+
3
+ <body>
4
+
5
+ <%= erb(:navigation) %>
6
+
7
+ <footer>
8
+ <p>&copy; <%= Time.now.year %></p>
9
+ </footer>
10
+ </body>
11
+ </html>
@@ -0,0 +1,25 @@
1
+ <div id="navigation" class="navbar navbar-fixed-top">
2
+ <div class="navbar-inner">
3
+ <div class="container">
4
+ <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
5
+ <span class="icon-bar"></span>
6
+ <span class="icon-bar"></span>
7
+ <span class="icon-bar"></span>
8
+ </a>
9
+ <div class="nav-collapse">
10
+ <ul class="nav">
11
+ <li class="item"><a href="/">Howler</a></li>
12
+ <li class="divider-vertical"></li>
13
+ <li class="item"><a href="/queues">Queues</a></li>
14
+ <li class="divider-vertical"></li>
15
+ <li class="item"><a href="/notifications">Notifications</a></li>
16
+ <li class="divider-vertical"></li>
17
+ <li class="item"><a href="/statistics">Statistics</a></li>
18
+ <li class="divider-vertical"></li>
19
+ <li class="item"><a href="/settings">Settings</a></li>
20
+ <li class="divider-vertical"></li>
21
+ </ul>
22
+ </div>
23
+ </div>
24
+ </div>
25
+ </div>
@@ -0,0 +1,24 @@
1
+ <div style="margin-top: 70px;" class="message_list">
2
+ <div class="table_title"></div>
3
+ <div class="table stable-striped table-bordered">
4
+ <table>
5
+ <thead>
6
+ <tr>
7
+ <th>Message</th>
8
+ <th>Notification</th>
9
+ <th>Occurred</th>
10
+ </tr>
11
+ </thead>
12
+
13
+ <tbody>
14
+ <% @notifications.each do |notification| %>
15
+ <tr>
16
+ <td><%= notification['class'] %>.<%= notification['method'] %>(<%= process_args(notification['args']) %>)</td>
17
+ <td><%= Howler::Util.at(notification['created_at']) %></td>
18
+ <td><%= notification['status'] %></td>
19
+ </tr>
20
+ <% end %>
21
+ </tbody>
22
+ </table>
23
+ </div>
24
+ </div>
@@ -0,0 +1,15 @@
1
+ <%= erb(:html) %>
2
+
3
+ <body>
4
+
5
+ <%= erb(:navigation) %>
6
+
7
+ <div id="notifications">
8
+ <%= erb(:notification_messages) %>
9
+ </div>
10
+
11
+ <footer>
12
+ <p>&copy; <%= Time.now.year %></p>
13
+ </footer>
14
+ </body>
15
+ </html>
@@ -0,0 +1,24 @@
1
+ <div style="margin-top: 40px;" class="message_list">
2
+ <div class="table_title">Pending Messages</div>
3
+ <div class="table stable-striped table-bordered">
4
+ <table>
5
+ <thead>
6
+ <tr>
7
+ <th>Message</th>
8
+ <th>Created At</th>
9
+ <th>Status</th>
10
+ </tr>
11
+ </thead>
12
+
13
+ <tbody>
14
+ <% @pending.each do |message| %>
15
+ <tr>
16
+ <td><%= message['class'] %>.<%= message['method'] %>(<%= process_args(message['args']) %>)</td>
17
+ <td><%= Howler::Util.at(message['created_at']) %></td>
18
+ <td><%= message['status'] || 'pending' %></td>
19
+ </tr>
20
+ <% end %>
21
+ </tbody>
22
+ </table>
23
+ </div>
24
+ </div>
@@ -0,0 +1,28 @@
1
+ <div style="margin-top: 10px;" class="message_list">
2
+ <div class="table_title">Processed Messages</div>
3
+ <div class="table stable-striped table-bordered">
4
+ <table>
5
+ <thead>
6
+ <tr>
7
+ <th>Message</th>
8
+ <th>Created At</th>
9
+ <th>System Runtime</th>
10
+ <th>Real Runtime</th>
11
+ <th>Status</th>
12
+ </tr>
13
+ </thead>
14
+
15
+ <tbody>
16
+ <% @messages.each do |message| %>
17
+ <tr>
18
+ <td><%= message['class'] %>.<%= message['method'] %>(<%= message['args'].to_s.gsub(/^\[|\]$/, '') %>)</td>
19
+ <td><%= Howler::Util.at(message['created_at']) %></td>
20
+ <td><%= message['time']['system'] %></td>
21
+ <td><%= message['time']['user'] %></td>
22
+ <td><%= message['status'] %></td>
23
+ </tr>
24
+ <% end %>
25
+ </tbody>
26
+ </table>
27
+ </div>
28
+ </div>
@@ -0,0 +1,36 @@
1
+ <%= erb(:html) %>
2
+
3
+ <body>
4
+
5
+ <%= erb(:navigation) %>
6
+
7
+
8
+ <div class="queue">
9
+ <div class="title">
10
+ <h2 id="<%= @queue.id %>">Queue: <%= @queue.id %></h2>
11
+ </div>
12
+ </div>
13
+
14
+ <div class="metadata">
15
+ <div id="pending_messages">
16
+ <%= erb(:pending_messages) %>
17
+ </div>
18
+
19
+ <div id="processed_messages">
20
+ <%= erb(:processed_messages) %>
21
+ </div>
22
+
23
+ <div id="failed_messages">
24
+ <%= erb(:failed_messages) %>
25
+ </div>
26
+ </div>
27
+
28
+ <div class="lists">
29
+
30
+ </div>
31
+
32
+ <footer>
33
+ <p>&copy; <%= Time.now.year %></p>
34
+ </footer>
35
+ </body>
36
+ </html>
@@ -0,0 +1,27 @@
1
+ <div style="margin-top: 70px;" class="queues">
2
+ <div class="table table-striped table-bordered">
3
+ <table>
4
+ <thead>
5
+ <tr>
6
+ <th>Queue ID</th>
7
+ <th>Created</th>
8
+ <th>Processed ct.</th>
9
+ <th>Failed ct.</th>
10
+ <th></th>
11
+ </tr>
12
+ </thead>
13
+
14
+ <tbody>
15
+ <% @queues.each do |queue| %>
16
+ <tr id="queue_<%= queue.id %>">
17
+ <td><%= queue.id %></td>
18
+ <td><%= queue.created_at %></td>
19
+ <td><%= queue.success %></td>
20
+ <td class="error"><%= queue.error %></td>
21
+ <td><a href="/queues/<%= queue.id %>">More...</a></td>
22
+ </tr>
23
+ <% end %>
24
+ </tbody>
25
+ </table>
26
+ </div>
27
+ </div>
@@ -0,0 +1,15 @@
1
+ <%= erb(:html) %>
2
+
3
+ <body>
4
+
5
+ <%= erb(:navigation) %>
6
+
7
+ <div class="lists">
8
+ <%= erb(:queue_table) %>
9
+ </div>
10
+
11
+ <footer>
12
+ <p>&copy; <%= Time.now.year %></p>
13
+ </footer>
14
+ </body>
15
+ </html>
@@ -0,0 +1,17 @@
1
+ require 'celluloid'
2
+
3
+ module Howler
4
+ class Worker
5
+ include Celluloid
6
+
7
+ def perform(message, queue)
8
+ queue = Howler::Queue.new(queue) unless queue.is_a?(Howler::Queue)
9
+
10
+ queue.statistics(message.klass, message.method, message.args) do
11
+ message.klass.new.send(message.method, *message.args)
12
+ end
13
+
14
+ Howler::Manager.current.done_chewing(current_actor)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,76 @@
1
+ require "spec_helper"
2
+
3
+ describe Howler::Async do
4
+ class Worker
5
+ async :fetch, :parse
6
+
7
+ def fetch
8
+ end
9
+
10
+ def self.parse
11
+ end
12
+ end
13
+
14
+ describe "#async" do
15
+ describe "for an instance method" do
16
+ it "should define 'async_' prefixed class methods" do
17
+ Worker.new.respond_to?(:fetch).should == true
18
+
19
+ Worker.respond_to?(:async_fetch).should == true
20
+ end
21
+ end
22
+
23
+ describe "for a class method" do
24
+ it "should define 'async_' prefixed class methods" do
25
+ Worker.respond_to?(:parse).should == true
26
+
27
+ Worker.respond_to?(:async_parse).should == true
28
+ end
29
+ end
30
+
31
+ describe "async_ methods" do
32
+ it "should accept 0 arguments" do
33
+ expect {
34
+ Worker.async_fetch
35
+ }.not_to raise_error(ArgumentError)
36
+ end
37
+
38
+ it "should accept 1 argument" do
39
+ expect {
40
+ Worker.async_fetch(1)
41
+ }.not_to raise_error(ArgumentError)
42
+ end
43
+
44
+ it "should accept many arguments" do
45
+ expect {
46
+ Worker.async_fetch(1,2,3)
47
+ }.not_to raise_error(ArgumentError)
48
+ end
49
+ end
50
+
51
+ describe "storing the message" do
52
+ let!(:manager) { Howler::Manager.current }
53
+
54
+ before do
55
+ Howler::Manager.stub(:current).and_return(manager)
56
+ end
57
+
58
+ describe "when there are no arguments" do
59
+ it "should register the message" do
60
+
61
+ manager.should_receive(:push).with("Worker", :fetch, [])
62
+
63
+ Worker.async_fetch
64
+ end
65
+ end
66
+
67
+ describe "when there are arguments" do
68
+ it "should register the message" do
69
+ manager.should_receive(:push).with("Worker", :fetch, [1, 2, {:key => 'value'}])
70
+
71
+ Worker.async_fetch(1, 2, :key => 'value')
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,15 @@
1
+ require "spec_helper"
2
+
3
+ describe Howler::Message::Failed do
4
+ it "should inherit from Howler::Message::Error" do
5
+ Howler::Message::Failed.ancestors.should include(Howler::Message::Error)
6
+ end
7
+
8
+ describe "#message" do
9
+ it "should have a default message" do
10
+ Timecop.freeze(DateTime.now) do
11
+ subject.message.should == "Message Failed at #{Howler::Util.now}"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,53 @@
1
+ require "spec_helper"
2
+
3
+ describe Howler::Message::Error do
4
+ it "should inherit from Exception" do
5
+ Howler::Message::Error.ancestors.should include(Exception)
6
+ end
7
+
8
+ describe "#message" do
9
+ it "should set the message" do
10
+ subject.message = "1234"
11
+
12
+ subject.message.should == "1234"
13
+ end
14
+ end
15
+
16
+ describe "#backtrace" do
17
+ it "should store a short backtrace" do
18
+ begin
19
+ raise Howler::Message::Error
20
+ rescue Howler::Message::Error => e
21
+ e.info['backtrace'].class.should == Array
22
+ e.info['backtrace'].length.should == 8
23
+ end
24
+ end
25
+ end
26
+
27
+ describe "#info" do
28
+ let!(:short_backtrace) { mock("short:backtrace") }
29
+ let!(:backtrace) { mock("backtrace", :[] => short_backtrace ) }
30
+
31
+ before do
32
+ Howler::Message::Error.any_instance.stub(:backtrace).and_return(backtrace)
33
+ Howler::Message::Error.any_instance.stub(:message).and_return("message")
34
+ end
35
+
36
+ begin
37
+ raise Howler::Message::Error.new(:key => 'value', :another => 1)
38
+ rescue Howler::Message::Error => e
39
+ it "should store a short backtrace" do
40
+ e.info['backtrace'].should == short_backtrace
41
+ end
42
+
43
+ it "should store the message" do
44
+ e.info['message'].should == 'message'
45
+ end
46
+
47
+ it "should store arbitrary information" do
48
+ e.info['key'].should == 'value'
49
+ e.info['another'].should == 1
50
+ end
51
+ end
52
+ end
53
+ end