class PerfCheck

Attributes

app_root[R]
git[R]
logger[RW]
options[R]
server[R]
test_cases[R]

Public Class Methods

new(app_root) click to toggle source
# File lib/perf_check.rb, line 16
def initialize(app_root)
  @app_root = app_root
  @options = OpenStruct.new(
    number_of_requests: 20,
    reference: 'master',
    branch: nil,
    cookie: nil,
    headers: {},
    http_statuses: [200],
    verify_no_diff: false,
    diff: false,
    diff_options: ['-U3',
                   '--ignore-matching-lines=/mini-profiler-resources/includes.js'],
    brief: false,
    caching: true,
    json: false,
    hard_reset: false,
    environment: 'development'
  )

  @logger = Logger.new(STDERR).tap do |logger|
    logger.formatter = proc do |severity, datetime, progname, msg|
      "[#{datetime.strftime("%Y-%m-%d %H:%M:%S")}] #{msg}\n"
    end
  end

  @git = Git.new(self)
  @server = Server.new(self)
  @test_cases = []
end

Public Instance Methods

add_test_case(route) click to toggle source
# File lib/perf_check.rb, line 68
def add_test_case(route)
  test_cases.push(TestCase.new(self, route.sub(/^([^\/])/, '/\1')))
end
before_start(&block) click to toggle source
# File lib/perf_check/callbacks.rb, line 12
def before_start(&block)
  @before_start_callbacks ||= []
  @before_start_callbacks << block
end
before_start_callbacks() click to toggle source
# File lib/perf_check/callbacks.rb, line 17
def before_start_callbacks
  (@before_start_callbacks || []) + [
    proc { |perf_check|
      perf_check.logger.info("=" * 77)
      perf_check.logger.info("PERRRRF CHERRRK! Grab a ☕️  and don't touch your working tree (we automate git)")
      perf_check.logger.info("=" * 77)
    }
  ]
end
load_config() click to toggle source
# File lib/perf_check.rb, line 47
def load_config
  File.stat(File.join(app_root,'Gemfile')).file?
  if File.exists?("#{app_root}/config/perf_check.rb")
    this = self
    Kernel.send(:define_method, :perf_check){ this }

    dir = Dir.pwd
    begin
      Dir.chdir(app_root)
      load "#{app_root}/config/perf_check.rb"
    rescue LoadError => e
      error = ConfigLoadError.new(e.message)
      error.set_backtrace(e.backtrace)
      raise error
    ensure
      Dir.chdir(dir)
      Kernel.send(:remove_method, :perf_check)
    end
  end
end
option_parser() click to toggle source
# File lib/perf_check/config.rb, line 12
  def option_parser
    @optparse ||= OptionParser.new do |opts|
      opts.banner = "Usage: perf_check [options] [route ...]"

      opts.separator "\nBenchmark options:"
      opts.on('--requests N', '-n',
        'Use N requests in benchmark, defaults to 20') do |n|
        options.number_of_requests = n.to_i
      end

      opts.on('--reference COMMIT', '-r',
        'Benchmark against COMMIT instead of master') do |commit|
        options.reference = commit
      end

      opts.on('--branch COMMIT', '-branch',
        'Set the current branch to benchmark against (defaults to the branch you currently have checked out)') do |branch|
        options.branch = branch
      end

      opts.on('--quick', '-q',
        '20 requests just on this branch (no comparison with master)') do
        options.reference = nil
      end

      opts.on('--compare-paths',
        'Compare two paths against each other on the same branch') do
        options[:compare_paths?] = true
      end

      opts.on('--302-success',
        'Consider HTTP 302 code a successful request') do
        options.http_statuses.push(302)
      end

      opts.on('--302-failure',
        'Consider HTTP 302 code an unsuccessful request') do
        options.http_statuses.delete(302)
      end

      opts.separator "\nRails environment"

      opts.on('--deployment','Use git fetch/reset instead of the safe/friendly checkout') do
        options.hard_reset = true
      end

      opts.on('--environment', '-e',
        'Change the rails environment we are profiling. Defaults to development') do |env|
        options.environment = env
      end

      opts.on('--no-caching',
        'Do not enable fragment caching (Rails.cache will still work)') do
        options.caching = false
      end

      opts.separator "\nMisc"

      opts.on('-h', 'Display this help') do
        # Do nothing, just don't error
      end

      opts.on('--run-migrations',
        'Run migrations on the branch and unmigrate at the end') do
        options[:run_migrations?] = true
      end

      opts.on('--cookie COOKIE', '-c') do |cookie|
        options.cookie = cookie
      end

      opts.on('--header HEADER', '-H') do |header|
        key, value = header.split(':', 2)
        options.headers[key.strip] = value.strip
      end

      opts.on('--input FILE', '-i') do |input|
        File.readlines(input).each do |resource|
          ARGV << resource.strip
        end
      end

      opts.on('--brief', '-b') do
        options.brief = true
      end

      opts.on('--verify-no-diff',
              'Check whether there is a diff between the responses of this and the reference branch') do
        options.verify_no_diff = true
      end

     opts.on('--diff') do
       options.diff = true
       options.brief = true
       options.verify_no_diff = true
       options.number_of_requests = 1
     end

     opts.on("--diff-option OPT") do |opt|
       options.diff_options << opt
     end

     opts.separator ''
     opts.separator <<EOF
Usage examples:
  Benchmark PostController#index against master
     perf_check /user/45/posts
     perf_check /user/45/posts -n5

  Benchmark against a specific commit
     perf_check /user/45/posts -r 0123abcdefg
     perf_check /user/45/posts -r HEAD~2

  Benchmark the changes in the working tree
     perf_check /user/45/posts -r HEAD

  Benchmark and diff the output against master
     perf_check /user/45/posts --verify-no-diff

  Just diff the output on your branch with master
     perf_check /user/45/posts --diff

  Diff a bunch of urls listed in a file (newline seperated)
    perf_check --diff --input FILE
