howler 1.0.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.
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