Class: Alicorn::Scaler

Inherits:
Object
  • Object
show all
Defined in:
lib/alicorn/scaler.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Scaler

Returns a new instance of Scaler.



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/alicorn/scaler.rb', line 14

def initialize(options = {})
  @min_workers        = options[:min_workers]         || 1
  @max_workers        = options[:max_workers]
  @target_ratio       = options[:target_ratio]        || 1.3
  @buffer             = options[:buffer]              || 2
  @listener_type      = options[:listener_type]       || "tcp"
  @listener_address   = options[:listener_address]    || "0.0.0.0:80"
  @delay              = options[:delay]               || 1
  @sample_count       = options[:sample_count]        || 30
  @app_name           = options[:app_name]            || "unicorn"
  @dry_run            = options[:dry_run]
  @signal_delay       = 0.5
  log_path            = options[:log_path]            || "/dev/null"

  self.logger = Logger.new(log_path)
  logger.level = options[:verbose] ? Logger::DEBUG : Logger::WARN
end

Instance Attribute Details

#app_nameObject

Returns the value of attribute app_name.



8
9
10
# File 'lib/alicorn/scaler.rb', line 8

def app_name
  @app_name
end

#bufferObject

Returns the value of attribute buffer.



8
9
10
# File 'lib/alicorn/scaler.rb', line 8

def buffer
  @buffer
end

#delayObject

Returns the value of attribute delay.



8
9
10
# File 'lib/alicorn/scaler.rb', line 8

def delay
  @delay
end

#dry_runObject

Returns the value of attribute dry_run.



8
9
10
# File 'lib/alicorn/scaler.rb', line 8

def dry_run
  @dry_run
end

#listener_addressObject

Returns the value of attribute listener_address.



8
9
10
# File 'lib/alicorn/scaler.rb', line 8

def listener_address
  @listener_address
end

#listener_typeObject

Returns the value of attribute listener_type.



8
9
10
# File 'lib/alicorn/scaler.rb', line 8

def listener_type
  @listener_type
end

#loggerObject

Returns the value of attribute logger.



8
9
10
# File 'lib/alicorn/scaler.rb', line 8

def logger
  @logger
end

#max_workersObject

Returns the value of attribute max_workers.



8
9
10
# File 'lib/alicorn/scaler.rb', line 8

def max_workers
  @max_workers
end

#min_workersObject

Returns the value of attribute min_workers.



8
9
10
# File 'lib/alicorn/scaler.rb', line 8

def min_workers
  @min_workers
end

#sample_countObject

Returns the value of attribute sample_count.



8
9
10
# File 'lib/alicorn/scaler.rb', line 8

def sample_count
  @sample_count
end

#signal_delayObject (readonly)

Returns the value of attribute signal_delay.



12
13
14
# File 'lib/alicorn/scaler.rb', line 12

def signal_delay
  @signal_delay
end

#target_ratioObject

Returns the value of attribute target_ratio.



8
9
10
# File 'lib/alicorn/scaler.rb', line 8

def target_ratio
  @target_ratio
end

Instance Method Details

#auto_scale(data, worker_count) ⇒ Object



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/alicorn/scaler.rb', line 51

def auto_scale(data, worker_count)
  return nil if data[:active].empty? or data[:queued].empty?
  connections = data[:active].zip(data[:queued]).map { |e| e.inject(:+) }
  connections = DataSet.new(connections)

  # Calculate target
  target = connections.max * target_ratio + buffer

  # Check hard thresholds
  target = max_workers if max_workers and target > max_workers
  target = min_workers if target < min_workers
  target = target.ceil

  logger.debug "target calculated at: #{target}, worker count at #{worker_count}"
  if worker_count == max_workers and target == max_workers
    logger.warn "at maximum capacity! cannot scale up"
    return nil, 0
  elsif connections.avg > worker_count and data[:queued].avg > 1
    logger.warn "danger, will robinson! scaling up fast!"
    return "TTIN", target - worker_count
  elsif target > worker_count
    logger.debug "scaling up!"
    return "TTIN", 1
  elsif target < worker_count
    logger.debug "scaling down!"
    return "TTOU", 1
  elsif target == worker_count
    logger.debug "just right!"
    return nil, 0
  end
end

#scaleObject



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/alicorn/scaler.rb', line 32

def scale
  data          = collect_data
  unicorns      = find_unicorns

  master_pid    = find_master_pid(unicorns)
  worker_count  = find_worker_count(unicorns)

  sig, number = auto_scale(data, worker_count)
  if sig and !dry_run
    number.times do
      send_signal(master_pid, sig) 
      sleep(signal_delay) # Make sure unicorn doesn't discard repeated signals
    end
  end
rescue StandardError => e
  logger.error "exception occurred: #{e.class}\n\n#{e.message}"
  raise e unless e.is_a?(AmbiguousMasterError) or e.is_a?(NoMasterError) # Master-related errors are fine, usually just indicate a start or restart
end