benchmark_driver 0.8.6 → 0.9.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 +5 -5
- data/.travis.yml +1 -3
- data/CHANGELOG.md +9 -0
- data/Gemfile +1 -6
- data/README.md +51 -52
- data/benchmark_driver.gemspec +3 -2
- data/bin/console +4 -11
- data/examples/exec_blank.rb +2 -2
- data/examples/exec_blank_simple.rb +2 -3
- data/exe/benchmark-driver +74 -83
- data/lib/benchmark_driver.rb +12 -1
- data/lib/benchmark_driver/config.rb +36 -0
- data/lib/benchmark_driver/default_job.rb +12 -0
- data/lib/benchmark_driver/default_job_parser.rb +68 -0
- data/lib/benchmark_driver/job_parser.rb +42 -0
- data/lib/benchmark_driver/metrics.rb +17 -0
- data/lib/benchmark_driver/output.rb +27 -0
- data/lib/benchmark_driver/output/compare.rb +196 -0
- data/lib/benchmark_driver/output/markdown.rb +102 -0
- data/lib/benchmark_driver/output/simple.rb +97 -0
- data/lib/benchmark_driver/rbenv.rb +11 -0
- data/lib/benchmark_driver/ruby_interface.rb +51 -0
- data/lib/benchmark_driver/runner.rb +42 -0
- data/lib/benchmark_driver/runner/ips.rb +239 -0
- data/lib/benchmark_driver/runner/memory.rb +142 -0
- data/lib/benchmark_driver/runner/time.rb +18 -0
- data/lib/benchmark_driver/struct.rb +85 -0
- data/lib/benchmark_driver/version.rb +3 -0
- metadata +21 -33
- data/bin/bench +0 -4
- data/examples/call.rb +0 -12
- data/examples/call_blank.rb +0 -13
- data/examples/call_erb.rb +0 -33
- data/examples/call_interpolation.rb +0 -13
- data/examples/eval_blank.rb +0 -12
- data/examples/eval_blank_loop.rb +0 -13
- data/examples/eval_interpolation.rb +0 -15
- data/lib/benchmark/driver.rb +0 -101
- data/lib/benchmark/driver/benchmark_result.rb +0 -21
- data/lib/benchmark/driver/bundle_installer.rb +0 -45
- data/lib/benchmark/driver/bundler.rb +0 -12
- data/lib/benchmark/driver/configuration.rb +0 -77
- data/lib/benchmark/driver/duration_runner.rb +0 -24
- data/lib/benchmark/driver/error.rb +0 -16
- data/lib/benchmark/driver/repeatable_runner.rb +0 -18
- data/lib/benchmark/driver/ruby_dsl_parser.rb +0 -78
- data/lib/benchmark/driver/time.rb +0 -12
- data/lib/benchmark/driver/version.rb +0 -5
- data/lib/benchmark/driver/yaml_parser.rb +0 -55
- data/lib/benchmark/output.rb +0 -20
- data/lib/benchmark/output/ips.rb +0 -143
- data/lib/benchmark/output/markdown.rb +0 -73
- data/lib/benchmark/output/memory.rb +0 -57
- data/lib/benchmark/output/time.rb +0 -57
- data/lib/benchmark/runner.rb +0 -14
- data/lib/benchmark/runner/call.rb +0 -97
- data/lib/benchmark/runner/eval.rb +0 -147
- data/lib/benchmark/runner/exec.rb +0 -193
@@ -1,193 +0,0 @@
|
|
1
|
-
require 'tempfile'
|
2
|
-
require 'shellwords'
|
3
|
-
require 'benchmark/driver/benchmark_result'
|
4
|
-
require 'benchmark/driver/bundler'
|
5
|
-
require 'benchmark/driver/duration_runner'
|
6
|
-
require 'benchmark/driver/repeatable_runner'
|
7
|
-
require 'benchmark/driver/error'
|
8
|
-
require 'benchmark/driver/time'
|
9
|
-
|
10
|
-
# Run benchmark by executing another Ruby process.
|
11
|
-
#
|
12
|
-
# Multiple Ruby binaries: o
|
13
|
-
# Memory output: o
|
14
|
-
class Benchmark::Runner::Exec
|
15
|
-
# This class can provide fields in `Benchmark::Driver::BenchmarkResult` if required by output plugins.
|
16
|
-
SUPPORTED_FIELDS = [:real, :max_rss]
|
17
|
-
|
18
|
-
WARMUP_DURATION = 1
|
19
|
-
BENCHMARK_DURATION = 4
|
20
|
-
GUESS_TIMES = [1, 1_000, 1_000_000, 10_000_000, 100_000_000]
|
21
|
-
GUESS_THRESHOLD = 0.4 # 400ms
|
22
|
-
|
23
|
-
# @param [Benchmark::Driver::Configuration::RunnerOptions] options
|
24
|
-
# @param [Benchmark::Output::*] output - Object that responds to methods used in this class
|
25
|
-
def initialize(options, output:)
|
26
|
-
@options = options
|
27
|
-
@output = output
|
28
|
-
end
|
29
|
-
|
30
|
-
# @param [Benchmark::Driver::Configuration] config
|
31
|
-
def run(config)
|
32
|
-
validate_config(config)
|
33
|
-
|
34
|
-
if config.jobs.any?(&:warmup_needed?)
|
35
|
-
run_warmup(config.jobs)
|
36
|
-
end
|
37
|
-
|
38
|
-
@output.start_running
|
39
|
-
|
40
|
-
config.jobs.each do |job|
|
41
|
-
@output.running(job.name)
|
42
|
-
|
43
|
-
@options.executables.each do |executable|
|
44
|
-
result = run_benchmark(job, executable)
|
45
|
-
@output.benchmark_stats(result)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
@output.finish
|
50
|
-
end
|
51
|
-
|
52
|
-
private
|
53
|
-
|
54
|
-
def validate_config(config)
|
55
|
-
config.jobs.each do |job|
|
56
|
-
unless job.script.is_a?(String)
|
57
|
-
raise NotImplementedError.new(
|
58
|
-
"#{self.class.name} only accepts String, but got #{job.script.inspect}"
|
59
|
-
)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
# @param [Benchmark::Driver::Configuration::Job] job
|
65
|
-
# @param [Benchmark::Driver::Configuration::Executable] executable
|
66
|
-
def run_benchmark(job, executable)
|
67
|
-
fields = @output.class::REQUIRED_FIELDS
|
68
|
-
if fields == [:real]
|
69
|
-
Benchmark::Driver::RepeatableRunner.new(job).run(
|
70
|
-
runner: build_runner(executable.command),
|
71
|
-
repeat_count: @options.repeat_count,
|
72
|
-
).tap do |result|
|
73
|
-
if result.real < 0
|
74
|
-
raise Benchmark::Driver::ExecutionTimeTooShort.new(job, result.iterations)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
elsif fields == [:max_rss] # TODO: we can also capture other metrics with /usr/bin/time
|
78
|
-
raise '/usr/bin/time is not available' unless File.exist?('/usr/bin/time')
|
79
|
-
|
80
|
-
script = BenchmarkScript.new(job.prelude, job.script).full_script(job.loop_count)
|
81
|
-
with_file(script) do |script_path|
|
82
|
-
out = Benchmark::Driver::Bundler.with_clean_env { IO.popen(['/usr/bin/time', *executable.command, script_path], err: [:child, :out], &:read) }
|
83
|
-
match_data = /^(?<user>\d+.\d+)user\s+(?<system>\d+.\d+)system\s+(?<elapsed1>\d+):(?<elapsed2>\d+.\d+)elapsed.+\([^\s]+\s+(?<maxresident>\d+)maxresident\)k$/.match(out)
|
84
|
-
raise "Unexpected format given from /usr/bin/time:\n#{out}" unless match_data[:maxresident]
|
85
|
-
|
86
|
-
Benchmark::Driver::BenchmarkResult.new(job).tap do |result|
|
87
|
-
result.max_rss = Integer(match_data[:maxresident])
|
88
|
-
end
|
89
|
-
end
|
90
|
-
else
|
91
|
-
raise "Unexpected REQUIRED_FIELDS for #{self.class.name}: #{fields.inspect}"
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
# @param [Array<Benchmark::Driver::Configuration::Job>] jobs
|
96
|
-
# @return [Hash{ Benchmark::Driver::Configuration::Job => Integer }] iters_by_job
|
97
|
-
def run_warmup(jobs)
|
98
|
-
@output.start_warming
|
99
|
-
|
100
|
-
jobs.each do |job|
|
101
|
-
next if job.loop_count
|
102
|
-
@output.warming(job.name)
|
103
|
-
|
104
|
-
result = Benchmark::Driver::DurationRunner.new(job).run(
|
105
|
-
seconds: WARMUP_DURATION,
|
106
|
-
unit_iters: guess_ip100ms(job),
|
107
|
-
runner: build_runner, # TODO: should use executables instead of RbConfig.ruby
|
108
|
-
)
|
109
|
-
job.guessed_count = (result.ips.to_f * BENCHMARK_DURATION).to_i
|
110
|
-
|
111
|
-
if result.duration < 0
|
112
|
-
raise Benchmark::Driver::ExecutionTimeTooShort.new(job, result.iterations)
|
113
|
-
end
|
114
|
-
@output.warmup_stats(result)
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
# @param [Benchmark::Driver::Configuration::Job] job
|
119
|
-
def guess_ip100ms(job)
|
120
|
-
ip100ms = 0
|
121
|
-
GUESS_TIMES.each do |times|
|
122
|
-
seconds = build_runner.call(job, times) # TODO: should use executables instead of RbConfig.ruby
|
123
|
-
ip100ms = (times.to_f / (seconds * 10.0)).ceil # ceil for times=1
|
124
|
-
if GUESS_THRESHOLD < seconds
|
125
|
-
return ip100ms
|
126
|
-
end
|
127
|
-
end
|
128
|
-
if ip100ms < 0
|
129
|
-
raise Benchmark::Driver::ExecutionTimeTooShort.new(job, GUESS_TIMES.last)
|
130
|
-
end
|
131
|
-
ip100ms
|
132
|
-
end
|
133
|
-
|
134
|
-
# @param [String] path - Path to Ruby executable
|
135
|
-
# @return [Proc] - Lambda to run benchmark
|
136
|
-
def build_runner(command = [RbConfig.ruby])
|
137
|
-
lambda do |job, times|
|
138
|
-
benchmark = BenchmarkScript.new(job.prelude, job.script)
|
139
|
-
measure_seconds(command, benchmark.full_script(times)) -
|
140
|
-
measure_seconds(command, benchmark.overhead_script(times))
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
def with_file(content, &block)
|
145
|
-
Tempfile.create(File.basename(__FILE__)) do |f|
|
146
|
-
f.write(content)
|
147
|
-
f.close
|
148
|
-
block.call(f.path)
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
def measure_seconds(command, script)
|
153
|
-
with_file(script) do |path|
|
154
|
-
cmd = [*command, path].shelljoin
|
155
|
-
|
156
|
-
Benchmark::Driver::Bundler.with_clean_env do
|
157
|
-
before = Benchmark::Driver::Time.now
|
158
|
-
system(cmd, out: File::NULL)
|
159
|
-
after = Benchmark::Driver::Time.now
|
160
|
-
|
161
|
-
after.to_f - before.to_f
|
162
|
-
end
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
class BenchmarkScript < Struct.new(:prelude, :script)
|
167
|
-
BATCH_SIZE = 1000
|
168
|
-
|
169
|
-
def overhead_script(times)
|
170
|
-
raise ArgumentError.new("Negative times: #{times}") if times < 0
|
171
|
-
<<-RUBY
|
172
|
-
#{prelude}
|
173
|
-
__benchmark_driver_i = 0
|
174
|
-
while __benchmark_driver_i < #{times / BATCH_SIZE}
|
175
|
-
__benchmark_driver_i += 1
|
176
|
-
end
|
177
|
-
RUBY
|
178
|
-
end
|
179
|
-
|
180
|
-
def full_script(times)
|
181
|
-
raise ArgumentError.new("Negative times: #{times}") if times < 0
|
182
|
-
<<-RUBY
|
183
|
-
#{prelude}
|
184
|
-
__benchmark_driver_i = 0
|
185
|
-
while __benchmark_driver_i < #{times / BATCH_SIZE}
|
186
|
-
__benchmark_driver_i += 1
|
187
|
-
#{"#{script};" * BATCH_SIZE}
|
188
|
-
end
|
189
|
-
#{"#{script};" * (times % BATCH_SIZE)}
|
190
|
-
RUBY
|
191
|
-
end
|
192
|
-
end
|
193
|
-
end
|