aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller/benchmarking.rb
blob: 746894497cbadfc09bdbe5c86443fb96cafd4f33 (plain) (blame)
1
2
3
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
103
104
105
106
require 'benchmark'

module ActionController #:nodoc:
  # The benchmarking module times the performance of actions and reports to the logger. If the Active Record
  # package has been included, a separate timing section for database calls will be added as well.
  module Benchmarking #:nodoc:
    def self.included(base)
      base.extend(ClassMethods)

      base.class_eval do
        alias_method_chain :perform_action, :benchmark
        alias_method_chain :render, :benchmark
      end
    end

    module ClassMethods
      # Log and benchmark the workings of a single block and silence whatever logging that may have happened inside it 
      # (unless <tt>use_silence</tt> is set to false).
      #
      # The benchmark is only recorded if the current level of the logger matches the <tt>log_level</tt>, which makes it
      # easy to include benchmarking statements in production software that will remain inexpensive because the benchmark
      # will only be conducted if the log level is low enough.
      def benchmark(title, log_level = Logger::DEBUG, use_silence = true)
        if logger && logger.level == log_level
          result = nil
          seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield }
          logger.add(log_level, "#{title} (#{('%.1f' % (seconds * 1000))}ms)")
          result
        else
          yield
        end
      end

      # Silences the logger for the duration of the block.
      def silence
        old_logger_level, logger.level = logger.level, Logger::ERROR if logger
        yield
      ensure
        logger.level = old_logger_level if logger
      end
    end

    protected
      def render_with_benchmark(options = nil, extra_options = {}, &block)
        if logger
          if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
            db_runtime = ActiveRecord::Base.connection.reset_runtime
          end

          render_output = nil
          @view_runtime = Benchmark::realtime { render_output = render_without_benchmark(options, extra_options, &block) }

          if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
            @db_rt_before_render = db_runtime
            @db_rt_after_render = ActiveRecord::Base.connection.reset_runtime
            @view_runtime -= @db_rt_after_render
          end

          render_output
        else
          render_without_benchmark(options, extra_options, &block)
        end
      end    

    private
      def perform_action_with_benchmark
        if logger
          seconds = [ Benchmark::measure{ perform_action_without_benchmark }.real, 0.0001 ].max
          logging_view          = defined?(@view_runtime)
          logging_active_record = Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?

          log_message  = "Completed in #{sprintf("%.0f", seconds * 1000)}ms"

          if logging_view || logging_active_record
            log_message << " ("
            log_message << view_runtime if logging_view

            if logging_active_record
              log_message << ", " + active_record_runtime + ")"
            else
              ")"
            end
          end

          log_message << " | #{headers["Status"]}"
          log_message << " [#{complete_request_uri rescue "unknown"}]"

          logger.info(log_message)
          response.headers["X-Runtime"] = "#{sprintf("%.0f", seconds * 1000)}ms"
        else
          perform_action_without_benchmark
        end
      end

      def view_runtime
        "View: %.0f" % (@view_runtime * 1000)
      end

      def active_record_runtime
        db_runtime = ActiveRecord::Base.connection.reset_runtime
        db_runtime += @db_rt_before_render if @db_rt_before_render
        db_runtime += @db_rt_after_render if @db_rt_after_render
        "DB: %.0f" % (db_runtime * 1000)
      end
  end
end