2 gem
'ruby-prof', '>= 0.6.1'
6 require 'rails/version'
12 if benchmark
= ARGV.include?('--benchmark') # HAX for rake test
15 :metrics => [:wall_time, :memory, :objects, :gc_runs, :gc_time],
16 :output => 'tmp/performance' }
18 { :benchmark => false,
21 :metrics => [:process_time, :memory, :objects],
22 :formats => [:flat, :graph_html, :call_tree],
23 :output => 'tmp/performance' }
26 def self.included(base
)
27 base
.superclass_delegating_accessor
:profile_options
28 base
.profile_options
= DEFAULTS
32 "#{self.class.name}##{method_name}"
36 return if method_name
=~
/^default_test$/
38 yield(self.class::STARTED, name
)
42 if profile_options
&& metrics
= profile_options
[:metrics]
43 metrics
.each
do |metric_name
|
44 if klass
= Metrics
[metric_name
.to_sym
]
45 run_profile(klass
.new
)
51 yield(self.class::FINISHED, name
)
54 def run_test(metric
, mode
)
57 metric
.send(mode
) { __send__
@method_name }
58 rescue ::Test::Unit::AssertionFailedError => e
59 add_failure(e
.message
, e
.backtrace
)
60 rescue StandardError
, ScriptError
65 run_callbacks
:teardown, :enumerator => :reverse_each
66 rescue ::Test::Unit::AssertionFailedError => e
67 add_failure(e
.message
, e
.backtrace
)
68 rescue StandardError
, ScriptError
77 time
= Metrics
::Time.new
78 run_test(time
, :benchmark)
79 puts
"%s (%s warmup)" % [full_test_name
, time
.format(time
.total
)]
84 def run_profile(metric
)
85 klass
= profile_options
[:benchmark] ? Benchmarker
: Profiler
86 performer
= klass
.new(self, metric
)
94 delegate
:run_test, :profile_options, :full_test_name, :to => :@harness
96 def initialize(harness
, metric
)
97 @harness, @metric = harness
, metric
101 rate
= @total / profile_options
[:runs]
102 '%20s: %s' % [@metric.name
, @metric.format(rate
)]
107 "#{profile_options[:output]}/#{full_test_name}_#{@metric.name}"
111 class Benchmarker
< Performer
113 profile_options
[:runs].to_i
.times
{ run_test(@metric, :benchmark) }
114 @total = @metric.total
118 avg
= @metric.total
/ profile_options
[:runs].to_i
119 now
= Time
.now
.utc
.xmlschema
120 with_output_file
do |file
|
121 file
.puts
"#{avg},#{now},#{environment}"
127 app
= "#{$1}.#{$2}" if File
.directory
?('.git') && `git branch -v` =~
/^\* (\S+)\s+(\S+)/
129 rails
= Rails
::VERSION::STRING
130 if File
.directory
?('vendor/rails/.git')
131 Dir
.chdir('vendor/rails') do
132 rails
+= ".#{$1}.#{$2}" if `git branch -v` =~
/^\* (\S+)\s+(\S+)/
136 ruby
= defined?(RUBY_ENGINE
) ? RUBY_ENGINE
: 'ruby'
137 ruby
+= "-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}"
139 @env = [app
, rails
, ruby
, RUBY_PLATFORM] * ','
146 HEADER
= 'measurement,created_at,app,rails,ruby,platform'
149 fname
= output_filename
151 if new
= !File
.exist
?(fname
)
152 FileUtils
.mkdir_p(File
.dirname(fname
))
155 File
.open(fname
, 'ab') do |file
|
156 file
.puts(HEADER
) if new
166 class Profiler
< Performer
167 def initialize(*args
)
169 @supported = @metric.measure_mode
rescue false
173 return unless @supported
175 RubyProf
.measure_mode
= @metric.measure_mode
178 profile_options
[:runs].to_i
.times
{ run_test(@metric, :profile) }
179 @data = RubyProf
.stop
180 @total = @data.threads
.values
.sum(0) { |method_infos
| method_infos
.sort
.last
.total_time
}
187 '%20s: unsupported' % @metric.name
192 return unless @supported
194 klasses
= profile_options
[:formats].map
{ |f
| RubyProf
.const_get("#{f.to_s.camelize}Printer") }.compact
196 klasses
.each
do |klass
|
197 fname
= output_filename(klass
)
198 FileUtils
.mkdir_p(File
.dirname(fname
))
199 File
.open(fname
, 'wb') do |file
|
200 klass
.new(@data).print(file
, profile_options
.slice(:min_percent))
206 def output_filename(printer_class
)
208 case printer_class
.name
.demodulize
209 when 'FlatPrinter'; 'flat.txt'
210 when 'GraphPrinter'; 'graph.txt'
211 when 'GraphHtmlPrinter'; 'graph.html'
212 when 'CallTreePrinter'; 'tree.txt'
213 else printer_class
.name
.sub(/Printer$/, '').underscore
216 "#{super()}_#{suffix}"
222 const_get(name
.to_s
.camelize
)
235 @name ||= self.class.name
.demodulize
.underscore
250 @total += (measure
- before
)
262 if GC
.respond_to
?(:enable_stats)
269 elsif defined?(GC
::Profiler)
291 def format(measurement
)
293 '%d ms' % (measurement
* 1000)
295 '%.2f sec' % measurement
300 class ProcessTime
< Time
301 Mode
= RubyProf
::PROCESS_TIME
304 RubyProf
.measure_process_time
308 class WallTime
< Time
309 Mode
= RubyProf
::WALL_TIME
312 RubyProf
.measure_wall_time
317 Mode
= RubyProf
::CPU_TIME if RubyProf
.const_defined
?(:CPU_TIME)
319 def initialize(*args
)
320 # FIXME: yeah my CPU is 2.33 GHz
321 RubyProf
.cpu_frequency
= 2.33e9
326 RubyProf
.measure_cpu_time
331 Mode
= RubyProf
::MEMORY if RubyProf
.const_defined
?(:MEMORY)
334 if RubyProf
.respond_to
?(:measure_memory)
336 RubyProf
.measure_memory
/ 1024.0
339 # Ruby 1.8 + railsbench patch
340 elsif GC
.respond_to
?(:allocated_size)
342 GC
.allocated_size
/ 1024.0
345 # Ruby 1.8 + lloyd patch
346 elsif GC
.respond_to
?(:heap_info)
348 GC
.heap_info
['heap_current_memory'] / 1024.0
351 # Ruby 1.9 with total_malloc_allocated_size patch
352 elsif GC
.respond_to
?(:malloc_total_allocated_size)
354 GC
.total_malloc_allocated_size
/ 1024.0
358 elsif GC
.respond_to
?(:malloc_allocated_size)
360 GC
.malloc_allocated_size
/ 1024.0
363 # Ruby 1.9 + GC profiler patch
364 elsif defined?(GC
::Profiler)
368 kb
= GC
::Profiler.data.last
[:HEAP_USE_SIZE] / 1024.0
374 def format(measurement
)
375 '%.2f KB' % measurement
380 Mode
= RubyProf
::ALLOCATIONS if RubyProf
.const_defined
?(:ALLOCATIONS)
382 if RubyProf
.respond_to
?(:measure_allocations)
384 RubyProf
.measure_allocations
387 # Ruby 1.8 + railsbench patch
388 elsif ObjectSpace
.respond_to
?(:allocated_objects)
390 ObjectSpace
.allocated_objects
393 # Ruby 1.9 + GC profiler patch
394 elsif defined?(GC
::Profiler)
398 last
= GC
::Profiler.data.last
399 count
= last
[:HEAP_LIVE_OBJECTS] + last
[:HEAP_FREE_OBJECTS]
405 def format(measurement
)
406 measurement
.to_i
.to_s
411 Mode
= RubyProf
::GC_RUNS if RubyProf
.const_defined
?(:GC_RUNS)
413 if RubyProf
.respond_to
?(:measure_gc_runs)
415 RubyProf
.measure_gc_runs
417 elsif GC
.respond_to
?(:collections)
421 elsif GC
.respond_to
?(:heap_info)
423 GC
.heap_info
['num_gc_passes']
427 def format(measurement
)
428 measurement
.to_i
.to_s
433 Mode
= RubyProf
::GC_TIME if RubyProf
.const_defined
?(:GC_TIME)
435 if RubyProf
.respond_to
?(:measure_gc_time)
437 RubyProf
.measure_gc_time
439 elsif GC
.respond_to
?(:time)
445 def format(measurement
)
446 '%d ms' % (measurement
/ 1000)