alicorn 0.3.1 → 0.4.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.
@@ -0,0 +1 @@
1
+ alicorn
data/Gemfile CHANGED
@@ -1,11 +1,11 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- gem "curb", "~> 0.8.0"
3
+ gem "raindrops", ">= 0.5.0"
4
4
 
5
5
  group :development do
6
6
  gem "shoulda", "~> 3.0.1"
7
7
  gem "rdoc", "~> 3.12"
8
- gem "bundler", "~> 1.1.3"
8
+ gem "bundler", "~> 1.3.5"
9
9
  gem "jeweler", "~> 1.8.3"
10
10
  gem "mocha", "~> 0.11.4"
11
11
  gem "simplecov", "~> 0.6.4", :require => false, :platforms => :ruby_19
@@ -1,7 +1,6 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
- curb (0.8.0)
5
4
  git (1.2.5)
6
5
  jeweler (1.8.3)
7
6
  bundler (~> 1.0)
@@ -13,6 +12,7 @@ GEM
13
12
  mocha (0.11.4)
14
13
  metaclass (~> 0.0.1)
15
14
  multi_json (1.3.4)
15
+ raindrops (0.6.1)
16
16
  rake (0.9.2.2)
17
17
  rdoc (3.12)
18
18
  json (~> 1.4)
@@ -30,10 +30,10 @@ PLATFORMS
30
30
  ruby
31
31
 
32
32
  DEPENDENCIES
33
- bundler (~> 1.1.3)
34
- curb (~> 0.8.0)
33
+ bundler (~> 1.3.5)
35
34
  jeweler (~> 1.8.3)
36
35
  mocha (~> 0.11.4)
36
+ raindrops (>= 0.5.0)
37
37
  rdoc (~> 3.12)
38
38
  shoulda (~> 3.0.1)
39
39
  simplecov (~> 0.6.4)
