Class: PerfCheck

Inherits:
Object show all
Defined in:
lib/perf_check.rb,
lib/perf_check/git.rb,
lib/perf_check/config.rb,
lib/perf_check/logger.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: Git, Middleware, Railtie, Server, TestCase

Constant Summary collapse

Options =
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 10') do |n|
    config.number_of_requests = n.to_i
  end

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

  opts.on('--quick', '-q',
          'Fire off 5 requests just on this branch, no comparison with master') do
    config.number_of_requests = 5
    config.reference = nil
  end

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

  opts.on('--fail-fast', '-f', 'Bail immediately on non-200 HTTP response') do
    config[:fail_fast?] = true
  end

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

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

  opts.separator "\nMisc"
  opts.on('--cookie COOKIE', '-c') do |cookie|
    config.cookie = cookie
  end

  opts.on('--json', '-j') do
    config.json = true
  end

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

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

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

  opts.on('--diff') do
    config.diff = true
    config.brief = true
    config.verify_responses = true
    config.number_of_requests = 1
  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-responses

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

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializePerfCheck

Returns a new instance of PerfCheck.



27
28
29
30
31
# File 'lib/perf_check.rb', line 27

def initialize
  self.options = OpenStruct.new
  self.server = Server.new
  self.test_cases = []
end

Instance Attribute Details

#options=(value) ⇒ Object

Sets the attribute options

Parameters:

  • value

    the value to set the attribute options to.



10
11
12
# File 'lib/perf_check.rb', line 10

def options=(value)
  @options = value
end

#serverObject

Returns the value of attribute server.



10
11
12
# File 'lib/perf_check.rb', line 10

def server
  @server
end

#test_casesObject

Returns the value of attribute test_cases.



10
11
12
# File 'lib/perf_check.rb', line 10

def test_cases
  @test_cases
end

Class Method Details

.app_rootObject



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

def self.app_root
  @app_root ||= begin
    dir = Dir.pwd
    until dir == '/' || File.exist?("#{dir}/config/application.rb")
      dir = File.dirname(dir)
    end

    unless File.exist?("#{dir}/config/application.rb")
      abort("perf_check should be run from a rails directory")
    end

    dir
  end
end

.before_start(&block) ⇒ Object



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

def self.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 self.before_start_callbacks
  (@before_start_callbacks || []) + [
    proc {
      logger.info("=" * 77)
      logger.info("PERRRRF CHERRRK! Grab a ☕️  and don't touch your working tree (we automate git)")
      logger.info("=" * 77)
    }
  ]
end

.configObject



4
5
6
7
8
9
10
11
12
13
14
# File 'lib/perf_check/config.rb', line 4

def self.config
  @config ||= OpenStruct.new(
    number_of_requests: 10,
    reference: 'master',
    cookie: nil,
    http_statuses: [200],
    verify_responses: false,
    caching: true,
    json: false
   )
end

.diff_optionsObject



116
117
118
119
# File 'lib/perf_check/config.rb', line 116

def self.diff_options
  @@diff_options ||=
    ['-U3', '--ignore-matching-lines=/mini-profiler-resources/includes.js']
end

.load_configObject



121
122
123
124
125
# File 'lib/perf_check/config.rb', line 121

def self.load_config
  if File.exists?("#{app_root}/config/perf_check.rb")
    require "#{app_root}/config/perf_check"
  end
end

.loggerObject



5
6
7
8
9
10
11
# File 'lib/perf_check/logger.rb', line 5

def self.logger
  @logger ||= Logger.new(STDERR).tap do |logger|
    logger.formatter = proc do |severity, datetime, progname, msg|
      "[#{datetime}] #{sprintf('%5s', severity)} --: #{msg}\n"
    end
  end
end

.when_finished(&block) ⇒ Object



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

def self.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 self.when_finished_callbacks
  @when_finished_callbacks || []
end

Instance Method Details

#add_test_case(route) ⇒ Object



33
34
35
# File 'lib/perf_check.rb', line 33

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

#configObject Also known as: options



16
17
18
# File 'lib/perf_check/config.rb', line 16

def config
  PerfCheck.config
end

#loggerObject



13
# File 'lib/perf_check/logger.rb', line 13

def logger; self.class.logger; 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_responses
    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_responses
  end
end


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

def print_json_results
  results = []
  test_cases.each do |test|
    results.push(
      route: test.resource,
      latency: test.this_latency,
      query_count: test.this_query_count,
      requests: []
    )

    test.this_profiles.each do |profile|
      results[-1][:requests].push(
        latency: profile.latency,
        query_count: profile.query_count,
        server_memory: profile.server_memory,
        response_code: profile.response_code,
        miniprofiler_url: profile.profile_url
      )
    end

    if options.reference
      results[-1].merge!(
        reference_latency: test.reference_latency,
        latency_difference: test.latency_difference,
        speedup_factor: test.reference_latency / test.this_latency,
        reference_query_count: test.reference_query_count,
        reference_requests: []
      )

      test.reference_profiles.each do |profile|
        results[-1][:reference_requests].push(
          latency: profile.latency,
          query_count: profile.query_count,
          server_memory: profile.server_memory,
          response_code: profile.response_code,
          miniprofiler_url: profile.profile_url
        )
      end
    end
  end
  puts JSON.pretty_generate(results)
end

#runObject



37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/perf_check.rb', line 37

def run
  trigger_before_start_callbacks

  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
end

#trigger_before_start_callbacksObject



28
29
30
# File 'lib/perf_check/callbacks.rb', line 28

def trigger_before_start_callbacks
  PerfCheck.before_start_callbacks.each{ |f| f.call(self) }
end

#trigger_when_finished_callbacks(data = {}) ⇒ Object



32
33
34
35
36
37
38
39
40
41
# File 'lib/perf_check/callbacks.rb', line 32

def trigger_when_finished_callbacks(data={})
  data = data.merge(:current_branch => PerfCheck::Git.current_branch)
  results = OpenStruct.new(data)
  results[:ARGV] = ORIGINAL_ARGV
  if test_cases.size == 1
    results.current_latency = test_cases.first.this_latency
    results.reference_latency = test_cases.first.reference_latency
  end
  PerfCheck.when_finished_callbacks.each{ |f| f.call(results) }
end