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
data/lib/benchmark_driver.rb
CHANGED
@@ -1 +1,12 @@
|
|
1
|
-
require '
|
1
|
+
require 'benchmark_driver/config'
|
2
|
+
require 'benchmark_driver/job_parser'
|
3
|
+
require 'benchmark_driver/output'
|
4
|
+
require 'benchmark_driver/rbenv'
|
5
|
+
require 'benchmark_driver/ruby_interface'
|
6
|
+
require 'benchmark_driver/runner'
|
7
|
+
require 'benchmark_driver/version'
|
8
|
+
|
9
|
+
require 'benchmark'
|
10
|
+
def Benchmark.driver(**args, &block)
|
11
|
+
BenchmarkDriver::RubyInterface.run(**args, &block)
|
12
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'benchmark_driver/struct'
|
2
|
+
|
3
|
+
module BenchmarkDriver
|
4
|
+
# All CLI options
|
5
|
+
Config = ::BenchmarkDriver::Struct.new(
|
6
|
+
:runner_type, # @param [String]
|
7
|
+
:output_type, # @param [String]
|
8
|
+
:paths, # @param [Array<String>]
|
9
|
+
:executables, # @param [Array<BenchmarkDriver::Config::Executable>]
|
10
|
+
:filters, # @param [Array<Regexp>]
|
11
|
+
:repeat_count, # @param [Integer]
|
12
|
+
:run_duration, # @param [Integer]
|
13
|
+
defaults: {
|
14
|
+
runner_type: 'ips',
|
15
|
+
output_type: 'compare',
|
16
|
+
filters: [],
|
17
|
+
repeat_count: 1,
|
18
|
+
run_duration: 3,
|
19
|
+
},
|
20
|
+
)
|
21
|
+
|
22
|
+
# Subset of FullConfig passed to JobRunner
|
23
|
+
Config::RunnerConfig = ::BenchmarkDriver::Struct.new(
|
24
|
+
:executables, # @param [Array<BenchmarkDriver::Config::Executable>]
|
25
|
+
:repeat_count, # @param [Integer]
|
26
|
+
:run_duration, # @param [Integer]
|
27
|
+
)
|
28
|
+
|
29
|
+
Config::Executable = ::BenchmarkDriver::Struct.new(
|
30
|
+
:name, # @param [String]
|
31
|
+
:command, # @param [Array<String>]
|
32
|
+
)
|
33
|
+
Config.defaults[:executables] = [
|
34
|
+
BenchmarkDriver::Config::Executable.new(name: RUBY_VERSION, command: [RbConfig.ruby]),
|
35
|
+
]
|
36
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'benchmark_driver/struct'
|
2
|
+
|
3
|
+
module BenchmarkDriver
|
4
|
+
DefaultJob = ::BenchmarkDriver::Struct.new(
|
5
|
+
:name, # @param [String] name
|
6
|
+
:script, # @param [String] benchmark
|
7
|
+
:prelude, # @param [String,nil] prelude (optional)
|
8
|
+
:teardown, # @param [String,nil] after (optional)
|
9
|
+
:loop_count, # @param [Integer,nil] loop_count (optional)
|
10
|
+
defaults: { prelude: '', teardown: '' },
|
11
|
+
)
|
12
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module BenchmarkDriver
|
2
|
+
module DefaultJobParser
|
3
|
+
# Build default JobParser for given job klass
|
4
|
+
def self.for(klass)
|
5
|
+
Module.new.tap do |parser|
|
6
|
+
class << parser
|
7
|
+
include DefaultJobParser
|
8
|
+
end
|
9
|
+
parser.define_singleton_method(:job_class) do
|
10
|
+
klass
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# This method is dynamically called by `BenchmarkDriver::JobParser.parse`
|
16
|
+
# @param [String] prelude
|
17
|
+
# @param [String,Array<String,Hash{ Symbol => String }>,Hash{ Symbol => String }] benchmark
|
18
|
+
# @param [String] teardown
|
19
|
+
# @param [Integer] loop_count
|
20
|
+
# @return [Array<BenchmarkDriver::Default::Job>]
|
21
|
+
def parse(prelude: nil, benchmark:, teardown: nil, loop_count: nil)
|
22
|
+
parse_benchmark(benchmark).each do |job|
|
23
|
+
job.prelude.prepend("#{prelude}\n") if prelude
|
24
|
+
job.teardown.prepend("#{teardown}\n") if teardown
|
25
|
+
job.loop_count ||= loop_count
|
26
|
+
end.each(&:freeze)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# @param [String,Array<String,Hash{ Symbol => String }>,Hash{ Symbol => String }] benchmark
|
32
|
+
def parse_benchmark(benchmark)
|
33
|
+
case benchmark
|
34
|
+
when String
|
35
|
+
[parse_job(benchmark)]
|
36
|
+
when Array
|
37
|
+
benchmark.map { |b| parse_job(b) }
|
38
|
+
when Hash
|
39
|
+
benchmark.map do |key, value|
|
40
|
+
job_class.new(name: key.to_s, script: value)
|
41
|
+
end
|
42
|
+
else
|
43
|
+
raise ArgumentError.new("benchmark must be String, Array or Hash, but got: #{benchmark.inspect}")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# @param [String,Hash{ Symbol => String }>] bench
|
48
|
+
def parse_job(benchmark)
|
49
|
+
case benchmark
|
50
|
+
when String
|
51
|
+
job_class.new(name: benchmark, script: benchmark)
|
52
|
+
when Hash
|
53
|
+
parse_job_hash(benchmark)
|
54
|
+
else
|
55
|
+
raise ArgumentError.new("Expected String or Hash in element of benchmark, but got: #{benchmark.inspect}")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def parse_job_hash(name: nil, prelude: '', script:, teardown: '', loop_count: nil)
|
60
|
+
name ||= script
|
61
|
+
job_class.new(name: name, prelude: prelude, script: script, teardown: teardown, loop_count: loop_count)
|
62
|
+
end
|
63
|
+
|
64
|
+
def job_class
|
65
|
+
raise NotImplementedError # override this
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'benchmark_driver/runner'
|
2
|
+
|
3
|
+
module BenchmarkDriver
|
4
|
+
class << JobParser = Module.new
|
5
|
+
# @param [Hash] config
|
6
|
+
def parse(config)
|
7
|
+
config = symbolize_keys(config)
|
8
|
+
type = config.fetch(:type)
|
9
|
+
if !type.is_a?(String)
|
10
|
+
raise ArgumentError.new("Invalid type: #{config[:type].inspect} (expected String)")
|
11
|
+
elsif !type.match(/\A[A-Za-z0-9_]+\z/)
|
12
|
+
raise ArgumentError.new("Invalid type: #{config[:type].inspect} (expected to include only [A-Za-z0-9_])")
|
13
|
+
end
|
14
|
+
config.delete(:type)
|
15
|
+
|
16
|
+
# Dynamic dispatch for plugin support
|
17
|
+
::BenchmarkDriver.const_get("Runner::#{camelize(type)}::JobParser", false).parse(config)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def camelize(str)
|
23
|
+
str.split('_').map(&:capitalize).join
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param [Object] config
|
27
|
+
def symbolize_keys(config)
|
28
|
+
case config
|
29
|
+
when Hash
|
30
|
+
config.dup.tap do |hash|
|
31
|
+
hash.keys.each do |key|
|
32
|
+
hash[key.to_sym] = symbolize_keys(hash.delete(key))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
when Array
|
36
|
+
config.map { |c| symbolize_keys(c) }
|
37
|
+
else
|
38
|
+
config
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'benchmark_driver/struct'
|
2
|
+
|
3
|
+
# All benchmark results should be expressed by this model.
|
4
|
+
module BenchmarkDriver
|
5
|
+
Metrics = ::BenchmarkDriver::Struct.new(
|
6
|
+
:value, # @param [Float] - The main field of benchmark result
|
7
|
+
:executable, # @param [BenchmarkDriver::Config::Executable] - Measured Ruby executable
|
8
|
+
:duration, # @param [Float,nil] - Time taken to run the script (optional)
|
9
|
+
)
|
10
|
+
|
11
|
+
Metrics::Type = ::BenchmarkDriver::Struct.new(
|
12
|
+
:unit, # @param [String] - A label of unit for the value.
|
13
|
+
:larger_better, # @param [TrueClass,FalseClass] - If true, larger value is preferred when measured multiple times.
|
14
|
+
:worse_word, # @param [String] - A label shown when the value is worse.
|
15
|
+
defaults: { larger_better: true, worse_word: 'slower' },
|
16
|
+
)
|
17
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module BenchmarkDriver
|
2
|
+
module Output
|
3
|
+
require 'benchmark_driver/output/compare'
|
4
|
+
require 'benchmark_driver/output/markdown'
|
5
|
+
require 'benchmark_driver/output/simple'
|
6
|
+
end
|
7
|
+
|
8
|
+
class << Output
|
9
|
+
# BenchmarkDriver::Output is pluggable.
|
10
|
+
# Create `BenchmarkDriver::Output::Foo` as benchmark_dirver-output-foo.gem and specify `-o foo`.
|
11
|
+
#
|
12
|
+
# @param [String] type
|
13
|
+
def find(type)
|
14
|
+
if type.include?(':')
|
15
|
+
raise ArgumentError.new("Output type '#{type}' cannot contain ':'")
|
16
|
+
end
|
17
|
+
|
18
|
+
::BenchmarkDriver::Output.const_get(camelize(type), false)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def camelize(str)
|
24
|
+
str.split('_').map(&:capitalize).join
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
# Compare output like benchmark-ips
|
2
|
+
class BenchmarkDriver::Output::Compare
|
3
|
+
NAME_LENGTH = 20
|
4
|
+
|
5
|
+
# @param [Array<BenchmarkDriver::*::Job>] jobs
|
6
|
+
# @param [Array<BenchmarkDriver::Config::Executable>] executables
|
7
|
+
# @param [BenchmarkDriver::Metrics::Type] metrics_type
|
8
|
+
def initialize(jobs:, executables:, metrics_type:)
|
9
|
+
@jobs = jobs
|
10
|
+
@executables = executables
|
11
|
+
@metrics_type = metrics_type
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param [BenchmarkDriver::Metrics] metrics
|
15
|
+
def with_warmup(&block)
|
16
|
+
without_stdout_buffering do
|
17
|
+
$stdout.puts 'Warming up --------------------------------------'
|
18
|
+
# TODO: show exec name if it has multiple ones
|
19
|
+
block.call
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param [BenchmarkDriver::Metrics] metrics
|
24
|
+
def with_benchmark(&block)
|
25
|
+
@metrics_by_job = Hash.new { |h, k| h[k] = [] }
|
26
|
+
|
27
|
+
without_stdout_buffering do
|
28
|
+
$stdout.puts 'Calculating -------------------------------------'
|
29
|
+
if @executables.size > 1
|
30
|
+
$stdout.print(' ' * NAME_LENGTH)
|
31
|
+
@executables.each do |executable|
|
32
|
+
$stdout.print(' %10s ' % executable.name)
|
33
|
+
end
|
34
|
+
$stdout.puts
|
35
|
+
end
|
36
|
+
|
37
|
+
block.call
|
38
|
+
end
|
39
|
+
ensure
|
40
|
+
if @executables.size > 1
|
41
|
+
compare_executables
|
42
|
+
elsif @jobs.size > 1
|
43
|
+
compare_jobs
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# @param [BenchmarkDriver::*::Job] job
|
48
|
+
def with_job(job, &block)
|
49
|
+
if job.name.length > NAME_LENGTH
|
50
|
+
$stdout.puts(job.name)
|
51
|
+
else
|
52
|
+
$stdout.print("%#{NAME_LENGTH}s" % job.name)
|
53
|
+
end
|
54
|
+
@current_job = job
|
55
|
+
@job_metrics = []
|
56
|
+
block.call
|
57
|
+
ensure
|
58
|
+
$stdout.print(@metrics_type.unit)
|
59
|
+
if job.loop_count
|
60
|
+
$stdout.print(" - #{humanize(job.loop_count)} times")
|
61
|
+
if @job_metrics.all? { |metrics| metrics.duration }
|
62
|
+
$stdout.print(" in")
|
63
|
+
show_durations
|
64
|
+
end
|
65
|
+
end
|
66
|
+
$stdout.puts
|
67
|
+
end
|
68
|
+
|
69
|
+
# @param [BenchmarkDriver::Metrics] metrics
|
70
|
+
def report(metrics)
|
71
|
+
if defined?(@metrics_by_job)
|
72
|
+
@metrics_by_job[@current_job] << metrics
|
73
|
+
end
|
74
|
+
|
75
|
+
@job_metrics << metrics
|
76
|
+
$stdout.print("#{humanize(metrics.value, [10, metrics.executable.name.length].max)} ")
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def show_durations
|
82
|
+
@job_metrics.each do |metrics|
|
83
|
+
$stdout.print(' %3.6fs' % metrics.duration)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Show pretty seconds / clocks too. As it takes long width, it's shown only with a single executable.
|
87
|
+
if @job_metrics.size == 1
|
88
|
+
metrics = @job_metrics.first
|
89
|
+
sec = metrics.duration
|
90
|
+
iter = @current_job.loop_count
|
91
|
+
if File.exist?('/proc/cpuinfo') && (clks = estimate_clock(sec, iter)) < 1_000
|
92
|
+
$stdout.print(" (#{pretty_sec(sec, iter)}/i, #{clks}clocks/i)")
|
93
|
+
else
|
94
|
+
$stdout.print(" (#{pretty_sec(sec, iter)}/i)")
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# benchmark_driver ouputs logs ASAP. This enables sync flag for it.
|
100
|
+
def without_stdout_buffering
|
101
|
+
sync, $stdout.sync = $stdout.sync, true
|
102
|
+
yield
|
103
|
+
ensure
|
104
|
+
$stdout.sync = sync
|
105
|
+
end
|
106
|
+
|
107
|
+
def humanize(value, width = 10)
|
108
|
+
if value < 0
|
109
|
+
raise ArgumentError.new("Negative value: #{value.inspect}")
|
110
|
+
end
|
111
|
+
|
112
|
+
scale = (Math.log10(value) / 3).to_i
|
113
|
+
prefix = "%#{width}.3f" % (value.to_f / (1000 ** scale))
|
114
|
+
suffix =
|
115
|
+
case scale
|
116
|
+
when 1; 'k'
|
117
|
+
when 2; 'M'
|
118
|
+
when 3; 'G'
|
119
|
+
when 4; 'T'
|
120
|
+
when 5; 'Q'
|
121
|
+
else # < 1000 or > 10^15, no scale or suffix
|
122
|
+
scale = 0
|
123
|
+
return " #{prefix}"
|
124
|
+
end
|
125
|
+
"#{prefix}#{suffix}"
|
126
|
+
end
|
127
|
+
|
128
|
+
def pretty_sec(sec, iter)
|
129
|
+
r = Rational(sec, iter)
|
130
|
+
case
|
131
|
+
when r >= 1
|
132
|
+
"#{'%3.2f' % r.to_f}s"
|
133
|
+
when r >= 1/1000r
|
134
|
+
"#{'%3.2f' % (r * 1_000).to_f}ms"
|
135
|
+
when r >= 1/1000_000r
|
136
|
+
"#{'%3.2f' % (r * 1_000_000).to_f}μs"
|
137
|
+
else
|
138
|
+
"#{'%3.2f' % (r * 1_000_000_000).to_f}ns"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def estimate_clock sec, iter
|
143
|
+
hz = File.read('/proc/cpuinfo').scan(/cpu MHz\s+:\s+([\d\.]+)/){|(f)| break hz = Rational(f.to_f) * 1_000_000}
|
144
|
+
r = Rational(sec, iter)
|
145
|
+
Integer(r/(1/hz))
|
146
|
+
end
|
147
|
+
|
148
|
+
def compare_jobs
|
149
|
+
$stdout.puts "\nComparison:"
|
150
|
+
results = @metrics_by_job.map { |job, metrics| Result.new(job: job, metrics: metrics.first) }
|
151
|
+
show_results(results, show_executable: false)
|
152
|
+
end
|
153
|
+
|
154
|
+
def compare_executables
|
155
|
+
$stdout.puts "\nComparison:"
|
156
|
+
|
157
|
+
@metrics_by_job.each do |job, metrics|
|
158
|
+
$stdout.puts("%#{NAME_LENGTH + 2 + 11}s" % job.name)
|
159
|
+
results = metrics.map { |metrics| Result.new(job: job, metrics: metrics) }
|
160
|
+
show_results(results, show_executable: true)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# @param [Array<BenchmarkDriver::Output::Compare::Result>] results
|
165
|
+
# @param [TrueClass,FalseClass] show_executable
|
166
|
+
def show_results(results, show_executable:)
|
167
|
+
results = results.sort_by do |result|
|
168
|
+
if @metrics_type.larger_better
|
169
|
+
-result.metrics.value
|
170
|
+
else
|
171
|
+
result.metrics.value
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
first = results.first
|
176
|
+
results.each do |result|
|
177
|
+
if result != first
|
178
|
+
if @metrics_type.larger_better
|
179
|
+
ratio = (first.metrics.value / result.metrics.value)
|
180
|
+
else
|
181
|
+
ratio = (result.metrics.value / first.metrics.value)
|
182
|
+
end
|
183
|
+
slower = "- %.2fx #{@metrics_type.worse_word}" % ratio
|
184
|
+
end
|
185
|
+
if show_executable
|
186
|
+
name = result.metrics.executable.name
|
187
|
+
else
|
188
|
+
name = result.job.name
|
189
|
+
end
|
190
|
+
$stdout.puts("%#{NAME_LENGTH}s: %11.1f %s #{slower}" % [name, result.metrics.value, @metrics_type.unit])
|
191
|
+
end
|
192
|
+
$stdout.puts
|
193
|
+
end
|
194
|
+
|
195
|
+
Result = ::BenchmarkDriver::Struct.new(:job, :metrics)
|
196
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
class BenchmarkDriver::Output::Markdown
|
2
|
+
NAME_LENGTH = 8
|
3
|
+
|
4
|
+
# @param [Array<BenchmarkDriver::*::Job>] jobs
|
5
|
+
# @param [Array<BenchmarkDriver::Config::Executable>] executables
|
6
|
+
# @param [BenchmarkDriver::Metrics::Type] metrics_type
|
7
|
+
def initialize(jobs:, executables:, metrics_type:)
|
8
|
+
@jobs = jobs
|
9
|
+
@executables = executables
|
10
|
+
@metrics_type = metrics_type
|
11
|
+
@name_length = jobs.map { |j| j.name.size }.max
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param [BenchmarkDriver::Metrics] metrics
|
15
|
+
def with_warmup(&block)
|
16
|
+
without_stdout_buffering do
|
17
|
+
$stdout.print 'warming up'
|
18
|
+
block.call
|
19
|
+
end
|
20
|
+
ensure
|
21
|
+
$stdout.puts
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param [BenchmarkDriver::Metrics] metrics
|
25
|
+
def with_benchmark(&block)
|
26
|
+
@with_benchmark = true
|
27
|
+
without_stdout_buffering do
|
28
|
+
# Show header
|
29
|
+
$stdout.puts "# benchmark results (#{@metrics_type.unit})\n\n"
|
30
|
+
|
31
|
+
# Show executable names
|
32
|
+
$stdout.print("|#{' ' * @name_length} ")
|
33
|
+
@executables.each do |executable|
|
34
|
+
$stdout.print("|%#{NAME_LENGTH}s" % executable.name) # same size as humanize
|
35
|
+
end
|
36
|
+
$stdout.puts('|')
|
37
|
+
|
38
|
+
# Show header separator
|
39
|
+
$stdout.print("|:#{'-' * (@name_length - 1)}--")
|
40
|
+
@executables.each do |executable|
|
41
|
+
$stdout.print("|:#{'-' * (NAME_LENGTH - 1)}") # same size as humanize
|
42
|
+
end
|
43
|
+
$stdout.puts('|')
|
44
|
+
|
45
|
+
block.call
|
46
|
+
end
|
47
|
+
rescue
|
48
|
+
@with_benchmark = false
|
49
|
+
end
|
50
|
+
|
51
|
+
# @param [BenchmarkDriver::*::Job] job
|
52
|
+
def with_job(job, &block)
|
53
|
+
if @with_benchmark
|
54
|
+
$stdout.print("|%-#{@name_length}s " % job.name)
|
55
|
+
end
|
56
|
+
block.call
|
57
|
+
ensure
|
58
|
+
if @with_benchmark
|
59
|
+
$stdout.puts('|')
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# @param [BenchmarkDriver::Metrics] metrics
|
64
|
+
def report(metrics)
|
65
|
+
if @with_benchmark
|
66
|
+
$stdout.print("|%#{NAME_LENGTH}s" % humanize(metrics.value))
|
67
|
+
else
|
68
|
+
$stdout.print '.'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# benchmark_driver ouputs logs ASAP. This enables sync flag for it.
|
75
|
+
def without_stdout_buffering
|
76
|
+
sync, $stdout.sync = $stdout.sync, true
|
77
|
+
yield
|
78
|
+
ensure
|
79
|
+
$stdout.sync = sync
|
80
|
+
end
|
81
|
+
|
82
|
+
def humanize(value)
|
83
|
+
if value < 0
|
84
|
+
raise ArgumentError.new("Negative value: #{value.inspect}")
|
85
|
+
end
|
86
|
+
|
87
|
+
scale = (Math.log10(value) / 3).to_i
|
88
|
+
prefix = "%6.3f" % (value.to_f / (1000 ** scale))
|
89
|
+
suffix =
|
90
|
+
case scale
|
91
|
+
when 1; 'k'
|
92
|
+
when 2; 'M'
|
93
|
+
when 3; 'G'
|
94
|
+
when 4; 'T'
|
95
|
+
when 5; 'Q'
|
96
|
+
else # < 1000 or > 10^15, no scale or suffix
|
97
|
+
scale = 0
|
98
|
+
return " #{prefix}"
|
99
|
+
end
|
100
|
+
"#{prefix}#{suffix}"
|
101
|
+
end
|
102
|
+
end
|