Class: PerfCheck

Inherits:
Object
  • Object
show all
Defined in:
lib/perf_check.rb,
lib/perf_check/git.rb,
lib/perf_check/config.rb,
lib/perf_check/output.rb,
lib/perf_check/server.rb,
lib/perf_check/railtie.rb,
lib/perf_check/callbacks.rb,
lib/perf_check/test_case.rb,
lib/perf_check/middleware.rb

Defined Under Namespace

Classes: ConfigLoadError, Exception, Git, Middleware, Railtie, Server, TestCase

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app_root) ⇒ PerfCheck

Returns a new instance of PerfCheck.



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/perf_check.rb', line 16

def initialize(app_root)
  @app_root = app_root

  @options = OpenStruct.new(
    number_of_requests: 20,
    reference: 'master',
    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
  )

  @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

Instance Attribute Details

#app_rootObject (readonly)

Returns the value of attribute app_root.



13
14
15
# File 'lib/perf_check.rb', line 13

def app_root
  @app_root
end

#gitObject (readonly)

Returns the value of attribute git.



13
14
15
# File 'lib/perf_check.rb', line 13

def git
  @git
end

#loggerObject

Returns the value of attribute logger.



14
15
16
# File 'lib/perf_check.rb', line 14

def logger
  @logger
end

#optionsObject (readonly)

Returns the value of attribute options.



13
14
15
# File 'lib/perf_check.rb', line 13

def options
  @options
end

#serverObject (readonly)

Returns the value of attribute server.



13
14
15
# File 'lib/perf_check.rb', line 13

def server
  @server
end

#test_casesObject (readonly)

Returns the value of attribute test_cases.



13
14
15
# File 'lib/perf_check.rb', line 13

def test_cases
  @test_cases
end

Instance Method Details

#add_test_case(route) ⇒ Object



65
66
67
# File 'lib/perf_check.rb', line 65

def add_test_case(route)
  test_cases.push(TestCase.new(self, route.sub(/^([^\/])/, '/\1')))
end

#before_start(&block) ⇒ Object



12
13
14
15
# File 'lib/perf_check/callbacks.rb', line 12

def before_start(&block)
  @before_start_callbacks ||= []
  @before_start_callbacks << block
end

#before_start_callbacksObject



17
18
19
20
21
22
23
24
25
# 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_configObject



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/perf_check.rb', line 45

def load_config
  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_parserObject



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# 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('--quick', '-q',
            'Fire off 20 requests just on this branch (no comparison with master)') do
      options.reference = nil
    end

    opts.on('--no-caching', 'Do not enable fragment caching') do
      options.caching = false
    end

    opts.on('--run-migrations', 'Run migrations on the branch and unmigrate at the end') do
      options[:run_migrations?] = 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 "\nMisc"
    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) ⇒ Object



5
6
7
8
9
10
# 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


11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/perf_check/output.rb', line 11

def print_brief_results
  test_cases.each do |test|
    print(test.resource.ljust(40) + ': ')

    codes = (test.this_profiles+test.reference_profiles).map(&:response_code).uniq
    print("(HTTP "+codes.join(',')+") ")

    printf('%.1fms', test.this_latency)

    puts && next if test.reference_profiles.empty?

    print(sprintf(' (%+5.1fms)', test.latency_difference).bold)
    print_diff_results(test.response_diff) if options.verify_no_diff
    puts
  end
end


3
4
5
6
7
8
9
# File 'lib/perf_check/output.rb', line 3

def print_diff_results(diff)
  if diff.changed?
    print(" Diff: #{diff.file}".bold.light_red)
  else
    print(" Diff: Output is identical!".bold.light_green)
  end
end


28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/perf_check/output.rb', line 28

def print_full_results
  puts("==== Results ====")
  test_cases.each do |test|
    puts(test.resource.bold)

    if test.reference_profiles.empty?
      printf("your branch: ".rjust(15)+"%.1fms\n", test.this_latency)
      next
    end

    master_latency = sprintf('%.1fms', test.reference_latency)
    this_latency = sprintf('%.1fms', test.this_latency)
    difference = sprintf('%+.1fms', test.latency_difference)

    if test.latency_difference < 0
      change_factor = test.reference_latency / test.this_latency
    else
      change_factor = test.this_latency / test.reference_latency
    end
    formatted_change = sprintf('%.1fx', change_factor)

    percent_change = 100*(test.latency_difference / test.reference_latency).abs
    if percent_change < 10
      formatted_change = "yours is about the same"
      color = :blue
    elsif test.latency_difference < 0
      formatted_change = "yours is #{formatted_change} faster!"
      color = :green
    else
      formatted_change = "yours is #{formatted_change} slower!!!"
      color = :light_red
    end
    formatted_change = difference + " (#{formatted_change})"

    puts("reference: ".rjust(15)  + "#{master_latency}")
    puts("your branch: ".rjust(15)+ "#{this_latency}")
    puts(("change: ".rjust(15)    + "#{formatted_change}").bold.send(color))

    print_diff_results(test.response_diff) if options.verify_no_diff
  end
end

#runObject



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/perf_check.rb', line 69

def run
  begin
    profile_requests

    if options.reference
      git.stash_if_needed
      git.checkout_reference(options.reference)
      test_cases.each{ |x| x.switch_to_reference_context }

      profile_requests
    end
  ensure
    server.exit rescue nil
    if options.reference
      git.checkout_current_branch(false) rescue nil
      (git.pop rescue nil) 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) ⇒ Object



28
29
30
# 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 = {}) ⇒ Object



32
33
34
35
36
37
38
39
40
# 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) ⇒ Object



3
4
5
6
# File 'lib/perf_check/callbacks.rb', line 3

def when_finished(&block)
  @when_finished_callbacks ||= []
  @when_finished_callbacks << block
end

#when_finished_callbacksObject



8
9
10
# File 'lib/perf_check/callbacks.rb', line 8

def when_finished_callbacks
  @when_finished_callbacks || []
end