jkr 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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