benchmark_driver 0.10.16 → 0.11.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.
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,5 +1,5 @@
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
 
@@ -8,51 +8,58 @@ class BenchmarkDriver::Runner::Recorded
8
8
  # JobParser returns this, `BenchmarkDriver::Runner.runner_for` searches "*::Job"
9
9
  Job = ::BenchmarkDriver::Struct.new(
10
10
  :name, # @param [String] name - This is mandatory for all runner
11
- :job, # @param [BenchmarkDriver::Runner::*::Job]
12
- :warmup_metrics, # @param [Hash]
13
- :benchmark_metrics, # @param [Hash]
14
- :metrics_type, # @param [BenchmarkDriver::Metrics::Type]
11
+ :warmup_results, # @param [Hash{ BenchmarkDriver::Context => Array<BenchmarkDriver::Metric> } }]
12
+ :benchmark_results, # @param [Hash{ BenchmarkDriver::Context => Array<BenchmarkDriver::Metric> } }]
13
+ :metrics, # @param [Array<BenchmarkDriver::Metric>]
15
14
  )
16
15
  # Dynamically fetched and used by `BenchmarkDriver::JobParser.parse`
17
16
  class << JobParser = Module.new
18
- # @param [Hash] metrics_by_job
19
- # @param [BenchmarkDriver::Metrics::Type] metrics_type
20
- def parse(metrics_by_job:, metrics_type:)
21
- metrics_by_job.map do |job, metrics_hash|
17
+ # @param [Hash{ String => Hash{ TrueClass,FalseClass => Hash{ BenchmarkDriver::Context => Hash{ BenchmarkDriver::Metric => Float } } } }] job_warmup_context_metric_value
18
+ # @param [BenchmarkDriver::Metrics::Type] metrics
19
+ def parse(job_warmup_context_metric_value:, metrics:)
20
+ job_warmup_context_metric_value.map do |job_name, warmup_context_values|
22
21
  Job.new(
23
- name: job.name,
24
- job: job,
25
- warmup_metrics: metrics_hash.fetch(:warmup, []),
26
- benchmark_metrics: metrics_hash.fetch(:benchmark),
27
- metrics_type: metrics_type,
22
+ name: job_name,
23
+ warmup_results: warmup_context_values.fetch(true, {}),
24
+ benchmark_results: warmup_context_values.fetch(false, {}),
25
+ metrics: metrics,
28
26
  )
29
27
  end
30
28
  end
31
29
  end
32
30
 
33
31
  # @param [BenchmarkDriver::Config::RunnerConfig] config
34
- # @param [BenchmarkDriver::Output::*] output
32
+ # @param [BenchmarkDriver::Output] output
35
33
  def initialize(config:, output:)
36
34
  @config = config
37
35
  @output = output
38
36
  end
39
37
 
40
38
  # This method is dynamically called by `BenchmarkDriver::JobRunner.run`
41
- # @param [Array<BenchmarkDriver::Default::Job>] jobs
39
+ # @param [Array<BenchmarkDriver::Runner::Recorded::Job>] record
42
40
  def run(records)
43
- @output.metrics_type = records.first.metrics_type
41
+ @output.metrics = records.first.metrics
44
42
 
45
43
  records.each do |record|
46
- unless record.warmup_metrics.empty?
44
+ unless record.warmup_results.empty?
47
45
  # TODO:
48
46
  end
49
47
  end
50
48
 
51
49
  @output.with_benchmark do
52
50
  records.each do |record|
53
- @output.with_job(record.job) do
54
- record.benchmark_metrics.each do |metrics|
55
- @output.report(metrics)
51
+ @output.with_job(name: record.name) do
52
+ record.benchmark_results.each do |context, metric_values|
53
+ @output.with_context(
54
+ name: context.name,
55
+ executable: context.executable,
56
+ duration: context.duration,
57
+ loop_count: context.loop_count,
58
+ ) do
59
+ metric_values.each do |metric, value|
60
+ @output.report(value: value, metric: metric)
61
+ end
62
+ end
56
63
  end
57
64
  end
58
65
  end