EOF

      opts.separator ''
    end
  end
parse_arguments(argv) click to toggle source
# File lib/perf_check/config.rb, line 5
def parse_arguments(argv)
  options.argv = argv.is_a?(String) ? Shellwords.shellsplit(argv) : argv
  option_parser.parse(options.argv).each do |route|
    add_test_case(route.strip)
  end
end
print_brief_results() click to toggle source
print_diff_results(diff) click to toggle source
print_full_results() click to toggle source
print_results_of_compared_paths() click to toggle source
run() click to toggle source
# File lib/perf_check.rb, line 72
def run
  begin
    if options.compare_paths?
      raise "Must have two paths" if test_cases.count != 2
      profile_compare_paths_requests
    else
      profile_requests
      if options.reference
        git.stash_if_needed
        git.checkout(options.reference, bundle_after_checkout: true, hard_reset: options.hard_reset)
        test_cases.each{ |x| x.switch_to_reference_context }

        profile_requests
      end
    end
  ensure
    server.exit
    if options.reference
      git.checkout(git.current_branch, bundle_after_checkout: true, hard_reset: options.hard_reset)
      git.pop if git.stashed?
    end

    callbacks = {}

    if $!
      callbacks[:error_message] = "#{$!.class}: #{$!.message}\n"
      callbacks[:error_message] << $!.backtrace.map{|x| "\t#{x}"}.join("\n")
    end

    trigger_when_finished_callbacks(callbacks)
  end
end
trigger_before_start_callbacks(test_case) click to toggle source
# File lib/perf_check/callbacks.rb, line 28
def trigger_before_start_callbacks(test_case)
  before_start_callbacks.each{ |f| f.call(self, test_case) }
end
trigger_when_finished_callbacks(data={}) click to toggle source
# File lib/perf_check/callbacks.rb, line 32
def trigger_when_finished_callbacks(data={})
  data = data.merge(:current_branch => git.current_branch)
  results = OpenStruct.new(data)
  if test_cases.size == 1
    results.current_latency = test_cases.first.this_latency
    results.reference_latency = test_cases.first.reference_latency
  end
  when_finished_callbacks.each{ |f| f.call(self, results) }
end
when_finished(&block) click to toggle source
# File lib/perf_check/callbacks.rb, line 3
def when_finished(&block)
  @when_finished_callbacks ||= []
  @when_finished_callbacks << block
end
when_finished_callbacks() click to toggle source
# File lib/perf_check/callbacks.rb, line 8
def when_finished_callbacks
  @when_finished_callbacks || []
end

Private Instance Methods

calculate_percent_change(latency_difference, reference_latency) click to toggle source
# File lib/perf_check/output.rb, line 127
def calculate_percent_change(latency_difference, reference_latency)
  100*(latency_difference / reference_latency).abs
end
change_factor(latency_difference, reference_latency, test_latency) click to toggle source
# File lib/perf_check/output.rb, line 99
def change_factor(latency_difference, reference_latency, test_latency)
  if latency_difference < 0
    reference_latency / test_latency
  else
    test_latency / reference_latency
  end
end
formatted_change_and_color(change_factor_output, percent_change, latency_difference) click to toggle source
# File lib/perf_check/output.rb, line 113
def formatted_change_and_color(change_factor_output, percent_change, latency_difference)
  if percent_change < 10
    formatted_change = "yours is about the same"
    color = :blue
  elsif latency_difference < 0
    formatted_change = "yours is #{change_factor_output} faster!"
    color = :green
  else
    formatted_change = "yours is #{change_factor_output} slower!!!"
    color = :light_red
  end
  [formatted_change, color]
end
latency_output(latency) click to toggle source
# File lib/perf_check/output.rb, line 95
def latency_output(latency)
  sprintf('%.1fms', latency)
end
print_results(reference_latency, test_latency, formatted_change, color) click to toggle source
profile_compare_paths_requests() click to toggle source
# File lib/perf_check.rb, line 107
def profile_compare_paths_requests
  first = test_cases[0]
  reference_test = test_cases[1]
  profile_test_case(first)
  reference_test.switch_to_reference_context
  profile_test_case(reference_test)
end
profile_requests() click to toggle source
# File lib/perf_check.rb, line 134
def profile_requests
  test_cases.each do |test|
    profile_test_case(test)
  end
end
profile_test_case(test) click to toggle source
# File lib/perf_check.rb, line 115
def profile_test_case(test)
  trigger_before_start_callbacks(test)
  run_migrations_up if options.run_migrations?
  server.restart

  test.cookie = options.cookie

  if options.diff
    logger.info("Issuing #{test.resource}")
  else
    logger.info ''
    logger.info("Benchmarking #{test.resource}:")
  end

  test.run(server, options)
ensure
  run_migrations_down if options.run_migrations?
end
run_migrations_down() click to toggle source
# File lib/perf_check.rb, line 148
def run_migrations_down
  Bundler.with_original_env do
    git.migrations_to_run_down.each do |version|
      logger.info "Running db:migrate:down VERSION=#{version}"
      logger.info `cd #{app_root} && bundle exec rake db:migrate:down VERSION=#{version}`
    end
  end
  git.clean_db
end
run_migrations_up() click to toggle source
# File lib/perf_check.rb, line 140
def run_migrations_up
  logger.info "Running db:migrate"
  Bundler.with_original_env do
    logger.info `cd #{app_root} && bundle exec rake db:migrate`
  end
  git.clean_db
end