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: Exception, Git, Middleware, Railtie, Server, TestCase

Instance Attribute Summary collapse

Class Method 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
# 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_responses: 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

Class Method Details

.diff_optionsObject



104
105
106
107
# File 'lib/perf_check/config.rb', line 104

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

Instance Method Details

#add_test_case(route) ⇒ Object



57
58
59
# File 'lib/perf_check.rb', line 57

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



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/perf_check.rb', line 41

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"
    ensure
      Dir.chdir(dir)
      Kernel.send(:remove_method, :perf_check)
    end
  end
end

#option_parserObject



4
5
6
7
8
9
10
11
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
# File 'lib/perf_check/config.rb', line 4

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('--json', '-j') do
      options.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
      options.verify_responses = true
    end

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

#      opts.on('--diff') do
#        options.diff = true
#        options.brief = true
#        options.verify_responses = true
#        options.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
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.speedup_factor,
        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



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/perf_check.rb', line 61

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
  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