benchmark_driver 0.10.16 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +5 -5
  2. data/.rspec +1 -0
  3. data/.travis.yml +1 -1
  4. data/CHANGELOG.md +16 -0
  5. data/README.md +25 -9
  6. data/Rakefile +5 -48
  7. data/benchmark-driver/.gitignore +12 -0
  8. data/benchmark-driver/CODE_OF_CONDUCT.md +74 -0
  9. data/benchmark-driver/Gemfile +6 -0
  10. data/benchmark-driver/LICENSE.txt +21 -0
  11. data/benchmark-driver/README.md +8 -0
  12. data/benchmark-driver/Rakefile +1 -0
  13. data/benchmark-driver/benchmark-driver.gemspec +21 -0
  14. data/benchmark-driver/bin/console +14 -0
  15. data/benchmark-driver/bin/setup +8 -0
  16. data/benchmark-driver/lib/benchmark-driver.rb +1 -0
  17. data/benchmark-driver/lib/benchmark/driver.rb +1 -0
  18. data/benchmark_driver.gemspec +3 -1
  19. data/exe/benchmark-driver +3 -3
  20. data/lib/benchmark_driver/config.rb +3 -3
  21. data/lib/benchmark_driver/metric.rb +70 -0
  22. data/lib/benchmark_driver/output.rb +62 -8
  23. data/lib/benchmark_driver/output/compare.rb +68 -52
  24. data/lib/benchmark_driver/output/markdown.rb +21 -16
  25. data/lib/benchmark_driver/output/record.rb +26 -21
  26. data/lib/benchmark_driver/output/simple.rb +21 -16
  27. data/lib/benchmark_driver/runner.rb +5 -3
  28. data/lib/benchmark_driver/runner/command_stdout.rb +19 -19
  29. data/lib/benchmark_driver/runner/ips.rb +30 -29
  30. data/lib/benchmark_driver/runner/memory.rb +15 -16
  31. data/lib/benchmark_driver/runner/once.rb +11 -15
  32. data/lib/benchmark_driver/runner/recorded.rb +28 -21
  33. data/lib/benchmark_driver/runner/ruby_stdout.rb +157 -0
  34. data/lib/benchmark_driver/runner/time.rb +7 -10
  35. data/lib/benchmark_driver/version.rb +1 -1
  36. metadata +46 -16
  37. data/examples/exec_blank.rb +0 -13
  38. data/examples/exec_blank_simple.rb +0 -13
  39. data/examples/yaml/array_duration_time.yml +0 -2
  40. data/examples/yaml/array_loop.yml +0 -3
  41. data/examples/yaml/blank_hash.yml +0 -8
  42. data/examples/yaml/blank_hash_array.yml +0 -10
  43. data/examples/yaml/blank_loop.yml +0 -9
  44. data/examples/yaml/blank_string.yml +0 -6
  45. data/examples/yaml/blank_string_array.yml +0 -8
  46. data/examples/yaml/example_multi.yml +0 -6
  47. data/examples/yaml/example_single.yml +0 -4
  48. data/lib/benchmark_driver/metrics.rb +0 -17
@@ -1,15 +1,14 @@
1
1
  class BenchmarkDriver::Output::Simple
2
2
  NAME_LENGTH = 8
3
3
 
4
- # @param [BenchmarkDriver::Metrics::Type] metrics_type
5
- attr_writer :metrics_type
4
+ # @param [Array<BenchmarkDriver::Metric>] metrics
5
+ attr_writer :metrics
6
6
 
7
- # @param [Array<BenchmarkDriver::*::Job>] jobs
8
- # @param [Array<BenchmarkDriver::Config::Executable>] executables
9
- def initialize(jobs:, executables:)
10
- @jobs = jobs
11
- @executables = executables
12
- @name_length = jobs.map { |j| j.name.size }.max
7
+ # @param [Array<String>] job_names
8
+ # @param [Array<String>] context_names
9
+ def initialize(job_names:, context_names:)
10
+ @context_names = context_names
11
+ @name_length = job_names.map(&:size).max
13
12
  end
14
13
 
15
14
  def with_warmup(&block)
@@ -25,13 +24,13 @@ class BenchmarkDriver::Output::Simple
25
24
  @with_benchmark = true
