module ActionController
  class Base
    # Profile each request and append the results to the response body.
    # Requires the ruby-prof C extension by Shugo Maeda which requires
    # Ruby 1.8 younger than 2005-03-22 (this means CVS.)
    #
    # TODO: Prof.started? to test whether profiling has begun
    # and Prof.clock_mode to read clock mode.
    #
    # Timing modes:
    #   :clock          Use clock(3).  Default for compatibility.
    #   :gettimeofday   Use gettimeofday(2).
    #   :cpu            Use the CPU clock counter like RDTSC on Pentium.
    #                   Available on Pentium and PowerPC only.
    def self.profile_requests(timing_mode = :clock)
      if ProfilerFilter.available?
        ProfilerFilter.timing_mode = timing_mode
        around_filter ProfilerFilter
      end
    end
  end
end

class ProfilerFilter
  class << self
    # Check whether this filter can be used.  It requires the ruby-prof
    # C extension and Ruby 1.8 younger than 2005-03-22 (this means CVS.)
    def available?
      load_prof
      true
    rescue LoadError
      false
    end

    # Timing modes:
    #   :clock          Use clock(3).  Default for compatibility.
    #   :gettimeofday   Use gettimeofday(2).
    #   :cpu            Use the CPU clock counter like RDTSC on Pentium.
    #                   Available on Pentium and PowerPC only.
    def timing_mode=(timing_mode)
      Prof.clock_mode = TIMING_MODES[timing_mode]
    rescue
      # Ignore if mode set while profiling.
      TIMING_MODES[timing_mode]
    end

    # Begin profiling.  Ignore if profiling has already begun.
    def before(controller)
      GC.disable
      Prof.start rescue true
    end

    # Finish profiling.  Append results to response body.
    def after(controller)
      results = Prof.stop
      GC.enable
      GC.start
      str = render_results(results)
      controller.response.body.gsub!(%r(</html>\s*$)m, "#{str}</html>")
    end

    private
      # Load ruby-prof library and initialize TIMING_MODES according
      # to available clock modes.
      def load_prof
        require 'prof'
        unless const_defined?(:TIMING_MODES)
          const_set :TIMING_MODES, {}
          %w(CLOCK GETTIMEOFDAY CPU).each do |mode|
            if Prof.const_defined?(mode)
              TIMING_MODES[mode.downcase.to_sym] = Prof.const_get(mode)
            end
          end
        end
      end

      # Preformatted output based on unprof.rb by Shugo Maeda.
      def render_results(results, percent_limit = 0.95)
        total = results.inject(0.0) { |sum, r| sum + r.self_time }
        total = 0.001 if total < 0.001

        str = "<pre>total request time: #{'%8d' % (total * 1000)} ms<br/>"
        str << "  %%   cumulative   self              self     total\n"
        str << " time   seconds   seconds    calls  ms/call  ms/call  name\n"

        sum = 0.0
        for r in results
          sum += r.self_time
          break if sum/total > percent_limit

          name =  if r.method_class.nil?
                    r.method_id.to_s
                  elsif r.method_class.is_a?(Class)
                    r.method_class.to_s + "#" + r.method_id.to_s
                  else
                    r.method_class.to_s + "." + r.method_id.to_s
                  end

          str << sprintf(
            "%6.2f %8.3f  %8.3f %8d %8.2f %8.2f  %s\n",
            r.self_time / total * 100,
            sum,
            r.self_time,
            r.count,
            r.self_time * 1000 / r.count,
            r.total_time * 1000 / r.count,
            CGI.escapeHTML(name)
          )
        end

        str << "</pre>"
      end
    end
  end
