howler 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|