gargor 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -1
- data/.travis.yml +11 -0
- data/Gemfile +19 -3
- data/README.md +9 -1
- data/README_ja.md +8 -0
- data/Rakefile +9 -0
- data/VERSION +1 -0
- data/bin/gargor +9 -4
- data/doc/howto.md +168 -0
- data/doc/sample.rb +90 -0
- data/lib/gargor.rb +138 -48
- data/lib/gargor/individual.rb +48 -22
- data/lib/gargor/parameter.rb +1 -0
- data/lib/gargor/version.rb +1 -1
- data/spec/fixtures/sample-1.rb +85 -0
- data/spec/gargor_individual_spec.rb +57 -0
- data/spec/gargor_spec.rb +183 -0
- data/spec/gargor_version_spec.rb +7 -0
- data/spec/helper.rb +30 -0
- metadata +17 -3
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/Gemfile
CHANGED
@@ -1,8 +1,24 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
# Specify your gem's dependencies in gargor.gemspec
|
4
|
-
gemspec
|
5
|
-
|
6
3
|
gem 'json'
|
7
4
|
gem 'jsonpath'
|
5
|
+
gem "progressbar", "~> 0.20.0"
|
6
|
+
gem "version", "~> 1.0.0"
|
7
|
+
|
8
|
+
group :development do
|
9
|
+
gem 'pry'
|
10
|
+
gem 'pry-debugger'
|
11
|
+
end
|
12
|
+
|
13
|
+
group :test, :development do
|
14
|
+
gem 'childlabor'
|
15
|
+
gem 'coveralls'
|
16
|
+
gem 'fakeweb'
|
17
|
+
gem 'rspec'
|
18
|
+
gem 'rspec-mocks'
|
19
|
+
gem 'simplecov'
|
20
|
+
end
|
21
|
+
|
22
|
+
# Specify your gem's dependencies in gargor.gemspec
|
23
|
+
gemspec
|
8
24
|
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# gargor
|
1
|
+
# gargor [![Build Status](https://travis-ci.org/tumf/gargor.png?branch=master)](https://travis-ci.org/tumf/gargor) [![Gem Version](https://badge.fury.io/rb/gargor.png)](http://badge.fury.io/rb/gargor) [![Code Climate](https://codeclimate.com/github/tumf/gargor.png)](https://codeclimate.com/github/tumf/gargor) [![Dependency Status](https://gemnasium.com/tumf/gargor.png)](https://gemnasium.com/tumf/gargor)
|
2
2
|
|
3
3
|
`gargor` is software which uses genetic algorithm to support parameter tuning of the servers controlled by Chef.Using this software, you are able to optimize and automate the server tuning, which you did until now based on a combination of my experience and intuition.
|
4
4
|
|
@@ -46,6 +46,14 @@ target_nodes ["www-1.example","www-2.example","db-1.example"]
|
|
46
46
|
# attack command
|
47
47
|
attack_cmd "ssh attacker.example ./bin/ghakai www-1.example.yml 2>/dev/null"
|
48
48
|
|
49
|
+
# logger
|
50
|
+
logger "gargor.log"
|
51
|
+
|
52
|
+
# or optional settings like blows:
|
53
|
+
#
|
54
|
+
# logger "gargor.log" do |log|
|
55
|
+
# log.level = Logger::INFO
|
56
|
+
# end
|
49
57
|
|
50
58
|
# evalute of the attack
|
51
59
|
# code: exit code of attack_cmd command (0 => succees)
|
data/README_ja.md
CHANGED
@@ -46,6 +46,14 @@ target_nodes ["www-1.example","www-2.example","db-1.example"]
|
|
46
46
|
# 攻撃コマンド
|
47
47
|
attack_cmd "ssh attacker.example ./bin/ghakai www-1.example.yml 2>/dev/null"
|
48
48
|
|
49
|
+
# ログ
|
50
|
+
logger "gargor.log"
|
51
|
+
|
52
|
+
# もしくは以下のようにしてLoggerの設定ができる
|
53
|
+
#
|
54
|
+
# logger "gargor.log" do |log|
|
55
|
+
# log.level = Logger::INFO
|
56
|
+
# end
|
49
57
|
|
50
58
|
# 攻撃結果の評価
|
51
59
|
# code: attack_cmdのプロセス終了コード(普通は0:成功)
|
data/Rakefile
CHANGED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.4
|
data/bin/gargor
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
3
|
+
$TESTING=false
|
3
4
|
require 'gargor'
|
5
|
+
require 'progressbar'
|
4
6
|
|
5
7
|
dsl_file=ARGV.last||"gargor.rb"
|
6
8
|
|
@@ -11,22 +13,25 @@ end
|
|
11
13
|
begin
|
12
14
|
Gargor.start
|
13
15
|
Gargor.load_dsl(dsl_file)
|
14
|
-
rescue
|
16
|
+
rescue =>e
|
17
|
+
STDERR.puts e.backtrace.join("\n")
|
18
|
+
|
15
19
|
usage
|
16
20
|
exit 1
|
17
21
|
end
|
18
22
|
|
23
|
+
pbar = ProgressBar.new("auto-tuning",Gargor.total_trials)
|
19
24
|
loop {
|
20
25
|
Gargor.populate.each { |i|
|
21
26
|
if i.fitness == nil
|
22
27
|
i.set_params
|
23
|
-
if i.deploy
|
24
|
-
i.attack
|
25
|
-
end
|
28
|
+
i.attack if i.deploy
|
26
29
|
end
|
30
|
+
pbar.set(Gargor.total_trials-Gargor.last_trials)
|
27
31
|
}
|
28
32
|
break unless Gargor.next_generation
|
29
33
|
}
|
34
|
+
pbar.finish
|
30
35
|
|
31
36
|
best = Gargor.individuals.max { |a,b| a.fitness <=> b.fitness }
|
32
37
|
if best
|
data/doc/howto.md
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
Auto-tuning internet servers by Genetic Algorithm
|
2
|
+
====================================================
|
3
|
+
|
4
|
+
You can get good settings during sleeping ;)
|
5
|
+
|
6
|
+
First it is necessary to prepare to the point where an ordinary stress test is possible. Please prepare the server to be used for attacking in a location that is close (in terms of network) to the target. Suitable specifications are required for the Attacker as well.
|
7
|
+
|
8
|
+
Anything is acceptable as a stress tool providing that it can be used from a command line. Although `ab(Apache Bench)` etc. have been included from the beginning and therefore would be simple, this time, a software called [green-hakai](https://github.com/KLab/green-hakai) was used. (The installation instructions for `green-hakai` are available from the product’s website)
|
9
|
+
|
10
|
+
When performing automatic tuning, the target (= sever group of the tuning target) is controlled by Chef and it is necessary that the parameters become attributes of Chef. For example, when the template `httpd.conf.erb` of `httpd.conf` becomes as shown:
|
11
|
+
|
12
|
+
```
|
13
|
+
<IfModule prefork.c>
|
14
|
+
StartServers 8
|
15
|
+
MinSpareServers 10
|
16
|
+
MaxSpareServers 20
|
17
|
+
ServerLimit 255
|
18
|
+
MaxClients 255
|
19
|
+
MaxRequestsPerChild 1000
|
20
|
+
</IfModule>
|
21
|
+
```
|
22
|
+
|
23
|
+
If these `MaxClients` are to be made attribute of Chef, do it as follows:
|
24
|
+
|
25
|
+
```
|
26
|
+
<IfModule prefork.c>
|
27
|
+
StartServers 8
|
28
|
+
MinSpareServers 10
|
29
|
+
MaxSpareServers 20
|
30
|
+
ServerLimit 255
|
31
|
+
MaxClients <%= node["httpd"]["max_clients"] %>
|
32
|
+
MaxRequestsPerChild 1000
|
33
|
+
</IfModule>
|
34
|
+
```
|
35
|
+
|
36
|
+
When in this state, by writing as shown below in `nodes/www-1.example.json`, it is possible to control `MaxClients` of the `www-1.example` server.
|
37
|
+
|
38
|
+
```
|
39
|
+
{
|
40
|
+
"httpd" :{
|
41
|
+
"max_clients" :255
|
42
|
+
},
|
43
|
+
...
|
44
|
+
}
|
45
|
+
```
|
46
|
+
|
47
|
+
As a result of rewriting this JSON, deploying it to the target by using Chef, and then applying the stress test, it will be `gargor` (the new software) that searches the new test parameter using GA.
|
48
|
+
|
49
|
+
Install `gargor` by doing the following: (Ruby 1.9.3 or higher is required)
|
50
|
+
|
51
|
+
```
|
52
|
+
[sudo] gem install gargor
|
53
|
+
```
|
54
|
+
|
55
|
+
Position `gargor.rb` under the Chef repository. Rewrite the contents as necessary.
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
# generations: set > 1
|
59
|
+
max_generations 10
|
60
|
+
|
61
|
+
# individuals number of some generation.
|
62
|
+
population 10
|
63
|
+
|
64
|
+
# elite number of some generation.(carried over)
|
65
|
+
elite 1
|
66
|
+
|
67
|
+
# Probability of mutation set "0.01" to "1%" (when crossover)
|
68
|
+
mutation 0.01
|
69
|
+
|
70
|
+
# target cook command : '%s' will replace by node name.
|
71
|
+
target_cooking_cmd "knife solo cook %s"
|
72
|
+
|
73
|
+
# target nodes
|
74
|
+
# performing target_cooking_command before the attack.
|
75
|
+
target_nodes ["www-1.example","www-2.example","db-1.example"]
|
76
|
+
|
77
|
+
# attack command
|
78
|
+
attack_cmd "ssh attacker.example ./bin/ghakai www-1.example.yml 2>/dev/null"
|
79
|
+
|
80
|
+
# logger
|
81
|
+
logger "gargor.log"
|
82
|
+
|
83
|
+
# or optional settings like blows:
|
84
|
+
#
|
85
|
+
# logger "gargor.log" do |log|
|
86
|
+
# log.level = Logger::INFO
|
87
|
+
# end
|
88
|
+
|
89
|
+
|
90
|
+
# evalute of the attack
|
91
|
+
# code: exit code of attack_cmd command (0 => succees)
|
92
|
+
# out: standard output of attack_cmd command
|
93
|
+
# time: execute time of attack_cmd
|
94
|
+
evaluate do |code,out,time|
|
95
|
+
puts out
|
96
|
+
fitness = 0
|
97
|
+
# get "FAILED" count from stadard output of stress-tool,
|
98
|
+
# and set fitess to 0 when FAILED > 0.
|
99
|
+
if time > 0 && code == 0 && /^FAILED (\d+)/ =~ out && $1 == "0"
|
100
|
+
# get fitness from stadard output of stress-tool.
|
101
|
+
# e.g.: request count:200, concurrenry:20, 45.060816 req/s
|
102
|
+
if /, ([\.\d]+) req\/s/ =~ out
|
103
|
+
fitness = $1.to_f
|
104
|
+
end
|
105
|
+
# To get fitness simply,to use execution time
|
106
|
+
# fitness = 1/time
|
107
|
+
end
|
108
|
+
# This block must return the fitness.(integer or float)
|
109
|
+
fitness
|
110
|
+
end
|
111
|
+
|
112
|
+
# definition of parameters(GA)
|
113
|
+
#
|
114
|
+
# param _name_ do
|
115
|
+
# json_file: Chef parameter(JSON) file (such as nodes/*.json or roles/*.json)
|
116
|
+
# (Warning!) gargor will overwrite their json files.
|
117
|
+
# json_path: to locate the value by JSONPath
|
118
|
+
# mutaion: to set value when mutaion
|
119
|
+
param "max_clients" do
|
120
|
+
json_file "roles/www.json"
|
121
|
+
json_path '$.httpd.max_clients'
|
122
|
+
mutation rand(500)+10
|
123
|
+
end
|
124
|
+
|
125
|
+
param "innodb_log_file_size" do
|
126
|
+
json_file "nodes/db-1.example.json"
|
127
|
+
json_path '$.mysqld.innodb_log_file_size'
|
128
|
+
mutation rand(200)
|
129
|
+
end
|
130
|
+
|
131
|
+
param "sort_buffer_size" do
|
132
|
+
json_file "nodes/db-1.example.json"
|
133
|
+
json_path '$.mysqld.sort_buffer_size'
|
134
|
+
mutation rand(1000)
|
135
|
+
end
|
136
|
+
|
137
|
+
param "read_buffer_size" do
|
138
|
+
json_file "nodes/db-1.example.json"
|
139
|
+
json_path '$.mysqld.read_buffer_size'
|
140
|
+
mutation rand(1000)
|
141
|
+
end
|
142
|
+
|
143
|
+
param "query_cache_size" do
|
144
|
+
json_file "nodes/db-1.example.json"
|
145
|
+
json_path '$.mysqld.query_cache_size'
|
146
|
+
mutation rand(100)
|
147
|
+
end
|
148
|
+
```
|
149
|
+
|
150
|
+
|
151
|
+
When using something other than `green-hakai`, it will be necessary to customize `evaluate` block.
|
152
|
+
|
153
|
+
|
154
|
+
```
|
155
|
+
# fitness = 1/time
|
156
|
+
```
|
157
|
+
|
158
|
+
However, by removing the comment out of this part, it is possible to decide a rough score by the (inverse number of) the measurement time.
|
159
|
+
|
160
|
+
`gargor` performs the stress test for the number of (number of generations * number of individuals) at the maximum and searches for parameters that appear good. As it takes a long time, it is probably best to run when finishing work for the day in order to confirm the results the next morning.
|
161
|
+
|
162
|
+
Of course, it is possible to perform multiple settings for parameters.
|
163
|
+
|
164
|
+
As GA is used, there are erratic elements, and unless the software is tried, it is unknown whether good results will be output. If the number of individuals and the number of generations are increased, it will take longer but the precision will be improved. Also, because the precision also changes depending on how `mutation` is written, the user is encouraged to try many things.
|
165
|
+
|
166
|
+
As `gargor` is only just-created software, please report bugs and fixes to `github`. Developer is waiting for pull-request.
|
167
|
+
|
168
|
+
> https://github.com/tumf/gargor
|
data/doc/sample.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# Gargor Sample DSL file
|
3
|
+
|
4
|
+
# 世代数: 1以上を指定してください
|
5
|
+
max_generations 10
|
6
|
+
|
7
|
+
# 個体数: 1世代あたりの個体数
|
8
|
+
population 10
|
9
|
+
|
10
|
+
# エリート: 世代交代の時に適応値の高い個体をそのまま次世代に引き継ぐ数
|
11
|
+
elite 1
|
12
|
+
|
13
|
+
# 突然変異の確率: "0.01"で"1%"
|
14
|
+
mutation 0.01
|
15
|
+
|
16
|
+
# ターゲットをChefで料理するコマンド %sには、ノード名が入る
|
17
|
+
target_cooking_cmd "knife solo cook %s"
|
18
|
+
|
19
|
+
# ターゲットのノード
|
20
|
+
# 攻撃前に以下のノードすべてに対してtarget_cooking_cmdが実施される
|
21
|
+
target_nodes ["www-1.example","www-2.example","db-1.example"]
|
22
|
+
|
23
|
+
# 攻撃コマンド
|
24
|
+
attack_cmd "ssh attacker.example ./bin/ghakai www-1.example.yml 2>/dev/null"
|
25
|
+
|
26
|
+
|
27
|
+
# 攻撃結果の評価
|
28
|
+
# code: attack_cmdのプロセス終了コード(普通は0:成功)
|
29
|
+
# out: attack_cmdの標準出力
|
30
|
+
# time: attack_cmdの実行時間
|
31
|
+
evaluate do |code,out,time|
|
32
|
+
puts out
|
33
|
+
fitness = 0
|
34
|
+
|
35
|
+
# 攻撃コマンドで使っている、グリーン破壊の標準出力から
|
36
|
+
# FAILEDの値を抜き出し0以外は適応値0としている
|
37
|
+
if time > 0 && code == 0 && /^FAILED (\d+)/ =~ out && $1 == "0"
|
38
|
+
# 攻撃コマンドで使っている、グリーン破壊の標準出力から
|
39
|
+
# 適応値に代用できそうなものを正規表現で取得する
|
40
|
+
# request count:200, concurrenry:20, 45.060816 req/s
|
41
|
+
if /, ([\.\d]+) req\/s/ =~ out
|
42
|
+
fitness = $1.to_f
|
43
|
+
end
|
44
|
+
# 単純に実行時間で適応値を設定したいなら以下のようにしても良い
|
45
|
+
# fitness = 1/time
|
46
|
+
end
|
47
|
+
# このブロックは必ず適応値を返すこと(整数or浮動小数)
|
48
|
+
fitness
|
49
|
+
end
|
50
|
+
|
51
|
+
# パラメータ定義
|
52
|
+
# GAにより変動されるパラメータをここで定義する
|
53
|
+
#
|
54
|
+
# param 名前 do
|
55
|
+
# json_file: 値を上書くJSONファイル nodes/*.jsonやroles/*.json
|
56
|
+
# (注意!) gargorはこのjsonファイルを容赦なく書き換える
|
57
|
+
# json_path: json_fileの中から書変える値の場所をJSONPath形式で指定する
|
58
|
+
# mutaion: 突然変異時の値の設定
|
59
|
+
param "max_clients" do
|
60
|
+
json_file "roles/www.json"
|
61
|
+
json_path '$.httpd.max_clients'
|
62
|
+
mutation rand(500)+10
|
63
|
+
end
|
64
|
+
|
65
|
+
param "innodb_log_file_size" do
|
66
|
+
json_file "nodes/db-1.example.json"
|
67
|
+
json_path '$.mysqld.innodb_log_file_size'
|
68
|
+
mutation rand(200)
|
69
|
+
end
|
70
|
+
|
71
|
+
param "sort_buffer_size" do
|
72
|
+
json_file "nodes/db-1.example.json"
|
73
|
+
json_path '$.mysqld.sort_buffer_size'
|
74
|
+
mutation rand(1000)
|
75
|
+
end
|
76
|
+
|
77
|
+
param "read_buffer_size" do
|
78
|
+
json_file "nodes/db-1.example.json"
|
79
|
+
json_path '$.mysqld.read_buffer_size'
|
80
|
+
mutation rand(1000)
|
81
|
+
end
|
82
|
+
|
83
|
+
param "query_cache_size" do
|
84
|
+
json_file "nodes/db-1.example.json"
|
85
|
+
json_path '$.mysqld.query_cache_size'
|
86
|
+
mutation rand(100)
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
|
data/lib/gargor.rb
CHANGED
@@ -1,13 +1,27 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
require 'rubygems' if RUBY_VERSION < '1.9'
|
3
|
+
require "logger"
|
4
|
+
|
3
5
|
require "gargor/version"
|
4
6
|
require "gargor/individual"
|
5
7
|
require "gargor/parameter"
|
6
8
|
|
7
9
|
class Gargor
|
10
|
+
class GargorError < RuntimeError; end
|
11
|
+
class ExterminationError < GargorError; end
|
12
|
+
class ParameterError < GargorError; end
|
13
|
+
class ValidationError < GargorError; end
|
14
|
+
class ArgumentError < GargorError; end
|
15
|
+
|
16
|
+
class Individuals < Array
|
17
|
+
def has? i
|
18
|
+
!!self.find { |ii| ii.params == i.params }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
8
22
|
GLOBAL_OPTS = ["population","max_generations","target_nodes",
|
9
23
|
"attack_cmd","elite","mutation","target_cooking_cmd",
|
10
|
-
"
|
24
|
+
"fitness_precision"]
|
11
25
|
|
12
26
|
GLOBAL_OPTS.each { |name|
|
13
27
|
define_method(name) { |val|
|
@@ -15,8 +29,29 @@ class Gargor
|
|
15
29
|
}
|
16
30
|
}
|
17
31
|
|
32
|
+
def log message,level=Logger::INFO
|
33
|
+
Gargor.log(message,level)
|
34
|
+
end
|
18
35
|
class << self
|
36
|
+
def log message,level=Logger::INFO
|
37
|
+
return if $TESTING
|
38
|
+
message.to_s.split("\n").each { |line| @@logger.add(level) {line} }
|
39
|
+
end
|
40
|
+
|
41
|
+
def debug message
|
42
|
+
log message,Logger::DEBUG
|
43
|
+
end
|
44
|
+
|
45
|
+
def params
|
46
|
+
result = {}
|
47
|
+
GLOBAL_OPTS.map { |name|
|
48
|
+
result[name] = Gargor.class_variable_get("@@#{name}")
|
49
|
+
}
|
50
|
+
result
|
51
|
+
end
|
52
|
+
|
19
53
|
def start
|
54
|
+
@@logger = Logger.new(STDOUT)
|
20
55
|
@@fitness_precision = 100000000
|
21
56
|
@@prev_generation = nil
|
22
57
|
@@individuals = []
|
@@ -29,26 +64,47 @@ class Gargor
|
|
29
64
|
@@attack_proc = nil
|
30
65
|
@@evaluate_proc = Proc.new { 0 }
|
31
66
|
@@target_nodes = []
|
67
|
+
@@dsl_file = nil
|
68
|
+
true
|
69
|
+
end
|
70
|
+
Gargor.start
|
71
|
+
|
72
|
+
def validate
|
73
|
+
raise ValidationError,"POPULATION isn't > 0" unless @@population > 0
|
74
|
+
true
|
75
|
+
end
|
76
|
+
|
77
|
+
def first_generation?
|
78
|
+
@@generation == 1
|
79
|
+
end
|
80
|
+
|
81
|
+
# 前世代の数
|
82
|
+
def prev_count g = @@prev_generation
|
83
|
+
# fitness > 0 適応している個体
|
84
|
+
g.select { |i| i.fitness && i.fitness > 0 }.count
|
32
85
|
end
|
33
86
|
|
34
87
|
def load_dsl(params_file)
|
35
|
-
|
88
|
+
@@dsl_file = params_file
|
89
|
+
contents = File.read(params_file)
|
36
90
|
new.instance_eval(contents)
|
37
|
-
|
91
|
+
validate
|
38
92
|
end
|
39
93
|
|
40
|
-
def
|
94
|
+
def mutate
|
41
95
|
individual = Individual.new
|
42
96
|
@@param_procs.each { |name,proc|
|
43
97
|
param = Parameter.new(name)
|
44
98
|
param.instance_eval(&proc)
|
45
99
|
individual.params[name] = param
|
46
100
|
}
|
101
|
+
log "mutate #{individual}"
|
47
102
|
individual
|
48
103
|
end
|
49
104
|
|
50
105
|
# 浮動小数点対応のrand
|
51
106
|
def float_rand(f,p = @@fitness_precision)
|
107
|
+
raise ArgumentError,"max must be > 0" unless f > 0
|
52
108
|
f *= @@fitness_precision
|
53
109
|
i = f.to_i
|
54
110
|
f = rand(i)
|
@@ -57,17 +113,11 @@ class Gargor
|
|
57
113
|
|
58
114
|
def crossover a,b
|
59
115
|
return a.clone if a.params == b.params
|
60
|
-
|
116
|
+
log "crossover: #{a} #{b}"
|
61
117
|
total = a.fitness + b.fitness
|
62
118
|
c = Individual.new
|
63
119
|
c.params = a.params.clone
|
64
|
-
|
65
|
-
c.params.each { |name,param|
|
66
|
-
cur = float_rand(total)
|
67
|
-
c.params[name] = b.params[name] if b.fitness > cur
|
68
|
-
}
|
69
|
-
puts "#{a.to_s} + #{b.to_s} \n => #{c.to_s}"
|
70
|
-
c
|
120
|
+
c.overwrite_by(b,b.fitness,total)
|
71
121
|
end
|
72
122
|
|
73
123
|
def selection g
|
@@ -77,58 +127,73 @@ class Gargor
|
|
77
127
|
return i if i.fitness > cur
|
78
128
|
cur -= i.fitness
|
79
129
|
}
|
80
|
-
raise "error selection"
|
130
|
+
raise GargorError,"error selection"
|
81
131
|
end
|
82
132
|
|
83
|
-
def
|
84
|
-
|
133
|
+
def populate_first_generation
|
134
|
+
individuals = Gargor::Individuals.new
|
135
|
+
individuals << mutate.load_now
|
136
|
+
loop{
|
137
|
+
break if individuals.length >= @@population
|
138
|
+
individuals << mutate
|
139
|
+
}
|
85
140
|
|
86
|
-
|
87
|
-
|
88
|
-
@@individuals << mutation.load_now
|
89
|
-
loop{
|
90
|
-
break if @@individuals.length >= @@population
|
91
|
-
@@individuals << mutation
|
92
|
-
}
|
93
|
-
return @@individuals
|
94
|
-
end
|
141
|
+
Gargor::Individuals.new(individuals.shuffle)
|
142
|
+
end
|
95
143
|
|
96
|
-
|
97
|
-
|
144
|
+
def select_elites g,count
|
145
|
+
return [] unless count > 0
|
146
|
+
Gargor::Individuals.new(g.sort{ |a,b| a.fitness<=>b.fitness }.last(count))
|
147
|
+
end
|
98
148
|
|
99
|
-
|
100
|
-
|
149
|
+
def mutation? mutation=@@mutation
|
150
|
+
rand <= mutation
|
151
|
+
end
|
152
|
+
|
153
|
+
def select_parents g
|
154
|
+
[selection(g),selection(g)]
|
155
|
+
end
|
156
|
+
|
157
|
+
def populate_one
|
158
|
+
if mutation?
|
159
|
+
mutate
|
160
|
+
else
|
161
|
+
crossover(*select_parents(@@prev_generation))
|
101
162
|
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def populate_next_generation
|
166
|
+
log "population: #{@@prev_generation.length}"
|
167
|
+
individuals = Gargor::Individuals.new(select_elites @@prev_generation,@@elite)
|
102
168
|
|
103
|
-
puts "population: #{@@prev_generation.length}"
|
104
|
-
@@individuals = @@prev_generation.sort{ |a,b| a.fitness<=>b.fitness }.last(@@elite) if @@elite > 0
|
105
169
|
loop{
|
106
|
-
break if
|
107
|
-
|
108
|
-
|
109
|
-
puts "mutation #{i}"
|
110
|
-
else
|
111
|
-
a = selection @@prev_generation
|
112
|
-
b = selection @@prev_generation
|
113
|
-
i = crossover(a,b)
|
114
|
-
end
|
115
|
-
|
116
|
-
#同じのは追加しない
|
117
|
-
if i and !@@individuals.find { |ii| ii.params == i.params }
|
118
|
-
@@individuals << i
|
119
|
-
end
|
170
|
+
break if individuals.length >= @@population
|
171
|
+
i = populate_one
|
172
|
+
individuals << i unless individuals.has?(i)
|
120
173
|
}
|
121
|
-
|
122
|
-
|
174
|
+
log "populate:"
|
175
|
+
individuals.each { |i| log i }
|
176
|
+
Gargor::Individuals.new(individuals.shuffle)
|
177
|
+
end
|
178
|
+
|
179
|
+
def populate
|
180
|
+
@@individuals = if first_generation?
|
181
|
+
# 第一世代
|
182
|
+
populate_first_generation
|
183
|
+
else
|
184
|
+
# 次世代
|
185
|
+
raise ExterminationError unless prev_count >= 2
|
186
|
+
populate_next_generation
|
187
|
+
end
|
123
188
|
end
|
124
189
|
|
125
190
|
|
126
191
|
def next_generation
|
127
|
-
|
192
|
+
log "<== end generation #{@@generation}"
|
128
193
|
@@generation += 1
|
129
194
|
return false if @@generation > @@max_generations
|
130
195
|
|
131
|
-
|
196
|
+
log "==> next generation #{@@generation}"
|
132
197
|
@@prev_generation = @@individuals
|
133
198
|
true
|
134
199
|
end
|
@@ -140,6 +205,24 @@ class Gargor
|
|
140
205
|
def opt name
|
141
206
|
Gargor.class_variable_get("@@#{name}")
|
142
207
|
end
|
208
|
+
|
209
|
+
def logfile file
|
210
|
+
File.expand_path(File.join(File.dirname(@@dsl_file),file))
|
211
|
+
end
|
212
|
+
|
213
|
+
def total_trials
|
214
|
+
@@population+(@@population-@@elite)*(@@max_generations-1)
|
215
|
+
end
|
216
|
+
|
217
|
+
def last_trials_at_this_generation
|
218
|
+
@@individuals.select{ |i| i.fitness == nil }.count
|
219
|
+
end
|
220
|
+
|
221
|
+
def last_trials
|
222
|
+
last_trials_at_this_generation +
|
223
|
+
(@@max_generations-@@generation)*(@@population-@@elite)
|
224
|
+
end
|
225
|
+
|
143
226
|
end
|
144
227
|
|
145
228
|
def param name,&block
|
@@ -154,4 +237,11 @@ class Gargor
|
|
154
237
|
def evaluate &block
|
155
238
|
@@evaluate_proc = block
|
156
239
|
end
|
240
|
+
|
241
|
+
def logger *args, &block
|
242
|
+
file = args.shift
|
243
|
+
@@logger = Logger.new(Gargor.logfile(file),*args)
|
244
|
+
block.call(@@logger) if block
|
245
|
+
end
|
246
|
+
|
157
247
|
end
|
data/lib/gargor/individual.rb
CHANGED
@@ -15,57 +15,83 @@ class Gargor
|
|
15
15
|
[@params,@fitness].to_s
|
16
16
|
end
|
17
17
|
|
18
|
+
def log message,level = Logger::INFO
|
19
|
+
Gargor.log message,level
|
20
|
+
end
|
21
|
+
|
18
22
|
def load_now
|
23
|
+
log "==> load current json"
|
19
24
|
@params.each { |name,param|
|
20
|
-
json = File.
|
25
|
+
json = File.read(param.file)
|
21
26
|
@params[name].value = JsonPath.on(json,param.path).first
|
22
27
|
}
|
23
28
|
self
|
24
29
|
end
|
25
30
|
|
31
|
+
def set_param param,json
|
32
|
+
JSON.pretty_generate(JsonPath.for(json).gsub(param.path) { |v| param.value }.to_hash)
|
33
|
+
end
|
34
|
+
|
26
35
|
def set_params
|
27
|
-
|
28
|
-
|
36
|
+
log "==> set params"
|
37
|
+
jsons = {}
|
29
38
|
@params.each { |name,param|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
39
|
+
unless jsons.has_key?(param.file)
|
40
|
+
log "load #{param.file}"
|
41
|
+
jsons[param.file] = File.read(param.file)
|
42
|
+
end
|
43
|
+
jsons[param.file] = set_param(param,jsons[param.file])
|
44
|
+
log " #{name}: #{param}"
|
45
|
+
}
|
46
|
+
jsons.each { |file,json|
|
47
|
+
File.open(file,"w") { |f| f.write(json) }
|
48
|
+
log " write #{file}"
|
35
49
|
}
|
36
50
|
end
|
37
51
|
|
38
52
|
def deploy
|
39
|
-
ret = true
|
40
53
|
Gargor.opt("target_nodes").each { |node|
|
41
|
-
|
54
|
+
log "==> deploy to #{node}"
|
42
55
|
cmd = Gargor.opt("target_cooking_cmd") % [node]
|
43
|
-
|
44
|
-
r =
|
45
|
-
unless r
|
46
|
-
|
56
|
+
log " #{cmd}"
|
57
|
+
out,r = shell(cmd)
|
58
|
+
unless r == 0
|
59
|
+
log "deploy failed",Logger::ERROR
|
47
60
|
@fitness = 0
|
48
61
|
sleep 1
|
62
|
+
return false
|
49
63
|
end
|
50
|
-
ret &= r
|
51
64
|
}
|
52
|
-
|
65
|
+
true
|
53
66
|
end
|
54
67
|
|
68
|
+
def shell command
|
69
|
+
out = `#{command}`
|
70
|
+
ret = $?
|
71
|
+
[out,ret.exitstatus]
|
72
|
+
end
|
55
73
|
|
56
74
|
def attack
|
57
75
|
ret = nil;out = nil
|
58
76
|
cmd = Gargor.opt('attack_cmd')
|
59
|
-
|
60
|
-
|
77
|
+
log "==> attack"
|
78
|
+
log "execute: #{cmd}"
|
61
79
|
tms = Benchmark.realtime do
|
62
|
-
out =
|
63
|
-
ret = $?
|
80
|
+
out,ret = shell(cmd)
|
64
81
|
end
|
65
82
|
|
66
|
-
@fitness = Gargor.opt('evaluate_proc').call(ret
|
67
|
-
|
83
|
+
@fitness = Gargor.opt('evaluate_proc').call(ret,out,tms)
|
84
|
+
log "fitness: #{@fitness}"
|
68
85
|
@fitness
|
69
86
|
end
|
87
|
+
|
88
|
+
# a/b の確率でiを上書きする
|
89
|
+
def overwrite_by i,a,b
|
90
|
+
@params.each { |name,param|
|
91
|
+
@params[name] = i.params[name] if a > Gargor.float_rand(b)
|
92
|
+
}
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
70
96
|
end
|
71
97
|
end
|
data/lib/gargor/parameter.rb
CHANGED
data/lib/gargor/version.rb
CHANGED
@@ -0,0 +1,85 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# generations: set > 1
|
3
|
+
max_generations 10
|
4
|
+
|
5
|
+
# individuals number of some generation.
|
6
|
+
population 10
|
7
|
+
|
8
|
+
# elite number of some generation.(carried over)
|
9
|
+
elite 1
|
10
|
+
|
11
|
+
# Probability of mutation set "0.01" to "1%" (when crossover)
|
12
|
+
mutation 0.01
|
13
|
+
|
14
|
+
# target cook command : '%s' will replace by node name.
|
15
|
+
target_cooking_cmd "knife solo cook %s"
|
16
|
+
|
17
|
+
# target nodes
|
18
|
+
# performing target_cooking_command before the attack.
|
19
|
+
target_nodes ["www-1.example","www-2.example","db-1.example"]
|
20
|
+
|
21
|
+
# attack command
|
22
|
+
attack_cmd "ssh attacker.example ./bin/ghakai www-1.example.yml 2>/dev/null"
|
23
|
+
|
24
|
+
# logging
|
25
|
+
logger("gargor.log") do |logger|
|
26
|
+
logger.level = Logger::INFO
|
27
|
+
end
|
28
|
+
|
29
|
+
# evalute of the attack
|
30
|
+
# code: exit code of attack_cmd command (0 => succees)
|
31
|
+
# out: standard output of attack_cmd command
|
32
|
+
# time: execute time of attack_cmd
|
33
|
+
evaluate do |code,out,time|
|
34
|
+
fitness = 0
|
35
|
+
# get "FAILED" count from stadard output of stress-tool,
|
36
|
+
# and set fitess to 0 when FAILED > 0.
|
37
|
+
if time > 0 && code == 0 && /^FAILED (\d+)/ =~ out && $1 == "0"
|
38
|
+
# get fitness from stadard output of stress-tool.
|
39
|
+
# e.g.: request count:200, concurrenry:20, 45.060816 req/s
|
40
|
+
if /, ([\.\d]+) req\/s/ =~ out
|
41
|
+
fitness = $1.to_f
|
42
|
+
end
|
43
|
+
# To get fitness simply,to use execution time
|
44
|
+
# fitness = 1/time
|
45
|
+
end
|
46
|
+
# This block must return the fitness.(integer or float)
|
47
|
+
fitness
|
48
|
+
end
|
49
|
+
|
50
|
+
# definition of parameters(GA)
|
51
|
+
#
|
52
|
+
# param _name_ do
|
53
|
+
# json_file: Chef parameter(JSON) file (such as nodes/*.json or roles/*.json)
|
54
|
+
# (Warning!) gargor will overwrite their json files.
|
55
|
+
# json_path: to locate the value by JSONPath
|
56
|
+
# mutaion: to set value when mutaion
|
57
|
+
param "max_clients" do
|
58
|
+
json_file "roles/www.json"
|
59
|
+
json_path '$.httpd.max_clients'
|
60
|
+
mutation rand(500)+10
|
61
|
+
end
|
62
|
+
|
63
|
+
param "innodb_log_file_size" do
|
64
|
+
json_file "nodes/db-1.example.json"
|
65
|
+
json_path '$.mysqld.innodb_log_file_size'
|
66
|
+
mutation rand(200)
|
67
|
+
end
|
68
|
+
|
69
|
+
param "sort_buffer_size" do
|
70
|
+
json_file "nodes/db-1.example.json"
|
71
|
+
json_path '$.mysqld.sort_buffer_size'
|
72
|
+
mutation rand(1000)
|
73
|
+
end
|
74
|
+
|
75
|
+
param "read_buffer_size" do
|
76
|
+
json_file "nodes/db-1.example.json"
|
77
|
+
json_path '$.mysqld.read_buffer_size'
|
78
|
+
mutation rand(1000)
|
79
|
+
end
|
80
|
+
|
81
|
+
param "query_cache_size" do
|
82
|
+
json_file "nodes/db-1.example.json"
|
83
|
+
json_path '$.mysqld.query_cache_size'
|
84
|
+
mutation rand(100)
|
85
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe Gargor::Individual,"#load_now" do
|
4
|
+
it "returns self" do
|
5
|
+
expect(Gargor::Individual.new.load_now).to be_kind_of Gargor::Individual
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
describe Gargor::Individual, "#set_params" do
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
describe Gargor::Individual, "#deploy" do
|
14
|
+
before do
|
15
|
+
to_load_fixture "sample-1.rb"
|
16
|
+
Gargor.start
|
17
|
+
Gargor.load_dsl("dummy")
|
18
|
+
|
19
|
+
@i = Gargor.mutate
|
20
|
+
end
|
21
|
+
|
22
|
+
it "build collect command-line" do
|
23
|
+
@i.stub(:shell) { |cmd|
|
24
|
+
expect(cmd).to match /knife solo cook (www-1|www-2|db-1).example/
|
25
|
+
["",0]
|
26
|
+
}
|
27
|
+
expect(@i.deploy).to be true
|
28
|
+
end
|
29
|
+
|
30
|
+
it "returns false if deploy failed" do
|
31
|
+
@i.stub(:shell) { |cmd|
|
32
|
+
["",255]
|
33
|
+
}
|
34
|
+
expect(@i.deploy).to be false
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe Gargor::Individual, "attack" do
|
40
|
+
it "build collect attack command" do
|
41
|
+
to_load_fixture "sample-1.rb"
|
42
|
+
Gargor.start
|
43
|
+
Gargor.load_dsl("dummy")
|
44
|
+
i = Gargor.mutate
|
45
|
+
i.stub(:shell) { |cmd|
|
46
|
+
expect(cmd).to eq Gargor.opt("attack_cmd")
|
47
|
+
out=<<EOF
|
48
|
+
abc, 3300 req/s
|
49
|
+
FAILED 0
|
50
|
+
EOF
|
51
|
+
[out,0]
|
52
|
+
}
|
53
|
+
i.attack
|
54
|
+
expect(i.fitness).to eq 3300.0
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
data/spec/gargor_spec.rb
ADDED
@@ -0,0 +1,183 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'helper'
|
3
|
+
|
4
|
+
describe Gargor,".start" do
|
5
|
+
it "must initialize attributes" do
|
6
|
+
expect(Gargor.start).to eq true
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe Gargor,".load_dsl" do
|
11
|
+
it "must raise RuntimeError when load from file with population 0" do
|
12
|
+
to_load_contents ""
|
13
|
+
Gargor.start
|
14
|
+
expect {
|
15
|
+
Gargor.load_dsl("dummy")
|
16
|
+
}.to raise_error(RuntimeError)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "must load DSL successfully from sample-1.rb file" do
|
20
|
+
to_load_fixture "sample-1.rb"
|
21
|
+
Gargor.start
|
22
|
+
expect(Gargor.load_dsl("dummy")).to be true
|
23
|
+
expect(Gargor.params["population"]).to be 10
|
24
|
+
|
25
|
+
to_load_contents "population 100"
|
26
|
+
expect(Gargor.load_dsl("dummy")).to be true
|
27
|
+
expect(Gargor.params["population"]).to be 100
|
28
|
+
end
|
29
|
+
|
30
|
+
it "must raise NoMethodError when unknown command" do
|
31
|
+
Gargor.start
|
32
|
+
to_load_contents "hoge 100"
|
33
|
+
expect {
|
34
|
+
Gargor.load_dsl("dummy")
|
35
|
+
}.to raise_error(NoMethodError)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
describe Gargor,".mutation" do
|
41
|
+
it "must create individual" do
|
42
|
+
to_load_fixture "sample-1.rb"
|
43
|
+
Gargor.start
|
44
|
+
Gargor.load_dsl("dummy")
|
45
|
+
expect(Gargor.mutate).to be_kind_of Gargor::Individual
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe Gargor,".poplutate" do
|
50
|
+
it "must create individuals" do
|
51
|
+
to_load_fixture "sample-1.rb"
|
52
|
+
Gargor.start
|
53
|
+
Gargor.load_dsl("dummy")
|
54
|
+
to_load_contents("{}") # dummy json
|
55
|
+
expect(Gargor.populate.size).to be Gargor.params["population"]
|
56
|
+
expect(Gargor.opt("generation")).to be 1
|
57
|
+
expect(Gargor.next_generation).to be true
|
58
|
+
expect(Gargor.opt("generation")).to be 2
|
59
|
+
|
60
|
+
expect{
|
61
|
+
Gargor.populate
|
62
|
+
}.to raise_error RuntimeError
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe Gargor,".float_rand" do
|
67
|
+
it "returns 0...max" do
|
68
|
+
Gargor.stub(:rand) { |max| max/2.0 }
|
69
|
+
expect(Gargor.float_rand(0.1,100)).to eq 0.05
|
70
|
+
end
|
71
|
+
|
72
|
+
it "raise RuntimeError unless max > 0" do
|
73
|
+
expect {
|
74
|
+
Gargor.float_rand(0)}.to raise_error RuntimeError
|
75
|
+
expect {
|
76
|
+
Gargor.float_rand(-1.2)}.to raise_error RuntimeError
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe Gargor,".crossover" do
|
81
|
+
it "crossoveres two Gargor::Individual objects" do
|
82
|
+
to_load_fixture "sample-1.rb"
|
83
|
+
Gargor.start
|
84
|
+
Gargor.load_dsl("dummy")
|
85
|
+
a = Gargor.mutate; a.fitness = 0.4
|
86
|
+
b = Gargor.mutate; b.fitness = 0.6
|
87
|
+
|
88
|
+
expect(Gargor.crossover(a,b)).to be_kind_of Gargor::Individual
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe Gargor,".selection" do
|
93
|
+
it "does something" do
|
94
|
+
to_load_fixture "sample-1.rb"
|
95
|
+
Gargor.start
|
96
|
+
Gargor.load_dsl("dummy")
|
97
|
+
a = Gargor.mutate; a.fitness = 0.4
|
98
|
+
b = Gargor.mutate; b.fitness = 0.6
|
99
|
+
g = Gargor::Individuals.new
|
100
|
+
g << a << b
|
101
|
+
expect(Gargor.selection g).to be_kind_of Gargor::Individual
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
describe Gargor, ".validate" do
|
107
|
+
it "raise Gargor::GargorError unless population > 0" do
|
108
|
+
Gargor.start
|
109
|
+
expect{
|
110
|
+
Gargor.validate
|
111
|
+
}.to raise_error Gargor::ValidationError
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe Gargor, ".first_generation?" do
|
116
|
+
it "returns true if not .next_generation called" do
|
117
|
+
Gargor.start
|
118
|
+
expect(Gargor.first_generation?).to be true
|
119
|
+
Gargor.next_generation
|
120
|
+
expect(Gargor.first_generation?).to be false
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe Gargor, ".prev_count" do
|
125
|
+
it "returns previous generation count" do
|
126
|
+
g = Gargor::Individuals.new
|
127
|
+
a = Gargor.mutate; a.fitness = 0.4
|
128
|
+
g << a
|
129
|
+
expect(Gargor.prev_count(g)).to be 1
|
130
|
+
|
131
|
+
b = Gargor.mutate; b.fitness = 0 # not fit for this environment
|
132
|
+
c = Gargor.mutate; c.fitness = 0.6
|
133
|
+
g << b << c
|
134
|
+
expect(Gargor.prev_count(g)).to be 2
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe Gargor, ".select_elites" do
|
139
|
+
it "returns Gargor::Individuals" do
|
140
|
+
g = Gargor::Individuals.new
|
141
|
+
3.times { g << Gargor.mutate }
|
142
|
+
|
143
|
+
Gargor.start
|
144
|
+
gg = Gargor.select_elites(g,2)
|
145
|
+
expect(gg).to be_kind_of Gargor::Individuals
|
146
|
+
expect(gg.count).to be 2
|
147
|
+
end
|
148
|
+
|
149
|
+
it "returns count of elites" do
|
150
|
+
g = Gargor::Individuals.new
|
151
|
+
3.times { g << Gargor.mutate }
|
152
|
+
Gargor.start
|
153
|
+
expect(Gargor.select_elites(g,3).count).to be 3
|
154
|
+
expect(Gargor.select_elites(g,4).count).to be 3
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
describe Gargor, ".mutaion?" do
|
159
|
+
it "returns true by mutation probability" do
|
160
|
+
Gargor.stub(:rand).and_return(0.05,0.15)
|
161
|
+
expect(Gargor.mutation?(0.1)).to be true
|
162
|
+
expect(Gargor.mutation?(0.1)).to be false
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
describe Gargor, ".logfile" do
|
167
|
+
it "return log full-path" do
|
168
|
+
to_load_fixture "sample-1.rb"
|
169
|
+
Gargor.start
|
170
|
+
Gargor.load_dsl("/tmp/test.rb")
|
171
|
+
expect(Gargor.logfile("gargor.log")).to eq "/tmp/gargor.log"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe Gargor, ".logger" do
|
176
|
+
it "set Logger object to @@logger " do
|
177
|
+
to_load_fixture "sample-1.rb"
|
178
|
+
Gargor.start
|
179
|
+
Gargor.load_dsl("/tmp/test.rb")
|
180
|
+
expect(Gargor.opt("logger")).to be_kind_of Logger
|
181
|
+
expect(Gargor.opt("logger").level).to be Logger::INFO
|
182
|
+
end
|
183
|
+
end
|
data/spec/helper.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
$TESTING=true
|
3
|
+
|
4
|
+
require 'simplecov'
|
5
|
+
require 'coveralls'
|
6
|
+
|
7
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
8
|
+
SimpleCov::Formatter::HTMLFormatter,
|
9
|
+
Coveralls::SimpleCov::Formatter
|
10
|
+
]
|
11
|
+
SimpleCov.start
|
12
|
+
|
13
|
+
$:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
|
14
|
+
require 'gargor'
|
15
|
+
require 'rspec'
|
16
|
+
RSpec.configure do |config|
|
17
|
+
end
|
18
|
+
|
19
|
+
def load_fixture name
|
20
|
+
file = File.join(File.dirname(__FILE__), "fixtures",name)
|
21
|
+
File.open(file) { |f| f.read }
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_load_fixture name
|
25
|
+
File.stub(:read).and_return(load_fixture(name))
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_load_contents text
|
29
|
+
File.stub(:read).and_return(text)
|
30
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gargor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-07-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -53,17 +53,26 @@ extensions: []
|
|
53
53
|
extra_rdoc_files: []
|
54
54
|
files:
|
55
55
|
- .gitignore
|
56
|
+
- .travis.yml
|
56
57
|
- Gemfile
|
57
58
|
- LICENSE.txt
|
58
59
|
- README.md
|
59
60
|
- README_ja.md
|
60
61
|
- Rakefile
|
62
|
+
- VERSION
|
61
63
|
- bin/gargor
|
64
|
+
- doc/howto.md
|
65
|
+
- doc/sample.rb
|
62
66
|
- gargor.gemspec
|
63
67
|
- lib/gargor.rb
|
64
68
|
- lib/gargor/individual.rb
|
65
69
|
- lib/gargor/parameter.rb
|
66
70
|
- lib/gargor/version.rb
|
71
|
+
- spec/fixtures/sample-1.rb
|
72
|
+
- spec/gargor_individual_spec.rb
|
73
|
+
- spec/gargor_spec.rb
|
74
|
+
- spec/gargor_version_spec.rb
|
75
|
+
- spec/helper.rb
|
67
76
|
homepage: https://github.com/tumf/gargor
|
68
77
|
licenses:
|
69
78
|
- MIT
|
@@ -90,4 +99,9 @@ signing_key:
|
|
90
99
|
specification_version: 3
|
91
100
|
summary: It is software which uses generic algorithm to support parameter tuning of
|
92
101
|
the servers controlled by Chef.
|
93
|
-
test_files:
|
102
|
+
test_files:
|
103
|
+
- spec/fixtures/sample-1.rb
|
104
|
+
- spec/gargor_individual_spec.rb
|
105
|
+
- spec/gargor_spec.rb
|
106
|
+
- spec/gargor_version_spec.rb
|
107
|
+
- spec/helper.rb
|