heroku-scalr 0.2.4 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,19 +1,20 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- heroku-scalr (0.2.4)
5
- heroku-api (~> 0.3.8)
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.3.2)
12
+ crack (0.4.0)
13
+ safe_yaml (~> 0.9.0)
13
14
  diff-lcs (1.2.4)
14
- excon (0.21.0)
15
- heroku-api (0.3.10)
16
- excon (~> 0.21.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.2.4"
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.8'
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"
@@ -9,12 +9,13 @@ class Heroku::Scalr::App
9
9
  ping_low: 200,
10
10
  ping_high: 500,
11
11
  metric: :ping,
12
- min_frequency: 60
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
- :min_frequency, :last_scaled_at
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] :min_frequency
32
- # leave at least `min_frequency` seconds before scaling again, default: 60
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
- @min_frequency = opts[:min_frequency].to_i
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 = next_scale_attempt
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
- do_scale(metric.by)
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
- # @return [Time] the next scale attempt
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(:min_frequency) { should be(60) }
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.2.4
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-05-28 00:00:00.000000000 Z
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.8
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.8
45
+ version: 0.3.12
46
46
  - !ruby/object:Gem::Dependency
47
47
  name: rake
48
48
  requirement: !ruby/object:Gem::Requirement