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.
- data/.gemrc +1 -0
- data/.gitignore +5 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +68 -0
- data/LICENSE +19 -0
- data/README.md +104 -0
- data/bin/howler +3 -0
- data/howler.gemspec +24 -0
- data/lib/howler.rb +34 -0
- data/lib/howler/async.rb +15 -0
- data/lib/howler/config.ru +4 -0
- data/lib/howler/exceptions.rb +5 -0
- data/lib/howler/exceptions/error.rb +25 -0
- data/lib/howler/exceptions/failed.rb +9 -0
- data/lib/howler/exceptions/notify.rb +15 -0
- data/lib/howler/exceptions/retry.rb +12 -0
- data/lib/howler/manager.rb +123 -0
- data/lib/howler/message.rb +15 -0
- data/lib/howler/queue.rb +138 -0
- data/lib/howler/runner.rb +34 -0
- data/lib/howler/support/config.rb +33 -0
- data/lib/howler/support/logger.rb +57 -0
- data/lib/howler/support/util.rb +23 -0
- data/lib/howler/support/version.rb +3 -0
- data/lib/howler/web.rb +47 -0
- data/lib/howler/web/public/application.css +24 -0
- data/lib/howler/web/public/bootstrap.css +3990 -0
- data/lib/howler/web/public/bootstrap.min.css +689 -0
- data/lib/howler/web/public/queues.css +19 -0
- data/lib/howler/web/views/failed_messages.erb +27 -0
- data/lib/howler/web/views/html.erb +10 -0
- data/lib/howler/web/views/index.erb +11 -0
- data/lib/howler/web/views/navigation.erb +25 -0
- data/lib/howler/web/views/notification_messages.erb +24 -0
- data/lib/howler/web/views/notifications.erb +15 -0
- data/lib/howler/web/views/pending_messages.erb +24 -0
- data/lib/howler/web/views/processed_messages.erb +28 -0
- data/lib/howler/web/views/queue.erb +36 -0
- data/lib/howler/web/views/queue_table.erb +27 -0
- data/lib/howler/web/views/queues.erb +15 -0
- data/lib/howler/worker.rb +17 -0
- data/spec/models/async_spec.rb +76 -0
- data/spec/models/exceptions/failed_spec.rb +15 -0
- data/spec/models/exceptions/message_spec.rb +53 -0
- data/spec/models/exceptions/notify_spec.rb +26 -0
- data/spec/models/exceptions/retry_spec.rb +49 -0
- data/spec/models/howler_spec.rb +69 -0
- data/spec/models/manager_spec.rb +397 -0
- data/spec/models/message_spec.rb +78 -0
- data/spec/models/queue_spec.rb +539 -0
- data/spec/models/runner_spec.rb +109 -0
- data/spec/models/support/config_spec.rb +56 -0
- data/spec/models/support/logger_spec.rb +147 -0
- data/spec/models/support/util_spec.rb +44 -0
- data/spec/models/worker_spec.rb +54 -0
- data/spec/requests/web_spec.rb +220 -0
- data/spec/spec_helper.rb +93 -0
- 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,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,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>© <%= 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,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
|