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 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