lib/startup_time/app.rb in startup-time-1.1.1 vs lib/startup_time/app.rb in startup-time-1.2.0
- old
+ new
@@ -1,15 +1,16 @@
# frozen_string_literal: true
require 'benchmark'
+require 'json'
require 'komenda'
require 'shellwords' # for Array#shelljoin
require 'tty/table'
-# FIXME we only need bundler/setup here, but it appears to create an incomplete
-# Bundler object which (sometimes) confuses Komenda as well as causing a
-# Gem::LoadError (for unicode-display_width)
+# FIXME we only need bundler/setup here (for Bundler.with_clean_env), but it appears
+# to create an incomplete Bundler object which (sometimes) confuses Komenda as well
+# as causing a Gem::LoadError (for unicode-display_width)
#
# require 'bundler/setup'
require 'bundler'
module StartupTime
@@ -18,14 +19,15 @@
class App
EXPECTED_OUTPUT = /\AHello, world!\r?\n\z/
include FileUtils # for `sh`
include Util # for `which`
- include Services.mixin %i[builder ids_to_groups selected_tests]
+ include Services.mixin %i[builder selected_tests]
def initialize(args = ARGV)
@options = Options.new(args)
+ @json = @options.format == :json
@verbosity = @options.verbosity
@times = []
# provide/publish the Options instance we've just created so it's
# available to other components
@@ -35,24 +37,19 @@
# run the command corresponding to the command-line options:
# either an auxiliary command (e.g. clean the build directory
# or print a help message) or the default command, which runs
# the selected benchmark-tests
def run
- if @verbosity == :verbose
- # used by StartupTime::App#time to dump the command line
- require 'shellwords'
- end
-
case @options.action
when :clean
builder.clean!
when :help
puts @options.usage
- when :show_ids
- puts render_ids_to_groups
when :version
puts VERSION
+ when :show_ids
+ render_ids_to_groups
else
benchmark
end
end
@@ -65,54 +62,80 @@
# 3) sort the results from the fastest to the slowest
# 4) display the results in the specified format (default: ASCII table)
def benchmark
builder.build!
- selected_tests.entries.shuffle.each do |id, test|
- time(id, test)
+ # run a test if:
+ #
+ # - its interpreter exists
+ # - it's a compiled executable (i.e. its compiler exists)
+ #
+ # otherwise, skip it
+ runnable_tests = selected_tests.each_with_object([]) do |(id, test), tests|
+ args = Array(test[:command])
+
+ if args.length == 1 # native executable
+ compiler = test[:compiler] || id
+ path = File.absolute_path(args.first)
+ next unless File.exist?(path)
+ else # interpreter + source
+ compiler = args.first
+ path = which(compiler)
+ next unless path
+ end
+
+ tests << {
+ id: id,
+ test: test,
+ args: args,
+ compiler: compiler,
+ path: path,
+ }
end
+ if runnable_tests.empty?
+ puts '[]' if @json
+ return
+ end
+
+ spec = @options.spec
+
+ if spec.type == :duration
+ spec = spec.with(value: spec.value.to_f / runnable_tests.length)
+ end
+
+ runnable_tests.shuffle.each do |config|
+ config[:spec] = spec
+ time(config)
+ end
+
sorted = @times.sort_by { |result| result[:time] }
- if @options.format == :json
- require 'json'
+ if @json
puts sorted.to_json
- elsif !sorted.empty?
+ else
pairs = sorted.map { |result| [result[:name], '%.02f' % result[:time]] }
table = TTY::Table.new(['Test', 'Time (ms)'], pairs)
puts unless @verbosity == :quiet
puts table.render(:basic, alignments: %i[left right])
end
end
- # an ASCII table representation of the mapping from test IDs (e.g. "scala")
- # to group IDs (e.g. "compiled, jvm, slow")
+ # print a JSON or ASCII-table representation of the mapping from test IDs
+ # (e.g. "scala") to group IDs (e.g. ["compiled", "jvm", "slow"])
def render_ids_to_groups
- table = TTY::Table.new(%w[Test Groups], ids_to_groups)
- table.render
+ if @json
+ puts Registry.ids_to_groups(format: :json).to_json
+ else
+ table = TTY::Table.new(%w[Test Groups], Registry.ids_to_groups)
+ puts table.render
+ end
end
- # takes a test ID and a test spec and measures how long it takes to execute
- # the test if either:
- #
- # - its interpreter exists
- # - it's a compiled executable (i.e. its compiler exists)
- #
- # otherwise, skip the test
- def time(id, test)
- args = Array(test[:command])
-
- if args.size == 1 # native executable
- compiler = test[:compiler] || id
- cmd = File.absolute_path(args.first)
- return unless File.exist?(cmd)
- else # interpreter + source
- compiler = args.first
- cmd = which(compiler)
- return unless cmd
- end
-
+ # takes a test configuration and measures how long it takes to execute the
+ # test
+ def time(id:, test:, args:, compiler:, path:, spec:)
# dump the compiler/interpreter's version if running in verbose mode
if @verbosity == :verbose
puts
puts "test: #{id}"
@@ -131,11 +154,11 @@
end
end
end
argv0 = args.shift
- command = [cmd, *args]
+ command = [path, *args]
unless @verbosity == :quiet
if @verbosity == :verbose
puts "command: #{command.shelljoin}"
else
@@ -158,12 +181,29 @@
times = []
# the bundler environment slows down ruby and breaks truffle-ruby,
# so make sure it's disabled for the benchmark
Bundler.with_clean_env do
- @options.rounds.times do
- times << Benchmark.realtime do
- system([cmd, argv0], *args, out: File::NULL)
+ if spec.type == :duration # how long to run the tests for
+ duration = spec.value
+ elapsed = 0
+ start = Time.now
+
+ loop do
+ time = Benchmark.realtime do
+ system([path, argv0], *args, out: File::NULL)
+ end
+
+ elapsed = Time.now - start
+ times << time
+
+ break if elapsed >= duration
+ end
+ else # how many times to run the tests
+ spec.value.times do
+ times << Benchmark.realtime do
+ system([path, argv0], *args, out: File::NULL)
+ end
end
end
end
@times << {