Class: HerokuVector::DynoScaler

Inherits:
Object
  • Object
show all
Includes:
Helper
Defined in:
lib/heroku_vector/dyno_scaler.rb

Constant Summary collapse

MIN_SCALE_TIME_DELTA_SEC =

5 mins

5 * 60

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Helper

included

Constructor Details

#initialize(name, options = {}) ⇒ DynoScaler

Returns a new instance of DynoScaler.



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/heroku_vector/dyno_scaler.rb', line 13

def initialize(name, options={})
  @name = name
  @period = options[:period] || 60 # 1 min
  sample_size = options[:sample_size] || Sampler.capacity_for_sample_period(@period)
  @sampler = Sampler.new( sample_size )
  @min_dynos = options[:min_dynos] || 2
  @max_dynos = options[:max_dynos] || 10
  @min_value = options[:min_value] || raise("DynoScaler: min_value required")
  @max_value = options[:max_value] || raise("DynoScaler: max_value required")

  @scale_up_by = options[:scale_up_by] || 1
  @scale_down_by = options[:scale_down_by] || 1
  @source = load_data_source(options)
  @engine = options[:engine] || Engine::Heroku.new
end

Instance Attribute Details

#engineObject (readonly)

Returns the value of attribute engine.



8
9
10
# File 'lib/heroku_vector/dyno_scaler.rb', line 8

def engine
  @engine
end

#last_scale_timeObject

Returns the value of attribute last_scale_time.



7
8
9
# File 'lib/heroku_vector/dyno_scaler.rb', line 7

def last_scale_time
  @last_scale_time
end

#max_dynosObject (readonly)

Returns the value of attribute max_dynos.



8
9
10
# File 'lib/heroku_vector/dyno_scaler.rb', line 8

def max_dynos
  @max_dynos
end

#max_valueObject (readonly)

Returns the value of attribute max_value.



8
9
10
# File 'lib/heroku_vector/dyno_scaler.rb', line 8

def max_value
  @max_value
end

#min_dynosObject (readonly)

Returns the value of attribute min_dynos.



8
9
10
# File 'lib/heroku_vector/dyno_scaler.rb', line 8

def min_dynos
  @min_dynos
end

#min_valueObject (readonly)

Returns the value of attribute min_value.



8
9
10
# File 'lib/heroku_vector/dyno_scaler.rb', line 8

def min_value
  @min_value
end

#nameObject (readonly)

Returns the value of attribute name.



8
9
10
# File 'lib/heroku_vector/dyno_scaler.rb', line 8

def name
  @name
end

#periodObject (readonly)

Returns the value of attribute period.



8
9
10
# File 'lib/heroku_vector/dyno_scaler.rb', line 8

def period
  @period
end

#samplerObject (readonly)

Returns the value of attribute sampler.



8
9
10
# File 'lib/heroku_vector/dyno_scaler.rb', line 8

def sampler
  @sampler
end

#scale_down_byObject (readonly)

Returns the value of attribute scale_down_by.



8
9
10
# File 'lib/heroku_vector/dyno_scaler.rb', line 8

def scale_down_by
  @scale_down_by
end

#scale_up_byObject (readonly)

Returns the value of attribute scale_up_by.



8
9
10
# File 'lib/heroku_vector/dyno_scaler.rb', line 8

def scale_up_by
  @scale_up_by
end

#sourceObject

Returns the value of attribute source.



7
8
9
# File 'lib/heroku_vector/dyno_scaler.rb', line 7

def source
  @source
end

Instance Method Details

#collect_sampleObject



89
90
91
# File 'lib/heroku_vector/dyno_scaler.rb', line 89

def collect_sample
  sampler << source.sample
end

#current_sizeObject



93
94
95
# File 'lib/heroku_vector/dyno_scaler.rb', line 93

def current_size
  engine.count_for_dyno_name(self.name)
end

#current_valueObject



85
86
87
# File 'lib/heroku_vector/dyno_scaler.rb', line 85

def current_value
  sampler.mean
end

#display_unitObject



128
129
130
# File 'lib/heroku_vector/dyno_scaler.rb', line 128

def display_unit
  source.unit rescue 'units'
end

#enough_samples?Boolean

Returns:

  • (Boolean)


117
118
119
# File 'lib/heroku_vector/dyno_scaler.rb', line 117

def enough_samples?
  sampler.full?
end

#evaluate_and_scaleObject



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/heroku_vector/dyno_scaler.rb', line 61

def evaluate_and_scale
  num_dynos = current_size
  total_min = min_value * num_dynos
  total_max = max_value * num_dynos
  value = current_value

  logger.debug "#{self.name}: #{num_dynos} dynos - #{value} #{display_unit}"
  if value < total_min
    unless num_dynos <= min_dynos
      logger.info "#{self.name}: #{num_dynos} dynos - #{value} #{display_unit} below #{total_min} - scaling down"
    end
    # Always scale down one at a time
    new_amount = num_dynos - scale_down_by
    scale_dynos(num_dynos, new_amount)
  elsif value > total_max
    unless num_dynos >= max_dynos
      logger.info "#{self.name}: #{num_dynos} dynos - #{value} #{display_unit} above #{total_max} - scaling up"
    end
    # Scale up to N new dynos
    new_amount = num_dynos + scale_up_by
    scale_dynos(num_dynos, new_amount)
  end
end

#load_data_source(options) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/heroku_vector/dyno_scaler.rb', line 29

def load_data_source(options)
  source = options[:source]

  if source.is_a?(Class)
    @source = source.new(options)
  else
    begin
      clazz = HerokuVector::Source.const_get(source)
      @source = clazz.new(options)
    rescue
      raise "DynoScaler: Invalid source class '#{source}'"
    end
  end
end

#normalize_dyno_increment(amount) ⇒ Object



106
107
108
109
110
111
# File 'lib/heroku_vector/dyno_scaler.rb', line 106

def normalize_dyno_increment(amount)
  [
    [amount, max_dynos].min,
    min_dynos
  ].max
end

#record_last_scale_eventObject



113
114
115
# File 'lib/heroku_vector/dyno_scaler.rb', line 113

def record_last_scale_event
  @last_scale_time = Time.now
end

#resetObject



44
45
46
47
# File 'lib/heroku_vector/dyno_scaler.rb', line 44

def reset
  sampler.clear
  @last_scale_time = nil
end

#runObject



49
50
51
52
53
54
55
56
57
58
59
# File 'lib/heroku_vector/dyno_scaler.rb', line 49

def run
  begin
    collect_sample
    return unless enough_samples?
    return if scaling_too_soon?

    evaluate_and_scale
  rescue => e
    logger.error "#{self.name} worker.run(): #{e}"
  end
end

#scale_dynos(current_amount, new_amount) ⇒ Object



97
98
99
100
101
102
103
104
# File 'lib/heroku_vector/dyno_scaler.rb', line 97

def scale_dynos(current_amount, new_amount)
  new_amount = normalize_dyno_increment(new_amount)
  return if current_amount == new_amount

  record_last_scale_event

  engine.scale_dynos(self.name, new_amount)
end

#scaling_too_soon?Boolean

Returns:

  • (Boolean)


121
122
123
124
125
126
# File 'lib/heroku_vector/dyno_scaler.rb', line 121

def scaling_too_soon?
  return false unless last_scale_time
  scale_delta = Time.now - last_scale_time

  MIN_SCALE_TIME_DELTA_SEC >= scale_delta
end