abprof 0.2.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/.gitignore +10 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +291 -0
- data/Rakefile +10 -0
- data/abprof.gemspec +36 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/examples/alt_ruby.rb +11 -0
- data/examples/command_dsl.rb +13 -0
- data/examples/for_loop_10k.rb +11 -0
- data/examples/inline_ruby_1800.rb +11 -0
- data/examples/inline_ruby_2500.rb +11 -0
- data/examples/inlined_ruby.rb +11 -0
- data/examples/profiling_ruby.rb +11 -0
- data/examples/simple_dsl.rb +20 -0
- data/examples/sleep.rb +11 -0
- data/examples/sleep_longer.rb +9 -0
- data/examples/vanilla_ruby.rb +11 -0
- data/exe/abcompare +1 -0
- data/exe/abprof +120 -0
- data/lib/abprof.rb +280 -0
- data/lib/abprof/benchmark_dsl.rb +162 -0
- data/lib/abprof/version.rb +3 -0
- metadata +175 -0
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "abprof"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "abprof"
|
4
|
+
|
5
|
+
STDERR.puts "ABProf example: optcarrot with alt Ruby dir"
|
6
|
+
|
7
|
+
ABProf::ABWorker.iteration do
|
8
|
+
`cd ../alt_build && ./tool/runruby.rb ../optcarrot/bin/optcarrot --benchmark ../optcarrot/examples/Lan_Master.nes`
|
9
|
+
end
|
10
|
+
|
11
|
+
ABProf::ABWorker.start
|
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "abprof"
|
4
|
+
|
5
|
+
STDERR.puts "ABProf example: optcarrot with profiling Ruby dir"
|
6
|
+
|
7
|
+
ABProf::ABWorker.iteration do
|
8
|
+
`cd ../inline_ruby_1800 && ./tool/runruby.rb ../optcarrot/bin/optcarrot --benchmark ../optcarrot/examples/Lan_Master.nes`
|
9
|
+
end
|
10
|
+
|
11
|
+
ABProf::ABWorker.start
|
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "abprof"
|
4
|
+
|
5
|
+
STDERR.puts "ABProf example: optcarrot with profiling Ruby dir"
|
6
|
+
|
7
|
+
ABProf::ABWorker.iteration do
|
8
|
+
`cd ../inline_ruby_2500 && ./tool/runruby.rb ../optcarrot/bin/optcarrot --benchmark ../optcarrot/examples/Lan_Master.nes`
|
9
|
+
end
|
10
|
+
|
11
|
+
ABProf::ABWorker.start
|
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "abprof"
|
4
|
+
|
5
|
+
STDERR.puts "ABProf example: optcarrot with with-inlined-funcs Ruby dir"
|
6
|
+
|
7
|
+
ABProf::ABWorker.iteration do
|
8
|
+
`cd ../inline_func_ruby && ./tool/runruby.rb ../optcarrot/bin/optcarrot --benchmark ../optcarrot/examples/Lan_Master.nes`
|
9
|
+
end
|
10
|
+
|
11
|
+
ABProf::ABWorker.start
|
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "abprof"
|
4
|
+
|
5
|
+
STDERR.puts "ABProf example: optcarrot with profiling Ruby dir"
|
6
|
+
|
7
|
+
ABProf::ABWorker.iteration do
|
8
|
+
`cd ../profiling_ruby && ./tool/runruby.rb ../optcarrot/bin/optcarrot --benchmark ../optcarrot/examples/Lan_Master.nes`
|
9
|
+
end
|
10
|
+
|
11
|
+
ABProf::ABWorker.start
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "abprof/benchmark_dsl"
|
2
|
+
|
3
|
+
ABProf.compare do
|
4
|
+
warmup 10
|
5
|
+
max_trials 5
|
6
|
+
min_trials 3
|
7
|
+
p_value 0.01
|
8
|
+
iters_per_trial 2
|
9
|
+
fail_on_divergence true # For testing, usually
|
10
|
+
bare true
|
11
|
+
|
12
|
+
report do
|
13
|
+
10_000.times {}
|
14
|
+
end
|
15
|
+
|
16
|
+
report do
|
17
|
+
sleep 0.1
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
data/examples/sleep.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "abprof"
|
4
|
+
|
5
|
+
STDERR.puts "ABProf example: optcarrot with vanilla Ruby dir"
|
6
|
+
|
7
|
+
ABProf::ABWorker.iteration do
|
8
|
+
`cd ../vanilla_build && ./tool/runruby.rb ../optcarrot/bin/optcarrot --benchmark ../optcarrot/examples/Lan_Master.nes`
|
9
|
+
end
|
10
|
+
|
11
|
+
ABProf::ABWorker.start
|
data/exe/abcompare
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
abprof
|
data/exe/abprof
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "trollop"
|
4
|
+
require "abprof"
|
5
|
+
require "abprof/benchmark_dsl"
|
6
|
+
|
7
|
+
OPTS = Trollop::options do
|
8
|
+
banner <<BANNER
|
9
|
+
Specify a first and second command line, and (often) a p-value or other
|
10
|
+
parameters.
|
11
|
+
|
12
|
+
Example: #{$0} examples/sleep.rb examples/sleep_longer.rb
|
13
|
+
|
14
|
+
The first and second commands are the first two arguments. You'll need to
|
15
|
+
quote multi-word commands, as is normal in bash.
|
16
|
+
|
17
|
+
Specifying lots of iterations and trials, high burn-in and a low P value
|
18
|
+
is accurate, but slow.
|
19
|
+
|
20
|
+
Specifying low iterations, trials and burn-in and a high P value gives
|
21
|
+
quick, rough results early on.
|
22
|
+
|
23
|
+
Specifying more iterations per trial is good for highly variable iteration
|
24
|
+
timing.
|
25
|
+
|
26
|
+
Specifying a lower max number of trials keeps the test from running *too*
|
27
|
+
long when the two are identical.
|
28
|
+
|
29
|
+
Specifying a high burn-in is necessary when cache behavior changes timing
|
30
|
+
significantly.
|
31
|
+
|
32
|
+
Vast numbers of trials can nearly always occasionally show differences
|
33
|
+
*somewhere* along the line, just by random chance. To avoid this, pick how
|
34
|
+
many samples first, run them all in one go, and then just check the p value
|
35
|
+
once.
|
36
|
+
|
37
|
+
A p value is often interpreted as the probability we got a wrong answer.
|
38
|
+
That's an oversimplification, but not (usually) a terrible one.
|
39
|
+
BANNER
|
40
|
+
opt :debug1, "Print first-process output to console"
|
41
|
+
opt :debug2, "Print second-process output to console"
|
42
|
+
opt :bare, "Use bare command-line commands, no Ruby harness", :default => ($0["compare"])
|
43
|
+
opt :pvalue, "P value (certainty) for Welch's T test", :default => 0.05
|
44
|
+
opt :burnin, "'Burn in' repetitions before real trials", :default => 10
|
45
|
+
opt :min_trials, "Minimum number of sample sets from each process", :default => 1
|
46
|
+
opt :max_trials, "Maximum number of sample sets from each process", :default => 20
|
47
|
+
opt :iters_per_trial, "Iterations per sample set", :default => 10
|
48
|
+
opt :print_samples, "Print all sample values for later analysis.", :default => false
|
49
|
+
opt :fail_on_divergence, "Return a non-zero code if pvalue is greater than specified."
|
50
|
+
end
|
51
|
+
|
52
|
+
if ARGV.length != 2
|
53
|
+
puts "Must specify both commands as normal arguments!"
|
54
|
+
exit -1
|
55
|
+
end
|
56
|
+
|
57
|
+
command1, command2 = ARGV
|
58
|
+
|
59
|
+
# Create DSL configuration for known properties,
|
60
|
+
# but don't actually run the sampling yet.
|
61
|
+
bm_inst = ABProf.compare(:no_at_exit => true) do
|
62
|
+
pvalue OPTS[:pvalue]
|
63
|
+
burnin OPTS[:burnin]
|
64
|
+
min_trials OPTS[:min_trials]
|
65
|
+
max_trials OPTS[:max_trials]
|
66
|
+
iters_per_trial OPTS[:iters_per_trial]
|
67
|
+
bare OPTS[:bare]
|
68
|
+
# No fail_on_divergence - we do this manually for the CLI utilities
|
69
|
+
|
70
|
+
report_command command1
|
71
|
+
report_command command2
|
72
|
+
end
|
73
|
+
|
74
|
+
state = bm_inst.run_sampling
|
75
|
+
p_val = state[:p_tests][-1]
|
76
|
+
diverged = false
|
77
|
+
if p_val < bm_inst.p_value
|
78
|
+
puts "Based on measured P value #{p_val}, we believe there is a speed difference."
|
79
|
+
puts "As of end of run, p value is #{p_val}. Now run more times to check, or with lower p."
|
80
|
+
|
81
|
+
summary11 = ABProf.summarize("mean", state[:samples][0])
|
82
|
+
summary12 = ABProf.summarize("median", state[:samples][0])
|
83
|
+
summary21 = ABProf.summarize("mean", state[:samples][1])
|
84
|
+
summary22 = ABProf.summarize("median", state[:samples][1])
|
85
|
+
|
86
|
+
fastest = "1"
|
87
|
+
command = bm_inst.reports[0]
|
88
|
+
mean_times = summary21 / summary11
|
89
|
+
median_times = summary22 / summary12
|
90
|
+
if summary11 > summary21
|
91
|
+
fastest = "2"
|
92
|
+
command = bm_inst.reports[1]
|
93
|
+
mean_times = summary11 / summary21
|
94
|
+
median_times = summary12 / summary22
|
95
|
+
end
|
96
|
+
|
97
|
+
puts "Lower (faster?) process is #{fastest}, command line: #{command.inspect}"
|
98
|
+
puts "Lower command is (very) roughly #{median_times} times lower (faster?) -- assuming linear sampling."
|
99
|
+
|
100
|
+
print "\n"
|
101
|
+
puts "Process 1 mean result: #{summary11}"
|
102
|
+
puts "Process 1 median result: #{summary12}"
|
103
|
+
puts "Process 2 mean result: #{summary21}"
|
104
|
+
puts "Process 2 median result: #{summary22}"
|
105
|
+
else
|
106
|
+
puts "Based on measured P value #{p_val} and threshold #{bm_inst.pvalue}, we believe there is"
|
107
|
+
puts "no significant difference detectable with this set of trials."
|
108
|
+
puts "If you believe there is a small difference that wasn't detected, try raising the number"
|
109
|
+
puts "of iterations per trial, or the maximum number of trials."
|
110
|
+
diverged = true
|
111
|
+
end
|
112
|
+
|
113
|
+
if OPTS[:print_samples]
|
114
|
+
puts "Samples for P1: #{state[:samples][0].inspect}"
|
115
|
+
puts "Samples for P2: #{state[:samples][1].inspect}"
|
116
|
+
end
|
117
|
+
|
118
|
+
exit 2 if diverged && OPTS[:fail_on_divergence]
|
119
|
+
|
120
|
+
# Otherwise, return success
|
data/lib/abprof.rb
ADDED
@@ -0,0 +1,280 @@
|
|
1
|
+
require "abprof/version"
|
2
|
+
|
3
|
+
require "multi_json"
|
4
|
+
|
5
|
+
# Protocol:
|
6
|
+
# Controller sends "ITERS [integer]\n"
|
7
|
+
# Controller sends "QUIT\n" when done
|
8
|
+
# Test process responds with "NOT OK\n" or crashes for bad results
|
9
|
+
# Test process responds with "VALUE 27.23432" to explicitly return a single value
|
10
|
+
# Test process responds with "VALUES [1.4, 2.714, 39.4, -71.4]" to explicitly return many values
|
11
|
+
# QUIT requires no response.
|
12
|
+
|
13
|
+
module ABProf
|
14
|
+
def self.debug
|
15
|
+
@debug
|
16
|
+
end
|
17
|
+
def self.debug=(new_val)
|
18
|
+
@debug = new_val
|
19
|
+
end
|
20
|
+
|
21
|
+
# These are primarily for DSL use.
|
22
|
+
PROPERTIES = [ :debug, :pvalue, :iters_per_trial, :min_trials, :max_trials, :burnin, :bare, :fail_on_divergence ]
|
23
|
+
|
24
|
+
# This class is used by programs that are *being* profiled.
|
25
|
+
# It's necessarily a singleton since it needs to control STDIN.
|
26
|
+
# The bare mode can do without it, but it's needed for harness
|
27
|
+
# processes.
|
28
|
+
class ABWorker
|
29
|
+
def debug string
|
30
|
+
STDERR.puts(string) if ABProf.debug
|
31
|
+
end
|
32
|
+
def self.debug string
|
33
|
+
STDERR.puts(string) if ABProf.debug
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.iteration(&block)
|
37
|
+
@iter_block = block
|
38
|
+
@return = :none
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.iteration_with_return_value(&block)
|
42
|
+
@iter_block = block
|
43
|
+
@return = :per_iteration
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.n_interations_with_return_value(&block)
|
47
|
+
@iter_block = block
|
48
|
+
@return = :per_n_iterations
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.run_n(n)
|
52
|
+
debug "WORKER #{Process.pid}: running #{n} times"
|
53
|
+
|
54
|
+
case @return
|
55
|
+
when :none
|
56
|
+
n.times do
|
57
|
+
@iter_block.call
|
58
|
+
end
|
59
|
+
STDOUT.write "OK\n"
|
60
|
+
when :per_iteration
|
61
|
+
values = (0..(n-1)).map { |i| @iter_block.call.to_f }
|
62
|
+
STDOUT.write "VALUES #{values.inspect}"
|
63
|
+
when :per_n_iterations
|
64
|
+
value = @iter_block.call(n)
|
65
|
+
if value.respond_to?(:each)
|
66
|
+
# Return array of numbers
|
67
|
+
STDOUT.write "VALUES #{value.to_a.inspect}"
|
68
|
+
else
|
69
|
+
# Return single number
|
70
|
+
STDOUT.write "VALUE #{value.to_f}"
|
71
|
+
end
|
72
|
+
else
|
73
|
+
raise "Unknown @return value #{@return.inspect} inside abprof!"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.read_once
|
78
|
+
debug "WORKER #{Process.pid}: read loop"
|
79
|
+
@input ||= ""
|
80
|
+
@input += (STDIN.gets || "")
|
81
|
+
debug "WORKER #{Process.pid}: Input #{@input.inspect}"
|
82
|
+
if @input["\n"]
|
83
|
+
command, @input = @input.split("\n", 2)
|
84
|
+
debug "WORKER #{Process.pid}: command: #{command.inspect}"
|
85
|
+
if command == "QUIT"
|
86
|
+
exit 0
|
87
|
+
elsif command["ITERS"]
|
88
|
+
iters = command[5..-1].to_i
|
89
|
+
values = run_n iters
|
90
|
+
STDOUT.flush # Why does this synchronous file descriptor not flush when given a string with a newline? Ugh!
|
91
|
+
debug "WORKER #{Process.pid}: finished command ITERS: OK"
|
92
|
+
else
|
93
|
+
STDERR.puts "Unrecognized ABProf command: #{command.inspect}!"
|
94
|
+
exit -1
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.start
|
100
|
+
loop do
|
101
|
+
read_once
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
SUMMARY_TYPES = {
|
107
|
+
"mean" => proc { |samples|
|
108
|
+
samples.inject(0.0, &:+) / samples.size
|
109
|
+
},
|
110
|
+
"median" => proc { |samples|
|
111
|
+
sz = samples.size
|
112
|
+
sorted = samples.sort
|
113
|
+
if sz % 2 == 1
|
114
|
+
# For odd-length, take middle element
|
115
|
+
sorted[ samples.size / 2 ]
|
116
|
+
else
|
117
|
+
# For even length, mean of two middle elements
|
118
|
+
(sorted[ sz / 2 ] + sorted[ sz / 2 + 1 ]) / 2.0
|
119
|
+
end
|
120
|
+
},
|
121
|
+
}
|
122
|
+
SUMMARY_METHODS = SUMMARY_TYPES.keys
|
123
|
+
def self.summarize(method, samples)
|
124
|
+
raise "Unknown summary method #{method.inspect}!" unless SUMMARY_METHODS.include?(method.to_s)
|
125
|
+
method_proc = SUMMARY_TYPES[method.to_s]
|
126
|
+
method_proc.call(samples)
|
127
|
+
end
|
128
|
+
|
129
|
+
class ABBareProcess
|
130
|
+
attr_reader :last_run
|
131
|
+
attr_reader :last_iters
|
132
|
+
|
133
|
+
def debug string
|
134
|
+
STDERR.puts(string) if @debug && ABProf.debug
|
135
|
+
end
|
136
|
+
|
137
|
+
def initialize command_line, opts = {}
|
138
|
+
@command = command_line
|
139
|
+
@debug = opts[:debug]
|
140
|
+
end
|
141
|
+
|
142
|
+
def quit
|
143
|
+
# No-op
|
144
|
+
end
|
145
|
+
|
146
|
+
def kill
|
147
|
+
# No-op
|
148
|
+
end
|
149
|
+
|
150
|
+
def run_iters(n)
|
151
|
+
t_start = t_end = nil
|
152
|
+
debug "Controller of #{@pid}: #{n} ITERS"
|
153
|
+
|
154
|
+
state = :succeeded
|
155
|
+
n.times do
|
156
|
+
if @command.respond_to?(:call)
|
157
|
+
t_start = Time.now
|
158
|
+
@command.call
|
159
|
+
t_end = Time.now
|
160
|
+
elsif @command.respond_to?(:to_s)
|
161
|
+
t_start = Time.now
|
162
|
+
system(@command.to_s)
|
163
|
+
t_end = Time.now
|
164
|
+
unless $?.success?
|
165
|
+
STDERR.puts "Failing process #{@pid} after failed iteration(s), error code #{state.inspect}"
|
166
|
+
# How to handle error with no self.kill?
|
167
|
+
raise "Failure from command #{@command.inspect}, dying!"
|
168
|
+
end
|
169
|
+
else
|
170
|
+
raise "Don't know how to execute bare object: #{@command.inspect}!"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
@last_run = [(t_end - t_start).to_f]
|
174
|
+
@last_iters = n
|
175
|
+
|
176
|
+
@last_run
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
class ABHarnessProcess
|
181
|
+
attr_reader :last_run
|
182
|
+
attr_reader :last_iters
|
183
|
+
|
184
|
+
def debug string
|
185
|
+
STDERR.puts(string) if @debug && ABProf.debug
|
186
|
+
end
|
187
|
+
|
188
|
+
def initialize command_line, opts = {}
|
189
|
+
debug "Controller of nobody yet: SPAWN"
|
190
|
+
@in_reader, @in_writer = IO.pipe
|
191
|
+
@out_reader, @out_writer = IO.pipe
|
192
|
+
@in_writer.sync = true
|
193
|
+
@out_writer.sync = true
|
194
|
+
|
195
|
+
@pid = fork do
|
196
|
+
STDOUT.reopen(@out_writer)
|
197
|
+
STDIN.reopen(@in_reader)
|
198
|
+
@out_reader.close
|
199
|
+
@in_writer.close
|
200
|
+
if command_line.respond_to?(:call)
|
201
|
+
puts "Caution! An ABProf Harness process (non-bare) is being used with a block. This is almost never what you want!"
|
202
|
+
command_line.call
|
203
|
+
elsif command_line.respond_to?(:to_s)
|
204
|
+
exec command_line.to_s
|
205
|
+
else
|
206
|
+
raise "Don't know how to execute benchmark code: #{command_line.inspect}!"
|
207
|
+
end
|
208
|
+
exit! 0
|
209
|
+
end
|
210
|
+
@out_writer.close
|
211
|
+
@in_reader.close
|
212
|
+
|
213
|
+
@debug = opts[:debug]
|
214
|
+
debug "Controller spawned #{@pid} (debug: #{@debug.inspect})"
|
215
|
+
end
|
216
|
+
|
217
|
+
def quit
|
218
|
+
debug "Controller of #{@pid}: QUIT"
|
219
|
+
@in_writer.write "QUIT\n"
|
220
|
+
end
|
221
|
+
|
222
|
+
def kill
|
223
|
+
debug "Controller of #{@pid}: DIE"
|
224
|
+
::Process.detach @pid
|
225
|
+
::Process.kill "TERM", @pid
|
226
|
+
end
|
227
|
+
|
228
|
+
def run_iters(n)
|
229
|
+
debug "Controller of #{@pid}: #{n} ITERS"
|
230
|
+
@in_writer.write "ITERS #{n.to_i}\n"
|
231
|
+
|
232
|
+
ignored_out = 0
|
233
|
+
state = :failed
|
234
|
+
t_start = Time.now
|
235
|
+
loop do
|
236
|
+
# Read and block
|
237
|
+
output = @out_reader.gets
|
238
|
+
ignored_out += output.length
|
239
|
+
puts "Controller of #{@pid} out: #{output.inspect}" if @debug
|
240
|
+
debug "Controller of #{@pid} out: #{output.inspect}"
|
241
|
+
if output =~ /^VALUES/ # These anchors match newlines, too
|
242
|
+
state = :succeeded
|
243
|
+
vals = MultiJson.load output[7..-1]
|
244
|
+
raise "Must return an array value from iterations!" unless vals.is_a?(Array)
|
245
|
+
raise "Must return an array of numbers from iterations!" unless vals[0].is_a?(Numeric)
|
246
|
+
@last_run = vals
|
247
|
+
elsif output =~ /^VALUE/ # These anchors match newlines, too
|
248
|
+
state = :succeeded
|
249
|
+
val = output[6..-1].to_f
|
250
|
+
raise "Must return a number from iterations!" unless val.is_a?(Numeric)
|
251
|
+
@last_run = [ val ]
|
252
|
+
elsif output =~ /^OK$/ # These anchors match newlines, too
|
253
|
+
state = :succeeded_get_time
|
254
|
+
break
|
255
|
+
end
|
256
|
+
if output =~ /^NOT OK$/ # These anchors match newlines, too
|
257
|
+
# Failed, break
|
258
|
+
state = :explicit_not_ok
|
259
|
+
break
|
260
|
+
end
|
261
|
+
if ignored_out > 10_000
|
262
|
+
# 10k of output and no OK? Bail with failed state.
|
263
|
+
state = :too_much_output_without_status
|
264
|
+
break
|
265
|
+
end
|
266
|
+
end
|
267
|
+
t_end = Time.now
|
268
|
+
unless [:succeeded, :succeeded_get_time].include?(state)
|
269
|
+
self.kill
|
270
|
+
STDERR.puts "Killing process #{@pid} after failed iterations, error code #{state.inspect}"
|
271
|
+
end
|
272
|
+
|
273
|
+
@last_run = [ (t_end - t_start).to_f ] if state == :succeeded_get_time
|
274
|
+
@last_iters = n
|
275
|
+
|
276
|
+
@last_run
|
277
|
+
end
|
278
|
+
|
279
|
+
end
|
280
|
+
end
|