our-eel-hacks 0.0.10 → 0.0.11

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.
@@ -68,6 +68,8 @@ module OurEelHacks
68
68
  @dynos = nil
69
69
  @soft_side = nil
70
70
 
71
+ @memoed_dyno_info = nil
72
+
71
73
  @last_scaled = Time.at(0)
72
74
  @entered_soft = Time.at(0)
73
75
  @last_reading = nil
@@ -80,8 +82,14 @@ module OurEelHacks
80
82
  @max_dynos = 10
81
83
  @lower_limits = LowerLimit.new(5, 1)
82
84
  @upper_limits = UpperLimit.new(30, 50)
83
- @soft_duration = 500
84
- @scaling_frequency = 200
85
+
86
+ @soft_duration = 10000
87
+ @scaling_frequency = 5000
88
+ @heroku_rate_limit = 80_000
89
+ @heroku_rate_limit_margin = 0.1
90
+
91
+ @millis_til_next_scale = nil
92
+
85
93
  @logger = NullLogger.new
86
94
  end
87
95
 
@@ -90,15 +98,20 @@ module OurEelHacks
90
98
  check_settings
91
99
  logger.info{ "Autoscaler configured for #{flavor || "{{unknown flavor}}"}"}
92
100
 
93
- update_dynos(Time.now)
94
- logger.debug{ self.inspect }
101
+ update_dynos(dyno_info.count, Time.now)
95
102
  end
96
103
 
104
+ MILLIS_PER_DAY = 24 * 60 * 60 * 1000
97
105
  def check_settings
98
106
  errors = []
99
107
  errors << "No heroku api key set" if @heroku_api_key.nil?
100
108
  errors << "No app name set" if @app_name.nil?
101
109
  errors << "No process type set" if @ps_type.nil?
110
+ if (MILLIS_PER_DAY / @heroku_rate_limit) *
111
+ (1.0 - @heroku_rate_limit_margin) *
112
+ API_CALLS_PER_SCALE > @scaling_frequency
113
+ errors << "Scaling frequency will lock up Heroku"
114
+ end
102
115
  unless errors.empty?
103
116
  logger.warn{ "Problems configuring Autoscaler: #{errors.inspect}" }
104
117
  raise "OurEelHacks::Autoscaler, configuration problem: " + errors.join(", ")
@@ -107,7 +120,7 @@ module OurEelHacks
107
120
 
108
121
  attr_accessor :min_dynos, :max_dynos, :lower_limits, :upper_limits, :ps_type,
109
122
  :soft_duration, :scaling_frequency, :logger, :heroku_api_key, :app_name
110
- attr_reader :last_scaled, :dynos, :entered_soft, :last_reading, :soft_side
123
+ attr_reader :last_scaled, :dynos, :entered_soft, :last_reading, :soft_side, :millis_til_next_scale
111
124
 
112
125
  def elapsed(start, finish)
113
126
  seconds = finish.to_i - start.to_i
@@ -117,22 +130,29 @@ module OurEelHacks
117
130
  return diff
118
131
  end
119
132
 
133
+ API_CALLS_PER_SCALE = 2
120
134
  def scale(metric)
121
135
  logger.debug{ "Scaling request for #{@ps_type}: metric is: #{metric}" }
122
136
  moment = Time.now
123
- if elapsed(last_scaled, moment) < scaling_frequency
124
- logger.debug{ "Not scaling: elapsed #{elapsed(last_scaled, moment)} less than configured #{scaling_frequency}" }
137
+ if elapsed(last_scaled, moment) < millis_til_next_scale
138
+ logger.debug{ "Not scaling: elapsed #{elapsed(last_scaled, moment)} less than computed #{millis_til_next_scale}" }
125
139
  return
126
140
  end
127
141
 
142
+ clear_dyno_info
143
+
144
+ starting_wait = millis_til_next_scale
145
+
146
+ update_dynos(dyno_info.count, moment)
147
+
128
148
  target_dynos = target_scale(metric, moment)
129
149
 
130
150
  target_dynos = [[target_dynos, max_dynos].min, min_dynos].max
131
151
  logger.debug{ "Target dynos at: #{min_dynos}/#{target_dynos}/#{max_dynos} (vs. current: #{@dynos})" }
132
152
 
133
- set_dynos(target_dynos)
153
+ set_dynos(target_dynos, moment)
134
154
 
135
- update_dynos(moment)
155
+ break_cadence(starting_wait)
136
156
  rescue => ex
