├── test ├── do_nothing.rb ├── current_failures_windows ├── prime_test.rb ├── exceptions_test.rb ├── exec_test.rb ├── enumerable_test.rb ├── test_helper.rb ├── no_method_class_test.rb ├── ruby-prof-bin ├── duplicate_names_test.rb ├── singleton_test.rb ├── test_suite.rb ├── module_test.rb ├── exclude_threads_test.rb ├── prime.rb ├── line_number_test.rb ├── method_elimination_test.rb ├── stack_printer_test.rb ├── multi_printer_test.rb ├── start_stop_test.rb ├── measurement_test.rb ├── aggregate_test.rb ├── stack_test.rb ├── unique_call_path_test.rb ├── thread_test.rb ├── basic_test.rb └── printers_test.rb ├── ext └── ruby_prof │ ├── vc │ ├── .gitignore │ ├── ruby_prof.def │ ├── ruby_prof.sln │ ├── ruby_prof.vcproj │ └── ruby_prof.vcxproj │ ├── version.h │ ├── extconf.rb │ ├── measure_wall_time.h │ ├── measure_gc_time.h │ ├── measure_process_time.h │ ├── measure_gc_runs.h │ ├── measure_allocations.h │ ├── measure_memory.h │ ├── measure_cpu_time.h │ └── ruby_prof.h ├── .gitignore ├── examples ├── empty.png ├── graph.png ├── minus.png ├── plus.png ├── multi.flat.txt ├── flat.txt ├── graph.dot └── multi.grind.dat ├── lib ├── ruby-prof │ ├── empty.png │ ├── minus.png │ ├── plus.png │ ├── symbol_to_proc.rb │ ├── rack.rb │ ├── aggregate_call_info.rb │ ├── abstract_printer.rb │ ├── multi_printer.rb │ ├── result.rb │ ├── flat_printer_with_line_numbers.rb │ ├── flat_printer.rb │ ├── graph_yaml_printer.rb │ ├── call_tree_printer.rb │ ├── call_info.rb │ ├── method_info.rb │ ├── test.rb │ ├── task.rb │ ├── dot_printer.rb │ └── graph_printer.rb ├── unprof.rb └── ruby-prof.rb ├── todo ├── rails ├── example │ └── example_test.rb ├── profile_test_helper.rb └── environment │ └── profile.rb ├── benchmarks └── benchmark.rb ├── LICENSE ├── ruby-prof.gemspec ├── Rakefile ├── patches ├── 1.9.3 │ ├── p0 │ │ └── gcdata.patch │ ├── p125 │ │ └── gcdata.patch │ └── preview1 │ │ └── gcdata.patch └── 1.9.2 │ ├── p180 │ └── gcdata.patch │ └── p290 │ └── gcdata.patch ├── bin └── ruby-prof └── CHANGES /test/do_nothing.rb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ext/ruby_prof/vc/.gitignore: -------------------------------------------------------------------------------- 1 | Debug/ 2 | ipch/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | doc 2 | ext/ruby_prof/Makefile 3 | heap.dump 4 | nbproject/ 5 | pkg/ -------------------------------------------------------------------------------- /examples/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wycats/ruby-prof/HEAD/examples/empty.png -------------------------------------------------------------------------------- /examples/graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wycats/ruby-prof/HEAD/examples/graph.png -------------------------------------------------------------------------------- /examples/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wycats/ruby-prof/HEAD/examples/minus.png -------------------------------------------------------------------------------- /examples/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wycats/ruby-prof/HEAD/examples/plus.png -------------------------------------------------------------------------------- /ext/ruby_prof/vc/ruby_prof.def: -------------------------------------------------------------------------------- 1 | LIBRARY ruby_prof 2 | EXPORTS 3 | Init_ruby_prof -------------------------------------------------------------------------------- /lib/ruby-prof/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wycats/ruby-prof/HEAD/lib/ruby-prof/empty.png -------------------------------------------------------------------------------- /lib/ruby-prof/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wycats/ruby-prof/HEAD/lib/ruby-prof/minus.png -------------------------------------------------------------------------------- /lib/ruby-prof/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wycats/ruby-prof/HEAD/lib/ruby-prof/plus.png -------------------------------------------------------------------------------- /todo: -------------------------------------------------------------------------------- 1 | take out the 1.9.2 checks and see if it then passes all unit tests 2 | email mark moseley "but can't you just use xxx?" 3 | see if ruby-debug19 installs on 1.9.2 -------------------------------------------------------------------------------- /ext/ruby_prof/version.h: -------------------------------------------------------------------------------- 1 | #define RUBY_PROF_VERSION "0.11.0" // for easy parsing from rake files 2 | #define RUBY_PROF_VERSION_MAJ 0 3 | #define RUBY_PROF_VERSION_MIN 11 4 | #define RUBY_PROF_VERSION_MIC 0 5 | -------------------------------------------------------------------------------- /lib/ruby-prof/symbol_to_proc.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | unless (:a.respond_to?(:to_proc)) 4 | class Symbol 5 | def to_proc 6 | proc {|stuff| stuff.send(self)} 7 | end 8 | end 9 | end 10 | 11 | -------------------------------------------------------------------------------- /lib/unprof.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require "ruby-prof" 4 | 5 | at_exit { 6 | result = RubyProf.stop 7 | printer = RubyProf::FlatPrinter.new(result) 8 | printer.print(STDOUT) 9 | } 10 | RubyProf.start 11 | -------------------------------------------------------------------------------- /rails/example/example_test.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '../profile_test_helper' 2 | 3 | class ExampleTest < Test::Unit::TestCase 4 | include RubyProf::Test 5 | 6 | def test_stuff 7 | puts "Test method" 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/current_failures_windows: -------------------------------------------------------------------------------- 1 | 1.8 passes, 1.9 however... 2 | 3 | 1) Failure: 4 | test_flat_string_with_numbers(PrintersTest) [E:/dev/ruby/ruby-prof/test/printers_test.rb:68]: 5 | <2> expected but was 6 | <29>. 7 | 8 | which is expected until core backports a recent fix. -------------------------------------------------------------------------------- /test/prime_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | require './test_helper' 5 | 6 | # -- Tests ---- 7 | class PrimeTest< Test::Unit::TestCase 8 | def test_consistency 9 | result = RubyProf.profile do 10 | run_primes 11 | end 12 | end 13 | end -------------------------------------------------------------------------------- /test/exceptions_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | require './test_helper' 5 | 6 | class ExceptionsTest < Test::Unit::TestCase 7 | def test_profile 8 | result = begin 9 | RubyProf.profile do 10 | raise(RuntimeError, 'Test error') 11 | end 12 | rescue => e 13 | end 14 | assert_not_nil(result) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/exec_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | require './test_helper' 5 | 6 | # -- Test for bug when it loads with no frames 7 | 8 | class EnumerableTest < Test::Unit::TestCase 9 | def test_being_able_to_run_its_binary 10 | Dir.chdir(File.dirname(__FILE__)) do 11 | assert system(OS.ruby_bin + " ruby-prof-bin do_nothing.rb") 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /benchmarks/benchmark.rb: -------------------------------------------------------------------------------- 1 | require 'ruby-prof' 2 | require 'benchmark' 3 | 4 | def go 5 | end 6 | 7 | puts Benchmark.realtime { 8 | RubyProf.profile do 9 | 100000.times { go } 10 | end 11 | } 12 | 13 | for n in [5, 100] do 14 | n.times { Thread.new { sleep }} 15 | puts Benchmark.realtime { 16 | RubyProf.profile do 17 | 100000.times { go } 18 | end 19 | } 20 | end 21 | -------------------------------------------------------------------------------- /test/enumerable_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | require './test_helper' 5 | 6 | # -- Test for bug 7 | # http://github.com/rdp/ruby-prof/issues#issue/12 8 | 9 | class EnumerableTest < Test::Unit::TestCase 10 | def test_enumerable 11 | result = RubyProf.profile do 12 | 3.times { [1,2,3].any? {|n| n} } 13 | end 14 | assert result.threads.to_a.first[1].length == 4 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | # To make testing/debugging easier, test within this source tree versus an installed gem 4 | 5 | dir = File.dirname(__FILE__) 6 | root = File.expand_path(File.join(dir, '..')) 7 | lib = File.expand_path(File.join(root, 'lib')) 8 | ext = File.expand_path(File.join(root, 'ext', 'ruby_prof')) 9 | 10 | $LOAD_PATH << lib 11 | $LOAD_PATH << ext 12 | 13 | require 'ruby-prof' 14 | require 'test/unit' 15 | -------------------------------------------------------------------------------- /test/no_method_class_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | require './test_helper' 5 | 6 | # Make sure this works with no class or method 7 | result = RubyProf.profile do 8 | sleep 1 9 | end 10 | 11 | methods = result.threads.values.first 12 | global_method = methods.sort_by {|method| method.full_name}.first 13 | if global_method.full_name != 'Global#[No method]' 14 | raise(RuntimeError, "Wrong method name. Expected: Global#[No method]. Actual: #{global_method.full_name}") 15 | end 16 | -------------------------------------------------------------------------------- /test/ruby-prof-bin: -------------------------------------------------------------------------------- 1 | #!E:/installs/ruby191p376/bin/ruby.exe 2 | # 3 | # This file was generated by RubyGems. 4 | # 5 | # The application 'ruby-prof' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'rubygems' 10 | 11 | version = ">= 0" 12 | 13 | if ARGV.first =~ /^_(.*)_$/ and Gem::Version.correct? $1 then 14 | version = $1 15 | ARGV.shift 16 | end 17 | 18 | #gem 'ruby-prof', version 19 | $: << File.dirname(__FILE__) + '/../lib' 20 | load File.dirname(__FILE__) + '/../bin/ruby-prof' 21 | -------------------------------------------------------------------------------- /rails/profile_test_helper.rb: -------------------------------------------------------------------------------- 1 | # Load profile environment 2 | env = ENV["RAILS_ENV"] = "profile" 3 | require File.expand_path(File.dirname(__FILE__) + "/../config/environment") 4 | 5 | # Load Rails testing infrastructure 6 | require 'test_help' 7 | 8 | # Now we can load test_helper since we've already loaded the 9 | # profile RAILS environment. 10 | require File.expand_path(File.join(RAILS_ROOT, 'test', 'test_helper')) 11 | 12 | # Reset the current environment back to profile 13 | # since test_helper reset it to test 14 | ENV["RAILS_ENV"] = env 15 | 16 | # Now load ruby-prof and away we go 17 | require 'ruby-prof' 18 | 19 | # Setup output directory to Rails tmp directory 20 | RubyProf::Test::PROFILE_OPTIONS[:output_dir] = 21 | File.expand_path(File.join(RAILS_ROOT, 'tmp', 'profile')) 22 | -------------------------------------------------------------------------------- /test/duplicate_names_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | require './test_helper' 5 | 6 | class DuplicateNames < Test::Unit::TestCase 7 | def test_names 8 | result = RubyProf::profile do 9 | str = %{module Foo; class Bar; def foo; end end end} 10 | 11 | eval str 12 | Foo::Bar.new.foo 13 | DuplicateNames.class_eval {remove_const :Foo} 14 | 15 | eval str 16 | Foo::Bar.new.foo 17 | DuplicateNames.class_eval {remove_const :Foo} 18 | 19 | eval str 20 | Foo::Bar.new.foo 21 | end 22 | 23 | # There should be 3 foo methods 24 | methods = result.threads.values.first.sort.reverse 25 | 26 | methods = methods.select do |method| 27 | method.full_name == 'DuplicateNames::Foo::Bar#foo' 28 | end 29 | 30 | assert_equal(3, methods.length) 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /test/singleton_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | require './test_helper' 5 | require 'timeout' 6 | 7 | # -- Test for bug [#5657] 8 | # http://rubyforge.org/tracker/index.php?func=detail&aid=5657&group_id=1814&atid=7060 9 | 10 | 11 | class A 12 | attr_accessor :as 13 | def initialize 14 | @as = [] 15 | class << @as 16 | def <<(an_a) 17 | super 18 | end 19 | end 20 | end 21 | 22 | def <<(an_a) 23 | @as << an_a 24 | end 25 | end 26 | 27 | class SingletonTest < Test::Unit::TestCase 28 | def test_singleton 29 | result = RubyProf.profile do 30 | a = A.new 31 | a << :first_thing 32 | assert_equal(1, a.as.size) 33 | end 34 | printer = RubyProf::FlatPrinter.new(result) 35 | output = ENV['SHOW_RUBY_PROF_PRINTER_OUTPUT'] == "1" ? STDOUT : '' 36 | printer.print(output) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/ruby-prof/rack.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module Rack 4 | class RubyProf 5 | def initialize(app) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | ::RubyProf.start 11 | result = @app.call(env) 12 | data = ::RubyProf.stop 13 | 14 | print(data) 15 | result 16 | end 17 | 18 | def print(data) 19 | require 'tmpdir' # late require so we load on demand only 20 | printers = {::RubyProf::FlatPrinter => ::File.join(Dir.tmpdir, 'profile.txt'), 21 | ::RubyProf::GraphHtmlPrinter => ::File.join(Dir.tmpdir, 'profile.html')} 22 | 23 | printers.each do |printer_klass, file_name| 24 | printer = printer_klass.new(data) 25 | ::File.open(file_name, 'wb') do |file| 26 | printer.print(file, :min_percent => 0.00000001 ) 27 | end 28 | end 29 | end 30 | end 31 | end -------------------------------------------------------------------------------- /test/test_suite.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | # Change to current directory so relative 4 | # requires work. 5 | dir = File.dirname(__FILE__) 6 | Dir.chdir(dir) 7 | 8 | require './test_helper' 9 | 10 | require './aggregate_test' 11 | require './basic_test' 12 | require './duplicate_names_test' 13 | require './exceptions_test' 14 | require './line_number_test' 15 | require './measurement_test' 16 | require './module_test' 17 | require './no_method_class_test' 18 | require './prime_test' 19 | require './printers_test' 20 | require './recursive_test' 21 | require './singleton_test' 22 | require './stack_test' 23 | require './start_stop_test' 24 | require './thread_test' 25 | require './unique_call_path_test' 26 | require './stack_printer_test' 27 | require './multi_printer_test' 28 | require './method_elimination_test' 29 | 30 | # Can't use this one here cause it breaks 31 | # the rest of the unit tets (Ruby Prof gets 32 | # started twice). 33 | #require './profile_unit_test' 34 | -------------------------------------------------------------------------------- /ext/ruby_prof/vc/ruby_prof.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ruby_prof", "ruby_prof.vcxproj", "{DDB3E992-BF4B-4413-B061-288E40AECAD3}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Win32 = Debug|Win32 9 | Release|Win32 = Release|Win32 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {DDB3E992-BF4B-4413-B061-288E40AECAD3}.Debug|Win32.ActiveCfg = Debug|Win32 13 | {DDB3E992-BF4B-4413-B061-288E40AECAD3}.Debug|Win32.Build.0 = Debug|Win32 14 | {DDB3E992-BF4B-4413-B061-288E40AECAD3}.Release|Win32.ActiveCfg = Release|Win32 15 | {DDB3E992-BF4B-4413-B061-288E40AECAD3}.Release|Win32.Build.0 = Release|Win32 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /rails/environment/profile.rb: -------------------------------------------------------------------------------- 1 | # Settings specified here will take precedence over those in config/environment.rb 2 | # The profile environment should match the same settings 3 | # as the production environment to give a reasonalbe 4 | # approximation of performance. However, it should 5 | # definitely not use the production databse! 6 | 7 | 8 | # Cache classes - otherwise your code 9 | # will run approximately 5 times slower and the 10 | # profiling results will be overwhelmed by Rails 11 | # dependency loading mechanism 12 | config.cache_classes = true 13 | 14 | # Don't check template timestamps - once again this 15 | # is to avoid IO times overwhelming profile results 16 | config.action_view.cache_template_loading = true 17 | 18 | # This is debatable, but turn off action controller 19 | # caching to see how long it really takes to run 20 | # queries and render templates 21 | config.action_controller.perform_caching = false 22 | 23 | # Turn off most logging 24 | config.log_level = :info 25 | -------------------------------------------------------------------------------- /test/module_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | require './test_helper' 5 | 6 | # Need to use wall time for this test due to the sleep calls 7 | RubyProf::measure_mode = RubyProf::WALL_TIME 8 | 9 | module Foo 10 | def Foo::hello 11 | sleep(0.5) 12 | end 13 | end 14 | 15 | module Bar 16 | def Bar::hello 17 | sleep(0.5) 18 | Foo::hello 19 | end 20 | 21 | def hello 22 | sleep(0.5) 23 | Bar::hello 24 | end 25 | end 26 | 27 | include Bar 28 | 29 | class ModuleTest < Test::Unit::TestCase 30 | def test_nested_modules 31 | result = RubyProf.profile do 32 | hello 33 | end 34 | 35 | methods = result.threads.values.first 36 | 37 | # Length should be 5 38 | assert_equal(5, methods.length) 39 | 40 | # these methods should be in there... (hard to tell order though). 41 | for name in ['ModuleTest#test_nested_modules','Bar#hello','Kernel#sleep','#hello','#hello'] 42 | assert methods.map(&:full_name).include?( name ) 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /test/exclude_threads_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | require './test_helper' 5 | 6 | 7 | # -- Tests ---- 8 | class ExcludeThreadsTest < Test::Unit::TestCase 9 | def test_exclude_threads 10 | 11 | def thread1_proc 12 | sleep(0.5) 13 | sleep(2) 14 | end 15 | 16 | def thread2_proc 17 | sleep(0.5) 18 | sleep(2) 19 | end 20 | 21 | thread1 = Thread.new do 22 | thread1_proc 23 | end 24 | 25 | thread2 = Thread.new do 26 | thread2_proc 27 | end 28 | 29 | RubyProf::exclude_threads = [ thread2 ] 30 | 31 | RubyProf.start 32 | 33 | thread1.join 34 | thread2.join 35 | 36 | result = RubyProf.stop 37 | 38 | RubyProf::exclude_threads = nil 39 | 40 | assert_equal(2, result.threads.length) 41 | 42 | output = Array.new 43 | result.threads.each do | thread_id, methods | 44 | methods.each do | m | 45 | if m.full_name.index("ExcludeThreadsTest#thread") == 0 46 | output.push(m.full_name) 47 | end 48 | end 49 | end 50 | 51 | assert_equal(1, output.length) 52 | assert_equal("ExcludeThreadsTest#thread1_proc", output[0]) 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /test/prime.rb: -------------------------------------------------------------------------------- 1 | # A silly little test program that finds prime numbers. It 2 | # is intentionally badly designed to show off the use 3 | # of ruby-prof. 4 | # 5 | # Source from http://people.cs.uchicago.edu/~bomb154/154/maclabs/profilers-lab/ 6 | 7 | def make_random_array(length, maxnum) 8 | result = Array.new(length) 9 | result.each_index do |i| 10 | result[i] = rand(maxnum) 11 | end 12 | 13 | result 14 | end 15 | 16 | def is_prime(x) 17 | y = 2 18 | y.upto(x-1) do |i| 19 | return false if (x % i) == 0 20 | end 21 | true 22 | end 23 | 24 | def find_primes(arr) 25 | result = arr.select do |value| 26 | is_prime(value) 27 | end 28 | result 29 | end 30 | 31 | def find_largest(primes) 32 | largest = primes.first 33 | 34 | # Intentionally use upto for example purposes 35 | # (upto is also called from is_prime) 36 | 0.upto(primes.length-1) do |i| 37 | sleep(0.02) 38 | prime = primes[i] 39 | if prime > largest 40 | largest = prime 41 | end 42 | end 43 | largest 44 | end 45 | 46 | def run_primes(length=10, maxnum=1000) 47 | # Create random numbers 48 | random_array = make_random_array(length, maxnum) 49 | 50 | # Find the primes 51 | primes = find_primes(random_array) 52 | 53 | # Find the largest primes 54 | # largest = find_largest(primes) 55 | end 56 | -------------------------------------------------------------------------------- /examples/multi.flat.txt: -------------------------------------------------------------------------------- 1 | Thread ID: 2148387240 2 | Total: 1.412370 3 | 4 | %self total self wait child calls name 5 | 71.15 1.37 1.00 0.00 0.37 20000 Integer#upto 6 | 14.16 0.20 0.20 0.00 0.00 1544726 Fixnum#% 7 | 11.93 0.17 0.17 0.00 0.00 1544726 Fixnum#== 8 | 0.98 1.39 0.01 0.00 1.38 20000 Object#is_prime 9 | 0.62 0.01 0.01 0.00 0.01 2 Array#each_index 10 | 0.58 1.40 0.01 0.00 1.39 2 Array#select 11 | 0.23 0.00 0.00 0.00 0.00 20000 Kernel#rand 12 | 0.19 0.00 0.00 0.00 0.00 20000 Array#[]= 13 | 0.17 0.00 0.00 0.00 0.00 20000 Fixnum#- 14 | 0.01 0.00 0.00 0.00 0.00 2 Array#initialize 15 | 0.00 0.01 0.00 0.00 0.01 2 Object#make_random_array 16 | 0.00 1.41 0.00 0.00 1.41 2 Object#run_primes 17 | 0.00 0.00 0.00 0.00 0.00 2 Class#new 18 | 0.00 1.41 0.00 0.00 1.41 1 PrintersTest#setup 19 | 0.00 0.71 0.00 0.00 0.71 1 PrintersTest#go 20 | 0.00 1.40 0.00 0.00 1.40 2 Object#find_primes 21 | 0.00 0.00 0.00 0.00 0.00 2 #allocate 22 | 23 | 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2005 Shugo Maeda 2 | All rights reserved. 3 | * 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * 13 | THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 17 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 21 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 23 | SUCH DAMAGE. -------------------------------------------------------------------------------- /ext/ruby_prof/extconf.rb: -------------------------------------------------------------------------------- 1 | require "mkmf" 2 | 3 | if RUBY_VERSION >= "1.8" 4 | if RUBY_RELEASE_DATE < "2005-03-22" 5 | STDERR.print("Ruby version is too old\n") 6 | exit(1) 7 | end 8 | else 9 | STDERR.print("Ruby version is too old\n") 10 | exit(1) 11 | end 12 | 13 | have_header("sys/times.h") 14 | 15 | # Stefan Kaes / Alexander Dymo GC patch 16 | have_func("rb_os_allocated_objects") 17 | have_func("rb_gc_allocated_size") 18 | have_func("rb_gc_collections") 19 | have_func("rb_gc_time") 20 | 21 | # 1.9.3 superclass 22 | have_func("rb_class_superclass") 23 | 24 | # Lloyd Hilaiel's heap info patch 25 | have_func("rb_heap_total_mem") 26 | have_func("rb_gc_heap_info") 27 | 28 | # Ruby 1.9 unexposed methods 29 | have_func("rb_gc_malloc_allocations") 30 | have_func("rb_gc_malloc_allocated_size") 31 | 32 | def add_define(name, value = nil) 33 | if value 34 | $defs.push("-D#{name}=#{value}") 35 | else 36 | $defs.push("-D#{name}") 37 | end 38 | end 39 | require 'rubygems' 40 | unless Gem.win_platform? || RUBY_PLATFORM =~ /darwin/ 41 | $LDFLAGS += " -lrt" # for clock_gettime 42 | end 43 | add_define("RUBY_VERSION", RUBY_VERSION.gsub('.', '')) 44 | 45 | # for ruby 1.9, determine whether threads inherit trace flags (latest 1.9.2 works correctly) 46 | if RUBY_VERSION > "1.9" 47 | require 'set' 48 | threads = Set.new 49 | set_trace_func lambda { |*args| threads << Thread.current.object_id } 50 | Thread.new{1}.join 51 | set_trace_func nil 52 | add_define("THREADS_INHERIT_EVENT_FLAGS", (threads.size == 2) ? "1" : "0") 53 | end 54 | 55 | create_makefile("ruby_prof") 56 | -------------------------------------------------------------------------------- /lib/ruby-prof/aggregate_call_info.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module RubyProf 4 | class AggregateCallInfo 5 | attr_reader :call_infos 6 | def initialize(call_infos) 7 | if call_infos.length == 0 8 | raise(ArgumentError, "Must specify at least one call info.") 9 | end 10 | @call_infos = call_infos 11 | end 12 | 13 | def target 14 | call_infos.first.target 15 | end 16 | 17 | def parent 18 | call_infos.first.parent 19 | end 20 | 21 | def line 22 | call_infos.first.line 23 | end 24 | 25 | def children 26 | call_infos.inject(Array.new) do |result, call_info| 27 | result.concat(call_info.children) 28 | end 29 | end 30 | 31 | def total_time 32 | aggregate_minimal(:total_time) 33 | end 34 | 35 | def self_time 36 | aggregate(:self_time) 37 | end 38 | 39 | def wait_time 40 | aggregate(:wait_time) 41 | end 42 | 43 | def children_time 44 | aggregate_minimal(:children_time) 45 | end 46 | 47 | def called 48 | aggregate(:called) 49 | end 50 | 51 | def to_s 52 | "#{call_infos.first.full_name}" 53 | end 54 | 55 | private 56 | 57 | def aggregate(method_name) 58 | self.call_infos.inject(0) do |sum, call_info| 59 | sum += call_info.send(method_name) 60 | end 61 | end 62 | 63 | def aggregate_minimal(method_name) 64 | self.call_infos.inject(0) do |sum, call_info| 65 | sum += call_info.send(method_name) if call_info.minimal? 66 | sum 67 | end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/ruby-prof/abstract_printer.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module RubyProf 4 | class AbstractPrinter 5 | def initialize(result) 6 | @result = result 7 | @output = nil 8 | @options = {} 9 | end 10 | 11 | # Specify print options. 12 | # 13 | # options - Hash table 14 | # :min_percent - Number 0 to 100 that specifes the minimum 15 | # %self (the methods self time divided by the 16 | # overall total time) that a method must take 17 | # for it to be printed out in the report. 18 | # Default value is 0. 19 | # 20 | # :print_file - True or false. Specifies if a method's source 21 | # file should be printed. Default value if false. 22 | # 23 | # :sort_method - Specifies method used for sorting method infos. 24 | # Available values are :total_time, :self_time, 25 | # :wait_time, :children_time 26 | # Default value is :total_time 27 | def setup_options(options = {}) 28 | @options = options 29 | end 30 | 31 | def min_percent 32 | get_options_key("min_percent") || 0 33 | end 34 | 35 | def print_file 36 | get_options_key("print_file") || false 37 | end 38 | 39 | def sort_method 40 | @options[:sort_method] || :total_time 41 | end 42 | 43 | def method_name(method) 44 | name = method.full_name 45 | if print_file 46 | name += " (#{method.source_file}:#{method.line}}" 47 | end 48 | name 49 | end 50 | 51 | private 52 | def get_options_key(key) 53 | if @options.is_a?(Hash) and @options.has_key?(key) 54 | @options[key] 55 | else 56 | nil 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /test/line_number_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | require './test_helper' 5 | require 'prime' 6 | 7 | class LineNumbers 8 | def method1 9 | a = 3 10 | end 11 | 12 | def method2 13 | a = 3 14 | method1 15 | end 16 | 17 | def method3 18 | sleep(1) 19 | end 20 | end 21 | 22 | # -- Tests ---- 23 | class LineNumbersTest < Test::Unit::TestCase 24 | def test_function_line_no 25 | numbers = LineNumbers.new 26 | 27 | result = RubyProf.profile do 28 | numbers.method2 29 | end 30 | 31 | methods = result.threads.values.first.sort.reverse 32 | assert_equal(3, methods.length) 33 | 34 | method = methods[0] 35 | assert_equal('LineNumbersTest#test_function_line_no', method.full_name) 36 | assert_equal(28, method.line) 37 | 38 | method = methods[1] 39 | assert_equal('LineNumbers#method2', method.full_name) 40 | assert_equal(12, method.line) 41 | 42 | method = methods[2] 43 | assert_equal('LineNumbers#method1', method.full_name) 44 | assert_equal(8, method.line) 45 | end 46 | 47 | def test_c_function 48 | numbers = LineNumbers.new 49 | 50 | result = RubyProf.profile do 51 | numbers.method3 52 | end 53 | 54 | methods = result.threads.values.first.sort_by {|method| method.full_name} 55 | assert_equal(3, methods.length) 56 | 57 | # Methods: 58 | # LineNumbers#method3 59 | # LineNumbersTest#test_c_function 60 | # Kernel#sleep 61 | 62 | method = methods[0] 63 | assert_equal('Kernel#sleep', method.full_name) 64 | assert_equal(0, method.line) 65 | 66 | method = methods[1] 67 | assert_equal('LineNumbers#method3', method.full_name) 68 | assert_equal(17, method.line) 69 | 70 | method = methods[2] 71 | assert_equal('LineNumbersTest#test_c_function', method.full_name) 72 | assert_equal(51, method.line) 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /test/method_elimination_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | require './test_helper' 5 | require 'tmpdir' 6 | 7 | # Test data 8 | # A 9 | # / \ 10 | # B C 11 | # \ 12 | # B 13 | 14 | class ESTPT 15 | def a 16 | 100.times{b} 17 | 300.times{c} 18 | c;c;c 19 | end 20 | 21 | def b 22 | sleep 0 23 | end 24 | 25 | def c 26 | 5.times{b} 27 | end 28 | end 29 | 30 | class MethodEliminationTest < Test::Unit::TestCase 31 | def setup 32 | # Need to use wall time for this test due to the sleep calls 33 | RubyProf::measure_mode = RubyProf::WALL_TIME 34 | end 35 | 36 | def test_setting_parent 37 | result = RubyProf.profile do 38 | 1000.times { 1+1 } 39 | end 40 | method_infos = result.threads.values.first 41 | assert(m1 = method_infos[0]) 42 | assert(c1 = m1.call_infos.first) 43 | assert_equal(c1, c1.parent = c1) 44 | assert_equal c1, c1.parent 45 | end 46 | 47 | def test_methods_can_be_eliminated 48 | RubyProf.start 49 | 5.times{ESTPT.new.a} 50 | result = RubyProf.stop 51 | # result.dump 52 | eliminated = result.eliminate_methods!([/Integer#times/]) 53 | # puts eliminated.inspect 54 | # result.dump 55 | eliminated.each do |m| 56 | assert_method_has_been_eliminated(result, m) 57 | end 58 | end 59 | 60 | private 61 | def assert_method_has_been_eliminated(result, eliminated_method) 62 | result.threads.each do |thread_id, methods| 63 | methods.each do |method| 64 | method.call_infos.each do |ci| 65 | assert(ci.target != eliminated_method, "broken self") 66 | assert(ci.parent.target != eliminated_method, "broken parent") if ci.parent 67 | ci.children.each do |callee| 68 | assert(callee.target != eliminated_method, "broken kid") 69 | end 70 | end 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/ruby-prof/multi_printer.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module RubyProf 4 | # Helper class to simplify printing profiles of several types from 5 | # one profiling run. Currently prints a flat profile, a callgrind 6 | # profile, a call stack profile and a graph profile. 7 | class MultiPrinter 8 | def initialize(result) 9 | @stack_printer = CallStackPrinter.new(result) 10 | @graph_printer = GraphHtmlPrinter.new(result) 11 | @tree_printer = CallTreePrinter.new(result) 12 | @flat_printer = FlatPrinter.new(result) 13 | end 14 | 15 | # create profile files under options[:path] or the current 16 | # directory. options[:profile] is used as the base name for the 17 | # pofile file, defaults to "profile". 18 | def print(options) 19 | @profile = options.delete(:profile) || "profile" 20 | @directory = options.delete(:path) || File.expand_path(".") 21 | File.open(stack_profile, "w") do |f| 22 | @stack_printer.print(f, options.merge(:graph => "#{@profile}.graph.html")) 23 | end 24 | File.open(graph_profile, "w") do |f| 25 | @graph_printer.print(f, options) 26 | end 27 | File.open(tree_profile, "w") do |f| 28 | @tree_printer.print(f, options) 29 | end 30 | File.open(flat_profile, "w") do |f| 31 | @flat_printer.print(f, options) 32 | end 33 | end 34 | 35 | # the name of the call stack profile file 36 | def stack_profile 37 | "#{@directory}/#{@profile}.stack.html" 38 | end 39 | 40 | # the name of the graph profile file 41 | def graph_profile 42 | "#{@directory}/#{@profile}.graph.html" 43 | end 44 | 45 | # the name of the callgrind profile file 46 | def tree_profile 47 | "#{@directory}/#{@profile}.grind.dat" 48 | end 49 | 50 | # the name of the flat profile file 51 | def flat_profile 52 | "#{@directory}/#{@profile}.flat.txt" 53 | end 54 | 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /test/stack_printer_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | require './test_helper' 5 | require 'tmpdir' 6 | 7 | # Test data 8 | # A 9 | # / \ 10 | # B C 11 | # \ 12 | # B 13 | 14 | class STPT 15 | def a 16 | 100.times{b} 17 | 300.times{c} 18 | c;c;c 19 | end 20 | 21 | def b 22 | sleep 0 23 | end 24 | 25 | def c 26 | 5.times{b} 27 | end 28 | end 29 | 30 | class StackPrinterTest < Test::Unit::TestCase 31 | def setup 32 | # Need to use wall time for this test due to the sleep calls 33 | RubyProf::measure_mode = RubyProf::WALL_TIME 34 | end 35 | 36 | def test_stack_can_be_printed 37 | start_time = Time.now 38 | RubyProf.start 39 | 5.times{STPT.new.a} 40 | result = RubyProf.stop 41 | end_time = Time.now 42 | expected_time = end_time - start_time 43 | 44 | file_contents = nil 45 | assert_nothing_raised { file_contents = print(result) } 46 | assert file_contents =~ /Thread: (\d+) \(100\.00% ~ ([.0-9]+)\)/ 47 | actual_time = $2.to_f 48 | difference = (expected_time-actual_time).abs 49 | assert_in_delta(expected_time, actual_time, 0.01) 50 | end 51 | 52 | def test_method_elimination 53 | RubyProf.start 54 | 5.times{STPT.new.a} 55 | result = RubyProf.stop 56 | assert_nothing_raised { 57 | # result.dump 58 | result.eliminate_methods!([/Integer#times/]) 59 | # $stderr.puts "================================" 60 | # result.dump 61 | print(result) 62 | } 63 | end 64 | 65 | private 66 | def print(result) 67 | test = caller.first =~ /in `(.*)'/ ? $1 : "test" 68 | testfile_name = "#{Dir::tmpdir}/ruby_prof_#{test}.html" 69 | printer = RubyProf::CallStackPrinter.new(result) 70 | File.open(testfile_name, "w") {|f| printer.print(f, :threshold => 0, :min_percent => 0, :title => "ruby_prof #{test}")} 71 | system("open '#{testfile_name}'") if RUBY_PLATFORM =~ /darwin/ && ENV['SHOW_RUBY_PROF_PRINTER_OUTPUT']=="1" 72 | File.open(testfile_name, "r"){|f| f.read} 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /test/multi_printer_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | require './test_helper' 5 | require 'tmpdir' 6 | 7 | # Test data 8 | # A 9 | # / \ 10 | # B C 11 | # \ 12 | # B 13 | 14 | class MSTPT 15 | def a 16 | 100.times{b} 17 | 300.times{c} 18 | c;c;c 19 | end 20 | 21 | def b 22 | sleep 0 23 | end 24 | 25 | def c 26 | 5.times{b} 27 | end 28 | end 29 | 30 | class MultiPrinterTest < Test::Unit::TestCase 31 | def setup 32 | # Need to use wall time for this test due to the sleep calls 33 | RubyProf::measure_mode = RubyProf::WALL_TIME 34 | end 35 | 36 | def test_all_profiles_can_be_created 37 | start_time = Time.now 38 | RubyProf.start 39 | 5.times{MSTPT.new.a} 40 | result = RubyProf.stop 41 | end_time = Time.now 42 | expected_time = end_time - start_time 43 | stack = graph = nil 44 | assert_nothing_raised { stack, graph = print(result) } 45 | re = Regexp.new(' 46 | \s* 47 | \s* 48 | \s* 49 | \s* 50 | \s* 51 | \s* 52 | \s* 53 | \s* 54 | \s* 55 | \s* 56 | \s* 57 | \s*
Thread IDTotal Time
\d+([\.0-9]+)
') 58 | assert graph =~ re 59 | display_time = $1.to_f 60 | difference = (expected_time-display_time).abs 61 | assert_in_delta expected_time, display_time, 0.005 62 | end 63 | 64 | private 65 | def print(result) 66 | test = caller.first =~ /in `(.*)'/ ? $1 : "test" 67 | path = Dir::tmpdir 68 | profile = "ruby_prof_#{test}" 69 | printer = RubyProf::MultiPrinter.new(result) 70 | printer.print(:path => path, :profile => profile, 71 | :threshold => 0, :min_percent => 0, :title => "ruby_prof #{test}") 72 | if RUBY_PLATFORM =~ /darwin/ && ENV['SHOW_RUBY_PROF_PRINTER_OUTPUT']=="1" 73 | system("open '#{printer.stack_profile}'") 74 | end 75 | if GC.respond_to?(:dump_file_and_line_info) 76 | GC.start 77 | GC.dump_file_and_line_info("heap.dump") 78 | end 79 | [File.open(printer.stack_profile){|f|f.read}, File.open(printer.graph_profile){|f|f.read}] 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /ext/ruby_prof/measure_wall_time.h: -------------------------------------------------------------------------------- 1 | /* :nodoc: 2 | * Copyright (C) 2008 Shugo Maeda 3 | * Charlie Savage 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions 8 | * are met: 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 16 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 19 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | * SUCH DAMAGE. */ 26 | 27 | 28 | #define MEASURE_WALL_TIME 1 29 | 30 | static prof_measure_t 31 | measure_wall_time() 32 | { 33 | struct timeval tv; 34 | gettimeofday(&tv, NULL); 35 | return tv.tv_sec * 1000000 + tv.tv_usec; 36 | } 37 | 38 | static double 39 | convert_wall_time(prof_measure_t c) 40 | { 41 | return (double) c / 1000000; 42 | } 43 | 44 | /* Document-method: prof_measure_wall_time 45 | call-seq: 46 | measure_wall_time -> float 47 | 48 | Returns the wall time.*/ 49 | static VALUE 50 | prof_measure_wall_time(VALUE self) 51 | { 52 | return rb_float_new(convert_wall_time(measure_wall_time())); 53 | } 54 | -------------------------------------------------------------------------------- /ext/ruby_prof/measure_gc_time.h: -------------------------------------------------------------------------------- 1 | /* :nodoc: 2 | * Copyright (C) 2008 Shugo Maeda 3 | * Charlie Savage 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions 8 | * are met: 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 16 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 19 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | * SUCH DAMAGE. */ 26 | 27 | #if defined(HAVE_RB_GC_TIME) 28 | #define MEASURE_GC_TIME 6 29 | 30 | static prof_measure_t 31 | measure_gc_time() 32 | { 33 | #if HAVE_LONG_LONG 34 | return NUM2LL(rb_gc_time()); 35 | #else 36 | return NUM2LONG(rb_gc_time()); 37 | #endif 38 | } 39 | 40 | static double 41 | convert_gc_time(prof_measure_t c) 42 | { 43 | return (double) c / 1000000; 44 | } 45 | 46 | /* Document-method: prof_measure_gc_time 47 | call-seq: 48 | gc_time -> Integer 49 | 50 | Returns the time spent doing garbage collections in microseconds.*/ 51 | static VALUE 52 | prof_measure_gc_time(VALUE self) 53 | { 54 | return rb_gc_time(); 55 | } 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /ruby-prof.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | # Read version from header file 4 | version_header = File.read(File.expand_path('../ext/ruby_prof/version.h', __FILE__)) 5 | match = version_header.match(/RUBY_PROF_VERSION\s*["](\d.+)["]/) 6 | raise(RuntimeError, "Could not determine RUBY_PROF_VERSION") if not match 7 | RUBY_PROF_VERSION = match[1] 8 | 9 | Gem::Specification.new do |spec| 10 | spec.name = "ruby-prof" 11 | 12 | spec.homepage = "http://rubyforge.org/projects/ruby-prof/" 13 | spec.summary = "Fast Ruby profiler" 14 | spec.description = <<-EOF 15 | ruby-prof is a fast code profiler for Ruby. It is a C extension and 16 | therefore is many times faster than the standard Ruby profiler. It 17 | supports both flat and graph profiles. For each method, graph profiles 18 | show how long the method ran, which methods called it and which 19 | methods it called. RubyProf generate both text and html and can output 20 | it to standard out or to a file. 21 | EOF 22 | 23 | spec.version = RUBY_PROF_VERSION 24 | 25 | spec.author = "Shugo Maeda, Charlie Savage, Roger Pack, Stefan Kaes" 26 | spec.email = "shugo@ruby-lang.org, cfis@savagexi.com, rogerdpack@gmail.com, skaes@railsexpress.de" 27 | spec.platform = Gem::Platform::RUBY 28 | spec.require_path = "lib" 29 | spec.bindir = "bin" 30 | spec.executables = ["ruby-prof"] 31 | spec.extensions = ["ext/ruby_prof/extconf.rb"] 32 | spec.files = Dir['Rakefile', 33 | 'README.rdoc', 34 | 'LICENSE', 35 | 'CHANGES', 36 | 'bin/*', 37 | 'doc/**/*', 38 | 'examples/*', 39 | 'ext/ruby_prof/*.c', 40 | 'ext/ruby_prof/*.h', 41 | 'ext/ruby_prof/mingw/Rakefile', 42 | 'ext/ruby_prof/mingw/build.rake', 43 | 'ext/vc/*.sln', 44 | 'ext/vc/*.vcproj', 45 | 'lib/**/*', 46 | 'rails/**/*', 47 | 'test/*'] 48 | 49 | spec.test_files = Dir["test/test_*.rb"] 50 | spec.required_ruby_version = '>= 1.8.4' 51 | spec.date = DateTime.now 52 | spec.rubyforge_project = 'ruby-prof' 53 | spec.add_development_dependency 'os' 54 | spec.add_development_dependency 'rake-compiler' 55 | 56 | end 57 | -------------------------------------------------------------------------------- /lib/ruby-prof.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | # Load the C-based binding. 4 | begin 5 | RUBY_VERSION =~ /(\d+.\d+)/ 6 | require "#{$1}/ruby_prof" 7 | rescue LoadError 8 | require "ruby_prof" 9 | end 10 | 11 | 12 | module RubyProf 13 | def self.camelcase(phrase) 14 | ('_' + phrase).gsub(/_([a-z])/){|b| b[1..1].upcase} 15 | end 16 | 17 | lib_dir = File.dirname(__FILE__) + '/ruby-prof/' 18 | 19 | for file in ['abstract_printer', 'aggregate_call_info', 'flat_printer', 'flat_printer_with_line_numbers', 20 | 'graph_printer', 'graph_html_printer', 'call_tree_printer', 'call_stack_printer', 'multi_printer', 'dot_printer'] 21 | autoload camelcase(file), lib_dir + file 22 | end 23 | 24 | # A few need to be loaded manually their classes were already defined by the .so file so autoload won't work for them. 25 | # plus we need them anyway 26 | for name in ['result', 'method_info', 'call_info'] 27 | require lib_dir + name 28 | end 29 | 30 | require File.dirname(__FILE__) + '/ruby-prof/rack' # do we even need to load this every time? 31 | 32 | # we don't require unprof.rb, as well, purposefully 33 | 34 | # Checks if the user specified the clock mode via 35 | # the RUBY_PROF_MEASURE_MODE environment variable 36 | def self.figure_measure_mode 37 | case ENV["RUBY_PROF_MEASURE_MODE"] 38 | when "wall" || "wall_time" 39 | RubyProf.measure_mode = RubyProf::WALL_TIME 40 | when "cpu" || "cpu_time" 41 | if ENV.key?("RUBY_PROF_CPU_FREQUENCY") 42 | RubyProf.cpu_frequency = ENV["RUBY_PROF_CPU_FREQUENCY"].to_f 43 | else 44 | begin 45 | open("/proc/cpuinfo") do |f| 46 | f.each_line do |line| 47 | s = line.slice(/cpu MHz\s*:\s*(.*)/, 1) 48 | if s 49 | RubyProf.cpu_frequency = s.to_f * 1000000 50 | break 51 | end 52 | end 53 | end 54 | rescue Errno::ENOENT 55 | end 56 | end 57 | RubyProf.measure_mode = RubyProf::CPU_TIME 58 | when "allocations" 59 | RubyProf.measure_mode = RubyProf::ALLOCATIONS 60 | when "memory" 61 | RubyProf.measure_mode = RubyProf::MEMORY 62 | else 63 | # the default... 64 | RubyProf.measure_mode = RubyProf::PROCESS_TIME 65 | end 66 | end 67 | end 68 | 69 | RubyProf::figure_measure_mode -------------------------------------------------------------------------------- /ext/ruby_prof/measure_process_time.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008 Shugo Maeda 3 | * Charlie Savage 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions 8 | * are met: 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 16 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 19 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | * SUCH DAMAGE. */ 26 | 27 | #include 28 | 29 | #define MEASURE_PROCESS_TIME 0 30 | 31 | static prof_measure_t 32 | measure_process_time() 33 | { 34 | #if defined(__linux__) 35 | struct timespec time; 36 | clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &time); 37 | return time.tv_sec * 1000000000 + time.tv_nsec ; 38 | #else 39 | return clock(); 40 | #endif 41 | } 42 | 43 | 44 | static double 45 | convert_process_time(prof_measure_t c) 46 | { 47 | #if defined(__linux__) 48 | return (double) c / 1000000000; 49 | #else 50 | return (double) c / CLOCKS_PER_SEC; 51 | #endif 52 | } 53 | 54 | /* Document-method: measure_process_time 55 | call-seq: 56 | measure_process_time -> float 57 | 58 | Returns the process time.*/ 59 | static VALUE 60 | prof_measure_process_time(VALUE self) 61 | { 62 | return rb_float_new(convert_process_time(measure_process_time())); 63 | } 64 | -------------------------------------------------------------------------------- /lib/ruby-prof/result.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'set' 4 | module RubyProf 5 | class Result 6 | # this method gets called internally when profiling is stopped. 7 | # it determines for each call_info whether it is minimal: a 8 | # call_info is minimal in a call tree if the call_info is not a 9 | # descendant of a call_info of the same method 10 | def compute_minimality 11 | threads.each do |threadid, method_infos| 12 | root_methods = method_infos.select{|mi| mi.root?} 13 | root_methods.each do |mi| 14 | mi.call_infos.select{|ci| ci.root?}.each do |call_info_root| 15 | call_info_root.compute_minimality(Set.new) 16 | end 17 | end 18 | end 19 | end 20 | 21 | # eliminate some calls from the graph by merging the information into callers. 22 | # matchers can be a list of strings or regular expressions or the name of a file containing regexps. 23 | def eliminate_methods!(matchers) 24 | matchers = read_regexps_from_file(matchers) if matchers.is_a?(String) 25 | eliminated = [] 26 | threads.each do |thread_id, methods| 27 | matchers.each{ |matcher| eliminated.concat(eliminate_methods(methods, matcher)) } 28 | end 29 | compute_minimality # is this really necessary? 30 | eliminated 31 | end 32 | 33 | def dump 34 | threads.each do |thread_id, methods| 35 | $stderr.puts "Call Info Dump for thread id #{thread_id}" 36 | methods.each do |method_info| 37 | $stderr.puts method_info.dump 38 | end 39 | end 40 | end 41 | 42 | private 43 | 44 | # read regexps from file 45 | def read_regexps_from_file(file_name) 46 | matchers = [] 47 | File.open(matchers).each_line do |l| 48 | next if (l =~ /^(#.*|\s*)$/) # emtpy lines and lines starting with # 49 | matchers << Regexp.new(l.strip) 50 | end 51 | end 52 | 53 | # eliminate methods matching matcher 54 | def eliminate_methods(methods, matcher) 55 | eliminated = [] 56 | i = 0 57 | while i < methods.size 58 | method_info = methods[i] 59 | method_name = method_info.full_name 60 | if matcher === method_name 61 | raise "can't eliminate root method" if method_info.root? 62 | eliminated << methods.delete_at(i) 63 | method_info.eliminate! 64 | else 65 | i += 1 66 | end 67 | end 68 | eliminated 69 | end 70 | 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /ext/ruby_prof/measure_gc_runs.h: -------------------------------------------------------------------------------- 1 | /* :nodoc: 2 | * Copyright (C) 2008 Shugo Maeda 3 | * Charlie Savage 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions 8 | * are met: 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 16 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 19 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | * SUCH DAMAGE. */ 26 | 27 | #if defined(HAVE_RB_GC_COLLECTIONS) 28 | #define MEASURE_GC_RUNS 5 29 | 30 | static prof_measure_t 31 | measure_gc_runs() 32 | { 33 | return NUM2INT(rb_gc_collections()); 34 | } 35 | 36 | static double 37 | convert_gc_runs(prof_measure_t c) 38 | { 39 | return c; 40 | } 41 | 42 | /* Document-method: prof_measure_gc_runs 43 | call-seq: 44 | gc_runs -> Integer 45 | 46 | Returns the total number of garbage collections.*/ 47 | static VALUE 48 | prof_measure_gc_runs(VALUE self) 49 | { 50 | return rb_gc_collections(); 51 | } 52 | 53 | #elif defined(HAVE_RB_GC_HEAP_INFO) 54 | #define MEASURE_GC_RUNS 5 55 | 56 | static prof_measure_t 57 | measure_gc_runs() 58 | { 59 | VALUE h = rb_gc_heap_info(); 60 | return NUM2UINT(rb_hash_aref(h, rb_str_new2("num_gc_passes"))); 61 | } 62 | 63 | static double 64 | convert_gc_runs(prof_measure_t c) 65 | { 66 | return c; 67 | } 68 | 69 | static VALUE 70 | prof_measure_gc_runs(VALUE self) 71 | { 72 | VALUE h = rb_gc_heap_info(); 73 | return rb_hash_aref(h, rb_str_new2("num_gc_passes")); 74 | } 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /lib/ruby-prof/flat_printer_with_line_numbers.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module RubyProf 4 | # Generates flat[link:files/examples/flat_txt.html] profile reports as text. 5 | # To use the flat printer with line numbers: 6 | # 7 | # result = RubyProf.profile do 8 | # [code to profile] 9 | # end 10 | # 11 | # printer = RubyProf::FlatPrinterWithLineNumbers.new(result) 12 | # printer.print(STDOUT, {}) 13 | # 14 | class FlatPrinterWithLineNumbers < FlatPrinter 15 | 16 | def print_methods(thread_id, methods) 17 | # Get total time 18 | toplevel = methods.max 19 | total_time = toplevel.total_time 20 | if total_time == 0 21 | total_time = 0.01 22 | end 23 | 24 | methods = methods.sort_by(&sort_method).reverse 25 | 26 | @output << "Thread ID: %d\n" % thread_id 27 | @output << "Total: %0.6f\n" % total_time 28 | @output << "\n" 29 | @output << " %self total self wait child calls name\n" 30 | sum = 0 31 | methods.each do |method| 32 | self_percent = (method.self_time / total_time) * 100 33 | next if self_percent < min_percent 34 | 35 | sum += method.self_time 36 | #self_time_called = method.called > 0 ? method.self_time/method.called : 0 37 | #total_time_called = method.called > 0? method.total_time/method.called : 0 38 | 39 | @output << "%6.2f %8.2f %8.2f %8.2f %8.2f %8d %s " % [ 40 | method.self_time / total_time * 100, # %self 41 | method.total_time, # total 42 | method.self_time, # self 43 | method.wait_time, # wait 44 | method.children_time, # children 45 | method.called, # calls 46 | method_name(method), # name 47 | ] 48 | if method.source_file != 'ruby_runtime' 49 | @output << " %s:%s" % [File.expand_path(method.source_file), method.line] 50 | end 51 | @output << "\n\tcalled from: " 52 | 53 | # make sure they're unique 54 | method.call_infos.map{|ci| 55 | if ci.parent && ci.parent.target.source_file != 'ruby_runtime' 56 | [method_name(ci.parent.target), File.expand_path(ci.parent.target.source_file), ci.parent.target.line] 57 | else 58 | nil 59 | end 60 | }.compact.uniq.each{|args| 61 | @output << " %s (%s:%s) " % args 62 | } 63 | @output << "\n\n" 64 | end 65 | end 66 | end 67 | end 68 | 69 | -------------------------------------------------------------------------------- /lib/ruby-prof/flat_printer.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module RubyProf 4 | # Generates flat[link:files/examples/flat_txt.html] profile reports as text. 5 | # To use the flat printer: 6 | # 7 | # result = RubyProf.profile do 8 | # [code to profile] 9 | # end 10 | # 11 | # printer = RubyProf::FlatPrinter.new(result) 12 | # printer.print(STDOUT, {}) 13 | # 14 | class FlatPrinter < AbstractPrinter 15 | # Print a flat profile report to the provided output. 16 | # 17 | # output - Any IO object, including STDOUT or a file. 18 | # The default value is STDOUT. 19 | # 20 | # options - Hash of print options. See #setup_options 21 | # for more information. 22 | # 23 | def print(output = STDOUT, options = {}) 24 | @output = output 25 | # Now sort methods by largest self time by default, 26 | # not total time like in other printouts 27 | options[:sort_method] ||= :self_time 28 | setup_options(options) 29 | print_threads 30 | end 31 | 32 | private 33 | 34 | def print_threads 35 | @result.threads.each do |thread_id, methods| 36 | print_methods(thread_id, methods) 37 | @output << "\n" * 2 38 | end 39 | end 40 | 41 | def print_methods(thread_id, methods) 42 | # Get total time 43 | toplevel = methods.max 44 | total_time = toplevel.total_time 45 | if total_time == 0 46 | total_time = 0.01 47 | end 48 | 49 | methods = methods.sort_by(&sort_method).reverse 50 | 51 | @output << "Thread ID: %d\n" % thread_id 52 | @output << "Total: %0.6f\n" % total_time 53 | @output << "\n" 54 | @output << " %self total self wait child calls name\n" 55 | 56 | sum = 0 57 | methods.each do |method| 58 | self_percent = (method.self_time / total_time) * 100 59 | next if self_percent < min_percent 60 | 61 | sum += method.self_time 62 | #self_time_called = method.called > 0 ? method.self_time/method.called : 0 63 | #total_time_called = method.called > 0? method.total_time/method.called : 0 64 | 65 | @output << "%6.2f %8.2f %8.2f %8.2f %8.2f %8d %s\n" % [ 66 | method.self_time / total_time * 100, # %self 67 | method.total_time, # total 68 | method.self_time, # self 69 | method.wait_time, # wait 70 | method.children_time, # children 71 | method.called, # calls 72 | method_name(method) # name 73 | ] 74 | end 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /ext/ruby_prof/measure_allocations.h: -------------------------------------------------------------------------------- 1 | /* :nodoc: 2 | * Copyright (C) 2008 Shugo Maeda 3 | * Charlie Savage 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions 8 | * are met: 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 16 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 19 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | * SUCH DAMAGE. */ 26 | 27 | #include 28 | 29 | #if defined(HAVE_RB_OS_ALLOCATED_OBJECTS) 30 | #define MEASURE_ALLOCATIONS 3 31 | 32 | static prof_measure_t 33 | measure_allocations() 34 | { 35 | return rb_os_allocated_objects(); 36 | } 37 | 38 | static double 39 | convert_allocations(prof_measure_t c) 40 | { 41 | return c; 42 | } 43 | 44 | /* Document-method: prof_measure_allocations 45 | call-seq: 46 | measure_allocations -> int 47 | 48 | Returns the total number of object allocations since Ruby started.*/ 49 | static VALUE 50 | prof_measure_allocations(VALUE self) 51 | { 52 | #if defined(HAVE_LONG_LONG) 53 | return ULL2NUM(rb_os_allocated_objects()); 54 | #else 55 | return ULONG2NUM(rb_os_allocated_objects()); 56 | #endif 57 | } 58 | 59 | #elif defined(HAVE_RB_GC_MALLOC_ALLOCATIONS) 60 | #define MEASURE_ALLOCATIONS 3 61 | 62 | static prof_measure_t 63 | measure_allocations() 64 | { 65 | return rb_gc_malloc_allocations(); 66 | } 67 | 68 | static double 69 | convert_allocations(prof_measure_t c) 70 | { 71 | return c; 72 | } 73 | 74 | static VALUE 75 | prof_measure_allocations(VALUE self) 76 | { 77 | #if defined(HAVE_LONG_LONG) 78 | return ULL2NUM(rb_os_allocated_objects()); 79 | #else 80 | return ULONG2NUM(rb_os_allocated_objects()); 81 | #endif 82 | } 83 | #endif 84 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require "rubygems/package_task" 4 | require "rake/extensiontask" 5 | require "rake/testtask" 6 | require "rdoc/task" 7 | require "date" 8 | 9 | # To release a version of ruby-prof: 10 | # * Update version.h 11 | # * Update CHANGES 12 | # * Update rdocs 13 | # * git commit to commit files 14 | # * rake clobber to remove extra files 15 | # * rake compile to build windows gems 16 | # * rake package to create the gems 17 | # * Tag the release in git (tag 0.10.1) 18 | # * Push to ruybgems.org (gem push pkg/) 19 | 20 | GEM_NAME = 'ruby-prof' 21 | SO_NAME = 'ruby_prof' 22 | 23 | default_spec = Gem::Specification.load("#{GEM_NAME}.gemspec") 24 | 25 | Rake::ExtensionTask.new do |ext| 26 | ext.gem_spec = default_spec 27 | ext.name = SO_NAME 28 | ext.ext_dir = "ext/#{SO_NAME}" 29 | ext.lib_dir = "lib/#{RUBY_VERSION.sub(/\.\d$/, '')}" 30 | ext.cross_compile = true 31 | ext.cross_platform = ['x86-mswin32-60', 'x86-mingw32-60'] 32 | end 33 | 34 | # Rake task to build the default package 35 | Gem::PackageTask.new(default_spec) do |pkg| 36 | pkg.need_tar = true 37 | end 38 | 39 | # Setup Windows Gem 40 | if RUBY_PLATFORM.match(/win32|mingw32/) 41 | # Windows specification 42 | win_spec = default_spec.clone 43 | win_spec.platform = Gem::Platform::CURRENT 44 | win_spec.files += Dir.glob('lib/**/*.so') 45 | win_spec.instance_variable_set(:@cache_file, nil) # Hack to work arond gem issue 46 | 47 | # Unset extensions 48 | win_spec.extensions = nil 49 | 50 | # Rake task to build the windows package 51 | Gem::PackageTask.new(win_spec) do |pkg| 52 | pkg.need_tar = false 53 | end 54 | end 55 | 56 | # --------- RDoc Documentation ------ 57 | desc "Generate rdoc documentation" 58 | RDoc::Task.new("rdoc") do |rdoc| 59 | rdoc.rdoc_dir = 'doc' 60 | rdoc.title = "ruby-prof" 61 | # Show source inline with line numbers 62 | rdoc.options << "--inline-source" << "--line-numbers" 63 | # Make the readme file the start page for the generated html 64 | rdoc.options << '--main' << 'README.rdoc' 65 | rdoc.rdoc_files.include('bin/**/*', 66 | 'doc/*.rdoc', 67 | 'examples/flat.txt', 68 | 'examples/graph.txt', 69 | 'examples/graph.html', 70 | 'lib/**/*.rb', 71 | 'ext/ruby_prof/ruby_prof.c', 72 | 'ext/ruby_prof/version.h', 73 | 'ext/ruby_prof/measure_*.h', 74 | 'README.rdoc', 75 | 'LICENSE') 76 | end 77 | 78 | task :default => :package 79 | 80 | desc 'Run the ruby-prof test suite' 81 | Rake::TestTask.new do |t| 82 | t.libs += %w(lib ext test) 83 | t.test_files = Dir['test/test_suite.rb'] 84 | t.verbose = true 85 | t.warning = true 86 | end -------------------------------------------------------------------------------- /lib/ruby-prof/graph_yaml_printer.rb: -------------------------------------------------------------------------------- 1 | require 'ruby-prof/abstract_printer' 2 | require 'yaml' 3 | 4 | module RubyProf 5 | # Generates call stack profile reports as yaml. 6 | # To use the yaml graph printer: 7 | # 8 | # result = RubyProf.profile do 9 | # [code to profile] 10 | # end 11 | # 12 | # printer = RubyProf::GraphYamlPrinter.new(result) 13 | # printer.print(STDOUT, 0) 14 | # 15 | class GraphYamlPrinter < AbstractPrinter 16 | # Print a yaml graph profile report to the provided output. 17 | # 18 | # output - Any IO oject, including STDOUT or a file. 19 | # The default value is STDOUT. 20 | # 21 | # options - Hash of print options. See #setup_options 22 | # for more information. 23 | # 24 | def print(output = STDOUT, options = {}) 25 | @output = output 26 | setup_options(options) 27 | gather_threads 28 | print_result 29 | end 30 | 31 | private 32 | 33 | def gather_threads 34 | calculate_thread_times 35 | @result_yaml = {"total_time" => @thread_times[:total_time], "threads" => {}} 36 | 37 | @result.threads.each do |thread_id, methods| 38 | @result_yaml["threads"][thread_id] = 39 | {"total_time" => @thread_times[:threads][thread_id], "methods" => {}} 40 | 41 | gather_methods(thread_id, methods) 42 | end 43 | end 44 | 45 | def gather_methods(thread_id, methods) 46 | methods = methods.sort 47 | 48 | toplevel = methods.last 49 | total_time = [toplevel.total_time, 0.01].max 50 | 51 | methods.reverse_each do |method| 52 | total_percentage = (method.total_time/total_time) * 100 53 | next if total_percentage < min_percent 54 | 55 | result = Hash.new 56 | result["calls"] = method.called 57 | result["total_time"] = method.total_time 58 | result["self_time"] = method.self_time 59 | result["children"] = get_children(method) unless method.children.empty? 60 | 61 | @result_yaml["threads"][thread_id]["methods"][method.full_name] = result 62 | end 63 | end 64 | 65 | def calculate_thread_times 66 | # Cache thread times since this is an expensive 67 | # operation with the required sorting 68 | @thread_times = {:total_time => 0.0, :threads => {}} 69 | @result.threads.each do |thread_id, methods| 70 | top = methods.max 71 | thread_time = [top.total_time, 0.01].max 72 | @thread_times[:threads][thread_id] = thread_time 73 | end 74 | @thread_times[:total_time] = [@thread_times[:threads].values.max, 0.01].max 75 | end 76 | 77 | def get_children(method) 78 | method.aggregate_children.sort_by(&:total_time).reverse.map { |c| c.target.full_name } 79 | end 80 | 81 | def print_result 82 | @output << YAML.dump(@result_yaml) 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /ext/ruby_prof/measure_memory.h: -------------------------------------------------------------------------------- 1 | /* :nodoc: 2 | * Copyright (C) 2008 Alexander Dymo 3 | * 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions 8 | * are met: 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 16 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 19 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | * SUCH DAMAGE. */ 26 | 27 | 28 | #if defined(HAVE_RB_GC_ALLOCATED_SIZE) 29 | #define MEASURE_MEMORY 4 30 | #define TOGGLE_GC_STATS 1 31 | 32 | static prof_measure_t 33 | measure_memory() 34 | { 35 | #if defined(HAVE_LONG_LONG) 36 | return NUM2LL(rb_gc_allocated_size()); 37 | #else 38 | return NUM2ULONG(rb_gc_allocated_size()); 39 | #endif 40 | } 41 | 42 | static double 43 | convert_memory(prof_measure_t c) 44 | { 45 | return (double) c / 1024; 46 | } 47 | 48 | /* Document-method: prof_measure_memory 49 | call-seq: 50 | measure_memory -> int 51 | 52 | Returns total allocated memory in bytes.*/ 53 | static VALUE 54 | prof_measure_memory(VALUE self) 55 | { 56 | return rb_gc_allocated_size(); 57 | } 58 | 59 | #elif defined(HAVE_RB_GC_MALLOC_ALLOCATED_SIZE) 60 | #define MEASURE_MEMORY 4 61 | 62 | static prof_measure_t 63 | measure_memory() 64 | { 65 | return rb_gc_malloc_allocated_size(); 66 | } 67 | 68 | static double 69 | convert_memory(prof_measure_t c) 70 | { 71 | return (double) c / 1024; 72 | } 73 | 74 | static VALUE 75 | prof_measure_memory(VALUE self) 76 | { 77 | return UINT2NUM(rb_gc_malloc_allocated_size()); 78 | } 79 | 80 | #elif defined(HAVE_RB_HEAP_TOTAL_MEM) 81 | #define MEASURE_MEMORY 4 82 | 83 | static prof_measure_t 84 | measure_memory() 85 | { 86 | return rb_heap_total_mem(); 87 | } 88 | 89 | static double 90 | convert_memory(prof_measure_t c) 91 | { 92 | return (double) c / 1024; 93 | } 94 | 95 | static VALUE 96 | prof_measure_memory(VALUE self) 97 | { 98 | return ULONG2NUM(rb_heap_total_mem()); 99 | } 100 | 101 | #endif 102 | -------------------------------------------------------------------------------- /lib/ruby-prof/call_tree_printer.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module RubyProf 4 | # Generate profiling information in calltree format 5 | # for use by kcachegrind and similar tools. 6 | 7 | class CallTreePrinter < AbstractPrinter 8 | # Specify print options. 9 | # 10 | # options - Hash table 11 | # :min_percent - Number 0 to 100 that specifes the minimum 12 | # %self (the methods self time divided by the 13 | # overall total time) that a method must take 14 | # for it to be printed out in the report. 15 | # Default value is 0. 16 | # 17 | # :print_file - True or false. Specifies if a method's source 18 | # file should be printed. Default value if false. 19 | # 20 | def print(output = STDOUT, options = {}) 21 | @output = output 22 | setup_options(options) 23 | 24 | # add a header - this information is somewhat arbitrary 25 | @output << "events: " 26 | case RubyProf.measure_mode 27 | when RubyProf::PROCESS_TIME 28 | @value_scale = RubyProf::CLOCKS_PER_SEC; 29 | @output << 'process_time' 30 | when RubyProf::WALL_TIME 31 | @value_scale = 1_000_000 32 | @output << 'wall_time' 33 | when RubyProf.const_defined?(:CPU_TIME) && RubyProf::CPU_TIME 34 | @value_scale = RubyProf.cpu_frequency 35 | @output << 'cpu_time' 36 | when RubyProf.const_defined?(:ALLOCATIONS) && RubyProf::ALLOCATIONS 37 | @value_scale = 1 38 | @output << 'allocations' 39 | when RubyProf.const_defined?(:MEMORY) && RubyProf::MEMORY 40 | @value_scale = 1 41 | @output << 'memory' 42 | when RubyProf.const_defined?(:GC_RUNS) && RubyProf::GC_RUNS 43 | @value_scale = 1 44 | @output << 'gc_runs' 45 | when RubyProf.const_defined?(:GC_TIME) && RubyProf::GC_TIME 46 | @value_scale = 1000000 47 | @output << 'gc_time' 48 | else 49 | raise "Unknown measure mode: #{RubyProf.measure_mode}" 50 | end 51 | @output << "\n\n" 52 | 53 | print_threads 54 | end 55 | 56 | def print_threads 57 | @result.threads.each do |thread_id, methods| 58 | print_methods(thread_id, methods) 59 | end 60 | end 61 | 62 | def convert(value) 63 | (value * @value_scale).round 64 | end 65 | 66 | def file(method) 67 | File.expand_path(method.source_file) 68 | end 69 | 70 | def print_methods(thread_id, methods) 71 | methods.reverse_each do |method| 72 | # Print out the file and method name 73 | @output << "fl=#{file(method)}\n" 74 | @output << "fn=#{method_name(method)}\n" 75 | 76 | # Now print out the function line number and its self time 77 | @output << "#{method.line} #{convert(method.self_time)}\n" 78 | 79 | # Now print out all the children methods 80 | method.children.each do |callee| 81 | @output << "cfl=#{file(callee.target)}\n" 82 | @output << "cfn=#{method_name(callee.target)}\n" 83 | @output << "calls=#{callee.called} #{callee.line}\n" 84 | 85 | # Print out total times here! 86 | @output << "#{callee.line} #{convert(callee.total_time)}\n" 87 | end 88 | @output << "\n" 89 | end 90 | end #end print_methods 91 | end # end class 92 | end # end packages 93 | -------------------------------------------------------------------------------- /lib/ruby-prof/call_info.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module RubyProf 4 | class CallInfo 5 | def depth 6 | result = 0 7 | call_info = self.parent 8 | 9 | while call_info 10 | result += 1 11 | call_info = call_info.parent 12 | end 13 | result 14 | end 15 | 16 | def children_time 17 | children.inject(0) do |sum, call_info| 18 | sum += call_info.total_time 19 | end 20 | end 21 | 22 | def stack 23 | @stack ||= begin 24 | methods = Array.new 25 | call_info = self 26 | 27 | while call_info 28 | methods << call_info.target 29 | call_info = call_info.parent 30 | end 31 | methods.reverse 32 | end 33 | end 34 | 35 | def call_sequence 36 | @call_sequence ||= begin 37 | stack.map {|method| method.full_name}.join('->') 38 | end 39 | end 40 | 41 | def root? 42 | self.parent.nil? 43 | end 44 | 45 | def to_s 46 | "#{call_sequence}" 47 | end 48 | 49 | def minimal? 50 | @minimal 51 | end 52 | 53 | def compute_minimality(parent_methods) 54 | if parent_methods.include?(target) 55 | @minimal = false 56 | else 57 | @minimal = true 58 | parent_methods << target unless children.empty? 59 | end 60 | children.each {|ci| ci.compute_minimality(parent_methods)} 61 | parent_methods.delete(target) if @minimal && !children.empty? 62 | end 63 | 64 | # eliminate call info from the call tree. 65 | # adds self and wait time to parent and attaches called methods to parent. 66 | # merges call trees for methods called from both praent end self. 67 | def eliminate! 68 | # puts "eliminating #{self}" 69 | return unless parent 70 | parent.add_self_time(self) 71 | parent.add_wait_time(self) 72 | children.each do |kid| 73 | if call = parent.find_call(kid) 74 | call.merge_call_tree(kid) 75 | else 76 | parent.children << kid 77 | # $stderr.puts "setting parent of #{kid}\nto #{parent}" 78 | kid.parent = parent 79 | end 80 | end 81 | parent.children.delete(self) 82 | end 83 | 84 | # find a sepcific call in list of children. returns nil if not found. 85 | # note: there can't be more than one child with a given target method. in other words: 86 | # x.children.grep{|y|y.target==m}.size <= 1 for all method infos m and call infos x 87 | def find_call(other) 88 | matching = children.select { |kid| kid.target == other.target } 89 | raise "inconsistent call tree" unless matching.size <= 1 90 | matching.first 91 | end 92 | 93 | # merge two call trees. adds self, wait, and total time of other to self and merges children of other into children of self. 94 | def merge_call_tree(other) 95 | # $stderr.puts "merging #{self}\nand #{other}" 96 | self.called += other.called 97 | add_self_time(other) 98 | add_wait_time(other) 99 | add_total_time(other) 100 | other.children.each do |other_kid| 101 | if kid = find_call(other_kid) 102 | # $stderr.puts "merging kids" 103 | kid.merge_call_tree(other_kid) 104 | else 105 | other_kid.parent = self 106 | children << other_kid 107 | end 108 | end 109 | other.children.clear 110 | other.target.call_infos.delete(other) 111 | end 112 | 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /examples/flat.txt: -------------------------------------------------------------------------------- 1 | = Flat Profiles 2 | 3 | Flat profiles show the total amount of time spent in each method. 4 | As an example, here is the output from running printers_test.rb. 5 | 6 | Thread ID: 21277412 7 | %self cumulative total self children calls self/call total/call name 8 | 46.34 4.06 8.72 4.06 4.66 501 0.01 0.02 Integer#upto 9 | 23.89 6.16 2.09 2.09 0.00 61 0.03 0.03 Kernel.sleep 10 | 15.12 7.48 1.33 1.33 0.00 250862 0.00 0.00 Fixnum#% 11 | 14.13 8.72 1.24 1.24 0.00 250862 0.00 0.00 Fixnum#== 12 | 0.18 8.74 0.02 0.02 0.00 1 0.02 0.02 Array#each_index 13 | 0.17 8.75 6.64 0.01 6.63 500 0.00 0.01 Object#is_prime 14 | 0.17 8.77 6.66 0.01 6.64 1 0.01 6.66 Array#select 15 | 0.00 8.77 0.00 0.00 0.00 501 0.00 0.00 Fixnum#- 16 | 0.00 8.77 0.00 0.00 0.00 1 0.00 0.00 Array#first 17 | 0.00 8.77 0.00 0.00 0.00 1 0.00 0.00 Array#length 18 | 0.00 8.77 0.00 0.00 0.00 1 0.00 0.00 Array#initialize 19 | 0.00 8.77 8.77 0.00 8.77 1 0.00 8.77 Object#run_primes 20 | 0.00 8.77 0.00 0.00 0.00 1 0.00 0.00 Integer#to_int 21 | 0.00 8.77 6.66 0.00 6.66 1 0.00 6.66 Object#find_primes 22 | 0.00 8.77 2.09 0.00 2.09 1 0.00 2.09 Object#find_largest 23 | 0.00 8.77 0.02 0.00 0.02 1 0.00 0.02 Object#make_random_array 24 | 0.00 8.77 0.00 0.00 0.00 1 0.00 0.00 Class#new 25 | 0.00 8.77 0.00 0.00 0.00 500 0.00 0.00 Array#[]= 26 | 0.00 8.77 0.00 0.00 0.00 61 0.00 0.00 Fixnum#> 27 | 0.00 8.77 0.00 0.00 0.00 61 0.00 0.00 Array#[] 28 | 0.00 8.77 8.77 0.00 8.77 1 0.00 8.77 #toplevel 29 | 0.00 8.77 0.00 0.00 0.00 500 0.00 0.00 Kernel.rand 30 | 31 | All values are in seconds. 32 | 33 | The columns are: 34 | 35 | %self - The percentage of time spent in this method, derived from self_time/total_time 36 | cumulative - The sum of the time spent in this method and all the methods listed above it. 37 | total - The time spent in this method and its children. 38 | self - The time spent in this method. 39 | children - The time spent in this method's children. 40 | calls - The number of times this method was called. 41 | self/call - The average time spent per call in this method. 42 | total/call - The average time spent per call in this method and its children. 43 | name - The name of the method. 44 | 45 | Methods are sorted based on %self, therefore the methods that execute the longest are listed 46 | first. 47 | 48 | The interpretation of method names is: 49 | * #toplevel - The root method that calls all other methods 50 | * MyObject#test - An instance method "test" of the class "MyObject" 51 | * #test - The <> characters indicate a singleton method on a singleton class. 52 | 53 | For example, wee can see that Integer#upto took the most time, 4.06 seconds. An additional 54 | 4.66 seconds were spent in its children, for a total time of 8.72 seconds. 55 | 56 | -------------------------------------------------------------------------------- /examples/graph.dot: -------------------------------------------------------------------------------- 1 | digraph "Profile" { 2 | label="WALL_TIME >=0%\nTotal: 1.382985"; 3 | labelloc=t; 4 | labeljust=l; 5 | subgraph "Thread 2148237740" { 6 | 2159427600 [label="[]=\n(0%)"]; 7 | 2159427680 [label="%\n(15%)"]; 8 | 2159427760 [label="run_primes\n(100%)"]; 9 | 2159427760 -> 2159428600 [label="2/2" fontsize=10 fontcolor="#666666"]; 10 | 2159427760 -> 2159428300 [label="2/2" fontsize=10 fontcolor="#666666"]; 11 | 2159427840 [label="new\n(0%)"]; 12 | 2159427840 -> 2159428080 [label="2/2" fontsize=10 fontcolor="#666666"]; 13 | 2159427840 -> 2159428760 [label="2/2" fontsize=10 fontcolor="#666666"]; 14 | 2159427920 [label="rand\n(0%)"]; 15 | 2159428000 [label="-\n(0%)"]; 16 | 2159428080 [label="initialize\n(0%)"]; 17 | 2159428160 [label="each_index\n(1%)"]; 18 | 2159428160 -> 2159427920 [label="20000/20000" fontsize=10 fontcolor="#666666"]; 19 | 2159428160 -> 2159427600 [label="20000/20000" fontsize=10 fontcolor="#666666"]; 20 | 2159428220 [label="go\n(49%)"]; 21 | 2159428220 -> 2159427760 [label="1/2" fontsize=10 fontcolor="#666666"]; 22 | 2159428300 [label="make_random_array\n(1%)"]; 23 | 2159428300 -> 2159428160 [label="2/2" fontsize=10 fontcolor="#666666"]; 24 | 2159428300 -> 2159427840 [label="2/2" fontsize=10 fontcolor="#666666"]; 25 | 2159428360 [label="setup\n(100%)"]; 26 | 2159428360 -> 2159427760 [label="1/2" fontsize=10 fontcolor="#666666"]; 27 | 2159428360 -> 2159428220 [label="1/1" fontsize=10 fontcolor="#666666"]; 28 | 2159428440 [label="select\n(99%)"]; 29 | 2159428440 -> 2159428520 [label="20000/20000" fontsize=10 fontcolor="#666666"]; 30 | 2159428520 [label="is_prime\n(98%)"]; 31 | 2159428520 -> 2159428680 [label="20000/20000" fontsize=10 fontcolor="#666666"]; 32 | 2159428520 -> 2159428000 [label="20000/20000" fontsize=10 fontcolor="#666666"]; 33 | 2159428600 [label="find_primes\n(99%)"]; 34 | 2159428600 -> 2159428440 [label="2/2" fontsize=10 fontcolor="#666666"]; 35 | 2159428680 [label="upto\n(97%)"]; 36 | 2159428680 -> 2159427680 [label="1562487/1562487" fontsize=10 fontcolor="#666666"]; 37 | 2159428680 -> 2159428920 [label="1562487/1562487" fontsize=10 fontcolor="#666666"]; 38 | 2159428760 [label="allocate\n(0%)"]; 39 | 2159428920 [label="==\n(12%)"]; 40 | } 41 | subgraph cluster_2159103200 { 42 | label = "Object"; 43 | fontcolor = "#666666"; 44 | fontsize = 16; 45 | color = "#666666"; 46 | 2159428600; 47 | 2159428520; 48 | 2159428300; 49 | 2159427760; 50 | } 51 | subgraph cluster_2159103260 { 52 | label = "Integer"; 53 | fontcolor = "#666666"; 54 | fontsize = 16; 55 | color = "#666666"; 56 | 2159428680; 57 | } 58 | subgraph cluster_2159103060 { 59 | label = "PrintersTest"; 60 | fontcolor = "#666666"; 61 | fontsize = 16; 62 | color = "#666666"; 63 | 2159428360; 64 | 2159428220; 65 | } 66 | subgraph cluster_2159103120 { 67 | label = "Array"; 68 | fontcolor = "#666666"; 69 | fontsize = 16; 70 | color = "#666666"; 71 | 2159428440; 72 | 2159428160; 73 | 2159428080; 74 | 2159427600; 75 | } 76 | subgraph cluster_2159103340 { 77 | label = ""; 78 | fontcolor = "#666666"; 79 | fontsize = 16; 80 | color = "#666666"; 81 | 2159428760; 82 | } 83 | subgraph cluster_2159102840 { 84 | label = "Class"; 85 | fontcolor = "#666666"; 86 | fontsize = 16; 87 | color = "#666666"; 88 | 2159427840; 89 | } 90 | subgraph cluster_2159102980 { 91 | label = "Kernel"; 92 | fontcolor = "#666666"; 93 | fontsize = 16; 94 | color = "#666666"; 95 | 2159427920; 96 | } 97 | subgraph cluster_2159103400 { 98 | label = "Fixnum"; 99 | fontcolor = "#666666"; 100 | fontsize = 16; 101 | color = "#666666"; 102 | 2159428920; 103 | 2159428000; 104 | 2159427680; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /lib/ruby-prof/method_info.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module RubyProf 4 | class MethodInfo 5 | include Comparable 6 | 7 | def <=>(other) 8 | if self.total_time < other.total_time 9 | -1 10 | elsif self.total_time > other.total_time 11 | 1 12 | elsif self.min_depth < other.min_depth 13 | 1 14 | elsif self.min_depth > other.min_depth 15 | -1 16 | else 17 | -1 * (self.full_name <=> other.full_name) 18 | end 19 | end 20 | 21 | def called 22 | @called ||= begin 23 | call_infos.inject(0) do |sum, call_info| 24 | sum += call_info.called 25 | end 26 | end 27 | end 28 | 29 | def total_time 30 | @total_time ||= begin 31 | call_infos.inject(0) do |sum, call_info| 32 | sum += call_info.total_time if call_info.minimal? 33 | sum 34 | end 35 | end 36 | end 37 | 38 | def self_time 39 | @self_time ||= begin 40 | call_infos.inject(0) do |sum, call_info| 41 | sum += call_info.self_time 42 | end 43 | end 44 | end 45 | 46 | def wait_time 47 | @wait_time ||= begin 48 | call_infos.inject(0) do |sum, call_info| 49 | sum += call_info.wait_time 50 | end 51 | end 52 | end 53 | 54 | def children_time 55 | @children_time ||= begin 56 | call_infos.inject(0) do |sum, call_info| 57 | sum += call_info.children_time if call_info.minimal? 58 | sum 59 | end 60 | end 61 | end 62 | 63 | def min_depth 64 | @min_depth ||= call_infos.map do |call_info| 65 | call_info.depth 66 | end.min 67 | end 68 | 69 | def root? 70 | @root ||= begin 71 | call_infos.find do |call_info| 72 | not call_info.root? 73 | end.nil? 74 | end 75 | end 76 | 77 | def children 78 | @children ||= begin 79 | call_infos.map do |call_info| 80 | call_info.children 81 | end.flatten 82 | end 83 | end 84 | 85 | def aggregate_parents 86 | # Group call info's based on their parents 87 | groups = self.call_infos.inject(Hash.new) do |hash, call_info| 88 | key = call_info.parent ? call_info.parent.target : self 89 | (hash[key] ||= []) << call_info 90 | hash 91 | end 92 | 93 | groups.map do |key, value| 94 | AggregateCallInfo.new(value) 95 | end 96 | end 97 | 98 | def aggregate_children 99 | # Group call info's based on their targets 100 | groups = self.children.inject(Hash.new) do |hash, call_info| 101 | key = call_info.target 102 | (hash[key] ||= []) << call_info 103 | hash 104 | end 105 | 106 | groups.map do |key, value| 107 | AggregateCallInfo.new(value) 108 | end 109 | end 110 | 111 | def to_s 112 | full_name 113 | end 114 | 115 | def dump 116 | res = "" 117 | res << "MINFO: #{klass_name}##{method_name} total_time: #{total_time} (#{full_name})\n" 118 | call_infos.each do |ci| 119 | pinfo = ci.root? ? "TOPLEVEL" : (p=ci.parent.target; "#{p.klass_name}##{p.method_name} (#{ci.parent.object_id}) (#{p.full_name})") 120 | res << "CINFO[#{ci.object_id}] called #{ci.called} times from #{pinfo}\n" 121 | end 122 | res 123 | end 124 | 125 | # remove method from the call graph. should not be called directly. 126 | def eliminate! 127 | # $stderr.puts "eliminating #{self}" 128 | call_infos.each{ |call_info| call_info.eliminate! } 129 | call_infos.clear 130 | end 131 | 132 | end 133 | end 134 | -------------------------------------------------------------------------------- /test/start_stop_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | require './test_helper' 5 | 6 | class StartStopTest < Test::Unit::TestCase 7 | def setup 8 | # Need to use wall time for this test due to the sleep calls 9 | RubyProf::measure_mode = RubyProf::WALL_TIME 10 | end 11 | 12 | def method1 13 | RubyProf.start 14 | method2 15 | end 16 | 17 | def method2 18 | method3 19 | end 20 | 21 | def method3 22 | sleep(2) 23 | @result = RubyProf.stop 24 | end 25 | 26 | def test_extra_stop_should_raise 27 | RubyProf.start 28 | assert_raise(RuntimeError) do 29 | RubyProf.start 30 | end 31 | 32 | assert_raise(RuntimeError) do 33 | RubyProf.profile {} 34 | end 35 | 36 | RubyProf.stop # ok 37 | assert_raise(RuntimeError) do 38 | RubyProf.stop 39 | end 40 | end 41 | 42 | 43 | def test_different_methods 44 | method1 45 | 46 | # Ruby prof should be stopped 47 | assert_equal(false, RubyProf.running?) 48 | 49 | 50 | # Length should be 4: 51 | # StartStopTest#method1 52 | # StartStopTest#method2 53 | # StartStopTest#method3 54 | # Kernel#sleep 55 | 56 | methods = @result.threads.values.first.sort.reverse 57 | assert_equal(4, methods.length) 58 | 59 | # Check StackTest#test_call_sequence 60 | method = methods[0] 61 | assert_equal('StartStopTest#method1', method.full_name) 62 | assert_equal(1, method.called) 63 | assert_in_delta(2, method.total_time, 0.05) 64 | assert_in_delta(0, method.wait_time, 0.01) 65 | assert_in_delta(0, method.self_time, 0.01) 66 | assert_in_delta(2, method.children_time, 0.05) 67 | assert_equal(1, method.call_infos.length) 68 | 69 | call_info = method.call_infos[0] 70 | assert_equal('StartStopTest#method1', call_info.call_sequence) 71 | assert_equal(1, call_info.children.length) 72 | 73 | method = methods[1] 74 | assert_equal('StartStopTest#method2', method.full_name) 75 | assert_equal(1, method.called) 76 | assert_in_delta(2, method.total_time, 0.05) 77 | assert_in_delta(0, method.wait_time, 0.01) 78 | assert_in_delta(0, method.self_time, 0.01) 79 | assert_in_delta(2, method.children_time, 0.05) 80 | assert_equal(1, method.call_infos.length) 81 | 82 | call_info = method.call_infos[0] 83 | assert_equal('StartStopTest#method1->StartStopTest#method2', call_info.call_sequence) 84 | assert_equal(1, call_info.children.length) 85 | 86 | method = methods[2] 87 | assert_equal('StartStopTest#method3', method.full_name) 88 | assert_equal(1, method.called) 89 | assert_in_delta(2, method.total_time, 0.01) 90 | assert_in_delta(0, method.wait_time, 0.01) 91 | assert_in_delta(0, method.self_time, 0.01) 92 | assert_in_delta(2, method.children_time, 0.01) 93 | assert_equal(1, method.call_infos.length) 94 | 95 | call_info = method.call_infos[0] 96 | assert_equal('StartStopTest#method1->StartStopTest#method2->StartStopTest#method3', call_info.call_sequence) 97 | assert_equal(1, call_info.children.length) 98 | 99 | method = methods[3] 100 | assert_equal('Kernel#sleep', method.full_name) 101 | assert_equal(1, method.called) 102 | assert_in_delta(2, method.total_time, 0.01) 103 | assert_in_delta(0, method.wait_time, 0.01) 104 | assert_in_delta(2, method.self_time, 0.01) 105 | assert_in_delta(0, method.children_time, 0.01) 106 | assert_equal(1, method.call_infos.length) 107 | 108 | call_info = method.call_infos[0] 109 | assert_equal('StartStopTest#method1->StartStopTest#method2->StartStopTest#method3->Kernel#sleep', call_info.call_sequence) 110 | assert_equal(0, call_info.children.length) 111 | end 112 | end 113 | -------------------------------------------------------------------------------- /test/measurement_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | require './test_helper' 5 | 6 | class MeasurementTest < Test::Unit::TestCase 7 | def setup 8 | # also done in C these days... 9 | GC.enable_stats if GC.respond_to?(:enable_stats) 10 | end 11 | 12 | def teardown 13 | # also done in C for normal runs... 14 | GC.disable_stats if GC.respond_to?(:disable_stats) 15 | end 16 | 17 | def test_process_time_mode 18 | RubyProf::measure_mode = RubyProf::PROCESS_TIME 19 | assert_equal(RubyProf::PROCESS_TIME, RubyProf::measure_mode) 20 | end 21 | 22 | def test_process_time 23 | t = RubyProf.measure_process_time 24 | assert_kind_of(Float, t) 25 | 26 | u = RubyProf.measure_process_time 27 | assert(u >= t, [t, u].inspect) 28 | end 29 | 30 | def test_wall_time_mode 31 | RubyProf::measure_mode = RubyProf::WALL_TIME 32 | assert_equal(RubyProf::WALL_TIME, RubyProf::measure_mode) 33 | end 34 | 35 | def test_wall_time 36 | t = RubyProf.measure_wall_time 37 | assert_kind_of Float, t 38 | 39 | u = RubyProf.measure_wall_time 40 | assert u >= t, [t, u].inspect 41 | end 42 | 43 | if RubyProf::CPU_TIME 44 | def test_cpu_time_mode 45 | RubyProf::measure_mode = RubyProf::CPU_TIME 46 | assert_equal(RubyProf::CPU_TIME, RubyProf::measure_mode) 47 | end 48 | 49 | def test_cpu_time 50 | RubyProf.cpu_frequency = 2.33e9 51 | 52 | t = RubyProf.measure_cpu_time 53 | assert_kind_of Float, t 54 | 55 | u = RubyProf.measure_cpu_time 56 | assert u > t, [t, u].inspect 57 | end 58 | end 59 | 60 | if RubyProf::ALLOCATIONS 61 | def test_allocations_mode 62 | RubyProf::measure_mode = RubyProf::ALLOCATIONS 63 | assert_equal(RubyProf::ALLOCATIONS, RubyProf::measure_mode) 64 | end 65 | 66 | def test_allocations 67 | t = RubyProf.measure_allocations 68 | assert_kind_of Integer, t 69 | 70 | u = RubyProf.measure_allocations 71 | assert u > t, [t, u].inspect 72 | end 73 | end 74 | 75 | def memory_test_helper 76 | result = RubyProf.profile {Array.new} 77 | total = result.threads.values.first.inject(0) { |sum, m| sum + m.total_time } 78 | assert(total < 1_000_000, 'Total should not have subtract overflow error') 79 | total 80 | end 81 | 82 | if RubyProf::MEMORY 83 | def test_memory_mode 84 | RubyProf::measure_mode = RubyProf::MEMORY 85 | assert_equal(RubyProf::MEMORY, RubyProf::measure_mode) 86 | end 87 | 88 | def test_memory 89 | t = RubyProf.measure_memory 90 | assert_kind_of Integer, t 91 | 92 | u = RubyProf.measure_memory 93 | assert(u >= t, [t, u].inspect) 94 | RubyProf::measure_mode = RubyProf::MEMORY 95 | total = memory_test_helper 96 | assert(total > 0, 'Should measure more than zero kilobytes of memory usage') 97 | assert_not_equal(0, total % 1, 'Should not truncate fractional kilobyte measurements') 98 | end 99 | end 100 | 101 | if RubyProf::GC_RUNS 102 | def test_gc_runs_mode 103 | RubyProf::measure_mode = RubyProf::GC_RUNS 104 | assert_equal(RubyProf::GC_RUNS, RubyProf::measure_mode) 105 | end 106 | 107 | def test_gc_runs 108 | t = RubyProf.measure_gc_runs 109 | assert_kind_of Integer, t 110 | 111 | GC.start 112 | 113 | u = RubyProf.measure_gc_runs 114 | assert u > t, [t, u].inspect 115 | RubyProf::measure_mode = RubyProf::GC_RUNS 116 | memory_test_helper 117 | end 118 | end 119 | 120 | if RubyProf::GC_TIME 121 | def test_gc_time 122 | t = RubyProf.measure_gc_time 123 | assert_kind_of Integer, t 124 | 125 | GC.start 126 | 127 | u = RubyProf.measure_gc_time 128 | assert u > t, [t, u].inspect 129 | RubyProf::measure_mode = RubyProf::GC_TIME 130 | memory_test_helper 131 | end 132 | end 133 | end 134 | -------------------------------------------------------------------------------- /test/aggregate_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | require './test_helper' 5 | 6 | # Test data 7 | # A B C 8 | # | | | 9 | # Z A A 10 | # | | 11 | # Z Z 12 | 13 | class AggClass 14 | def z 15 | sleep 1 16 | end 17 | 18 | def a 19 | z 20 | end 21 | 22 | def b 23 | a 24 | end 25 | 26 | def c 27 | a 28 | end 29 | end 30 | 31 | class AggregateTest < Test::Unit::TestCase 32 | def setup 33 | # Need to use wall time for this test due to the sleep calls 34 | RubyProf::measure_mode = RubyProf::WALL_TIME 35 | end 36 | 37 | def test_all_call_infos_are_minimal_as_there_is_no_recursion 38 | c1 = AggClass.new 39 | result = RubyProf.profile do 40 | c1.a 41 | c1.b 42 | c1.c 43 | end 44 | methods = result.threads.values.first.sort.reverse 45 | methods.each do |m| 46 | m.call_infos.each do |ci| 47 | assert ci.minimal?, "#{ci.call_sequence} should be minimal in the call tree" 48 | end 49 | end 50 | end 51 | 52 | def test_call_infos 53 | c1 = AggClass.new 54 | result = RubyProf.profile do 55 | c1.a 56 | c1.b 57 | c1.c 58 | end 59 | 60 | methods = result.threads.values.first.sort.reverse 61 | method = methods.find {|meth| meth.full_name == 'AggClass#z'} 62 | 63 | # Check AggClass#z 64 | assert_equal('AggClass#z', method.full_name) 65 | assert_equal(3, method.called) 66 | assert_in_delta(3, method.total_time, 0.01) 67 | assert_in_delta(0, method.wait_time, 0.01) 68 | assert_in_delta(0, method.self_time, 0.01) 69 | assert_in_delta(3, method.children_time, 0.01) 70 | assert_equal(3, method.call_infos.length) 71 | 72 | call_info = method.call_infos[0] 73 | assert_equal('AggregateTest#test_call_infos->AggClass#a->AggClass#z', call_info.call_sequence) 74 | assert_equal(1, call_info.children.length) 75 | 76 | call_info = method.call_infos[1] 77 | assert_equal('AggregateTest#test_call_infos->AggClass#b->AggClass#a->AggClass#z', call_info.call_sequence) 78 | assert_equal(1, call_info.children.length) 79 | 80 | call_info = method.call_infos[2] 81 | assert_equal('AggregateTest#test_call_infos->AggClass#c->AggClass#a->AggClass#z', call_info.call_sequence) 82 | assert_equal(1, call_info.children.length) 83 | end 84 | 85 | def test_aggregates_parents 86 | c1 = AggClass.new 87 | result = RubyProf.profile do 88 | c1.a 89 | c1.b 90 | c1.c 91 | end 92 | 93 | methods = result.threads.values.first.sort.reverse 94 | method = methods.find {|meth| meth.full_name == 'AggClass#z'} 95 | 96 | # Check AggClass#z 97 | assert_equal('AggClass#z', method.full_name) 98 | 99 | call_infos = method.aggregate_parents 100 | assert_equal(1, call_infos.length) 101 | 102 | call_info = call_infos.first 103 | assert_equal('AggClass#a', call_info.parent.target.full_name) 104 | assert_in_delta(3, call_info.total_time, 0.05) 105 | assert_in_delta(0, call_info.wait_time, 0.01) 106 | assert_in_delta(0, call_info.self_time, 0.05) 107 | assert_in_delta(3, call_info.children_time, 0.05) 108 | assert_equal(3, call_info.called) 109 | end 110 | 111 | def test_aggregates_children 112 | c1 = AggClass.new 113 | result = RubyProf.profile do 114 | c1.a 115 | c1.b 116 | c1.c 117 | end 118 | 119 | methods = result.threads.values.first.sort.reverse 120 | method = methods.find {|meth| meth.full_name == 'AggClass#a'} 121 | 122 | # Check AggClass#a 123 | assert_equal('AggClass#a', method.full_name) 124 | 125 | call_infos = method.aggregate_children 126 | assert_equal(1, call_infos.length) 127 | 128 | call_info = call_infos.first 129 | assert_equal('AggClass#z', call_info.target.full_name) 130 | assert_in_delta(3, call_info.total_time, 0.05) 131 | assert_in_delta(0, call_info.wait_time, 0.01) 132 | assert_in_delta(0, call_info.self_time, 0.05) 133 | assert_in_delta(3, call_info.children_time, 0.05) 134 | assert_equal(3, call_info.called) 135 | end 136 | end 137 | -------------------------------------------------------------------------------- /lib/ruby-prof/test.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | # Now load ruby-prof and away we go 4 | require 'fileutils' 5 | require 'ruby-prof' 6 | require 'benchmark' 7 | 8 | module RubyProf 9 | module Test 10 | PROFILE_OPTIONS = { 11 | :measure_modes => [RubyProf::PROCESS_TIME], 12 | :count => 10, 13 | :printers => [RubyProf::FlatPrinter, RubyProf::GraphHtmlPrinter], 14 | :min_percent => 0.05, 15 | :output_dir => Dir.pwd } 16 | 17 | def output_dir 18 | PROFILE_OPTIONS[:output_dir] 19 | end 20 | 21 | def run(result) 22 | return if @method_name.to_s == "default_test" 23 | 24 | yield(self.class::STARTED, name) 25 | @_result = result 26 | run_warmup 27 | PROFILE_OPTIONS[:measure_modes].each do |measure_mode| 28 | data = run_profile(measure_mode) 29 | report_profile(data, measure_mode) 30 | result.add_run 31 | end 32 | yield(self.class::FINISHED, name) 33 | end 34 | 35 | def run_test 36 | begin 37 | setup 38 | yield 39 | rescue ::Test::Unit::AssertionFailedError => e 40 | add_failure(e.message, e.backtrace) 41 | rescue StandardError, ScriptError 42 | add_error($!) 43 | ensure 44 | begin 45 | teardown 46 | rescue ::Test::Unit::AssertionFailedError => e 47 | add_failure(e.message, e.backtrace) 48 | rescue StandardError, ScriptError 49 | add_error($!) 50 | end 51 | end 52 | end 53 | 54 | def run_warmup 55 | print "\n#{self.class.name}##{method_name}" 56 | 57 | run_test do 58 | bench = Benchmark.realtime do 59 | __send__(@method_name) 60 | end 61 | puts " (%.2fs warmup)" % bench 62 | end 63 | end 64 | 65 | def run_profile(measure_mode) 66 | RubyProf.measure_mode = measure_mode 67 | 68 | print ' ' 69 | PROFILE_OPTIONS[:count].times do |i| 70 | run_test do 71 | begin 72 | print '.' 73 | $stdout.flush 74 | GC.disable 75 | 76 | RubyProf.resume do 77 | __send__(@method_name) 78 | end 79 | ensure 80 | GC.enable 81 | end 82 | end 83 | end 84 | 85 | data = RubyProf.stop 86 | bench = data.threads.values.inject(0) do |total, method_infos| 87 | top = method_infos.max 88 | total += top.total_time 89 | total 90 | end 91 | 92 | puts "\n #{measure_mode_name(measure_mode)}: #{format_profile_total(bench, measure_mode)}\n" 93 | 94 | data 95 | end 96 | 97 | def format_profile_total(total, measure_mode) 98 | case measure_mode 99 | when RubyProf::PROCESS_TIME, RubyProf::WALL_TIME 100 | "%.2f seconds" % total 101 | when RubyProf::MEMORY 102 | "%.2f kilobytes" % total 103 | when RubyProf::ALLOCATIONS 104 | "%d allocations" % total 105 | else 106 | "%.2f #{measure_mode}" 107 | end 108 | end 109 | 110 | def report_profile(data, measure_mode) 111 | PROFILE_OPTIONS[:printers].each do |printer_klass| 112 | printer = printer_klass.new(data) 113 | 114 | # Makes sure the output directory exits 115 | FileUtils.mkdir_p(output_dir) 116 | 117 | # Open the file 118 | file_name = report_filename(printer, measure_mode) 119 | 120 | File.open(file_name, 'wb') do |file| 121 | printer.print(file, PROFILE_OPTIONS) 122 | end 123 | end 124 | end 125 | 126 | # The report filename is test_name + measure_mode + report_type 127 | def report_filename(printer, measure_mode) 128 | suffix = 129 | case printer 130 | when RubyProf::FlatPrinter; 'flat.txt' 131 | when RubyProf::GraphPrinter; 'graph.txt' 132 | when RubyProf::GraphHtmlPrinter; 'graph.html' 133 | when RubyProf::CallTreePrinter; 'tree.txt' 134 | else printer.to_s.downcase 135 | end 136 | 137 | "#{output_dir}/#{method_name}_#{measure_mode_name(measure_mode)}_#{suffix}" 138 | end 139 | 140 | def measure_mode_name(measure_mode) 141 | case measure_mode 142 | when RubyProf::PROCESS_TIME; 'process_time' 143 | when RubyProf::WALL_TIME; 'wall_time' 144 | when RubyProf::MEMORY; 'memory' 145 | when RubyProf::ALLOCATIONS; 'allocations' 146 | else "measure#{measure_mode}" 147 | end 148 | end 149 | end 150 | end 151 | -------------------------------------------------------------------------------- /patches/1.9.3/p0/gcdata.patch: -------------------------------------------------------------------------------- 1 | --- a/gc.c (revision 33566) 2 | +++ b/gc.c (working copy) 3 | @@ -309,16 +309,12 @@ 4 | struct gc_list *next; 5 | }; 6 | 7 | -#define CALC_EXACT_MALLOC_SIZE 0 8 | - 9 | typedef struct rb_objspace { 10 | struct { 11 | size_t limit; 12 | size_t increase; 13 | -#if CALC_EXACT_MALLOC_SIZE 14 | size_t allocated_size; 15 | size_t allocations; 16 | -#endif 17 | } malloc_params; 18 | struct { 19 | size_t increment; 20 | @@ -715,10 +711,6 @@ 21 | } 22 | if (size == 0) size = 1; 23 | 24 | -#if CALC_EXACT_MALLOC_SIZE 25 | - size += sizeof(size_t); 26 | -#endif 27 | - 28 | if ((ruby_gc_stress && !ruby_disable_gc_stress) || 29 | (malloc_increase+size) > malloc_limit) { 30 | garbage_collect_with_gvl(objspace); 31 | @@ -732,12 +724,8 @@ 32 | { 33 | malloc_increase += size; 34 | 35 | -#if CALC_EXACT_MALLOC_SIZE 36 | objspace->malloc_params.allocated_size += size; 37 | objspace->malloc_params.allocations++; 38 | - ((size_t *)mem)[0] = size; 39 | - mem = (size_t *)mem + 1; 40 | -#endif 41 | 42 | return mem; 43 | } 44 | @@ -776,11 +764,7 @@ 45 | if (ruby_gc_stress && !ruby_disable_gc_stress) 46 | garbage_collect_with_gvl(objspace); 47 | 48 | -#if CALC_EXACT_MALLOC_SIZE 49 | - size += sizeof(size_t); 50 | objspace->malloc_params.allocated_size -= size; 51 | - ptr = (size_t *)ptr - 1; 52 | -#endif 53 | 54 | mem = realloc(ptr, size); 55 | if (!mem) { 56 | @@ -793,11 +777,7 @@ 57 | } 58 | malloc_increase += size; 59 | 60 | -#if CALC_EXACT_MALLOC_SIZE 61 | objspace->malloc_params.allocated_size += size; 62 | - ((size_t *)mem)[0] = size; 63 | - mem = (size_t *)mem + 1; 64 | -#endif 65 | 66 | return mem; 67 | } 68 | @@ -805,14 +785,6 @@ 69 | static void 70 | vm_xfree(rb_objspace_t *objspace, void *ptr) 71 | { 72 | -#if CALC_EXACT_MALLOC_SIZE 73 | - size_t size; 74 | - ptr = ((size_t *)ptr) - 1; 75 | - size = ((size_t*)ptr)[0]; 76 | - objspace->malloc_params.allocated_size -= size; 77 | - objspace->malloc_params.allocations--; 78 | -#endif 79 | - 80 | free(ptr); 81 | } 82 | 83 | @@ -3384,7 +3356,6 @@ 84 | } 85 | 86 | 87 | -#if CALC_EXACT_MALLOC_SIZE 88 | /* 89 | * call-seq: 90 | * GC.malloc_allocated_size -> Integer 91 | @@ -3394,8 +3365,8 @@ 92 | * It returns the allocated size by malloc(). 93 | */ 94 | 95 | -static VALUE 96 | -gc_malloc_allocated_size(VALUE self) 97 | +VALUE 98 | +rb_gc_malloc_allocated_size(VALUE self) 99 | { 100 | return UINT2NUM((&rb_objspace)->malloc_params.allocated_size); 101 | } 102 | @@ -3409,12 +3380,11 @@ 103 | * It returns the number of allocated memory object by malloc(). 104 | */ 105 | 106 | -static VALUE 107 | -gc_malloc_allocations(VALUE self) 108 | +VALUE 109 | +rb_gc_malloc_allocations(VALUE self) 110 | { 111 | return UINT2NUM((&rb_objspace)->malloc_params.allocations); 112 | } 113 | -#endif 114 | 115 | static VALUE 116 | gc_profile_record_get(void) 117 | @@ -3599,6 +3569,8 @@ 118 | rb_define_singleton_method(rb_mGC, "start", rb_gc_start, 0); 119 | rb_define_singleton_method(rb_mGC, "enable", rb_gc_enable, 0); 120 | rb_define_singleton_method(rb_mGC, "disable", rb_gc_disable, 0); 121 | + rb_define_singleton_method(rb_mGC, "malloc_allocated_size", rb_gc_malloc_allocated_size, 0); 122 | + rb_define_singleton_method(rb_mGC, "malloc_allocations", rb_gc_malloc_allocations, 0); 123 | rb_define_singleton_method(rb_mGC, "stress", gc_stress_get, 0); 124 | rb_define_singleton_method(rb_mGC, "stress=", gc_stress_set, 1); 125 | rb_define_singleton_method(rb_mGC, "count", gc_count, 0); 126 | @@ -3613,6 +3585,7 @@ 127 | rb_define_singleton_method(rb_mProfiler, "result", gc_profile_result, 0); 128 | rb_define_singleton_method(rb_mProfiler, "report", gc_profile_report, -1); 129 | rb_define_singleton_method(rb_mProfiler, "total_time", gc_profile_total_time, 0); 130 | + rb_define_singleton_method(rb_mProfiler, "data", gc_profile_record_get, 0); 131 | 132 | rb_mObSpace = rb_define_module("ObjectSpace"); 133 | rb_define_module_function(rb_mObSpace, "each_object", os_each_obj, -1); 134 | @@ -3632,9 +3605,4 @@ 135 | rb_define_method(rb_mKernel, "object_id", rb_obj_id, 0); 136 | 137 | rb_define_module_function(rb_mObSpace, "count_objects", count_objects, -1); 138 | - 139 | -#if CALC_EXACT_MALLOC_SIZE 140 | - rb_define_singleton_method(rb_mGC, "malloc_allocated_size", gc_malloc_allocated_size, 0); 141 | - rb_define_singleton_method(rb_mGC, "malloc_allocations", gc_malloc_allocations, 0); 142 | -#endif 143 | } 144 | -------------------------------------------------------------------------------- /patches/1.9.3/p125/gcdata.patch: -------------------------------------------------------------------------------- 1 | --- a/gc.c (revision 34957) 2 | +++ b/gc.c (working copy) 3 | @@ -319,16 +319,12 @@ 4 | struct gc_list *next; 5 | }; 6 | 7 | -#define CALC_EXACT_MALLOC_SIZE 0 8 | - 9 | typedef struct rb_objspace { 10 | struct { 11 | size_t limit; 12 | size_t increase; 13 | -#if CALC_EXACT_MALLOC_SIZE 14 | size_t allocated_size; 15 | size_t allocations; 16 | -#endif 17 | } malloc_params; 18 | struct { 19 | size_t increment; 20 | @@ -730,10 +726,6 @@ 21 | } 22 | if (size == 0) size = 1; 23 | 24 | -#if CALC_EXACT_MALLOC_SIZE 25 | - size += sizeof(size_t); 26 | -#endif 27 | - 28 | if ((ruby_gc_stress && !ruby_disable_gc_stress) || 29 | (malloc_increase+size) > malloc_limit) { 30 | garbage_collect_with_gvl(objspace); 31 | @@ -747,12 +739,8 @@ 32 | { 33 | malloc_increase += size; 34 | 35 | -#if CALC_EXACT_MALLOC_SIZE 36 | objspace->malloc_params.allocated_size += size; 37 | objspace->malloc_params.allocations++; 38 | - ((size_t *)mem)[0] = size; 39 | - mem = (size_t *)mem + 1; 40 | -#endif 41 | 42 | return mem; 43 | } 44 | @@ -791,11 +779,7 @@ 45 | if (ruby_gc_stress && !ruby_disable_gc_stress) 46 | garbage_collect_with_gvl(objspace); 47 | 48 | -#if CALC_EXACT_MALLOC_SIZE 49 | - size += sizeof(size_t); 50 | objspace->malloc_params.allocated_size -= size; 51 | - ptr = (size_t *)ptr - 1; 52 | -#endif 53 | 54 | mem = realloc(ptr, size); 55 | if (!mem) { 56 | @@ -808,11 +792,7 @@ 57 | } 58 | malloc_increase += size; 59 | 60 | -#if CALC_EXACT_MALLOC_SIZE 61 | objspace->malloc_params.allocated_size += size; 62 | - ((size_t *)mem)[0] = size; 63 | - mem = (size_t *)mem + 1; 64 | -#endif 65 | 66 | return mem; 67 | } 68 | @@ -820,14 +800,6 @@ 69 | static void 70 | vm_xfree(rb_objspace_t *objspace, void *ptr) 71 | { 72 | -#if CALC_EXACT_MALLOC_SIZE 73 | - size_t size; 74 | - ptr = ((size_t *)ptr) - 1; 75 | - size = ((size_t*)ptr)[0]; 76 | - objspace->malloc_params.allocated_size -= size; 77 | - objspace->malloc_params.allocations--; 78 | -#endif 79 | - 80 | free(ptr); 81 | } 82 | 83 | @@ -3401,7 +3373,6 @@ 84 | } 85 | 86 | 87 | -#if CALC_EXACT_MALLOC_SIZE 88 | /* 89 | * call-seq: 90 | * GC.malloc_allocated_size -> Integer 91 | @@ -3411,8 +3382,8 @@ 92 | * It returns the allocated size by malloc(). 93 | */ 94 | 95 | -static VALUE 96 | -gc_malloc_allocated_size(VALUE self) 97 | +VALUE 98 | +rb_gc_malloc_allocated_size(VALUE self) 99 | { 100 | return UINT2NUM((&rb_objspace)->malloc_params.allocated_size); 101 | } 102 | @@ -3426,12 +3397,11 @@ 103 | * It returns the number of allocated memory object by malloc(). 104 | */ 105 | 106 | -static VALUE 107 | -gc_malloc_allocations(VALUE self) 108 | +VALUE 109 | +rb_gc_malloc_allocations(VALUE self) 110 | { 111 | return UINT2NUM((&rb_objspace)->malloc_params.allocations); 112 | } 113 | -#endif 114 | 115 | static VALUE 116 | gc_profile_record_get(void) 117 | @@ -3616,6 +3586,8 @@ 118 | rb_define_singleton_method(rb_mGC, "start", rb_gc_start, 0); 119 | rb_define_singleton_method(rb_mGC, "enable", rb_gc_enable, 0); 120 | rb_define_singleton_method(rb_mGC, "disable", rb_gc_disable, 0); 121 | + rb_define_singleton_method(rb_mGC, "malloc_allocated_size", rb_gc_malloc_allocated_size, 0); 122 | + rb_define_singleton_method(rb_mGC, "malloc_allocations", rb_gc_malloc_allocations, 0); 123 | rb_define_singleton_method(rb_mGC, "stress", gc_stress_get, 0); 124 | rb_define_singleton_method(rb_mGC, "stress=", gc_stress_set, 1); 125 | rb_define_singleton_method(rb_mGC, "count", gc_count, 0); 126 | @@ -3630,6 +3602,7 @@ 127 | rb_define_singleton_method(rb_mProfiler, "result", gc_profile_result, 0); 128 | rb_define_singleton_method(rb_mProfiler, "report", gc_profile_report, -1); 129 | rb_define_singleton_method(rb_mProfiler, "total_time", gc_profile_total_time, 0); 130 | + rb_define_singleton_method(rb_mProfiler, "data", gc_profile_record_get, 0); 131 | 132 | rb_mObSpace = rb_define_module("ObjectSpace"); 133 | rb_define_module_function(rb_mObSpace, "each_object", os_each_obj, -1); 134 | @@ -3649,9 +3622,4 @@ 135 | rb_define_method(rb_mKernel, "object_id", rb_obj_id, 0); 136 | 137 | rb_define_module_function(rb_mObSpace, "count_objects", count_objects, -1); 138 | - 139 | -#if CALC_EXACT_MALLOC_SIZE 140 | - rb_define_singleton_method(rb_mGC, "malloc_allocated_size", gc_malloc_allocated_size, 0); 141 | - rb_define_singleton_method(rb_mGC, "malloc_allocations", gc_malloc_allocations, 0); 142 | -#endif 143 | } 144 | -------------------------------------------------------------------------------- /patches/1.9.2/p180/gcdata.patch: -------------------------------------------------------------------------------- 1 | --- gc.c (revision 31173) 2 | +++ gc.c (working copy) 3 | @@ -291,16 +291,12 @@ 4 | struct gc_list *next; 5 | }; 6 | 7 | -#define CALC_EXACT_MALLOC_SIZE 0 8 | - 9 | typedef struct rb_objspace { 10 | struct { 11 | size_t limit; 12 | size_t increase; 13 | -#if CALC_EXACT_MALLOC_SIZE 14 | size_t allocated_size; 15 | size_t allocations; 16 | -#endif 17 | } malloc_params; 18 | struct { 19 | size_t increment; 20 | @@ -647,10 +643,6 @@ 21 | } 22 | if (size == 0) size = 1; 23 | 24 | -#if CALC_EXACT_MALLOC_SIZE 25 | - size += sizeof(size_t); 26 | -#endif 27 | - 28 | if ((ruby_gc_stress && !ruby_disable_gc_stress) || 29 | (malloc_increase+size) > malloc_limit) { 30 | garbage_collect_with_gvl(objspace); 31 | @@ -665,13 +657,9 @@ 32 | } 33 | } 34 | malloc_increase += size; 35 | - 36 | -#if CALC_EXACT_MALLOC_SIZE 37 | + 38 | objspace->malloc_params.allocated_size += size; 39 | objspace->malloc_params.allocations++; 40 | - ((size_t *)mem)[0] = size; 41 | - mem = (size_t *)mem + 1; 42 | -#endif 43 | 44 | return mem; 45 | } 46 | @@ -691,12 +679,8 @@ 47 | } 48 | if (ruby_gc_stress && !ruby_disable_gc_stress) 49 | garbage_collect_with_gvl(objspace); 50 | - 51 | -#if CALC_EXACT_MALLOC_SIZE 52 | - size += sizeof(size_t); 53 | + 54 | objspace->malloc_params.allocated_size -= size; 55 | - ptr = (size_t *)ptr - 1; 56 | -#endif 57 | 58 | mem = realloc(ptr, size); 59 | if (!mem) { 60 | @@ -708,12 +692,8 @@ 61 | } 62 | } 63 | malloc_increase += size; 64 | - 65 | -#if CALC_EXACT_MALLOC_SIZE 66 | + 67 | objspace->malloc_params.allocated_size += size; 68 | - ((size_t *)mem)[0] = size; 69 | - mem = (size_t *)mem + 1; 70 | -#endif 71 | 72 | return mem; 73 | } 74 | @@ -721,14 +701,6 @@ 75 | static void 76 | vm_xfree(rb_objspace_t *objspace, void *ptr) 77 | { 78 | -#if CALC_EXACT_MALLOC_SIZE 79 | - size_t size; 80 | - ptr = ((size_t *)ptr) - 1; 81 | - size = ((size_t*)ptr)[0]; 82 | - objspace->malloc_params.allocated_size -= size; 83 | - objspace->malloc_params.allocations--; 84 | -#endif 85 | - 86 | free(ptr); 87 | } 88 | 89 | @@ -2998,7 +2970,6 @@ 90 | return UINT2NUM((&rb_objspace)->count); 91 | } 92 | 93 | -#if CALC_EXACT_MALLOC_SIZE 94 | /* 95 | * call-seq: 96 | * GC.malloc_allocated_size -> Integer 97 | @@ -3008,8 +2979,8 @@ 98 | * It returns the allocated size by malloc(). 99 | */ 100 | 101 | -static VALUE 102 | -gc_malloc_allocated_size(VALUE self) 103 | +VALUE 104 | +rb_gc_malloc_allocated_size(VALUE self) 105 | { 106 | return UINT2NUM((&rb_objspace)->malloc_params.allocated_size); 107 | } 108 | @@ -3023,12 +2994,11 @@ 109 | * It returns the number of allocated memory object by malloc(). 110 | */ 111 | 112 | -static VALUE 113 | -gc_malloc_allocations(VALUE self) 114 | +VALUE 115 | +rb_gc_malloc_allocations(VALUE self) 116 | { 117 | return UINT2NUM((&rb_objspace)->malloc_params.allocations); 118 | } 119 | -#endif 120 | 121 | static VALUE 122 | gc_profile_record_get(void) 123 | @@ -3186,6 +3156,8 @@ 124 | rb_define_singleton_method(rb_mGC, "stress", gc_stress_get, 0); 125 | rb_define_singleton_method(rb_mGC, "stress=", gc_stress_set, 1); 126 | rb_define_singleton_method(rb_mGC, "count", gc_count, 0); 127 | + rb_define_singleton_method(rb_mGC, "malloc_allocated_size", rb_gc_malloc_allocated_size, 0); 128 | + rb_define_singleton_method(rb_mGC, "malloc_allocations", rb_gc_malloc_allocations, 0); 129 | rb_define_method(rb_mGC, "garbage_collect", rb_gc_start, 0); 130 | 131 | rb_mProfiler = rb_define_module_under(rb_mGC, "Profiler"); 132 | @@ -3196,6 +3168,7 @@ 133 | rb_define_singleton_method(rb_mProfiler, "result", gc_profile_result, 0); 134 | rb_define_singleton_method(rb_mProfiler, "report", gc_profile_report, -1); 135 | rb_define_singleton_method(rb_mProfiler, "total_time", gc_profile_total_time, 0); 136 | + rb_define_singleton_method(rb_mProfiler, "data", gc_profile_record_get, 0); 137 | 138 | rb_mObSpace = rb_define_module("ObjectSpace"); 139 | rb_define_module_function(rb_mObSpace, "each_object", os_each_obj, -1); 140 | @@ -3215,9 +3188,4 @@ 141 | rb_define_method(rb_mKernel, "object_id", rb_obj_id, 0); 142 | 143 | rb_define_module_function(rb_mObSpace, "count_objects", count_objects, -1); 144 | - 145 | -#if CALC_EXACT_MALLOC_SIZE 146 | - rb_define_singleton_method(rb_mGC, "malloc_allocated_size", gc_malloc_allocated_size, 0); 147 | - rb_define_singleton_method(rb_mGC, "malloc_allocations", gc_malloc_allocations, 0); 148 | -#endif 149 | } 150 | -------------------------------------------------------------------------------- /ext/ruby_prof/measure_cpu_time.h: -------------------------------------------------------------------------------- 1 | /* :nodoc: 2 | * Copyright (C) 2008 Shugo Maeda 3 | * Charlie Savage 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions 8 | * are met: 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 16 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 19 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | * SUCH DAMAGE. */ 26 | 27 | #include 28 | 29 | #if defined(_WIN32) || (defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__) || defined(__powerpc__) || defined(__ppc__))) 30 | #define MEASURE_CPU_TIME 2 31 | 32 | static unsigned LONG_LONG cpu_frequency; 33 | 34 | #if defined(__GNUC__) 35 | 36 | #include 37 | 38 | static prof_measure_t 39 | measure_cpu_time() 40 | { 41 | #if defined(__i386__) || defined(__x86_64__) 42 | uint32_t a, d; 43 | __asm__ volatile("rdtsc" : "=a" (a), "=d" (d)); 44 | return ((uint64_t)d << 32) + a; 45 | #elif defined(__powerpc__) || defined(__ppc__) 46 | unsigned long long x, y; 47 | 48 | __asm__ __volatile__ ("\n\ 49 | 1: mftbu %1\n\ 50 | mftb %L0\n\ 51 | mftbu %0\n\ 52 | cmpw %0,%1\n\ 53 | bne- 1b" 54 | : "=r" (x), "=r" (y)); 55 | return x; 56 | #endif 57 | } 58 | 59 | #elif defined(_WIN32) 60 | 61 | static prof_measure_t 62 | measure_cpu_time() 63 | { 64 | prof_measure_t cycles = 0; 65 | 66 | __asm 67 | { 68 | rdtsc 69 | mov DWORD PTR cycles, eax 70 | mov DWORD PTR [cycles + 4], edx 71 | } 72 | return cycles; 73 | } 74 | 75 | #endif 76 | 77 | 78 | /* The _WIN32 check is needed for msys (and maybe cygwin?) */ 79 | #if defined(__GNUC__) && !defined(_WIN32) 80 | 81 | unsigned long long get_cpu_frequency() 82 | { 83 | unsigned long long x, y; 84 | 85 | struct timespec ts; 86 | ts.tv_sec = 0; 87 | ts.tv_nsec = 500000000; 88 | x = measure_cpu_time(); 89 | nanosleep(&ts, NULL); 90 | y = measure_cpu_time(); 91 | return (y - x) * 2; 92 | } 93 | 94 | #elif defined(_WIN32) 95 | 96 | unsigned LONG_LONG get_cpu_frequency() 97 | { 98 | unsigned LONG_LONG x, y; 99 | unsigned LONG_LONG frequency; 100 | x = measure_cpu_time(); 101 | 102 | /* Use the windows sleep function, not Ruby's */ 103 | Sleep(500); 104 | y = measure_cpu_time(); 105 | frequency = 2*(y-x); 106 | return frequency; 107 | } 108 | #endif 109 | 110 | static double 111 | convert_cpu_time(prof_measure_t c) 112 | { 113 | return (double) c / cpu_frequency; 114 | } 115 | 116 | /* Document-method: prof_measure_cpu_time 117 | call-seq: 118 | measure_cpu_time -> float 119 | 120 | Returns the cpu time.*/ 121 | static VALUE 122 | prof_measure_cpu_time(VALUE self) 123 | { 124 | return rb_float_new(convert_cpu_time(measure_cpu_time())); 125 | } 126 | 127 | /* Document-method: prof_get_cpu_frequency 128 | call-seq: 129 | cpu_frequency -> int 130 | 131 | Returns the cpu's frequency. This value is needed when 132 | RubyProf::measure_mode is set to CPU_TIME. */ 133 | static VALUE 134 | prof_get_cpu_frequency(VALUE self) 135 | { 136 | return ULL2NUM(cpu_frequency); 137 | } 138 | 139 | /* Document-method: prof_set_cpu_frequency 140 | call-seq: 141 | cpu_frequency=value -> void 142 | 143 | Sets the cpu's frequency. This value is needed when 144 | RubyProf::measure_mode is set to CPU_TIME. */ 145 | static VALUE 146 | prof_set_cpu_frequency(VALUE self, VALUE val) 147 | { 148 | cpu_frequency = NUM2LL(val); 149 | return val; 150 | } 151 | 152 | #endif 153 | -------------------------------------------------------------------------------- /patches/1.9.3/preview1/gcdata.patch: -------------------------------------------------------------------------------- 1 | --- a/gc.c (revision 32826) 2 | +++ b/gc.c (working copy) 3 | @@ -309,16 +309,12 @@ 4 | struct gc_list *next; 5 | }; 6 | 7 | -#define CALC_EXACT_MALLOC_SIZE 0 8 | - 9 | typedef struct rb_objspace { 10 | struct { 11 | size_t limit; 12 | size_t increase; 13 | -#if CALC_EXACT_MALLOC_SIZE 14 | size_t allocated_size; 15 | size_t allocations; 16 | -#endif 17 | } malloc_params; 18 | struct { 19 | size_t increment; 20 | @@ -715,10 +711,6 @@ 21 | } 22 | if (size == 0) size = 1; 23 | 24 | -#if CALC_EXACT_MALLOC_SIZE 25 | - size += sizeof(size_t); 26 | -#endif 27 | - 28 | if ((ruby_gc_stress && !ruby_disable_gc_stress) || 29 | (malloc_increase+size) > malloc_limit) { 30 | garbage_collect_with_gvl(objspace); 31 | @@ -731,13 +723,9 @@ 32 | vm_malloc_fixup(rb_objspace_t *objspace, void *mem, size_t size) 33 | { 34 | malloc_increase += size; 35 | - 36 | -#if CALC_EXACT_MALLOC_SIZE 37 | + 38 | objspace->malloc_params.allocated_size += size; 39 | objspace->malloc_params.allocations++; 40 | - ((size_t *)mem)[0] = size; 41 | - mem = (size_t *)mem + 1; 42 | -#endif 43 | 44 | return mem; 45 | } 46 | @@ -776,11 +764,7 @@ 47 | if (ruby_gc_stress && !ruby_disable_gc_stress) 48 | garbage_collect_with_gvl(objspace); 49 | 50 | -#if CALC_EXACT_MALLOC_SIZE 51 | - size += sizeof(size_t); 52 | objspace->malloc_params.allocated_size -= size; 53 | - ptr = (size_t *)ptr - 1; 54 | -#endif 55 | 56 | mem = realloc(ptr, size); 57 | if (!mem) { 58 | @@ -792,12 +776,8 @@ 59 | } 60 | } 61 | malloc_increase += size; 62 | - 63 | -#if CALC_EXACT_MALLOC_SIZE 64 | + 65 | objspace->malloc_params.allocated_size += size; 66 | - ((size_t *)mem)[0] = size; 67 | - mem = (size_t *)mem + 1; 68 | -#endif 69 | 70 | return mem; 71 | } 72 | @@ -805,14 +785,6 @@ 73 | static void 74 | vm_xfree(rb_objspace_t *objspace, void *ptr) 75 | { 76 | -#if CALC_EXACT_MALLOC_SIZE 77 | - size_t size; 78 | - ptr = ((size_t *)ptr) - 1; 79 | - size = ((size_t*)ptr)[0]; 80 | - objspace->malloc_params.allocated_size -= size; 81 | - objspace->malloc_params.allocations--; 82 | -#endif 83 | - 84 | free(ptr); 85 | } 86 | 87 | @@ -3368,7 +3340,6 @@ 88 | } 89 | 90 | 91 | -#if CALC_EXACT_MALLOC_SIZE 92 | /* 93 | * call-seq: 94 | * GC.malloc_allocated_size -> Integer 95 | @@ -3378,8 +3349,8 @@ 96 | * It returns the allocated size by malloc(). 97 | */ 98 | 99 | -static VALUE 100 | -gc_malloc_allocated_size(VALUE self) 101 | +VALUE 102 | +rb_gc_malloc_allocated_size(VALUE self) 103 | { 104 | return UINT2NUM((&rb_objspace)->malloc_params.allocated_size); 105 | } 106 | @@ -3393,12 +3364,11 @@ 107 | * It returns the number of allocated memory object by malloc(). 108 | */ 109 | 110 | -static VALUE 111 | -gc_malloc_allocations(VALUE self) 112 | +VALUE 113 | +rb_gc_malloc_allocations(VALUE self) 114 | { 115 | return UINT2NUM((&rb_objspace)->malloc_params.allocations); 116 | } 117 | -#endif 118 | 119 | static VALUE 120 | gc_profile_record_get(void) 121 | @@ -3583,6 +3553,8 @@ 122 | rb_define_singleton_method(rb_mGC, "start", rb_gc_start, 0); 123 | rb_define_singleton_method(rb_mGC, "enable", rb_gc_enable, 0); 124 | rb_define_singleton_method(rb_mGC, "disable", rb_gc_disable, 0); 125 | + rb_define_singleton_method(rb_mGC, "malloc_allocated_size", rb_gc_malloc_allocated_size, 0); 126 | + rb_define_singleton_method(rb_mGC, "malloc_allocations", rb_gc_malloc_allocations, 0); 127 | rb_define_singleton_method(rb_mGC, "stress", gc_stress_get, 0); 128 | rb_define_singleton_method(rb_mGC, "stress=", gc_stress_set, 1); 129 | rb_define_singleton_method(rb_mGC, "count", gc_count, 0); 130 | @@ -3597,6 +3569,7 @@ 131 | rb_define_singleton_method(rb_mProfiler, "result", gc_profile_result, 0); 132 | rb_define_singleton_method(rb_mProfiler, "report", gc_profile_report, -1); 133 | rb_define_singleton_method(rb_mProfiler, "total_time", gc_profile_total_time, 0); 134 | + rb_define_singleton_method(rb_mProfiler, "data", gc_profile_record_get, 0); 135 | 136 | rb_mObSpace = rb_define_module("ObjectSpace"); 137 | rb_define_module_function(rb_mObSpace, "each_object", os_each_obj, -1); 138 | @@ -3616,9 +3589,4 @@ 139 | rb_define_method(rb_mKernel, "object_id", rb_obj_id, 0); 140 | 141 | rb_define_module_function(rb_mObSpace, "count_objects", count_objects, -1); 142 | - 143 | -#if CALC_EXACT_MALLOC_SIZE 144 | - rb_define_singleton_method(rb_mGC, "malloc_allocated_size", gc_malloc_allocated_size, 0); 145 | - rb_define_singleton_method(rb_mGC, "malloc_allocations", gc_malloc_allocations, 0); 146 | -#endif 147 | } 148 | -------------------------------------------------------------------------------- /patches/1.9.2/p290/gcdata.patch: -------------------------------------------------------------------------------- 1 | Index: gc.c 2 | =================================================================== 3 | --- gc.c (revision 32826) 4 | +++ gc.c (working copy) 5 | @@ -291,16 +291,12 @@ 6 | struct gc_list *next; 7 | }; 8 | 9 | -#define CALC_EXACT_MALLOC_SIZE 0 10 | - 11 | typedef struct rb_objspace { 12 | struct { 13 | size_t limit; 14 | size_t increase; 15 | -#if CALC_EXACT_MALLOC_SIZE 16 | size_t allocated_size; 17 | size_t allocations; 18 | -#endif 19 | } malloc_params; 20 | struct { 21 | size_t increment; 22 | @@ -647,10 +643,6 @@ 23 | } 24 | if (size == 0) size = 1; 25 | 26 | -#if CALC_EXACT_MALLOC_SIZE 27 | - size += sizeof(size_t); 28 | -#endif 29 | - 30 | if ((ruby_gc_stress && !ruby_disable_gc_stress) || 31 | (malloc_increase+size) > malloc_limit) { 32 | garbage_collect_with_gvl(objspace); 33 | @@ -665,13 +657,9 @@ 34 | } 35 | } 36 | malloc_increase += size; 37 | - 38 | -#if CALC_EXACT_MALLOC_SIZE 39 | + 40 | objspace->malloc_params.allocated_size += size; 41 | objspace->malloc_params.allocations++; 42 | - ((size_t *)mem)[0] = size; 43 | - mem = (size_t *)mem + 1; 44 | -#endif 45 | 46 | return mem; 47 | } 48 | @@ -691,12 +679,8 @@ 49 | } 50 | if (ruby_gc_stress && !ruby_disable_gc_stress) 51 | garbage_collect_with_gvl(objspace); 52 | - 53 | -#if CALC_EXACT_MALLOC_SIZE 54 | - size += sizeof(size_t); 55 | + 56 | objspace->malloc_params.allocated_size -= size; 57 | - ptr = (size_t *)ptr - 1; 58 | -#endif 59 | 60 | mem = realloc(ptr, size); 61 | if (!mem) { 62 | @@ -708,27 +692,15 @@ 63 | } 64 | } 65 | malloc_increase += size; 66 | - 67 | -#if CALC_EXACT_MALLOC_SIZE 68 | + 69 | objspace->malloc_params.allocated_size += size; 70 | - ((size_t *)mem)[0] = size; 71 | - mem = (size_t *)mem + 1; 72 | -#endif 73 | - 74 | + 75 | return mem; 76 | } 77 | 78 | static void 79 | vm_xfree(rb_objspace_t *objspace, void *ptr) 80 | { 81 | -#if CALC_EXACT_MALLOC_SIZE 82 | - size_t size; 83 | - ptr = ((size_t *)ptr) - 1; 84 | - size = ((size_t*)ptr)[0]; 85 | - objspace->malloc_params.allocated_size -= size; 86 | - objspace->malloc_params.allocations--; 87 | -#endif 88 | - 89 | free(ptr); 90 | } 91 | 92 | @@ -2998,7 +2970,6 @@ 93 | return UINT2NUM((&rb_objspace)->count); 94 | } 95 | 96 | -#if CALC_EXACT_MALLOC_SIZE 97 | /* 98 | * call-seq: 99 | * GC.malloc_allocated_size -> Integer 100 | @@ -3008,8 +2979,8 @@ 101 | * It returns the allocated size by malloc(). 102 | */ 103 | 104 | -static VALUE 105 | -gc_malloc_allocated_size(VALUE self) 106 | +VALUE 107 | +rb_gc_malloc_allocated_size(VALUE self) 108 | { 109 | return UINT2NUM((&rb_objspace)->malloc_params.allocated_size); 110 | } 111 | @@ -3023,12 +2994,11 @@ 112 | * It returns the number of allocated memory object by malloc(). 113 | */ 114 | 115 | -static VALUE 116 | -gc_malloc_allocations(VALUE self) 117 | +VALUE 118 | +rb_gc_malloc_allocations(VALUE self) 119 | { 120 | return UINT2NUM((&rb_objspace)->malloc_params.allocations); 121 | } 122 | -#endif 123 | 124 | static VALUE 125 | gc_profile_record_get(void) 126 | @@ -3186,6 +3156,8 @@ 127 | rb_define_singleton_method(rb_mGC, "stress", gc_stress_get, 0); 128 | rb_define_singleton_method(rb_mGC, "stress=", gc_stress_set, 1); 129 | rb_define_singleton_method(rb_mGC, "count", gc_count, 0); 130 | + rb_define_singleton_method(rb_mGC, "malloc_allocated_size", rb_gc_malloc_allocated_size, 0); 131 | + rb_define_singleton_method(rb_mGC, "malloc_allocations", rb_gc_malloc_allocations, 0); 132 | rb_define_method(rb_mGC, "garbage_collect", rb_gc_start, 0); 133 | 134 | rb_mProfiler = rb_define_module_under(rb_mGC, "Profiler"); 135 | @@ -3196,6 +3168,7 @@ 136 | rb_define_singleton_method(rb_mProfiler, "result", gc_profile_result, 0); 137 | rb_define_singleton_method(rb_mProfiler, "report", gc_profile_report, -1); 138 | rb_define_singleton_method(rb_mProfiler, "total_time", gc_profile_total_time, 0); 139 | + rb_define_singleton_method(rb_mProfiler, "data", gc_profile_record_get, 0); 140 | 141 | rb_mObSpace = rb_define_module("ObjectSpace"); 142 | rb_define_module_function(rb_mObSpace, "each_object", os_each_obj, -1); 143 | @@ -3215,9 +3188,4 @@ 144 | rb_define_method(rb_mKernel, "object_id", rb_obj_id, 0); 145 | 146 | rb_define_module_function(rb_mObSpace, "count_objects", count_objects, -1); 147 | - 148 | -#if CALC_EXACT_MALLOC_SIZE 149 | - rb_define_singleton_method(rb_mGC, "malloc_allocated_size", gc_malloc_allocated_size, 0); 150 | - rb_define_singleton_method(rb_mGC, "malloc_allocations", gc_malloc_allocations, 0); 151 | -#endif 152 | } 153 | -------------------------------------------------------------------------------- /examples/multi.grind.dat: -------------------------------------------------------------------------------- 1 | events: wall_time 2 | 3 | fl=/Users/skaes/src/ruby-prof/test/prime.rb 4 | fn=Object#make_random_array 5 | 7 7 6 | cfl=/Users/skaes/src/ruby-prof/ruby_runtime 7 | cfn=Array#each_index 8 | calls=1 9 9 | 9 7463 10 | cfl=/Users/skaes/src/ruby-prof/ruby_runtime 11 | cfn=Class#new 12 | calls=1 8 13 | 8 54 14 | cfl=/Users/skaes/src/ruby-prof/ruby_runtime 15 | cfn=Array#each_index 16 | calls=1 9 17 | 9 7256 18 | cfl=/Users/skaes/src/ruby-prof/ruby_runtime 19 | cfn=Class#new 20 | calls=1 8 21 | 8 52 22 | 23 | fl=/Users/skaes/src/ruby-prof/ruby_runtime 24 | fn=Fixnum#% 25 | 0 199945 26 | 27 | fl=/Users/skaes/src/ruby-prof/ruby_runtime 28 | fn=Class#new 29 | 0 6 30 | cfl=/Users/skaes/src/ruby-prof/ruby_runtime 31 | cfn=#allocate 32 | calls=1 8 33 | 8 1 34 | cfl=/Users/skaes/src/ruby-prof/ruby_runtime 35 | cfn=Array#initialize 36 | calls=1 8 37 | 8 48 38 | cfl=/Users/skaes/src/ruby-prof/ruby_runtime 39 | cfn=#allocate 40 | calls=1 8 41 | 8 1 42 | cfl=/Users/skaes/src/ruby-prof/ruby_runtime 43 | cfn=Array#initialize 44 | calls=1 8 45 | 8 50 46 | 47 | fl=/Users/skaes/src/ruby-prof/ruby_runtime 48 | fn=Kernel#rand 49 | 0 3238 50 | 51 | fl=/Users/skaes/src/ruby-prof/ruby_runtime 52 | fn=Fixnum#- 53 | 0 2361 54 | 55 | fl=/Users/skaes/src/ruby-prof/test/printers_test.rb 56 | fn=PrintersTest#setup 57 | 16 5 58 | cfl=/Users/skaes/src/ruby-prof/test/prime.rb 59 | cfn=Object#run_primes 60 | calls=1 16 61 | 16 702856 62 | cfl=/Users/skaes/src/ruby-prof/test/printers_test.rb 63 | cfn=PrintersTest#go 64 | calls=1 17 65 | 17 709509 66 | 67 | fl=/Users/skaes/src/ruby-prof/ruby_runtime 68 | fn=Array#initialize 69 | 0 98 70 | 71 | fl=/Users/skaes/src/ruby-prof/ruby_runtime 72 | fn=Array#each_index 73 | 0 8789 74 | cfl=/Users/skaes/src/ruby-prof/ruby_runtime 75 | cfn=Array#[]= 76 | calls=10000 10 77 | 10 1364 78 | cfl=/Users/skaes/src/ruby-prof/ruby_runtime 79 | cfn=Kernel#rand 80 | calls=10000 10 81 | 10 1602 82 | cfl=/Users/skaes/src/ruby-prof/ruby_runtime 83 | cfn=Array#[]= 84 | calls=10000 10 85 | 10 1328 86 | cfl=/Users/skaes/src/ruby-prof/ruby_runtime 87 | cfn=Kernel#rand 88 | calls=10000 10 89 | 10 1636 90 | 91 | fl=/Users/skaes/src/ruby-prof/test/prime.rb 92 | fn=Object#is_prime 93 | 16 13805 94 | cfl=/Users/skaes/src/ruby-prof/ruby_runtime 95 | cfn=Integer#upto 96 | calls=10000 18 97 | 18 683251 98 | cfl=/Users/skaes/src/ruby-prof/ruby_runtime 99 | cfn=Fixnum#- 100 | calls=10000 18 101 | 18 1157 102 | cfl=/Users/skaes/src/ruby-prof/ruby_runtime 103 | cfn=Integer#upto 104 | calls=10000 18 105 | 18 689970 106 | cfl=/Users/skaes/src/ruby-prof/ruby_runtime 107 | cfn=Fixnum#- 108 | calls=10000 18 109 | 18 1204 110 | 111 | fl=/Users/skaes/src/ruby-prof/test/printers_test.rb 112 | fn=PrintersTest#go 113 | 9 5 114 | cfl=/Users/skaes/src/ruby-prof/test/prime.rb 115 | cfn=Object#run_primes 116 | calls=1 10 117 | 10 709504 118 | 119 | fl=/Users/skaes/src/ruby-prof/test/prime.rb 120 | fn=Object#find_primes 121 | 24 3 122 | cfl=/Users/skaes/src/ruby-prof/ruby_runtime 123 | cfn=Array#select 124 | calls=1 25 125 | 25 695331 126 | cfl=/Users/skaes/src/ruby-prof/ruby_runtime 127 | cfn=Array#select 128 | calls=1 25 129 | 25 702187 130 | 131 | fl=/Users/skaes/src/ruby-prof/ruby_runtime 132 | fn=Array#select 133 | 0 8131 134 | cfl=/Users/skaes/src/ruby-prof/test/prime.rb 135 | cfn=Object#is_prime 136 | calls=10000 26 137 | 26 691245 138 | cfl=/Users/skaes/src/ruby-prof/test/prime.rb 139 | cfn=Object#is_prime 140 | calls=10000 26 141 | 26 698142 142 | 143 | fl=/Users/skaes/src/ruby-prof/ruby_runtime 144 | fn=Integer#upto 145 | 0 1004845 146 | cfl=/Users/skaes/src/ruby-prof/ruby_runtime 147 | cfn=Fixnum#== 148 | calls=766408 19 149 | 19 83149 150 | cfl=/Users/skaes/src/ruby-prof/ruby_runtime 151 | cfn=Fixnum#% 152 | calls=766408 19 153 | 19 99302 154 | cfl=/Users/skaes/src/ruby-prof/ruby_runtime 155 | cfn=Fixnum#== 156 | calls=778318 19 157 | 19 85282 158 | cfl=/Users/skaes/src/ruby-prof/ruby_runtime 159 | cfn=Fixnum#% 160 | calls=778318 19 161 | 19 100643 162 | 163 | fl=/Users/skaes/src/ruby-prof/ruby_runtime 164 | fn=#allocate 165 | 0 2 166 | 167 | fl=/Users/skaes/src/ruby-prof/ruby_runtime 168 | fn=Fixnum#== 169 | 0 168431 170 | 171 | fl=/Users/skaes/src/ruby-prof/test/prime.rb 172 | fn=Object#run_primes 173 | 46 7 174 | cfl=/Users/skaes/src/ruby-prof/test/prime.rb 175 | cfn=Object#find_primes 176 | calls=1 51 177 | 51 695333 178 | cfl=/Users/skaes/src/ruby-prof/test/prime.rb 179 | cfn=Object#make_random_array 180 | calls=1 48 181 | 48 7519 182 | cfl=/Users/skaes/src/ruby-prof/test/prime.rb 183 | cfn=Object#find_primes 184 | calls=1 51 185 | 51 702188 186 | cfl=/Users/skaes/src/ruby-prof/test/prime.rb 187 | cfn=Object#make_random_array 188 | calls=1 48 189 | 48 7313 190 | 191 | fl=/Users/skaes/src/ruby-prof/ruby_runtime 192 | fn=Array#[]= 193 | 0 2692 194 | 195 | -------------------------------------------------------------------------------- /lib/ruby-prof/task.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: utf-8 3 | 4 | require 'rake' 5 | require 'rake/testtask' 6 | require 'fileutils' 7 | 8 | module RubyProf 9 | 10 | # Define a task library for profiling unit tests with ruby-prof. 11 | # 12 | # All of the options provided by 13 | # the Rake:TestTask are supported except the loader 14 | # which is set to ruby-prof. For detailed information 15 | # please refer to the Rake:TestTask documentation. 16 | # 17 | # ruby-prof specific options include: 18 | # 19 | # output_dir - For each file specified an output 20 | # file with profile information will be 21 | # written to the output directory. 22 | # By default, the output directory is 23 | # called "profile" and is created underneath 24 | # the current working directory. 25 | # 26 | # printer - Specifies the output printer. Valid values include 27 | # :flat, :graph, :graph_html and :call_tree. 28 | # 29 | # min_percent - Methods that take less than the specified percent 30 | # will not be written out. 31 | # 32 | # Example: 33 | # 34 | # require 'ruby-prof/task' 35 | # 36 | # RubyProf::ProfileTask.new do |t| 37 | # t.test_files = FileList['test/test*.rb'] 38 | # t.output_dir = "c:/temp" 39 | # t.printer = :graph 40 | # t.min_percent = 10 41 | # end 42 | # 43 | # If rake is invoked with a "TEST=filename" command line option, 44 | # then the list of test files will be overridden to include only the 45 | # filename specified on the command line. This provides an easy way 46 | # to run just one test. 47 | # 48 | # If rake is invoked with a "TESTOPTS=options" command line option, 49 | # then the given options are passed to the test process after a 50 | # '--'. This allows Test::Unit options to be passed to the test 51 | # suite. 52 | # 53 | # Examples: 54 | # 55 | # rake profile # run tests normally 56 | # rake profile TEST=just_one_file.rb # run just one test file. 57 | # rake profile TESTOPTS="-v" # run in verbose mode 58 | # rake profile TESTOPTS="--runner=fox" # use the fox test runner 59 | 60 | class ProfileTask < Rake::TestTask 61 | attr_accessor :output_dir 62 | attr_accessor :min_percent 63 | attr_accessor :printer 64 | 65 | def initialize(name = :profile) 66 | super(name) 67 | end 68 | 69 | # Create the tasks defined by this task lib. 70 | def define 71 | lib_path = @libs.join(File::PATH_SEPARATOR) 72 | desc "Profile" + (@name==:profile ? "" : " for #{@name}") 73 | 74 | task @name do 75 | create_output_directory 76 | 77 | @ruby_opts.unshift( "-I#{lib_path}" ) 78 | @ruby_opts.unshift( "-w" ) if @warning 79 | @ruby_opts.push("-S ruby-prof") 80 | @ruby_opts.push("--printer #{@printer}") 81 | @ruby_opts.push("--min_percent #{@min_percent}") 82 | 83 | file_list.each do |file_path| 84 | run_script(file_path) 85 | end 86 | end 87 | self 88 | end 89 | 90 | # Run script 91 | def run_script(script_path) 92 | run_code = '' 93 | RakeFileUtils.verbose(@verbose) do 94 | file_name = File.basename(script_path, File.extname(script_path)) 95 | case @printer 96 | when :flat, :graph, :call_tree 97 | file_name += ".txt" 98 | when :graph_html 99 | file_name += ".html" 100 | else 101 | file_name += ".txt" 102 | end 103 | 104 | output_file_path = File.join(output_directory, file_name) 105 | 106 | command_line = @ruby_opts.join(" ") + 107 | " --file=" + output_file_path + 108 | " " + script_path 109 | 110 | puts "ruby " + command_line 111 | # We have to catch the exeption to continue on. However, 112 | # the error message will have been output to STDERR 113 | # already by the time we get here so we don't have to 114 | # do that again 115 | begin 116 | ruby command_line 117 | rescue => e 118 | STDOUT << e << "\n" 119 | STDOUT.flush 120 | end 121 | puts "" 122 | puts "" 123 | end 124 | end 125 | 126 | def output_directory 127 | File.expand_path(@output_dir) 128 | end 129 | 130 | def create_output_directory 131 | if not File.exist?(output_directory) 132 | Dir.mkdir(output_directory) 133 | end 134 | end 135 | 136 | def clean_output_directory 137 | if File.exist?(output_directory) 138 | files = Dir.glob(output_directory + '/*') 139 | FileUtils.rm(files) 140 | end 141 | end 142 | 143 | def option_list # :nodoc: 144 | ENV['OPTIONS'] || @options.join(" ") || "" 145 | end 146 | end 147 | end 148 | -------------------------------------------------------------------------------- /test/stack_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | require './test_helper' 5 | 6 | # Test data 7 | # A 8 | # / \ 9 | # B C 10 | # \ 11 | # B 12 | 13 | class StackClass 14 | def a 15 | sleep 1 16 | b 17 | c 18 | end 19 | 20 | def b 21 | sleep 2 22 | end 23 | 24 | def c 25 | sleep 3 26 | b 27 | end 28 | end 29 | 30 | class StackTest < Test::Unit::TestCase 31 | def setup 32 | # Need to use wall time for this test due to the sleep calls 33 | RubyProf::measure_mode = RubyProf::WALL_TIME 34 | end 35 | 36 | def test_call_sequence 37 | c = StackClass.new 38 | result = RubyProf.profile do 39 | c.a 40 | end 41 | 42 | # Length should be 5: 43 | # StackTest#test_call_sequence 44 | # StackClass#a 45 | # Kernel#sleep 46 | # StackClass#c 47 | # StackClass#b 48 | 49 | methods = result.threads.values.first.sort.reverse 50 | assert_equal(5, methods.length) 51 | 52 | # Check StackTest#test_call_sequence 53 | method = methods[0] 54 | assert_equal('StackTest#test_call_sequence', method.full_name) 55 | assert_equal(1, method.called) 56 | assert_in_delta(8, method.total_time, 0.25) 57 | assert_in_delta(0, method.wait_time, 0.01) 58 | assert_in_delta(0, method.self_time, 0.01) 59 | assert_in_delta(8, method.children_time, 0.25) 60 | assert_equal(1, method.call_infos.length) 61 | 62 | call_info = method.call_infos[0] 63 | assert_equal('StackTest#test_call_sequence', call_info.call_sequence) 64 | assert_equal(1, call_info.children.length) 65 | 66 | # Check StackClass#a 67 | method = methods[1] 68 | assert_equal('StackClass#a', method.full_name) 69 | assert_equal(1, method.called) 70 | assert_in_delta(8, method.total_time, 0.15) 71 | assert_in_delta(0, method.wait_time, 0.01) 72 | assert_in_delta(0, method.self_time, 0.01) 73 | assert_in_delta(8, method.children_time, 0.05) 74 | assert_equal(1, method.call_infos.length) 75 | 76 | call_info = method.call_infos[0] 77 | assert_equal('StackTest#test_call_sequence->StackClass#a', call_info.call_sequence) 78 | assert_equal(3, call_info.children.length) 79 | 80 | # Check Kernel#sleep 81 | method = methods[2] 82 | assert_equal('Kernel#sleep', method.full_name) 83 | assert_equal(4, method.called) 84 | assert_in_delta(8, method.total_time, 0.05) 85 | assert_in_delta(0, method.wait_time, 0.01) 86 | assert_in_delta(8, method.self_time, 0.05) 87 | assert_in_delta(0, method.children_time, 0.05) 88 | assert_equal(4, method.call_infos.length) 89 | 90 | call_info = method.call_infos[0] 91 | assert_equal('StackTest#test_call_sequence->StackClass#a->Kernel#sleep', call_info.call_sequence) 92 | assert_equal(0, call_info.children.length) 93 | 94 | call_info = method.call_infos[1] 95 | assert_equal('StackTest#test_call_sequence->StackClass#a->StackClass#b->Kernel#sleep', call_info.call_sequence) 96 | assert_equal(0, call_info.children.length) 97 | 98 | call_info = method.call_infos[2] 99 | assert_equal('StackTest#test_call_sequence->StackClass#a->StackClass#c->Kernel#sleep', call_info.call_sequence) 100 | assert_equal(0, call_info.children.length) 101 | 102 | call_info = method.call_infos[3] 103 | assert_equal('StackTest#test_call_sequence->StackClass#a->StackClass#c->StackClass#b->Kernel#sleep', call_info.call_sequence) 104 | assert_equal(0, call_info.children.length) 105 | 106 | # Check StackClass#c 107 | method = methods[3] 108 | assert_equal('StackClass#c', method.full_name) 109 | assert_equal(1, method.called) 110 | assert_in_delta(5, method.total_time, 0.05) 111 | assert_in_delta(0, method.wait_time, 0.01) 112 | assert_in_delta(0, method.self_time, 0.01) 113 | assert_in_delta(5, method.children_time, 0.05) 114 | assert_equal(1, method.call_infos.length) 115 | 116 | call_info = method.call_infos[0] 117 | assert_equal('StackTest#test_call_sequence->StackClass#a->StackClass#c', call_info.call_sequence) 118 | assert_equal(2, call_info.children.length) 119 | 120 | # Check StackClass#b 121 | method = methods[4] 122 | assert_equal('StackClass#b', method.full_name) 123 | assert_equal(2, method.called) 124 | assert_in_delta(4, method.total_time, 0.05) 125 | assert_in_delta(0, method.wait_time, 0.01) 126 | assert_in_delta(0, method.self_time, 0.01) 127 | assert_in_delta(4, method.children_time, 0.05) 128 | assert_equal(2, method.call_infos.length) 129 | 130 | call_info = method.call_infos[0] 131 | assert_equal('StackTest#test_call_sequence->StackClass#a->StackClass#b', call_info.call_sequence) 132 | assert_equal(1, call_info.children.length) 133 | 134 | call_info = method.call_infos[1] 135 | assert_equal('StackTest#test_call_sequence->StackClass#a->StackClass#c->StackClass#b', call_info.call_sequence) 136 | assert_equal(1, call_info.children.length) 137 | end 138 | end 139 | -------------------------------------------------------------------------------- /lib/ruby-prof/dot_printer.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'set' 4 | 5 | module RubyProf 6 | # Generates a graphviz graph in dot format. 7 | # To use the dot printer: 8 | # 9 | # result = RubyProf.profile do 10 | # [code to profile] 11 | # end 12 | # 13 | # printer = RubyProf::DotPrinter.new(result) 14 | # printer.print(STDOUT) 15 | # 16 | # You can use either dot viewer such as GraphViz, or the dot command line tool 17 | # to reformat the output into a wide variety of outputs: 18 | # 19 | # dot -Tpng graph.dot > graph.png 20 | # 21 | class DotPrinter < RubyProf::AbstractPrinter 22 | CLASS_COLOR = '"#666666"' 23 | EDGE_COLOR = '"#666666"' 24 | 25 | # Creates the DotPrinter using a RubyProf::Result. 26 | def initialize(result) 27 | super(result) 28 | @seen_methods = Set.new 29 | end 30 | 31 | # Print a graph report to the provided output. 32 | # 33 | # output - Any IO object, including STDOUT or a file. The default value is 34 | # STDOUT. 35 | # 36 | # options - Hash of print options. See #setup_options 37 | # for more information. 38 | # 39 | # When profiling results that cover a large number of method calls it 40 | # helps to use the :min_percent option, for example: 41 | # 42 | # DotPrinter.new(result).print(STDOUT, :min_percent=>5) 43 | # 44 | def print(output = STDOUT, options = {}) 45 | @output = output 46 | setup_options(options) 47 | 48 | total_time = thread_times.values.inject{|a,b| a+b} 49 | 50 | puts 'digraph "Profile" {' 51 | puts "label=\"#{mode_name} >=#{min_percent}%\\nTotal: #{total_time}\";" 52 | puts "labelloc=t;" 53 | puts "labeljust=l;" 54 | print_threads 55 | puts '}' 56 | end 57 | 58 | private 59 | 60 | # Something of a hack, figure out which constant went with the 61 | # RubyProf.measure_mode so that we can display it. Otherwise it's easy to 62 | # forget what measurement was made. 63 | def mode_name 64 | mode = RubyProf.constants.find{|c| RubyProf.const_get(c) == RubyProf.measure_mode} 65 | end 66 | 67 | # Computes the total time per thread: 68 | def thread_times 69 | @thread_times ||= begin 70 | times = {} 71 | @result.threads.each do |thread_id, methods| 72 | toplevel = methods.sort.last 73 | 74 | total_time = toplevel.total_time 75 | # This looks like a hack for very small times... from GraphPrinter 76 | total_time = 0.01 if total_time == 0 77 | times[thread_id] = total_time 78 | end 79 | 80 | times 81 | end 82 | end 83 | 84 | def print_threads 85 | # sort assumes that spawned threads have higher object_ids 86 | @result.threads.sort.each do |thread_id, methods| 87 | puts "subgraph \"Thread #{thread_id}\" {" 88 | 89 | print_methods(thread_id, methods) 90 | puts "}" 91 | 92 | print_classes(thread_id, methods) 93 | end 94 | end 95 | 96 | # Determines an ID to use to represent the subject in the Dot file. 97 | def dot_id(subject) 98 | subject.object_id 99 | end 100 | 101 | def print_methods(thread_id, methods) 102 | total_time = thread_times[thread_id] 103 | methods.sort_by(&sort_method).reverse_each do |method| 104 | total_percentage = (method.total_time/total_time) * 100 105 | 106 | next if total_percentage < min_percent 107 | name = method_name(method).split("#").last 108 | puts "#{dot_id(method)} [label=\"#{name}\\n(#{total_percentage.round}%)\"];" 109 | @seen_methods << method 110 | print_edges(total_time, method) 111 | end 112 | end 113 | 114 | def print_classes(thread_id, methods) 115 | methods.group_by{|m| m.klass_name}.each do |cls, methods| 116 | # Filter down to just seen methods 117 | big_methods, small_methods = methods.partition{|m| @seen_methods.include? m} 118 | 119 | if !big_methods.empty? 120 | puts "subgraph cluster_#{cls.object_id} {" 121 | puts "label = \"#{cls}\";" 122 | puts "fontcolor = #{CLASS_COLOR};" 123 | puts "fontsize = 16;" 124 | puts "color = #{CLASS_COLOR};" 125 | big_methods.each do |m| 126 | puts "#{m.object_id};" 127 | end 128 | puts "}" 129 | end 130 | end 131 | end 132 | 133 | def print_edges(total_time, method) 134 | method.aggregate_children.sort_by(&:total_time).reverse.each do |child| 135 | 136 | target_percentage = (child.target.total_time / total_time) * 100.0 137 | next if target_percentage < min_percent 138 | 139 | # Get children method 140 | puts "#{dot_id(method)} -> #{dot_id(child.target)} [label=\"#{child.called}/#{child.target.called}\" fontsize=10 fontcolor=#{EDGE_COLOR}];" 141 | end 142 | end 143 | 144 | # Silly little helper for printing to the @output 145 | def puts(str) 146 | @output.puts(str) 147 | end 148 | 149 | end 150 | end 151 | -------------------------------------------------------------------------------- /lib/ruby-prof/graph_printer.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module RubyProf 4 | # Generates graph[link:files/examples/graph_txt.html] profile reports as text. 5 | # To use the graph printer: 6 | # 7 | # result = RubyProf.profile do 8 | # [code to profile] 9 | # end 10 | # 11 | # printer = RubyProf::GraphPrinter.new(result, 5) 12 | # printer.print(STDOUT, {}) 13 | # 14 | # The constructor takes two arguments. See the README 15 | 16 | class GraphPrinter < AbstractPrinter 17 | PERCENTAGE_WIDTH = 8 18 | TIME_WIDTH = 10 19 | CALL_WIDTH = 17 20 | 21 | # Create a GraphPrinter. Result is a RubyProf::Result 22 | # object generated from a profiling run. 23 | def initialize(result) 24 | super(result) 25 | @thread_times = Hash.new 26 | calculate_thread_times 27 | end 28 | 29 | def calculate_thread_times 30 | # Cache thread times since this is an expensive 31 | # operation with the required sorting 32 | @result.threads.each do |thread_id, methods| 33 | top = methods.max 34 | 35 | thread_time = [top.total_time, 0.01].max 36 | 37 | @thread_times[thread_id] = thread_time 38 | end 39 | end 40 | 41 | # Print a graph report to the provided output. 42 | # 43 | # output - Any IO oject, including STDOUT or a file. 44 | # The default value is STDOUT. 45 | # 46 | # options - Hash of print options. See #setup_options 47 | # for more information. 48 | # 49 | def print(output = STDOUT, options = {}) 50 | @output = output 51 | setup_options(options) 52 | print_threads 53 | end 54 | 55 | private 56 | def print_threads 57 | # sort assumes that spawned threads have higher object_ids 58 | @result.threads.sort.each do |thread_id, methods| 59 | print_methods(thread_id, methods) 60 | @output << "\n" * 2 61 | end 62 | end 63 | 64 | def print_methods(thread_id, methods) 65 | # Sort methods from longest to shortest total time 66 | methods = methods.sort_by(&sort_method) 67 | 68 | toplevel = methods.last 69 | total_time = toplevel.total_time 70 | if total_time == 0 71 | total_time = 0.01 72 | end 73 | 74 | print_heading(thread_id) 75 | 76 | # Print each method in total time order 77 | methods.reverse_each do |method| 78 | total_percentage = (method.total_time/total_time) * 100 79 | self_percentage = (method.self_time/total_time) * 100 80 | 81 | next if total_percentage < min_percent 82 | 83 | @output << "-" * 80 << "\n" 84 | 85 | print_parents(thread_id, method) 86 | 87 | # 1 is for % sign 88 | @output << sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", total_percentage) 89 | @output << sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", self_percentage) 90 | @output << sprintf("%#{TIME_WIDTH}.2f", method.total_time) 91 | @output << sprintf("%#{TIME_WIDTH}.2f", method.self_time) 92 | @output << sprintf("%#{TIME_WIDTH}.2f", method.wait_time) 93 | @output << sprintf("%#{TIME_WIDTH}.2f", method.children_time) 94 | @output << sprintf("%#{CALL_WIDTH}i", method.called) 95 | @output << sprintf(" %s", method_name(method)) 96 | if print_file 97 | @output << sprintf(" %s:%s", method.source_file, method.line) 98 | end 99 | @output << "\n" 100 | 101 | print_children(method) 102 | end 103 | end 104 | 105 | def print_heading(thread_id) 106 | @output << "Thread ID: #{thread_id}\n" 107 | @output << "Total Time: #{@thread_times[thread_id]}\n" 108 | @output << "\n" 109 | 110 | # 1 is for % sign 111 | @output << sprintf("%#{PERCENTAGE_WIDTH}s", "%total") 112 | @output << sprintf("%#{PERCENTAGE_WIDTH}s", "%self") 113 | @output << sprintf("%#{TIME_WIDTH}s", "total") 114 | @output << sprintf("%#{TIME_WIDTH}s", "self") 115 | @output << sprintf("%#{TIME_WIDTH}s", "wait") 116 | @output << sprintf("%#{TIME_WIDTH}s", "child") 117 | @output << sprintf("%#{CALL_WIDTH}s", "calls") 118 | @output << " Name" 119 | @output << "\n" 120 | end 121 | 122 | def print_parents(thread_id, method) 123 | method.aggregate_parents.sort_by(&:total_time).each do |caller| 124 | next unless caller.parent 125 | @output << " " * 2 * PERCENTAGE_WIDTH 126 | @output << sprintf("%#{TIME_WIDTH}.2f", caller.total_time) 127 | @output << sprintf("%#{TIME_WIDTH}.2f", caller.self_time) 128 | @output << sprintf("%#{TIME_WIDTH}.2f", caller.wait_time) 129 | @output << sprintf("%#{TIME_WIDTH}.2f", caller.children_time) 130 | 131 | call_called = "#{caller.called}/#{method.called}" 132 | @output << sprintf("%#{CALL_WIDTH}s", call_called) 133 | @output << sprintf(" %s", caller.parent.target.full_name) 134 | @output << "\n" 135 | end 136 | end 137 | 138 | def print_children(method) 139 | method.aggregate_children.sort_by(&:total_time).reverse.each do |child| 140 | # Get children method 141 | 142 | @output << " " * 2 * PERCENTAGE_WIDTH 143 | 144 | @output << sprintf("%#{TIME_WIDTH}.2f", child.total_time) 145 | @output << sprintf("%#{TIME_WIDTH}.2f", child.self_time) 146 | @output << sprintf("%#{TIME_WIDTH}.2f", child.wait_time) 147 | @output << sprintf("%#{TIME_WIDTH}.2f", child.children_time) 148 | 149 | call_called = "#{child.called}/#{child.target.called}" 150 | @output << sprintf("%#{CALL_WIDTH}s", call_called) 151 | @output << sprintf(" %s", child.target.full_name) 152 | @output << "\n" 153 | end 154 | end 155 | end 156 | end 157 | 158 | -------------------------------------------------------------------------------- /test/unique_call_path_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | require './test_helper' 5 | 6 | class UniqueCallPath 7 | def method_a(i) 8 | if i==1 9 | method_b 10 | else 11 | method_c 12 | end 13 | end 14 | 15 | def method_b 16 | method_c 17 | end 18 | 19 | def method_c 20 | c = 3 21 | end 22 | 23 | def method_k(i) 24 | method_a(i) 25 | end 26 | end 27 | 28 | 29 | # -- Tests ---- 30 | class UniqueCallPathTest < Test::Unit::TestCase 31 | def test_root_method 32 | unique_call_path = UniqueCallPath.new 33 | 34 | result = RubyProf.profile do 35 | unique_call_path.method_a(1) 36 | end 37 | 38 | root_methods = Array.new 39 | result.threads.each do | thread_id, methods | 40 | methods.each do | m | 41 | if m.root? 42 | root_methods.push(m) 43 | end 44 | end 45 | end 46 | 47 | assert_equal(1, root_methods.length) 48 | assert_equal("UniqueCallPathTest#test_root_method", root_methods[0].full_name) 49 | end 50 | 51 | def test_root_children 52 | unique_call_path = UniqueCallPath.new 53 | 54 | result = RubyProf.profile do 55 | unique_call_path.method_a(1) 56 | unique_call_path.method_k(2) 57 | end 58 | 59 | root_methods = Array.new 60 | result.threads.each do | thread_id, methods | 61 | methods.each do | m | 62 | if m.root? 63 | root_methods.push(m) 64 | end 65 | end 66 | end 67 | 68 | assert_equal(1, root_methods.length) 69 | 70 | root_children = Array.new 71 | root_methods[0].children.each do | c | 72 | if c.parent.target.eql?(root_methods[0]) 73 | root_children.push(c) 74 | end 75 | end 76 | 77 | children = root_children.sort do |c1, c2| 78 | c1.target.full_name <=> c2.target.full_name 79 | end 80 | 81 | assert_equal(2, children.length) 82 | assert_equal("UniqueCallPath#method_a", children[0].target.full_name) 83 | assert_equal("UniqueCallPath#method_k", children[1].target.full_name) 84 | end 85 | 86 | def test_children_of 87 | unique_call_path = UniqueCallPath.new 88 | 89 | result = RubyProf.profile do 90 | unique_call_path.method_a(1) 91 | unique_call_path.method_k(2) 92 | end 93 | 94 | root_methods = Array.new 95 | result.threads.each do | thread_id, methods | 96 | methods.each do | m | 97 | if m.root? 98 | root_methods.push(m) 99 | end 100 | end 101 | end 102 | 103 | assert_equal(1, root_methods.length) 104 | method = root_methods[0] 105 | assert_equal('UniqueCallPathTest#test_children_of', method.full_name) 106 | 107 | call_info_a = nil 108 | root_methods[0].children.each do | c | 109 | if c.target.full_name == "UniqueCallPath#method_a" 110 | call_info_a = c 111 | break 112 | end 113 | end 114 | 115 | assert !call_info_a.nil? 116 | 117 | children_of_a = Array.new 118 | 119 | call_info_a.children.each do | c | 120 | if c.parent.eql?(call_info_a) 121 | children_of_a.push(c) 122 | end 123 | end 124 | 125 | if RUBY_VERSION < '1.9' 126 | assert_equal(4, call_info_a.target.children.length) 127 | else 128 | assert_equal(2, call_info_a.target.children.length) 129 | end 130 | 131 | children_of_a = children_of_a.sort do |c1, c2| 132 | c1.target.full_name <=> c2.target.full_name 133 | end 134 | if RUBY_VERSION < '1.9' 135 | assert_equal(2, children_of_a.length) 136 | assert_equal("Fixnum#==", children_of_a[0].target.full_name) 137 | assert_equal("UniqueCallPath#method_b", children_of_a[1].target.full_name) 138 | else 139 | assert_equal(1, children_of_a.length) 140 | assert_equal("UniqueCallPath#method_b", children_of_a[0].target.full_name) 141 | end 142 | 143 | end 144 | 145 | def test_id2ref 146 | unique_call_path = UniqueCallPath.new 147 | 148 | result = RubyProf.profile do 149 | unique_call_path.method_a(1) 150 | end 151 | 152 | root_methods = Array.new 153 | result.threads.each do | thread_id, methods | 154 | methods.each do | m | 155 | if m.root? 156 | root_methods.push(m) 157 | end 158 | end 159 | end 160 | 161 | child = root_methods[0].children[0] 162 | 163 | assert_not_equal(0, child.object_id) 164 | #assert_equal(RubyProf::CallInfo.id2ref(child.id).target.full_name, child.target.full_name) 165 | end 166 | 167 | def test_unique_path 168 | unique_call_path = UniqueCallPath.new 169 | 170 | result = RubyProf.profile do 171 | unique_call_path.method_a(1) 172 | unique_call_path.method_k(1) 173 | end 174 | 175 | root_methods = Array.new 176 | result.threads.each do | thread_id, methods | 177 | methods.each do | m | 178 | if m.root? 179 | root_methods.push(m) 180 | end 181 | end 182 | end 183 | 184 | assert_equal(1, root_methods.length) 185 | 186 | call_info_a = nil 187 | root_methods[0].children.each do | c | 188 | if c.target.full_name == "UniqueCallPath#method_a" 189 | call_info_a = c 190 | break 191 | end 192 | end 193 | 194 | assert !call_info_a.nil? 195 | 196 | children_of_a = Array.new 197 | call_info_a.children.each do |c| 198 | if c.parent.eql?(call_info_a) 199 | children_of_a.push(c) 200 | end 201 | end 202 | 203 | if RUBY_VERSION < '1.9' 204 | assert_equal(4, call_info_a.target.children.length) 205 | else 206 | assert_equal(2, call_info_a.target.children.length) 207 | end 208 | 209 | children_of_a = children_of_a.sort do |c1, c2| 210 | c1.target.full_name <=> c2.target.full_name 211 | end 212 | 213 | if RUBY_VERSION < '1.9' 214 | assert_equal(2, children_of_a.length) 215 | assert_equal(1, children_of_a[0].called) 216 | assert_equal("Fixnum#==", children_of_a[0].target.full_name) 217 | assert_equal(1, children_of_a[1].called) 218 | assert_equal("UniqueCallPath#method_b", children_of_a[1].target.full_name) 219 | else 220 | assert_equal(1, children_of_a.length) 221 | assert_equal(1, children_of_a[0].called) 222 | assert_equal("UniqueCallPath#method_b", children_of_a[0].target.full_name) 223 | end 224 | end 225 | end 226 | -------------------------------------------------------------------------------- /ext/ruby_prof/vc/ruby_prof.vcproj: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 26 | 29 | 32 | 35 | 38 | 41 | 55 | 58 | 61 | 64 | 76 | 79 | 82 | 85 | 88 | 91 | 94 | 97 | 98 | 106 | 109 | 112 | 115 | 118 | 121 | 131 | 134 | 137 | 140 | 154 | 157 | 160 | 163 | 166 | 169 | 172 | 175 | 176 | 177 | 178 | 179 | 180 | 185 | 188 | 189 | 190 | 195 | 198 | 199 | 202 | 203 | 206 | 207 | 210 | 211 | 214 | 215 | 218 | 219 | 222 | 223 | 226 | 227 | 230 | 231 | 232 | 237 | 238 | 239 | 240 | 241 | 242 | -------------------------------------------------------------------------------- /test/thread_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | require './test_helper' 5 | require 'timeout' 6 | 7 | # -- Tests ---- 8 | class ThreadTest < Test::Unit::TestCase 9 | def setup 10 | # Need to use wall time for this test due to the sleep calls 11 | RubyProf::measure_mode = RubyProf::WALL_TIME 12 | end 13 | 14 | def test_thread_count 15 | RubyProf.start 16 | 17 | thread = Thread.new do 18 | sleep(1) 19 | end 20 | 21 | thread.join 22 | result = RubyProf.stop 23 | assert_equal(2, result.threads.keys.length) # this should pass... 24 | end 25 | 26 | def test_thread_identity 27 | RubyProf.start 28 | thread = Thread.new do 29 | sleep(1) 30 | end 31 | thread.join 32 | result = RubyProf.stop 33 | 34 | thread_ids = result.threads.keys.sort 35 | threads = [Thread.current, thread] 36 | assert_equal(2, thread_ids.length) # should pass 37 | 38 | assert(thread_ids.include?(threads[0].object_id)) 39 | assert(thread_ids.include?(threads[1].object_id)) 40 | 41 | assert_instance_of(Thread, ObjectSpace._id2ref(thread_ids[0])) 42 | assert(threads.include?(ObjectSpace._id2ref(thread_ids[0]))) 43 | 44 | assert_instance_of(Thread, ObjectSpace._id2ref(thread_ids[1])) 45 | assert(threads.include?(ObjectSpace._id2ref(thread_ids[1]))) 46 | end 47 | 48 | def test_thread_timings 49 | RubyProf.start 50 | thread = Thread.new do 51 | sleep 0 # force it to hit thread.join, below, first 52 | # thus forcing sleep(1), below, to be counted as (wall) self_time 53 | # since we currently count time "in some other thread" as self.wait_time 54 | # for whatever reason 55 | sleep(1) 56 | end 57 | thread.join 58 | result = RubyProf.stop 59 | 60 | # Check background thread 61 | assert_equal(2, result.threads.length) 62 | methods = result.threads[thread.object_id].sort.reverse 63 | assert_equal(2, methods.length) 64 | 65 | method = methods[0] 66 | assert_equal('ThreadTest#test_thread_timings', method.full_name) 67 | assert_equal(1, method.called) 68 | assert_in_delta(1, method.total_time, 0.05) 69 | assert_in_delta(0, method.self_time, 0.01) 70 | assert_in_delta(0, method.wait_time, 0.01) 71 | assert_in_delta(1, method.children_time, 0.01) 72 | assert_equal(1, method.call_infos.length) 73 | call_info = method.call_infos[0] 74 | assert_equal('ThreadTest#test_thread_timings', call_info.call_sequence) 75 | assert_equal(1, call_info.children.length) 76 | 77 | method = methods[1] 78 | assert_equal('Kernel#sleep', method.full_name) 79 | assert_equal(2, method.called) 80 | assert_in_delta(1, method.total_time, 0.01) 81 | assert_in_delta(1.0, method.self_time, 0.01) 82 | assert_in_delta(0, method.wait_time, 0.01) 83 | assert_in_delta(0, method.children_time, 0.01) 84 | 85 | assert_equal(1, method.call_infos.length) 86 | call_info = method.call_infos[0] 87 | assert_equal('ThreadTest#test_thread_timings->Kernel#sleep', call_info.call_sequence) 88 | assert_equal(0, call_info.children.length) 89 | 90 | # Check foreground thread 91 | methods = result.threads[Thread.current.object_id].sort.reverse 92 | assert_equal(4, methods.length) 93 | methods = methods.sort.reverse 94 | 95 | method = methods[0] 96 | assert_equal('ThreadTest#test_thread_timings', method.full_name) 97 | # the sub calls to Object#new, when popped, 98 | # cause the parent frame to be created for method #test_thread_timings, which means a +1 when it's popped in the end 99 | # xxxx a test that shows it the other way, too (never creates parent frame--if that's even possible) 100 | assert_equal(1, method.called) 101 | assert_in_delta(1, method.total_time, 0.01) 102 | assert_in_delta(0, method.self_time, 0.05) 103 | assert_in_delta(0, method.wait_time, 0.05) 104 | assert_in_delta(1, method.children_time, 0.01) 105 | 106 | assert_equal(1, method.call_infos.length) 107 | call_info = method.call_infos[0] 108 | assert_equal('ThreadTest#test_thread_timings', call_info.call_sequence) 109 | assert_equal(2, call_info.children.length) 110 | 111 | method = methods[1] 112 | assert_equal('Thread#join', method.full_name) 113 | assert_equal(1, method.called) 114 | assert_in_delta(1, method.total_time, 0.01) 115 | assert_in_delta(0, method.self_time, 0.01) 116 | assert_in_delta(1.0, method.wait_time, 0.01) 117 | assert_in_delta(0, method.children_time, 0.01) 118 | 119 | assert_equal(1, method.call_infos.length) 120 | call_info = method.call_infos[0] 121 | assert_equal('ThreadTest#test_thread_timings->Thread#join', call_info.call_sequence) 122 | assert_equal(0, call_info.children.length) 123 | 124 | method = methods[2] 125 | assert_equal('#new', method.full_name) 126 | assert_equal(1, method.called) 127 | assert_in_delta(0, method.total_time, 0.01) 128 | assert_in_delta(0, method.self_time, 0.01) 129 | assert_in_delta(0, method.wait_time, 0.01) 130 | assert_in_delta(0, method.children_time, 0.01) 131 | 132 | assert_equal(1, method.call_infos.length) 133 | call_info = method.call_infos[0] 134 | assert_equal('ThreadTest#test_thread_timings->#new', call_info.call_sequence) 135 | assert_equal(1, call_info.children.length) 136 | 137 | method = methods[3] 138 | assert_equal('Thread#initialize', method.full_name) 139 | assert_equal(1, method.called) 140 | assert_in_delta(0, method.total_time, 0.01) 141 | assert_in_delta(0, method.self_time, 0.01) 142 | assert_in_delta(0, method.wait_time, 0.01) 143 | assert_in_delta(0, method.children_time, 0.01) 144 | 145 | assert_equal(1, method.call_infos.length) 146 | call_info = method.call_infos[0] 147 | assert_equal('ThreadTest#test_thread_timings->#new->Thread#initialize', call_info.call_sequence) 148 | assert_equal(0, call_info.children.length) 149 | end 150 | 151 | # useless test 152 | def test_thread_back_and_forth 153 | result = RubyProf.profile do 154 | a = Thread.new { 100_000.times { sleep 0 }} 155 | b = Thread.new { 100_000.times { sleep 0 }} 156 | a.join 157 | b.join 158 | end 159 | assert result.threads.values.flatten.sort[-1].total_time < 10 # 10s. Amazingly, this can fail in OS X at times. Amazing. 160 | end 161 | 162 | def test_thread 163 | result = RubyProf.profile do 164 | begin 165 | status = Timeout::timeout(2) do 166 | while true 167 | next 168 | end 169 | end 170 | rescue Timeout::Error 171 | end 172 | end 173 | end 174 | end 175 | -------------------------------------------------------------------------------- /ext/ruby_prof/vc/ruby_prof.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | {DDB3E992-BF4B-4413-B061-288E40AECAD3} 15 | rubyprof 16 | Win32Proj 17 | 18 | 19 | 20 | DynamicLibrary 21 | Unicode 22 | true 23 | 24 | 25 | DynamicLibrary 26 | Unicode 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | <_ProjectFileVersion>10.0.30128.1 40 | C:\MinGW\local\src\ruby-prof\lib 41 | $(Configuration)\ 42 | $(SolutionDir)$(Configuration)\ 43 | $(Configuration)\ 44 | false 45 | .so 46 | 47 | 48 | 49 | Disabled 50 | C:\MinGW\local\ruby193vc\include\ruby-1.9.1;C:\MinGW\local\ruby193vc\include\ruby-1.9.1\i386-mswin32_100;%(AdditionalIncludeDirectories) 51 | WIN32;_DEBUG;_WINDOWS;_USRDLL;RUBYPROF_EXPORTS;%(PreprocessorDefinitions) 52 | true 53 | EnableFastChecks 54 | MultiThreadedDebugDLL 55 | 56 | 57 | 58 | 59 | Level3 60 | ProgramDatabase 61 | 62 | 63 | msvcr100-ruby191.lib;%(AdditionalDependencies) 64 | $(OutDir)$(ProjectName)$(TargetExt) 65 | C:\MinGW\local\ruby193vc\lib;%(AdditionalLibraryDirectories) 66 | true 67 | Windows 68 | false 69 | 70 | 71 | MachineX86 72 | ruby_prof.def 73 | 74 | 75 | 76 | 77 | C:\Development\ruby\lib\ruby\1.8\i386-mswin32;%(AdditionalIncludeDirectories) 78 | WIN32;NDEBUG;_WINDOWS;_USRDLL;RUBYPROF_EXPORTS;%(PreprocessorDefinitions) 79 | MultiThreadedDLL 80 | 81 | 82 | Level3 83 | ProgramDatabase 84 | 85 | 86 | msvcrt-ruby18.lib;%(AdditionalDependencies) 87 | $(OutDir)$(ProjectName)$(TargetExt) 88 | C:\Development\ruby\lib;%(AdditionalLibraryDirectories) 89 | true 90 | Windows 91 | true 92 | true 93 | false 94 | 95 | 96 | MachineX86 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /ext/ruby_prof/ruby_prof.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008 Shugo Maeda 3 | * Charlie Savage 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions 8 | * are met: 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 16 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 19 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | * SUCH DAMAGE. 26 | */ 27 | 28 | /* ruby-prof tracks the time spent executing every method in ruby programming. 29 | The main players are: 30 | 31 | prof_result_t - Its one field, values, contains the overall results 32 | thread_data_t - Stores data about a single thread. 33 | prof_stack_t - The method call stack in a particular thread 34 | prof_method_t - Profiling information for each method 35 | prof_call_info_t - Keeps track a method's callers and callees. 36 | 37 | The final resulut is a hash table of thread_data_t, keyed on the thread 38 | id. Each thread has an hash a table of prof_method_t, keyed on the 39 | method id. A hash table is used for quick look up when doing a profile. 40 | However, it is exposed to Ruby as an array. 41 | 42 | Each prof_method_t has two hash tables, parent and children, of prof_call_info_t. 43 | These objects keep track of a method's callers (who called the method) and its 44 | callees (who the method called). These are keyed the method id, but once again, 45 | are exposed to Ruby as arrays. Each prof_call_into_t maintains a pointer to the 46 | caller or callee method, thereby making it easy to navigate through the call 47 | hierarchy in ruby - which is very helpful for creating call graphs. 48 | */ 49 | 50 | #ifndef RUBY_PROF_H 51 | #define RUBY_PROF_H 52 | 53 | #include 54 | 55 | #include 56 | 57 | #ifndef RUBY_VM 58 | #include 59 | #include 60 | typedef rb_event_t rb_event_flag_t; 61 | #define rb_sourcefile() (node ? node->nd_file : 0) 62 | #define rb_sourceline() (node ? nd_line(node) : 0) 63 | #endif 64 | 65 | #include "version.h" 66 | 67 | /* ================ Constants =================*/ 68 | #define INITIAL_STACK_SIZE 8 69 | #define INITIAL_CALL_INFOS_SIZE 2 70 | 71 | 72 | /* ================ Measurement =================*/ 73 | #ifdef HAVE_LONG_LONG 74 | typedef unsigned LONG_LONG prof_measure_t; // long long is 8 bytes on 32-bit 75 | #else 76 | typedef unsigned long prof_measure_t; 77 | #endif 78 | 79 | #include "measure_process_time.h" 80 | #include "measure_wall_time.h" 81 | #include "measure_cpu_time.h" 82 | #include "measure_allocations.h" 83 | #include "measure_memory.h" 84 | #include "measure_gc_runs.h" 85 | #include "measure_gc_time.h" 86 | 87 | static prof_measure_t (*get_measurement)() = measure_process_time; 88 | static double (*convert_measurement)(prof_measure_t) = convert_process_time; 89 | 90 | /* ================ DataTypes =================*/ 91 | static VALUE mProf; 92 | static VALUE cResult; 93 | static VALUE cMethodInfo; 94 | static VALUE cCallInfo; 95 | 96 | /* nasty hack to avoid compilation warnings related to 64/32 bit conversions */ 97 | #ifndef SIZEOF_ST_INDEX_T 98 | #define st_index_t int 99 | #endif 100 | 101 | /* Profiling information for each method. */ 102 | typedef struct { 103 | VALUE klass; /* The method's class. */ 104 | ID mid; /* The method id. */ 105 | int depth; /* The recursion depth. */ 106 | st_index_t key; /* Cache calculated key */ 107 | } prof_method_key_t; 108 | 109 | struct prof_call_infos_t; 110 | 111 | /* Profiling information for each method. */ 112 | typedef struct { 113 | prof_method_key_t *key; /* Method key */ 114 | const char *source_file; /* The method's source file */ 115 | int line; /* The method's line number. */ 116 | struct prof_call_infos_t *call_infos; /* Call info objects for this method */ 117 | VALUE object; /* Cahced ruby object */ 118 | } prof_method_t; 119 | 120 | /* Callers and callee information for a method. */ 121 | typedef struct prof_call_info_t { 122 | prof_method_t *target; /* Use target instead of method to avoid conflict with Ruby method */ 123 | struct prof_call_info_t *parent; 124 | st_table *call_infos; 125 | int called; 126 | prof_measure_t total_time; 127 | prof_measure_t self_time; 128 | prof_measure_t wait_time; 129 | int line; 130 | VALUE object; 131 | VALUE children; 132 | } prof_call_info_t; 133 | 134 | /* Array of call_info objects */ 135 | typedef struct prof_call_infos_t { 136 | prof_call_info_t **start; 137 | prof_call_info_t **end; 138 | prof_call_info_t **ptr; 139 | VALUE object; 140 | } prof_call_infos_t; 141 | 142 | 143 | /* Temporary object that maintains profiling information 144 | for active methods - there is one per method.*/ 145 | typedef struct { 146 | /* Caching prof_method_t values significantly 147 | increases performance. */ 148 | prof_call_info_t *call_info; 149 | prof_measure_t start_time; 150 | prof_measure_t wait_time; 151 | prof_measure_t child_time; 152 | unsigned int line; 153 | } prof_frame_t; 154 | 155 | /* Current stack of active methods.*/ 156 | typedef struct { 157 | prof_frame_t *start; 158 | prof_frame_t *end; 159 | prof_frame_t *ptr; 160 | } prof_stack_t; 161 | 162 | /* Profiling information for a thread. */ 163 | typedef struct { 164 | VALUE thread_id; /* Thread id */ 165 | st_table* method_table; /* Methods called in the thread */ 166 | prof_stack_t* stack; /* Active methods */ 167 | prof_measure_t last_switch; /* Point of last context switch */ 168 | } thread_data_t; 169 | 170 | typedef struct { 171 | VALUE threads; 172 | } prof_result_t; 173 | 174 | 175 | /* ================ Variables =================*/ 176 | static int measure_mode; 177 | static st_table *threads_tbl = NULL; 178 | static st_table *exclude_threads_tbl = NULL; 179 | 180 | /* TODO - If Ruby become multi-threaded this has to turn into 181 | a separate stack since this isn't thread safe! */ 182 | static thread_data_t* last_thread_data = NULL; 183 | 184 | 185 | /* Forward declarations */ 186 | static VALUE prof_call_infos_wrap(prof_call_infos_t *call_infos); 187 | static VALUE prof_call_info_wrap(prof_call_info_t *call_info); 188 | static VALUE prof_method_wrap(prof_method_t *result); 189 | 190 | #endif 191 | -------------------------------------------------------------------------------- /test/basic_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | require './test_helper' 5 | 6 | class C1 7 | def C1.hello 8 | sleep(0.1) 9 | end 10 | 11 | def hello 12 | sleep(0.2) 13 | end 14 | end 15 | 16 | module M1 17 | def hello 18 | sleep(0.3) 19 | end 20 | end 21 | 22 | class C2 23 | include M1 24 | extend M1 25 | end 26 | 27 | class C3 28 | def hello 29 | sleep(0.4) 30 | end 31 | end 32 | 33 | module M4 34 | def hello 35 | sleep(0.5) 36 | end 37 | end 38 | 39 | module M5 40 | include M4 41 | def goodbye 42 | hello 43 | end 44 | end 45 | 46 | class C6 47 | include M5 48 | def test 49 | goodbye 50 | end 51 | end 52 | 53 | class BasicTest < Test::Unit::TestCase 54 | def setup 55 | # Need to use wall time for this test due to the sleep calls 56 | RubyProf::measure_mode = RubyProf::WALL_TIME 57 | end 58 | 59 | def test_running 60 | assert(!RubyProf.running?) 61 | RubyProf.start 62 | assert(RubyProf.running?) 63 | RubyProf.stop 64 | assert(!RubyProf.running?) 65 | end 66 | 67 | def test_double_profile 68 | RubyProf.start 69 | assert_raise(RuntimeError) do 70 | RubyProf.start 71 | end 72 | 73 | assert_raise(RuntimeError) do 74 | RubyProf.profile do 75 | puts 1 76 | end 77 | end 78 | RubyProf.stop 79 | end 80 | 81 | def test_no_block 82 | assert_raise(ArgumentError) do 83 | RubyProf.profile 84 | end 85 | end 86 | 87 | def test_class_methods 88 | result = RubyProf.profile do 89 | C1.hello 90 | end 91 | 92 | # Length should be 3: 93 | # BasicTest#test_class_methods 94 | # #hello 95 | # Kernel#sleep 96 | 97 | methods = result.threads.values.first.sort.reverse 98 | assert_equal(3, methods.length) 99 | 100 | # Check the names 101 | assert_equal('BasicTest#test_class_methods', methods[0].full_name) 102 | assert_equal('#hello', methods[1].full_name) 103 | assert_equal('Kernel#sleep', methods[2].full_name) 104 | 105 | # Check times 106 | assert_in_delta(0.1, methods[0].total_time, 0.01) 107 | assert_in_delta(0, methods[0].wait_time, 0.01) 108 | assert_in_delta(0, methods[0].self_time, 0.01) 109 | 110 | assert_in_delta(0.1, methods[1].total_time, 0.01) 111 | assert_in_delta(0, methods[1].wait_time, 0.01) 112 | assert_in_delta(0, methods[1].self_time, 0.01) 113 | 114 | assert_in_delta(0.1, methods[2].total_time, 0.01) 115 | assert_in_delta(0, methods[2].wait_time, 0.01) 116 | assert_in_delta(0.1, methods[2].self_time, 0.01) 117 | end 118 | 119 | if RUBY_VERSION < '1.9' 120 | PARENT = Object 121 | else 122 | PARENT = BasicObject 123 | end 124 | 125 | def test_instance_methods 126 | result = RubyProf.profile do 127 | C1.new.hello 128 | end 129 | 130 | # Methods called 131 | # BasicTest#test_instance_methods 132 | # Class.new 133 | # Class:Object#allocate 134 | # for Object#initialize 135 | # C1#hello 136 | # Kernel#sleep 137 | 138 | methods = result.threads.values.first.sort.reverse 139 | assert_equal(6, methods.length) 140 | names = methods.map(&:full_name) 141 | assert_equal('BasicTest#test_instance_methods', names[0]) 142 | assert_equal('C1#hello', names[1]) 143 | assert_equal('Kernel#sleep', names[2]) 144 | assert_equal('Class#new', names[3]) 145 | # order can differ 146 | assert(names.include?("#allocate")) 147 | assert(names.include?("#{PARENT}#initialize")) 148 | 149 | # Check times 150 | assert_in_delta(0.2, methods[0].total_time, 0.02) 151 | assert_in_delta(0, methods[0].wait_time, 0.02) 152 | assert_in_delta(0, methods[0].self_time, 0.02) 153 | 154 | assert_in_delta(0.2, methods[1].total_time, 0.02) 155 | assert_in_delta(0, methods[1].wait_time, 0.02) 156 | assert_in_delta(0, methods[1].self_time, 0.02) 157 | 158 | assert_in_delta(0.2, methods[2].total_time, 0.02) 159 | assert_in_delta(0, methods[2].wait_time, 0.02) 160 | assert_in_delta(0.2, methods[2].self_time, 0.02) 161 | 162 | assert_in_delta(0, methods[3].total_time, 0.01) 163 | assert_in_delta(0, methods[3].wait_time, 0.01) 164 | assert_in_delta(0, methods[3].self_time, 0.01) 165 | 166 | assert_in_delta(0, methods[4].total_time, 0.01) 167 | assert_in_delta(0, methods[4].wait_time, 0.01) 168 | assert_in_delta(0, methods[4].self_time, 0.01) 169 | 170 | assert_in_delta(0, methods[5].total_time, 0.01) 171 | assert_in_delta(0, methods[5].wait_time, 0.01) 172 | assert_in_delta(0, methods[5].self_time, 0.01) 173 | end 174 | 175 | def test_module_methods 176 | result = RubyProf.profile do 177 | C2.hello 178 | end 179 | 180 | # Methods: 181 | # BasicTest#test_module_methods 182 | # M1#hello 183 | # Kernel#sleep 184 | 185 | methods = result.threads.values.first.sort.reverse 186 | assert_equal(3, methods.length) 187 | 188 | assert_equal('BasicTest#test_module_methods', methods[0].full_name) 189 | assert_equal('M1#hello', methods[1].full_name) 190 | assert_equal('Kernel#sleep', methods[2].full_name) 191 | 192 | # Check times 193 | assert_in_delta(0.3, methods[0].total_time, 0.1) 194 | assert_in_delta(0, methods[0].wait_time, 0.02) 195 | assert_in_delta(0, methods[0].self_time, 0.02) 196 | 197 | assert_in_delta(0.3, methods[1].total_time, 0.1) 198 | assert_in_delta(0, methods[1].wait_time, 0.02) 199 | assert_in_delta(0, methods[1].self_time, 0.02) 200 | 201 | assert_in_delta(0.3, methods[2].total_time, 0.1) 202 | assert_in_delta(0, methods[2].wait_time, 0.02) 203 | assert_in_delta(0.3, methods[2].self_time, 0.1) 204 | end 205 | 206 | def test_module_instance_methods 207 | result = RubyProf.profile do 208 | C2.new.hello 209 | end 210 | 211 | # Methods: 212 | # BasicTest#test_module_instance_methods 213 | # Class#new 214 | # #allocate 215 | # Object#initialize 216 | # M1#hello 217 | # Kernel#sleep 218 | 219 | methods = result.threads.values.first.sort.reverse 220 | assert_equal(6, methods.length) 221 | names = methods.map(&:full_name) 222 | assert_equal('BasicTest#test_module_instance_methods', names[0]) 223 | assert_equal('M1#hello', names[1]) 224 | assert_equal('Kernel#sleep', names[2]) 225 | assert_equal('Class#new', names[3]) 226 | assert(names.include?("#allocate")) 227 | assert(names.include?("#{PARENT}#initialize")) 228 | 229 | # Check times 230 | assert_in_delta(0.3, methods[0].total_time, 0.1) 231 | assert_in_delta(0, methods[0].wait_time, 0.1) 232 | assert_in_delta(0, methods[0].self_time, 0.1) 233 | 234 | assert_in_delta(0.3, methods[1].total_time, 0.02) 235 | assert_in_delta(0, methods[1].wait_time, 0.01) 236 | assert_in_delta(0, methods[1].self_time, 0.01) 237 | 238 | assert_in_delta(0.3, methods[2].total_time, 0.02) 239 | assert_in_delta(0, methods[2].wait_time, 0.01) 240 | assert_in_delta(0.3, methods[2].self_time, 0.02) 241 | 242 | assert_in_delta(0, methods[3].total_time, 0.01) 243 | assert_in_delta(0, methods[3].wait_time, 0.01) 244 | assert_in_delta(0, methods[3].self_time, 0.01) 245 | 246 | assert_in_delta(0, methods[4].total_time, 0.01) 247 | assert_in_delta(0, methods[4].wait_time, 0.01) 248 | assert_in_delta(0, methods[4].self_time, 0.01) 249 | 250 | assert_in_delta(0, methods[5].total_time, 0.01) 251 | assert_in_delta(0, methods[5].wait_time, 0.01) 252 | assert_in_delta(0, methods[5].self_time, 0.01) 253 | end 254 | 255 | def test_singleton 256 | c3 = C3.new 257 | 258 | class << c3 259 | def hello 260 | end 261 | end 262 | 263 | result = RubyProf.profile do 264 | c3.hello 265 | end 266 | 267 | methods = result.threads.values.first.sort.reverse 268 | assert_equal(2, methods.length) 269 | 270 | assert_equal('BasicTest#test_singleton', methods[0].full_name) 271 | assert_equal('#hello', methods[1].full_name) 272 | 273 | assert_in_delta(0, methods[0].total_time, 0.01) 274 | assert_in_delta(0, methods[0].wait_time, 0.01) 275 | assert_in_delta(0, methods[0].self_time, 0.01) 276 | 277 | assert_in_delta(0, methods[1].total_time, 0.01) 278 | assert_in_delta(0, methods[1].wait_time, 0.01) 279 | assert_in_delta(0, methods[1].self_time, 0.01) 280 | end 281 | 282 | def test_traceback 283 | RubyProf.start 284 | assert_raise(NoMethodError) do 285 | RubyProf.xxx 286 | end 287 | 288 | RubyProf.stop 289 | end 290 | end 291 | -------------------------------------------------------------------------------- /bin/ruby-prof: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | 3 | # == Synopsis 4 | # 5 | # Profiles a Ruby program. 6 | # 7 | # == Usage 8 | # 9 | # ruby_prof [options] [--] [script-options]" 10 | # 11 | # Various options: 12 | # run "$ ruby-prof --help" to see them 13 | # 14 | # See also the readme "reports" section for the various outputs 15 | 16 | require 'ostruct' 17 | require 'optparse' 18 | require File.dirname(__FILE__) + "/../lib/ruby-prof" 19 | 20 | options = OpenStruct.new 21 | options.measure_mode = RubyProf::PROCESS_TIME 22 | options.printer = RubyProf::FlatPrinter 23 | options.min_percent = 0 24 | options.file = nil 25 | options.replace_prog_name = false 26 | options.specialized_instruction = false 27 | 28 | opts = OptionParser.new do |opts| 29 | opts.banner = "ruby_prof #{RubyProf::VERSION}\n" + 30 | "Usage: ruby-prof [options] [--] [profiled-script-command-line-options]" 31 | 32 | opts.separator "" 33 | opts.separator "Options:" 34 | 35 | 36 | opts.on('-p printer', '--printer=printer', [:flat, :flat_with_line_numbers, :graph, :graph_html, :call_tree, :call_stack, :dot], 37 | 'Select a printer:', 38 | ' flat - Prints a flat profile as text (default).', 39 | ' flat_with_line_numbers - same as flat, with line numbers.', 40 | ' graph - Prints a graph profile as text.', 41 | ' graph_html - Prints a graph profile as html.', 42 | ' call_tree - format for KCacheGrind', 43 | ' call_stack - prints a HTML visualization of the call tree', 44 | ' dot - Prints a graph profile as a dot file' 45 | ) do |printer| 46 | 47 | 48 | case printer 49 | when :flat 50 | options.printer = RubyProf::FlatPrinter 51 | when :flat_with_line_numbers 52 | options.printer = RubyProf::FlatPrinterWithLineNumbers 53 | when :graph 54 | options.printer = RubyProf::GraphPrinter 55 | when :graph_html 56 | options.printer = RubyProf::GraphHtmlPrinter 57 | when :call_tree 58 | options.printer = RubyProf::CallTreePrinter 59 | when :call_stack 60 | options.printer = RubyProf::CallStackPrinter 61 | when :dot 62 | options.printer = RubyProf::DotPrinter 63 | end 64 | end 65 | 66 | opts.on('-m min_percent', '--min_percent=min_percent', Float, 67 | 'The minimum percent a method must take before ', 68 | ' being included in output reports.', 69 | ' this option is not supported for call tree.') do |min_percent| 70 | options.min_percent = min_percent 71 | end 72 | 73 | opts.on('-f path', '--file=path', 74 | 'Output results to a file instead of standard out.') do |file| 75 | options.file = file 76 | options.old_wd = Dir.pwd 77 | end 78 | 79 | opts.on('--mode=measure_mode', 80 | [:process, :wall, :cpu, :allocations, :memory, :gc_runs, :gc_time], 81 | 'Select what ruby-prof should measure:', 82 | ' process - Process time (default).', 83 | ' wall - Wall time.', 84 | ' cpu - CPU time (Pentium and PowerPCs only).', 85 | ' allocations - Object allocations (requires patched Ruby interpreter).', 86 | ' memory - Allocated memory in KB (requires patched Ruby interpreter).', 87 | ' gc_runs - Number of garbage collections (requires patched Ruby interpreter).', 88 | ' gc_time - Time spent in garbage collection (requires patched Ruby interpreter).') do |measure_mode| 89 | 90 | case measure_mode 91 | when :process 92 | options.measure_mode = RubyProf::PROCESS_TIME 93 | when :wall 94 | options.measure_mode = RubyProf::WALL_TIME 95 | when :cpu 96 | options.measure_mode = RubyProf::CPU_TIME 97 | when :allocations 98 | options.measure_mode = RubyProf::ALLOCATIONS 99 | when :memory 100 | options.measure_mode = RubyProf::MEMORY 101 | when :gc_runs 102 | options.measure_mode = RubyProf::GC_RUNS 103 | when :gc_time 104 | options.measure_mode = RubyProf::GC_TIME 105 | end 106 | end 107 | 108 | opts.on('-s sort_mode', '--sort=sort_mode', [:total, :self, :wait, :child], 109 | 'Select how ruby-prof results should be sorted:', 110 | ' total - Total time', 111 | ' self - Self time', 112 | ' wait - Wait time', 113 | ' child - Child time') do |sort_mode| 114 | 115 | options.sort_method = case sort_mode 116 | when :total 117 | :total_time 118 | when :self 119 | :self_time 120 | when :wait 121 | :wait_time 122 | when :child 123 | :children_time 124 | end 125 | end 126 | 127 | opts.on("--replace-progname", "Replace $0 when loading the .rb files.") do 128 | options.replace_prog_name = true 129 | end 130 | 131 | if defined?(VM) 132 | opts.on("--specialized-instruction", "Turn on specified instruction.") do 133 | options.specialized_instruction = true 134 | end 135 | end 136 | 137 | opts.on_tail("-h", "--help", "Show help message") do 138 | puts opts 139 | exit 140 | end 141 | 142 | opts.on_tail("--version", "Show version #{RubyProf::VERSION}") do 143 | puts "ruby_prof " + RubyProf::VERSION 144 | exit 145 | end 146 | 147 | opts.on("-v","Show version, set $VERBOSE to true, profile script if option given") do 148 | puts "ruby version: " + [RUBY_PATCHLEVEL, RUBY_PLATFORM, RUBY_VERSION].join(' ') 149 | $VERBOSE = true 150 | end 151 | 152 | opts.on("-d", "Set $DEBUG to true") do 153 | $DEBUG = true 154 | end 155 | 156 | opts.on('-R lib', '--require-noprof lib', 'require a specific library (not profiled)') do |lib| 157 | options.pre_libs ||= [] 158 | options.pre_libs << lib 159 | end 160 | 161 | opts.on('-E code', '--eval-noprof code', 'execute the ruby statements (not profiled)') do |code| 162 | options.pre_exec ||= [] 163 | options.pre_exec << code 164 | end 165 | 166 | opts.on('-r lib', '--require lib', 'require a specific library') do |lib| 167 | options.libs ||= [] 168 | options.libs << lib 169 | end 170 | 171 | opts.on('-e code', '--eval', 'execute the ruby statements') do |code| 172 | options.exec ||= [] 173 | options.exec << code 174 | end 175 | end 176 | 177 | begin 178 | opts.parse! ARGV 179 | rescue OptionParser::InvalidOption, OptionParser::InvalidArgument, 180 | OptionParser::MissingArgument => e 181 | puts opts 182 | puts 183 | puts e.message 184 | exit(-1) 185 | end 186 | 187 | # Make sure the user specified at least one file 188 | if ARGV.length < 1 and not options.exec 189 | puts opts 190 | puts "" 191 | puts "Must specify a script to run" 192 | exit(-1) 193 | end 194 | 195 | 196 | # Install at_exit handler. It is important that we do this 197 | # before loading the scripts so our at_exit handler run 198 | # *after* any other one that will be installed. 199 | 200 | at_exit { 201 | # Stop profiling 202 | result = RubyProf.stop 203 | 204 | # Create a printer 205 | printer = options.printer.new(result) 206 | printer_options = {:min_percent => options.min_percent, :sort_method => options.sort_method} 207 | 208 | # Get output 209 | if options.file 210 | # write it relative to the dir they *started* in, as it's a bit surprising to write it in the dir they end up in. 211 | Dir.chdir(options.old_wd) do 212 | File.open(options.file, 'w') do |file| 213 | printer.print(file, printer_options) 214 | end 215 | end 216 | else 217 | # Print out results 218 | printer.print(STDOUT, printer_options) 219 | end 220 | } 221 | 222 | # Now set measure mode 223 | RubyProf.measure_mode = options.measure_mode 224 | 225 | # Set VM compile option 226 | if defined?(VM) 227 | VM::InstructionSequence.compile_option = { 228 | :trace_instruction => true, 229 | :specialized_instruction => options.specialized_instruction 230 | } 231 | end 232 | 233 | # Get the script we will execute 234 | script = ARGV.shift 235 | if options.replace_prog_name 236 | $0 = File.expand_path(script) 237 | end 238 | 239 | if options.pre_libs 240 | options.pre_libs.each { |l| require l } 241 | end 242 | 243 | if options.pre_exec 244 | options.pre_exec.each { |c| eval c } 245 | end 246 | 247 | # do not pollute profiling report with OpenStruct#libs 248 | ol = options.libs 249 | oe = options.exec 250 | 251 | # Start profiling 252 | RubyProf.start 253 | 254 | if ol 255 | ol.each { |l| require l } 256 | end 257 | 258 | if oe 259 | oe.each { |c| eval c } 260 | end 261 | 262 | # Load the script 263 | load script if script 264 | -------------------------------------------------------------------------------- /test/printers_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | require './test_helper' 5 | require './prime' 6 | require 'stringio' 7 | require 'fileutils' 8 | 9 | # -- Tests ---- 10 | class PrintersTest < Test::Unit::TestCase 11 | 12 | def go 13 | run_primes(1000) 14 | end 15 | 16 | def setup 17 | RubyProf::measure_mode = RubyProf::WALL_TIME # WALL_TIME so we can use sleep in our test and get same measurements on linux and doze 18 | @result = RubyProf.profile do 19 | begin 20 | run_primes(1000) 21 | go 22 | rescue => e 23 | p e 24 | end 25 | end 26 | 27 | end 28 | 29 | def test_printers 30 | assert_nothing_raised do 31 | output = ENV['SHOW_RUBY_PROF_PRINTER_OUTPUT'] == "1" ? STDOUT : StringIO.new('') 32 | 33 | printer = RubyProf::FlatPrinter.new(@result) 34 | printer.print(output) 35 | 36 | printer = RubyProf::FlatPrinterWithLineNumbers.new(@result) 37 | printer.print(output) 38 | 39 | printer = RubyProf::GraphHtmlPrinter.new(@result) 40 | printer.print(output) 41 | 42 | printer = RubyProf::GraphPrinter.new(@result) 43 | printer.print(output) 44 | 45 | printer = RubyProf::CallTreePrinter.new(@result) 46 | printer.print(output) 47 | output_dir = 'examples2' 48 | 49 | if ENV['SAVE_NEW_PRINTER_EXAMPLES'] 50 | output_dir = 'examples' 51 | end 52 | FileUtils.mkdir_p output_dir 53 | 54 | printer = RubyProf::DotPrinter.new(@result) 55 | File.open("#{output_dir}/graph.dot", "w") {|f| printer.print(f)} 56 | 57 | printer = RubyProf::CallStackPrinter.new(@result) 58 | File.open("#{output_dir}/stack.html", "w") {|f| printer.print(f, :application => "primes")} 59 | 60 | printer = RubyProf::MultiPrinter.new(@result) 61 | printer.print(:path => "#{output_dir}", :profile => "multi", :application => "primes") 62 | for file in ['empty.png', 'graph.dot', 'minus.png', 'multi.flat.txt', 'multi.graph.html', 'multi.grind.dat', 'multi.stack.html', 'plus.png', 'stack.html'] 63 | existant_file = output_dir + '/' + file 64 | assert File.size(existant_file) > 0 65 | end 66 | end 67 | end 68 | 69 | def test_flat_string 70 | output = helper_test_flat_string RubyProf::FlatPrinter 71 | assert_no_match(/prime.rb/, output) 72 | end 73 | 74 | def helper_test_flat_string klass 75 | output = '' 76 | 77 | printer = klass.new(@result) 78 | printer.print(output) 79 | 80 | assert_match(/Thread ID: -?\d+/i, output) 81 | assert_match(/Total: \d+\.\d+/i, output) 82 | assert_match(/Object#run_primes/i, output) 83 | output 84 | end 85 | 86 | def test_flat_string_with_numbers 87 | output = helper_test_flat_string RubyProf::FlatPrinterWithLineNumbers 88 | assert_match(/prime.rb/, output) 89 | assert_no_match(/ruby_runtime:0/, output) 90 | assert_match(/called from/, output) 91 | 92 | # should combine common parents 93 | if RUBY_VERSION < '1.9' 94 | assert_equal(3, output.scan(/Object#is_prime/).length) 95 | else 96 | # 1.9 inlines it's Fixnum#- so we don't see as many 97 | assert_equal(2, output.scan(/Object#is_prime/).length) 98 | end 99 | assert_no_match(/\.\/test\/prime.rb/, output) # don't use relative paths 100 | end 101 | 102 | def test_graph_html_string 103 | output = '' 104 | printer = RubyProf::GraphHtmlPrinter.new(@result) 105 | printer.print(output) 106 | 107 | assert_match( /DTD HTML 4\.01/i, output ) 108 | assert_match( %r{Total Time}i, output ) 109 | assert_match( /Object#run_primes/i, output ) 110 | end 111 | 112 | def test_graph_string 113 | output = '' 114 | printer = RubyProf::GraphPrinter.new(@result) 115 | printer.print(output) 116 | 117 | assert_match( /Thread ID: -?\d+/i, output ) 118 | assert_match( /Total Time: \d+\.\d+/i, output ) 119 | assert_match( /Object#run_primes/i, output ) 120 | end 121 | 122 | def test_call_tree_string 123 | output = '' 124 | printer = RubyProf::CallTreePrinter.new(@result) 125 | printer.print(output) 126 | assert_match(/fn=Object#find_primes/i, output) 127 | assert_match(/events: wall_time/i, output) 128 | assert_no_match(/d\d\d\d\d\d/, output) # old bug looked [in error] like Object::run_primes(d5833116) 129 | end 130 | 131 | def do_nothing 132 | start = Time.now 133 | while(Time.now == start) 134 | end 135 | end 136 | 137 | def test_all_with_small_percentiles 138 | 139 | result = RubyProf.profile do 140 | sleep 2 141 | do_nothing 142 | end 143 | 144 | # RubyProf::CallTreePrinter doesn't "do" a min_percent 145 | # RubyProf::FlatPrinter only outputs if self time > percent... 146 | # RubyProf::FlatPrinterWithLineNumbers same 147 | for klass in [ RubyProf::GraphPrinter, RubyProf::GraphHtmlPrinter] 148 | printer = klass.new(result) 149 | out = '' 150 | output = printer.print(out, :min_percent => 0.00000001 ) 151 | assert_match(/do_nothing/, out) 152 | end 153 | 154 | end 155 | 156 | def test_flat_result_sorting_by_self_time_is_default 157 | printer = RubyProf::FlatPrinter.new(@result) 158 | 159 | printer.print(output = '') 160 | self_times = flat_output_nth_column_values(output, 3) 161 | 162 | assert_sorted self_times 163 | end 164 | 165 | def test_flat_result_sorting 166 | printer = RubyProf::FlatPrinter.new(@result) 167 | 168 | sort_method_with_column_number = {:total_time => 2, :self_time => 3, :wait_time => 4, :children_time => 5} 169 | 170 | sort_method_with_column_number.each_pair do |sort_method, n| 171 | printer.print(output = '', :sort_method => sort_method) 172 | times = flat_output_nth_column_values(output, n) 173 | assert_sorted times 174 | end 175 | end 176 | 177 | def test_flat_result_with_line_numbers_sorting_by_self_time_is_default 178 | printer = RubyProf::FlatPrinterWithLineNumbers.new(@result) 179 | 180 | printer.print(output = '') 181 | self_times = flat_output_nth_column_values(output, 3) 182 | 183 | assert_sorted self_times 184 | end 185 | 186 | def test_flat_with_line_numbers_result_sorting 187 | printer = RubyProf::FlatPrinterWithLineNumbers.new(@result) 188 | 189 | sort_method_with_column_number = {:total_time => 2, :self_time => 3, :wait_time => 4, :children_time => 5} 190 | 191 | sort_method_with_column_number.each_pair do |sort_method, n| 192 | printer.print(output = '', :sort_method => sort_method) 193 | times = flat_output_nth_column_values(output, n) 194 | assert_sorted times 195 | end 196 | end 197 | 198 | def test_graph_result_sorting_by_total_time_is_default 199 | printer = RubyProf::GraphPrinter.new(@result) 200 | printer.print(output = '') 201 | total_times = graph_output_nth_column_values(output, 3) 202 | 203 | assert_sorted total_times 204 | end 205 | 206 | def test_graph_results_sorting 207 | printer = RubyProf::GraphPrinter.new(@result) 208 | 209 | sort_method_with_column_number = {:total_time => 3, :self_time => 4, :wait_time => 5, :children_time => 6} 210 | 211 | sort_method_with_column_number.each_pair do |sort_method, n| 212 | printer.print(output = '', :sort_method => sort_method) 213 | times = graph_output_nth_column_values(output, n) 214 | assert_sorted times 215 | end 216 | end 217 | 218 | def test_graph_html_result_sorting_by_total_time_is_default 219 | printer = RubyProf::GraphHtmlPrinter.new(@result) 220 | printer.print(output = '') 221 | total_times = graph_html_output_nth_column_values(output, 3) 222 | 223 | assert_sorted total_times 224 | end 225 | 226 | def test_graph_html_result_sorting 227 | printer = RubyProf::GraphHtmlPrinter.new(@result) 228 | 229 | sort_method_with_column_number = {:total_time => 3, :self_time => 4, :wait_time => 5, :children_time => 6} 230 | 231 | sort_method_with_column_number.each_pair do |sort_method, n| 232 | printer.print(output = '', :sort_method => sort_method) 233 | times = graph_html_output_nth_column_values(output, n) 234 | assert_sorted times 235 | end 236 | end 237 | 238 | private 239 | def flat_output_nth_column_values(output, n) 240 | only_method_calls = output.split("\n").select { |line| line =~ /^ +\d+/ } 241 | only_method_calls.collect { |line| line.split(/ +/)[n] } 242 | end 243 | 244 | def graph_output_nth_column_values(output, n) 245 | only_root_calls = output.split("\n").select { |line| line =~ /^ +[\d\.]+%/ } 246 | only_root_calls.collect { |line| line.split(/ +/)[n] } 247 | end 248 | 249 | def graph_html_output_nth_column_values(output, n) 250 | only_root_calls = output.split('') 251 | only_root_calls.delete_at(0) 252 | only_root_calls.collect {|line| line.scan(/[\d\.]+/)[n - 1] } 253 | end 254 | 255 | def assert_sorted array 256 | assert_equal array, array.sort.reverse, "Array #{array.inspect} is not sorted" 257 | end 258 | end 259 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | 0.10.7 2 | ====================== 3 | Fix a bug with REE's GC stats. Issue #53 [thanks graaff] 4 | 5 | 0.10.6 6 | ====================== 7 | Slightly more normalized url for linux/windows links to files. 8 | 9 | 0.10.5 10 | ======================= 11 | 1.8.6 compat for -v command (bug fix) 12 | 13 | 0.10.4 14 | ======================= 15 | Faster load time for ruby-prof itself. 16 | 17 | 0.10.3 18 | ======================= 19 | Can cleanly load before rubygems now. 20 | 21 | 0.10.2 22 | ======================= 23 | Fix for 1.9.2, os x for latest commits (thanks skaes!) 24 | 25 | 0.10.1 26 | ======================= 27 | Fix bug in linux wall time, also load with only relative paths so that you can use it to benchmark rubygems startup overhead, 28 | itself. 29 | 30 | 0.10.0 31 | ======================= 32 | Some rdoc changes, for linux wall time attempt to use higher granularity (thanks to all the contributors for this round!) 33 | 34 | 0.9.2 35 | ======================= 36 | Make graphviz work on 1.8.6 37 | roll back some 1.9.2 optimizations until I can figure out what caused them. 38 | 39 | 0.9.1 40 | ======================= 41 | Add a graphviz output 42 | 43 | 0.9.0 44 | ======================= 45 | * measurements for recursive methods are now correct 46 | * gave up on splitting up recursive methods according to call depth 47 | * made it possible to eliminate methods from profiling results 48 | * new printer for call stack visualization 49 | * new printer to print several profiles in one run 50 | * HTML profiles contain Textmate links so you can jump to the source code easily 51 | * producing an event log is now a runtime option 52 | 53 | 0.7.10 (2009-01-22) 54 | ======================= 55 | * fix SEGFAULT in 1.9 56 | * add new printer flat_printer_with_line_numbers 57 | 58 | 0.7.7 (2009-01-13) 59 | ====================== 60 | * "fix" multi threading support for 1.9 http://redmine.ruby-lang.org/issues/show/2012 61 | * speedups 62 | 63 | 0.7.6 (2009-12-31) 64 | ====================== 65 | * fix some tests for 1.9 (no real code changes) 66 | 67 | 0.7.5 (2009-12) 68 | ======================== 69 | * fix a GC collection bug (nobu's patch). 70 | * correctly report recursive call depths (Kevin Scaldeferri). 71 | * sort methods on output (Kevin Scaldeferri). 72 | 73 | 0.7.3 (2008-12-09) 74 | ======================== 75 | * Fixed compile error with new x86_64 code using GCC. 76 | 77 | 0.7.2 (2008-12-08) 78 | ======================== 79 | * Fixed major bug in printing child methods in graph reports. 80 | 81 | * Fixes for supporting x86_64 machines (Diego Pettenò) 82 | 83 | 84 | 0.7.1 (2008-11-28) 85 | ======================== 86 | * Added new AggregateCallInfo class for printers to 87 | make results easier to read. Take this call sequence 88 | for example: 89 | 90 | A B C 91 | | | | 92 | Z A A 93 | | | 94 | Z Z 95 | 96 | By default, ruby-prof will show that Z was called by 3 separate 97 | instances of A. In an IDE that is helpful but in a text report 98 | it is not since it makes the report much harder to read. 99 | As a result, printers now aggregate together callers (and children), 100 | matching ruby-prof's output from versions prior to 0.7.0. 101 | 102 | * Fixes for supporting x86_64 machines (Matt Sanford) 103 | 104 | 105 | 0.7.0 (2008-11-04) 106 | ======================== 107 | 108 | Features 109 | -------- 110 | * Added two new methods - RubyProf.resume and RubyProf.pause. 111 | RubyProf.resume takes an optional block, which ensures that 112 | RubyProf.pause is called. For example: 113 | 114 | 10.times do |i| 115 | RubyProf.resume do 116 | # Some long process 117 | end 118 | end 119 | 120 | result = RubyProf.stop 121 | 122 | * Added support for profiling tests that use Ruby's built-in 123 | unit test framework (ie, test derived from 124 | Test::Unit::TestCase). To enable profiling simply add 125 | the following line of code to your test class: 126 | 127 | include RubyProf::Test 128 | 129 | By default, profiling results are written to the current 130 | processes working directory. To change this, or other 131 | profiling options, simply modify the PROFILE_OPTIONS hash 132 | table as needed. 133 | 134 | * Used the new support for profiling test cases to revamp 135 | the way that Rails profiling works. For more information 136 | please refer to RubyProf's documentation. 137 | 138 | * Keep track of call stack for each method to enable more 139 | powerful profile visualizations in Integrated Development 140 | Environments (Hin Boean, work supported by CodeGear). 141 | 142 | * Expose measurements to Ruby (Jeremy Kemper). 143 | 144 | * Add support for additional memory measurements modes in Ruby 1.9 (Jeremy Kemper). 145 | 146 | * Add support for Lloyd Hilaiel's Ruby patch for measuring total heap size. 147 | See http://lloydforge.org/projects/ruby. (Jeremy Kemper). 148 | 149 | 150 | Fixes 151 | ------- 152 | * RubyProf.profile no longer crashes if an exception is 153 | thrown during a profiling run. 154 | 155 | * Measure memory in fractional kilobytes rather than rounding down (Jeremy Kemper) 156 | 157 | 158 | 0.6.0 (2008-02-03) 159 | ======================== 160 | 161 | ruby-prof 0.6.0 adds support for Ruby 1.9 and memory profiling. 162 | 163 | Features 164 | -------- 165 | * Added support for ruby 1.9 (Shugo Maeda) 166 | * Added support for outputting printer results to a String, Array or IO 167 | object (Michael Granger) 168 | * Add new memory profiling mode. Note this mode depends on a 169 | patched Ruby interpreter (Alexander Dymo) 170 | 171 | Fixes 172 | ------- 173 | * Improvements to GraphHtmlPrinter including updated documentation, 174 | fixes for min_time support, ability to specify templates using 175 | strings or filenames, and table layout fixes (Makoto Kuwata) 176 | * Fixes to scaling factor for calltrees so that precision is not lost 177 | due to the conversion to doubles (Sylvain Joyeux) 178 | * Changed constant ALLOCATED_OBJECTS to ALLOCATIONS in the C code to 179 | match the Ruby code (Sylvain Joyeux) 180 | * Added support for calltree printer to ruby-prof binary script (Sylvain Joyeux) 181 | * Fix support for the allocator measure mode to extconf.rb (Sylvain Joyeux) 182 | * Honor measure mode when specified on the command line (Sylvain Joyeux) 183 | * Sorting of methods by total time was incorrect (Dan Fitch, Charlie Savage) 184 | * Fix ruby-prof to work with the latest version of GEMS (Alexander Dymo) 185 | * Always define MEASURE_CPU_TIME and MEASURE_ALLOCATIONS in Ruby code, but 186 | set their values to nil if the functionality is not available. 187 | 188 | 189 | 0.5.2 (2007-07-19) 190 | ======================== 191 | 192 | ruby-prof 0.5.2 is a bug fix release. 193 | 194 | Fixes 195 | ------- 196 | * Include missing rails plugin 197 | 198 | 199 | 0.5.1 (2007-07-18) 200 | ======================== 201 | 202 | ruby-prof 0.5.1 is a bug fix and performance release. 203 | 204 | Performance 205 | -------- 206 | * Significantly reduced the number of thread lookups by 207 | caching the last executed thread. 208 | 209 | Fixes 210 | ------- 211 | * Properly escape method names in HTML reports 212 | * Fix use of -m and --min-percent command line switches 213 | * Default source file information to ruby_runtime#0 for c calls 214 | * Moved rails_plugin to top level so it is more obvious 215 | * Updated rails_plugin to write reports to the current 216 | Rails log directory 217 | * Added additional tests 218 | 219 | 220 | 0.5.0 (2007-07-09) 221 | ======================== 222 | 223 | Features 224 | -------- 225 | * Added support for timing multi-threaded applications 226 | * Added support for 64 bit systems (patch from Diego 'Flameeyes' Petten) 227 | * Added suport for outputting data in the format used by 228 | KCacheGrind (patch from Carl Shimer) 229 | * Add filename and line numbers to call tree information (patch from Carl Shimer) 230 | * Added Visual Studio 2005 project file. 231 | * Added replace-progname switch, als rcov. 232 | * Added better support for recursive methods 233 | * Added better support for profiling Rails applications 234 | 235 | Fixes 236 | ------- 237 | * Fixes bug when the type of an attached object (singleton) is inherited 238 | from T_OBJECT as opposed to being a T_OBJECT (identified by Francis Cianfrocca) 239 | * ruby-prof now works in IRB. 240 | * Fix sort order in reports. 241 | * Fixed rdoc compile error. 242 | * Fix tabs in erb template for graph html report on windows. 243 | 244 | 0.4.1 (2006-06-26) 245 | ======================== 246 | 247 | Features 248 | -------- 249 | * Added a RubyProf.running? method to indicate whether a profile is in progress. 250 | * Added tgz and zip archives to release 251 | 252 | Fixes 253 | ------- 254 | * Duplicate method names are now allowed 255 | * The documentation has been updated to show the correct API usage is RubyProf.stop not RubyProf.end 256 | 257 | 258 | 0.4.0 (2006-06-16) 259 | ======================== 260 | Features 261 | -------- 262 | * added support for call graphs 263 | * added support for printers. Currently there is a FlatPrinter, 264 | GraphPrinter and GraphHtmlPrinter. 265 | * added support for recursive methods 266 | * added Windows support 267 | * now packaged as a RubyGem 268 | 269 | Fixes 270 | ------- 271 | * Fixes bug where RubyProf would crash depending on the 272 | way it was invoked - for example, it did not run when 273 | used with Arachno Ruby's customized version of Ruby. 274 | --------------------------------------------------------------------------------