aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport
diff options
context:
space:
mode:
authorJeremy Kemper <jeremy@bitsweat.net>2011-05-17 14:28:40 -0700
committerJeremy Kemper <jeremy@bitsweat.net>2011-05-17 14:28:40 -0700
commitb77e032ccf50249685c672c2d75f173db2ee2dbf (patch)
tree378cb69c599c5d0198731d1ffe6696e427ffe42c /activesupport
parent1d8d799a01956525eef9bf22088c34e0d1ad4423 (diff)
parent7fcf0ca69e18841be9fb0dcce2721f4ed2c8eca9 (diff)
downloadrails-b77e032ccf50249685c672c2d75f173db2ee2dbf.tar.gz
rails-b77e032ccf50249685c672c2d75f173db2ee2dbf.tar.bz2
rails-b77e032ccf50249685c672c2d75f173db2ee2dbf.zip
Merge pull request #247 from goncalossilva/performance_test
Performance tests improved
Diffstat (limited to 'activesupport')
-rw-r--r--activesupport/lib/active_support/testing/performance.rb637
-rw-r--r--activesupport/lib/active_support/testing/performance/jruby.rb115
-rw-r--r--activesupport/lib/active_support/testing/performance/rubinius.rb113
-rw-r--r--activesupport/lib/active_support/testing/performance/ruby.rb152
-rw-r--r--activesupport/lib/active_support/testing/performance/ruby/mri.rb59
-rw-r--r--activesupport/lib/active_support/testing/performance/ruby/yarv.rb57
6 files changed, 738 insertions, 395 deletions
diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb
index 7cd9bfa947..02c19448fd 100644
--- a/activesupport/lib/active_support/testing/performance.rb
+++ b/activesupport/lib/active_support/testing/performance.rb
@@ -1,470 +1,317 @@
-begin
- require 'ruby-prof'
-
- require 'fileutils'
- require 'rails/version'
- require 'active_support/concern'
- require 'active_support/core_ext/class/delegating_attributes'
- require 'active_support/core_ext/string/inflections'
-
- module ActiveSupport
- module Testing
- module Performance
- extend ActiveSupport::Concern
-
- included do
- superclass_delegating_accessor :profile_options
- self.profile_options = DEFAULTS
-
- if defined?(MiniTest::Assertions) && TestCase < MiniTest::Assertions
- include ForMiniTest
- else
- include ForClassicTestUnit
- end
+require 'fileutils'
+require 'rails/version'
+require 'active_support/concern'
+require 'active_support/core_ext/class/delegating_attributes'
+require 'active_support/core_ext/string/inflections'
+require 'action_view/helpers/number_helper'
+
+module ActiveSupport
+ module Testing
+ module Performance
+ extend ActiveSupport::Concern
+
+ included do
+ superclass_delegating_accessor :profile_options
+ self.profile_options = {}
+
+ if defined?(MiniTest::Assertions) && TestCase < MiniTest::Assertions
+ include ForMiniTest
+ else
+ include ForClassicTestUnit
end
+ end
+
+ # each implementation should define metrics and freeze the defaults
+ DEFAULTS =
+ if ARGV.include?('--benchmark') # HAX for rake test
+ { :runs => 4,
+ :output => 'tmp/performance',
+ :benchmark => true }
+ else
+ { :runs => 1,
+ :output => 'tmp/performance',
+ :benchmark => false }
+ end
+
+ def full_profile_options
+ DEFAULTS.merge(profile_options)
+ end
- module ForMiniTest
- def run(runner)
- @runner = runner
-
- 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)
- end
+ def full_test_name
+ "#{self.class.name}##{method_name}"
+ end
+
+ module ForMiniTest
+ def run(runner)
+ @runner = runner
+
+ run_warmup
+ if full_profile_options && metrics = full_profile_options[:metrics]
+ metrics.each do |metric_name|
+ if klass = Metrics[metric_name.to_sym]
+ run_profile(klass.new)
end
end
end
+
+ return
+ end
- def run_test(metric, mode)
- result = '.'
+ def run_test(metric, mode)
+ result = '.'
+ begin
+ run_callbacks :setup
+ setup
+ metric.send(mode) { __send__ method_name }
+ rescue Exception => e
+ result = @runner.puke(self.class, method_name, e)
+ ensure
begin
- run_callbacks :setup
- setup
- metric.send(mode) { __send__ method_name }
+ teardown
+ run_callbacks :teardown, :enumerator => :reverse_each
rescue Exception => e
result = @runner.puke(self.class, method_name, e)
- ensure
- begin
- teardown
- run_callbacks :teardown, :enumerator => :reverse_each
- rescue Exception => e
- result = @runner.puke(self.class, method_name, e)
- end
end
- result
end
+ result
end
+ end
- module ForClassicTestUnit
- def run(result)
- return if method_name =~ /^default_test$/
+ module ForClassicTestUnit
+ def run(result)
+ return if method_name =~ /^default_test$/
- yield(self.class::STARTED, name)
- @_result = result
+ 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
+ run_warmup
+ if full_profile_options && metrics = full_profile_options[:metrics]
+ metrics.each do |metric_name|
+ if klass = Metrics[metric_name.to_sym]
+ run_profile(klass.new)
+ result.add_run
+ else
+ puts '%20s: unsupported' % metric_name
end
end
-
- yield(self.class::FINISHED, name)
end
- def run_test(metric, mode)
- run_callbacks :setup
- setup
- metric.send(mode) { __send__ @method_name }
+ 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 => e
+ add_error(e)
+ ensure
+ begin
+ teardown
+ run_callbacks :teardown, :enumerator => :reverse_each
rescue ::Test::Unit::AssertionFailedError => e
- add_failure(e.message, e.backtrace)
+ add_failure(e.message, e.backtrace)
rescue StandardError, ScriptError => e
add_error(e)
- ensure
- begin
- teardown
- run_callbacks :teardown, :enumerator => :reverse_each
- rescue ::Test::Unit::AssertionFailedError => e
- add_failure(e.message, e.backtrace)
- rescue StandardError, ScriptError => e
- add_error(e)
- end
end
end
+ end
- DEFAULTS =
- if benchmark = ARGV.include?('--benchmark') # HAX for rake test
- { :benchmark => true,
- :runs => 4,
- :metrics => [:wall_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 full_test_name
- "#{self.class.name}##{method_name}"
- 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)]
+ protected
+ # overridden by each implementation
+ def run_gc; end
+
+ def run_warmup
+ run_gc
- GC.start
- end
+ time = Metrics::Time.new
+ run_test(time, :benchmark)
+ puts "%s (%s warmup)" % [full_test_name, time.format(time.total)]
- def run_profile(metric)
- klass = profile_options[:benchmark] ? Benchmarker : Profiler
- performer = klass.new(self, metric)
-
- performer.run
- puts performer.report
- performer.record
- end
+ run_gc
+ end
+
+ def run_profile(metric)
+ klass = full_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
+ class Performer
+ delegate :run_test, :full_profile_options, :full_test_name, :to => :@harness
- def initialize(harness, metric)
- @harness, @metric = harness, metric
- end
+ def initialize(harness, metric)
+ @harness, @metric, @supported = harness, metric, false
+ end
- def report
- rate = @total / profile_options[:runs]
+ def report
+ if @supported
+ rate = @total / full_profile_options[:runs]
'%20s: %s' % [@metric.name, @metric.format(rate)]
+ else
+ '%20s: unsupported' % @metric.name
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
+ protected
+ def output_filename
+ "#{full_profile_options[:output]}/#{full_test_name}_#{@metric.name}"
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
+
+ # overridden by each implementation
+ class Profiler < Performer
+ def time_with_block
+ before = Time.now
+ yield
+ Time.now - before
end
+
+ def run; end
+ def record; 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.max.total_time }
- end
+ class Benchmarker < Performer
+ def initialize(*args)
+ super
+ @supported = @metric.respond_to?('measure')
+ end
+
+ def run
+ return unless @supported
+
+ full_profile_options[:runs].to_i.times { run_test(@metric, :benchmark) }
+ @total = @metric.total
+ end
- def report
- if @supported
- super
- else
- '%20s: unsupported' % @metric.name
- end
+ def record
+ avg = @metric.total / full_profile_options[:runs].to_i
+ now = Time.now.utc.xmlschema
+ with_output_file do |file|
+ file.puts "#{avg},#{now},#{environment}"
end
+ end
- def record
- return unless @supported
-
- klasses = profile_options[:formats].map { |f| RubyProf.const_get("#{f.to_s.camelize}Printer") }.compact
+ def environment
+ unless defined? @env
+ app = "#{$1}.#{$2}" if File.directory?('.git') && `git branch -v` =~ /^\* (\S+)\s+(\S+)/
- 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))
+ 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
- end
- protected
- def output_filename(printer_class)
- suffix =
- case printer_class.name.demodulize
- when 'FlatPrinter'; 'flat.txt'
- when 'FlatPrinterWithLineNumbers'; 'flat_line_numbers.txt'
- when 'GraphPrinter'; 'graph.txt'
- when 'GraphHtmlPrinter'; 'graph.html'
- when 'GraphYamlPrinter'; 'graph.yml'
- when 'CallTreePrinter'; 'tree.txt'
- when 'CallStackPrinter'; 'stack.html'
- when 'DotPrinter'; 'graph.dot'
- else printer_class.name.sub(/Printer$/, '').underscore
- end
-
- "#{super()}_#{suffix}"
- end
- end
+ ruby = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'
+ ruby += "-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}"
- module Metrics
- def self.[](name)
- const_get(name.to_s.camelize)
- rescue NameError
- nil
+ @env = [app, rails, ruby, RUBY_PLATFORM] * ','
end
- class Base
- attr_reader :total
-
- def initialize
- @total = 0
- end
+ @env
+ end
- def name
- @name ||= self.class.name.demodulize.underscore
- end
+ protected
+ HEADER = 'measurement,created_at,app,rails,ruby,platform'
- def measure_mode
- self.class::Mode
- end
+ def with_output_file
+ fname = output_filename
- def measure
- 0
- end
-
- def benchmark
- with_gc_stats do
- before = measure
- yield
- @total += (measure - before)
- end
+ if new = !File.exist?(fname)
+ FileUtils.mkdir_p(File.dirname(fname))
end
- def profile
- RubyProf.resume
- yield
- ensure
- RubyProf.pause
+ File.open(fname, 'ab') do |file|
+ file.puts(HEADER) if new
+ yield file
end
-
- protected
- # Ruby 1.9 with GC::Profiler
- if defined?(GC::Profiler)
- def with_gc_stats
- GC::Profiler.enable
- GC.start
- yield
- ensure
- GC::Profiler.disable
- end
-
- # Ruby 1.8 + ruby-prof wrapper (enable/disable stats for Benchmarker)
- elsif GC.respond_to?(:enable_stats)
- def with_gc_stats
- GC.enable_stats
- yield
- ensure
- GC.disable_stats
- 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 < 1
- '%d ms' % (measurement * 1000)
- else
- '%.2f sec' % measurement
- end
- end
+ def output_filename
+ "#{super}.csv"
end
+ end
+
+ module Metrics
+ def self.[](name)
+ const_get(name.to_s.camelize)
+ rescue NameError
+ nil
+ end
- class ProcessTime < Time
- Mode = RubyProf::PROCESS_TIME
+ class Base
+ include ActionView::Helpers::NumberHelper
+
+ attr_reader :total
- def measure
- RubyProf.measure_process_time
- end
+ def initialize
+ @total = 0
end
- class WallTime < Time
- Mode = RubyProf::WALL_TIME
-
- def measure
- RubyProf.measure_wall_time
- end
+ def name
+ @name ||= self.class.name.demodulize.underscore
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 unless RubyProf.cpu_frequency > 0
- super
- end
-
- def measure
- RubyProf.measure_cpu_time
+ def benchmark
+ with_gc_stats do
+ before = measure
+ yield
+ @total += (measure - before)
end
end
-
- class Memory < Base
- Mode = RubyProf::MEMORY if RubyProf.const_defined?(:MEMORY)
-
- # Ruby 1.9 + GCdata patch
- if GC.respond_to?(:malloc_allocated_size)
- def measure
- GC.malloc_allocated_size / 1024.0
- end
-
- # Ruby 1.8 + ruby-prof wrapper
- elsif RubyProf.respond_to?(:measure_memory)
- def measure
- RubyProf.measure_memory / 1024.0
- end
- end
-
- def format(measurement)
- '%.2f KB' % measurement
- end
+
+ # overridden by each implementation
+ def profile; end
+
+ protected
+ # overridden by each implementation
+ def with_gc_stats; end
+ end
+
+ class Time < Base
+ def measure
+ ::Time.now.to_f
end
- class Objects < Base
- Mode = RubyProf::ALLOCATIONS if RubyProf.const_defined?(:ALLOCATIONS)
-
- # Ruby 1.9 + GCdata patch
- if GC.respond_to?(:malloc_allocations)
- def measure
- GC.malloc_allocations
- end
-
- # Ruby 1.8 + ruby-prof wrapper
- elsif RubyProf.respond_to?(:measure_allocations)
- def measure
- RubyProf.measure_allocations
- end
- end
-
- def format(measurement)
- measurement.to_i.to_s
+ def format(measurement)
+ if measurement < 1
+ '%d ms' % (measurement * 1000)
+ else
+ '%.2f sec' % measurement
end
end
-
- class GcRuns < Base
- Mode = RubyProf::GC_RUNS if RubyProf.const_defined?(:GC_RUNS)
-
- # Ruby 1.9
- if GC.respond_to?(:count)
- def measure
- GC.count
- end
-
- # Ruby 1.8 + ruby-prof wrapper
- elsif RubyProf.respond_to?(:measure_gc_runs)
- def measure
- RubyProf.measure_gc_runs
- end
- end
-
- def format(measurement)
- measurement.to_i.to_s
- end
+ end
+
+ class Amount < Base
+ def format(measurement)
+ number_with_delimiter(measurement.floor)
end
-
- class GcTime < Base
- Mode = RubyProf::GC_TIME if RubyProf.const_defined?(:GC_TIME)
-
- # Ruby 1.9 with GC::Profiler
- if defined?(GC::Profiler) && GC::Profiler.respond_to?(:total_time)
- def measure
- GC::Profiler.total_time
- end
-
- # Ruby 1.8 + ruby-prof wrapper
- elsif RubyProf.respond_to?(:measure_gc_time)
- def measure
- RubyProf.measure_gc_time / 1000
- end
- end
-
- def format(measurement)
- '%.2f ms' % measurement
- end
+ end
+
+ class DigitalInformationUnit < Base
+ def format(measurement)
+ number_to_human_size(measurement, :precision => 2)
end
end
+
+ # each implementation provides its own metrics like ProcessTime, Memory or GcRuns
end
end
end
-rescue LoadError
+end
+
+RUBY_ENGINE = 'ruby' unless defined?(RUBY_ENGINE) # mri 1.8
+case RUBY_ENGINE
+ when 'ruby' then require 'active_support/testing/performance/ruby'
+ when 'rbx' then require 'active_support/testing/performance/rubinius'
+ when 'jruby' then require 'active_support/testing/performance/jruby'
+ else
+ $stderr.puts 'Your ruby interpreter is not supported for benchmarking.'
+ exit
end
diff --git a/activesupport/lib/active_support/testing/performance/jruby.rb b/activesupport/lib/active_support/testing/performance/jruby.rb
new file mode 100644
index 0000000000..6b27959840
--- /dev/null
+++ b/activesupport/lib/active_support/testing/performance/jruby.rb
@@ -0,0 +1,115 @@
+require 'jruby/profiler'
+require 'java'
+import java.lang.management.ManagementFactory
+
+module ActiveSupport
+ module Testing
+ module Performance
+ DEFAULTS.merge!(
+ if ARGV.include?('--benchmark')
+ {:metrics => [:wall_time, :user_time, :memory, :gc_runs, :gc_time]}
+ else
+ { :metrics => [:wall_time],
+ :formats => [:flat, :graph] }
+ end).freeze
+
+ protected
+ def run_gc
+ ManagementFactory.memory_mx_bean.gc
+ end
+
+ class Profiler < Performer
+ def initialize(*args)
+ super
+ @supported = @metric.is_a?(Metrics::WallTime)
+ end
+
+ def run
+ return unless @supported
+
+ @total = time_with_block do
+ @data = JRuby::Profiler.profile do
+ full_profile_options[:runs].to_i.times { run_test(@metric, :profile) }
+ end
+ end
+ end
+
+ def record
+ return unless @supported
+
+ klasses = full_profile_options[:formats].map { |f| JRuby::Profiler.const_get("#{f.to_s.camelize}ProfilePrinter") }.compact
+
+ klasses.each do |klass|
+ fname = output_filename(klass)
+ FileUtils.mkdir_p(File.dirname(fname))
+ file = File.open(fname, 'wb') do |file|
+ klass.new(@data).printProfile(file)
+ end
+ end
+ end
+
+ protected
+ def output_filename(printer_class)
+ suffix =
+ case printer_class.name.demodulize
+ when 'FlatProfilePrinter'; 'flat.txt'
+ when 'GraphProfilePrinter'; 'graph.txt'
+ else printer_class.name.sub(/ProfilePrinter$/, '').underscore
+ end
+
+ "#{super()}_#{suffix}"
+ end
+ end
+
+ module Metrics
+ class Base
+ def profile
+ yield
+ end
+
+ protected
+ def with_gc_stats
+ ManagementFactory.memory_mx_bean.gc
+ yield
+ end
+ end
+
+ class WallTime < Time
+ def measure
+ super
+ end
+ end
+
+ class CpuTime < Time
+ def measure
+ ManagementFactory.thread_mx_bean.get_current_thread_cpu_time / 1000 / 1000 / 1000.0 # seconds
+ end
+ end
+
+ class UserTime < Time
+ def measure
+ ManagementFactory.thread_mx_bean.get_current_thread_user_time / 1000 / 1000 / 1000.0 # seconds
+ end
+ end
+
+ class Memory < DigitalInformationUnit
+ def measure
+ ManagementFactory.memory_mx_bean.non_heap_memory_usage.used + ManagementFactory.memory_mx_bean.heap_memory_usage.used
+ end
+ end
+
+ class GcRuns < Amount
+ def measure
+ ManagementFactory.garbage_collector_mx_beans.inject(0) { |total_runs, current_gc| total_runs += current_gc.collection_count }
+ end
+ end
+
+ class GcTime < Time
+ def measure
+ ManagementFactory.garbage_collector_mx_beans.inject(0) { |total_time, current_gc| total_time += current_gc.collection_time } / 1000.0 # seconds
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/testing/performance/rubinius.rb b/activesupport/lib/active_support/testing/performance/rubinius.rb
new file mode 100644
index 0000000000..198d235548
--- /dev/null
+++ b/activesupport/lib/active_support/testing/performance/rubinius.rb
@@ -0,0 +1,113 @@
+require 'rubinius/agent'
+
+module ActiveSupport
+ module Testing
+ module Performance
+ DEFAULTS.merge!(
+ if ARGV.include?('--benchmark')
+ {:metrics => [:wall_time, :memory, :objects, :gc_runs, :gc_time]}
+ else
+ { :metrics => [:wall_time],
+ :formats => [:flat, :graph] }
+ end).freeze
+
+ protected
+ def run_gc
+ GC.run(true)
+ end
+
+ class Performer; end
+
+ class Profiler < Performer
+ def initialize(*args)
+ super
+ @supported = @metric.is_a?(Metrics::WallTime)
+ end
+
+ def run
+ return unless @supported
+
+ @profiler = Rubinius::Profiler::Instrumenter.new
+
+ @total = time_with_block do
+ @profiler.profile(false) do
+ full_profile_options[:runs].to_i.times { run_test(@metric, :profile) }
+ end
+ end
+ end
+
+ def record
+ return unless @supported
+
+ if(full_profile_options[:formats].include?(:flat))
+ create_path_and_open_file(:flat) do |file|
+ @profiler.show(file)
+ end
+ end
+
+ if(full_profile_options[:formats].include?(:graph))
+ create_path_and_open_file(:graph) do |file|
+ @profiler.show(file)
+ end
+ end
+ end
+
+ protected
+ def create_path_and_open_file(printer_name)
+ fname = "#{output_filename}_#{printer_name}.txt"
+ FileUtils.mkdir_p(File.dirname(fname))
+ File.open(fname, 'wb') do |file|
+ yield(file)
+ end
+ end
+ end
+
+ module Metrics
+ class Base
+ attr_reader :loopback
+
+ def profile
+ yield
+ end
+
+ protected
+ def with_gc_stats
+ @loopback = Rubinius::Agent.loopback
+ GC.run(true)
+ yield
+ end
+ end
+
+ class WallTime < Time
+ def measure
+ super
+ end
+ end
+
+ class Memory < DigitalInformationUnit
+ def measure
+ loopback.get("system.memory.counter.bytes").last
+ end
+ end
+
+ class Objects < Amount
+ def measure
+ loopback.get("system.memory.counter.objects").last
+ end
+ end
+
+ class GcRuns < Amount
+ def measure
+ loopback.get("system.gc.full.count").last + loopback.get("system.gc.young.count").last
+ end
+ end
+
+ class GcTime < Time
+ def measure
+ (loopback.get("system.gc.full.wallclock").last + loopback.get("system.gc.young.wallclock").last) / 1000.0
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/testing/performance/ruby.rb b/activesupport/lib/active_support/testing/performance/ruby.rb
new file mode 100644
index 0000000000..b29ec6719c
--- /dev/null
+++ b/activesupport/lib/active_support/testing/performance/ruby.rb
@@ -0,0 +1,152 @@
+begin
+ require 'ruby-prof'
+rescue LoadError
+ $stderr.puts 'Specify ruby-prof as application\'s dependency in Gemfile to run benchmarks.'
+ exit
+end
+
+module ActiveSupport
+ module Testing
+ module Performance
+ DEFAULTS.merge!(
+ if ARGV.include?('--benchmark')
+ { :metrics => [:wall_time, :memory, :objects, :gc_runs, :gc_time] }
+ else
+ { :min_percent => 0.01,
+ :metrics => [:process_time, :memory, :objects],
+ :formats => [:flat, :graph_html, :call_tree, :call_stack] }
+ end).freeze
+
+ protected
+ def run_gc
+ GC.start
+ 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
+ full_profile_options[:runs].to_i.times { run_test(@metric, :profile) }
+ @data = RubyProf.stop
+ @total = @data.threads.values.sum(0) { |method_infos| method_infos.max.total_time }
+ end
+
+ def record
+ return unless @supported
+
+ klasses = full_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, full_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 'FlatPrinterWithLineNumbers'; 'flat_line_numbers.txt'
+ when 'GraphPrinter'; 'graph.txt'
+ when 'GraphHtmlPrinter'; 'graph.html'
+ when 'GraphYamlPrinter'; 'graph.yml'
+ when 'CallTreePrinter'; 'tree.txt'
+ when 'CallStackPrinter'; 'stack.html'
+ when 'DotPrinter'; 'graph.dot'
+ else printer_class.name.sub(/Printer$/, '').underscore
+ end
+
+ "#{super()}_#{suffix}"
+ end
+ end
+
+ module Metrics
+ class Base
+ def measure_mode
+ self.class::Mode
+ end
+
+ def profile
+ RubyProf.resume
+ yield
+ ensure
+ RubyProf.pause
+ end
+
+ protected
+ # overridden by each implementation
+ def with_gc_stats
+ yield
+ end
+ end
+
+ class ProcessTime < Time
+ Mode = RubyProf::PROCESS_TIME if RubyProf.const_defined?(:PROCESS_TIME)
+
+ def measure
+ RubyProf.measure_process_time
+ end
+ end
+
+ class WallTime < Time
+ Mode = RubyProf::WALL_TIME if RubyProf.const_defined?(: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 unless RubyProf.cpu_frequency > 0
+ super
+ end
+
+ def measure
+ RubyProf.measure_cpu_time
+ end
+ end
+
+ class Memory < DigitalInformationUnit
+ Mode = RubyProf::MEMORY if RubyProf.const_defined?(:MEMORY)
+ end
+
+ class Objects < Amount
+ Mode = RubyProf::ALLOCATIONS if RubyProf.const_defined?(:ALLOCATIONS)
+ end
+
+ class GcRuns < Amount
+ Mode = RubyProf::GC_RUNS if RubyProf.const_defined?(:GC_RUNS)
+ end
+
+ class GcTime < Time
+ Mode = RubyProf::GC_TIME if RubyProf.const_defined?(:GC_TIME)
+ end
+ end
+ end
+ end
+end
+
+if RUBY_VERSION.between?('1.9.2', '2.0')
+ require 'active_support/testing/performance/ruby/yarv'
+elsif RUBY_VERSION.between?('1.8.6', '1.9')
+ require 'active_support/testing/performance/ruby/mri'
+else
+ $stderr.puts 'Update your ruby interpreter to be able to run benchmarks.'
+ exit
+end
diff --git a/activesupport/lib/active_support/testing/performance/ruby/mri.rb b/activesupport/lib/active_support/testing/performance/ruby/mri.rb
new file mode 100644
index 0000000000..86e650050b
--- /dev/null
+++ b/activesupport/lib/active_support/testing/performance/ruby/mri.rb
@@ -0,0 +1,59 @@
+module ActiveSupport
+ module Testing
+ module Performance
+ module Metrics
+ class Base
+ protected
+ # Ruby 1.8 + ruby-prof wrapper (enable/disable stats for Benchmarker)
+ if GC.respond_to?(:enable_stats)
+ def with_gc_stats
+ GC.enable_stats
+ GC.start
+ yield
+ ensure
+ GC.disable_stats
+ end
+ end
+ end
+
+ class Memory < DigitalInformationUnit
+ # Ruby 1.8 + ruby-prof wrapper
+ if RubyProf.respond_to?(:measure_memory)
+ def measure
+ RubyProf.measure_memory
+ end
+ end
+ end
+
+ class Objects < Amount
+ # Ruby 1.8 + ruby-prof wrapper
+ if RubyProf.respond_to?(:measure_allocations)
+ def measure
+ RubyProf.measure_allocations
+ end
+ end
+ end
+
+ class GcRuns < Amount
+ # Ruby 1.8 + ruby-prof wrapper
+ if RubyProf.respond_to?(:measure_gc_runs)
+ def measure
+ RubyProf.measure_gc_runs
+ end
+ end
+ end
+
+ class GcTime < Time
+ # Ruby 1.8 + ruby-prof wrapper
+ if RubyProf.respond_to?(:measure_gc_time)
+ def measure
+ RubyProf.measure_gc_time / 1000.0 / 1000.0
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+
diff --git a/activesupport/lib/active_support/testing/performance/ruby/yarv.rb b/activesupport/lib/active_support/testing/performance/ruby/yarv.rb
new file mode 100644
index 0000000000..62095a8fe4
--- /dev/null
+++ b/activesupport/lib/active_support/testing/performance/ruby/yarv.rb
@@ -0,0 +1,57 @@
+module ActiveSupport
+ module Testing
+ module Performance
+ module Metrics
+ class Base
+ protected
+ # Ruby 1.9 with GC::Profiler
+ if defined?(GC::Profiler)
+ def with_gc_stats
+ GC::Profiler.enable
+ GC.start
+ yield
+ ensure
+ GC::Profiler.disable
+ end
+ end
+ end
+
+ class Memory < DigitalInformationUnit
+ # Ruby 1.9 + GCdata patch
+ if GC.respond_to?(:malloc_allocated_size)
+ def measure
+ GC.malloc_allocated_size
+ end
+ end
+ end
+
+ class Objects < Amount
+ # Ruby 1.9 + GCdata patch
+ if GC.respond_to?(:malloc_allocations)
+ def measure
+ GC.malloc_allocations
+ end
+ end
+ end
+
+ class GcRuns < Amount
+ # Ruby 1.9
+ if GC.respond_to?(:count)
+ def measure
+ GC.count
+ end
+ end
+ end
+
+ class GcTime < Time
+ # Ruby 1.9 with GC::Profiler
+ if defined?(GC::Profiler) && GC::Profiler.respond_to?(:total_time)
+ def measure
+ GC::Profiler.total_time
+ end
+ end
+ end
+ end
+ end
+ end
+end