26
25
  without_stdout_buffering do
27
26
  # Show header
28
- $stdout.puts "benchmark results (#{@metrics_type.unit}):"
27
+ $stdout.puts "#{@metrics.first.name} (#{@metrics.first.unit}):"
29
28
 
30
29
  # Show executable names
31
- if @executables.size > 1
30
+ if @context_names.size > 1
32
31
  $stdout.print("#{' ' * @name_length} ")
33
- @executables.each do |executable|
34
- $stdout.print("%#{NAME_LENGTH}s " % executable.name)
32
+ @context_name.each do |context_name|
33
+ $stdout.print("%#{NAME_LENGTH}s " % context_name)
35
34
  end
36
35
  $stdout.puts
37
36
  end
@@ -42,7 +41,7 @@ class BenchmarkDriver::Output::Simple
42
41
  @with_benchmark = false
43
42
  end
44
43
 
45
- # @param [BenchmarkDriver::*::Job] job
44
+ # @param [BenchmarkDriver::Job] job
46
45
  def with_job(job, &block)
47
46
  if @with_benchmark
48
47
  $stdout.print("%-#{@name_length}s " % job.name)
@@ -54,10 +53,16 @@ class BenchmarkDriver::Output::Simple
54
53
  end
55
54
  end
56
55
 
57
- # @param [BenchmarkDriver::Metrics] metrics
58
- def report(metrics)
56
+ # @param [BenchmarkDriver::Context] context
57
+ def with_context(context, &block)
58
+ block.call
59
+ end
60
+
61
+ # @param [Float] value
62
+ # @param [BenchmarkDriver::Metric] metic
63
+ def report(value:, metric:)
59
64
  if @with_benchmark
60
- $stdout.print("%#{NAME_LENGTH}s " % humanize(metrics.value))
65
+ $stdout.print("%#{NAME_LENGTH}s " % humanize(value))
61
66
  else
62
67
  $stdout.print '.'
63
68
  end
@@ -5,6 +5,7 @@ module BenchmarkDriver
5
5
  require 'benchmark_driver/runner/memory'
6
6
  require 'benchmark_driver/runner/once'
7
7
  require 'benchmark_driver/runner/recorded'
8
+ require 'benchmark_driver/runner/ruby_stdout'
8
9
  require 'benchmark_driver/runner/time'
9
10
  end
10
11
 
@@ -28,9 +29,10 @@ module BenchmarkDriver
28
29
 
29
30
  jobs.group_by(&:class).each do |klass, jobs_group|
30
31
  runner = runner_for(klass)
31
- output = Output.find(config.output_type).new(
32
- jobs: jobs,
33
- executables: config.executables,
32
+ output = Output.new(
33
+ type: config.output_type,
34
+ job_names: jobs.map(&:name),
35
+ context_names: config.executables.map(&:name),
34
36
  )
35
37
  with_clean_env do
36
38
  runner.new(config: runner_config, output: output).run(jobs)
@@ -1,17 +1,17 @@
1
1
  require 'benchmark_driver/struct'
2
- require 'benchmark_driver/metrics'
2
+ require 'benchmark_driver/metric'
3
3
  require 'tempfile'
4
4
  require 'shellwords'
5
5
  require 'open3'
6
6
 
7
- # Run only once, for testing
7
+ # Use stdout of ruby command
8
8
  class BenchmarkDriver::Runner::CommandStdout
9
9
  # JobParser returns this, `BenchmarkDriver::Runner.runner_for` searches "*::Job"
10
10
  Job = ::BenchmarkDriver::Struct.new(
11
11
  :name, # @param [String] name - This is mandatory for all runner
12
12
  :command, # @param [Array<String>]
13
13
  :working_directory, # @param [String,NilClass]
14
- :metrics_type, # @param [BenchmarkDriver::Metrics::Type]
14
+ :metrics, # @param [Array<BenchmarkDriver::Metric>]
15
15
  :stdout_to_metrics, # @param [String]
16
16
  )
17
17
  # Dynamically fetched and used by `BenchmarkDriver::JobParser.parse`
@@ -26,24 +26,27 @@ class BenchmarkDriver::Runner::CommandStdout
26
26
  name: name,
27
27
  command: command.shellsplit,
28
28
  working_directory: working_directory,
29
- metrics_type: parse_metrics_type(metrics_type),
29
+ metrics: parse_metrics(metrics_type),
30
30
  stdout_to_metrics: stdout_to_metrics,
31
31
  )
