jkr 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,6 @@
1
+ === 1.0.0 / 2010-04-03
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
6
+
data/Manifest.txt ADDED
@@ -0,0 +1,14 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ bin/jkr
6
+ lib/jkr.rb
7
+ lib/jkr/analysis.rb
8
+ lib/jkr/env.rb
9
+ lib/jkr/plan.rb
10
+ lib/jkr/rake.rb
11
+ lib/jkr/trial.rb
12
+ lib/jkr/userutils.rb
13
+ lib/jkr/utils.rb
14
+ test/test_jkr.rb
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
@@ -0,0 +1,12 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+
6
+ Hoe.spec 'jkr' do
7
+ developer('Yuto HAYAMIZU', 'y.hayamizu@gmail.com')
8
+
9
+ # self.rubyforge_name = 'jkrx' # if different than 'jkr'
10
+ end
11
+
12
+ # vim: syntax=ruby
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
+
@@ -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
@@ -0,0 +1,9 @@
1
+
2
+ require 'jkr/env'
3
+ require 'jkr/plan'
4
+ require 'jkr/trial'
5
+ require 'jkr/analysis'
6
+
7
+ class Jkr
8
+ VERSION = '0.0.1'
9
+ end
data/test/test_jkr.rb ADDED
@@ -0,0 +1,8 @@
1
+ require "test/unit"
2
+ require "jkr"
3
+
4
+ class TestJkr < Test::Unit::TestCase
5
+ def test_sanity
6
+ flunk "write tests or I will kneecap you"
7
+ end
8
+ end
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