gargor 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in gargor.gemspec
4
+ gemspec
5
+
6
+ require 'json'
7
+ require 'jsonpath'
8
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Yoshihiro TAKAHARA
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,171 @@
1
+ # gargor
2
+
3
+ `gargor`はChefで管理されたサーバのパラメータと負荷試験結果を遺伝的アルゴリズム(genetic algorithm)により機械学習し、最適値を探索します。本ソフトウェアをうまく使うことで今まで勘と経験に頼っていたサーバチューニングをより最適にかつ自動化することができます。
4
+
5
+ ## インストール
6
+
7
+ Ruby 1.9以降が必要です
8
+
9
+ $ gem install gargor
10
+
11
+ ## どのように動くのか
12
+
13
+ 1. 現在のChefの設定ファイル(JSON)から個体を一つ作ります。
14
+ 2. 残りの個体を突然変異により作ります。
15
+ 3. 各個体に対し、負荷試験を実施し、適応値を算出します。
16
+ 4. 現世代の個体群に対して、エリートと残りを交叉および突然変異により次世代の個体群をつくります。
17
+ 5. 次世代の個体群を現世代として`3.`に戻ります。これを指定した世代分実施します。
18
+ 6. 最後に最も高い適応値の個体をサーバに配備して終了します
19
+
20
+ ## 使い方
21
+
22
+ $ gargor [dsl-file]
23
+
24
+
25
+ `dsl-file`を省略した場合は、カレントディレクトリの`gargor.rb`を探します。
26
+
27
+ dsl-fileの書き方は、添付の`doc/sample.rb`を御覧ください
28
+
29
+
30
+ ### 注意
31
+
32
+ `gargor`は、DSLファイルで指定されたChefのJSONを直接(問答無用で)書き換えます。`git stash`を使うなりしてオリジナルが消えないように配慮ください。
33
+
34
+ ## サンプルレシピ
35
+
36
+ `Chef`のサンプルレシピをTipsを交えてご紹介します。
37
+
38
+ ### mysql
39
+
40
+
41
+ ```ruby
42
+ # この時点で起動しようとすると以前の設定ファイルがおかしい場合にエラーが出てしまう
43
+ service "mysqld" do
44
+ action :nothing
45
+ end
46
+
47
+ template "/etc/my.cnf" do
48
+ source "www/my.cnf.erb"
49
+ notifies :restart,"service[mysqld]"
50
+ end
51
+
52
+ # ib_logfile0,1はib_logfile1はinnodb_log_file_sizeを変えるとエラーになるので毎回消す
53
+ file "/var/lib/mysql/ib_logfile0" do
54
+ action :delete
55
+ notifies :restart,"service[mysqld]"
56
+ end
57
+ file "/var/lib/mysql/ib_logfile1" do
58
+ action :delete
59
+ notifies :restart,"service[mysqld]"
60
+ end
61
+ ```
62
+
63
+ このテンプレートは以下のようになっています。`innodb_buffer_pool_size`を探索対象のパラメータにしたいのですが、「`innodb_log_file_size`を`innodb_buffer_pool_size`の25%にすべし」と注意書きがあるので、`innodb_log_file_size`を設定対象にして、`innodb_buffer_pool_size`をその4倍にしています。
64
+
65
+ ```
66
+ [mysqld]
67
+ datadir=/var/lib/mysql
68
+ socket=/var/lib/mysql/mysql.sock
69
+ user=mysql
70
+ # Default to using old password format for compatibility with mysql 3.x
71
+ # clients (those using the mysqlclient10 compatibility package).
72
+ old_passwords=1
73
+
74
+ # Disabling symbolic-links is recommended to prevent assorted security risks;
75
+ # to do so, uncomment this line:
76
+ # symbolic-links=0
77
+
78
+ skip-locking
79
+ query_cache_size = <%= node["mysqld"]["query_cache_size"] %>M
80
+ key_buffer = <%= node["mysqld"]["key_buffer"] %>K
81
+ max_allowed_packet = <%= node["mysqld"]["max_allowed_packet"] %>M
82
+ table_cache = <%= node["mysqld"]["table_cache"] %>
83
+ sort_buffer_size = <%= node["mysqld"]["sort_buffer_size"] %>K
84
+ read_buffer_size = <%= node["mysqld"]["read_buffer_size"] %>K
85
+ read_rnd_buffer_size = <%= node["mysqld"]["read_rnd_buffer_size"] %>K
86
+ net_buffer_length = <%= node["mysqld"]["net_buffer_length"] %>K
87
+ thread_stack = <%= node["mysqld"]["thread_stack"] %>K
88
+
89
+ skip-networking
90
+ server-id = 1
91
+
92
+ # Uncomment the following if you want to log updates
93
+ #log-bin=mysql-bin
94
+
95
+ # Disable Federated by default
96
+ skip-federated
97
+
98
+ # Uncomment the following if you are NOT using BDB tables
99
+ #skip-bdb
100
+
101
+ # Uncomment the following if you are using InnoDB tables
102
+ innodb_data_home_dir = /var/lib/mysql/
103
+ innodb_data_file_path = ibdata1:10M:autoextend
104
+ innodb_log_group_home_dir = /var/lib/mysql/
105
+ innodb_log_arch_dir = /var/lib/mysql/
106
+ # You can set .._buffer_pool_size up to 50 - 80 %
107
+ # of RAM but beware of setting memory usage too high
108
+ innodb_buffer_pool_size = <%= node["mysqld"]["innodb_log_file_size"]*4 %>M
109
+ innodb_additional_mem_pool_size = <%= node["mysqld"]["innodb_additional_mem_pool_size"] %>M
110
+ # Set .._log_file_size to 25 % of buffer pool size
111
+ innodb_log_file_size = <%= node["mysqld"]["innodb_log_file_size"] %>M
112
+ innodb_log_buffer_size = <%= node["mysqld"]["innodb_log_buffer_size"] %>M
113
+ innodb_flush_log_at_trx_commit = 1
114
+ innodb_lock_wait_timeout = 50
115
+
116
+ [mysqld_safe]
117
+ log-error=/var/log/mysqld.log
118
+ pid-file=/var/run/mysqld/mysqld.pid
119
+ ```
120
+
121
+ ### httpd
122
+
123
+ Apacheのパフォーマンスチューニングでは以下の様にしています。レシピには全く工夫はありません。
124
+
125
+ ```
126
+ service "httpd" do
127
+ action :nothing
128
+ end
129
+
130
+ template "/etc/httpd/conf/httpd.conf" do
131
+ source "www/httpd.conf.erb"
132
+ notifies :restart,"service[httpd]"
133
+ end
134
+ ```
135
+
136
+ ポイントは`MinSpareServers`と`MaxSpareServers`のように上下関係のあるパラメータの決定方法を以下のように`min_spare_servers`と`range_spare_servers`としている点です。また、`ServerLimit`=`MaxClients`としています。
137
+
138
+ ```
139
+ <IfModule prefork.c>
140
+ StartServers <%= node["httpd"]["start_servers"] %>
141
+ MinSpareServers <%= node["httpd"]["min_spare_servers"] %>
142
+ MaxSpareServers <%= node["httpd"]["min_spare_servers"] + node["httpd"]["range_spare_servers"] %>
143
+ ServerLimit <%= node["httpd"]["max_clients"] %>
144
+ MaxClients <%= node["httpd"]["max_clients"] %>
145
+ MaxRequestsPerChild <%= node["httpd"]["max_request_per_child"] %>
146
+ </IfModule>
147
+ ```
148
+
149
+ ## 負荷試験ツール
150
+
151
+ コマンドラインで使えるものであれば、大体使うことができます。サンプルでは、[グリーン破壊](https://github.com/KLab/green-hakai/)を使わせて頂きました。
152
+
153
+ 負荷試験の厳しさによって個体が死滅することがあります。すると以下のように表示され、プログラムが終了します。
154
+
155
+ ***** EXTERMINATION ******
156
+
157
+ これは、個体の環境が厳しすぎるためで負荷試験を緩めて再度実施してください。
158
+
159
+ ## FAQ
160
+
161
+ ##### `gargor`はなんて読むの?
162
+
163
+ 「がるごる」
164
+
165
+ ## Contributing
166
+
167
+ 1. Fork it
168
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
169
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
170
+ 4. Push to the branch (`git push origin my-new-feature`)
171
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/gargor ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
3
+ require 'gargor'
4
+
5
+ dsl_file=ARGV.last||"gargor.rb"
6
+
7
+ Gargor.start
8
+ Gargor.load_dsl(dsl_file)
9
+ loop {
10
+ Gargor.populate.each { |i|
11
+ if i.fitness == nil
12
+ i.set_params
13
+ if i.deploy
14
+ i.attack
15
+ end
16
+ end
17
+ }
18
+ break unless Gargor.next_generation
19
+ }
20
+
21
+ best = Gargor.individuals.max { |a,b| a.fitness <=> b.fitness }
22
+ if best
23
+ best.set_params
24
+ best.deploy
25
+ end
26
+ p best
data/gargor.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'gargor/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "gargor"
8
+ spec.version = Gargor::VERSION
9
+ spec.authors = ["Yoshihiro TAKAHARA"]
10
+ spec.email = ["y.takahara@gmail.com"]
11
+ spec.description = %q{auto server tunner by generic algorithm}
12
+ spec.summary = %q{auto server tunner by generic algorithm using chef}
13
+ spec.homepage = "https://github.com/tumf/gargor"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ end
data/lib/gargor.rb ADDED
@@ -0,0 +1,154 @@
1
+ # -*- coding: utf-8 -*-
2
+ require "gargor/version"
3
+ require "gargor/individual"
4
+ require "gargor/parameter"
5
+
6
+ class Gargor
7
+ GLOBAL_OPTS = ["population","max_generations","target_nodes",
8
+ "attack_cmd","elite","mutation","target_cooking_cmd",
9
+ "attack_node","fitness_precision","attack_result"]
10
+
11
+ GLOBAL_OPTS.each { |name|
12
+ define_method(name) { |val|
13
+ Gargor.class_variable_set("@@#{name}", val)
14
+ }
15
+ }
16
+
17
+ class << self
18
+ def start
19
+ @@fitness_precision = 100000000
20
+ @@prev_generation = nil
21
+ @@individuals = []
22
+ @@param_procs = {}
23
+ @@population = 10
24
+ @@max_generations = 1
25
+ @@generation = 1
26
+ @@elite = 0
27
+ @@attack_cmd = "false"
28
+ @@attack_proc = nil
29
+ @@evaluate_proc = nil
30
+ end
31
+
32
+ def load_dsl(params_file)
33
+ contents = File.open(params_file, 'rb'){ |f| f.read }
34
+ new.instance_eval(contents)
35
+ end
36
+
37
+ def mutation
38
+ individual = Individual.new
39
+ @@param_procs.each { |name,proc|
40
+ param = Parameter.new(name)
41
+ param.instance_eval(&proc)
42
+ individual.params[name] = param
43
+ }
44
+ individual
45
+ end
46
+
47
+ # 浮動小数点対応のrand
48
+ def float_rand(f,p = @@fitness_precision)
49
+ f *= @@fitness_precision
50
+ i = f.to_i
51
+ f = rand(i)
52
+ f / @@fitness_precision.to_f
53
+ end
54
+
55
+ def crossover a,b
56
+ return a.clone if a.params == b.params
57
+ puts "crossover: #{a} #{b}"
58
+ total = a.fitness + b.fitness
59
+ c = Individual.new
60
+ c.params = a.params.clone
61
+
62
+ c.params.each { |name,param|
63
+ cur = float_rand(total)
64
+ c.params[name] = b.params[name] if b.fitness > cur
65
+ }
66
+ puts "#{a.to_s} + #{b.to_s} \n => #{c.to_s}"
67
+ c
68
+ end
69
+
70
+ def selection g
71
+ total = g.inject(0) { |sum,i| sum += i.fitness }
72
+ cur = float_rand(total)
73
+ g.each { |i|
74
+ return i if i.fitness > cur
75
+ cur -= i.fitness
76
+ }
77
+ raise "error selection"
78
+ end
79
+
80
+ def populate
81
+ @@individuals = []
82
+
83
+ # 第一世代
84
+ unless @@prev_generation
85
+ @@individuals << mutation.load_now
86
+ loop{
87
+ break if @@individuals.length >= @@population
88
+ @@individuals << mutation
89
+ }
90
+ return @@individuals
91
+ end
92
+
93
+ # fitness > 0 適応している個体
94
+ prev_count = @@prev_generation.select { |i| i.fitness > 0 }.count
95
+
96
+ if prev_count < 2
97
+ raise "***** EXTERMINATION ******"
98
+ end
99
+
100
+ puts "population: #{@@prev_generation.length}"
101
+ @@individuals = @@prev_generation.sort{ |a,b| a.fitness<=>b.fitness }.last(@@elite) if @@elite > 0
102
+ loop{
103
+ break if @@individuals.length >= @@population
104
+ if rand <= @@mutation
105
+ i = mutation
106
+ puts "mutation #{i}"
107
+ else
108
+ a = selection @@prev_generation
109
+ b = selection @@prev_generation
110
+ i = crossover(a,b)
111
+ end
112
+
113
+ #同じのは追加しない
114
+ if i and !@@individuals.find { |ii| ii.params == i.params }
115
+ @@individuals << i
116
+ end
117
+ }
118
+ puts "poulate: #{@@individuals}"
119
+ @@individuals
120
+ end
121
+
122
+
123
+ def next_generation
124
+ puts "<== end generation #{@@generation}"
125
+ @@generation += 1
126
+ return false if @@generation > @@max_generations
127
+
128
+ puts "==> next generation #{@@generation}"
129
+ @@prev_generation = @@individuals
130
+ true
131
+ end
132
+
133
+ def individuals
134
+ @@individuals
135
+ end
136
+
137
+ def opt name
138
+ Gargor.class_variable_get("@@#{name}")
139
+ end
140
+ end
141
+
142
+ def param name,&block
143
+ @@param_procs[name] = block
144
+ end
145
+
146
+ def attack cmd,&block
147
+ @@attack_cmd = cmd
148
+ @@attack_proc = block
149
+ end
150
+
151
+ def evaluate &block
152
+ @@evaluate_proc = block
153
+ end
154
+ end
@@ -0,0 +1,71 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'json'
3
+ require 'jsonpath'
4
+ require 'benchmark'
5
+
6
+ class Gargor
7
+ class Individual
8
+ attr_accessor :params,:fitness
9
+ def initialize
10
+ @params = {}
11
+ @fitness = nil
12
+ end
13
+
14
+ def to_s
15
+ [@params,@fitness].to_s
16
+ end
17
+
18
+ def load_now
19
+ @params.each { |name,param|
20
+ json = File.open(param.file).read
21
+ @params[name].value = JsonPath.on(json,param.path).first
22
+ }
23
+ self
24
+ end
25
+
26
+ def set_params
27
+ puts "==> set params"
28
+ puts @params
29
+ @params.each { |name,param|
30
+ json = File.open(param.file).read
31
+ json = JSON.pretty_generate(JsonPath.for(json).gsub(param.path) { |v| param.value }.to_hash)
32
+ # 書き出し
33
+ File.open(param.file,"w") { |f| f.write(json) }
34
+ puts " write #{param.file}"
35
+ }
36
+ end
37
+
38
+ def deploy
39
+ ret = true
40
+ Gargor.opt("target_nodes").each { |node|
41
+ puts "==> deploy to #{node}"
42
+ cmd = Gargor.opt("target_cooking_cmd") % [node]
43
+ puts " #{cmd}"
44
+ r = system(cmd)
45
+ unless r
46
+ puts "deploy failed"
47
+ @fitness = 0
48
+ sleep 1
49
+ end
50
+ ret &= r
51
+ }
52
+ ret
53
+ end
54
+
55
+
56
+ def attack
57
+ ret = nil;out = nil
58
+ cmd = Gargor.opt('attack_cmd')
59
+ puts "==> attack"
60
+ puts "execute: #{cmd}"
61
+ tms = Benchmark.realtime do
62
+ out = `#{cmd}`
63
+ ret = $?
64
+ end
65
+
66
+ @fitness = Gargor.opt('evaluate_proc').call(ret.to_i,out,tms)
67
+ puts "fitness: #{@fitness}"
68
+ @fitness
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,24 @@
1
+ # -*- coding: utf-8 -*-
2
+ class Gargor
3
+ class Parameter
4
+ attr_accessor :file, :path, :value, :name
5
+ alias :json_file :file=
6
+ alias :json_path :path=
7
+ alias :mutation :value=
8
+ alias_method :eql, :==
9
+ def initialize name
10
+ @name = name
11
+ end
12
+ def to_s
13
+ @value.to_s
14
+ end
15
+
16
+ def ==(other)
17
+ name == other.name and
18
+ file == other.file and
19
+ path == other.path and
20
+ value == other.value
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,3 @@
1
+ class Gargor
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gargor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Yoshihiro TAKAHARA
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-06-16 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.3'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: auto server tunner by generic algorithm
47
+ email:
48
+ - y.takahara@gmail.com
49
+ executables:
50
+ - gargor
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - .gitignore
55
+ - Gemfile
56
+ - LICENSE.txt
57
+ - README.md
58
+ - Rakefile
59
+ - bin/gargor
60
+ - gargor.gemspec
61
+ - lib/gargor.rb
62
+ - lib/gargor/individual.rb
63
+ - lib/gargor/parameter.rb
64
+ - lib/gargor/version.rb
65
+ homepage: https://github.com/tumf/gargor
66
+ licenses:
67
+ - MIT
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ requirements: []
85
+ rubyforge_project:
86
+ rubygems_version: 1.8.25
87
+ signing_key:
88
+ specification_version: 3
89
+ summary: auto server tunner by generic algorithm using chef
90
+ test_files: []