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.
- data/.ruby-gemset +1 -0
- data/Gemfile +2 -2
- data/Gemfile.lock +3 -3
- data/README.rdoc +4 -2
- data/Rakefile +0 -18
- data/bin/alicorn +8 -5
- data/lib/alicorn/log_parser.rb +20 -16
- data/lib/alicorn/scaler.rb +10 -16
- data/lib/alicorn/version.rb +1 -1
- data/test/fixtures/sample.alicorn.log +0 -4
- data/test/test_log_parser.rb +2 -6
- data/test/test_scaler.rb +4 -6
- metadata +58 -22
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
alicorn
|
data/Gemfile
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
source "http://rubygems.org"
|
2
2
|
|
3
|
-
gem "
|
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.
|
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
|
data/Gemfile.lock
CHANGED
@@ -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.
|
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)
|
data/README.rdoc
CHANGED
@@ -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
|
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
|
data/bin/alicorn
CHANGED
@@ -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
|
11
|
-
|
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("--
|
41
|
-
options[:
|
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|
|
data/lib/alicorn/log_parser.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/alicorn/scaler.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
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
|
-
:
|
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
|
-
@
|
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
|
-
|
87
|
+
active, queued = DataSet.new, DataSet.new
|
86
88
|
sample_count.times do
|
87
|
-
|
88
|
-
|
89
|
-
|
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
|
-
{:
|
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
|
data/lib/alicorn/version.rb
CHANGED
@@ -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
|
data/test/test_log_parser.rb
CHANGED
@@ -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 = [ { :
|
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
|
-
{ :
|
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
|
|
data/test/test_scaler.rb
CHANGED
@@ -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
|
9
|
-
|
10
|
-
|
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 = { :
|
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.
|
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:
|
12
|
+
date: 2013-06-12 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
16
|
-
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.
|
21
|
+
version: 0.5.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
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:
|
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:
|
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:
|
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:
|
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:
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
50
65
|
none: false
|
51
66
|
requirements:
|
52
67
|
- - ~>
|
53
68
|
- !ruby/object:Gem::Version
|
54
|
-
version: 1.
|
69
|
+
version: 1.3.5
|
55
70
|
type: :development
|
56
71
|
prerelease: false
|
57
|
-
version_requirements:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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.
|
184
|
+
rubygems_version: 1.8.25
|
149
185
|
signing_key:
|
150
186
|
specification_version: 3
|
151
187
|
summary: Standalone auto-scaler for unicorn
|