alicorn 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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