buren/honey_format

View on GitHub
lib/honey_format/cli/benchmark_cli.rb

Summary

Maintainability
A
2 hrs
Test Coverage
# frozen_string_literal: true

require 'optparse'
require 'honey_format/cli/result_writer'

module HoneyFormat
  # Benchmark CLI
  # @attr_reader [Hash] options from command line arguments
  # @attr_reader [CLIResultWriter] writer the CLI result writer
  class BenchmarkCLI
    # CSV default test data location
    CSV_TEST_DATA_URL = 'https://gist.github.com/buren/b669dd82fa37e37672da2cab33c8a830/raw/54ba14a698941ff61f3b854b66df0a7782c79c85/csv_1000_rows.csv'.freeze
    # CSV default test data cache location
    CSV_TEST_DATA_CACHE_PATH = '/tmp/honey-format-benchmark-test.csv'.freeze

    attr_reader :writer, :options

    # Instantiate the CLI
    # @param writer [CLIResultWriter] the result writer to use
    def initialize(writer: CLIResultWriter.new)
      @used_input_path = nil
      @writer = writer
      @options = parse_options(argv: ARGV)
      writer.verbose = true if @options[:verbose]
    end

    # Returns the expected runtime in seconds
    # @param report_count [Integer] number of reports in benchmark
    # @return [Integer] expected runtime in seconds
    def expected_runtime_seconds(report_count:)
      runs = report_count * options[:lines_multipliers].length
      warmup_time_seconds = runs * options[:benchmark_warmup]
      bench_time_seconds = runs * options[:benchmark_time]

      warmup_time_seconds + bench_time_seconds
    end

    # Return the input path used for the benchmark
    # @return [String] the input path (URL or filepath)
    def used_input_path
      options[:input_path] || @used_input_path
    end

    # Download or fetch the default benchmark file from cache
    # @return [String] CSV file as a string
    def fetch_default_benchmark_csv
      cache_path = CSV_TEST_DATA_CACHE_PATH

      if File.exist?(cache_path)
        writer.puts "Cache file found at #{cache_path}.", verbose: true
        @used_input_path = cache_path
        return File.read(cache_path)
      end

      writer.print 'Downloading test data file from GitHub..', verbose: true
      require 'open-uri'
      open(CSV_TEST_DATA_URL).read.tap do |csv| # rubocop:disable Security/Open
        @used_input_path = CSV_TEST_DATA_URL
        writer.puts 'done!', verbose: true
        File.write(cache_path, csv)
        writer.puts "Wrote cache file to #{cache_path}..", verbose: true
      end
    end

    # Parse command line arguments and return options
    # @param [Array<String>] argv the command lines arguments
    # @return [Hash] the command line options
    def parse_options(argv:)
      input_path = nil
      benchmark_time = 30
      benchmark_warmup = 5
      lines_multipliers = [1]
      verbose = false

      OptionParser.new do |parser|
        parser.banner = 'Usage: bin/benchmark [file.csv] [options]'
        parser.default_argv = argv

        parser.on('--csv=[file1.csv]', String, 'CSV file(s)') do |value|
          input_path = value
        end

        parser.on('--[no-]verbose', 'Verbose output') do |value|
          verbose = value
        end

        parser.on('--lines-multipliers=[1,10,50]', Array, 'Multiply the rows in the CSV file (default: 1)') do |value|
          lines_multipliers = value.map do |v|
            Integer(v).tap do |int|
              unless int >= 1
                raise(ArgumentError, '--lines-multiplier must be 1 or greater')
              end
            end
          end
        end

        parser.on('--time=[30]', String, 'Benchmark time (default: 30)') do |value|
          benchmark_time = Integer(value)
        end

        parser.on('--warmup=[30]', String, 'Benchmark warmup (default: 30)') do |value|
          benchmark_warmup = Integer(value)
        end

        parser.on('-h', '--help', 'How to use') do
          puts parser
          exit
        end

        # No argument, shows at tail. This will print an options summary.
        parser.on_tail('-h', '--help', 'Show this message') do
          puts parser
          exit
        end
      end.parse!

      {
        input_path: input_path,
        benchmark_time: benchmark_time,
        benchmark_warmup: benchmark_warmup,
        lines_multipliers: lines_multipliers,
        verbose: verbose,
      }
    end
  end
end