Class: OurEelHacks::Autoscaler
- Inherits:
-
Object
- Object
- OurEelHacks::Autoscaler
- Defined in:
- lib/our-eel-hacks/autoscaler.rb
Defined Under Namespace
Classes: Limit, LowerLimit, UpperLimit
Constant Summary collapse
- MILLIS_PER_DAY =
24 * 60 * 60 * 1000
- API_CALLS_PER_SCALE =
2
Instance Attribute Summary collapse
-
#app_name ⇒ Object
Returns the value of attribute app_name.
-
#dynos ⇒ Object
readonly
Returns the value of attribute dynos.
-
#entered_soft ⇒ Object
readonly
Returns the value of attribute entered_soft.
-
#heroku_api_key ⇒ Object
Returns the value of attribute heroku_api_key.
-
#heroku_rate_limit ⇒ Object
Returns the value of attribute heroku_rate_limit.
-
#heroku_rate_limit_margin ⇒ Object
Returns the value of attribute heroku_rate_limit_margin.
-
#last_reading ⇒ Object
readonly
Returns the value of attribute last_reading.
-
#last_scaled ⇒ Object
readonly
Returns the value of attribute last_scaled.
-
#logger ⇒ Object
Returns the value of attribute logger.
-
#lower_limits ⇒ Object
Returns the value of attribute lower_limits.
-
#max_dynos ⇒ Object
Returns the value of attribute max_dynos.
-
#millis_til_next_scale ⇒ Object
readonly
Returns the value of attribute millis_til_next_scale.
-
#min_dynos ⇒ Object
Returns the value of attribute min_dynos.
-
#ps_type ⇒ Object
Returns the value of attribute ps_type.
-
#scaling_frequency ⇒ Object
Returns the value of attribute scaling_frequency.
-
#soft_duration ⇒ Object
Returns the value of attribute soft_duration.
-
#soft_side ⇒ Object
readonly
Returns the value of attribute soft_side.
-
#upper_limits ⇒ Object
Returns the value of attribute upper_limits.
Class Method Summary collapse
- .configure(flavor = :web, &block) ⇒ Object
- .get_instance(flavor) ⇒ Object
- .instance_for(flavor = :web) ⇒ Object
Instance Method Summary collapse
- #check_settings ⇒ Object
- #clear_dyno_info ⇒ Object
- #configure(flavor = nil) {|_self| ... } ⇒ Object
- #dyno_info ⇒ Object
- #dynos_stable? ⇒ Boolean
- #elapsed(start, finish) ⇒ Object
- #heroku ⇒ Object
-
#initialize ⇒ Autoscaler
constructor
A new instance of Autoscaler.
- #scale(metric) ⇒ Object
- #set_dynos(count, moment) ⇒ Object
- #soft_limit(metric, moment) ⇒ Object
- #target_scale(metric, moment) ⇒ Object
- #update_dynos(new_value, moment) ⇒ Object
- #update_scaling_delay(starting_wait) ⇒ Object
Constructor Details
#initialize ⇒ Autoscaler
Returns a new instance of Autoscaler.
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 67 def initialize() @dynos = nil @soft_side = nil @memoed_dyno_info = nil @last_scaled = Time.at(0) @entered_soft = Time.at(0) @last_reading = nil @app_name = nil @ps_type = nil @heroku_api_key = nil @min_dynos = 1 @max_dynos = 10 @lower_limits = LowerLimit.new(5, 1) @upper_limits = UpperLimit.new(30, 50) @soft_duration = 10000 @scaling_frequency = 5000 @heroku_rate_limit = 80_000 @heroku_rate_limit_margin = 0.1 @millis_til_next_scale = nil @logger = NullLogger.new end |
Instance Attribute Details
#app_name ⇒ Object
Returns the value of attribute app_name.
122 123 124 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 122 def app_name @app_name end |
#dynos ⇒ Object (readonly)
Returns the value of attribute dynos.
124 125 126 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 124 def dynos @dynos end |
#entered_soft ⇒ Object (readonly)
Returns the value of attribute entered_soft.
124 125 126 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 124 def entered_soft @entered_soft end |
#heroku_api_key ⇒ Object
Returns the value of attribute heroku_api_key.
122 123 124 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 122 def heroku_api_key @heroku_api_key end |
#heroku_rate_limit ⇒ Object
Returns the value of attribute heroku_rate_limit.
122 123 124 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 122 def heroku_rate_limit @heroku_rate_limit end |
#heroku_rate_limit_margin ⇒ Object
Returns the value of attribute heroku_rate_limit_margin.
122 123 124 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 122 def heroku_rate_limit_margin @heroku_rate_limit_margin end |
#last_reading ⇒ Object (readonly)
Returns the value of attribute last_reading.
124 125 126 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 124 def last_reading @last_reading end |
#last_scaled ⇒ Object (readonly)
Returns the value of attribute last_scaled.
124 125 126 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 124 def last_scaled @last_scaled end |
#logger ⇒ Object
Returns the value of attribute logger.
122 123 124 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 122 def logger @logger end |
#lower_limits ⇒ Object
Returns the value of attribute lower_limits.
122 123 124 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 122 def lower_limits @lower_limits end |
#max_dynos ⇒ Object
Returns the value of attribute max_dynos.
122 123 124 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 122 def max_dynos @max_dynos end |
#millis_til_next_scale ⇒ Object (readonly)
Returns the value of attribute millis_til_next_scale.
124 125 126 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 124 def millis_til_next_scale @millis_til_next_scale end |
#min_dynos ⇒ Object
Returns the value of attribute min_dynos.
122 123 124 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 122 def min_dynos @min_dynos end |
#ps_type ⇒ Object
Returns the value of attribute ps_type.
122 123 124 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 122 def ps_type @ps_type end |
#scaling_frequency ⇒ Object
Returns the value of attribute scaling_frequency.
122 123 124 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 122 def scaling_frequency @scaling_frequency end |
#soft_duration ⇒ Object
Returns the value of attribute soft_duration.
122 123 124 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 122 def soft_duration @soft_duration end |
#soft_side ⇒ Object (readonly)
Returns the value of attribute soft_side.
124 125 126 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 124 def soft_side @soft_side end |
#upper_limits ⇒ Object
Returns the value of attribute upper_limits.
122 123 124 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 122 def upper_limits @upper_limits end |
Class Method Details
.configure(flavor = :web, &block) ⇒ Object
19 20 21 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 19 def configure(flavor = :web, &block) get_instance(flavor).configure(flavor, &block) end |
.get_instance(flavor) ⇒ Object
13 14 15 16 17 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 13 def get_instance(flavor) flavor = flavor.to_sym @instances ||= Hash.new{ |h,k| h[k] = self.new } return @instances[flavor] end |
.instance_for(flavor = :web) ⇒ Object
23 24 25 26 27 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 23 def instance_for(flavor = :web) instance = get_instance(flavor) instance.check_settings return instance end |
Instance Method Details
#check_settings ⇒ Object
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 106 def check_settings errors = [] errors << "No heroku api key set" if @heroku_api_key.nil? errors << "No app name set" if @app_name.nil? errors << "No process type set" if @ps_type.nil? if (MILLIS_PER_DAY / @heroku_rate_limit) * (1.0 - @heroku_rate_limit_margin) * API_CALLS_PER_SCALE > @scaling_frequency errors << "Scaling frequency will lock up Heroku" end unless errors.empty? logger.warn{ "Problems configuring Autoscaler: #{errors.inspect}" } raise "OurEelHacks::Autoscaler, configuration problem: " + errors.join(", ") end end |
#clear_dyno_info ⇒ Object
213 214 215 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 213 def clear_dyno_info @memoed_dyno_info = nil end |
#configure(flavor = nil) {|_self| ... } ⇒ Object
96 97 98 99 100 101 102 103 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 96 def configure(flavor = nil) yield self check_settings logger.info{ "Autoscaler configured for #{flavor || "{{unknown flavor}}"}"} update_dynos(dyno_info.count, Time.now) update_scaling_delay(0) end |
#dyno_info ⇒ Object
217 218 219 220 221 222 223 224 225 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 217 def dyno_info return @memoed_dyno_info ||= begin regexp = /^#{ps_type}[.].*/ heroku.ps(app_name).find_all do |dyno| dyno["process"] =~ regexp end end end |
#dynos_stable? ⇒ Boolean
227 228 229 230 231 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 227 def dynos_stable? return dyno_info.all? do |dyno| dyno["state"] == "up" end end |
#elapsed(start, finish) ⇒ Object
126 127 128 129 130 131 132 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 126 def elapsed(start, finish) seconds = finish.to_i - start.to_i micros = finish.usec - start.usec diff = seconds * 1000 + micros / 1000 logger.debug{ "Elapsed: #{start.to_s}:#{finish.to_s} : #{diff}ms" } return diff end |
#heroku ⇒ Object
233 234 235 236 237 238 239 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 233 def heroku @heroku ||= Heroku::Client.new("", heroku_api_key).tap do |client| unless client.info(app_name)[:stack] == "cedar" raise "#{self.class.name} built against cedar stack" end end end |
#scale(metric) ⇒ Object
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 135 def scale(metric) logger.debug{ "Scaling request for #{@ps_type}: metric is: #{metric}" } moment = Time.now if elapsed(last_scaled, moment) < millis_til_next_scale logger.debug{ "Not scaling: elapsed #{elapsed(last_scaled, moment)} less than computed #{millis_til_next_scale}" } return end clear_dyno_info starting_wait = millis_til_next_scale update_dynos(dyno_info.count, moment) target_dynos = target_scale(metric, moment) target_dynos = [[target_dynos, max_dynos].min, min_dynos].max logger.debug{ "Target dynos at: #{min_dynos}/#{target_dynos}/#{max_dynos} (vs. current: #{@dynos})" } set_dynos(target_dynos, moment) update_scaling_delay(starting_wait) rescue => ex logger.warn{ "Problem scaling: #{ex.inspect} \t#{ex.backtrace.join("\t\n")}" } end |
#set_dynos(count, moment) ⇒ Object
241 242 243 244 245 246 247 248 249 250 251 252 253 254 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 241 def set_dynos(count,moment) if count == dynos logger.debug{ "Not scaling: #{count} ?= #{dynos}" } return end if not (stable = dynos_stable?) logger.debug{ "Not scaling: dynos not stable (iow: not all #{ps_type} dynos are up)" } return end logger.info{ "Scaling from #{dynos} to #{count} dynos for #{ps_type}" } heroku.ps_scale(app_name, :type => ps_type, :qty => count) update_dynos(count, moment) end |
#soft_limit(metric, moment) ⇒ Object
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 172 def soft_limit(metric, moment) hit_limit = [lower_limits, upper_limits].find{|lim| lim.includes? metric} if soft_side == hit_limit if elapsed(entered_soft, moment) > soft_duration entered_soft = moment case hit_limit when upper_limits return +1 when lower_limits return -1 else return 0 end else return 0 end else @entered_soft = moment end @soft_side = hit_limit return 0 end |
#target_scale(metric, moment) ⇒ Object
161 162 163 164 165 166 167 168 169 170 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 161 def target_scale(metric, moment) if lower_limits > metric return dynos - 1 elsif upper_limits < metric return dynos + 1 elsif result = (dynos + soft_limit(metric, moment)) return result end end |
#update_dynos(new_value, moment) ⇒ Object
204 205 206 207 208 209 210 211 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 204 def update_dynos(new_value, moment) if new_value != dynos @last_scaled = moment @entered_soft = moment end @dynos = new_value @last_reading = moment end |
#update_scaling_delay(starting_wait) ⇒ Object
197 198 199 200 201 202 |
# File 'lib/our-eel-hacks/autoscaler.rb', line 197 def update_scaling_delay(starting_wait) @millis_til_next_scale = scaling_frequency * @dynos if starting_wait > millis_til_next_scale @millis_til_next_scale = rand((@millis_til_next_scale..starting_wait)) end end |