whacamole 0.0.1 → 0.1.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/.gitignore +2 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +26 -0
- data/README.md +64 -0
- data/Rakefile +20 -0
- data/bin/whacamole +13 -0
- data/lib/whacamole.rb +30 -0
- data/lib/whacamole/config.rb +11 -0
- data/lib/whacamole/events.rb +26 -0
- data/lib/whacamole/heroku_wrapper.rb +72 -0
- data/lib/whacamole/stream.rb +81 -0
- data/lib/whacamole/version.rb +1 -1
- data/spec/config_spec.rb +11 -0
- data/spec/events_spec.rb +36 -0
- data/spec/heroku_wrapper_spec.rb +44 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/stream_spec.rb +86 -0
- data/spec/whacamole_spec.rb +33 -0
- data/whacamole.gemspec +4 -3
- metadata +33 -24
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
whacamole (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.2.4)
|
10
|
+
rake (10.1.0)
|
11
|
+
rspec (2.14.1)
|
12
|
+
rspec-core (~> 2.14.0)
|
13
|
+
rspec-expectations (~> 2.14.0)
|
14
|
+
rspec-mocks (~> 2.14.0)
|
15
|
+
rspec-core (2.14.5)
|
16
|
+
rspec-expectations (2.14.2)
|
17
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
18
|
+
rspec-mocks (2.14.3)
|
19
|
+
|
20
|
+
PLATFORMS
|
21
|
+
ruby
|
22
|
+
|
23
|
+
DEPENDENCIES
|
24
|
+
rake (~> 10.1)
|
25
|
+
rspec (~> 2.11)
|
26
|
+
whacamole!
|
data/README.md
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
whacamole
|
2
|
+
=========
|
3
|
+
|
4
|
+
[](https://travis-ci.org/arches/whacamole)
|
5
|
+
|
6
|
+
Whacamole keeps track of your Heroku dynos' memory usage and restarts large dynos before they start
|
7
|
+
swapping to disk (aka get super slow).
|
8
|
+
|
9
|
+
Here’s what Heroku says about dyno memory usage:
|
10
|
+
|
11
|
+
> Dynos are available in 1X or 2X sizes and are allocated 512MB or 1024MB respectively.
|
12
|
+
>
|
13
|
+
> Dynos whose processes exceed their memory quota are identified by an R14 error in the logs. This doesn’t terminate the process, but it does warn of deteriorating application conditions: memory used above quota will swap out to disk, which substantially degrades dyno performance.
|
14
|
+
>
|
15
|
+
> If the memory size keeps growing until it reaches three times its quota, the dyno manager will restart your dyno with an R15 error.
|
16
|
+
>
|
17
|
+
> - From https://devcenter.heroku.com/articles/dynos on 8/8/13
|
18
|
+
|
19
|
+
Heroku dynos swap to disk for up to 3GB. That is not good and that is the problem whacamole addresses.
|
20
|
+
|
21
|
+
# Usage
|
22
|
+
|
23
|
+
Enable log-runtime-metrics on your heroku app:
|
24
|
+
|
25
|
+
```bash
|
26
|
+
$ heroku labs:enable log-runtime-metrics --app YOUR_APP_NAME
|
27
|
+
```
|
28
|
+
|
29
|
+
Add whacamole to your gemfile:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
gem 'whacamole'
|
33
|
+
```
|
34
|
+
|
35
|
+
Create a config file with your app info. Personally I put it in my Rails app at config/whacamole.rb. The
|
36
|
+
most important parts are your app name and your Heroku api token (which can be found by running `heroku auth:token`
|
37
|
+
on the command line).
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
Whacamole.configure("HEROKU APP NAME") do |config|
|
41
|
+
config.api_token = ENV['HEROKU_API_TOKEN'] # you could also paste your token in here as a string
|
42
|
+
end
|
43
|
+
|
44
|
+
# you can monitor multiple apps at once, just add more configure blocks
|
45
|
+
Whacamole.configure("ANOTHER HEROKU APP") do |config|
|
46
|
+
config.api_token = ENV['HEROKU_API_TOKEN'] # you could also paste your token in here as a string
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
Add whacamole to your Procfile, specifying the config file you created:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
whacamole: bundle exec whacamole -c ./config/whacamole.rb
|
54
|
+
```
|
55
|
+
|
56
|
+
Start foreman, and you're done!
|
57
|
+
|
58
|
+
```bash
|
59
|
+
# locally
|
60
|
+
$ foreman start whacamole
|
61
|
+
|
62
|
+
# on heroku
|
63
|
+
$ heroku ps:scale whacamole=1 --app YOUR_APP_NAME
|
64
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'rake'
|
11
|
+
|
12
|
+
require 'rspec/core/rake_task'
|
13
|
+
|
14
|
+
desc 'Default: run specs.'
|
15
|
+
task :default => [:spec]
|
16
|
+
|
17
|
+
desc "Run specs"
|
18
|
+
RSpec::Core::RakeTask.new do |t|
|
19
|
+
end
|
20
|
+
|
data/bin/whacamole
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'whacamole'
|
4
|
+
$stdout.sync = true
|
5
|
+
|
6
|
+
if ARGV[0] == "-c"
|
7
|
+
require ARGV[1]
|
8
|
+
|
9
|
+
Whacamole.monitor
|
10
|
+
else
|
11
|
+
puts "No Whacamole config specified. Use `whacamole -c path/to/config`\nSee http://github.com/arches/whacamole for usage."
|
12
|
+
end
|
13
|
+
|
data/lib/whacamole.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'whacamole/config'
|
2
|
+
require 'whacamole/events'
|
3
|
+
require 'whacamole/heroku_wrapper'
|
4
|
+
require 'whacamole/stream'
|
5
|
+
|
6
|
+
module Whacamole
|
7
|
+
|
8
|
+
@@config = {}
|
9
|
+
|
10
|
+
def self.configure(app_name)
|
11
|
+
@@config[app_name.to_s] ||= Config.new(app_name)
|
12
|
+
yield @@config[app_name.to_s]
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.monitor
|
16
|
+
threads = []
|
17
|
+
@@config.each do |app_name, config|
|
18
|
+
threads << Thread.new do
|
19
|
+
heroku = HerokuWrapper.new(app_name, config.api_token)
|
20
|
+
|
21
|
+
while true
|
22
|
+
stream_url = heroku.create_log_session
|
23
|
+
Stream.new(stream_url, heroku, &config.event_handler).watch
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
threads.collect(&:join)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Whacamole
|
2
|
+
module Events
|
3
|
+
class Event
|
4
|
+
attr_accessor :process
|
5
|
+
|
6
|
+
def initialize(attributes={})
|
7
|
+
attributes.each do |k,v|
|
8
|
+
self.send("#{k}=", v)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class DynoSize < Event
|
14
|
+
attr_accessor :units
|
15
|
+
attr_reader :size
|
16
|
+
|
17
|
+
def size=(size)
|
18
|
+
@size = size.to_f
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class DynoRestart < Event
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'net/http'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Whacamole
|
6
|
+
class HerokuWrapper
|
7
|
+
attr_accessor :api_token, :app_name
|
8
|
+
|
9
|
+
RESTART_RATE_LIMIT = 30*60
|
10
|
+
|
11
|
+
def initialize(app_name, api_token)
|
12
|
+
self.app_name = app_name
|
13
|
+
self.api_token = api_token
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_log_session
|
17
|
+
uri = URI(log_sessions_url)
|
18
|
+
req = Net::HTTP::Post.new(uri.path)
|
19
|
+
req['Authorization'] = authorization
|
20
|
+
req['Content-type'] = content_type
|
21
|
+
req['Accept'] = accept
|
22
|
+
req.set_form_data({'tail' => true})
|
23
|
+
res = Net::HTTP.start(uri.host, uri.port, :use_ssl => (uri.scheme == "https")) {|http| http.request(req)}
|
24
|
+
JSON.parse(res.body)['logplex_url']
|
25
|
+
end
|
26
|
+
|
27
|
+
def authorization
|
28
|
+
"Basic " + Base64.encode64(":#{api_token}").gsub("\n", '')
|
29
|
+
end
|
30
|
+
|
31
|
+
def restart(process)
|
32
|
+
return false if recently_restarted?(process)
|
33
|
+
|
34
|
+
uri = URI(dyno_url(process))
|
35
|
+
req = Net::HTTP::Delete.new(uri.path)
|
36
|
+
req['Authorization'] = authorization
|
37
|
+
req['Content-type'] = content_type
|
38
|
+
req['Accept'] = accept
|
39
|
+
res = Net::HTTP.start(uri.host, uri.port, :use_ssl => (uri.scheme == "https")) {|http| http.request(req)}
|
40
|
+
|
41
|
+
restarts[process] = Time.now
|
42
|
+
|
43
|
+
true
|
44
|
+
end
|
45
|
+
|
46
|
+
def recently_restarted?(process)
|
47
|
+
restarts[process] > (Time.now - RESTART_RATE_LIMIT)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
def content_type
|
52
|
+
"application/json"
|
53
|
+
end
|
54
|
+
|
55
|
+
def accept
|
56
|
+
"application/vnd.heroku+json; version=3"
|
57
|
+
end
|
58
|
+
|
59
|
+
def log_sessions_url
|
60
|
+
"https://api.heroku.com/apps/#{app_name}/log-sessions"
|
61
|
+
end
|
62
|
+
|
63
|
+
def dyno_url(process)
|
64
|
+
"https://api.heroku.com/apps/#{app_name}/dynos/#{process}"
|
65
|
+
end
|
66
|
+
|
67
|
+
def restarts
|
68
|
+
@restarts ||= Hash.new { Time.now - RESTART_RATE_LIMIT*2 }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module Whacamole
|
4
|
+
|
5
|
+
class Stream
|
6
|
+
|
7
|
+
RESTART_THRESHOLD = 1000
|
8
|
+
|
9
|
+
def initialize(url, restart_handler, &blk)
|
10
|
+
@url = url
|
11
|
+
@restart_handler = restart_handler
|
12
|
+
@event_handler = blk
|
13
|
+
end
|
14
|
+
|
15
|
+
def watch
|
16
|
+
uri = URI(url)
|
17
|
+
Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') do |http|
|
18
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
19
|
+
http.request(request) do |response|
|
20
|
+
response.read_body do |chunk|
|
21
|
+
dispatch_handlers(chunk)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def dispatch_handlers(chunk)
|
28
|
+
memory_size_from_chunk(chunk).each do |dyno, size|
|
29
|
+
event = Events::DynoSize.new({:process => dyno, :size => size, :units => "MB"})
|
30
|
+
event_handler.call(event)
|
31
|
+
|
32
|
+
restart(event.process) if restart_necessary?(event)
|
33
|
+
end
|
34
|
+
|
35
|
+
# TODO: handle R14 errors here also
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def restart(process)
|
40
|
+
restarted = restart_handler.restart(process)
|
41
|
+
|
42
|
+
if restarted
|
43
|
+
event_handler.call( Events::DynoRestart.new({:process => process}) )
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def restart_necessary?(event)
|
48
|
+
event.size > restart_threshold
|
49
|
+
end
|
50
|
+
|
51
|
+
def memory_size_from_chunk(chunk)
|
52
|
+
sizes = []
|
53
|
+
chunk.split("\n").select{|line| line.include? "sample#memory_total"}.each do |line|
|
54
|
+
dyno = line.match(/web\.\d+/)
|
55
|
+
next unless dyno
|
56
|
+
size = line.match(/sample#memory_total=([\d\.]+)/)
|
57
|
+
sizes << [dyno[0], size[1]]
|
58
|
+
end
|
59
|
+
sizes
|
60
|
+
end
|
61
|
+
|
62
|
+
def url
|
63
|
+
@url
|
64
|
+
end
|
65
|
+
|
66
|
+
def event_handler
|
67
|
+
@event_handler
|
68
|
+
end
|
69
|
+
|
70
|
+
def restart_handler
|
71
|
+
@restart_handler
|
72
|
+
end
|
73
|
+
|
74
|
+
def restart_threshold
|
75
|
+
RESTART_THRESHOLD
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
|
data/lib/whacamole/version.rb
CHANGED
data/spec/config_spec.rb
ADDED
data/spec/events_spec.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "DynoSize" do
|
4
|
+
let (:e) { Whacamole::Events::DynoSize.new }
|
5
|
+
|
6
|
+
describe "setting size" do
|
7
|
+
it "converts strings to floats" do
|
8
|
+
e.size = "766.65"
|
9
|
+
e.size.should == 766.65
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "setting units" do
|
14
|
+
it "stores the units on the object" do
|
15
|
+
e.units = "MB"
|
16
|
+
e.units.should == "MB"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "setting process" do
|
21
|
+
it "stores the process on the object" do
|
22
|
+
e.process = "web.2"
|
23
|
+
e.process.should == "web.2"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "initialization" do
|
28
|
+
it "sets the variables from the input hash" do
|
29
|
+
e = Whacamole::Events::DynoSize.new({:size => "766.65", :units => "MB", :process => "web.2"})
|
30
|
+
e.size.should == 766.65
|
31
|
+
e.units.should == "MB"
|
32
|
+
e.process.should == "web.2"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# TODO: use VCR instead of these method expectations
|
4
|
+
describe Whacamole::HerokuWrapper do
|
5
|
+
let(:h) { Whacamole::HerokuWrapper.new("staging", "foobar") }
|
6
|
+
|
7
|
+
describe "authorization" do
|
8
|
+
it "base64-encodes the api token and a preceding colon" do
|
9
|
+
h.authorization.should == "Basic OmZvb2Jhcg=="
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "create_log_session" do
|
14
|
+
it "executes a request using the default headers" do
|
15
|
+
req = {}
|
16
|
+
Net::HTTP::Post.should_receive(:new).with("/apps/staging/log-sessions") { req }
|
17
|
+
req.should_receive(:[]=).with("Authorization", h.authorization)
|
18
|
+
req.should_receive(:[]=).with("Content-type", "application/json")
|
19
|
+
req.should_receive(:[]=).with("Accept", "application/vnd.heroku+json; version=3")
|
20
|
+
req.should_receive(:set_form_data).with({'tail' => true})
|
21
|
+
Net::HTTP.should_receive(:start).with("api.heroku.com", 443, use_ssl: true).and_return(OpenStruct.new(:body => "{\"logplex_url\": \"https://api.heroku.com/log/session/url\"}"))
|
22
|
+
h.create_log_session.should == "https://api.heroku.com/log/session/url"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "restart" do
|
27
|
+
it "executes a request using the default headers" do
|
28
|
+
req = {}
|
29
|
+
Net::HTTP::Delete.should_receive(:new).with("/apps/staging/dynos/web.3") { req }
|
30
|
+
req.should_receive(:[]=).with("Authorization", h.authorization)
|
31
|
+
req.should_receive(:[]=).with("Content-type", "application/json")
|
32
|
+
req.should_receive(:[]=).with("Accept", "application/vnd.heroku+json; version=3")
|
33
|
+
Net::HTTP.should_receive(:start).with("api.heroku.com", 443, use_ssl: true)
|
34
|
+
h.restart("web.3").should be_true
|
35
|
+
end
|
36
|
+
|
37
|
+
it "respects the rate limit" do
|
38
|
+
Net::HTTP::Delete.should_not_receive(:new)
|
39
|
+
|
40
|
+
h.stub(:recently_restarted? => true)
|
41
|
+
h.restart("web.2").should be_false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/spec/stream_spec.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class EventHandler
|
4
|
+
attr_accessor :events
|
5
|
+
|
6
|
+
def process(event)
|
7
|
+
events << event
|
8
|
+
end
|
9
|
+
|
10
|
+
def events
|
11
|
+
@events ||= []
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class RestartHandler
|
16
|
+
def restart(process)
|
17
|
+
true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe Whacamole::Stream do
|
22
|
+
let(:eh) { EventHandler.new }
|
23
|
+
let(:restart_handler) { RestartHandler.new }
|
24
|
+
let(:stream) do
|
25
|
+
Whacamole::Stream.new("https://api.heroku.com/path/to/stream/stream", restart_handler) do |event|
|
26
|
+
eh.process(event)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "stream" do
|
31
|
+
it "opens the url for streaming" do
|
32
|
+
stream.watch
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "handle_chunk" do
|
37
|
+
context "when memory usage is present" do
|
38
|
+
it "surfaces the memory usage" do
|
39
|
+
stream.dispatch_handlers <<-CHUNK
|
40
|
+
2013-08-22T16:39:22.208103+00:00 heroku[router]: at=info method=GET path=/favicon.ico host=aisle50.com fwd="205.159.94.63" dyno=web.3 connect=1ms service=20ms status=200 bytes=894
|
41
|
+
2013-08-22T16:39:22.224847+00:00 heroku[router]: at=info method=GET path=/ host=www.aisle50.com fwd="119.63.193.132" dyno=web.3 connect=1ms service=5ms status=301 bytes=0
|
42
|
+
2013-08-22T16:39:22.919300+00:00 heroku[web.2]: source=web.2 dyno=heroku.772639.a334caa8-736c-48b3-bac2-d366f75d7fa0 sample#load_avg_1m=0.20 sample#load_avg_5m=0.33 sample#load_avg_15m=0.38
|
43
|
+
2013-08-22T16:39:22.919536+00:00 heroku[web.2]: source=web.2 dyno=heroku.772639.a334caa8-736c-48b3-bac2-d366f75d7fa0 sample#memory_total=581.95MB sample#memory_rss=581.75MB sample#memory_cache=0.16MB sample#memory_swap=0.03MB sample#memory_pgpgin=0pages sample#memory_pgpgout=179329pages
|
44
|
+
2013-08-22T16:39:22.919773+00:00 heroku[web.2]: source=web.2 dyno=heroku.772639.a334caa8-736c-48b3-bac2-d366f75d7fa0 sample#diskmbytes=0MB
|
45
|
+
2013-08-22T16:39:23.045250+00:00 heroku[web.1]: source=web.1 dyno=heroku.772639.4c9dcf54-f339-4d81-9756-8dad47f178a4 sample#load_avg_1m=0.24 sample#load_avg_5m=0.59
|
46
|
+
2013-08-22T16:39:23.045521+00:00 heroku[web.90]: source=web.1 dyno=heroku.772639.4c9dcf54-f339-4d81-9756-8dad47f178a4 sample#memory_total=66MB sample#memory_rss=471.21MB sample#memory_cache=0.05MB sample#memory_swap=0.02MB sample#memory_pgpgin=0pages sample#memory_pgpgout=145277pages
|
47
|
+
2013-08-22T16:39:23.045789+00:00 heroku[web.1]: source=web.1 dyno=heroku.772639.4c9dcf54-f339-4d81-9756-8dad47f178a4 sample#diskmbytes=0MB
|
48
|
+
2013-08-22T16:39:23.364649+00:00 heroku[worker.1]: source=worker.1 dyno=heroku.772639.ae391b5d-e776-43f9-b056-360912563d61 sample#load_avg_1m=0.00 sample#load_avg_5m=0.01 sample#load_avg_15m=0.02
|
49
|
+
CHUNK
|
50
|
+
|
51
|
+
eh.events.length.should == 2
|
52
|
+
|
53
|
+
eh.events.first.should be_a Whacamole::Events::DynoSize
|
54
|
+
eh.events.first.size.should == 581.95
|
55
|
+
eh.events.first.units.should == "MB"
|
56
|
+
eh.events.first.process.should == "web.2"
|
57
|
+
|
58
|
+
eh.events.last.should be_a Whacamole::Events::DynoSize
|
59
|
+
eh.events.last.size.should == 66.0
|
60
|
+
eh.events.last.units.should == "MB"
|
61
|
+
eh.events.last.process.should == "web.90"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "when memory usages is over the threshold" do
|
66
|
+
it "kicks off a restart" do
|
67
|
+
restart_handler.should_receive(:restart).with("web.2")
|
68
|
+
stream.dispatch_handlers <<-CHUNK
|
69
|
+
2013-08-22T16:39:22.919536+00:00 heroku[web.2]: source=web.2 dyno=heroku.772639.a334caa8-736c-48b3-bac2-d366f75d7fa0 sample#memory_total=1001MB sample#memory_rss=581.75MB sample#memory_cache=0.16MB sample#memory_swap=0.03MB sample#memory_pgpgin=0pages sample#memory_pgpgout=179329pages
|
70
|
+
CHUNK
|
71
|
+
end
|
72
|
+
|
73
|
+
it "surfaces the restart" do
|
74
|
+
stream.dispatch_handlers <<-CHUNK
|
75
|
+
2013-08-22T16:39:22.919536+00:00 heroku[web.2]: source=web.2 dyno=heroku.772639.a334caa8-736c-48b3-bac2-d366f75d7fa0 sample#memory_total=1001MB sample#memory_rss=581.75MB sample#memory_cache=0.16MB sample#memory_swap=0.03MB sample#memory_pgpgin=0pages sample#memory_pgpgout=179329pages
|
76
|
+
CHUNK
|
77
|
+
|
78
|
+
restart = eh.events.last
|
79
|
+
restart.should be_a Whacamole::Events::DynoRestart
|
80
|
+
restart.process.should == "web.2"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Whacamole do
|
4
|
+
describe "configure" do
|
5
|
+
it "returns unique config objects by app name" do
|
6
|
+
prod_config = nil
|
7
|
+
Whacamole.configure("production") do |config|
|
8
|
+
prod_config = config
|
9
|
+
end
|
10
|
+
|
11
|
+
staging_config = nil
|
12
|
+
Whacamole.configure("staging") do |config|
|
13
|
+
staging_config = config
|
14
|
+
end
|
15
|
+
|
16
|
+
staging_config.app_name.should == "staging"
|
17
|
+
prod_config.app_name.should == "production"
|
18
|
+
|
19
|
+
prod_config.should_not == staging_config
|
20
|
+
end
|
21
|
+
|
22
|
+
it "returns the same config object if asked a second time" do
|
23
|
+
Whacamole.configure("production") do |config|
|
24
|
+
config.api_token = "prod token"
|
25
|
+
end
|
26
|
+
|
27
|
+
Whacamole.configure("production") do |config|
|
28
|
+
config.api_token.should == "prod token"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
data/whacamole.gemspec
CHANGED
@@ -18,7 +18,8 @@ Gem::Specification.new do |gem|
|
|
18
18
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
19
|
gem.require_paths = ["lib"]
|
20
20
|
|
21
|
-
gem.
|
22
|
-
|
23
|
-
gem.add_development_dependency '
|
21
|
+
gem.executables = ['whacamole']
|
22
|
+
|
23
|
+
gem.add_development_dependency 'rspec', '~> 2.11'
|
24
|
+
gem.add_development_dependency 'rake', '~> 10.1'
|
24
25
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: whacamole
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,24 +9,8 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-08-
|
12
|
+
date: 2013-08-29 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
|
-
- !ruby/object:Gem::Dependency
|
15
|
-
name: cucumber
|
16
|
-
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
|
-
requirements:
|
19
|
-
- - ~>
|
20
|
-
- !ruby/object:Gem::Version
|
21
|
-
version: 1.2.1
|
22
|
-
type: :development
|
23
|
-
prerelease: false
|
24
|
-
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
|
-
requirements:
|
27
|
-
- - ~>
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: 1.2.1
|
30
14
|
- !ruby/object:Gem::Dependency
|
31
15
|
name: rspec
|
32
16
|
requirement: !ruby/object:Gem::Requirement
|
@@ -34,7 +18,7 @@ dependencies:
|
|
34
18
|
requirements:
|
35
19
|
- - ~>
|
36
20
|
- !ruby/object:Gem::Version
|
37
|
-
version: 2.11
|
21
|
+
version: '2.11'
|
38
22
|
type: :development
|
39
23
|
prerelease: false
|
40
24
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -42,7 +26,7 @@ dependencies:
|
|
42
26
|
requirements:
|
43
27
|
- - ~>
|
44
28
|
- !ruby/object:Gem::Version
|
45
|
-
version: 2.11
|
29
|
+
version: '2.11'
|
46
30
|
- !ruby/object:Gem::Dependency
|
47
31
|
name: rake
|
48
32
|
requirement: !ruby/object:Gem::Requirement
|
@@ -50,7 +34,7 @@ dependencies:
|
|
50
34
|
requirements:
|
51
35
|
- - ~>
|
52
36
|
- !ruby/object:Gem::Version
|
53
|
-
version:
|
37
|
+
version: '10.1'
|
54
38
|
type: :development
|
55
39
|
prerelease: false
|
56
40
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -58,14 +42,33 @@ dependencies:
|
|
58
42
|
requirements:
|
59
43
|
- - ~>
|
60
44
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
45
|
+
version: '10.1'
|
62
46
|
description: Whacamole
|
63
47
|
email: archslide@gmail.com
|
64
|
-
executables:
|
48
|
+
executables:
|
49
|
+
- whacamole
|
65
50
|
extensions: []
|
66
51
|
extra_rdoc_files: []
|
67
52
|
files:
|
53
|
+
- .gitignore
|
54
|
+
- .travis.yml
|
55
|
+
- Gemfile
|
56
|
+
- Gemfile.lock
|
57
|
+
- README.md
|
58
|
+
- Rakefile
|
59
|
+
- bin/whacamole
|
60
|
+
- lib/whacamole.rb
|
61
|
+
- lib/whacamole/config.rb
|
62
|
+
- lib/whacamole/events.rb
|
63
|
+
- lib/whacamole/heroku_wrapper.rb
|
64
|
+
- lib/whacamole/stream.rb
|
68
65
|
- lib/whacamole/version.rb
|
66
|
+
- spec/config_spec.rb
|
67
|
+
- spec/events_spec.rb
|
68
|
+
- spec/heroku_wrapper_spec.rb
|
69
|
+
- spec/spec_helper.rb
|
70
|
+
- spec/stream_spec.rb
|
71
|
+
- spec/whacamole_spec.rb
|
69
72
|
- whacamole.gemspec
|
70
73
|
homepage: http://github.com/arches/whacamole
|
71
74
|
licenses:
|
@@ -92,4 +95,10 @@ rubygems_version: 1.8.19
|
|
92
95
|
signing_key:
|
93
96
|
specification_version: 3
|
94
97
|
summary: ''
|
95
|
-
test_files:
|
98
|
+
test_files:
|
99
|
+
- spec/config_spec.rb
|
100
|
+
- spec/events_spec.rb
|
101
|
+
- spec/heroku_wrapper_spec.rb
|
102
|
+
- spec/spec_helper.rb
|
103
|
+
- spec/stream_spec.rb
|
104
|
+
- spec/whacamole_spec.rb
|