@@ -0,0 +1,157 @@
1
+ require 'benchmark_driver/struct'
2
+ require 'benchmark_driver/metric'
3
+ require 'tempfile'
4
+ require 'shellwords'
5
+ require 'open3'
6
+
7
+ # Use stdout of ruby command
8
+ class BenchmarkDriver::Runner::RubyStdout
9
+ # JobParser returns this, `BenchmarkDriver::Runner.runner_for` searches "*::Job"
10
+ Job = ::BenchmarkDriver::Struct.new(
11
+ :name, # @param [String] name - This is mandatory for all runner
12
+ :command, # @param [Array<String>]
13
+ :working_directory, # @param [String,NilClass]
14
+ :metrics, # @param [Array<BenchmarkDriver::Metric>]
15
+ :value_from_stdout, # @param [String]
16
+ :environment_from_stdout # @param [Hash{ String => String }]
17
+ )
18
+ # Dynamically fetched and used by `BenchmarkDriver::JobParser.parse`
19
+ class << JobParser = Module.new
20
+ # @param [String] name
21
+ # @param [String] command
22
+ # @param [String,NilClass] working_directory
23
+ # @param [Hash] metrics_type
24
+ # @param [String] stdout_to_metrics
25
+ def parse(name:, command:, working_directory: nil, metrics:, environment: {})
26
+ unless metrics.is_a?(Hash)
27
+ raise ArgumentError.new("metrics must be Hash, but got #{metrics.class}")
28
+ end
29
+ if metrics.size == 0
30
+ raise ArgumentError.new('At least one metric must be specified"')
31
+ elsif metrics.size != 1
32
+ raise NotImplementedError.new('Having multiple metrics is not supported yet')
33
+ end
34
+
35
+ metric, value_from_stdout = parse_metric(*metrics.first)
36
+ environment_from_stdout = Hash[environment.map { |k, v| [k, parse_environment(v)] }]
37
+
38
+ Job.new(
39
+ name: name,
40
+ command: command.shellsplit,
41
+ working_directory: working_directory,
42
+ metrics: [metric],
43
+ value_from_stdout: value_from_stdout,
44
+ environment_from_stdout: environment_from_stdout,
45
+ )
46
+ end
47
+
48
+ private
49
+
50
+ def parse_metric(name, unit:, from_stdout:, larger_better: true, worse_word: 'slower')
51
+ metric = BenchmarkDriver::Metric.new(
52
+ name: name,
53
+ unit: unit,
54
+ larger_better: larger_better,
55
+ worse_word: worse_word,
56
+ )
57
+ [metric, from_stdout]
58
+ end
59
+
60
+ def parse_environment(from_stdout:)
61
+ from_stdout
62
+ end
63
+ end
64
+
65
+ # @param [BenchmarkDriver::Config::RunnerConfig] config
66
+ # @param [BenchmarkDriver::Output] output
67
+ def initialize(config:, output:)
68
+ @config = config
69
+ @output = output
70
+ end
71
+
72
+ # This method is dynamically called by `BenchmarkDriver::JobRunner.run`
73
+ # @param [Array<BenchmarkDriver::Default::Job>] jobs
74
+ def run(jobs)
75
+ metric = jobs.first.metrics.first
76
+ @output.metrics = [metric]
77
+
78
+ @output.with_benchmark do
79
+ jobs.each do |job|
80
+ @output.with_job(name: job.name) do
81
+ @config.executables.each do |exec|
82
+ best_value, environment = with_repeat(metric) do
83
+ stdout = with_chdir(job.working_directory) do
84
+ with_ruby_prefix(exec) { execute(*exec.command, *job.command) }
85
+ end
86
+ script = StdoutToMetrics.new(
87
+ stdout: stdout,
88
+ value_from_stdout: job.value_from_stdout,
89
+ environment_from_stdout: job.environment_from_stdout,
90
+ )
91
+ [script.value, script.environment]
92
+ end
93
+
94
+ @output.with_context(name: exec.name, executable: exec, environment: environment) do
95
+ @output.report(value: best_value, metric: metric)
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ private
104
+
105
+ def with_ruby_prefix(executable, &block)
106
+ env = ENV.to_h.dup
107
+ ENV['PATH'] = "#{File.dirname(executable.command.first)}:#{ENV['PATH']}"
108
+ block.call
109
+ ensure
110
+ ENV.replace(env)
111
+ end
112
+
113
+ def with_chdir(working_directory, &block)
114
+ if working_directory
115
+ Dir.chdir(working_directory) { block.call }
116
+ else
117
+ block.call
118
+ end
119
+ end
120
+
121
+ def execute(*args)
122
+ stdout, stderr, status = Open3.capture3(*args)
123
+ unless status.success?
124
+ raise "Failed to execute: #{args.shelljoin} (status: #{$?.exitstatus}):\n[stdout]:\n#{stdout}\n[stderr]:\n#{stderr}"
125
+ end
126
+ stdout
127
+ end
128
+
129
+ # Return multiple times and return the best metrics
130
+ def with_repeat(metric, &block)
131
+ value_environments = @config.repeat_count.times.map do
132
+ block.call
133
+ end
134
+ value_environments.sort_by do |value, _|
135
+ if metric.larger_better
136
+ value
137
+ else
138
+ -value
139
+ end
140
+ end.last
141
+ end
142
+
143
+ StdoutToMetrics = ::BenchmarkDriver::Struct.new(:stdout, :value_from_stdout, :environment_from_stdout) do
144
+ def value
145
+ value = eval(value_from_stdout, binding)
146
+ end
147
+
148
+ def environment
149
+ ret = {}
150
+ environment_from_stdout.each do |name, from_stdout|
151
+ ret[name] = eval(from_stdout, binding)
152
+ end
153
+ ret
154
+ end
155
+ end
156
+ private_constant :StdoutToMetrics
157
+ end
@@ -6,18 +6,15 @@ class BenchmarkDriver::Runner::Time < BenchmarkDriver::Runner::Ips
6
6
  # Dynamically fetched and used by `BenchmarkDriver::JobParser.parse`