137
157
  logger.warn{ "Problem scaling: #{ex.inspect}" }
138
158
  end
@@ -173,16 +193,15 @@ module OurEelHacks
173
193
  return 0
174
194
  end
175
195
 
176
- def dyno_info
177
- regexp = /^#{ps_type}[.].*/
178
- return heroku.ps(app_name).find_all do |dyno|
179
- dyno["process"] =~ regexp
196
+ def break_cadence(starting_wait)
197
+ if starting_wait > millis_til_next_scale
198
+ @millis_til_next_scale = rand((starting_wait..@millis_til_next_scale))
180
199
  end
181
200
  end
182
201
 
183
- def update_dynos(moment)
184
- new_value = dyno_info.count
202
+ def update_dynos(new_value, moment)
185
203
  if new_value != dynos
204
+ @millis_til_next_scale = scaling_frequency * new_value
186
205
  @last_scaled = moment
187
206
  @entered_soft = moment
188
207
  end
@@ -190,6 +209,20 @@ module OurEelHacks
190
209
  @last_reading = moment
191
210
  end
192
211
 
212
+ def clear_dyno_info
213
+ @memoed_dyno_info = nil
214
+ end
215
+
216
+ def dyno_info
217
+ return @memoed_dyno_info ||=
218
+ begin
219
+ regexp = /^#{ps_type}[.].*/
220
+ heroku.ps(app_name).find_all do |dyno|
221
+ dyno["process"] =~ regexp
222
+ end
223
+ end
224
+ end
225
+
193
226
  def dynos_stable?
194
227
  return dyno_info.all? do |dyno|
195
228
  dyno["state"] == "up"
@@ -204,7 +237,7 @@ module OurEelHacks
204
237
  end
205
238
  end
206
239
 
207
- def set_dynos(count)
240
+ def set_dynos(count,moment)
208
241
  if count == dynos
209
242
  logger.debug{ "Not scaling: #{count} ?= #{dynos}" }
210
243
  return
@@ -216,7 +249,7 @@ module OurEelHacks
216
249
  end
217
250
  logger.info{ "Scaling from #{dynos} to #{count} dynos for #{ps_type}" }
218
251
  heroku.ps_scale(app_name, :type => ps_type, :qty => count)
219
- @last_scaled = Time.now
252
+ update_dynos(count, moment)
220
253
  end
221
254
  end
222
255
  end
data/spec/autoscaler.rb CHANGED
@@ -1,4 +1,3 @@
1
- require 'spec_helper'
2
1
  require 'our-eel-hacks/rack'
3
2
  require 'logger'
4
3
 
@@ -18,11 +17,11 @@ describe OurEelHacks::Autoscaler do
18
17
  end
19
18
 
20
19
  let :scaling_freq do
21
- 200
20
+ 2500
22
21
  end
23
22
 
24
23
  let :soft_dur do
25
- 500
24
+ 5000
26
25
  end
27
26
 
28
27
  let :ideal_value do
@@ -45,6 +44,14 @@ describe OurEelHacks::Autoscaler do
45
44
  -10
46
45
  end
47
46
 
47
+ let :dyno_count do
48
+ 3
49
+ end
50
+
51
+ let :expected_scale_frequency do
52
+ scaling_freq * dyno_count
53
+ end
54
+
48
55
  let! :starting_time do
49
56
  Time.now
50
57
  end
@@ -83,7 +90,7 @@ describe OurEelHacks::Autoscaler do
83
90
  end
84
91
 
85
92
  it "should get a count of dynos at start" do
86
- autoscaler.dynos.should == 3 #happens to be the number of web dynos right now
93
+ autoscaler.dynos.should == dyno_count #comes from the VCR cassette
87
94
  end
88
95
 
89
96
  before :each do
@@ -95,14 +102,14 @@ describe OurEelHacks::Autoscaler do
95
102
  describe "scaling frequency" do
96
103
 
97
104
  it "should not scale too soon" do
98
- time_adjust(scaling_freq - 5)
105
+ time_adjust(expected_scale_frequency - 5)
99
106
 
100
107
  heroku.should_not_receive(:ps_scale)
101
108
  autoscaler.scale(hard_high)
102
109
  end
103
110
 
104
111
  it "should scale up if time has elapsed and hard limit exceeded" do
105
- time_adjust(scaling_freq + 5)
112
+ time_adjust(expected_scale_frequency + 5)
106
113
 
