Froze rails gems
[depot.git] / vendor / rails / activesupport / lib / active_support / testing / performance.rb
diff --git a/vendor/rails/activesupport/lib/active_support/testing/performance.rb b/vendor/rails/activesupport/lib/active_support/testing/performance.rb
new file mode 100644 (file)
index 0000000..bd136c2
--- /dev/null
@@ -0,0 +1,452 @@
+require 'rubygems'
+gem 'ruby-prof', '>= 0.6.1'
+require 'ruby-prof'
+
+require 'fileutils'
+require 'rails/version'
+
+module ActiveSupport
+  module Testing
+    module Performance
+      DEFAULTS =
+        if benchmark = ARGV.include?('--benchmark')  # HAX for rake test
+          { :benchmark => true,
+            :runs => 4,
+            :metrics => [:process_time, :memory, :objects, :gc_runs, :gc_time],
+            :output => 'tmp/performance' }
+        else
+          { :benchmark => false,
+            :runs => 1,
+            :min_percent => 0.01,
+            :metrics => [:process_time, :memory, :objects],
+            :formats => [:flat, :graph_html, :call_tree],
+            :output => 'tmp/performance' }
+        end.freeze
+
+      def self.included(base)
+        base.superclass_delegating_accessor :profile_options
+        base.profile_options = DEFAULTS
+      end
+
+      def full_test_name
+        "#{self.class.name}##{method_name}"
+      end
+
+      def run(result)
+        return if method_name =~ /^default_test$/
+
+        yield(self.class::STARTED, name)
+        @_result = result
+
+        run_warmup
+        if profile_options && metrics = profile_options[:metrics]
+          metrics.each do |metric_name|
+            if klass = Metrics[metric_name.to_sym]
+              run_profile(klass.new)
+              result.add_run
+            end
+          end
+        end
+
+        yield(self.class::FINISHED, name)
+      end
+
+      def run_test(metric, mode)
+        run_callbacks :setup
+        setup
+        metric.send(mode) { __send__ @method_name }
+      rescue ::Test::Unit::AssertionFailedError => e
+        add_failure(e.message, e.backtrace)
+      rescue StandardError, ScriptError
+        add_error($!)
+      ensure
+        begin
+          teardown
+          run_callbacks :teardown, :enumerator => :reverse_each
+        rescue ::Test::Unit::AssertionFailedError => e
+          add_failure(e.message, e.backtrace)
+        rescue StandardError, ScriptError
+          add_error($!)
+        end
+      end
+
+      protected
+        def run_warmup
+          GC.start
+
+          time = Metrics::Time.new
+          run_test(time, :benchmark)
+          puts "%s (%s warmup)" % [full_test_name, time.format(time.total)]
+
+          GC.start
+        end
+
+        def run_profile(metric)
+          klass = profile_options[:benchmark] ? Benchmarker : Profiler
+          performer = klass.new(self, metric)
+
+          performer.run
+          puts performer.report
+          performer.record
+        end
+
+      class Performer
+        delegate :run_test, :profile_options, :full_test_name, :to => :@harness
+
+        def initialize(harness, metric)
+          @harness, @metric = harness, metric
+        end
+
+        def report
+          rate = @total / profile_options[:runs]
+          '%20s: %s' % [@metric.name, @metric.format(rate)]
+        end
+
+        protected
+          def output_filename
+            "#{profile_options[:output]}/#{full_test_name}_#{@metric.name}"
+          end
+      end
+
+      class Benchmarker < Performer
+        def run
+          profile_options[:runs].to_i.times { run_test(@metric, :benchmark) }
+          @total = @metric.total
+        end
+
+        def record
+          avg = @metric.total / profile_options[:runs].to_i
+          now = Time.now.utc.xmlschema
+          with_output_file do |file|
+            file.puts "#{avg},#{now},#{environment}"
+          end
+        end
+
+        def environment
+          unless defined? @env
+            app = "#{$1}.#{$2}" if File.directory?('.git') && `git branch -v` =~ /^\* (\S+)\s+(\S+)/
+
+            rails = Rails::VERSION::STRING
+            if File.directory?('vendor/rails/.git')
+              Dir.chdir('vendor/rails') do
+                rails += ".#{$1}.#{$2}" if `git branch -v` =~ /^\* (\S+)\s+(\S+)/
+              end
+            end
+
+            ruby = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'
+            ruby += "-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}"
+
+            @env = [app, rails, ruby, RUBY_PLATFORM] * ','
+          end
+
+          @env
+        end
+
+        protected
+          HEADER = 'measurement,created_at,app,rails,ruby,platform'
+
+          def with_output_file
+            fname = output_filename
+
+            if new = !File.exist?(fname)
+              FileUtils.mkdir_p(File.dirname(fname))
+            end
+
+            File.open(fname, 'ab') do |file|
+              file.puts(HEADER) if new
+              yield file
+            end
+          end
+
+          def output_filename
+            "#{super}.csv"
+          end
+      end
+
+      class Profiler < Performer
+        def initialize(*args)
+          super
+          @supported = @metric.measure_mode rescue false
+        end
+
+        def run
+          return unless @supported
+
+          RubyProf.measure_mode = @metric.measure_mode
+          RubyProf.start
+          RubyProf.pause
+          profile_options[:runs].to_i.times { run_test(@metric, :profile) }
+          @data = RubyProf.stop
+          @total = @data.threads.values.sum(0) { |method_infos| method_infos.sort.last.total_time }
+        end
+
+        def report
+          if @supported
+            super
+          else
+            '%20s: unsupported' % @metric.name
+          end
+        end
+
+        def record
+          return unless @supported
+
+          klasses = profile_options[:formats].map { |f| RubyProf.const_get("#{f.to_s.camelize}Printer") }.compact
+
+          klasses.each do |klass|
+            fname = output_filename(klass)
+            FileUtils.mkdir_p(File.dirname(fname))
+            File.open(fname, 'wb') do |file|
+              klass.new(@data).print(file, profile_options.slice(:min_percent))
+            end
+          end
+        end
+
+        protected
+          def output_filename(printer_class)
+            suffix =
+              case printer_class.name.demodulize
+                when 'FlatPrinter'; 'flat.txt'
+                when 'GraphPrinter'; 'graph.txt'
+                when 'GraphHtmlPrinter'; 'graph.html'
+                when 'CallTreePrinter'; 'tree.txt'
+                else printer_class.name.sub(/Printer$/, '').underscore
+              end
+
+            "#{super()}_#{suffix}"
+          end
+      end
+
+      module Metrics
+        def self.[](name)
+          const_get(name.to_s.camelize)
+        rescue NameError
+          nil
+        end
+
+        class Base
+          attr_reader :total
+
+          def initialize
+            @total = 0
+          end
+
+          def name
+            @name ||= self.class.name.demodulize.underscore
+          end
+
+          def measure_mode
+            self.class::Mode
+          end
+
+          def measure
+            0
+          end
+
+          def benchmark
+            with_gc_stats do
+              before = measure
+              yield
+              @total += (measure - before)
+            end
+          end
+
+          def profile
+            RubyProf.resume
+            yield
+          ensure
+            RubyProf.pause
+          end
+
+          protected
+            if GC.respond_to?(:enable_stats)
+              def with_gc_stats
+                GC.enable_stats
+                yield
+              ensure
+                GC.disable_stats
+              end
+            elsif defined?(GC::Profiler)
+              def with_gc_stats
+                GC.start
+                GC.disable
+                GC::Profiler.enable
+                yield
+              ensure
+                GC::Profiler.disable
+                GC.enable
+              end
+            else
+              def with_gc_stats
+                yield
+              end
+            end
+        end
+
+        class Time < Base
+          def measure
+            ::Time.now.to_f
+          end
+
+          def format(measurement)
+            if measurement < 2
+              '%d ms' % (measurement * 1000)
+            else
+              '%.2f sec' % measurement
+            end
+          end
+        end
+
+        class ProcessTime < Time
+          Mode = RubyProf::PROCESS_TIME
+
+          def measure
+            RubyProf.measure_process_time
+          end
+        end
+
+        class WallTime < Time
+          Mode = RubyProf::WALL_TIME
+
+          def measure
+            RubyProf.measure_wall_time
+          end
+        end
+
+        class CpuTime < Time
+          Mode = RubyProf::CPU_TIME if RubyProf.const_defined?(:CPU_TIME)
+
+          def initialize(*args)
+            # FIXME: yeah my CPU is 2.33 GHz
+            RubyProf.cpu_frequency = 2.33e9
+            super
+          end
+
+          def measure
+            RubyProf.measure_cpu_time
+          end
+        end
+
+        class Memory < Base
+          Mode = RubyProf::MEMORY if RubyProf.const_defined?(:MEMORY)
+
+          # ruby-prof wrapper
+          if RubyProf.respond_to?(:measure_memory)
+            def measure
+              RubyProf.measure_memory / 1024.0
+            end
+
+          # Ruby 1.8 + railsbench patch
+          elsif GC.respond_to?(:allocated_size)
+            def measure
+              GC.allocated_size / 1024.0
+            end
+
+          # Ruby 1.8 + lloyd patch
+          elsif GC.respond_to?(:heap_info)
+            def measure
+              GC.heap_info['heap_current_memory'] / 1024.0
+            end
+
+          # Ruby 1.9 with total_malloc_allocated_size patch
+          elsif GC.respond_to?(:malloc_total_allocated_size)
+            def measure
+              GC.total_malloc_allocated_size / 1024.0
+            end
+
+          # Ruby 1.9 unpatched
+          elsif GC.respond_to?(:malloc_allocated_size)
+            def measure
+              GC.malloc_allocated_size / 1024.0
+            end
+
+          # Ruby 1.9 + GC profiler patch
+          elsif defined?(GC::Profiler)
+            def measure
+              GC.enable
+              GC.start
+              kb = GC::Profiler.data.last[:HEAP_USE_SIZE] / 1024.0
+              GC.disable
+              kb
+            end
+          end
+
+          def format(measurement)
+            '%.2f KB' % measurement
+          end
+        end
+
+        class Objects < Base
+          Mode = RubyProf::ALLOCATIONS if RubyProf.const_defined?(:ALLOCATIONS)
+
+          if RubyProf.respond_to?(:measure_allocations)
+            def measure
+              RubyProf.measure_allocations
+            end
+
+          # Ruby 1.8 + railsbench patch
+          elsif ObjectSpace.respond_to?(:allocated_objects)
+            def measure
+              ObjectSpace.allocated_objects
+            end
+
+          # Ruby 1.9 + GC profiler patch
+          elsif defined?(GC::Profiler)
+            def measure
+              GC.enable
+              GC.start
+              last = GC::Profiler.data.last
+              count = last[:HEAP_LIVE_OBJECTS] + last[:HEAP_FREE_OBJECTS]
+              GC.disable
+              count
+            end
+          end
+
+          def format(measurement)
+            measurement.to_i.to_s
+          end
+        end
+
+        class GcRuns < Base
+          Mode = RubyProf::GC_RUNS if RubyProf.const_defined?(:GC_RUNS)
+
+          if RubyProf.respond_to?(:measure_gc_runs)
+            def measure
+              RubyProf.measure_gc_runs
+            end
+          elsif GC.respond_to?(:collections)
+            def measure
+              GC.collections
+            end
+          elsif GC.respond_to?(:heap_info)
+            def measure
+              GC.heap_info['num_gc_passes']
+            end
+          end
+
+          def format(measurement)
+            measurement.to_i.to_s
+          end
+        end
+
+        class GcTime < Base
+          Mode = RubyProf::GC_TIME if RubyProf.const_defined?(:GC_TIME)
+
+          if RubyProf.respond_to?(:measure_gc_time)
+            def measure
+              RubyProf.measure_gc_time
+            end
+          elsif GC.respond_to?(:time)
+            def measure
+              GC.time
+            end
+          end
+
+          def format(measurement)
+            '%d ms' % (measurement / 1000)
+          end
+        end
+      end
+    end
+  end
+end