abprof 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|