107
114
  heroku.should_receive(:ps_scale).with(app_name, hash_including(:qty => 4))
108
115
  autoscaler.scale(hard_high)
@@ -111,7 +118,7 @@ describe OurEelHacks::Autoscaler do
111
118
 
112
119
  describe "hard limits" do
113
120
  before :each do
114
- time_adjust(scaling_freq + 5)
121
+ time_adjust(expected_scale_frequency + 5)
115
122
  end
116
123
 
117
124
  it "should scale down if hard lower limit exceeded" do
@@ -122,13 +129,13 @@ describe OurEelHacks::Autoscaler do
122
129
 
123
130
  describe "soft upper limit" do
124
131
  before :each do
125
- time_adjust(scaling_freq * 2)
132
+ time_adjust(expected_scale_frequency * 2)
126
133
  autoscaler.scale(soft_high)
127
134
  end
128
135
 
129
136
  describe "if soft_duration hasn't elapsed" do
130
137
  before :each do
131
- time_adjust((scaling_freq * 2) + soft_dur - 5)
138
+ time_adjust((expected_scale_frequency * 2) + soft_dur - 5)
132
139
  heroku.should_not_receive(:ps_scale)
133
140
  end
134
141
 
@@ -143,7 +150,7 @@ describe OurEelHacks::Autoscaler do
143
150
 
144
151
  describe "if soft_duration has elapsed" do
145
152
  before :each do
146
- time_adjust(scaling_freq * 2 + soft_dur + 5)
153
+ time_adjust(expected_scale_frequency * 2 + soft_dur + 5)
147
154
  end
148
155
 
149
156
  it "should scale up if above upper soft limit" do
@@ -160,13 +167,13 @@ describe OurEelHacks::Autoscaler do
160
167
 
161
168
  describe "soft lower limit" do
162
169
  before :each do
163
- time_adjust(scaling_freq * 2)
170
+ time_adjust(expected_scale_frequency * 2)
164
171
  autoscaler.scale(soft_low)
165
172
  end
166
173
 
167
174
  describe "if soft_duration hasn't elapsed" do
168
175
  before :each do
169
- time_adjust(scaling_freq * 2 + soft_dur - 5)
176
+ time_adjust(expected_scale_frequency * 2 + soft_dur - 5)
170
177
  heroku.should_not_receive(:ps_scale)
171
178
  end
172
179
 
@@ -181,7 +188,7 @@ describe OurEelHacks::Autoscaler do
181
188
 
182
189
  describe "if soft_duration has elapsed" do
183
190
  before :each do
184
- time_adjust(scaling_freq * 2 + soft_dur + 5)
191
+ time_adjust(expected_scale_frequency * 2 + soft_dur + 5)
185
192
  end
186
193
 
187
194
  it "should not scale up even if above upper soft limit" do
data/spec/rack.rb CHANGED
@@ -1,4 +1,3 @@
1
- require 'spec_helper'
2
1
  require 'our-eel-hacks/rack'
3
2
 
4
3
  describe OurEelHacks::Rack do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: our-eel-hacks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.10
4
+ version: 0.0.11
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-09 00:00:00.000000000 Z
12
+ date: 2012-05-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: corundum
16
- requirement: &80593790 !ruby/object:Gem::Requirement
16
+ requirement: &75630260 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 0.0.1
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *80593790
24
+ version_requirements: *75630260
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: heroku
27
- requirement: &80592420 !ruby/object:Gem::Requirement
27
+ requirement: &75643080 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>'
@@ -34,7 +34,7 @@ dependencies:
34
34
  - 0
35
35
  type: :runtime
36
36
  prerelease: false
37
- version_requirements: *80592420
37
+ version_requirements: *75643080
38
38
  description: ! " Middleware for Rack and Sidekiq to scale heroku.\n\n A heroku process
39
39
  knows everything it needs in order to scale itself. A little configuration, and
40
40
  you're set.\n"
@@ -66,7 +66,7 @@ rdoc_options:
66
66
  - --main
67
67
  - doc/README
68
68
  - --title
69
- - our-eel-hacks-0.0.10 RDoc
69
+ - our-eel-hacks-0.0.11 RDoc
70
70
  require_paths:
71
71
  - lib/
72
72
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -77,7 +77,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
77
77
  version: '0'
78
78
  segments:
79
79
  - 0
80
- hash: -794712049
80
+ hash: 838375187
81
81
  required_rubygems_version: !ruby/object:Gem::Requirement
82
82
  none: false
83
83
  requirements: