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
44
45
# 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

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



68
69
70
# 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) ⇒ 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



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# 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_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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# 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 "Usage examples:\nBenchmark PostController#index against master\n   perf_check /user/45/posts\n   perf_check /user/45/posts -n5\n\nBenchmark against a specific commit\n   perf_check /user/45/posts -r 0123abcdefg\n   perf_check /user/45/posts -r HEAD~2\n\nBenchmark the changes in the working tree\n   perf_check /user/45/posts -r HEAD\n\nBenchmark and diff the output against master\n   perf_check /user/45/posts --verify-no-diff\n\nJust diff the output on your branch with master\n   perf_check /user/45/posts --diff\n\nDiff a bunch of urls listed in a file (newline seperated)\n  perf_check --diff --input FILE\n"

    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


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
# File 'lib/perf_check/output.rb', line 59

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

    reference_latency_output = latency_output(test.reference_latency)
    test_latency_output = latency_output(test.this_latency)

    latency_difference = test.latency_difference
    reference_latency = test.reference_latency
    test_latency = test.this_latency

    change_factor = change_factor(
      latency_difference,
      reference_latency,
      test_latency
    )

    change_factor_output = sprintf('%.1fx', change_factor)
    percent_change = calculate_percent_change(latency_difference, reference_latency)

    formatted_change, color = formatted_change_and_color(change_factor_output, percent_change, latency_difference)
    formatted_change = latency_output(latency_difference) + " (#{formatted_change})"

    print_results(reference_latency_output, test_latency_output, formatted_change, color)
    print_diff_results(test.response_diff) if options.verify_no_diff
  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
# File 'lib/perf_check/output.rb', line 28

def print_results_of_compared_paths
  puts("==== Results ====")

  first_test = test_cases[0]
  second_test = test_cases[1]

  test_latency = first_test.this_latency
  reference_latency = second_test.reference_latency

  test_latency_output = latency_output(test_latency)
  reference_latency_output = latency_output(reference_latency)

  puts("reference path:" + first_test.resource.bold)
  puts("test path:" + second_test.resource.bold)

  latency_difference = test_latency - reference_latency

  change_factor = change_factor(latency_difference, reference_latency, test_latency)
  change_factor_output = sprintf('%.1fx', change_factor)
  percent_change = calculate_percent_change(latency_difference, reference_latency)

  formatted_change, color = formatted_change_and_color(
    change_factor_output,
    percent_change,
    latency_difference
  )

  formatted_change = latency_output(latency_difference) + " (#{formatted_change})"
  print_results(reference_latency_output, test_latency, formatted_change, color)
end

#runObject



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
# 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) ⇒ 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