batch_experiment 0.1.0
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.
- checksums.yaml +7 -0
- data/examples/sample_batch.rb +48 -0
- data/examples/ukp_batch.rb +39 -0
- data/lib/batch_experiment/extractor.rb +29 -0
- data/lib/batch_experiment/sample_extractors.rb +49 -0
- data/lib/batch_experiment.rb +345 -0
- metadata +64 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4d594a77b4909c00eb2c07a05dbc6c4f0cc68c9a
|
4
|
+
data.tar.gz: 979f0d1800486061aff5d9a1728e55790e675e34
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cc0d896402fc7820e7c1f2741ae9e07ffdea6ab14edc3156e9bdcd5d3f4c24b71e753087e1980fdb97c8b85006cc2c7ef1bbb3bdad3a181b0853a7c94f76ea82
|
7
|
+
data.tar.gz: bfde027e77f78360a9728eacca5075a00a2b59e47cd0224e692c2e44bc6118e942c93c4913a0bd65a5861612191f92deb4402052440037e416391c9433d2629c
|
@@ -0,0 +1,48 @@
|
|
1
|
+
#!/bin/ruby
|
2
|
+
|
3
|
+
require 'batch_experiment'
|
4
|
+
require 'batch_experiment/sample_extractors'
|
5
|
+
|
6
|
+
comms_info = [{
|
7
|
+
# String with command to be executed. Must have 'pattern' as substring.
|
8
|
+
command: 'sleep 1 && echo X X',
|
9
|
+
# Substring present in 'command'. Often replaced by the instance filename.
|
10
|
+
pattern: 'X',
|
11
|
+
# Extractor object. Receives the output of the command and return
|
12
|
+
# the most important fields.
|
13
|
+
extractor: SampleExtractor.new,
|
14
|
+
# String used to identify the command. Will be used to prefix the return of
|
15
|
+
# extractor.names.
|
16
|
+
prefix: 'doubled',
|
17
|
+
}, {
|
18
|
+
command: 'sleep 3 && echo "banana X"',
|
19
|
+
pattern: 'X',
|
20
|
+
extractor: SampleExtractor.new,
|
21
|
+
prefix: 'banana',
|
22
|
+
}, {
|
23
|
+
command: 'sleep 100 && echo "never gonna happen X"',
|
24
|
+
pattern: 'X',
|
25
|
+
extractor: SampleExtractor.new,
|
26
|
+
prefix: 'timeout',
|
27
|
+
}]
|
28
|
+
|
29
|
+
execution_info = {
|
30
|
+
# IDs of the CPU cores that can be used for executing tests.
|
31
|
+
cpus_available: [1, 2, 3],
|
32
|
+
# Maximum number of seconds that a command can run. After this a kill command
|
33
|
+
# (TERM signal) will be issued.
|
34
|
+
timeout: 5,
|
35
|
+
# Maximum number of seconds that a command can run after a kill command was
|
36
|
+
# issued. After this a kill -9 command (KILL signal) will be issued.
|
37
|
+
post_timeout: 1,
|
38
|
+
}
|
39
|
+
|
40
|
+
conf = {
|
41
|
+
# The name of the file where will be written the CSV data.
|
42
|
+
csvfname: 'sample.csv',
|
43
|
+
}
|
44
|
+
|
45
|
+
files = ['apple', 'orange'] # Applejack would be proud
|
46
|
+
|
47
|
+
BatchExperiment::experiment(comms_info, execution_info, conf, files)
|
48
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require_relative 'batch_experiment'
|
4
|
+
require_relative 'batch_experiment/sample_extractors'
|
5
|
+
|
6
|
+
# I run the three lines below in the console to disable hyperthreading cores on
|
7
|
+
# my computer before examining the cores with the top command.
|
8
|
+
# for i in 4 5 6 7; do
|
9
|
+
# sudo sh -c "echo 0 > /sys/devices/system/cpu/cpu$i/online";
|
10
|
+
# done
|
11
|
+
|
12
|
+
comms_info = [{
|
13
|
+
command: 'pyasukpt -src INST_FILE',
|
14
|
+
pattern: 'INST_FILE',
|
15
|
+
extractor: PyaExtractor.new,
|
16
|
+
prefix: 'PYAsUKP',
|
17
|
+
}, {
|
18
|
+
command: 'run_ukp5.out INST_FILE',
|
19
|
+
pattern: 'INST_FILE',
|
20
|
+
extractor: UKP5Extractor.new,
|
21
|
+
prefix: 'UKP5',
|
22
|
+
}]
|
23
|
+
|
24
|
+
execution_info = {
|
25
|
+
cpus_available: [1, 2, 3],
|
26
|
+
timeout: 10,
|
27
|
+
post_timeout: 5,
|
28
|
+
}
|
29
|
+
|
30
|
+
conf = { csvfname: 'pya_site8.csv' }
|
31
|
+
|
32
|
+
files = ['corepb.ukp', 'exnsd18.ukp', 'exnsd26.ukp', 'exnsdbis18.ukp', 'exnsd16.ukp', 'exnsd20.ukp', 'exnsdbis10.ukp', 'exnsds12.ukp']
|
33
|
+
# If you don't execute the script from the ukp files folder you need to put the
|
34
|
+
# folder relative or absolute path here (with trailing slash).
|
35
|
+
path = ''
|
36
|
+
files.map! { | f | path + f }
|
37
|
+
|
38
|
+
experiment(comms_info, execution_info, conf, files)
|
39
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Extractor
|
2
|
+
# For when there's a field whose value is after '<field>: '.
|
3
|
+
def self.get_field(lines, field)
|
4
|
+
lines.grep(/^#{field}: .*/).each { | l | return l.match(/:[\t ]+(.*)/)[1] }
|
5
|
+
''
|
6
|
+
end
|
7
|
+
|
8
|
+
# For when there's a field whose value is in the next line.
|
9
|
+
def self.get_hfield(lines, field)
|
10
|
+
if ix = lines.find_index(field) then lines[ix + 1] else '' end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Return the field names for each of the elements returned by
|
14
|
+
# extract. Ex.: ['Time', 'Max Mem Use', 'opt', ... ]
|
15
|
+
def names
|
16
|
+
fail 'This method should have been overwritten by a subclass.'
|
17
|
+
end
|
18
|
+
|
19
|
+
def extract(content)
|
20
|
+
extract_from_lines(content.lines.map! { | l | l.chomp! })
|
21
|
+
end
|
22
|
+
|
23
|
+
# Extract an array of values from the command output. This array has the same
|
24
|
+
# size as the one returned by field_names.
|
25
|
+
def extract_from_lines(lines)
|
26
|
+
fail 'This method should have been overwritten by a subclass.'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'require_relative'
|
2
|
+
require_relative './extractor.rb'
|
3
|
+
|
4
|
+
# Sample extractors used at https://github.com/henriquebecker91/masters, where
|
5
|
+
# this code had its beggining. This file contains the code used to extract info
|
6
|
+
# from the different outputs generated by UKP solving programs.
|
7
|
+
|
8
|
+
class SampleExtractor
|
9
|
+
include Extractor
|
10
|
+
def names
|
11
|
+
['first word', 'second word', 'ext_time', 'ext_mem']
|
12
|
+
end
|
13
|
+
|
14
|
+
def extract_from_lines(lines)
|
15
|
+
words = lines.empty? || lines[0].nil? ? ['',''] : lines[0].split().take(2)
|
16
|
+
words << Extractor.get_field(lines, 'ext_time')
|
17
|
+
words << Extractor.get_field(lines, 'ext_mem')
|
18
|
+
words
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class UKP5Extractor
|
23
|
+
include Extractor
|
24
|
+
def names
|
25
|
+
['internal time', 'external time', 'external memory', 'opt']
|
26
|
+
end
|
27
|
+
|
28
|
+
def extract_from_lines(lines)
|
29
|
+
['Seconds', 'ext_time', 'ext_mem', 'opt'].map do | label |
|
30
|
+
Extractor.get_field(lines, label)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class PyaExtractor
|
36
|
+
include Extractor
|
37
|
+
def names
|
38
|
+
['internal time', 'external time', 'external memory', 'opt']
|
39
|
+
end
|
40
|
+
|
41
|
+
def extract_from_lines(lines)
|
42
|
+
values = ['Total Time ', 'ext_time', 'ext_mem'].map do | label |
|
43
|
+
Extractor.get_field(lines, label)
|
44
|
+
end
|
45
|
+
opt_key = '#The optimal value for the given capacity'
|
46
|
+
values << Extractor.get_hfield(lines, opt_key)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
@@ -0,0 +1,345 @@
|
|
1
|
+
require 'childprocess'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
module BatchExperiment
|
5
|
+
# The default callable class used by batch to convert a command into a
|
6
|
+
# filename.
|
7
|
+
class FilenameSanitizer
|
8
|
+
def call(command)
|
9
|
+
fname = command.strip
|
10
|
+
fname.gsub!(/[^[:alnum:]]/, '_')
|
11
|
+
fname.gsub!(/_+/, '_')
|
12
|
+
fname.gsub!(/^_/, '')
|
13
|
+
fname.gsub!(/_$/, '')
|
14
|
+
fname
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Internal use only. DO NOT DEPEND.
|
19
|
+
# Remove any finished commands from comms_running, insert the cpus
|
20
|
+
# freed by the commands termination to the free_cpus, insert the
|
21
|
+
# terminated commands on comms_executed.
|
22
|
+
def self.update_finished(free_cpus, comms_running, comms_executed)
|
23
|
+
comms_running.delete_if do | job |
|
24
|
+
if job[:proc].exited?
|
25
|
+
free_cpus.push(job[:cpu])
|
26
|
+
File.delete(job[:lockfname])
|
27
|
+
comms_executed << job[:command]
|
28
|
+
end
|
29
|
+
job[:proc].exited? # bool returned to delete_if
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Takes a list of commands, execute them only on the designed core/cpus, and
|
34
|
+
# kill them if the timeout expires, never lets a core/cpu rest for more than
|
35
|
+
# conf[:busy_loop_sleep] seconds between a command and another. The
|
36
|
+
# conf[:fname_sanitizer] is called over the commands to generate partial
|
37
|
+
# filenames. Appending '.out' to one of the partial filenames will give the
|
38
|
+
# filename were the command stdout was redirected. The analogue is valid for
|
39
|
+
# '.err' and stderr. The first partial filename corresponds to the first
|
40
|
+
# command in commands, and so on. Right before a command begans to run, a
|
41
|
+
# "partial_filename.#{conf[:unfinished_ext]}" file is created. After the
|
42
|
+
# command ends its execution this file is removed. If the command ends its
|
43
|
+
# execution by means of a timeout the file is also removed. The file only
|
44
|
+
# remains if the batch procedure is interrupted (not a specific command).
|
45
|
+
#
|
46
|
+
# @param commands [Array<String>] The shell commands.
|
47
|
+
# @param conf [Hash] The configurations, as follows:
|
48
|
+
# :cpus_available [Array<Fixnum>] Cpu cores that can be used to run the
|
49
|
+
# commands. Required parameter. The cpu numbers begin at 0, despite what
|
50
|
+
# htop tells you;
|
51
|
+
# :timeout [Number] Number of seconds before killing a command. Required
|
52
|
+
# parameter. Is the same for all the commands;
|
53
|
+
# :time_fmt [String] A string in the time (external command) format. See
|
54
|
+
# http://linux.die.net/man/1/time. Default: 'ext_time: %e\next_mem: %M\n'.
|
55
|
+
# :busy_loop_sleep [Number] How many seconds to wait before checking if a
|
56
|
+
# command ended execution. This is max time a cpu will be vacant between
|
57
|
+
# two commands. Default: 0.1;
|
58
|
+
# :post_timeout [Number] A command isn't guaranteed to end after receiving
|
59
|
+
# a TERM signal. If the command hasn't stopped, waits post_timeout seconds
|
60
|
+
# before sending a KILL signal (give it a chance to end gracefully).
|
61
|
+
# Default: 5;
|
62
|
+
# :fname_sanitizer [Callable Object] The call method of this object
|
63
|
+
# should take a String and convert it (possibly losing information), to a
|
64
|
+
# valid filename. Used over the commands to define the output files of
|
65
|
+
# commands.
|
66
|
+
# Default: BatchExperiment::FilenameSanitizer.new;
|
67
|
+
# :skip_done_comms [FalseClass,TrueClass] Skip any command for what a
|
68
|
+
# corresponding '.out' file exists, except if both a '.out' and a
|
69
|
+
# '.unfinished' file exist, in the last case the command is executed.
|
70
|
+
# Default: true;
|
71
|
+
# :unfinished_ext [String] Extension to be used in place of '.unfinished'.
|
72
|
+
# Default: '.unfinished';
|
73
|
+
# :out_ext [String] Extension to be used in place of '.out'.
|
74
|
+
# Default: '.out';
|
75
|
+
# :err_ext [String] Extension to be used in place of '.err'.
|
76
|
+
# Default: '.err';
|
77
|
+
# @return [String] Which commands were executed. Can be different from
|
78
|
+
# the 'commands' argument if commands are skipped (see :skip_done_comms).
|
79
|
+
#
|
80
|
+
# @note This procedure was not designed to support equal commands (the last
|
81
|
+
# equal command executed will subscribe the '.out', '.err' and '.unfinished'
|
82
|
+
# files used by any previous equal command). But the parameter
|
83
|
+
# conf[:fname_sanitizer] can be used to circumvent the restriction over
|
84
|
+
# equal commands (if the object has state it can return a different
|
85
|
+
# filename for every time it's called with the same argument).
|
86
|
+
# @note This procedure makes use of the following linux commands: time (not
|
87
|
+
# the bash internal one, but the package one, i.e.
|
88
|
+
# https://www.archlinux.org/packages/extra/x86_64/time/); timeout (from
|
89
|
+
# coreutils); taskset (from util-linux,
|
90
|
+
# https://www.archlinux.org/packages/core/x86_64/util-linux/); sh (the
|
91
|
+
# shell).
|
92
|
+
# @note The command is executed inside a call to "sh -c command", so it has
|
93
|
+
# to be a valid sh command.
|
94
|
+
# @note The output of the command "time -f #{conf[:time_fmt]}" will be
|
95
|
+
# appended to the '.out' file of every command. If you set conf[:time_fmt]
|
96
|
+
# to a empty string only a newline will be appended.
|
97
|
+
def self.batch(commands, conf)
|
98
|
+
# Throw exceptions if required configurations aren't provided.
|
99
|
+
fail "conf[:cpus_available] not set" unless conf[:cpus_available]
|
100
|
+
fail "conf[:timeout] not set" unless conf[:timeout]
|
101
|
+
|
102
|
+
# Initialize optional configurations with default values if they weren't
|
103
|
+
# provided. Don't change the conf argument, only our version of conf.
|
104
|
+
conf = conf.clone
|
105
|
+
conf[:time_fmt] ||= 'ext_time: %e\\next_mem: %M\\n'
|
106
|
+
conf[:unfinished_ext] ||= '.unfinished'
|
107
|
+
conf[:out_ext] ||= '.out'
|
108
|
+
conf[:err_ext] ||= '.err'
|
109
|
+
conf[:busy_loop_sleep] ||= 0.1
|
110
|
+
conf[:post_timeout] ||= 5
|
111
|
+
conf[:fname_sanitizer] ||= BatchExperiment::FilenameSanitizer.new
|
112
|
+
conf[:skip_done_comms] = true if conf[:skip_done_comms].nil?
|
113
|
+
|
114
|
+
# Initialize main variables
|
115
|
+
free_cpus = conf[:cpus_available].clone
|
116
|
+
comms_running = []
|
117
|
+
cpu = nil
|
118
|
+
comms_executed = []
|
119
|
+
|
120
|
+
commands.each do | command |
|
121
|
+
commfname = conf[:fname_sanitizer].call(command)
|
122
|
+
out_fname = commfname + conf[:out_ext]
|
123
|
+
err_fname = commfname + conf[:err_ext]
|
124
|
+
lockfname = commfname + conf[:unfinished_ext]
|
125
|
+
|
126
|
+
if conf[:skip_done_comms] && File.exists?(out_fname)
|
127
|
+
if File.exists?(lockfname)
|
128
|
+
puts "found file #{out_fname}, but a #{lockfname} also exists"
|
129
|
+
puts "will execute command '#{command}' anyway"
|
130
|
+
else
|
131
|
+
puts "found file #{commfname}, skipping command: #{command}"
|
132
|
+
STDOUT.flush
|
133
|
+
next
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
puts "waiting to execute command: #{command}"
|
138
|
+
STDOUT.flush
|
139
|
+
|
140
|
+
while free_cpus.empty? do
|
141
|
+
sleep conf[:busy_loop_sleep]
|
142
|
+
update_finished(free_cpus, comms_running, comms_executed)
|
143
|
+
end
|
144
|
+
|
145
|
+
cpu = free_cpus.pop
|
146
|
+
|
147
|
+
cproc = ChildProcess.build(
|
148
|
+
'taskset', '-c', cpu.to_s,
|
149
|
+
'time', '-f', conf[:time_fmt], '--append', '-o', out_fname,
|
150
|
+
'timeout', '--preserve-status', '-k', "#{conf[:post_timeout]}s",
|
151
|
+
"#{conf[:timeout]}s",
|
152
|
+
'sh', '-c', command
|
153
|
+
)
|
154
|
+
|
155
|
+
File.open(lockfname, 'w') {} # empty on purpose
|
156
|
+
out = File.open(out_fname, 'w')
|
157
|
+
err = File.open(err_fname, 'w')
|
158
|
+
|
159
|
+
cproc.io.stdout = out
|
160
|
+
cproc.io.stderr = err
|
161
|
+
|
162
|
+
cproc.start
|
163
|
+
|
164
|
+
comms_running << {
|
165
|
+
proc: cproc,
|
166
|
+
cpu: cpu,
|
167
|
+
lockfname: lockfname,
|
168
|
+
command: command
|
169
|
+
}
|
170
|
+
|
171
|
+
puts "command assigned to cpu#{cpu}"
|
172
|
+
STDOUT.flush
|
173
|
+
end
|
174
|
+
|
175
|
+
until comms_running.empty? do
|
176
|
+
sleep conf[:busy_loop_sleep]
|
177
|
+
update_finished(free_cpus, comms_running, comms_executed)
|
178
|
+
end
|
179
|
+
|
180
|
+
comms_executed
|
181
|
+
end
|
182
|
+
|
183
|
+
# gencommff: GENerate COMMands For Files
|
184
|
+
#
|
185
|
+
# @param comm [String] A string with 'patt' as a substring.
|
186
|
+
# @param patt [String] A string contained in 'comm'.
|
187
|
+
# @param files [Enumerable<String>] A list of strings to substitute patt at
|
188
|
+
# comm.
|
189
|
+
# @return [Array<String>] Example: gencommff('echo STR', 'STR', ['a', 'b',
|
190
|
+
# 'c']) returns ['echo a', 'echo b', 'echo c'].
|
191
|
+
def self.gencommff(comm, patt, files)
|
192
|
+
ret = []
|
193
|
+
files.each { | f | ret << comm.gsub(patt, f) }
|
194
|
+
ret
|
195
|
+
end
|
196
|
+
|
197
|
+
# Intercalate a variable number of variable sized arrays in one array.
|
198
|
+
#
|
199
|
+
# @param [Array<Array<Object>>] xss An array of arrays.
|
200
|
+
# @return [Array<Object>] An array of the same size as the sum of the size
|
201
|
+
# of all inner arrays. The values are the same (not copies) as the values
|
202
|
+
# of the array. Example: intercalate([[1, 4, 6, 7], [], [2, 5], [3]])
|
203
|
+
# returns [1, 2, 3, 4, 5, 6, 7].
|
204
|
+
def self.intercalate(xss)
|
205
|
+
ret = []
|
206
|
+
xss = xss.map { | xs | xs.reverse }
|
207
|
+
until xss.empty? do
|
208
|
+
xss.delete_if do | xs |
|
209
|
+
unless xs.empty?
|
210
|
+
ret << xs.pop
|
211
|
+
end
|
212
|
+
xs.empty?
|
213
|
+
end
|
214
|
+
end
|
215
|
+
ret
|
216
|
+
end
|
217
|
+
|
218
|
+
# Takes N shell commands and M files/parameters, execute each command of the
|
219
|
+
# N commands over the M files, save the output of each command/file
|
220
|
+
# combination, use objects provided with the command to extract relevant
|
221
|
+
# information from the output file, and group those information in a CVS
|
222
|
+
# file. Easier to understand seeing the sample_batch.rb example in action.
|
223
|
+
#
|
224
|
+
# @param comms_info [Array<Hash>] An array of hashs, each with the config
|
225
|
+
# needed to know how to deal with the command. Four required fields
|
226
|
+
# (all keys are symbols):
|
227
|
+
# command [String] A string with a sh shell command.
|
228
|
+
# pattern [String] A substring of command, will be replace by the strings
|
229
|
+
# in the paramenter 'files'.
|
230
|
+
# extractor [Extractor] An object that implements the Extractor interface.
|
231
|
+
# prefix [String] A string that will be used to prefix the extractor.names
|
232
|
+
# when they are used as column names. Improves Extractor reusability.
|
233
|
+
# @param batch_conf [Hash] Configuration used to call batch. See the
|
234
|
+
# explanation for parameter 'conf' on the documentation of the batch
|
235
|
+
# method. There are required fields for this hash parameter.
|
236
|
+
# @param conf [Hash] Lots of parameters. Here's a list:
|
237
|
+
# csvfname [String] The filename/filepath for the file that will contain
|
238
|
+
# the CSV data. Required field.
|
239
|
+
# separator [String] The separator used at the CSV file. Default: ';'.
|
240
|
+
# ic_columns [TrueClass, FalseClass] Intercalate the data returned by the
|
241
|
+
# extractors. In other words, the csv line for some file will not present
|
242
|
+
# all fields of the first command, then all fields of the second command,
|
243
|
+
# etc, but instead will present the first field of all commands, the second
|
244
|
+
# field of all commands, and so on. Default: true.
|
245
|
+
# ic_comms [TrueClass, FalseClass] Intercalate the commands execution.
|
246
|
+
# Instead of executing the first command over all files first, execute all
|
247
|
+
# the commands over the first file first. This was made to avoid
|
248
|
+
# confounding (statistical concept). If something disrupts the processing
|
249
|
+
# power for some period of time, the effect will probably be distributed
|
250
|
+
# between commands. The risk some algorithm seems better or worse than it
|
251
|
+
# really is will be reduced. For example: you are making tests at an
|
252
|
+
# notebook, the notebook becomes unplugged for a short time. The cores will
|
253
|
+
# probably enter in energy saving mode and affect the observed performance.
|
254
|
+
# If this happens when all tested commands are the same, then will seem
|
255
|
+
# that that an command had a worse performance. If this happens when the
|
256
|
+
# commands are intercalated, then maybe some instances will seem harder
|
257
|
+
# than others (what is less problematic). Default: true.
|
258
|
+
# skip_commands [TrueClass, FalseClass] If true, will not execute the
|
259
|
+
# commands and assume that the outputs are already saved. Will only execute
|
260
|
+
# the extractors over the already saved outputs, and create the CSV file
|
261
|
+
# from them. Default: false.
|
262
|
+
#
|
263
|
+
# @param files [Array<Strings>] The strings that will replace the :pattern
|
264
|
+
# on :command, for every element in comms_info.
|
265
|
+
#
|
266
|
+
# @return [NilClass,Array<String>] The return of the internal #batch
|
267
|
+
# call. Returns nil if conf[:skip_commands] was set to true.
|
268
|
+
#
|
269
|
+
# @see BatchExperiment.batch
|
270
|
+
def self.experiment(comms_info, batch_conf, conf, files)
|
271
|
+
# Throw exceptions if required configurations aren't provided.
|
272
|
+
fail 'conf[:csvfname] is not defined' unless conf[:csvfname]
|
273
|
+
|
274
|
+
# Initialize optional configurations with default values if they weren't
|
275
|
+
# provided. Don't change the conf argument, only our version of conf.
|
276
|
+
conf = conf.clone
|
277
|
+
conf[:separator] ||= ';'
|
278
|
+
conf[:ic_columns] = true if conf[:ic_columns].nil?
|
279
|
+
conf[:ic_comms] = true if conf[:ic_comms].nil?
|
280
|
+
#conf[:skip_commands] defaults to false/nil
|
281
|
+
|
282
|
+
# Get some of the batch config that we use inside here too.
|
283
|
+
out_ext = batch_conf[:out_ext] || '.out'
|
284
|
+
unfinished_ext = batch_conf[:unfinished_ext] || '.unfinished'
|
285
|
+
fname_sanitizer = batch_conf[:fname_sanitizer]
|
286
|
+
fname_sanitizer ||= BatchExperiment::FilenameSanitizer.new
|
287
|
+
|
288
|
+
# Create commands the templates and the file list.
|
289
|
+
comms_sets = []
|
290
|
+
comms_info.each do | comm_info |
|
291
|
+
comms_sets << gencommff(comm_info[:command], comm_info[:pattern], files)
|
292
|
+
end
|
293
|
+
|
294
|
+
comm_list = conf[:ic_comm] ? intercalate(comms_sets) : comms_sets.flatten
|
295
|
+
|
296
|
+
# Execute the commands (or not).
|
297
|
+
ret = batch(comm_list, batch_conf) unless conf[:skip_commands]
|
298
|
+
|
299
|
+
# Build header (first csv line, column names).
|
300
|
+
header = []
|
301
|
+
comms_info.each do | comm_info |
|
302
|
+
prefixed_names = comm_info[:extractor].names.map do | name |
|
303
|
+
(comm_info[:prefix] + ' ') << name
|
304
|
+
end
|
305
|
+
header << prefixed_names
|
306
|
+
end
|
307
|
+
header = intercalate(header) if conf[:ic_columns]
|
308
|
+
header = ['Filename'].concat(header).join(conf[:separator])
|
309
|
+
|
310
|
+
# Build body (inspect all output files an make csv lines).
|
311
|
+
body = [header]
|
312
|
+
files.each_with_index do | inst_fname, j |
|
313
|
+
line = []
|
314
|
+
comms_info.each_with_index do | comm_info, i |
|
315
|
+
command =
|
316
|
+
if conf[:ic_comm]
|
317
|
+
comm_list[(j * comms_info.size) + i]
|
318
|
+
else
|
319
|
+
comm_list[(i * files.size) + j]
|
320
|
+
end
|
321
|
+
|
322
|
+
partial_fname = fname_sanitizer.call(command)
|
323
|
+
out_fname = partial_fname + out_ext
|
324
|
+
lockfname = partial_fname + unfinished_ext
|
325
|
+
if File.exists?(out_fname)
|
326
|
+
f_content = File.open(out_fname, 'r') { | f | f.read }
|
327
|
+
line << comm_info[:extractor].extract(f_content)
|
328
|
+
else
|
329
|
+
# if the file wasn't created insert a empty column set
|
330
|
+
# of the same size the true column set would be
|
331
|
+
line << comm_info[:extractor].names.map { | _ | '' }
|
332
|
+
end
|
333
|
+
end
|
334
|
+
line = intercalate(line) if conf[:ic_columns]
|
335
|
+
body << [inst_fname].concat(line).join(conf[:separator])
|
336
|
+
end
|
337
|
+
body = body.map! { | line | line << conf[:separator] }.join("\n")
|
338
|
+
|
339
|
+
# Write CSV data into a CSV file.
|
340
|
+
File.open(conf[:csvfname], 'w') { | f | f.write(body) }
|
341
|
+
|
342
|
+
return ret
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: batch_experiment
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Henrique Becker
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-03-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: childprocess
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.5'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.5'
|
27
|
+
description: ''
|
28
|
+
email: henriquebecker91@gmail.com
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- examples/sample_batch.rb
|
34
|
+
- examples/ukp_batch.rb
|
35
|
+
- lib/batch_experiment.rb
|
36
|
+
- lib/batch_experiment/extractor.rb
|
37
|
+
- lib/batch_experiment/sample_extractors.rb
|
38
|
+
homepage: https://rubygems.org/gems/batch_experiment
|
39
|
+
licenses:
|
40
|
+
- Public Domain
|
41
|
+
- Unlicense
|
42
|
+
metadata: {}
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
requirements: []
|
58
|
+
rubyforge_project:
|
59
|
+
rubygems_version: 2.5.1
|
60
|
+
signing_key:
|
61
|
+
specification_version: 4
|
62
|
+
summary: A ruby script that distributes system commands between cpu cores, and save
|
63
|
+
their output.
|
64
|
+
test_files: []
|