jkr 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +6 -0
- data/Manifest.txt +14 -0
- data/README.txt +57 -0
- data/Rakefile +12 -0
- data/bin/jkr +224 -0
- data/lib/jkr/analysis.rb +27 -0
- data/lib/jkr/env.rb +38 -0
- data/lib/jkr/plan.rb +134 -0
- data/lib/jkr/rake.rb +18 -0
- data/lib/jkr/trial.rb +54 -0
- data/lib/jkr/userutils.rb +156 -0
- data/lib/jkr/utils.rb +470 -0
- data/lib/jkr.rb +9 -0
- data/test/test_jkr.rb +8 -0
- metadata +89 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
= jkr
|
2
|
+
|
3
|
+
* http://github.com/hayamiz/jkr
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
FIX (describe your package)
|
8
|
+
|
9
|
+
== FEATURES/PROBLEMS:
|
10
|
+
|
11
|
+
* FIX (list of features or problems)
|
12
|
+
|
13
|
+
== SYNOPSIS:
|
14
|
+
|
15
|
+
FIX (code sample of usage)
|
16
|
+
|
17
|
+
== REQUIREMENTS:
|
18
|
+
|
19
|
+
* FIX (list of requirements)
|
20
|
+
|
21
|
+
== INSTALL:
|
22
|
+
|
23
|
+
* FIX (sudo gem install, anything else)
|
24
|
+
|
25
|
+
== DEVELOPERS:
|
26
|
+
|
27
|
+
After checking out the source, run:
|
28
|
+
|
29
|
+
$ rake newb
|
30
|
+
|
31
|
+
This task will install any missing dependencies, run the tests/specs,
|
32
|
+
and generate the RDoc.
|
33
|
+
|
34
|
+
== LICENSE:
|
35
|
+
|
36
|
+
(The MIT License)
|
37
|
+
|
38
|
+
Copyright (c) 2010 FIX
|
39
|
+
|
40
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
41
|
+
a copy of this software and associated documentation files (the
|
42
|
+
'Software'), to deal in the Software without restriction, including
|
43
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
44
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
45
|
+
permit persons to whom the Software is furnished to do so, subject to
|
46
|
+
the following conditions:
|
47
|
+
|
48
|
+
The above copyright notice and this permission notice shall be
|
49
|
+
included in all copies or substantial portions of the Software.
|
50
|
+
|
51
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
52
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
53
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
54
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
55
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
56
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
57
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
data/bin/jkr
ADDED
@@ -0,0 +1,224 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'optparse'
|
5
|
+
require 'jkr'
|
6
|
+
|
7
|
+
def ul(str)
|
8
|
+
"\033[4m#{str}\033[24m"
|
9
|
+
end
|
10
|
+
|
11
|
+
def identity
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
class JkrCmd
|
16
|
+
def jkr_commands()
|
17
|
+
self.methods.map{|method_sym|
|
18
|
+
if method_sym.to_s =~ /^([a-z_-]+)_cmd$/
|
19
|
+
$~[1]
|
20
|
+
else
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
}.compact
|
24
|
+
end
|
25
|
+
|
26
|
+
def setup_optparser()
|
27
|
+
@optparser = OptionParser.new
|
28
|
+
|
29
|
+
@optparser.banner = <<EOS
|
30
|
+
Usage: #{$0} [#{ul('command')} [#{ul('options')}]]
|
31
|
+
|
32
|
+
Available commands: #{self.jkr_commands.join(', ')}
|
33
|
+
|
34
|
+
'#{$0} help #{ul('command')}' shows detailed usage of #{ul('command')}
|
35
|
+
|
36
|
+
EOS
|
37
|
+
|
38
|
+
@options[:directory] = Dir.pwd
|
39
|
+
@optparser.on('-C', '--directory DIR',
|
40
|
+
"Change to directory DIR before reading jkr config files."
|
41
|
+
) do |directory|
|
42
|
+
@options[:directory] = directory
|
43
|
+
end
|
44
|
+
|
45
|
+
@options[:jkr_directory] = File.join(@options[:directory], "jkr")
|
46
|
+
@optparser.on('-f', '--jkr-directory DIR',
|
47
|
+
"Jkr specification file."
|
48
|
+
) do |jkr_directory|
|
49
|
+
@options[:jkr_directory] = jkr_directory
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def parse_args(argv)
|
54
|
+
@optparser.parse!(argv)
|
55
|
+
@options
|
56
|
+
end
|
57
|
+
|
58
|
+
def initialize()
|
59
|
+
@options = Hash.new
|
60
|
+
self.setup_optparser()
|
61
|
+
@jkr_env = Jkr::Env.new(@options[:directory], @options[:jkr_directory])
|
62
|
+
end
|
63
|
+
|
64
|
+
def dispatch(argv)
|
65
|
+
self.parse_args(argv) # argv updated destructively
|
66
|
+
|
67
|
+
if argv.empty?
|
68
|
+
argv = ["run"]
|
69
|
+
end
|
70
|
+
command = argv.shift
|
71
|
+
unless self.jkr_commands.include?(command)
|
72
|
+
$stderr.puts "No such command: #{command}"
|
73
|
+
puts
|
74
|
+
puts @optparser.help
|
75
|
+
exit 1
|
76
|
+
end
|
77
|
+
|
78
|
+
self.send("#{command}_cmd", argv)
|
79
|
+
end
|
80
|
+
|
81
|
+
## Command definitions
|
82
|
+
|
83
|
+
def analyze_cmd(argv)
|
84
|
+
argv.each do |arg|
|
85
|
+
Jkr::Analysis.analyze(@jkr_env, arg)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
def analyze_cmd_help()
|
89
|
+
{
|
90
|
+
:summary => "analyze results",
|
91
|
+
:desc => "Usage: #{$0} analyze #{ul('RESULT_ID')} ..."
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
def list_cmd(argv)
|
96
|
+
puts "Existing plans:"
|
97
|
+
puts
|
98
|
+
plans = @jkr_env.plans.map do |plan_file_path|
|
99
|
+
plan = Jkr::Plan.new(@jkr_env, plan_file_path)
|
100
|
+
[File.basename(plan_file_path, ".plan"), plan.title]
|
101
|
+
end
|
102
|
+
maxlen = plans.map{|plan| plan[0].size}.max
|
103
|
+
plans.each do |plan|
|
104
|
+
printf(" %#{maxlen}s : %s\n", plan[0], plan[1])
|
105
|
+
end
|
106
|
+
puts
|
107
|
+
end
|
108
|
+
def list_cmd_help()
|
109
|
+
{
|
110
|
+
:summary => "list existing plans",
|
111
|
+
:desc => "list existing plans"
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
def run_cmd(argv)
|
116
|
+
filter = (if argv.empty?
|
117
|
+
lambda{|x| true}
|
118
|
+
else
|
119
|
+
lambda do |plan_file_path|
|
120
|
+
argv.map{|arg|
|
121
|
+
Regexp.compile(arg)
|
122
|
+
}.any?{|regex|
|
123
|
+
File.basename(plan_file_path) =~ regex
|
124
|
+
}
|
125
|
+
end
|
126
|
+
end)
|
127
|
+
@jkr_env.plans.each do |plan_file_path|
|
128
|
+
if filter.call(plan_file_path)
|
129
|
+
plan = Jkr::Plan.new(@jkr_env, plan_file_path)
|
130
|
+
Jkr::Trial.run(@jkr_env, plan)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
def run_cmd_help()
|
135
|
+
{
|
136
|
+
:summary => "run experiments",
|
137
|
+
:desc => ""
|
138
|
+
}
|
139
|
+
end
|
140
|
+
|
141
|
+
def install_cmd(argv)
|
142
|
+
jkr_dir = File.join(@options[:directory], "jkr")
|
143
|
+
result_dir = File.join(@options[:directory], "jkr", "result")
|
144
|
+
plan_dir = File.join(@options[:directory], "jkr", "plan")
|
145
|
+
[jkr_dir, result_dir, plan_dir].each{|dir| FileUtils.mkdir(dir) }
|
146
|
+
File.open(File.join(plan_dir, "sample.plan"), "w") do |file|
|
147
|
+
file.puts <<EOSS
|
148
|
+
# -*- mode: ruby -*-
|
149
|
+
|
150
|
+
title "Sample experiment plan"
|
151
|
+
description <<EOS
|
152
|
+
This is a sample experiment plan specification file.
|
153
|
+
EOS
|
154
|
+
|
155
|
+
# Discard this file after the experiments conducted.
|
156
|
+
discard_on_finish
|
157
|
+
|
158
|
+
def_experiment_plan do |p|
|
159
|
+
# 'p.variable' for variables
|
160
|
+
# 'p.param' for static parameters
|
161
|
+
|
162
|
+
p.variable :num_xxx => 1..10
|
163
|
+
p.param :use_yyy => false
|
164
|
+
|
165
|
+
if p.param[:use_yyy]
|
166
|
+
p.param[:yyy_zzz] = "foo"
|
167
|
+
else
|
168
|
+
p.param[:yyy_zzz] = "bar"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def_execution do |params|
|
173
|
+
puts "hello world, #\{params.num_xxx\}, #\{params[:num_xxx]\}"
|
174
|
+
end
|
175
|
+
EOSS
|
176
|
+
end
|
177
|
+
end
|
178
|
+
def install_cmd_help()
|
179
|
+
{
|
180
|
+
:summary => "install jkr skelton setting files",
|
181
|
+
:desc => ""
|
182
|
+
}
|
183
|
+
end
|
184
|
+
|
185
|
+
def help_cmd(argv)
|
186
|
+
case argv[0]
|
187
|
+
when /^commands/
|
188
|
+
commands = self.jkr_commands
|
189
|
+
puts "Available commands:"
|
190
|
+
max_len = commands.map(&:size).max
|
191
|
+
commands.each do |command|
|
192
|
+
help = self.send("#{command}_cmd_help")
|
193
|
+
printf " %#{max_len}s : #{help[:summary]}\n", command
|
194
|
+
end
|
195
|
+
puts
|
196
|
+
else
|
197
|
+
commands = argv.select{|arg| self.jkr_commands.include?(arg)}
|
198
|
+
if commands.size > 0
|
199
|
+
commands.each do |cmd|
|
200
|
+
help = self.send("#{cmd}_cmd_help")
|
201
|
+
puts("== help: #{cmd} ==\n#{help[:summary]}\n\n#{help[:desc]}\n")
|
202
|
+
end
|
203
|
+
else
|
204
|
+
puts @optparser.help
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
def help_cmd_help()
|
209
|
+
{
|
210
|
+
:summary => "show helps",
|
211
|
+
:desc => ""
|
212
|
+
}
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
216
|
+
|
217
|
+
def main(argv)
|
218
|
+
JkrCmd.new.dispatch(argv)
|
219
|
+
end
|
220
|
+
|
221
|
+
if __FILE__ == $0
|
222
|
+
main(ARGV.dup)
|
223
|
+
end
|
224
|
+
|
data/lib/jkr/analysis.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
class Jkr
|
3
|
+
class Analysis
|
4
|
+
def self.analyze(env, resultset_num)
|
5
|
+
resultset_num = sprintf "%03d", resultset_num.to_i
|
6
|
+
resultset_dir = Dir.glob(File.join(env.jkr_result_dir, resultset_num)+"*")
|
7
|
+
if resultset_dir.size != 1
|
8
|
+
raise RuntimeError.new "cannot specify resultset dir (#{resultset_dir.join(" ")})"
|
9
|
+
end
|
10
|
+
resultset_dir = resultset_dir.first
|
11
|
+
|
12
|
+
plan_files = Dir.glob(File.join(resultset_dir, "*.plan"))
|
13
|
+
if plan_files.size == 0
|
14
|
+
raise RuntimeError.new "cannot find plan file"
|
15
|
+
elsif plan_files.size > 1
|
16
|
+
raise RuntimeError.new "there are two or more plan files"
|
17
|
+
end
|
18
|
+
plan_file_path = plan_files.first
|
19
|
+
|
20
|
+
plan = Jkr::Plan.new(env, plan_file_path)
|
21
|
+
|
22
|
+
Jkr::AnalysisUtils.define_analysis_utils(resultset_dir, plan)
|
23
|
+
plan.analysis.call(plan)
|
24
|
+
Jkr::AnalysisUtils.undef_analysis_utils(plan)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/jkr/env.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
class Jkr
|
5
|
+
class Env
|
6
|
+
attr_reader :jkr_dir
|
7
|
+
attr_reader :working_dir
|
8
|
+
attr_reader :jkr_result_dir
|
9
|
+
attr_reader :jkr_plan_dir
|
10
|
+
attr_reader :jkr_script_dir
|
11
|
+
|
12
|
+
PLAN_DIR = "plan"
|
13
|
+
RESULT_DIR = "result"
|
14
|
+
SCRIPT_DIR = "script"
|
15
|
+
|
16
|
+
def initialize(working_dir = Dir.pwd, jkr_dir = File.join(Dir.pwd, "jkr"))
|
17
|
+
@jkr_dir = jkr_dir
|
18
|
+
@working_dir = working_dir
|
19
|
+
@jkr_plan_dir = File.join(@jkr_dir, PLAN_DIR)
|
20
|
+
@jkr_result_dir = File.join(@jkr_dir, RESULT_DIR)
|
21
|
+
@jkr_script_dir = File.join(@jkr_dir, SCRIPT_DIR)
|
22
|
+
|
23
|
+
[@jkr_dir, @jkr_result_dir, @jkr_plan_dir, @jkr_script_dir].each do |dir_path|
|
24
|
+
unless Dir.exists?(dir_path)
|
25
|
+
FileUtils.mkdir_p(dir_path)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def next_plan
|
31
|
+
self.plans.first
|
32
|
+
end
|
33
|
+
|
34
|
+
def plans
|
35
|
+
Dir.glob("#{@jkr_plan_dir}#{File::SEPARATOR}*.plan").sort
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/jkr/plan.rb
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
|
2
|
+
require 'jkr/utils'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
class Jkr
|
6
|
+
class Plan
|
7
|
+
attr_accessor :title
|
8
|
+
attr_accessor :desc
|
9
|
+
|
10
|
+
attr_accessor :params
|
11
|
+
attr_accessor :vars
|
12
|
+
|
13
|
+
# Proc's
|
14
|
+
attr_accessor :prep
|
15
|
+
attr_accessor :cleanup
|
16
|
+
attr_accessor :routine
|
17
|
+
attr_accessor :analysis
|
18
|
+
|
19
|
+
attr_accessor :src
|
20
|
+
|
21
|
+
attr_reader :file_path
|
22
|
+
attr_reader :jkr_env
|
23
|
+
|
24
|
+
def initialize(jkr_env, plan_file_path = nil)
|
25
|
+
@jkr_env = jkr_env
|
26
|
+
@file_path = plan_file_path || jkr_env.next_plan
|
27
|
+
return nil unless @file_path
|
28
|
+
|
29
|
+
@title = "no title"
|
30
|
+
@desc = "no desc"
|
31
|
+
|
32
|
+
@params = {}
|
33
|
+
@vars = {}
|
34
|
+
@routine = lambda do |_|
|
35
|
+
raise NotImplementedError.new("A routine of experiment '#{@title}' is not implemented")
|
36
|
+
end
|
37
|
+
@prep = lambda do |_|
|
38
|
+
raise NotImplementedError.new("A prep of experiment '#{@title}' is not implemented")
|
39
|
+
end
|
40
|
+
@cleanup = lambda do |_|
|
41
|
+
raise NotImplementedError.new("A cleanup of experiment '#{@title}' is not implemented")
|
42
|
+
end
|
43
|
+
|
44
|
+
@src = nil
|
45
|
+
|
46
|
+
PlanLoader.load_plan(self)
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
class PlanLoader
|
51
|
+
class PlanParams
|
52
|
+
attr_reader :vars
|
53
|
+
attr_reader :params
|
54
|
+
|
55
|
+
def initialize()
|
56
|
+
@vars = {}
|
57
|
+
@params = {}
|
58
|
+
end
|
59
|
+
|
60
|
+
def [](key)
|
61
|
+
@params[key]
|
62
|
+
end
|
63
|
+
|
64
|
+
def []=(key, val)
|
65
|
+
@params[key] = val
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
include Jkr::PlanUtils
|
70
|
+
|
71
|
+
def initialize(plan)
|
72
|
+
@plan = plan
|
73
|
+
@params = nil
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.load_plan(plan)
|
77
|
+
plan_loader = self.new(plan)
|
78
|
+
plan.src = File.open(plan.file_path, "r").read
|
79
|
+
plan_loader.instance_eval(plan.src, plan.file_path, 1)
|
80
|
+
plan
|
81
|
+
end
|
82
|
+
|
83
|
+
## Functions for describing plans in '.plan' files below
|
84
|
+
def plan
|
85
|
+
@plan
|
86
|
+
end
|
87
|
+
|
88
|
+
def title(plan_title)
|
89
|
+
@plan.title = plan_title.to_s
|
90
|
+
end
|
91
|
+
|
92
|
+
def description(plan_desc)
|
93
|
+
@plan.desc = plan_desc.to_s
|
94
|
+
end
|
95
|
+
|
96
|
+
def def_parameters(&proc)
|
97
|
+
@params = PlanParams.new
|
98
|
+
proc.call()
|
99
|
+
@plan.params.merge!(@params.params)
|
100
|
+
@plan.vars.merge!(@params.vars)
|
101
|
+
end
|
102
|
+
|
103
|
+
def def_routine(&proc)
|
104
|
+
@plan.routine = proc
|
105
|
+
end
|
106
|
+
def def_prep(&proc)
|
107
|
+
@plan.prep = proc
|
108
|
+
end
|
109
|
+
def def_cleanup(&proc)
|
110
|
+
@plan.cleanup = proc
|
111
|
+
end
|
112
|
+
def def_analysis(&proc)
|
113
|
+
@plan.analysis = proc
|
114
|
+
end
|
115
|
+
|
116
|
+
def parameter(arg = nil)
|
117
|
+
if arg.is_a? Hash
|
118
|
+
# set param
|
119
|
+
@params.params.merge!(arg)
|
120
|
+
else
|
121
|
+
@params
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def variable(arg = nil)
|
126
|
+
if arg.is_a? Hash
|
127
|
+
@params.vars.merge!(arg)
|
128
|
+
else
|
129
|
+
@params
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
data/lib/jkr/rake.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
desc "Preparation before experiments"
|
4
|
+
task :before
|
5
|
+
|
6
|
+
desc "Running experiments"
|
7
|
+
task :run => FileList['jkr/queue/*.plan'] do
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
desc "Wrapping-up data after experiments"
|
13
|
+
task :after
|
14
|
+
|
15
|
+
task :run => [:before]
|
16
|
+
task :after => [:run]
|
17
|
+
|
18
|
+
task :default => [:after]
|
data/lib/jkr/trial.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
|
2
|
+
require 'fileutils'
|
3
|
+
require 'jkr/utils'
|
4
|
+
|
5
|
+
class Jkr
|
6
|
+
class Trial
|
7
|
+
attr_reader :params
|
8
|
+
|
9
|
+
def self.make_trials(resultset_dir, plan)
|
10
|
+
var_combs = [{}]
|
11
|
+
plan.vars.each do |key, vals|
|
12
|
+
var_combs = vals.map {|val|
|
13
|
+
var_combs.map do |var_comb|
|
14
|
+
var_comb.dup.merge(key => val)
|
15
|
+
end
|
16
|
+
}.flatten
|
17
|
+
end
|
18
|
+
|
19
|
+
var_combs.map do |var_comb|
|
20
|
+
result_dir = Utils.reserve_next_dir(resultset_dir)
|
21
|
+
Trial.new(result_dir, plan, plan.params.merge(var_comb))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.run(env, plan)
|
26
|
+
plan_suffix = File.basename(plan.file_path, ".plan")
|
27
|
+
resultset_dir = Utils.reserve_next_dir(env.jkr_result_dir, plan_suffix)
|
28
|
+
trials = self.make_trials(resultset_dir, plan)
|
29
|
+
|
30
|
+
FileUtils.copy_file(plan.file_path,
|
31
|
+
File.join(resultset_dir, File.basename(plan.file_path)))
|
32
|
+
params = plan.params.merge(plan.vars)
|
33
|
+
plan.freeze
|
34
|
+
plan.prep.call(plan)
|
35
|
+
trials.each do |trial|
|
36
|
+
trial.run
|
37
|
+
end
|
38
|
+
plan.cleanup.call(plan)
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize(result_dir, plan, params)
|
42
|
+
@result_dir = result_dir
|
43
|
+
@plan = plan
|
44
|
+
@params = params
|
45
|
+
end
|
46
|
+
private :initialize
|
47
|
+
|
48
|
+
def run()
|
49
|
+
Jkr::TrialUtils.define_routine_utils(@result_dir, @plan, @params)
|
50
|
+
@plan.routine.call(@plan, @params)
|
51
|
+
Jkr::TrialUtils.undef_routine_utils(@plan)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
|
2
|
+
require 'kconv'
|
3
|
+
require 'time'
|
4
|
+
require 'date'
|
5
|
+
require 'csv'
|
6
|
+
|
7
|
+
class Jkr
|
8
|
+
class SysUtils
|
9
|
+
def self.cpu_cores()
|
10
|
+
self.num_cores()
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.num_cores()
|
14
|
+
`grep "core id" /proc/cpuinfo|wc -l`.to_i
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.num_processors()
|
18
|
+
`grep "physical id" /proc/cpuinfo|sort|uniq|wc -l`.to_i
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class DataUtils
|
23
|
+
BLOCKSIZE = 268435456 # 256MB
|
24
|
+
def self.read_blockseq(io_or_filepath, separator = "\n\n", &proc)
|
25
|
+
file = io_or_filepath
|
26
|
+
if ! io_or_filepath.is_a? IO
|
27
|
+
file = File.open(io_or_filepath, "r")
|
28
|
+
end
|
29
|
+
proc ||= lambda do |blockstr|
|
30
|
+
unless blockstr.strip.empty?
|
31
|
+
blockstr.split
|
32
|
+
else
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
result = []
|
38
|
+
bufstr = ""
|
39
|
+
while ! file.eof?
|
40
|
+
bufstr += file.read(BLOCKSIZE)
|
41
|
+
blocks = bufstr.split(separator)
|
42
|
+
bufstr = blocks.pop
|
43
|
+
blocks.each do |block|
|
44
|
+
ret = proc.call(block)
|
45
|
+
result.push(ret) if ret
|
46
|
+
end
|
47
|
+
end
|
48
|
+
ret = proc.call(bufstr)
|
49
|
+
result.push(ret) if ret
|
50
|
+
|
51
|
+
result
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.read_rowseq(io_or_filepath, &block)
|
55
|
+
self.read_blockseq(io_or_filepath, "\n", &block)
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.read_mpstat_avg(io_or_filepath)
|
59
|
+
self.read_blockseq(io_or_filepath){|blockstr|
|
60
|
+
if blockstr =~ /^Average:/
|
61
|
+
result = Hash.new
|
62
|
+
rows = blockstr.lines.map(&:strip)
|
63
|
+
header = rows.shift.split
|
64
|
+
header.shift
|
65
|
+
result[:labels] = header
|
66
|
+
result[:data] = rows.map { |row|
|
67
|
+
vals = row.split
|
68
|
+
vals.shift
|
69
|
+
if vals.size != result[:labels].size
|
70
|
+
raise RuntimeError.new("Invalid mpstat data")
|
71
|
+
end
|
72
|
+
vals.map{|val|
|
73
|
+
begin
|
74
|
+
Float(val)
|
75
|
+
rescue ArgumentError
|
76
|
+
val
|
77
|
+
end
|
78
|
+
}
|
79
|
+
}
|
80
|
+
result
|
81
|
+
end
|
82
|
+
}.last
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.read_mpstat(io_or_filepath)
|
86
|
+
hostname = `hostname`.strip
|
87
|
+
|
88
|
+
date = nil
|
89
|
+
last_time = nil
|
90
|
+
self.read_blockseq(io_or_filepath) do |blockstr|
|
91
|
+
if blockstr =~ /^Linux/ && blockstr =~ /(\d{2})\/(\d{2})\/(\d{2})$/
|
92
|
+
# the first line
|
93
|
+
y = $~[3].to_i; m = $~[1].to_i; d = $~[2].to_i
|
94
|
+
date = Date.new(2000 + y, m, d)
|
95
|
+
next
|
96
|
+
else
|
97
|
+
# it's a data block, maybe
|
98
|
+
unless date
|
99
|
+
$stderr.puts "Cannot find date in your mpstat log. It was assumed today."
|
100
|
+
date = Date.today
|
101
|
+
end
|
102
|
+
|
103
|
+
result = Hash.new
|
104
|
+
rows = blockstr.lines.map(&:strip)
|
105
|
+
header = rows.shift.split
|
106
|
+
next if header.shift =~ /Average/
|
107
|
+
result[:labels] = header
|
108
|
+
time = nil
|
109
|
+
result[:data] = rows.map { |row|
|
110
|
+
vals = row.split
|
111
|
+
wallclock = vals.shift
|
112
|
+
unless time
|
113
|
+
unless wallclock =~ /(\d{2}):(\d{2}):(\d{2})/
|
114
|
+
raise RuntimeError.new("Cannot extract wallclock time from mpstat data")
|
115
|
+
end
|
116
|
+
time = Time.local(date.year, date.month, date.day,
|
117
|
+
$~[1].to_i, $~[2].to_i, $~[3].to_i)
|
118
|
+
if last_time && time < last_time
|
119
|
+
date += 1
|
120
|
+
time = Time.local(date.year, date.month, date.day,
|
121
|
+
$~[1].to_i, $~[2].to_i, $~[3].to_i)
|
122
|
+
end
|
123
|
+
result[:time] = time
|
124
|
+
last_time = time
|
125
|
+
end
|
126
|
+
if vals.size != result[:labels].size
|
127
|
+
raise RuntimeError.new("Invalid mpstat data")
|
128
|
+
end
|
129
|
+
vals.map{|val|
|
130
|
+
begin
|
131
|
+
Float(val)
|
132
|
+
rescue ArgumentError
|
133
|
+
val
|
134
|
+
end
|
135
|
+
}
|
136
|
+
}
|
137
|
+
result
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def self.read_csv(io_or_filepath, fs = ",", rs = nil, &proc)
|
143
|
+
if io_or_filepath.is_a?(String) && File.exists?(io_or_filepath)
|
144
|
+
io_or_filepath = File.open(io_or_filepath, "r")
|
145
|
+
end
|
146
|
+
|
147
|
+
result = []
|
148
|
+
proc ||= lambda{|row| row}
|
149
|
+
CSV.parse(io_or_filepath).each do |row|
|
150
|
+
ret = proc.call(row)
|
151
|
+
result.push ret if ret
|
152
|
+
end
|
153
|
+
result
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
data/lib/jkr/utils.rb
ADDED
@@ -0,0 +1,470 @@
|
|
1
|
+
|
2
|
+
require 'fileutils'
|
3
|
+
require 'popen4'
|
4
|
+
require 'thread'
|
5
|
+
|
6
|
+
require 'jkr/userutils'
|
7
|
+
|
8
|
+
class Barrier
|
9
|
+
def initialize(num)
|
10
|
+
@mux = Mutex.new
|
11
|
+
@cond = ConditionVariable.new
|
12
|
+
|
13
|
+
@num = num
|
14
|
+
@cur_num = num
|
15
|
+
end
|
16
|
+
|
17
|
+
def wait()
|
18
|
+
@mux.lock
|
19
|
+
@cur_num -= 1
|
20
|
+
if @cur_num == 0
|
21
|
+
@cur_num = @num
|
22
|
+
@cond.broadcast
|
23
|
+
else
|
24
|
+
@cond.wait(@mux)
|
25
|
+
end
|
26
|
+
@mux.unlock
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Jkr
|
31
|
+
class Utils
|
32
|
+
def self.reserve_next_dir(dir, suffix = "")
|
33
|
+
dirs = Dir.glob("#{dir}#{File::SEPARATOR}???*")
|
34
|
+
max_num = -1
|
35
|
+
dirs.each do |dir|
|
36
|
+
if /\A[0-9]+/ =~ File.basename(dir)
|
37
|
+
max_num = [$~[0].to_i, max_num].max
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
num = max_num + 1
|
42
|
+
dir = "#{dir}#{File::SEPARATOR}" + sprintf("%03d%s", num, suffix)
|
43
|
+
FileUtils.mkdir(dir)
|
44
|
+
dir
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
module PlanUtils
|
49
|
+
# info about processes spawned by me
|
50
|
+
def procdb
|
51
|
+
@procdb ||= Hash.new
|
52
|
+
end
|
53
|
+
def procdb_spawn(pid, command, owner_thread)
|
54
|
+
@procdb_mutex.synchronize do
|
55
|
+
self.procdb[pid] = {
|
56
|
+
:pid => pid,
|
57
|
+
:command => command,
|
58
|
+
:thread => owner_thread,
|
59
|
+
:status => nil
|
60
|
+
}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
def procdb_waitpid(pid)
|
64
|
+
t = nil
|
65
|
+
@procdb_mutex.synchronize do
|
66
|
+
if self.procdb[pid]
|
67
|
+
t = self.procdb[pid][:thread]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
t.join if t
|
71
|
+
end
|
72
|
+
def procdb_resetpid(pid)
|
73
|
+
@procdb_mutex.synchronize do
|
74
|
+
if self.procdb[pid]
|
75
|
+
self.procdb.delete(pid)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
def procdb_update_status(pid, status)
|
80
|
+
@procdb_mutex.synchronize do
|
81
|
+
if self.procdb[pid]
|
82
|
+
self.procdb[pid][:status] = status
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
def procdb_get(pid)
|
87
|
+
@procdb_mutex.synchronize do
|
88
|
+
self.procdb[pid]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
def procdb_get_status(pid)
|
92
|
+
proc = self.procdb_get(pid)
|
93
|
+
proc && proc[:status]
|
94
|
+
end
|
95
|
+
def procdb_get_command(pid)
|
96
|
+
proc = self.procdb_get(pid)
|
97
|
+
proc && proc[:command]
|
98
|
+
end
|
99
|
+
|
100
|
+
def cmd(*args)
|
101
|
+
@procdb_mutex ||= Mutex.new
|
102
|
+
options = (if args.last.is_a? Hash
|
103
|
+
args.pop
|
104
|
+
else
|
105
|
+
{}
|
106
|
+
end)
|
107
|
+
options = {
|
108
|
+
:wait => true,
|
109
|
+
:timeout => 0,
|
110
|
+
:raise_failure => true,
|
111
|
+
:stdin => nil,
|
112
|
+
:stdout => [$stdout],
|
113
|
+
:stderr => [$stderr]
|
114
|
+
}.merge(options)
|
115
|
+
|
116
|
+
if options[:timeout] > 0 && ! options[:wait]
|
117
|
+
raise ArgumentError.new("cmd: 'wait' must be true if 'timeout' specified.")
|
118
|
+
end
|
119
|
+
|
120
|
+
start_time = Time.now
|
121
|
+
pid = nil
|
122
|
+
status = nil
|
123
|
+
args.flatten!
|
124
|
+
args.map!(&:to_s)
|
125
|
+
command = args.join(" ")
|
126
|
+
barrier = Barrier.new(2)
|
127
|
+
process_exited = false
|
128
|
+
|
129
|
+
t = Thread.new {
|
130
|
+
pipers = []
|
131
|
+
status = POpen4::popen4(command){|p_stdout, p_stderr, p_stdin, p_id|
|
132
|
+
pid = p_id
|
133
|
+
barrier.wait
|
134
|
+
stdouts = if options[:stdout].is_a? Array
|
135
|
+
options[:stdout]
|
136
|
+
else
|
137
|
+
[options[:stdout]]
|
138
|
+
end
|
139
|
+
stderrs = if options[:stderr].is_a? Array
|
140
|
+
options[:stderr]
|
141
|
+
else
|
142
|
+
[options[:stderr]]
|
143
|
+
end
|
144
|
+
pipers << Thread.new{
|
145
|
+
target = p_stdout
|
146
|
+
timeout_count = 0
|
147
|
+
while true
|
148
|
+
begin
|
149
|
+
if (ready = IO.select([target], [], [], 1))
|
150
|
+
ready.first.each do |fd|
|
151
|
+
buf = fd.read_nonblock(4096)
|
152
|
+
stdouts.each{|out| out.print buf}
|
153
|
+
end
|
154
|
+
Thread.exit if target.eof?
|
155
|
+
else
|
156
|
+
if process_exited
|
157
|
+
timeout_count += 1
|
158
|
+
if timeout_count > 5
|
159
|
+
target.close_read
|
160
|
+
Thread.exit
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
rescue IOError => err
|
165
|
+
if target.closed?
|
166
|
+
Thread.exit
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
}
|
171
|
+
pipers << Thread.new{
|
172
|
+
target = p_stderr
|
173
|
+
timeout_count = 0
|
174
|
+
while true
|
175
|
+
begin
|
176
|
+
if (ready = IO.select([target], [], [], 1))
|
177
|
+
ready.first.each do |fd|
|
178
|
+
buf = fd.read_nonblock(4096)
|
179
|
+
stderrs.each{|out| out.print buf}
|
180
|
+
end
|
181
|
+
Thread.exit if target.eof?
|
182
|
+
else
|
183
|
+
if process_exited
|
184
|
+
timeout_count += 1
|
185
|
+
if timeout_count > 5
|
186
|
+
target.close_read
|
187
|
+
Thread.exit
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
rescue IOError => err
|
192
|
+
if target.closed?
|
193
|
+
Thread.exit
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
}
|
198
|
+
if options[:stdin]
|
199
|
+
pipers << Thread.new{
|
200
|
+
target = options[:stdin]
|
201
|
+
timeout_count = 0
|
202
|
+
while true
|
203
|
+
begin
|
204
|
+
if (ready = IO.select([target], [], [], 1))
|
205
|
+
ready.first.each do |fd|
|
206
|
+
buf = fd.read_nonblock(4096)
|
207
|
+
p_stdin.print buf
|
208
|
+
end
|
209
|
+
else
|
210
|
+
if process_exited
|
211
|
+
timeout_count += 1
|
212
|
+
if timeout_count > 5
|
213
|
+
p_stdin.close_write
|
214
|
+
Thread.exit
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
rescue IOError => err
|
219
|
+
if target.closed?
|
220
|
+
Thread.exit
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
}
|
225
|
+
end
|
226
|
+
}
|
227
|
+
pipers.each{|t| t.join}
|
228
|
+
raise ArgumentError.new("Invalid command: #{command}") unless status
|
229
|
+
procdb_update_status(pid, status)
|
230
|
+
}
|
231
|
+
barrier.wait
|
232
|
+
procdb_spawn(pid, command, t)
|
233
|
+
timekeeper = nil
|
234
|
+
|
235
|
+
killed = false
|
236
|
+
timekeeper = nil
|
237
|
+
if options[:timeout] > 0
|
238
|
+
timekeeper = Thread.new do
|
239
|
+
sleep(options[:timeout])
|
240
|
+
begin
|
241
|
+
Process.kill(:INT, pid)
|
242
|
+
killed = true
|
243
|
+
rescue Errno::ESRCH # No such process
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
if options[:wait]
|
248
|
+
timekeeper.join if timekeeper
|
249
|
+
t.join
|
250
|
+
if (! killed) && options[:raise_failure] && status.exitstatus != 0
|
251
|
+
raise RuntimeError.new("'#{command}' failed.")
|
252
|
+
end
|
253
|
+
end
|
254
|
+
while ! pid
|
255
|
+
sleep 0.001 # do nothing
|
256
|
+
end
|
257
|
+
|
258
|
+
pid
|
259
|
+
end
|
260
|
+
|
261
|
+
def with_process2(*args)
|
262
|
+
options = (if args.last.is_a? Hash
|
263
|
+
args.pop
|
264
|
+
else
|
265
|
+
{}
|
266
|
+
end )
|
267
|
+
options = {
|
268
|
+
:kill_on_exit => false
|
269
|
+
}.merge(options)
|
270
|
+
|
271
|
+
command = args.flatten.map(&:to_s).join(" ")
|
272
|
+
pid = Process.spawn(command)
|
273
|
+
|
274
|
+
err = nil
|
275
|
+
begin
|
276
|
+
yield
|
277
|
+
rescue Exception => e
|
278
|
+
err = e
|
279
|
+
end
|
280
|
+
|
281
|
+
if options[:kill_on_exit]
|
282
|
+
Process.kill(:INT, pid)
|
283
|
+
else
|
284
|
+
if err
|
285
|
+
begin
|
286
|
+
Process.kill(:TERM, pid)
|
287
|
+
rescue Exception
|
288
|
+
end
|
289
|
+
else
|
290
|
+
begin
|
291
|
+
status = Process.waitpid(pid)
|
292
|
+
p status
|
293
|
+
rescue Errno::ESRCH
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
raise err if err
|
298
|
+
end
|
299
|
+
|
300
|
+
def with_process(*args)
|
301
|
+
options = (if args.last.is_a? Hash
|
302
|
+
args.pop
|
303
|
+
else
|
304
|
+
{}
|
305
|
+
end )
|
306
|
+
options = {
|
307
|
+
:kill_on_exit => false
|
308
|
+
}.merge(options)
|
309
|
+
options[:wait] = false
|
310
|
+
|
311
|
+
args.push(options)
|
312
|
+
pid = cmd(*args)
|
313
|
+
|
314
|
+
err = nil
|
315
|
+
begin
|
316
|
+
yield
|
317
|
+
rescue Exception => e
|
318
|
+
err = e
|
319
|
+
end
|
320
|
+
|
321
|
+
if options[:kill_on_exit]
|
322
|
+
Process.kill(:INT, pid)
|
323
|
+
else
|
324
|
+
if err
|
325
|
+
begin
|
326
|
+
Process.kill(:TERM, pid)
|
327
|
+
rescue Exception
|
328
|
+
end
|
329
|
+
else
|
330
|
+
procdb_waitpid(pid)
|
331
|
+
status = procdb_get_status(pid)
|
332
|
+
unless status && status.exitstatus == 0
|
333
|
+
command = procdb_get_command(pid) || "Unknown command"
|
334
|
+
raise RuntimeError.new("'#{command}' failed.")
|
335
|
+
end
|
336
|
+
procdb_resetpid(pid)
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
raise err if err
|
341
|
+
end
|
342
|
+
|
343
|
+
def use_script(name)
|
344
|
+
name = name.to_s
|
345
|
+
name = name + ".rb" unless name =~ /\.rb$/
|
346
|
+
dir = @plan.jkr_env.jkr_script_dir
|
347
|
+
path = File.join(dir, name)
|
348
|
+
script = File.open(path, "r").read
|
349
|
+
self.instance_eval(script, path, 1)
|
350
|
+
true
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
class TrialUtils
|
355
|
+
def self.undef_routine_utils(plan)
|
356
|
+
plan.routine.binding.eval <<EOS
|
357
|
+
undef result_file
|
358
|
+
undef result_file_name
|
359
|
+
undef touch_result_file
|
360
|
+
undef with_result_file
|
361
|
+
EOS
|
362
|
+
end
|
363
|
+
|
364
|
+
def self.define_routine_utils(result_dir, plan, params)
|
365
|
+
line = __LINE__; src = <<EOS
|
366
|
+
def result_file_name(basename)
|
367
|
+
File.join(#{result_dir.inspect}, basename)
|
368
|
+
end
|
369
|
+
|
370
|
+
def result_file(basename, mode = "a+")
|
371
|
+
path = result_file_name(basename)
|
372
|
+
File.open(path, mode)
|
373
|
+
end
|
374
|
+
|
375
|
+
def touch_result_file(basename, options = {})
|
376
|
+
path = result_file_name(basename)
|
377
|
+
FileUtils.touch(path, options)
|
378
|
+
path
|
379
|
+
end
|
380
|
+
|
381
|
+
def with_result_file(basename, mode = "a+")
|
382
|
+
file = result_file(basename, mode)
|
383
|
+
err = nil
|
384
|
+
begin
|
385
|
+
yield(file)
|
386
|
+
rescue Exception => e
|
387
|
+
err = e
|
388
|
+
end
|
389
|
+
file.close
|
390
|
+
raise err if err
|
391
|
+
file.path
|
392
|
+
end
|
393
|
+
EOS
|
394
|
+
plan.routine.binding.eval(src, __FILE__, line)
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
class AnalysisUtils
|
399
|
+
def self.undef_analysis_utils(plan)
|
400
|
+
plan.analysis.binding.eval <<EOS
|
401
|
+
undef resultset
|
402
|
+
undef result_file
|
403
|
+
undef result_file_name
|
404
|
+
undef with_result_file
|
405
|
+
undef common_file
|
406
|
+
undef common_file_name
|
407
|
+
undef with_common_file
|
408
|
+
EOS
|
409
|
+
end
|
410
|
+
|
411
|
+
def self.define_analysis_utils(resultset_dir, plan)
|
412
|
+
line = __LINE__; src = <<EOS
|
413
|
+
def resultset()
|
414
|
+
dirs = Dir.glob(File.join(#{resultset_dir.inspect}, "*"))
|
415
|
+
dirs.map{|dir| File.basename dir}.select{|basename|
|
416
|
+
basename =~ /\\A\\d{3,}\\Z/
|
417
|
+
}
|
418
|
+
end
|
419
|
+
|
420
|
+
def result_file_name(num, basename)
|
421
|
+
if num.is_a? Integer
|
422
|
+
num = sprintf "%03d", num
|
423
|
+
end
|
424
|
+
File.join(#{resultset_dir.inspect}, num, basename)
|
425
|
+
end
|
426
|
+
|
427
|
+
def result_file(num, basename, mode = "r")
|
428
|
+
path = result_file_name(num, basename)
|
429
|
+
File.open(path, mode)
|
430
|
+
end
|
431
|
+
|
432
|
+
def common_file_name(basename)
|
433
|
+
File.join(#{resultset_dir.inspect}, basename)
|
434
|
+
end
|
435
|
+
|
436
|
+
def common_file(basename, mode = "r")
|
437
|
+
path = common_file_name(basename)
|
438
|
+
File.open(path, mode)
|
439
|
+
end
|
440
|
+
|
441
|
+
def with_common_file(basename, mode = "r")
|
442
|
+
file = common_file(basename, mode)
|
443
|
+
err = nil
|
444
|
+
begin
|
445
|
+
yield(file)
|
446
|
+
rescue Exception => e
|
447
|
+
err = e
|
448
|
+
end
|
449
|
+
file.close
|
450
|
+
raise err if err
|
451
|
+
file.path
|
452
|
+
end
|
453
|
+
|
454
|
+
def with_result_file(basename, mode = "r")
|
455
|
+
file = result_file(basename, mode)
|
456
|
+
err = nil
|
457
|
+
begin
|
458
|
+
yield(file)
|
459
|
+
rescue Exception => e
|
460
|
+
err = e
|
461
|
+
end
|
462
|
+
file.close
|
463
|
+
raise err if err
|
464
|
+
file.path
|
465
|
+
end
|
466
|
+
EOS
|
467
|
+
plan.routine.binding.eval(src, __FILE__, line)
|
468
|
+
end
|
469
|
+
end
|
470
|
+
end
|
data/lib/jkr.rb
ADDED
data/test/test_jkr.rb
ADDED
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jkr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Yuto HAYAMIZU
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-05-17 00:00:00 +09:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rubyforge
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 2.0.4
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: hoe
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.6.0
|
34
|
+
version:
|
35
|
+
description: FIX (describe your package)
|
36
|
+
email:
|
37
|
+
- y.hayamizu@gmail.com
|
38
|
+
executables:
|
39
|
+
- jkr
|
40
|
+
extensions: []
|
41
|
+
|
42
|
+
extra_rdoc_files:
|
43
|
+
- History.txt
|
44
|
+
- Manifest.txt
|
45
|
+
- README.txt
|
46
|
+
files:
|
47
|
+
- History.txt
|
48
|
+
- Manifest.txt
|
49
|
+
- README.txt
|
50
|
+
- Rakefile
|
51
|
+
- bin/jkr
|
52
|
+
- lib/jkr.rb
|
53
|
+
- lib/jkr/analysis.rb
|
54
|
+
- lib/jkr/env.rb
|
55
|
+
- lib/jkr/plan.rb
|
56
|
+
- lib/jkr/rake.rb
|
57
|
+
- lib/jkr/trial.rb
|
58
|
+
- lib/jkr/userutils.rb
|
59
|
+
- lib/jkr/utils.rb
|
60
|
+
- test/test_jkr.rb
|
61
|
+
has_rdoc: true
|
62
|
+
homepage: http://github.com/hayamiz/jkr
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options:
|
65
|
+
- --main
|
66
|
+
- README.txt
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: "0"
|
74
|
+
version:
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: "0"
|
80
|
+
version:
|
81
|
+
requirements: []
|
82
|
+
|
83
|
+
rubyforge_project: jkr
|
84
|
+
rubygems_version: 1.3.1
|
85
|
+
signing_key:
|
86
|
+
specification_version: 2
|
87
|
+
summary: FIX (describe your package)
|
88
|
+
test_files:
|
89
|
+
- test/test_jkr.rb
|