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.
Files changed (58) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +1 -3
  3. data/CHANGELOG.md +9 -0
  4. data/Gemfile +1 -6
  5. data/README.md +51 -52
  6. data/benchmark_driver.gemspec +3 -2
  7. data/bin/console +4 -11
  8. data/examples/exec_blank.rb +2 -2
  9. data/examples/exec_blank_simple.rb +2 -3
  10. data/exe/benchmark-driver +74 -83
  11. data/lib/benchmark_driver.rb +12 -1
  12. data/lib/benchmark_driver/config.rb +36 -0
  13. data/lib/benchmark_driver/default_job.rb +12 -0
  14. data/lib/benchmark_driver/default_job_parser.rb +68 -0
  15. data/lib/benchmark_driver/job_parser.rb +42 -0
  16. data/lib/benchmark_driver/metrics.rb +17 -0
  17. data/lib/benchmark_driver/output.rb +27 -0
  18. data/lib/benchmark_driver/output/compare.rb +196 -0
  19. data/lib/benchmark_driver/output/markdown.rb +102 -0
  20. data/lib/benchmark_driver/output/simple.rb +97 -0
  21. data/lib/benchmark_driver/rbenv.rb +11 -0
  22. data/lib/benchmark_driver/ruby_interface.rb +51 -0
  23. data/lib/benchmark_driver/runner.rb +42 -0
  24. data/lib/benchmark_driver/runner/ips.rb +239 -0
  25. data/lib/benchmark_driver/runner/memory.rb +142 -0
  26. data/lib/benchmark_driver/runner/time.rb +18 -0
  27. data/lib/benchmark_driver/struct.rb +85 -0
  28. data/lib/benchmark_driver/version.rb +3 -0
  29. metadata +21 -33
  30. data/bin/bench +0 -4
  31. data/examples/call.rb +0 -12
  32. data/examples/call_blank.rb +0 -13
  33. data/examples/call_erb.rb +0 -33
  34. data/examples/call_interpolation.rb +0 -13
  35. data/examples/eval_blank.rb +0 -12
  36. data/examples/eval_blank_loop.rb +0 -13
  37. data/examples/eval_interpolation.rb +0 -15
  38. data/lib/benchmark/driver.rb +0 -101
  39. data/lib/benchmark/driver/benchmark_result.rb +0 -21
  40. data/lib/benchmark/driver/bundle_installer.rb +0 -45
  41. data/lib/benchmark/driver/bundler.rb +0 -12
  42. data/lib/benchmark/driver/configuration.rb +0 -77
  43. data/lib/benchmark/driver/duration_runner.rb +0 -24
  44. data/lib/benchmark/driver/error.rb +0 -16
  45. data/lib/benchmark/driver/repeatable_runner.rb +0 -18
  46. data/lib/benchmark/driver/ruby_dsl_parser.rb +0 -78
  47. data/lib/benchmark/driver/time.rb +0 -12
  48. data/lib/benchmark/driver/version.rb +0 -5
  49. data/lib/benchmark/driver/yaml_parser.rb +0 -55
  50. data/lib/benchmark/output.rb +0 -20
  51. data/lib/benchmark/output/ips.rb +0 -143
  52. data/lib/benchmark/output/markdown.rb +0 -73
  53. data/lib/benchmark/output/memory.rb +0 -57
  54. data/lib/benchmark/output/time.rb +0 -57
  55. data/lib/benchmark/runner.rb +0 -14
  56. data/lib/benchmark/runner/call.rb +0 -97
  57. data/lib/benchmark/runner/eval.rb +0 -147
  58. 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