7
7
  JobParser = BenchmarkDriver::DefaultJobParser.for(Job)
8
8
 
9
- METRICS_TYPE = BenchmarkDriver::Metrics::Type.new(unit: 's', larger_better: false)
9
+ METRIC = BenchmarkDriver::Metric.new(name: 'Execution time', unit: 's', larger_better: false)
10
10
 
11
- # Overriding BenchmarkDriver::Runner::Ips#set_metrics_type
12
- def set_metrics_type
13
- @output.metrics_type = METRICS_TYPE
11
+ # Overriding BenchmarkDriver::Runner::Ips#metric
12
+ def metric
13
+ METRIC
14
14
  end
15
15
 
16
- # Overriding BenchmarkDriver::Runner::Ips#build_metrics
17
- def build_metrics(duration:, executable:, loop_count:)
18
- BenchmarkDriver::Metrics.new(
19
- value: duration,
20
- executable: executable,
21
- )
16
+ # Overriding BenchmarkDriver::Runner::Ips#value_duration
17
+ def value_duration(duration:, loop_count:)
18
+ [duration, duration]
22
19
  end
23
20
  end
@@ -1,3 +1,3 @@
1
1
  module BenchmarkDriver
2
- VERSION = '0.10.16'
2
+ VERSION = '0.11.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: benchmark_driver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.16
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Takashi Kokubun
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-06-26 00:00:00.000000000 Z
11
+ date: 2018-06-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -38,6 +38,34 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-retry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
41
69
  description: Fully-featured accurate benchmark driver for Ruby
42
70
  email:
43
71
  - takashikkbn@gmail.com
@@ -47,26 +75,27 @@ extensions: []
47
75
  extra_rdoc_files: []
48
76
  files:
49
77
  - ".gitignore"
78
+ - ".rspec"
50
79
  - ".travis.yml"
51
80
  - CHANGELOG.md
52
81
  - Gemfile
53
82
  - LICENSE.txt
54
83
  - README.md
55
84
  - Rakefile
85
+ - benchmark-driver/.gitignore
86
+ - benchmark-driver/CODE_OF_CONDUCT.md
87
+ - benchmark-driver/Gemfile
88
+ - benchmark-driver/LICENSE.txt
89
+ - benchmark-driver/README.md
90
+ - benchmark-driver/Rakefile
91
+ - benchmark-driver/benchmark-driver.gemspec
92
+ - benchmark-driver/bin/console
93
+ - benchmark-driver/bin/setup
94
+ - benchmark-driver/lib/benchmark-driver.rb
95
+ - benchmark-driver/lib/benchmark/driver.rb
56
96
  - benchmark_driver.gemspec