@@ -1,6 +1,8 @@
1
- = Alicorn: Auto-scaler for Unicorn webserver
1
+ = Alicorn: Auto-scaler for Unicorn webserver
2
+ {<img src="https://codeclimate.com/badge.png" />}[https://codeclimate.com/github/bensomers/alicorn]
3
+ {<img src="https://gemnasium.com/bensomers/alicorn.png" alt="Dependency Status" />}[https://gemnasium.com/bensomers/alicorn]
2
4
 
3
- An experimental auto-scaler for Eric Wong's {Unicorn}[http://unicorn.bogomips.org]
5
+ An experimental auto-scaler for the {Unicorn}[http://unicorn.bogomips.org]
4
6
  webserver. Designed for Unicorn webservers running with the {Raindrops}[raindrops.bogomips.org]
5
7
  middleware enabled. It depends on the "active" and "queued" fields reported by
6
8
  Raindrops, and so only works on Linux.
data/Rakefile CHANGED
@@ -35,22 +35,4 @@ Rake::TestTask.new(:test) do |test|
35
35
  test.verbose = true
36
36
  end
37
37
 
38
- # require 'rcov/rcovtask'
39
- # Rcov::RcovTask.new do |test|
40
- # test.libs << 'test'
41
- # test.pattern = 'test/**/test_*.rb'
42
- # test.verbose = true
43
- # test.rcov_opts << '--exclude "gems/*"'
44
- # end
45
-
46
38
  task :default => :test
47
-
48
- require 'rdoc/task'
49
- Rake::RDocTask.new do |rdoc|
50
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
51
-
52
- rdoc.rdoc_dir = 'rdoc'
53
- rdoc.title = "alicorn #{version}"
54
- rdoc.rdoc_files.include('README*')
55
- rdoc.rdoc_files.include('lib/**/*.rb')
56
- end
@@ -7,9 +7,8 @@ require 'alicorn'
7
7
  options = {}
8
8
  OptionParser.new do |opts|
9
9
  opts.banner = "Alicorn is a tool for auto-scaling unicorn worker counts.
10
- It relies on unicorn's built-in Raindrops middleware (so make sure you have
11
- enabled it). It computes rough load scores based on separately averaged
12
- data sets. \n\nUsage: alicorn [options]"
10
+ It computes rough load scores based on separately averaged data sets.
11
+ \n\nUsage: alicorn [options]"
13
12
 
14
13
  opts.on('-h', '--help', 'Display this screen') do
15
14
  puts opts
@@ -37,8 +36,12 @@ data sets. \n\nUsage: alicorn [options]"
37
36
  options[:target_ratio] = v
38
37
  end
39
38
 
40
- opts.on("--url [URL]", String, "raindrops URL to check, defaults to 127.0.0.1/_raindrops") do |v|
41
- options[:url] = v
39
+ opts.on("--listener-type", String, "the type of listener to use, tcp or unix -- defaults to tcp") do |v|
40
+ options[:listener_type] = v
41
+ end
42
+
43
+ opts.on("--listener-address", String, "the listener address to use - defaults to 0.0.0.0:80") do |v|
44
+ options[:listener_address] = v
42
45
  end
43
46
 
44
47
  opts.on("--sample-count [N]", Integer, "number of data points to check before scaling") do |v|
@@ -2,6 +2,8 @@ module Alicorn
2
2
  class LogParser
3
3
  attr_accessor :filename, :samples
4
4
 
5
+ SAMPLE_REGEX = /(?<sample_type>active|queued|calling|writing):\[(?<values>.+)\]/
6
+
5
7
  def initialize(file = "alicorn.log")
6
8
  self.filename = file
7
9
  self.samples = []
@@ -10,24 +12,26 @@ module Alicorn
10
12
  def parse
11
13
  f = File.open(filename)
12
14
  f.each do |line|
13
- if line.match(/Sampling/)
14
- @sample_hash = {} # this will reset every sample
15
- elsif line.match(/calling:\[(.+)\]/)
16
- data = $1.split(", ").map(&:to_i)
17
- @sample_hash[:calling] = (DataSet.new << data).flatten
18
- elsif line.match(/writing:\[(.+)\]/)
19
- data = $1.split(", ").map(&:to_i)
20
- @sample_hash[:writing] = (DataSet.new << data).flatten
21
- elsif line.match(/active:\[(.+)\]/)
22
- data = $1.split(", ").map(&:to_i)
23
- @sample_hash[:active] = (DataSet.new << data).flatten
24
- elsif line.match(/queued:\[(.+)\]/)
25
- data = $1.split(", ").map(&:to_i)
26
- @sample_hash[:queued] = (DataSet.new << data).flatten
27
- samples << @sample_hash # store the old sample
28
- end
15
+ sample_line(line)
29
16
  end
30
17
  samples
31
18
  end
19
+
20
+ private
21
+
22
+ # Not suitable for calling in isolation, since an actual data sample
23
+ # spans 5 lines in the log file. This method checks for lines indicating
24
+ # either the beginning or end of a sample, or for data lines in the middle.
25
+ def sample_line(line)
26
+ if line.match(/Sampling/)
27
+ @sample_hash = {} # reset for the new sample
28
+ elsif match_result = line.match(SAMPLE_REGEX)
29
+ data = match_result[:values].split(", ").map(&:to_i)
30
+ @sample_hash[match_result[:sample_type].to_sym] = (DataSet.new << data).flatten
31
+ elsif line.match(/target calculated at:/)
32
+ samples << @sample_hash # store the old sample
33
+ end
34
+ end
35
+
32
36
  end
33
37
  end
@@ -1,4 +1,4 @@
1
- require 'curl'
1
+ require 'raindrops'
2
2
  require 'logger'
3
3
  require 'alicorn/dataset'
4
4
  require 'alicorn/errors'
@@ -6,7 +6,8 @@ require 'alicorn/errors'
6
6
  module Alicorn
7
7
  class Scaler
8
8
  attr_accessor :min_workers, :max_workers, :target_ratio, :buffer,
9
- :raindrops_url, :delay, :sample_count, :app_name, :dry_run, :logger
9
+ :listener_type, :listener_address, :delay, :sample_count, :app_name,
10
+ :dry_run, :logger
10
11
 
11
12
  attr_reader :signal_delay
12
13
 
@@ -15,7 +16,8 @@ module Alicorn
15
16
  @max_workers = options[:max_workers]
16
17
  @target_ratio = options[:target_ratio] || 1.3
17
18
  @buffer = options[:buffer] || 2
18
- @raindrops_url = options[:url] || "http://127.0.0.1/_raindrops"
19
+ @listener_type = options[:listener_type] || "tcp"
20
+ @listener_address = options[:listener_address] || "0.0.0.0:80"
19
21
  @delay = options[:delay] || 1
20
22
  @sample_count = options[:sample_count] || 30
21
23
  @app_name = options[:app_name] || "unicorn"
@@ -82,23 +84,19 @@ module Alicorn
82
84
 
83
85
  def collect_data
84
86
  logger.debug "Sampling #{sample_count} times at #{delay} second intervals"
85
- calling, writing, active, queued = DataSet.new, DataSet.new, DataSet.new, DataSet.new
87
+ active, queued = DataSet.new, DataSet.new
86
88
  sample_count.times do
87
- raindrops = get_raindrops(raindrops_url)
88
- calling << $1.to_i if raindrops.detect { |line| line.match(/calling: ([0-9]+)/) }
89
- writing << $1.to_i if raindrops.detect { |line| line.match(/writing: ([0-9]+)/) }
90
- active << $1.to_i if raindrops.detect { |line| line.match(/active: ([0-9]+)/) }
91
- queued << $1.to_i if raindrops.detect { |line| line.match(/queued: ([0-9]+)/) }
89
+ stats = Raindrops::Linux.send(:"#{listener_type}_listener_stats", listener_address)[listener_address]
90
+ active << stats.active
91
+ queued << stats.queued
92
92
  sleep(delay)
93
93
  end
94
94
 
95
95
  logger.debug "Collected:"
96
- logger.debug "calling:#{calling}"
97
- logger.debug "writing:#{writing}"
98
96
  logger.debug "active:#{active}"
99
97
  logger.debug "queued:#{queued}"
100
98
 
101
- {:calling => calling, :writing => writing, :active => active, :queued => queued}
99
+ {:active => active, :queued => queued}
102
100
  end
103
101
 
104
102
  private
@@ -136,9 +134,5 @@ module Alicorn
136
134
  def send_signal(master_pid, sig)
137
135
  Process.kill(sig, master_pid)
138
136
  end
139
-
140
- def get_raindrops(url)
141
- Curl::Easy.http_get(url).body_str.split("\n")
142
- end
143
137
  end
144
138
  end
@@ -1,3 +1,3 @@
1
1
  module Alicorn
2
- VERSION = "0.3.1"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -1,14 +1,10 @@
1
1
  D, [2012-05-17T14:45:02.092344 #26889] DEBUG -- : Sampling 3 times
2
2
  D, [2012-05-17T14:45:32.901373 #26889] DEBUG -- : Collected:
3
- D, [2012-05-17T14:45:32.901563 #26889] DEBUG -- : calling:[1, 2, 1]
4
- D, [2012-05-17T14:45:32.901960 #26889] DEBUG -- : writing:[2, 1, 2]
5
3
  D, [2012-05-17T14:45:32.902297 #26889] DEBUG -- : active:[1, 4, 3]
6
4
  D, [2012-05-17T14:45:32.902606 #26889] DEBUG -- : queued:[0, 2, 1]
7
5
  D, [2012-05-17T14:45:32.902944 #26889] DEBUG -- : target calculated at: 4, worker count at 2
8
6
  D, [2012-05-17T14:50:01.575204 #27727] DEBUG -- : Sampling 5 times
9
7
  D, [2012-05-17T14:50:31.697833 #27727] DEBUG -- : Collected:
10
- D, [2012-05-17T14:50:31.698001 #27727] DEBUG -- : calling:[11, 12, 11, 13, 14]
11
- D, [2012-05-17T14:50:31.698452 #27727] DEBUG -- : writing:[12, 11, 12, 14, 13]
12
8
  D, [2012-05-17T14:50:31.698792 #27727] DEBUG -- : active:[11, 14, 13, 12, 11]
13
9
  D, [2012-05-17T14:50:31.699107 #27727] DEBUG -- : queued:[10, 12, 11, 11, 12]
14
10
  D, [2012-05-17T14:50:31.699448 #27727] DEBUG -- : target calculated at: 14, worker count at 12
@@ -9,13 +9,9 @@ class TestLogParser < Test::Unit::TestCase
9
9
 
10
10
  context "#parse" do
11
11
  should "correctly read log file" do
12
- expected = [ { :calling => DataSet.new([1,2,1]),
13
- :writing => DataSet.new([2,1,2]),
14
- :active => DataSet.new([1,4,3]),
12
+ expected = [ { :active => DataSet.new([1,4,3]),
15
13
  :queued => DataSet.new([0,2,1]) },
16
- { :calling => DataSet.new([11, 12, 11, 13, 14]),
17
- :writing => DataSet.new([12, 11, 12, 14, 13]),
18
- :active => DataSet.new([11, 14, 13, 12, 11]),
14
+ { :active => DataSet.new([11, 14, 13, 12, 11]),
19
15
  :queued => DataSet.new([10, 12, 11, 11, 12]) }
20
16
  ]
21
17
 
@@ -5,9 +5,9 @@ class TestScaler < Test::Unit::TestCase
5
5
  def setup
6
6
  @scaler = Alicorn::Scaler.new(:delay => 0)
7
7
 
8
- # stub out external call
9
- raindrops = "calling: 3\nwriting: 1\n/tmp/cart.socket active: 4\n/tmp/cart.socket queued: 0\n"
10
- Curl::Easy.stubs(:http_get).returns(stub(:body_str => raindrops))
8
+ # stub out stats gathering
9
+ mock_struct = stub_everything(:active => 4, :queued => 0)
10
+ Raindrops::Linux.stubs(:tcp_listener_stats).returns({"0.0.0.0:80" => mock_struct})
11
11
 
12
12
  # enable us to test private methods, for the complicated ones
13
13
  class << @scaler
@@ -194,9 +194,7 @@ class TestScaler < Test::Unit::TestCase
194
194
  context "#collect_data" do
195
195
  should "return the correct data" do
196
196
  data = @scaler.send(:collect_data)
197
- expected_data = { :calling => [3]*30,
198
- :writing => [1]*30,
199
- :active => [4]*30,
197
+ expected_data = { :active => [4]*30,
200
198
  :queued => [0]*30 }
201
199
  assert_equal expected_data, data
202
200
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: alicorn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,22 +9,27 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-06-08 00:00:00.000000000 Z
12
+ date: 2013-06-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: curb
16
- requirement: &70277797703480 !ruby/object:Gem::Requirement
15
+ name: raindrops
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
- - - ~>
19
+ - - ! '>='
20
20
  - !ruby/object:Gem::Version
21
- version: 0.8.0
21
+ version: 0.5.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70277797703480
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 0.5.0
25
30
  - !ruby/object:Gem::Dependency
26
31
  name: shoulda
27
- requirement: &70277797702640 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
28
33
  none: false
29
34
  requirements:
30
35
  - - ~>
@@ -32,10 +37,15 @@ dependencies:
32
37
  version: 3.0.1
33
38
  type: :development
34
39
  prerelease: false
35
- version_requirements: *70277797702640
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 3.0.1
36
46
  - !ruby/object:Gem::Dependency
37
47
  name: rdoc
38
- requirement: &70277797701780 !ruby/object:Gem::Requirement
48
+ requirement: !ruby/object:Gem::Requirement
39
49
  none: false
40
50
  requirements:
41
51
  - - ~>
@@ -43,21 +53,31 @@ dependencies:
43
53
  version: '3.12'
44
54
  type: :development
45
55
  prerelease: false
46
- version_requirements: *70277797701780
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '3.12'
47
62
  - !ruby/object:Gem::Dependency
48
63
  name: bundler
49
- requirement: &70277797701060 !ruby/object:Gem::Requirement
64
+ requirement: !ruby/object:Gem::Requirement
50
65
  none: false
51
66
  requirements:
52
67
  - - ~>
53
68
  - !ruby/object:Gem::Version
54
- version: 1.1.3
69
+ version: 1.3.5
55
70
  type: :development
56
71
  prerelease: false
57
- version_requirements: *70277797701060
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 1.3.5
58
78
  - !ruby/object:Gem::Dependency
59
79
  name: jeweler
60
- requirement: &70277797700120 !ruby/object:Gem::Requirement
80
+ requirement: !ruby/object:Gem::Requirement
61
81
  none: false
62
82
  requirements:
63
83
  - - ~>
@@ -65,10 +85,15 @@ dependencies:
65
85
  version: 1.8.3
66
86
  type: :development
67
87
  prerelease: false
68
- version_requirements: *70277797700120
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 1.8.3
69
94
  - !ruby/object:Gem::Dependency
70
95
  name: mocha
71
- requirement: &70277797715880 !ruby/object:Gem::Requirement
96
+ requirement: !ruby/object:Gem::Requirement
72
97
  none: false
73
98
  requirements:
74
99
  - - ~>
@@ -76,10 +101,15 @@ dependencies:
76
101
  version: 0.11.4
77
102
  type: :development
78
103
  prerelease: false
79
- version_requirements: *70277797715880
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: 0.11.4
80
110
  - !ruby/object:Gem::Dependency
81
111
  name: simplecov
82
- requirement: &70277797715200 !ruby/object:Gem::Requirement
112
+ requirement: !ruby/object:Gem::Requirement
83
113
  none: false
84
114
  requirements:
85
115
  - - ~>
@@ -87,7 +117,12 @@ dependencies:
87
117
  version: 0.6.4
88
118
  type: :development
89
119
  prerelease: false
90
- version_requirements: *70277797715200
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ~>
124
+ - !ruby/object:Gem::Version
125
+ version: 0.6.4
91
126
  description: Highly configurable dumb auto-scaler for managing unicorn web servers
92
127
  email: somers.ben@gmail.com
93
128
  executables:
@@ -99,6 +134,7 @@ extra_rdoc_files:
99
134
  - README.rdoc
100
135
  files:
101
136
  - .document
137
+ - .ruby-gemset
102
138
  - CHANGELOG.txt
103
139
  - Gemfile
104
140
  - Gemfile.lock
@@ -136,7 +172,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
136
172
  version: '0'
137
173
  segments:
138
174
  - 0
139
- hash: 4049117903748411097
175
+ hash: 2717373348284717997
140
176
  required_rubygems_version: !ruby/object:Gem::Requirement
141
177
  none: false
142
178
  requirements:
@@ -145,7 +181,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
145
181
  version: '0'
146
182
  requirements: []
147
183
  rubyforge_project:
148
- rubygems_version: 1.8.17
184
+ rubygems_version: 1.8.25
149
185
  signing_key:
150
186
  specification_version: 3
151
187
  summary: Standalone auto-scaler for unicorn