32
32
  end
33
33
 
34
34
  private
35
35
 
36
- def parse_metrics_type(unit:, larger_better: nil, worse_word: nil)
37
- BenchmarkDriver::Metrics::Type.new(
36
+ def parse_metrics(unit:, name: nil, larger_better: nil, worse_word: nil)
37
+ name ||= unit
38
+ metric = BenchmarkDriver::Metric.new(
39
+ name: name,
38
40
  unit: unit,
39
41
  larger_better: larger_better,
40
42
  worse_word: worse_word,
41
43
  )
44
+ [metric]
42
45
  end
43
46
  end
44
47
 
45
48
  # @param [BenchmarkDriver::Config::RunnerConfig] config
46
- # @param [BenchmarkDriver::Output::*] output
49
+ # @param [BenchmarkDriver::Output] output
47
50
  def initialize(config:, output:)
48
51
  @config = config
49
52
  @output = output
@@ -52,14 +55,14 @@ class BenchmarkDriver::Runner::CommandStdout
52
55
  # This method is dynamically called by `BenchmarkDriver::JobRunner.run`
53
56
  # @param [Array<BenchmarkDriver::Default::Job>] jobs
54
57
  def run(jobs)
55
- metrics_type = jobs.first.metrics_type
56
- @output.metrics_type = metrics_type
58
+ metric = jobs.first.metrics.first
59
+ @output.metrics = [metric]
57
60
 
58
61
  @output.with_benchmark do
59
62
  jobs.each do |job|
60
- @output.with_job(job) do
63
+ @output.with_job(name: job.name) do
61
64
  @config.executables.each do |exec|
62
- best_value = with_repeat(metrics_type) do
65
+ best_value = with_repeat(metric) do
63
66
  stdout = with_chdir(job.working_directory) do
64
67
  with_ruby_prefix(exec) { execute(*exec.command, *job.command) }
65
68
  end
@@ -69,12 +72,9 @@ class BenchmarkDriver::Runner::CommandStdout
69
72
  ).metrics_value
70
73
  end
71
74
 
72
- @output.report(
73
- BenchmarkDriver::Metrics.new(
74
- value: best_value,
75
- executable: exec,
76
- )
77
- )
75
+ @output.with_context(name: exec.name, executable: exec) do
76
+ @output.report(value: best_value, metric: metric)
77
+ end
78
78
  end
79
79
  end
80
80
  end
@@ -108,12 +108,12 @@ class BenchmarkDriver::Runner::CommandStdout
108
108
  end
109
109
 
110
110
  # Return multiple times and return the best metrics
111
- def with_repeat(metrics_type, &block)
111
+ def with_repeat(metric, &block)
112
112
  values = @config.repeat_count.times.map do
113
113
  block.call
114
114
  end
115
115
  values.sort_by do |value|
116
- if metrics_type.larger_better
116
+ if metric.larger_better
117
117
  value
118
118
  else
119
119
  -value
@@ -1,5 +1,5 @@
1
1
  require 'benchmark_driver/struct'
2
- require 'benchmark_driver/metrics'
2
+ require 'benchmark_driver/metric'
3
3
  require 'benchmark_driver/default_job'
4
4
  require 'benchmark_driver/default_job_parser'
5
5
  require 'tempfile'
@@ -12,10 +12,10 @@ class BenchmarkDriver::Runner::Ips
12
12
  # Dynamically fetched and used by `BenchmarkDriver::JobParser.parse`
13
13
  JobParser = BenchmarkDriver::DefaultJobParser.for(Job)
14
14
 
15
- METRICS_TYPE = BenchmarkDriver::Metrics::Type.new(unit: 'i/s')
15
+ METRIC = BenchmarkDriver::Metric.new(name: 'Iteration per second', unit: 'i/s')
16
16
 
17
17
  # @param [BenchmarkDriver::Config::RunnerConfig] config