57
97
  - bin/console
58
98
  - bin/setup
59
- - examples/exec_blank.rb
60
- - examples/exec_blank_simple.rb
61
- - examples/yaml/array_duration_time.yml
62
- - examples/yaml/array_loop.yml
63
- - examples/yaml/blank_hash.yml
64
- - examples/yaml/blank_hash_array.yml
65
- - examples/yaml/blank_loop.yml
66
- - examples/yaml/blank_string.yml
67
- - examples/yaml/blank_string_array.yml
68
- - examples/yaml/example_multi.yml
69
- - examples/yaml/example_single.yml
70
99
  - exe/benchmark-driver
71
100
  - images/optcarrot.png
72
101
  - lib/benchmark_driver.rb
@@ -74,7 +103,7 @@ files:
74
103
  - lib/benchmark_driver/default_job.rb
75
104
  - lib/benchmark_driver/default_job_parser.rb
76
105
  - lib/benchmark_driver/job_parser.rb
77
- - lib/benchmark_driver/metrics.rb
106
+ - lib/benchmark_driver/metric.rb
78
107
  - lib/benchmark_driver/output.rb
79
108
  - lib/benchmark_driver/output/compare.rb
80
109
  - lib/benchmark_driver/output/markdown.rb
@@ -88,10 +117,11 @@ files:
88
117
  - lib/benchmark_driver/runner/memory.rb
89
118
  - lib/benchmark_driver/runner/once.rb
90
119
  - lib/benchmark_driver/runner/recorded.rb
120
+ - lib/benchmark_driver/runner/ruby_stdout.rb
91
121
  - lib/benchmark_driver/runner/time.rb
92
122
  - lib/benchmark_driver/struct.rb
93
123
  - lib/benchmark_driver/version.rb
94
- homepage: https://github.com/k0kubun/benchmark_driver
124
+ homepage: https://github.com/benchmark-driver/benchmark-driver
95
125
  licenses:
96
126
  - MIT
97
127
  metadata: {}
@@ -111,7 +141,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
111
141
  version: '0'
112
142
  requirements: []
113
143
  rubyforge_project:
114
- rubygems_version: 2.7.7
144
+ rubygems_version: 2.6.14.1
115
145
  signing_key:
116
146
  specification_version: 4
117
147
  summary: Fully-featured accurate benchmark driver for Ruby
@@ -1,13 +0,0 @@
1
- require 'benchmark_driver'
2
-
3
- Benchmark.driver do |x|
4
- x.prelude <<-EOS
5
- class Array
6
- alias_method :blank?, :empty?
7
- end
8
- array = []
9
- EOS
10
- x.report 'Array#empty?', %{ array.empty? }
11
- x.report 'Array#blank?', %{ array.blank? }
12
- x.output 'markdown'
13
- end
@@ -1,13 +0,0 @@
1
- require 'benchmark_driver'
2
-
3
- Benchmark.driver(output: :simple) do |x|
4
- x.prelude <<-EOS
5
- class Array
6
- alias_method :blank?, :empty?
7
- end
8
- array = []
9
- EOS
10
- x.report %{ array.empty? }
11
- x.report %{ array.blank? }
12
- x.loop_count 10000000
13
- end
@@ -1,2 +0,0 @@
1
- benchmark:
2
- bm_vm2_array: a = [1,2,3,4,5,6,7,8,9,10]
@@ -1,3 +0,0 @@
1
- loop_count: 6000000
2
- benchmark:
3
- bm_vm2_array: a = [1,2,3,4,5,6,7,8,9,10]
@@ -1,8 +0,0 @@
1
- prelude: |
2
- class Array
3
- alias_method :blank?, :empty?
4
- end
5
- array = []
6
- benchmark:
7
- empty: array.empty?
8
- blank: array.blank?
@@ -1,10 +0,0 @@
1
- prelude: |
2
- class Array
3
- alias_method :blank?, :empty?
4
- end
5
- array = []
6
- benchmark:
7
- - name: Array#empty?
8
- script: array.empty?
9
- - name: Array#blank?
10
- script: array.blank?