heroku-scalr 0.2.4 → 0.3.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/Gemfile.lock +8 -6
- data/heroku-scalr.gemspec +2 -2
- data/lib/heroku/scalr/app.rb +31 -12
- data/spec/heroku/scalr/app_spec.rb +17 -2
- metadata +4 -4
data/Gemfile.lock
CHANGED
@@ -1,19 +1,20 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
heroku-scalr (0.
|
5
|
-
heroku-api (~> 0.3.
|
4
|
+
heroku-scalr (0.3.0)
|
5
|
+
heroku-api (~> 0.3.12)
|
6
6
|
timers
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
11
|
addressable (2.3.4)
|
12
|
-
crack (0.
|
12
|
+
crack (0.4.0)
|
13
|
+
safe_yaml (~> 0.9.0)
|
13
14
|
diff-lcs (1.2.4)
|
14
|
-
excon (0.
|
15
|
-
heroku-api (0.3.
|
16
|
-
excon (~> 0.
|
15
|
+
excon (0.23.0)
|
16
|
+
heroku-api (0.3.12)
|
17
|
+
excon (~> 0.23.0)
|
17
18
|
rake (10.0.4)
|
18
19
|
rspec (2.13.0)
|
19
20
|
rspec-core (~> 2.13.0)
|
@@ -23,6 +24,7 @@ GEM
|
|
23
24
|
rspec-expectations (2.13.0)
|
24
25
|
diff-lcs (>= 1.1.3, < 2.0)
|
25
26
|
rspec-mocks (2.13.1)
|
27
|
+
safe_yaml (0.9.3)
|
26
28
|
timers (1.1.0)
|
27
29
|
webmock (1.11.0)
|
28
30
|
addressable (>= 2.2.7)
|
data/heroku-scalr.gemspec
CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.name = File.basename(__FILE__, '.gemspec')
|
8
8
|
s.summary = "Watch and scale your dynos!"
|
9
9
|
s.description = "Issues recurring 'pings' to your Heroku apps and scales dynos up or down depending on pre-defined rules"
|
10
|
-
s.version = "0.
|
10
|
+
s.version = "0.3.0"
|
11
11
|
|
12
12
|
s.authors = ["Black Square Media"]
|
13
13
|
s.email = "info@blacksquaremedia.com"
|
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
20
|
|
21
21
|
s.add_dependency "timers"
|
22
|
-
s.add_dependency "heroku-api", '~> 0.3.
|
22
|
+
s.add_dependency "heroku-api", '~> 0.3.12'
|
23
23
|
|
24
24
|
s.add_development_dependency "rake"
|
25
25
|
s.add_development_dependency "bundler"
|
data/lib/heroku/scalr/app.rb
CHANGED
@@ -9,12 +9,13 @@ class Heroku::Scalr::App
|
|
9
9
|
ping_low: 200,
|
10
10
|
ping_high: 500,
|
11
11
|
metric: :ping,
|
12
|
-
|
12
|
+
cool_freq: 180,
|
13
|
+
heat_freq: 60,
|
13
14
|
}.freeze
|
14
15
|
|
15
16
|
attr_reader :name, :url, :api_key, :interval, :min_dynos, :max_dynos,
|
16
17
|
:metric, :wait_low, :wait_high, :ping_low, :ping_high,
|
17
|
-
:
|
18
|
+
:cool_freq, :heat_freq, :last_scaled_at
|
18
19
|
|
19
20
|
# @param [String] name Heroku app name
|
20
21
|
# @param [Hash] opts options
|
@@ -28,8 +29,14 @@ class Heroku::Scalr::App
|
|
28
29
|
# lowers the number of dynos if queue wait time is less than `wait_low` ms, default: 10
|
29
30
|
# @option opts [Integer] :wait_high
|
30
31
|
# lowers the number of dynos if queue wait time is more than `wait_high` ms, default: 100
|
31
|
-
# @option opts [Integer] :
|
32
|
-
#
|
32
|
+
# @option opts [Integer] :ping_low
|
33
|
+
# lowers the number of dynos if ping time is less than `ping_low` ms, default: 200
|
34
|
+
# @option opts [Integer] :ping_high
|
35
|
+
# lowers the number of dynos if ping time is more than `ping_high` ms, default: 500
|
36
|
+
# @option opts [Integer] :cool_freq
|
37
|
+
# leave at least `cool_freq` seconds before scaling down again, default: 180
|
38
|
+
# @option opts [Integer] :heat_freq
|
39
|
+
# leave at least `heat_freq` seconds before scaling up again, default: 60
|
33
40
|
# @option opts [String] :api_key
|
34
41
|
# the Heroku account's API key
|
35
42
|
def initialize(name, opts = {})
|
@@ -51,20 +58,22 @@ class Heroku::Scalr::App
|
|
51
58
|
@ping_low = opts[:ping_low].to_i
|
52
59
|
@ping_high = opts[:ping_high].to_i
|
53
60
|
@metric = Heroku::Scalr::Metric.new(opts[:metric], self)
|
54
|
-
@
|
61
|
+
@cool_freq = opts[:cool_freq].to_i
|
62
|
+
@heat_freq = opts[:heat_freq].to_i
|
55
63
|
@last_scaled_at = Time.at(0)
|
56
64
|
end
|
57
65
|
|
58
66
|
# Scales the app
|
59
67
|
def scale!
|
60
|
-
scale_at =
|
68
|
+
scale_at = last_scaled_at + [cool_freq, heat_freq].min
|
61
69
|
now = Time.now
|
62
70
|
if now < scale_at
|
63
71
|
log :debug, "skip check, next attempt in #{(scale_at - now).to_i}s"
|
64
72
|
return
|
65
73
|
end
|
66
74
|
|
67
|
-
|
75
|
+
by = metric.by
|
76
|
+
do_scale(by) if must_scale?(by, now)
|
68
77
|
rescue => e
|
69
78
|
msg = "#{e.class}: #{e.to_s}"
|
70
79
|
msg << "\n\t" << e.backtrace.join("\n\t") if e.backtrace
|
@@ -80,15 +89,25 @@ class Heroku::Scalr::App
|
|
80
89
|
|
81
90
|
protected
|
82
91
|
|
92
|
+
def must_scale?(by, now)
|
93
|
+
scale_dn = last_scaled_at + cool_freq
|
94
|
+
scale_up = last_scaled_at + heat_freq
|
95
|
+
|
96
|
+
if by < 0 && now < scale_dn
|
97
|
+
log :debug, "skip scaling, next down attempt in #{(scale_dn - now).to_i}s"
|
98
|
+
return false
|
99
|
+
elsif by > 0 && now < scale_up
|
100
|
+
log :debug, "skip scaling, next up attempt in #{(scale_up - now).to_i}s"
|
101
|
+
return false
|
102
|
+
elsif by == 0
|
103
|
+
log :debug, "no scaling required"
|
104
|
+
return false
|
105
|
+
end
|
83
106
|
|
84
|
-
|
85
|
-
def next_scale_attempt
|
86
|
-
last_scaled_at + min_frequency
|
107
|
+
true
|
87
108
|
end
|
88
109
|
|
89
110
|
def do_scale(by)
|
90
|
-
return if by.zero?
|
91
|
-
|
92
111
|
api = Heroku::API.new(api_key: api_key)
|
93
112
|
info = api.get_app(name)
|
94
113
|
unless info.status == 200
|
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Heroku::Scalr::App do
|
4
4
|
|
5
|
-
subject { described_class.new('name', api_key: 'key', max_dynos: 3) }
|
5
|
+
subject { described_class.new('name', api_key: 'key', max_dynos: 3, heat_freq: 30) }
|
6
6
|
|
7
7
|
def mock_response(status, body)
|
8
8
|
mock "APIResponse", status: status, headers: {}, body: body
|
@@ -20,7 +20,8 @@ describe Heroku::Scalr::App do
|
|
20
20
|
its(:wait_high) { should be(100) }
|
21
21
|
its(:ping_low) { should be(200) }
|
22
22
|
its(:ping_high) { should be(500) }
|
23
|
-
its(:
|
23
|
+
its(:heat_freq) { should be(30) }
|
24
|
+
its(:cool_freq) { should be(180) }
|
24
25
|
its(:last_scaled_at) { should == Time.at(0)}
|
25
26
|
|
26
27
|
|
@@ -82,6 +83,7 @@ describe Heroku::Scalr::App do
|
|
82
83
|
context "down" do
|
83
84
|
|
84
85
|
it "should return the new number of dynos" do
|
86
|
+
subject.instance_variable_set :@last_scaled_at, (Time.now - 185)
|
85
87
|
mock_api.should_receive(:post_ps_scale).with("name", "web", 1).and_return mock_response(200, "")
|
86
88
|
subject.scale!.should == 1
|
87
89
|
end
|
@@ -92,6 +94,12 @@ describe Heroku::Scalr::App do
|
|
92
94
|
subject.scale!.should be_nil
|
93
95
|
end
|
94
96
|
|
97
|
+
it "should skip if scaled too recently" do
|
98
|
+
subject.instance_variable_set :@last_scaled_at, (Time.now - 175)
|
99
|
+
mock_api.should_not_receive(:post_ps_scale)
|
100
|
+
subject.scale!.should be_nil
|
101
|
+
end
|
102
|
+
|
95
103
|
end
|
96
104
|
|
97
105
|
context "up" do
|
@@ -99,6 +107,7 @@ describe Heroku::Scalr::App do
|
|
99
107
|
before { subject.metric.stub by: 1 }
|
100
108
|
|
101
109
|
it "should return the new number of dynos" do
|
110
|
+
subject.instance_variable_set :@last_scaled_at, (Time.now - 35)
|
102
111
|
mock_api.should_receive(:post_ps_scale).with("name", "web", 3).and_return mock_response(200, "")
|
103
112
|
subject.scale!.should == 3
|
104
113
|
end
|
@@ -109,6 +118,12 @@ describe Heroku::Scalr::App do
|
|
109
118
|
subject.scale!.should be_nil
|
110
119
|
end
|
111
120
|
|
121
|
+
it "should skip if scaled too recently" do
|
122
|
+
subject.instance_variable_set :@last_scaled_at, (Time.now - 25)
|
123
|
+
mock_api.should_not_receive(:post_ps_scale)
|
124
|
+
subject.scale!.should be_nil
|
125
|
+
end
|
126
|
+
|
112
127
|
end
|
113
128
|
|
114
129
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: heroku-scalr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-06-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: timers
|
@@ -34,7 +34,7 @@ dependencies:
|
|
34
34
|
requirements:
|
35
35
|
- - ~>
|
36
36
|
- !ruby/object:Gem::Version
|
37
|
-
version: 0.3.
|
37
|
+
version: 0.3.12
|
38
38
|
type: :runtime
|
39
39
|
prerelease: false
|
40
40
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -42,7 +42,7 @@ dependencies:
|
|
42
42
|
requirements:
|
43
43
|
- - ~>
|
44
44
|
- !ruby/object:Gem::Version
|
45
|
-
version: 0.3.
|
45
|
+
version: 0.3.12
|
46
46
|
- !ruby/object:Gem::Dependency
|
47
47
|
name: rake
|
48
48
|
requirement: !ruby/object:Gem::Requirement
|