18
- # @param [BenchmarkDriver::Output::*] output
18
+ # @param [BenchmarkDriver::Output] output
19
19
  def initialize(config:, output:)
20
20
  @config = config
21
21
  @output = output
@@ -24,19 +24,23 @@ class BenchmarkDriver::Runner::Ips
24
24
  # This method is dynamically called by `BenchmarkDriver::JobRunner.run`
25
25
  # @param [Array<BenchmarkDriver::Default::Job>] jobs
26
26
  def run(jobs)
27
- set_metrics_type
27
+ @output.metrics = [metric]
28
28
 
29
29
  if jobs.any? { |job| job.loop_count.nil? }
30
30
  @output.with_warmup do
31
31
  jobs = jobs.map do |job|
32
32
  next job if job.loop_count # skip warmup if loop_count is set
33
33
 
34
- @output.with_job(job) do
35
- result = run_warmup(job, exec: job.runnable_execs(@config.executables).first)
36
- metrics = build_metrics(result)
37
- @output.report(metrics)
34
+ @output.with_job(name: job.name) do
35
+ executable = job.runnable_execs(@config.executables).first
36
+ duration, loop_count = run_warmup(job, exec: executable)
37
+ value, duration = value_duration(duration: duration, loop_count: loop_count)
38
38
 
39
- loop_count = (result.fetch(:loop_count).to_f * @config.run_duration / result.fetch(:duration)).floor
39
+ @output.with_context(name: executable.name, executable: executable, duration: duration, loop_count: loop_count) do
40
+ @output.report(value: value, metric: metric)
41
+ end
42
+
43
+ loop_count = (loop_count.to_f * @config.run_duration / duration).floor
40
44
  Job.new(job.to_h.merge(loop_count: loop_count))
41
45
  end
42
46
  end
@@ -45,12 +49,14 @@ class BenchmarkDriver::Runner::Ips
45
49
 
46
50
  @output.with_benchmark do
47
51
  jobs.each do |job|
48
- @output.with_job(job) do
52
+ @output.with_job(name: job.name) do
49
53
  job.runnable_execs(@config.executables).each do |exec|
50
- best_metrics = with_repeat(@config.repeat_count) do
54
+ value, duration = with_repeat(@config.repeat_count) do
51
55
  run_benchmark(job, exec: exec)
52
56
  end
53
- @output.report(best_metrics)
57
+ @output.with_context(name: exec.name, executable: exec, duration: duration) do
58
+ @output.report(value: value, metric: metric)
59
+ end
54
60
  end
55
61
  end
56
62
  end
@@ -71,23 +77,23 @@ class BenchmarkDriver::Runner::Ips
71
77
  second_warmup_duration: @config.run_duration / 3.0, # default: 1.0
72
78
  )
73
79
 
74
- hash = Tempfile.open(['benchmark_driver-', '.rb']) do |f|
80
+ duration, loop_count = Tempfile.open(['benchmark_driver-', '.rb']) do |f|
75
81
  with_script(warmup.render(result: f.path)) do |path|
76
82
  execute(*exec.command, path)
77
83
  end
78
84
  eval(f.read)
79
85
  end
80
86
 
81
- hash.merge(executable: exec)
87
+ [duration, loop_count]
82
88
  end
83
89
 
84
90
  # Return multiple times and return the best metrics
85
91
  def with_repeat(repeat_times, &block)
86
- all_metrics = repeat_times.times.map do
92
+ value_durations = repeat_times.times.map do
87
93
  block.call
88
94
  end
89
- all_metrics.sort_by do |metrics|
90
- metrics.value
95
+ value_durations.sort_by do |value, _|
96
+ metric.larger_better ? value : -value
91
97
  end.last
92
98
  end
93
99
 
@@ -109,25 +115,20 @@ class BenchmarkDriver::Runner::Ips
109
115
  Float(f.read)
110
116
  end
111
117
 
