module BenchmarkDriver
def build_contexts(contexts, executables:)
def build_contexts(contexts, executables:) # If contexts are not specified, just use executables as contexts. if !contexts.is_a?(Array) || contexts.empty? return executables.map { |exec| BenchmarkDriver::Context.new(name: exec.name, executable: exec) } end with_executables, without_executables = contexts.partition { |context| context.name && context.executable } with_executables + build_contexts_with_executables(without_executables, executables) end
def build_contexts_with_executables(contexts, executables)
def build_contexts_with_executables(contexts, executables) # Create direct product of contexts contexts.product(executables).map do |context, executable| name = context.name if name.nil? # Use the first gem name and version by default name = context.gems.first.join(' ') # Append Ruby executable name if it's matrix if executables.size > 1 name = "#{name} (#{executable.name})" end end BenchmarkDriver::Context.new( name: name, executable: executable, gems: context.gems, prelude: context.prelude, ) end end
def camelize(str)
def camelize(str) str.split('_').map(&:capitalize).join end
def configure_defaults(klass, defaults)
def configure_defaults(klass, defaults) class << klass attr_accessor :defaults end klass.defaults = defaults klass.prepend(Module.new { def initialize(*) super self.class.defaults.each do |key, value| if public_send(key).nil? begin value = value.dup rescue TypeError # for Ruby <= 2.3, like `true.dup` end public_send("#{key}=", value) end end end }) def klass.inherited(child) child.defaults = self.defaults end end
def description
-
(String)
- - Return result of `ruby -v`. This is for convenience of output plugins.
def description @cache[:description] ||= IO.popen([*command, '-v'], &:read).rstrip end
def force_deep_freeze(klass)
def force_deep_freeze(klass) klass.class_eval do def freeze members.each do |member| value = public_send(member) if value.is_a?(Array) value.each(&:freeze) end value.freeze end super end end end
def freeze
def freeze members.each do |member| value = public_send(member) if value.is_a?(Array) value.each(&:freeze) end value.freeze end super end
def initialize(*)
def initialize(*) super @cache = {} # modifiable storage even after `#freeze` end
def initialize(**args)
-
args
(Hash{ Symbol => Object }
) --
def initialize(**args) args.each do |key, value| unless members.include?(key) raise ArgumentError.new("unknown keywords: #{key}") next end public_send("#{key}=", value) end end
def initialize(*)
def initialize(*) super self.class.defaults.each do |key, value| if public_send(key).nil? begin value = value.dup rescue TypeError # for Ruby <= 2.3, like `true.dup` end public_send("#{key}=", value) end end end
def keyword_init_struct(*args, &block)
def keyword_init_struct(*args, &block) ::Struct.new(*args).tap do |klass| klass.prepend(Module.new { # @param [Hash{ Symbol => Object }] args def initialize(**args) args.each do |key, value| unless members.include?(key) raise ArgumentError.new("unknown keywords: #{key}") next end public_send("#{key}=", value) end end }) klass.prepend(Module.new(&block)) end end
def new(*args, defaults: {}, &block)
-
defaults
(Hash{ Symbol => Object }
) -- -
args
(Array
) --
def new(*args, defaults: {}, &block) # Polyfill `keyword_init: true` if ::Struct::SUPPORT_KEYWORD_P klass = ::Struct.new(*args, keyword_init: true, &block) else klass = keyword_init_struct(*args, &block) end # Default value config configure_defaults(klass, defaults) # Force deeply freezing members force_deep_freeze(klass) klass end
def parse(config, working_directory: nil)
-
working_directory
(Hash
) -- - YAML-specific special parameter for "command_stdout" and a relative path in type -
config
(Hash
) --
def parse(config, working_directory: nil) config = symbolize_keys(config) type = config.fetch(:type) if !type.is_a?(String) raise ArgumentError.new("Invalid type: #{config[:type].inspect} (expected String)") elsif !type.match(/\A[A-Za-z0-9_\/]+\z/) raise ArgumentError.new("Invalid type: #{config[:type].inspect} (expected to include only [A-Za-z0-9_\/])") end config.delete(:type) # Dynamic dispatch for plugin support if type.include?('/') require File.join(working_directory || '.', type) type = File.basename(type) else require "benchmark_driver/runner/#{type}" end job = ::BenchmarkDriver.const_get("Runner::#{camelize(type)}::JobParser", false).parse(**config) if job.respond_to?(:working_directory) && job.respond_to?(:working_directory=) && job.working_directory.nil? job.working_directory = working_directory end job end
def run(jobs, config:)
-
config
(BenchmarkDriver::Config
) -- -
jobs
(Array
) --
def run(jobs, config:) if config.verbose >= 1 config.executables.each do |exec| $stdout.puts "#{exec.name}: #{IO.popen([*exec.command, '-v'], &:read)}" end end runner_config = Config::RunnerConfig.new runner_config.members.each do |member| runner_config[member] = config[member] end jobs.group_by{ |j| j.respond_to?(:contexts) && j.contexts }.each do |contexts, contexts_jobs| contexts_jobs.group_by(&:metrics).each do |metrics, metrics_jobs| metrics_jobs.group_by(&:class).each do |klass, klass_jobs| runner = runner_for(klass) if runner_config.alternate && runner != BenchmarkDriver::Runner::RubyStdout abort "--alternate is supported only for ruby_stdout runner for now" end contexts = build_contexts(contexts, executables: config.executables) output = Output.new( type: config.output_type, metrics: metrics, jobs: klass_jobs.map { |job| BenchmarkDriver::Job.new(name: job.name) }, contexts: contexts, options: config.output_opts, ) with_clean_env do runner.new(config: runner_config, output: output, contexts: contexts).run(klass_jobs) end end end end end
def runner_for(klass)
-
(Class)
-
Parameters:
-
klass
(Class
) -- - BenchmarkDriver::*::Job
def runner_for(klass) unless match = klass.name.match(/\ABenchmarkDriver::Runner::(?<namespace>[^:]+)::Job\z/) raise "Unexpected job class: #{klass}" end BenchmarkDriver.const_get("Runner::#{match[:namespace]}", false) end
def symbolize_keys(config)
-
config
(Object
) --
def symbolize_keys(config) case config when Hash config.dup.tap do |hash| hash.keys.each do |key| case key when String, Symbol hash[key.to_sym] = symbolize_keys(hash.delete(key)) else # Struct hash[key] = symbolize_keys(hash.delete(key)) end end end when Array config.map { |c| symbolize_keys(c) } else config end end
def version
-
(String)
- - Return RUBY_VERSION
def version @cache[:version] ||= IO.popen([*command, '-e', 'print RUBY_VERSION'], &:read) end
def with_clean_env(&block)
def with_clean_env(&block) # chruby sets GEM_HOME and GEM_PATH in your shell. We have to unset it in the child # process to avoid installing gems to the version that is running benchmark-driver. env = nil ['GEM_HOME', 'GEM_PATH'].each do |var| if ENV.key?(var) env ||= ENV.to_h ENV.delete(var) end end unless defined?(Gem::Version) # default-gem Bundler can be loaded and broken with --disable-gems return block.call end require 'bundler' if Bundler.respond_to?(:with_unbundled_env) Bundler.with_unbundled_env do block.call end else Bundler.with_clean_env do block.call end end rescue LoadError block.call ensure if env ENV.replace(env) end end