112
- build_metrics(
118
+ value_duration(
113
119
  loop_count: job.loop_count,
114
120
  duration: duration,
115
- executable: exec,
116
121
  )
117
122
  end
118
123
 
119
124
  # This method is overridden by BenchmarkDriver::Runner::Time
120
- def build_metrics(duration:, executable:, loop_count:)
121
- BenchmarkDriver::Metrics.new(
122
- value: loop_count.to_f / duration,
123
- duration: duration,
124
- executable: executable,
125
- )
125
+ def metric
126
+ METRIC
126
127
  end
127
128
 
128
- # This method is overridden by BenchmarkDriver::Runner::Time
129
- def set_metrics_type
130
- @output.metrics_type = METRICS_TYPE
129
+ # Overridden by BenchmarkDriver::Runner::Time
130
+ def value_duration(duration:, loop_count:)
131
+ [loop_count.to_f / duration, duration]
131
132
  end
132
133
 
133
134
  def with_script(script)
@@ -186,7 +187,7 @@ end
186
187
 
187
188
  #{teardown}
188
189
 
189
- File.write(#{result.dump}, { duration: __bmdv_duration, loop_count: __bmdv_loops }.inspect)
190
+ File.write(#{result.dump}, [__bmdv_duration, __bmdv_loops].inspect)
190
191
  RUBY
191
192
  end
192
193
  end
@@ -1,5 +1,5 @@
1
1
  require 'benchmark_driver/struct'
2
- require 'benchmark_driver/metrics'
2
+ require 'benchmark_driver/metric'
3
3
  require 'benchmark_driver/default_job'
4
4
  require 'benchmark_driver/default_job_parser'
5
5
  require 'tempfile'
@@ -12,10 +12,12 @@ class BenchmarkDriver::Runner::Memory
12
12
  # Dynamically fetched and used by `BenchmarkDriver::JobParser.parse`
13
13
  JobParser = BenchmarkDriver::DefaultJobParser.for(Job)
14
14
 
15
- METRICS_TYPE = BenchmarkDriver::Metrics::Type.new(unit: 'bytes', larger_better: false, worse_word: 'larger')
15
+ METRIC = BenchmarkDriver::Metric.new(
16
+ name: 'Max resident set size', unit: 'bytes', larger_better: false, worse_word: 'larger',
17
+ )
16
18
 
17
19
  # @param [BenchmarkDriver::Config::RunnerConfig] config
18
- # @param [BenchmarkDriver::Output::*] output
20
+ # @param [BenchmarkDriver::Output] output
19
21
  def initialize(config:, output:)
20
22
  @config = config
21
23
  @output = output
@@ -29,7 +31,7 @@ class BenchmarkDriver::Runner::Memory
29
31
  raise "memory output is not supported for '#{Etc.uname[:sysname]}' for now"
30
32
  end
31
33
 
32
- @output.metrics_type = METRICS_TYPE
34
+ @output.metrics = [METRIC]
33
35
 
34
36
  if jobs.any? { |job| job.loop_count.nil? }
35
37
  jobs = jobs.map do |job|
@@ -39,12 +41,14 @@ class BenchmarkDriver::Runner::Memory
39
41
 
40
42
  @output.with_benchmark do
41
43
  jobs.each do |job|
42
- @output.with_job(job) do
44
+ @output.with_job(name: job.name) do
43
45
  job.runnable_execs(@config.executables).each do |exec|
44
- best_metrics = with_repeat(@config.repeat_count) do
46
+ best_value = with_repeat(@config.repeat_count) do
45
47
  run_benchmark(job, exec: exec)
46
48
  end
47
- @output.report(best_metrics)
49
+ @output.with_context(name: exec.name, executable: exec, loop_count: job.loop_count) do
50
+ @output.report(value: best_value, metric: METRIC)
51
+ end
48
52
  end
49
53
  end
50
54
  end
@@ -53,14 +57,12 @@ class BenchmarkDriver::Runner::Memory
53
57
 
54
58
  private
55
59
 
56
- # Return multiple times and return the best metrics
60
+ # Return multiple times and return the best value (smallest usage)
57
61
  def with_repeat(repeat_times, &block)
58
- all_metrics = repeat_times.times.map do
62
+ values = repeat_times.times.map do
59
63
  block.call
60
64
  end
61
- all_metrics.sort_by do |metrics|
62
- metrics.value
63
- end.first
65
+ values.sort.first
64
66
  end
65
67
 
66
68
  # @param [BenchmarkDriver::Runner::Ips::Job] job - loop_count is not nil
@@ -81,10 +83,7 @@ class BenchmarkDriver::Runner::Memory
81
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(output)
82
84
  raise "Unexpected format given from /usr/bin/time:\n#{out}" unless match_data[:maxresident]
83
85
 
84
- BenchmarkDriver::Metrics.new(
85
- value: Integer(match_data[:maxresident]) * 1000.0, # kilobytes -> bytes
86
- executable: exec,
87
- )
86
+ Integer(match_data[:maxresident]) * 1000.0 # kilobytes -> bytes
88
87
  end
89
88
 
90
89
  def with_script(script)
@@ -1,5 +1,5 @@
1
1
  require 'benchmark_driver/struct'
2
- require 'benchmark_driver/metrics'
2
+ require 'benchmark_driver/metric'
3
3
  require 'benchmark_driver/default_job'
4
4
  require 'benchmark_driver/default_job_parser'
5
5
  require 'tempfile'
@@ -12,10 +12,10 @@ class BenchmarkDriver::Runner::Once
12
12
  # Dynamically fetched and used by `BenchmarkDriver::JobParser.parse`
13
13
  JobParser = BenchmarkDriver::DefaultJobParser.for(Job)
14
14
 
15
- METRICS_TYPE = BenchmarkDriver::Metrics::Type.new(unit: 'i/s')
15
+ METRIC = BenchmarkDriver::Metric.new(name: 'Iteration per second', unit: 'i/s')
16
16
 
17
17
  # @param [BenchmarkDriver::Config::RunnerConfig] config
18
- # @param [BenchmarkDriver::Output::*] output
18
+ # @param [BenchmarkDriver::Output] output
19
19
  def initialize(config:, output:)
20
20
  @config = config
21
21
  @output = output
@@ -24,7 +24,7 @@ class BenchmarkDriver::Runner::Once
24
24
  # This method is dynamically called by `BenchmarkDriver::JobRunner.run`
25
25
  # @param [Array<BenchmarkDriver::Default::Job>] jobs
26
26
  def run(jobs)
27
- @output.metrics_type = METRICS_TYPE
27
+ @output.metrics = [METRIC]
28
28
 
29
29
  jobs = jobs.map do |job|
30
30
  Job.new(job.to_h.merge(loop_count: 1)) # to show this on output
@@ -32,10 +32,12 @@ class BenchmarkDriver::Runner::Once
32
32
 
33
33
  @output.with_benchmark do
34
34
  jobs.each do |job|
35
- @output.with_job(job) do
35
+ @output.with_job(name: job.name) do
36
36
  job.runnable_execs(@config.executables).each do |exec|
37
- metrics = run_benchmark(job, exec: exec) # no repeat support
38
- @output.report(metrics)
37
+ duration = run_benchmark(job, exec: exec) # no repeat support
38
+ @output.with_context(name: exec.name, executable: exec, duration: duration, loop_count: 1) do
39
+ @output.report(value: 1.0 / duration, metric: METRIC)
40
+ end
39
41
  end
40
42
  end
41
43
  end
@@ -46,7 +48,7 @@ class BenchmarkDriver::Runner::Once
46
48
 
47
49
  # @param [BenchmarkDriver::Runner::Ips::Job] job - loop_count is not nil
48
50
  # @param [BenchmarkDriver::Config::Executable] exec
49
- # @return [BenchmarkDriver::Metrics]
51
+ # @return [Float] duration
50
52
  def run_benchmark(job, exec:)
51
53
  benchmark = BenchmarkScript.new(
52
54
  prelude: job.prelude,
@@ -55,18 +57,12 @@ class BenchmarkDriver::Runner::Once
55
57
  loop_count: job.loop_count,
56
58
  )
57
59
 
58
- duration = Tempfile.open(['benchmark_driver-', '.rb']) do |f|
60
+ Tempfile.open(['benchmark_driver-', '.rb']) do |f|
59
61
  with_script(benchmark.render(result: f.path)) do |path|
60
62
  execute(*exec.command, path)
61
63
  end
62
64
  Float(f.read)
63
65
  end
64
-
65
- BenchmarkDriver::Metrics.new(
66
- value: 1.0 / duration,
67
- duration: duration,
68
- executable: exec,
69
- )
70
66
  end
71
67
 
72
68
  def with_script(script)