[DRE-commits] [ruby-prof] 01/14: Imported Upstream version 0.7.3
Jonas Genannt
jonas at brachium-system.net
Thu Dec 19 19:42:23 UTC 2013
This is an automated email from the git hooks/post-receive script.
hggh-guest pushed a commit to branch master
in repository ruby-prof.
commit 04ca289f8ae4fe5ad3a60949ff3e8cdf6c4b72f8
Author: Jonas Genannt <jonas at brachium-system.net>
Date: Thu Dec 19 18:29:17 2013 +0100
Imported Upstream version 0.7.3
---
CHANGES | 202 ++++
LICENSE | 23 +
README | 436 +++++++++
Rakefile | 123 +++
bin/ruby-prof | 207 +++++
examples/flat.txt | 55 ++
examples/graph.html | 823 +++++++++++++++++
examples/graph.txt | 170 ++++
ext/extconf.rb | 34 +
ext/measure_allocations.h | 58 ++
ext/measure_cpu_time.h | 152 +++
ext/measure_gc_runs.h | 76 ++
ext/measure_gc_time.h | 57 ++
ext/measure_memory.h | 101 ++
ext/measure_process_time.h | 52 ++
ext/measure_wall_time.h | 53 ++
ext/mingw/Rakefile | 23 +
ext/mingw/build.rake | 38 +
ext/mingw/ruby_prof.so | Bin 0 -> 38794 bytes
ext/ruby_prof.c | 1680 ++++++++++++++++++++++++++++++++++
ext/ruby_prof.h | 188 ++++
ext/vc/ruby_prof.sln | 20 +
ext/vc/ruby_prof.vcproj | 241 +++++
ext/version.h | 4 +
lib/ruby-prof.rb | 48 +
lib/ruby-prof/abstract_printer.rb | 41 +
lib/ruby-prof/aggregate_call_info.rb | 62 ++
lib/ruby-prof/call_info.rb | 47 +
lib/ruby-prof/call_tree_printer.rb | 84 ++
lib/ruby-prof/flat_printer.rb | 79 ++
lib/ruby-prof/graph_html_printer.rb | 256 ++++++
lib/ruby-prof/graph_printer.rb | 164 ++++
lib/ruby-prof/method_info.rb | 111 +++
lib/ruby-prof/task.rb | 146 +++
lib/ruby-prof/test.rb | 148 +++
lib/unprof.rb | 8 +
rails/environment/profile.rb | 24 +
rails/example/example_test.rb | 9 +
rails/profile_test_helper.rb | 21 +
test/aggregate_test.rb | 121 +++
test/basic_test.rb | 283 ++++++
test/duplicate_names_test.rb | 32 +
test/exceptions_test.rb | 15 +
test/exclude_threads_test.rb | 54 ++
test/line_number_test.rb | 73 ++
test/measurement_test.rb | 121 +++
test/module_test.rb | 54 ++
test/no_method_class_test.rb | 13 +
test/prime.rb | 58 ++
test/prime_test.rb | 13 +
test/printers_test.rb | 71 ++
test/recursive_test.rb | 254 +++++
test/singleton_test.rb | 37 +
test/stack_test.rb | 138 +++
test/start_stop_test.rb | 95 ++
test/test_suite.rb | 23 +
test/thread_test.rb | 159 ++++
test/unique_call_path_test.rb | 206 +++++
58 files changed, 7884 insertions(+)
diff --git a/CHANGES b/CHANGES
new file mode 100644
index 0000000..83bcb9c
--- /dev/null
+++ b/CHANGES
@@ -0,0 +1,202 @@
+0.7.3 (2008-12-09)
+========================
+* Fixed compile error with new x86_64 code using GCC.
+
+
+0.7.2 (2008-12-08)
+========================
+* Fixed major bug in printing child methods in graph reports.
+
+* Fixes for supporting x86_64 machines (Diego Pettenò)
+
+
+0.7.1 (2008-11-28)
+========================
+* Added new AggregateCallInfo class for printers to
+ make results easier to read. Take this call sequence
+ for example:
+
+ A B C
+ | | |
+ Z A A
+ | |
+ Z Z
+
+ By default, ruby-prof will show that Z was called by 3 separate
+ instances of A. In an IDE that is helpful but in a text report
+ it is not since it makes the report much harder to read.
+ As a result, printers now aggregate together callers (and children),
+ matching ruby-prof's output from versions prior to 0.7.0.
+
+* Fixes for supporting x86_64 machines (Matt Sanford)
+
+
+0.7.0 (2008-11-04)
+========================
+
+Features
+--------
+* Added two new methods - RubyProf.resume and RubyProf.pause.
+ RubyProf.resume takes an optional block, which ensures that
+ RubyProf.pause is called. For example:
+
+ 10.times do |i|
+ RubyProf.resume do
+ # Some long process
+ end
+ end
+
+ result = RubyProf.stop
+
+* Added support for profiling tests that use Ruby's built-in
+ unit test framework (ie, test derived from
+ Test::Unit::TestCase). To enable profiling simply add
+ the following line of code to your test class:
+
+ include RubyProf::Test
+
+ By default, profiling results are written to the current
+ processes working directory. To change this, or other
+ profiling options, simply modify the PROFILE_OPTIONS hash
+ table as needed.
+
+* Used the new support for profiling test cases to revamp
+ the way that Rails profiling works. For more information
+ please refer to RubyProf's documentation.
+
+* Keep track of call stack for each method to enable more
+ powerful profile visualizations in Integrated Development
+ Environments (Hin Boean, work supported by CodeGear).
+
+* Expose measurements to Ruby (Jeremy Kemper).
+
+* Add support for additional memory measurements modes in Ruby 1.9 (Jeremy Kemper).
+
+* Add support for Lloyd Hilaiel's Ruby patch for measuring total heap size.
+ See http://lloydforge.org/projects/ruby. (Jeremy Kemper).
+
+
+Fixes
+-------
+* RubyProf.profile no longer crashes if an exception is
+ thrown during a profiling run.
+
+* Measure memory in fractional kilobytes rather than rounding down (Jeremy Kemper)
+
+
+0.6.0 (2008-02-03)
+========================
+
+ruby-prof 0.6.0 adds support for Ruby 1.9 and memory profiling.
+
+Features
+--------
+* Added support for ruby 1.9 (Shugo Maeda)
+* Added support for outputting printer results to a String, Array or IO
+ object (Michael Granger)
+* Add new memory profiling mode. Note this mode depends on a
+ patched Ruby interpreter (Alexander Dymo)
+
+Fixes
+-------
+* Improvements to GraphHtmlPrinter including updated documentation,
+ fixes for min_time support, ability to specify templates using
+ strings or filenames, and table layout fixes (Makoto Kuwata)
+* Fixes to scaling factor for calltrees so that precision is not lost
+ due to the conversion to doubles (Sylvain Joyeux)
+* Changed constant ALLOCATED_OBJECTS to ALLOCATIONS in the C code to
+ match the Ruby code (Sylvain Joyeux)
+* Added support for calltree printer to ruby-prof binary script (Sylvain Joyeux)
+* Fix support for the allocator measure mode to extconf.rb (Sylvain Joyeux)
+* Honor measure mode when specified on the command line (Sylvain Joyeux)
+* Sorting of methods by total time was incorrect (Dan Fitch, Charlie Savage)
+* Fix ruby-prof to work with the latest version of GEMS (Alexander Dymo)
+* Always define MEASURE_CPU_TIME and MEASURE_ALLOCATIONS in Ruby code, but
+ set their values to nil if the functionality is not available.
+
+
+0.5.2 (2007-07-19)
+========================
+
+ruby-prof 0.5.2 is a bug fix release.
+
+Fixes
+-------
+* Include missing rails plugin
+
+
+0.5.1 (2007-07-18)
+========================
+
+ruby-prof 0.5.1 is a bug fix and performance release.
+
+Performance
+--------
+* Significantly reduced the number of thread lookups by
+ caching the last executed thread.
+
+Fixes
+-------
+* Properly escape method names in HTML reports
+* Fix use of -m and --min-percent command line switches
+* Default source file information to ruby_runtime#0 for c calls
+* Moved rails_plugin to top level so it is more obvious
+* Updated rails_plugin to write reports to the current
+ Rails log directory
+* Added additional tests
+
+
+0.5.0 (2007-07-09)
+========================
+
+Features
+--------
+* Added support for timing multi-threaded applications
+* Added support for 64 bit systems (patch from Diego 'Flameeyes' Petten)
+* Added suport for outputting data in the format used by
+ KCacheGrind (patch from Carl Shimer)
+* Add filename and line numbers to call tree information (patch from Carl Shimer)
+* Added Visual Studio 2005 project file.
+* Added replace-progname switch, als rcov.
+* Added better support for recursive methods
+* Added better support for profiling Rails applications
+
+Fixes
+-------
+* Fixes bug when the type of an attached object (singleton) is inherited
+ from T_OBJECT as opposed to being a T_OBJECT (identified by Francis Cianfrocca)
+* ruby-prof now works in IRB.
+* Fix sort order in reports.
+* Fixed rdoc compile error.
+* Fix tabs in erb template for graph html report on windows.
+
+0.4.1 (2006-06-26)
+========================
+
+Features
+--------
+* Added a RubyProf.running? method to indicate whether a profile is in progress.
+* Added tgz and zip archives to release
+
+Fixes
+-------
+* Duplicate method names are now allowed
+* The documentation has been updated to show the correct API usage is RubyProf.stop not RubyProf.end
+
+
+0.4.0 (2006-06-16)
+========================
+Features
+--------
+* added support for call graphs
+* added support for printers. Currently there is a FlatPrinter,
+ GraphPrinter and GraphHtmlPrinter.
+* added support for recursive methods
+* added Windows support
+* now packaged as a RubyGem
+
+Fixes
+-------
+* Fixes bug where RubyProf would crash depending on the
+ way it was invoked - for example, it did not run when
+ used with Arachno Ruby's customized version of Ruby.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..ebc4eef
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,23 @@
+Copyright (C) 2005 Shugo Maeda <shugo at ruby-lang.org>
+All rights reserved.
+ *
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ *
+THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
\ No newline at end of file
diff --git a/README b/README
new file mode 100644
index 0000000..a5df9ce
--- /dev/null
+++ b/README
@@ -0,0 +1,436 @@
+= ruby-prof
+
+== Overview
+
+ruby-prof is a fast code profiler for Ruby. Its features include:
+
+* Speed - it is a C extension and therefore many times faster than the standard Ruby profiler.
+* Modes - Ruby prof can measure a number of different parameters, including
+ call times, memory usage and object allocations.
+* Reports - can generate text and cross-referenced html reports
+ - Flat Profiles - similar to the reports generated by the standard Ruby profiler
+ - Graph profiles - similar to GProf, these show how long a method runs, which methods call it and which methods it calls.
+ - Call tree profiles - outputs results in the calltree format suitable for the KCacheGrind profiling tool.
+* Threads - supports profiling multiple threads simultaneously
+* Recursive calls - supports profiling recursive method calls
+
+
+== Requirements
+
+ruby-prof requires Ruby 1.8.4 or higher.
+
+If you are running Linux or Unix you'll need a C compiler so the extension
+can be compiled when it is installed.
+
+If you are running Windows, then install the Windows specific RubyGem which
+includes an already built extension.
+
+
+== Install
+
+The easiest way to install ruby-prof is by using Ruby Gems. To install:
+
+<tt>gem install ruby-prof</tt>
+
+If you are running Windows, make sure to install the Win32 RubyGem which
+includes a pre-built binary. Due to a bug in ruby-gems, you cannot
+install the gem to a path that contains spaces
+(see http://rubyforge.org/tracker/?func=detail&aid=23003&group_id=126&atid=577).
+
+ruby-prof is also available as a tarred gzip archive and zip archive.
+
+== Usage
+
+There are three ways of running ruby-prof.
+
+
+=== ruby-prof executable
+
+The first is to use ruby-prof to run the Ruby program
+you want to profile. For more information refer to
+the ruby-prof documentation[link:files/bin/ruby-prof.html].
+
+
+=== ruby-prof API
+
+The second way is to use the ruby-prof API to profile
+particular segments of code.
+
+ require 'ruby-prof'
+
+ # Profile the code
+ RubyProf.start
+ ...
+ [code to profile]
+ ...
+ result = RubyProf.stop
+
+ # Print a flat profile to text
+ printer = RubyProf::FlatPrinter.new(result)
+ printer.print(STDOUT, 0)
+
+Alternatively, you can use a block to tell ruby-prof what
+to profile:
+
+ require 'ruby-prof'
+
+ # Profile the code
+ result = RubyProf.profile do
+ ...
+ [code to profile]
+ ...
+ end
+
+ # Print a graph profile to text
+ printer = RubyProf::GraphPrinter.new(result)
+ printer.print(STDOUT, 0)
+
+Starting with the 0.6.1 release, ruby-prof also supports pausing and resuming
+profiling runs.
+
+ require 'ruby-prof'
+
+ # Profile the code
+ RubyProf.start
+ [code to profile]
+ RubyProf.pause
+ [other code]
+ RubyProf.resume
+ [code to profile]
+ result = RubyProf.stop
+
+Note that resume will automatically call start if a profiling run
+has not yet started. In addition, resume can also take a block:
+
+ require 'ruby-prof'
+
+ # Profile the code
+ RubyProf.resume do
+ [code to profile]
+ end
+
+ data = RubyProf.stop
+
+With this usage, resume will automatically call pause at the
+end of the block.
+
+
+=== require unprof
+
+The third way of using ruby-prof is by requiring unprof.rb:
+
+ require 'unprof'
+
+This will start profiling immediately and will output the results
+using a flat profile report.
+
+This method is provided for backwards compatibility. Using
+{ruby-prof}[link:files/bin/ruby-prof.html] provides more flexibility.
+
+
+== Profiling Tests
+
+Starting with the 0.6.1 release, ruby-prof supports profiling tests cases
+written using Ruby's built-in unit test framework (ie, test derived from
+Test::Unit::TestCase). To enable profiling simply add the following line
+of code to your test class:
+
+ include RubyProf::Test
+
+Each test method is profiled separately. ruby-prof will run each test method
+once as a warmup and then ten additional times to gather profile data.
+Note that the profile data will *not* include the class's setup or
+teardown methods.
+
+Separate reports are generated for each method and saved, by default,
+in the test process's working directory. To change this, or other profiling
+options, modify your test class's PROFILE_OPTIONS hash table. To globally
+change test profiling options, modify RubyProf::Test::PROFILE_OPTIONS.
+
+
+== Profiling Rails
+
+To profile a Rails application it is vital to run it using production like
+settings (cache classes, cache view lookups, etc.). Otherwise, Rail's
+dependency loading code will overwhelm any time spent in the application
+itself (our tests show that Rails dependency loading causes a roughly 6x
+slowdown). The best way to do this is create a new Rails environment,
+profile.rb.
+
+So to profile Rails:
+
+1. Create a new profile.rb environment - or simply copy the example file
+ in ruby-prof/rails/environment/profile.rb
+
+2. Copy the file:
+
+ ruby-prof/rails/profile_test_helper.rb
+
+ To:
+
+ your_rails_app/test/profile_test_helper.rb
+
+3. Create a new test directory for profiling:
+
+ your_rails_app/test/profile
+
+
+4. Write unit, functional or integration tests specifically designed
+ to profile some part of your Rails application. At the top
+ of each test, replace this line:
+
+ require File.dirname(__FILE__) + '/../test_helper'
+
+ With:
+
+ require File.dirname(__FILE__) + '/../profile_test_helper'
+
+ For example:
+
+ require File.dirname(__FILE__) + '/../profile_test_helper'
+
+ class ExampleTest < Test::Unit::TestCase
+ include RubyProf::Test
+ fixtures ....
+
+ def test_stuff
+ puts "Test method"
+ end
+ end
+
+5. Now run your tests. Results will be written to:
+
+ your_rails_app/tmp/profile
+
+
+== Reports
+
+ruby-prof can generate a number of different reports:
+
+* Flat Reports
+* Graph Reports
+* HTML Graph Reports
+* Call graphs
+
+Flat profiles show the overall time spent in each method. They
+are a good of quickly identifying which methods take the most time.
+An example of a flat profile and an explanation can be found in
+{examples/flat.txt}[link:files/examples/flat_txt.html].
+
+Graph profiles also show the overall time spent in each method.
+In addition, they also show which methods call the current
+method and which methods its calls. Thus they are good for
+understanding how methods gets called and provide insight into
+the flow of your program. An example text graph profile
+is located at {examples/graph.txt}[link:files/examples/graph_txt.html].
+
+HTML Graph profiles are the same as graph profiles, except
+output is generated in hyper-linked HTML. Since graph profiles
+can be quite large, the embedded links make it much easier to
+navigate the results. An example html graph profile
+is located at {examples/graph.html}[link:files/examples/graph_html.html].
+
+HTML Graph profiles are the same as graph profiles, except
+output is generated in hyper-linked HTML. Since graph profiles
+can be quite large, the embedded links make it much easier to
+navigate the results. An example html graph profile
+is located at {examples/graph.html}[link:files/examples/graph_html.html].
+
+Call graphs output results in the calltree profile format which is used
+by KCachegrind. Call graph support was generously donated by Carl Shimer.
+More information about the format can be found at
+the {KCachegrind}[link:http://kcachegrind.sourceforge.net/cgi-bin/show.cgi/KcacheGrindCalltreeFormat] site.
+
+
+== Printers
+
+Reports are created by printers. Supported printers include:
+
+* RubyProf::FlatPrinter - Creates a flat report in text format
+* RubyProf::GraphPrinter - Creates a call graph report in text format
+* RubyProf::GraphHtmlPrinter - Creates a call graph report in HTML (separate files per thread)
+* RubyProf::CallTreePrinter - Creates a call tree report compatible with KCachegrind.
+
+To use a printer:
+
+ result = RubyProf.end
+ printer = RubyProf::GraphPrinter.new(result)
+ printer.print(STDOUT, 0)
+
+The first parameter is any writable IO object such as STDOUT or a file.
+The second parameter, which has a default value of 0, specifies
+the minimum percentage a method must take to be printed. Percentages
+should be specified as integers in the range 0 to 100. For more
+information please see the documentation for the different printers.
+
+
+== Measurements
+
+Depending on the mode and platform, ruby-prof can measure various
+aspects of a Ruby program. Supported measurements include:
+
+* process time (RubyProf::PROCESS_TIME)
+* wall time (RubyProf::WALL_TIME)
+* cpu time (RubyProf::CPU_TIME)
+* object allocations (RubyProf::ALLOCATIONS)
+* memory usage (RubyProf::MEMORY)
+* garbage collections runs (RubyProf::GC_RUNS)
+* garbage collection time (RubyProf::GC_TIME)
+
+
+Process time measures the time used by a process between any two moments.
+It is unaffected by other processes concurrently running
+on the system. Note that Windows does not support measuring process
+times - therefore, all measurements on Windows use wall time.
+
+Wall time measures the real-world time elapsed between any two moments.
+If there are other processes concurrently running on the system
+that use significant CPU or disk time during a profiling run
+then the reported results will be too large.
+
+CPU time uses the CPU clock counter to measure time. The returned
+values are dependent on the correctly setting the CPU's frequency.
+This mode is only supported on Pentium or PowerPC platforms.
+
+Object allocation reports show how many objects each method in
+a program allocates. This support was added by Sylvain Joyeux
+and requires a patched Ruby interpreter. For more information and
+the patch, please see:
+http://rubyforge.org/tracker/index.php?func=detail&aid=11497&group_id=426&atid=1700
+
+Memory usage reports show how much memory each method in a program
+uses. This support was added by Alexander Dymo and requires a
+patched Ruby interpreter. For more information, see:
+http://rubyforge.org/tracker/index.php?func=detail&aid=17676&group_id=1814&atid=7062
+
+Garbage collection runs report how many times Ruby's garbage collector
+is invoked during a profiling session. This support was added by Jeremy
+Kemper and requires a patched Ruby interpreter. For more information, see:
+http://rubyforge.org/tracker/index.php?func=detail&aid=17676&group_id=1814&atid=7062
+
+Garbage collection time reports how much time is spent in Ruby's garbage collector
+during a profiling session. This support was added by Jeremy Kemper
+and requires a patched Ruby interpreter. For more information, see:
+http://rubyforge.org/tracker/index.php?func=detail&aid=17676&group_id=1814&atid=7062
+
+To set the measurement:
+
+* RubyProf.measure_mode = RubyProf::PROCESS_TIME
+* RubyProf.measure_mode = RubyProf::WALL_TIME
+* RubyProf.measure_mode = RubyProf::CPU_TIME
+* RubyProf.measure_mode = RubyProf::ALLOCATIONS
+* RubyProf.measure_mode = RubyProf::MEMORY
+* RubyProf.measure_mode = RubyProf::GC_RUNS
+* RubyProf.measure_mode = RubyProf::GC_TIME
+
+The default value is RubyProf::PROCESS_TIME.
+
+You may also specify the measure_mode by using the RUBY_PROF_MEASURE_MODE
+environment variable:
+
+* export RUBY_PROF_MEASURE_MODE=process
+* export RUBY_PROF_MEASURE_MODE=wall
+* export RUBY_PROF_MEASURE_MODE=cpu
+* export RUBY_PROF_MEASURE_MODE=allocations
+* export RUBY_PROF_MEASURE_MODE=memory
+* export RUBY_PROF_MEASURE_MODE=gc_runs
+* export RUBY_PROF_MEASURE_MODE=gc_time
+
+Note that these values have changed since ruby-prof-0.3.0.
+
+On Linux, process time is measured using the clock method provided
+by the C runtime library. Note that the clock method does not
+report time spent in the kernel or child processes and therefore
+does not measure time spent in methods such as Kernel.sleep method.
+If you need to measure these values, then use wall time. Wall time
+is measured using the gettimeofday kernel method.
+
+On Windows, timings are always wall times. If you set the clock
+mode to PROCESS_TIME, then timing are read using the clock method
+provided by the C runtime library. Note though, these values are
+wall times on Windows and not process times like on Linux.
+Wall time is measured using the GetLocalTime API.
+
+If you use wall time, the results will be affected by other
+processes running on your computer, network delays, disk access,
+etc. As result, for the best results, try to make sure your
+computer is only performing your profiling run and is
+otherwise quiescent.
+
+On both platforms, cpu time is measured using the RDTSC assembly
+function provided by the Pentium and PowerPC platforms. CPU time
+is dependent on the cpu's frequency. On Linux, ruby-prof attempts
+to read this value from "/proc/cpuinfo." On Windows, you must
+specify the clock frequency. This can be done using the
+RUBY_PROF_CPU_FREQUENCY environment variable:
+
+ export RUBY_PROF_CPU_FREQUENCY=<value>
+
+You can also directly set the cpu frequency by calling:
+
+ RubyProf.cpu_frequency = <value>
+
+
+== Recursive Calls
+
+Recursive calls occur when method A calls method A and cycles
+occur when method A calls method B calls method C calls method A.
+ruby-prof detects both direct recursive calls and cycles. Both
+are indicated in reports by a dash and number following a method
+name. For example, here is a flat profile from the test method
+RecursiveTest#test_recursive:
+
+
+%self total self wait child calls name
+100.00 2.00 2.00 0.00 0.00 2 Kernel#sleep
+ 0.00 2.00 0.00 0.00 2.00 0 RecursiveTest#test_cycle
+ 0.00 0.00 0.00 0.00 0.00 2 Fixnum#==
+ 0.00 0.00 0.00 0.00 0.00 2 Fixnum#-
+ 0.00 1.00 0.00 0.00 1.00 1 Object#sub_cycle-1
+ 0.00 2.00 0.00 0.00 2.00 1 Object#sub_cycle
+ 0.00 2.00 0.00 0.00 2.00 1 Object#cycle
+ 0.00 1.00 0.00 0.00 1.00 1 Object#cycle-1
+
+Notice the presence of Object#cycle and Object#cycle-1. The -1 means
+the method was either recursively called (directly or indirectly).
+
+However, the self time values for recursive calls should always
+be accurate. It is also believed that the total times are
+accurate, but these should be carefully analyzed to verify their veracity.
+
+
+== Multi-threaded Applications
+
+Unfortunately, Ruby does not provide an internal api
+for detecting thread context switches. As a result, the
+timings ruby-prof reports for each thread may be slightly
+inaccurate. In particular, this will happen for newly
+spanned threads that immediately go to sleep. For instance,
+if you use Ruby's timeout library to wait for 2 seconds,
+the 2 seconds will be assigned to the foreground thread
+and not the newly created background thread. These errors
+can largely be avoided if the background thread performs an
+operation before going to sleeep.
+
+
+== Performance
+
+Significant effort has been put into reducing ruby-prof's overhead
+as much as possible. Our tests show that the overhead associated
+with profiling code varies considerably with the code being
+profiled. Most programs will run approximately twice as slow
+while highly recursive programs (like the fibonacci series test)
+will run three times slower.
+
+
+== Windows Binary
+
+The Windows binary is built with the latest version of MinGW. The source
+repository also includes a Microsoft VC++ 2005 solution. If you wish to run
+a debug version of ruby-prof on Windows, then it is highly recommended
+you use VC++.
+
+
+== License
+
+See LICENSE for license information.
diff --git a/Rakefile b/Rakefile
new file mode 100644
index 0000000..2287b61
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,123 @@
+require 'rubygems'
+require 'rake/gempackagetask'
+require 'rake/rdoctask'
+require 'rake/testtask'
+require 'date'
+
+# ------- Version ----
+# Read version from header file
+version_header = File.read('ext/version.h')
+match = version_header.match(/RUBY_PROF_VERSION\s*["](\d.+)["]/)
+raise(RuntimeError, "Could not determine RUBY_PROF_VERSION") if not match
+RUBY_PROF_VERSION = match[1]
+
+
+# ------- Default Package ----------
+FILES = FileList[
+ 'Rakefile',
+ 'README',
+ 'LICENSE',
+ 'CHANGES',
+ 'bin/*',
+ 'doc/**/*',
+ 'examples/*',
+ 'ext/*',
+ 'ext/mingw/Rakefile',
+ 'ext/mingw/build.rake',
+ 'ext/vc/*.sln',
+ 'ext/vc/*.vcproj',
+ 'lib/**/*',
+ 'rails/**/*',
+ 'test/*'
+]
+
+# Default GEM Specification
+default_spec = Gem::Specification.new do |spec|
+ spec.name = "ruby-prof"
+
+ spec.homepage = "http://rubyforge.org/projects/ruby-prof/"
+ spec.summary = "Fast Ruby profiler"
+ spec.description = <<-EOF
+ruby-prof is a fast code profiler for Ruby. It is a C extension and
+therefore is many times faster than the standard Ruby profiler. It
+supports both flat and graph profiles. For each method, graph profiles
+show how long the method ran, which methods called it and which
+methods it called. RubyProf generate both text and html and can output
+it to standard out or to a file.
+EOF
+
+ spec.version = RUBY_PROF_VERSION
+
+ spec.author = "Shugo Maeda and Charlie Savage"
+ spec.email = "shugo at ruby-lang.org and cfis at savagexi.com"
+ spec.platform = Gem::Platform::RUBY
+ spec.require_path = "lib"
+ spec.bindir = "bin"
+ spec.executables = ["ruby-prof"]
+ spec.extensions = ["ext/extconf.rb"]
+ spec.files = FILES.to_a
+ spec.test_files = Dir["test/test_*.rb"]
+
+
+ spec.required_ruby_version = '>= 1.8.4'
+ spec.date = DateTime.now
+ spec.rubyforge_project = 'ruby-prof'
+
+ # rdoc
+ spec.has_rdoc = true
+end
+
+# Rake task to build the default package
+Rake::GemPackageTask.new(default_spec) do |pkg|
+ pkg.need_tar = true
+ #pkg.need_zip = true
+end
+
+
+# ------- Windows Package ----------
+if RUBY_PLATFORM.match(/win32/)
+ binaries = (FileList['ext/mingw/*.so',
+ 'ext/mingw/*.dll*'])
+
+ # Windows specification
+ win_spec = default_spec.clone
+ win_spec.extensions = ['ext/mingw/Rakefile']
+ win_spec.platform = Gem::Platform::CURRENT
+ win_spec.files += binaries.to_a
+
+ # Rake task to build the windows package
+ Rake::GemPackageTask.new(win_spec) do |pkg|
+ end
+end
+
+# --------- RDoc Documentation ------
+desc "Generate rdoc documentation"
+Rake::RDocTask.new("rdoc") do |rdoc|
+ rdoc.rdoc_dir = 'doc'
+ rdoc.title = "ruby-prof"
+ # Show source inline with line numbers
+ rdoc.options << "--inline-source" << "--line-numbers"
+ # Make the readme file the start page for the generated html
+ rdoc.options << '--main' << 'README'
+ rdoc.rdoc_files.include('bin/**/*',
+ 'doc/*.rdoc',
+ 'examples/flat.txt',
+ 'examples/graph.txt',
+ 'examples/graph.html',
+ 'lib/**/*.rb',
+ 'ext/ruby_prof.c',
+ 'ext/version.h',
+ 'ext/measure_*.h',
+ 'README',
+ 'LICENSE')
+end
+
+task :default => :package
+
+desc 'Run the ruby-prof test suite'
+Rake::TestTask.new do |t|
+ t.libs += %w(lib ext test)
+ t.test_files = Dir['test/test_suite.rb']
+ t.verbose = true
+ t.warning = true
+end
\ No newline at end of file
diff --git a/bin/ruby-prof b/bin/ruby-prof
new file mode 100755
index 0000000..b3f72e0
--- /dev/null
+++ b/bin/ruby-prof
@@ -0,0 +1,207 @@
+#! /usr/bin/env ruby
+
+# == Synopsis
+#
+# Profiles a Ruby program.
+#
+# == Usage
+#
+# ruby_prof [options] <script.rb> [--] [script-options]"
+#
+# Options:
+# -p, --printer=printer Select a printer:
+# flat - Prints a flat profile as text (default).
+# graph - Prints a graph profile as text.
+# graph_html - Prints a graph profile as html.
+# call_tree - format for KCacheGrind
+# -f, --file=path Output results to a file instead of standard out.
+# -m, --min_percent=min_percent The minimum percent a method must take before ',
+# being included in output reports. Should be an
+# integer between 1 and 100. 0 means all methods are printed.
+# --mode=measure_mode Select a measurement mode:
+# process - Use process time (default).
+# wall - Use wall time.
+# cpu - Use the CPU clock counter
+# (only supported on Pentium and PowerPCs).
+# allocations - Tracks object allocations
+# (requires a patched Ruby interpreter).
+# memory - Tracks total memory size
+# (requires a patched Ruby interpreter).
+# gc_runs - Tracks number of garbage collection runs
+# (requires a patched Ruby interpreter).
+# gc_time - Tracks time spent doing garbage collection
+# (requires a patched Ruby interpreter).
+# --replace-progname Replace $0 when loading the .rb files.
+# --specialized-instruction Turn on specialized instruction.
+# -h, --help Show help message
+# --version Show version
+#
+#
+# See also: {flat profiles}[link:files/examples/flat_txt.html], {graph profiles}[link:files/examples/graph_txt.html], {html graph profiles}[link:files/examples/graph_html.html]
+#
+
+require 'ostruct'
+require 'optparse'
+require 'ruby-prof'
+
+options = OpenStruct.new
+options.measure_mode = RubyProf::PROCESS_TIME
+options.printer = RubyProf::FlatPrinter
+options.min_percent = 0
+options.file = nil
+options.replace_prog_name = false
+options.specialized_instruction = false
+
+opts = OptionParser.new do |opts|
+ opts.banner = "ruby_prof #{RubyProf::VERSION}\n" +
+ "Usage: ruby_prof [options] <script.rb> [--] [script-options]"
+
+ opts.separator ""
+ opts.separator "Options:"
+
+
+ opts.on('-p printer', '--printer=printer', [:flat, :graph, :graph_html, :call_tree],
+ 'Select a printer:',
+ ' flat - Prints a flat profile as text (default).',
+ ' graph - Prints a graph profile as text.',
+ ' graph_html - Prints a graph profile as html.',
+ ' call_tree - format for KCacheGrind' ) do |printer|
+
+
+ case printer
+ when :flat
+ options.printer = RubyProf::FlatPrinter
+ when :graph
+ options.printer = RubyProf::GraphPrinter
+ when :graph_html
+ options.printer = RubyProf::GraphHtmlPrinter
+ when :call_tree
+ options.printer = RubyProf::CallTreePrinter
+ end
+ end
+
+ opts.on('-m min_percent', '--min_percent=min_percent', Float,
+ 'The minimum percent a method must take before ',
+ ' being included in output reports.',
+ ' this option is not supported for call tree.') do |min_percent|
+ options.min_percent = min_percent
+ end
+
+ opts.on('-f path', '--file=path',
+ 'Output results to a file instead of standard out.') do |file|
+ options.file = file
+ end
+
+ opts.on('--mode=measure_mode',
+ [:process, :wall, :cpu, :allocations, :memory, :gc_runs, :gc_time],
+ 'Select what ruby-prof should measure:',
+ ' process - Process time (default).',
+ ' wall - Wall time.',
+ ' cpu - CPU time (Pentium and PowerPCs only).',
+ ' allocations - Object allocations (requires patched Ruby interpreter).',
+ ' memory - Allocated memory in KB (requires patched Ruby interpreter).',
+ ' gc_runs - Number of garbage collections (requires patched Ruby interpreter).',
+ ' gc_time - Time spent in garbage collection (requires patched Ruby interpreter).') do |measure_mode|
+
+ case measure_mode
+ when :process
+ options.measure_mode = RubyProf::PROCESS_TIME
+ when :wall
+ options.measure_mode = RubyProf::WALL_TIME
+ when :cpu
+ options.measure_mode = RubyProf::CPU_TIME
+ when :allocations
+ options.measure_mode = RubyProf::ALLOCATIONS
+ when :memory
+ options.measure_mode = RubyProf::MEMORY
+ when :gc_runs
+ options.measure_mode = RubyProf::GC_RUNS
+ when :gc_time
+ options.measure_mode = RubyProf::GC_TIME
+ end
+ end
+
+ opts.on("--replace-progname", "Replace $0 when loading the .rb files.") do
+ options.replace_prog_name = true
+ end
+
+ if defined?(VM)
+ opts.on("--specialized-instruction", "Turn on specified instruction.") do
+ options.specialized_instruction = true
+ end
+ end
+
+ opts.on_tail("-h", "--help", "Show help message") do
+ puts opts
+ exit
+ end
+
+ opts.on_tail("-v", "--version", "Show version") do
+ puts "ruby_prof " + RubyProf::VERSION
+ exit
+ end
+end
+
+begin
+ opts.parse! ARGV
+rescue OptionParser::InvalidOption, OptionParser::InvalidArgument,
+ OptionParser::MissingArgument => e
+ puts opts
+ puts
+ puts e.message
+ exit(-1)
+end
+
+# Make sure the user specified at least one file
+if ARGV.length < 1
+ puts opts
+ puts ""
+ puts "Must specify a script to run"
+ exit(-1)
+end
+
+
+# Install at_exit handler. It is important that we do this
+# before loading the scripts so our at_exit handler run
+# *after* any other one that will be installed.
+
+at_exit {
+ # Stop profiling
+ result = RubyProf.stop
+
+ # Create a printer
+ printer = options.printer.new(result)
+
+ # Get output
+ if options.file
+ File.open(options.file, 'w') do |file|
+ printer.print(file, {:min_percent => options.min_percent})
+ end
+ else
+ # Print out results
+ printer.print(STDOUT, {:min_percent => options.min_percent})
+ end
+}
+
+# Now set measure mode
+RubyProf.measure_mode = options.measure_mode
+
+# Set VM compile option
+if defined?(VM)
+ VM::InstructionSequence.compile_option = {
+ :trace_instruction => true,
+ :specialized_instruction => options.specialized_instruction
+ }
+end
+
+# Get the script we will execute
+script = ARGV.shift
+if options.replace_prog_name
+ $0 = File.expand_path(script)
+end
+
+# Start profiling
+RubyProf.start
+
+# Load the script
+load script
\ No newline at end of file
diff --git a/examples/flat.txt b/examples/flat.txt
new file mode 100644
index 0000000..28d9afe
--- /dev/null
+++ b/examples/flat.txt
@@ -0,0 +1,55 @@
+= Flat Profiles
+
+Flat profiles show the total amount of time spent in each method.
+As an example, here is the output from running printers_test.rb.
+
+Thread ID: 21277412
+ %self cumulative total self children calls self/call total/call name
+ 46.34 4.06 8.72 4.06 4.66 501 0.01 0.02 Integer#upto
+ 23.89 6.16 2.09 2.09 0.00 61 0.03 0.03 Kernel.sleep
+ 15.12 7.48 1.33 1.33 0.00 250862 0.00 0.00 Fixnum#%
+ 14.13 8.72 1.24 1.24 0.00 250862 0.00 0.00 Fixnum#==
+ 0.18 8.74 0.02 0.02 0.00 1 0.02 0.02 Array#each_index
+ 0.17 8.75 6.64 0.01 6.63 500 0.00 0.01 Object#is_prime
+ 0.17 8.77 6.66 0.01 6.64 1 0.01 6.66 Array#select
+ 0.00 8.77 0.00 0.00 0.00 501 0.00 0.00 Fixnum#-
+ 0.00 8.77 0.00 0.00 0.00 1 0.00 0.00 Array#first
+ 0.00 8.77 0.00 0.00 0.00 1 0.00 0.00 Array#length
+ 0.00 8.77 0.00 0.00 0.00 1 0.00 0.00 Array#initialize
+ 0.00 8.77 8.77 0.00 8.77 1 0.00 8.77 Object#run_primes
+ 0.00 8.77 0.00 0.00 0.00 1 0.00 0.00 Integer#to_int
+ 0.00 8.77 6.66 0.00 6.66 1 0.00 6.66 Object#find_primes
+ 0.00 8.77 2.09 0.00 2.09 1 0.00 2.09 Object#find_largest
+ 0.00 8.77 0.02 0.00 0.02 1 0.00 0.02 Object#make_random_array
+ 0.00 8.77 0.00 0.00 0.00 1 0.00 0.00 Class#new
+ 0.00 8.77 0.00 0.00 0.00 500 0.00 0.00 Array#[]=
+ 0.00 8.77 0.00 0.00 0.00 61 0.00 0.00 Fixnum#>
+ 0.00 8.77 0.00 0.00 0.00 61 0.00 0.00 Array#[]
+ 0.00 8.77 8.77 0.00 8.77 1 0.00 8.77 #toplevel
+ 0.00 8.77 0.00 0.00 0.00 500 0.00 0.00 Kernel.rand
+
+All values are in seconds.
+
+The columns are:
+
+ %self - The percentage of time spent in this method, derived from self_time/total_time
+ cumulative - The sum of the time spent in this method and all the methods listed above it.
+ total - The time spent in this method and its children.
+ self - The time spent in this method.
+ children - The time spent in this method's children.
+ calls - The number of times this method was called.
+ self/call - The average time spent per call in this method.
+ total/call - The average time spent per call in this method and its children.
+ name - The name of the method.
+
+Methods are sorted based on %self, therefore the methods that execute the longest are listed
+first.
+
+The interpretation of method names is:
+* #toplevel - The root method that calls all other methods
+* MyObject#test - An instance method "test" of the class "MyObject"
+* <Object:MyObject>#test - The <> characters indicate a singleton method on a singleton class.
+
+For example, wee can see that Integer#upto took the most time, 4.06 seconds. An additional
+4.66 seconds were spent in its children, for a total time of 8.72 seconds.
+
diff --git a/examples/graph.html b/examples/graph.html
new file mode 100644
index 0000000..46b1f9f
--- /dev/null
+++ b/examples/graph.html
@@ -0,0 +1,823 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+<style media="all" type="text/css">
+ table {
+ border-collapse: collapse;
+ border: 1px solid #CCC;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 9pt;
+ line-height: normal;
+ }
+
+ th {
+ text-align: center;
+ border-top: 1px solid #FB7A31;
+ border-bottom: 1px solid #FB7A31;
+ background: #FFC;
+ padding: 0.3em;
+ border-left: 1px solid silver;
+ }
+
+ tr.break td {
+ border: 0;
+ border-top: 1px solid #FB7A31;
+ padding: 0;
+ margin: 0;
+ }
+
+ tr.method td {
+ font-weight: bold;
+ }
+
+ td {
+ padding: 0.3em;
+ }
+
+ td:first-child {
+ width: 190px;
+ }
+
+ td {
+ border-left: 1px solid #CCC;
+ text-align: center;
+ }
+ </style>
+</head>
+<body>
+<h1> Graph Profile</h1>
+<p>Graph profiles show how long each method runs, which methods call it
+ and which methods it calls. To understand how to read a graph profile, refer to
+ the <a href="graph.txt">documentation</a> for the text graph report. </p>
+<p>The main advantage of an HTML graph format versus the text format is the use of
+ hyperlinks to jump between methods. This makes it easier to understand the flow
+of control through a program and which methods take the most time.</p>
+<p>Below is the output from running printers_test.rb reproduced in HTML. </p>
+<p>Profile Report</p>
+
+<table>
+ <tr>
+ <th>Thread ID</th>
+ <th>Total Time</th>
+ </tr>
+ <tr>
+ <td><a href="#21277412">21277412</a></td>
+ <td>8.766</td>
+ </tr>
+</table>
+
+<h2><a name="21277412">Thread 21277412</a></h2>
+<table>
+ <tr>
+ <th> %Total</th>
+ <th> %Self</th>
+ <th> Total</th>
+ <th> Self</th>
+ <th> Children</th>
+ <th> Calls</th>
+ <th>Name</th>
+ </tr>
+
+ <tr class="method">
+ <td> 100.00%</td>
+ <td> 0.00%</td>
+ <td> 8.77</td>
+ <td> 0.00</td>
+ <td> 8.77</td>
+ <td> 1</td>
+ <td><a name="_toplevel_21277412">#toplevel</a></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 8.77</td>
+ <td> 0.00</td>
+ <td> 8.77</td>
+ <td> 1/1</td>
+ <td><a href="#Object_run_primes_21277412">Object#run_primes</a></td>
+ </tr>
+
+ <tr class="break">
+ <td colspan="7"></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 8.77</td>
+ <td> 0.00</td>
+ <td> 8.77</td>
+ <td> 1/1</td>
+ <td><a href="#_toplevel_21277412">#toplevel</a></td>
+ </tr>
+ <tr class="method">
+ <td> 100.00%</td>
+ <td> 0.00%</td>
+ <td> 8.77</td>
+ <td> 0.00</td>
+ <td> 8.77</td>
+ <td> 1</td>
+ <td><a name="Object_run_primes_21277412">Object#run_primes</a></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 0.02</td>
+ <td> 0.00</td>
+ <td> 0.02</td>
+ <td> 1/1</td>
+ <td><a href="#Object_make_random_array_21277412">Object#make_random_array</a></td>
+ </tr>
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 2.09</td>
+ <td> 0.00</td>
+ <td> 2.09</td>
+ <td> 1/1</td>
+ <td><a href="#Object_find_largest_21277412">Object#find_largest</a></td>
+ </tr>
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 6.66</td>
+ <td> 0.00</td>
+ <td> 6.66</td>
+ <td> 1/1</td>
+ <td><a href="#Object_find_primes_21277412">Object#find_primes</a></td>
+ </tr>
+
+ <tr class="break">
+ <td colspan="7"></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 6.63</td>
+ <td> 4.06</td>
+ <td> 2.56</td>
+ <td> 500/501</td>
+ <td><a href="#Object_is_prime_21277412">Object#is_prime</a></td>
+ </tr>
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 2.09</td>
+ <td> 0.00</td>
+ <td> 2.09</td>
+ <td> 1/501</td>
+ <td><a href="#Object_find_largest_21277412">Object#find_largest</a></td>
+ </tr>
+ <tr class="method">
+ <td> 99.48%</td>
+ <td> 46.34%</td>
+ <td> 8.72</td>
+ <td> 4.06</td>
+ <td> 4.66</td>
+ <td> 501</td>
+ <td><a name="Integer_upto_21277412">Integer#upto</a></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 61/61</td>
+ <td><a href="#Array_[]_21277412">Array#[]</a></td>
+ </tr>
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 61/61</td>
+ <td><a href="#Fixnum_>_21277412">Fixnum#_</a></td>
+ </tr>
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 2.09</td>
+ <td> 2.09</td>
+ <td> 0.00</td>
+ <td> 61/61</td>
+ <td><a href="#Kernel_sleep_21277412">Kernel.sleep</a></td>
+ </tr>
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 1.24</td>
+ <td> 1.24</td>
+ <td> 0.00</td>
+ <td> 250862/250862</td>
+ <td><a href="#Fixnum____21277412">Fixnum#==</a></td>
+ </tr>
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 1.33</td>
+ <td> 1.33</td>
+ <td> 0.00</td>
+ <td> 250862/250862</td>
+ <td><a href="#Fixnum_%_21277412">Fixnum#%</a></td>
+ </tr>
+
+ <tr class="break">
+ <td colspan="7"></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 6.66</td>
+ <td> 0.01</td>
+ <td> 6.64</td>
+ <td> 1/1</td>
+ <td><a href="#Object_find_primes_21277412">Object#find_primes</a></td>
+ </tr>
+ <tr class="method">
+ <td> 75.93%</td>
+ <td> 0.17%</td>
+ <td> 6.66</td>
+ <td> 0.01</td>
+ <td> 6.64</td>
+ <td> 1</td>
+ <td><a name="Array_select_21277412">Array#select</a></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 6.64</td>
+ <td> 0.01</td>
+ <td> 6.63</td>
+ <td> 500/500</td>
+ <td><a href="#Object_is_prime_21277412">Object#is_prime</a></td>
+ </tr>
+
+ <tr class="break">
+ <td colspan="7"></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 6.66</td>
+ <td> 0.00</td>
+ <td> 6.66</td>
+ <td> 1/1</td>
+ <td><a href="#Object_run_primes_21277412">Object#run_primes</a></td>
+ </tr>
+ <tr class="method">
+ <td> 75.93%</td>
+ <td> 0.00%</td>
+ <td> 6.66</td>
+ <td> 0.00</td>
+ <td> 6.66</td>
+ <td> 1</td>
+ <td><a name="Object_find_primes_21277412">Object#find_primes</a></td>
+ </tr>
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 6.66</td>
+ <td> 0.01</td>
+ <td> 6.64</td>
+ <td> 1/1</td>
+ <td><a href="#Array_select_21277412">Array#select</a></td>
+ </tr>
+ <tr class="break">
+ <td colspan="7"></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 6.64</td>
+ <td> 0.01</td>
+ <td> 6.63</td>
+ <td> 500/500</td>
+ <td><a href="#Array_select_21277412">Array#select</a></td>
+ </tr>
+ <tr class="method">
+ <td> 75.76%</td>
+ <td> 0.17%</td>
+ <td> 6.64</td>
+ <td> 0.01</td>
+ <td> 6.63</td>
+ <td> 500</td>
+ <td><a name="Object_is_prime_21277412">Object#is_prime</a></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 500/501</td>
+ <td><a href="#Fixnum_-_21277412">Fixnum#-</a></td>
+ </tr>
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 6.63</td>
+ <td> 4.06</td>
+ <td> 2.56</td>
+ <td> 500/501</td>
+ <td><a href="#Integer_upto_21277412">Integer#upto</a></td>
+ </tr>
+
+ <tr class="break">
+ <td colspan="7"></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 2.09</td>
+ <td> 0.00</td>
+ <td> 2.09</td>
+ <td> 1/1</td>
+ <td><a href="#Object_run_primes_21277412">Object#run_primes</a></td>
+ </tr>
+ <tr class="method">
+ <td> 23.89%</td>
+ <td> 0.00%</td>
+ <td> 2.09</td>
+ <td> 0.00</td>
+ <td> 2.09</td>
+ <td> 1</td>
+ <td><a name="Object_find_largest_21277412">Object#find_largest</a></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 1/501</td>
+ <td><a href="#Fixnum_-_21277412">Fixnum#-</a></td>
+ </tr>
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 2.09</td>
+ <td> 0.00</td>
+ <td> 2.09</td>
+ <td> 1/501</td>
+ <td><a href="#Integer_upto_21277412">Integer#upto</a></td>
+ </tr>
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 1/1</td>
+ <td><a href="#Array_first_21277412">Array#first</a></td>
+ </tr>
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 1/1</td>
+ <td><a href="#Array_length_21277412">Array#length</a></td>
+ </tr>
+
+ <tr class="break">
+ <td colspan="7"></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 2.09</td>
+ <td> 2.09</td>
+ <td> 0.00</td>
+ <td> 61/61</td>
+ <td><a href="#Integer_upto_21277412">Integer#upto</a></td>
+ </tr>
+ <tr class="method">
+ <td> 23.89%</td>
+ <td> 23.89%</td>
+ <td> 2.09</td>
+ <td> 2.09</td>
+ <td> 0.00</td>
+ <td> 61</td>
+ <td><a name="Kernel_sleep_21277412">Kernel.sleep</a></td>
+ </tr>
+
+
+ <tr class="break">
+ <td colspan="7"></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 1.33</td>
+ <td> 1.33</td>
+ <td> 0.00</td>
+ <td> 250862/250862</td>
+ <td><a href="#Integer_upto_21277412">Integer#upto</a></td>
+ </tr>
+ <tr class="method">
+ <td> 15.12%</td>
+ <td> 15.12%</td>
+ <td> 1.33</td>
+ <td> 1.33</td>
+ <td> 0.00</td>
+ <td> 250862</td>
+ <td><a name="Fixnum_%_21277412">Fixnum#%</a></td>
+ </tr>
+
+ <tr class="break">
+ <td colspan="7"></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 1.24</td>
+ <td> 1.24</td>
+ <td> 0.00</td>
+ <td> 250862/250862</td>
+ <td><a href="#Integer_upto_21277412">Integer#upto</a></td>
+ </tr>
+ <tr class="method">
+ <td> 14.13%</td>
+ <td> 14.13%</td>
+ <td> 1.24</td>
+ <td> 1.24</td>
+ <td> 0.00</td>
+ <td> 250862</td>
+ <td><a name="Fixnum____21277412">Fixnum#==</a></td>
+ </tr>
+
+
+ <tr class="break">
+ <td colspan="7"></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 0.02</td>
+ <td> 0.00</td>
+ <td> 0.02</td>
+ <td> 1/1</td>
+ <td><a href="#Object_run_primes_21277412">Object#run_primes</a></td>
+ </tr>
+ <tr class="method">
+ <td> 0.18%</td>
+ <td> 0.00%</td>
+ <td> 0.02</td>
+ <td> 0.00</td>
+ <td> 0.02</td>
+ <td> 1</td>
+ <td><a name="Object_make_random_array_21277412">Object#make_random_array</a></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 0.02</td>
+ <td> 0.02</td>
+ <td> 0.00</td>
+ <td> 1/1</td>
+ <td><a href="#Array_each_index_21277412">Array#each_index</a></td>
+ </tr>
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 1/1</td>
+ <td><a href="#Class_new_21277412">Class#new</a></td>
+ </tr>
+
+ <tr class="break">
+ <td colspan="7"></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 0.02</td>
+ <td> 0.02</td>
+ <td> 0.00</td>
+ <td> 1/1</td>
+ <td><a href="#Object_make_random_array_21277412">Object#make_random_array</a></td>
+ </tr>
+ <tr class="method">
+ <td> 0.18%</td>
+ <td> 0.18%</td>
+ <td> 0.02</td>
+ <td> 0.02</td>
+ <td> 0.00</td>
+ <td> 1</td>
+ <td><a name="Array_each_index_21277412">Array#each_index</a></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 500/500</td>
+ <td><a href="#Kernel_rand_21277412">Kernel.rand</a></td>
+ </tr>
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 500/500</td>
+ <td><a href="#Array_[]__21277412">Array#[]=</a></td>
+ </tr>
+
+ <tr class="break">
+ <td colspan="7"></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 500/501</td>
+ <td><a href="#Object_is_prime_21277412">Object#is_prime</a></td>
+ </tr>
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 1/501</td>
+ <td><a href="#Object_find_largest_21277412">Object#find_largest</a></td>
+ </tr>
+ <tr class="method">
+ <td> 0.00%</td>
+ <td> 0.00%</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 501</td>
+ <td><a name="Fixnum_-_21277412">Fixnum#-</a></td>
+ </tr>
+
+
+ <tr class="break">
+ <td colspan="7"></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 1/1</td>
+ <td><a href="#Kernel_rand_21277412">Kernel.rand</a></td>
+ </tr>
+ <tr class="method">
+ <td> 0.00%</td>
+ <td> 0.00%</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 1</td>
+ <td><a name="Integer_to_int_21277412">Integer#to_int</a></td>
+ </tr>
+
+
+ <tr class="break">
+ <td colspan="7"></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 1/1</td>
+ <td><a href="#Object_find_largest_21277412">Object#find_largest</a></td>
+ </tr>
+ <tr class="method">
+ <td> 0.00%</td>
+ <td> 0.00%</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 1</td>
+ <td><a name="Array_first_21277412">Array#first</a></td>
+ </tr>
+
+
+ <tr class="break">
+ <td colspan="7"></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 1/1</td>
+ <td><a href="#Class_new_21277412">Class#new</a></td>
+ </tr>
+ <tr class="method">
+ <td> 0.00%</td>
+ <td> 0.00%</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 1</td>
+ <td><a name="Array_initialize_21277412">Array#initialize</a></td>
+ </tr>
+
+
+ <tr class="break">
+ <td colspan="7"></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 1/1</td>
+ <td><a href="#Object_find_largest_21277412">Object#find_largest</a></td>
+ </tr>
+ <tr class="method">
+ <td> 0.00%</td>
+ <td> 0.00%</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 1</td>
+ <td><a name="Array_length_21277412">Array#length</a></td>
+ </tr>
+
+
+ <tr class="break">
+ <td colspan="7"></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 1/1</td>
+ <td><a href="#Object_make_random_array_21277412">Object#make_random_array</a></td>
+ </tr>
+ <tr class="method">
+ <td> 0.00%</td>
+ <td> 0.00%</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 1</td>
+ <td><a name="Class_new_21277412">Class#new</a></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 1/1</td>
+ <td><a href="#Array_initialize_21277412">Array#initialize</a></td>
+ </tr>
+
+ <tr class="break">
+ <td colspan="7"></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 61/61</td>
+ <td><a href="#Integer_upto_21277412">Integer#upto</a></td>
+ </tr>
+ <tr class="method">
+ <td> 0.00%</td>
+ <td> 0.00%</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 61</td>
+ <td><a name="Fixnum_>_21277412">Fixnum#_</a></td>
+ </tr>
+
+
+ <tr class="break">
+ <td colspan="7"></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 61/61</td>
+ <td><a href="#Integer_upto_21277412">Integer#upto</a></td>
+ </tr>
+ <tr class="method">
+ <td> 0.00%</td>
+ <td> 0.00%</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 61</td>
+ <td><a name="Array_[]_21277412">Array#[]</a></td>
+ </tr>
+
+
+ <tr class="break">
+ <td colspan="7"></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 500/500</td>
+ <td><a href="#Array_each_index_21277412">Array#each_index</a></td>
+ </tr>
+ <tr class="method">
+ <td> 0.00%</td>
+ <td> 0.00%</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 500</td>
+ <td><a name="Array_[]__21277412">Array#[]=</a></td>
+ </tr>
+
+
+ <tr class="break">
+ <td colspan="7"></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 500/500</td>
+ <td><a href="#Array_each_index_21277412">Array#each_index</a></td>
+ </tr>
+ <tr class="method">
+ <td> 0.00%</td>
+ <td> 0.00%</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 500</td>
+ <td><a name="Kernel_rand_21277412">Kernel.rand</a></td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 0.00</td>
+ <td> 1/1</td>
+ <td><a href="#Integer_to_int_21277412">Integer#to_int</a></td>
+ </tr>
+
+ <tr class="break">
+ <td colspan="7"></td>
+ </tr>
+</table>
+</body>
+</html>
diff --git a/examples/graph.txt b/examples/graph.txt
new file mode 100644
index 0000000..99c071d
--- /dev/null
+++ b/examples/graph.txt
@@ -0,0 +1,170 @@
+= Graph Profiles
+
+Graph profiles show how long each method runs, which methods call it
+and which methods it calls.
+
+As an example, here is the output from running printers_test.rb:
+
+
+Thread ID: 21277412
+ %total %self total self children calls Name
+ --------------------------------------------------------------------------------
+ 100.00% 0.00% 8.77 0.00 8.77 1 #toplevel
+ 8.77 0.00 8.77 1/1 Object#run_primes
+ --------------------------------------------------------------------------------
+ 8.77 0.00 8.77 1/1 #toplevel
+ 100.00% 0.00% 8.77 0.00 8.77 1 Object#run_primes
+ 0.02 0.00 0.02 1/1 Object#make_random_array
+ 2.09 0.00 2.09 1/1 Object#find_largest
+ 6.66 0.00 6.66 1/1 Object#find_primes
+ --------------------------------------------------------------------------------
+ 6.63 4.06 2.56 500/501 Object#is_prime
+ 2.09 0.00 2.09 1/501 Object#find_largest
+ 99.48% 46.34% 8.72 4.06 4.66 501 Integer#upto
+ 0.00 0.00 0.00 61/61 Array#[]
+ 0.00 0.00 0.00 61/61 Fixnum#>
+ 2.09 2.09 0.00 61/61 Kernel.sleep
+ 1.24 1.24 0.00 250862/250862 Fixnum#==
+ 1.33 1.33 0.00 250862/250862 Fixnum#%
+ --------------------------------------------------------------------------------
+ 6.66 0.01 6.64 1/1 Object#find_primes
+ 75.93% 0.17% 6.66 0.01 6.64 1 Array#select
+ 6.64 0.01 6.63 500/500 Object#is_prime
+ --------------------------------------------------------------------------------
+ 6.66 0.00 6.66 1/1 Object#run_primes
+ 75.93% 0.00% 6.66 0.00 6.66 1 Object#find_primes
+ 6.66 0.01 6.64 1/1 Array#select
+ --------------------------------------------------------------------------------
+ 6.64 0.01 6.63 500/500 Array#select
+ 75.76% 0.17% 6.64 0.01 6.63 500 Object#is_prime
+ 0.00 0.00 0.00 500/501 Fixnum#-
+ 6.63 4.06 2.56 500/501 Integer#upto
+ --------------------------------------------------------------------------------
+ 2.09 0.00 2.09 1/1 Object#run_primes
+ 23.89% 0.00% 2.09 0.00 2.09 1 Object#find_largest
+ 0.00 0.00 0.00 1/501 Fixnum#-
+ 2.09 0.00 2.09 1/501 Integer#upto
+ 0.00 0.00 0.00 1/1 Array#first
+ 0.00 0.00 0.00 1/1 Array#length
+ --------------------------------------------------------------------------------
+ 2.09 2.09 0.00 61/61 Integer#upto
+ 23.89% 23.89% 2.09 2.09 0.00 61 Kernel.sleep
+ --------------------------------------------------------------------------------
+ 1.33 1.33 0.00 250862/250862 Integer#upto
+ 15.12% 15.12% 1.33 1.33 0.00 250862 Fixnum#%
+ --------------------------------------------------------------------------------
+ 1.24 1.24 0.00 250862/250862 Integer#upto
+ 14.13% 14.13% 1.24 1.24 0.00 250862 Fixnum#==
+ --------------------------------------------------------------------------------
+ 0.02 0.00 0.02 1/1 Object#run_primes
+ 0.18% 0.00% 0.02 0.00 0.02 1 Object#make_random_array
+ 0.02 0.02 0.00 1/1 Array#each_index
+ 0.00 0.00 0.00 1/1 Class#new
+ --------------------------------------------------------------------------------
+ 0.02 0.02 0.00 1/1 Object#make_random_array
+ 0.18% 0.18% 0.02 0.02 0.00 1 Array#each_index
+ 0.00 0.00 0.00 500/500 Kernel.rand
+ 0.00 0.00 0.00 500/500 Array#[]=
+ --------------------------------------------------------------------------------
+ 0.00 0.00 0.00 500/501 Object#is_prime
+ 0.00 0.00 0.00 1/501 Object#find_largest
+ 0.00% 0.00% 0.00 0.00 0.00 501 Fixnum#-
+ --------------------------------------------------------------------------------
+ 0.00 0.00 0.00 1/1 Kernel.rand
+ 0.00% 0.00% 0.00 0.00 0.00 1 Integer#to_int
+ --------------------------------------------------------------------------------
+ 0.00 0.00 0.00 1/1 Object#find_largest
+ 0.00% 0.00% 0.00 0.00 0.00 1 Array#first
+ --------------------------------------------------------------------------------
+ 0.00 0.00 0.00 1/1 Class#new
+ 0.00% 0.00% 0.00 0.00 0.00 1 Array#initialize
+ --------------------------------------------------------------------------------
+ 0.00 0.00 0.00 1/1 Object#find_largest
+ 0.00% 0.00% 0.00 0.00 0.00 1 Array#length
+ --------------------------------------------------------------------------------
+ 0.00 0.00 0.00 1/1 Object#make_random_array
+ 0.00% 0.00% 0.00 0.00 0.00 1 Class#new
+ 0.00 0.00 0.00 1/1 Array#initialize
+ --------------------------------------------------------------------------------
+ 0.00 0.00 0.00 61/61 Integer#upto
+ 0.00% 0.00% 0.00 0.00 0.00 61 Fixnum#>
+ --------------------------------------------------------------------------------
+ 0.00 0.00 0.00 61/61 Integer#upto
+ 0.00% 0.00% 0.00 0.00 0.00 61 Array#[]
+ --------------------------------------------------------------------------------
+ 0.00 0.00 0.00 500/500 Array#each_index
+ 0.00% 0.00% 0.00 0.00 0.00 500 Array#[]=
+ --------------------------------------------------------------------------------
+ 0.00 0.00 0.00 500/500 Array#each_index
+ 0.00% 0.00% 0.00 0.00 0.00 500 Kernel.rand
+ 0.00 0.00 0.00 1/1 Integer#to_int
+
+
+
+== Overview
+Dashed lines divide the report into entries, with one entry
+per method. Entries are sorted by total time which is the
+time spent in the method plus its children.
+
+Each entry has a primary line demarked by values in the
+%total and %self columns. The primary line represents
+the method being profiled. Lines above it are the methods
+that called this method (parents) while the lines below it
+are the methods it called (children).
+
+All values are in seconds. For the primary line, the columns represent:
+
+ %total - The percentage of time spent in this method and its children
+ %self - The percentage of time spent in this method
+ total - The time spent in this method and its children.
+ self - The time spent in this method.
+ children - The time spent in this method's children.
+ calls - The number of times this method was called.
+ name - The name of the method.
+
+The interpretation of method names is:
+* #toplevel - The root method that calls all other methods
+* MyObject#test - An instance method "test" of the class "MyObject"
+* <Object:MyObject>#test - The <> characters indicate a singleton method on a singleton class.
+
+For example, we see that 99.48% of the time was spent in Integer#upto and its children.
+Of that time, 4.06 seconds was spent in Integer#upto itself and 4.66 in its children.
+Overall, Integer#upto was called 501 times.
+
+== Parents
+In each entry, the lines above the primary line are the methods that
+called the current method. If the current method is a root method then
+no parents are shown.
+
+
+For parent lines, the columns represent:
+
+ total - The time spent in the current method and it children on behalf of the parent method.
+ self - The time spent in this method on behalf of the parent method.
+ children - The time spent in this method's children on behalf of the parent.
+ calls - The number of times the parent method called this child
+
+Looking at Integer#upto again, we see that it was called 500 times from Object#is_prime
+and 1 time from find_largest. Of the 8.72 total seconds spent in Integer#upto, 6.63
+were done for Object#is_prime and 2.09 for Object#find_largest.
+
+
+== Children
+In each entry, the lines below the primary line are the methods that
+the current method called. If the current method is a leaf method then
+no children are shown.
+
+For children lines, the columns represent:
+
+ total - The time spent in the child, and its children, on behalf of the current method
+ self - The time spent in the child on behalf of the current method.
+ children - The time spent in the child's children (ie, granchildren) in behalf of the current method
+ calls - The number of times the child method was called by the current method.
+
+Taking our example of Integer#upto, we see that it called five other methods - Array#[],
+Fixnum#>, Kernel.sleep, Fixnum#= and Fixnum#%. Looking at Kernel.sleep, we see that
+its spent 2.09 seconds working for Integer#upto and its children spent 0 time working for
+Integer#upto. To see the overall time Kernel.sleep took we would have to look up its entry
+in the graph table.
+
+
diff --git a/ext/extconf.rb b/ext/extconf.rb
new file mode 100644
index 0000000..363ae3c
--- /dev/null
+++ b/ext/extconf.rb
@@ -0,0 +1,34 @@
+require "mkmf"
+
+if RUBY_VERSION >= "1.9"
+ if RUBY_RELEASE_DATE < "2005-03-17"
+ STDERR.print("Ruby version is too old\n")
+ exit(1)
+ end
+elsif RUBY_VERSION >= "1.8"
+ if RUBY_RELEASE_DATE < "2005-03-22"
+ STDERR.print("Ruby version is too old\n")
+ exit(1)
+ end
+else
+ STDERR.print("Ruby version is too old\n")
+ exit(1)
+end
+
+have_header("sys/times.h")
+
+# Stefan Kaes / Alexander Dymo GC patch
+have_func("rb_os_allocated_objects")
+have_func("rb_gc_allocated_size")
+have_func("rb_gc_collections")
+have_func("rb_gc_time")
+
+# Lloyd Hilaiel's heap info patch
+have_func("rb_heap_total_mem")
+have_func("rb_gc_heap_info")
+
+# Ruby 1.9 unexposed methods
+have_func("rb_gc_malloc_allocations")
+have_func("rb_gc_malloc_allocated_size")
+
+create_makefile("ruby_prof")
diff --git a/ext/measure_allocations.h b/ext/measure_allocations.h
new file mode 100644
index 0000000..a7e09f7
--- /dev/null
+++ b/ext/measure_allocations.h
@@ -0,0 +1,58 @@
+/* :nodoc:
+ * Copyright (C) 2008 Shugo Maeda <shugo at ruby-lang.org>
+ * Charlie Savage <cfis at savagexi.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE. */
+
+#include <ruby.h>
+
+#if defined(HAVE_RB_OS_ALLOCATED_OBJECTS)
+#define MEASURE_ALLOCATIONS 3
+
+static prof_measure_t
+measure_allocations()
+{
+ return rb_os_allocated_objects();
+}
+
+static double
+convert_allocations(prof_measure_t c)
+{
+ return c;
+}
+
+/* Document-method: prof_measure_allocations
+ call-seq:
+ measure_allocations -> int
+
+Returns the total number of object allocations since Ruby started.*/
+static VALUE
+prof_measure_allocations(VALUE self)
+{
+#if defined(HAVE_LONG_LONG)
+ return ULL2NUM(rb_os_allocated_objects());
+#else
+ return ULONG2NUM(rb_os_allocated_objects());
+#endif
+}
+#endif
diff --git a/ext/measure_cpu_time.h b/ext/measure_cpu_time.h
new file mode 100644
index 0000000..df382e1
--- /dev/null
+++ b/ext/measure_cpu_time.h
@@ -0,0 +1,152 @@
+/* :nodoc:
+ * Copyright (C) 2008 Shugo Maeda <shugo at ruby-lang.org>
+ * Charlie Savage <cfis at savagexi.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE. */
+
+#include <ruby.h>
+
+#if defined(_WIN32) || (defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__) || defined(__powerpc__) || defined(__ppc__)))
+#define MEASURE_CPU_TIME 2
+
+static unsigned long long cpu_frequency;
+
+#if defined(__GNUC__)
+
+#include <stdint.h>
+
+static prof_measure_t
+measure_cpu_time()
+{
+#if defined(__i386__) || defined(__x86_64__)
+ uint32_t a, d;
+ __asm__ volatile("rdtsc" : "=a" (a), "=d" (d));
+ return ((uint64_t)d << 32) + a;
+#elif defined(__powerpc__) || defined(__ppc__)
+ unsigned long long x, y;
+
+ __asm__ __volatile__ ("\n\
+1: mftbu %1\n\
+ mftb %L0\n\
+ mftbu %0\n\
+ cmpw %0,%1\n\
+ bne- 1b"
+ : "=r" (x), "=r" (y));
+ return x;
+#endif
+}
+
+#elif defined(_WIN32)
+
+static prof_measure_t
+measure_cpu_time()
+{
+ prof_measure_t cycles = 0;
+
+ __asm
+ {
+ rdtsc
+ mov DWORD PTR cycles, eax
+ mov DWORD PTR [cycles + 4], edx
+ }
+ return cycles;
+}
+
+#endif
+
+
+/* The _WIN32 check is needed for msys (and maybe cygwin?) */
+#if defined(__GNUC__) && !defined(_WIN32)
+
+unsigned long long get_cpu_frequency()
+{
+ unsigned long long x, y;
+
+ struct timespec ts;
+ ts.tv_sec = 0;
+ ts.tv_nsec = 500000000;
+ x = measure_cpu_time();
+ nanosleep(&ts, NULL);
+ y = measure_cpu_time();
+ return (y - x) * 2;
+}
+
+#elif defined(_WIN32)
+
+unsigned long long get_cpu_frequency()
+{
+ unsigned long long x, y;
+ unsigned long long frequency;
+ x = measure_cpu_time();
+
+ /* Use the windows sleep function, not Ruby's */
+ Sleep(500);
+ y = measure_cpu_time();
+ frequency = 2*(y-x);
+ return frequency;
+}
+#endif
+
+static double
+convert_cpu_time(prof_measure_t c)
+{
+ return (double) c / cpu_frequency;
+}
+
+/* Document-method: prof_measure_cpu_time
+ call-seq:
+ measure_cpu_time -> float
+
+Returns the cpu time.*/
+static VALUE
+prof_measure_cpu_time(VALUE self)
+{
+ return rb_float_new(convert_cpu_time(measure_cpu_time()));
+}
+
+/* Document-method: prof_get_cpu_frequency
+ call-seq:
+ cpu_frequency -> int
+
+Returns the cpu's frequency. This value is needed when
+RubyProf::measure_mode is set to CPU_TIME. */
+static VALUE
+prof_get_cpu_frequency(VALUE self)
+{
+ return ULL2NUM(cpu_frequency);
+}
+
+/* Document-method: prof_set_cpu_frequency
+ call-seq:
+ cpu_frequency=value -> void
+
+Sets the cpu's frequency. This value is needed when
+RubyProf::measure_mode is set to CPU_TIME. */
+static VALUE
+prof_set_cpu_frequency(VALUE self, VALUE val)
+{
+ cpu_frequency = NUM2LL(val);
+ return val;
+}
+
+#endif
diff --git a/ext/measure_gc_runs.h b/ext/measure_gc_runs.h
new file mode 100644
index 0000000..e98e325
--- /dev/null
+++ b/ext/measure_gc_runs.h
@@ -0,0 +1,76 @@
+/* :nodoc:
+ * Copyright (C) 2008 Shugo Maeda <shugo at ruby-lang.org>
+ * Charlie Savage <cfis at savagexi.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE. */
+
+#if defined(HAVE_RB_GC_COLLECTIONS)
+#define MEASURE_GC_RUNS 5
+
+static prof_measure_t
+measure_gc_runs()
+{
+ return NUM2INT(rb_gc_collections());
+}
+
+static double
+convert_gc_runs(prof_measure_t c)
+{
+ return c;
+}
+
+/* Document-method: prof_measure_gc_runs
+ call-seq:
+ gc_runs -> Integer
+
+Returns the total number of garbage collections.*/
+static VALUE
+prof_measure_gc_runs(VALUE self)
+{
+ return rb_gc_collections();
+}
+
+#elif defined(HAVE_RB_GC_HEAP_INFO)
+#define MEASURE_GC_RUNS 5
+
+static prof_measure_t
+measure_gc_runs()
+{
+ VALUE h = rb_gc_heap_info();
+ return NUM2UINT(rb_hash_aref(h, rb_str_new2("num_gc_passes")));
+}
+
+static double
+convert_gc_runs(prof_measure_t c)
+{
+ return c;
+}
+
+static VALUE
+prof_measure_gc_runs(VALUE self)
+{
+ VALUE h = rb_gc_heap_info();
+ return rb_hash_aref(h, rb_str_new2("num_gc_passes"));
+}
+
+#endif
diff --git a/ext/measure_gc_time.h b/ext/measure_gc_time.h
new file mode 100644
index 0000000..ee0057b
--- /dev/null
+++ b/ext/measure_gc_time.h
@@ -0,0 +1,57 @@
+/* :nodoc:
+ * Copyright (C) 2008 Shugo Maeda <shugo at ruby-lang.org>
+ * Charlie Savage <cfis at savagexi.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE. */
+
+#if defined(HAVE_RB_GC_TIME)
+#define MEASURE_GC_TIME 6
+
+static prof_measure_t
+measure_gc_time()
+{
+#if HAVE_LONG_LONG
+ return NUM2LL(rb_gc_time());
+#else
+ return NUM2LONG(rb_gc_time());
+#endif
+}
+
+static double
+convert_gc_time(prof_measure_t c)
+{
+ return (double) c / 1000000;
+}
+
+/* Document-method: prof_measure_gc_time
+ call-seq:
+ gc_time -> Integer
+
+Returns the time spent doing garbage collections in microseconds.*/
+static VALUE
+prof_measure_gc_time(VALUE self)
+{
+ return rb_gc_time();
+}
+
+#endif
diff --git a/ext/measure_memory.h b/ext/measure_memory.h
new file mode 100644
index 0000000..c613db7
--- /dev/null
+++ b/ext/measure_memory.h
@@ -0,0 +1,101 @@
+/* :nodoc:
+ * Copyright (C) 2008 Alexander Dymo <adymo at pluron.com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE. */
+
+
+#if defined(HAVE_RB_GC_ALLOCATED_SIZE)
+#define MEASURE_MEMORY 4
+#define TOGGLE_GC_STATS 1
+
+static prof_measure_t
+measure_memory()
+{
+#if defined(HAVE_LONG_LONG)
+ return NUM2LL(rb_gc_allocated_size());
+#else
+ return NUM2ULONG(rb_gc_allocated_size());
+#endif
+}
+
+static double
+convert_memory(prof_measure_t c)
+{
+ return (double) c / 1024;
+}
+
+/* Document-method: prof_measure_memory
+ call-seq:
+ measure_memory -> int
+
+Returns total allocated memory in bytes.*/
+static VALUE
+prof_measure_memory(VALUE self)
+{
+ return rb_gc_allocated_size();
+}
+
+#elif defined(HAVE_RB_GC_MALLOC_ALLOCATED_SIZE)
+#define MEASURE_MEMORY 4
+
+static prof_measure_t
+measure_memory()
+{
+ return rb_gc_malloc_allocated_size();
+}
+
+static double
+convert_memory(prof_measure_t c)
+{
+ return (double) c / 1024;
+}
+
+static VALUE
+prof_measure_memory(VALUE self)
+{
+ return UINT2NUM(rb_gc_malloc_allocated_size());
+}
+
+#elif defined(HAVE_RB_HEAP_TOTAL_MEM)
+#define MEASURE_MEMORY 4
+
+static prof_measure_t
+measure_memory()
+{
+ return rb_heap_total_mem();
+}
+
+static double
+convert_memory(prof_measure_t c)
+{
+ return (double) c / 1024;
+}
+
+static VALUE
+prof_measure_memory(VALUE self)
+{
+ return ULONG2NUM(rb_heap_total_mem());
+}
+
+#endif
diff --git a/ext/measure_process_time.h b/ext/measure_process_time.h
new file mode 100644
index 0000000..b104967
--- /dev/null
+++ b/ext/measure_process_time.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2008 Shugo Maeda <shugo at ruby-lang.org>
+ * Charlie Savage <cfis at savagexi.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE. */
+
+#include <time.h>
+
+#define MEASURE_PROCESS_TIME 0
+
+static prof_measure_t
+measure_process_time()
+{
+ return clock();
+}
+
+static double
+convert_process_time(prof_measure_t c)
+{
+ return (double) c / CLOCKS_PER_SEC;
+}
+
+/* Document-method: measure_process_time
+ call-seq:
+ measure_process_time -> float
+
+Returns the process time.*/
+static VALUE
+prof_measure_process_time(VALUE self)
+{
+ return rb_float_new(convert_process_time(measure_process_time()));
+}
diff --git a/ext/measure_wall_time.h b/ext/measure_wall_time.h
new file mode 100644
index 0000000..faa550a
--- /dev/null
+++ b/ext/measure_wall_time.h
@@ -0,0 +1,53 @@
+/* :nodoc:
+ * Copyright (C) 2008 Shugo Maeda <shugo at ruby-lang.org>
+ * Charlie Savage <cfis at savagexi.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE. */
+
+
+#define MEASURE_WALL_TIME 1
+
+static prof_measure_t
+measure_wall_time()
+{
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ return tv.tv_sec * 1000000 + tv.tv_usec;
+}
+
+static double
+convert_wall_time(prof_measure_t c)
+{
+ return (double) c / 1000000;
+}
+
+/* Document-method: prof_measure_wall_time
+ call-seq:
+ measure_wall_time -> float
+
+Returns the wall time.*/
+static VALUE
+prof_measure_wall_time(VALUE self)
+{
+ return rb_float_new(convert_wall_time(measure_wall_time()));
+}
diff --git a/ext/mingw/Rakefile b/ext/mingw/Rakefile
new file mode 100644
index 0000000..add032d
--- /dev/null
+++ b/ext/mingw/Rakefile
@@ -0,0 +1,23 @@
+# We can't use Ruby's standard build procedures
+# on Windows because the Ruby executable is
+# built with VC++ while here we want to build
+# with MingW. So just roll our own...
+
+require 'fileutils'
+require 'rbconfig'
+
+EXTENSION_NAME = "ruby_prof.#{Config::CONFIG["DLEXT"]}"
+
+# This is called when the Windows GEM is installed!
+task :install do
+ # Gems will pass these two environment variables:
+ # RUBYARCHDIR=#{dest_path}
+ # RUBYLIBDIR=#{dest_path}
+
+ dest_path = ENV['RUBYLIBDIR']
+
+ # Copy the extension
+ cp(EXTENSION_NAME, dest_path)
+end
+
+task :default => :install
diff --git a/ext/mingw/build.rake b/ext/mingw/build.rake
new file mode 100644
index 0000000..edd66a2
--- /dev/null
+++ b/ext/mingw/build.rake
@@ -0,0 +1,38 @@
+# We can't use Ruby's standard build procedures
+# on Windows because the Ruby executable is
+# built with VC++ while here we want to build
+# with MingW. So just roll our own...
+
+require 'rake/clean'
+require 'rbconfig'
+
+RUBY_INCLUDE_DIR = Config::CONFIG["archdir"]
+RUBY_BIN_DIR = Config::CONFIG["bindir"]
+RUBY_LIB_DIR = Config::CONFIG["libdir"]
+RUBY_SHARED_LIB = Config::CONFIG["LIBRUBY"]
+RUBY_SHARED_DLL = RUBY_SHARED_LIB.gsub(/lib$/, 'dll')
+
+EXTENSION_NAME = "ruby_prof.#{Config::CONFIG["DLEXT"]}"
+
+CLEAN.include('*.o')
+CLOBBER.include(EXTENSION_NAME)
+
+task :default => "ruby_prof"
+
+SRC = FileList['../*.c']
+OBJ = SRC.collect do |file_name|
+ File.basename(file_name).ext('o')
+end
+
+SRC.each do |srcfile|
+ objfile = File.basename(srcfile).ext('o')
+ file objfile => srcfile do
+ command = "gcc -c -fPIC -O2 -Wall -o #{objfile} -I/usr/local/include #{srcfile} -I#{RUBY_INCLUDE_DIR}"
+ sh "sh -c '#{command}'"
+ end
+end
+
+file "ruby_prof" => OBJ do
+ command = "gcc -shared -o #{EXTENSION_NAME} -L/usr/local/lib #{OBJ} #{RUBY_BIN_DIR}/#{RUBY_SHARED_DLL}"
+ sh "sh -c '#{command}'"
+end
\ No newline at end of file
diff --git a/ext/mingw/ruby_prof.so b/ext/mingw/ruby_prof.so
new file mode 100755
index 0000000..70adf02
Binary files /dev/null and b/ext/mingw/ruby_prof.so differ
diff --git a/ext/ruby_prof.c b/ext/ruby_prof.c
new file mode 100644
index 0000000..34ee12a
--- /dev/null
+++ b/ext/ruby_prof.c
@@ -0,0 +1,1680 @@
+/*
+ * Copyright (C) 2008 Shugo Maeda <shugo at ruby-lang.org>
+ * Charlie Savage <cfis at savagexi.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* ruby-prof tracks the time spent executing every method in ruby programming.
+ The main players are:
+
+ prof_result_t - Its one field, values, contains the overall results
+ thread_data_t - Stores data about a single thread.
+ prof_stack_t - The method call stack in a particular thread
+ prof_method_t - Profiling information for each method
+ prof_call_info_t - Keeps track a method's callers and callees.
+
+ The final resulut is a hash table of thread_data_t, keyed on the thread
+ id. Each thread has an hash a table of prof_method_t, keyed on the
+ method id. A hash table is used for quick look up when doing a profile.
+ However, it is exposed to Ruby as an array.
+
+ Each prof_method_t has two hash tables, parent and children, of prof_call_info_t.
+ These objects keep track of a method's callers (who called the method) and its
+ callees (who the method called). These are keyed the method id, but once again,
+ are exposed to Ruby as arrays. Each prof_call_into_t maintains a pointer to the
+ caller or callee method, thereby making it easy to navigate through the call
+ hierarchy in ruby - which is very helpful for creating call graphs.
+*/
+
+#include "ruby_prof.h"
+
+
+/* ================ Helper Functions =================*/
+static VALUE
+figure_singleton_name(VALUE klass)
+{
+ VALUE result = Qnil;
+
+ /* We have come across a singleton object. First
+ figure out what it is attached to.*/
+ VALUE attached = rb_iv_get(klass, "__attached__");
+
+ /* Is this a singleton class acting as a metaclass? */
+ if (BUILTIN_TYPE(attached) == T_CLASS)
+ {
+ result = rb_str_new2("<Class::");
+ rb_str_append(result, rb_inspect(attached));
+ rb_str_cat2(result, ">");
+ }
+
+ /* Is this for singleton methods on a module? */
+ else if (BUILTIN_TYPE(attached) == T_MODULE)
+ {
+ result = rb_str_new2("<Module::");
+ rb_str_append(result, rb_inspect(attached));
+ rb_str_cat2(result, ">");
+ }
+
+ /* Is this for singleton methods on an object? */
+ else if (BUILTIN_TYPE(attached) == T_OBJECT)
+ {
+ /* Make sure to get the super class so that we don't
+ mistakenly grab a T_ICLASS which would lead to
+ unknown method errors. */
+#ifdef RCLASS_SUPER
+ VALUE super = rb_class_real(RCLASS_SUPER(klass));
+#else
+ VALUE super = rb_class_real(RCLASS(klass)->super);
+#endif
+ result = rb_str_new2("<Object::");
+ rb_str_append(result, rb_inspect(super));
+ rb_str_cat2(result, ">");
+ }
+
+ /* Ok, this could be other things like an array made put onto
+ a singleton object (yeah, it happens, see the singleton
+ objects test case). */
+ else
+ {
+ result = rb_inspect(klass);
+ }
+
+ return result;
+}
+
+static VALUE
+klass_name(VALUE klass)
+{
+ VALUE result = Qnil;
+
+ if (klass == 0 || klass == Qnil)
+ {
+ result = rb_str_new2("Global");
+ }
+ else if (BUILTIN_TYPE(klass) == T_MODULE)
+ {
+ result = rb_inspect(klass);
+ }
+ else if (BUILTIN_TYPE(klass) == T_CLASS && FL_TEST(klass, FL_SINGLETON))
+ {
+ result = figure_singleton_name(klass);
+ }
+ else if (BUILTIN_TYPE(klass) == T_CLASS)
+ {
+ result = rb_inspect(klass);
+ }
+ else
+ {
+ /* Should never happen. */
+ result = rb_str_new2("Unknown");
+ }
+
+ return result;
+}
+
+static VALUE
+method_name(ID mid, int depth)
+{
+ VALUE result;
+
+ if (mid == ID_ALLOCATOR)
+ result = rb_str_new2("allocate");
+ else if (mid == 0)
+ result = rb_str_new2("[No method]");
+ else
+ result = rb_String(ID2SYM(mid));
+
+ if (depth > 0)
+ {
+ char buffer[65];
+ sprintf(buffer, "%i", depth);
+ rb_str_cat2(result, "-");
+ rb_str_cat2(result, buffer);
+ }
+
+ return result;
+}
+
+static VALUE
+full_name(VALUE klass, ID mid, int depth)
+{
+ VALUE result = klass_name(klass);
+ rb_str_cat2(result, "#");
+ rb_str_append(result, method_name(mid, depth));
+
+ return result;
+}
+
+/* ================ Stack Handling =================*/
+/* Creates a stack of prof_frame_t to keep track
+ of timings for active methods. */
+static prof_stack_t *
+stack_create()
+{
+ prof_stack_t *stack = ALLOC(prof_stack_t);
+ stack->start = ALLOC_N(prof_frame_t, INITIAL_STACK_SIZE);
+ stack->ptr = stack->start;
+ stack->end = stack->start + INITIAL_STACK_SIZE;
+ return stack;
+}
+
+static void
+stack_free(prof_stack_t *stack)
+{
+ xfree(stack->start);
+ xfree(stack);
+}
+
+static prof_frame_t *
+stack_push(prof_stack_t *stack)
+{
+ /* Is there space on the stack? If not, double
+ its size. */
+ if (stack->ptr == stack->end)
+ {
+ size_t len = stack->ptr - stack->start;
+ size_t new_capacity = (stack->end - stack->start) * 2;
+ REALLOC_N(stack->start, prof_frame_t, new_capacity);
+ stack->ptr = stack->start + len;
+ stack->end = stack->start + new_capacity;
+ }
+ return stack->ptr++;
+}
+
+static prof_frame_t *
+stack_pop(prof_stack_t *stack)
+{
+ if (stack->ptr == stack->start)
+ return NULL;
+ else
+ return --stack->ptr;
+}
+
+static prof_frame_t *
+stack_peek(prof_stack_t *stack)
+{
+ if (stack->ptr == stack->start)
+ return NULL;
+ else
+ return stack->ptr - 1;
+}
+
+/* ================ Method Key =================*/
+static int
+method_table_cmp(prof_method_key_t *key1, prof_method_key_t *key2)
+{
+ return (key1->klass != key2->klass) ||
+ (key1->mid != key2->mid) ||
+ (key1->depth != key2->depth);
+}
+
+static int
+method_table_hash(prof_method_key_t *key)
+{
+ return key->key;
+}
+
+static struct st_hash_type type_method_hash = {
+ method_table_cmp,
+ method_table_hash
+};
+
+static void
+method_key(prof_method_key_t* key, VALUE klass, ID mid, int depth)
+{
+ key->klass = klass;
+ key->mid = mid;
+ key->depth = depth;
+ key->key = (klass << 4) + (mid << 2) + depth;
+}
+
+
+/* ================ Call Info =================*/
+static st_table *
+call_info_table_create()
+{
+ return st_init_table(&type_method_hash);
+}
+
+static size_t
+call_info_table_insert(st_table *table, const prof_method_key_t *key, prof_call_info_t *val)
+{
+ return st_insert(table, (st_data_t) key, (st_data_t) val);
+}
+
+static prof_call_info_t *
+call_info_table_lookup(st_table *table, const prof_method_key_t *key)
+{
+ st_data_t val;
+ if (st_lookup(table, (st_data_t) key, &val))
+ {
+ return (prof_call_info_t *) val;
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+static void
+call_info_table_free(st_table *table)
+{
+ st_free_table(table);
+}
+
+/* Document-class: RubyProf::CallInfo
+RubyProf::CallInfo is a helper class used by RubyProf::MethodInfo
+to keep track of which child methods were called and how long
+they took to execute. */
+
+/* :nodoc: */
+static prof_call_info_t *
+prof_call_info_create(prof_method_t* method, prof_call_info_t* parent)
+{
+ prof_call_info_t *result = ALLOC(prof_call_info_t);
+ result->object = Qnil;
+ result->target = method;
+ result->parent = parent;
+ result->call_infos = call_info_table_create();
+ result->children = Qnil;
+
+ result->called = 0;
+ result->total_time = 0;
+ result->self_time = 0;
+ result->wait_time = 0;
+ result->line = 0;
+ return result;
+}
+
+static void
+prof_call_info_mark(prof_call_info_t *call_info)
+{
+ rb_gc_mark(prof_method_wrap(call_info->target));
+ rb_gc_mark(call_info->children);
+ if (call_info->parent)
+ rb_gc_mark(prof_call_info_wrap(call_info->parent));
+}
+
+static void
+prof_call_info_free(prof_call_info_t *call_info)
+{
+ call_info_table_free(call_info->call_infos);
+ xfree(call_info);
+}
+
+static VALUE
+prof_call_info_wrap(prof_call_info_t *call_info)
+{
+ if (call_info->object == Qnil)
+ {
+ call_info->object = Data_Wrap_Struct(cCallInfo, prof_call_info_mark, prof_call_info_free, call_info);
+ }
+ return call_info->object;
+}
+
+static prof_call_info_t *
+prof_get_call_info_result(VALUE obj)
+{
+ if (BUILTIN_TYPE(obj) != T_DATA)
+ {
+ /* Should never happen */
+ rb_raise(rb_eTypeError, "Not a call info object");
+ }
+ return (prof_call_info_t *) DATA_PTR(obj);
+}
+
+
+/* call-seq:
+ called -> MethodInfo
+
+Returns the target method. */
+static VALUE
+prof_call_info_target(VALUE self)
+{
+ /* Target is a pointer to a method_info - so we have to be careful
+ about the GC. We will wrap the method_info but provide no
+ free method so the underlying object is not freed twice! */
+
+ prof_call_info_t *result = prof_get_call_info_result(self);
+ return prof_method_wrap(result->target);
+}
+
+/* call-seq:
+ called -> int
+
+Returns the total amount of time this method was called. */
+static VALUE
+prof_call_info_called(VALUE self)
+{
+ prof_call_info_t *result = prof_get_call_info_result(self);
+ return INT2NUM(result->called);
+}
+
+/* call-seq:
+ line_no -> int
+
+ returns the line number of the method */
+static VALUE
+prof_call_info_line(VALUE self)
+{
+ prof_call_info_t *result = prof_get_call_info_result(self);
+ return rb_int_new(result->line);
+}
+
+/* call-seq:
+ total_time -> float
+
+Returns the total amount of time spent in this method and its children. */
+static VALUE
+prof_call_info_total_time(VALUE self)
+{
+ prof_call_info_t *result = prof_get_call_info_result(self);
+ return rb_float_new(convert_measurement(result->total_time));
+}
+
+/* call-seq:
+ self_time -> float
+
+Returns the total amount of time spent in this method. */
+static VALUE
+prof_call_info_self_time(VALUE self)
+{
+ prof_call_info_t *result = prof_get_call_info_result(self);
+
+ return rb_float_new(convert_measurement(result->self_time));
+}
+
+/* call-seq:
+ wait_time -> float
+
+Returns the total amount of time this method waited for other threads. */
+static VALUE
+prof_call_info_wait_time(VALUE self)
+{
+ prof_call_info_t *result = prof_get_call_info_result(self);
+
+ return rb_float_new(convert_measurement(result->wait_time));
+}
+
+/* call-seq:
+ parent -> call_info
+
+Returns the call_infos parent call_info object (the method that called this method).*/
+static VALUE
+prof_call_info_parent(VALUE self)
+{
+ prof_call_info_t *result = prof_get_call_info_result(self);
+ if (result->parent)
+ return prof_call_info_wrap(result->parent);
+ else
+ return Qnil;
+}
+
+static int
+prof_call_info_collect_children(st_data_t key, st_data_t value, st_data_t result)
+{
+ prof_call_info_t *call_info = (prof_call_info_t *) value;
+ VALUE arr = (VALUE) result;
+ rb_ary_push(arr, prof_call_info_wrap(call_info));
+ return ST_CONTINUE;
+}
+
+/* call-seq:
+ children -> hash
+
+Returns an array of call info objects of methods that this method
+called (ie, children).*/
+static VALUE
+prof_call_info_children(VALUE self)
+{
+ prof_call_info_t *call_info = prof_get_call_info_result(self);
+ if (call_info->children == Qnil)
+ {
+ call_info->children = rb_ary_new();
+ st_foreach(call_info->call_infos, prof_call_info_collect_children, call_info->children);
+ }
+ return call_info->children;
+}
+
+/* ================ Call Infos =================*/
+static prof_call_infos_t*
+prof_call_infos_create()
+{
+ prof_call_infos_t *result = ALLOC(prof_call_infos_t);
+ result->start = ALLOC_N(prof_call_info_t*, INITIAL_CALL_INFOS_SIZE);
+ result->end = result->start + INITIAL_CALL_INFOS_SIZE;
+ result->ptr = result->start;
+ result->object = Qnil;
+ return result;
+}
+
+static void
+prof_call_infos_free(prof_call_infos_t *call_infos)
+{
+ xfree(call_infos->start);
+ xfree(call_infos);
+}
+
+static void
+prof_add_call_info(prof_call_infos_t *call_infos, prof_call_info_t *call_info)
+{
+ if (call_infos->ptr == call_infos->end)
+ {
+ size_t len = call_infos->ptr - call_infos->start;
+ size_t new_capacity = (call_infos->end - call_infos->start) * 2;
+ REALLOC_N(call_infos->start, prof_call_info_t*, new_capacity);
+ call_infos->ptr = call_infos->start + len;
+ call_infos->end = call_infos->start + new_capacity;
+ }
+ *call_infos->ptr = call_info;
+ call_infos->ptr++;
+}
+
+static VALUE
+prof_call_infos_wrap(prof_call_infos_t *call_infos)
+{
+ if (call_infos->object == Qnil)
+ {
+ prof_call_info_t **i;
+ call_infos->object = rb_ary_new();
+ for(i=call_infos->start; i<call_infos->ptr; i++)
+ {
+ VALUE call_info = prof_call_info_wrap(*i);
+ rb_ary_push(call_infos->object, call_info);
+ }
+ }
+ return call_infos->object;
+}
+
+
+/* ================ Method Info =================*/
+/* Document-class: RubyProf::MethodInfo
+The RubyProf::MethodInfo class stores profiling data for a method.
+One instance of the RubyProf::MethodInfo class is created per method
+called per thread. Thus, if a method is called in two different
+thread then there will be two RubyProf::MethodInfo objects
+created. RubyProf::MethodInfo objects can be accessed via
+the RubyProf::Result object.
+*/
+
+static prof_method_t*
+prof_method_create(prof_method_key_t *key, const char* source_file, int line)
+{
+ prof_method_t *result = ALLOC(prof_method_t);
+ result->object = Qnil;
+ result->key = ALLOC(prof_method_key_t);
+ method_key(result->key, key->klass, key->mid, key->depth);
+
+ result->call_infos = prof_call_infos_create();
+
+ result->active = 0;
+
+ if (source_file != NULL)
+ {
+ int len = strlen(source_file) + 1;
+ char *buffer = ALLOC_N(char, len);
+
+ MEMCPY(buffer, source_file, char, len);
+ result->source_file = buffer;
+ }
+ else
+ {
+ result->source_file = source_file;
+ }
+ result->line = line;
+
+ return result;
+}
+
+static void
+prof_method_mark(prof_method_t *method)
+{
+ rb_gc_mark(method->call_infos->object);
+ rb_gc_mark(method->key->klass);
+}
+
+static void
+prof_method_free(prof_method_t *method)
+{
+ if (method->source_file)
+ {
+ xfree((char*)method->source_file);
+ }
+
+ prof_call_infos_free(method->call_infos);
+ xfree(method->key);
+ xfree(method);
+}
+
+static VALUE
+prof_method_wrap(prof_method_t *result)
+{
+ if (result->object == Qnil)
+ {
+ result->object = Data_Wrap_Struct(cMethodInfo, prof_method_mark, prof_method_free, result);
+ }
+ return result->object;
+}
+
+static prof_method_t *
+get_prof_method(VALUE obj)
+{
+ return (prof_method_t *) DATA_PTR(obj);
+}
+
+/* call-seq:
+ line_no -> int
+
+ returns the line number of the method */
+static VALUE
+prof_method_line(VALUE self)
+{
+ return rb_int_new(get_prof_method(self)->line);
+}
+
+/* call-seq:
+ source_file => string
+
+return the source file of the method
+*/
+static VALUE prof_method_source_file(VALUE self)
+{
+ const char* sf = get_prof_method(self)->source_file;
+ if(!sf)
+ {
+ return rb_str_new2("ruby_runtime");
+ }
+ else
+ {
+ return rb_str_new2(sf);
+ }
+}
+
+
+/* call-seq:
+ method_class -> klass
+
+Returns the Ruby klass that owns this method. */
+static VALUE
+prof_method_klass(VALUE self)
+{
+ prof_method_t *result = get_prof_method(self);
+ return result->key->klass;
+}
+
+/* call-seq:
+ method_id -> ID
+
+Returns the id of this method. */
+static VALUE
+prof_method_id(VALUE self)
+{
+ prof_method_t *result = get_prof_method(self);
+ return ID2SYM(result->key->mid);
+}
+
+/* call-seq:
+ klass_name -> string
+
+Returns the name of this method's class. Singleton classes
+will have the form <Object::Object>. */
+
+static VALUE
+prof_klass_name(VALUE self)
+{
+ prof_method_t *method = get_prof_method(self);
+ return klass_name(method->key->klass);
+}
+
+/* call-seq:
+ method_name -> string
+
+Returns the name of this method in the format Object#method. Singletons
+methods will be returned in the format <Object::Object>#method.*/
+
+static VALUE
+prof_method_name(VALUE self, int depth)
+{
+ prof_method_t *method = get_prof_method(self);
+ return method_name(method->key->mid, depth);
+}
+
+/* call-seq:
+ full_name -> string
+
+Returns the full name of this method in the format Object#method.*/
+
+static VALUE
+prof_full_name(VALUE self)
+{
+ prof_method_t *method = get_prof_method(self);
+ return full_name(method->key->klass, method->key->mid, method->key->depth);
+}
+
+/* call-seq:
+ call_infos -> Array of call_info
+
+Returns an array of call info objects that contain profiling information
+about the current method.*/
+static VALUE
+prof_method_call_infos(VALUE self)
+{
+ prof_method_t *method = get_prof_method(self);
+ return prof_call_infos_wrap(method->call_infos);
+}
+
+static int
+collect_methods(st_data_t key, st_data_t value, st_data_t result)
+{
+ /* Called for each method stored in a thread's method table.
+ We want to store the method info information into an array.*/
+ VALUE methods = (VALUE) result;
+ prof_method_t *method = (prof_method_t *) value;
+ rb_ary_push(methods, prof_method_wrap(method));
+
+ /* Wrap call info objects */
+ prof_call_infos_wrap(method->call_infos);
+
+ return ST_CONTINUE;
+}
+
+/* ================ Method Table =================*/
+static st_table *
+method_table_create()
+{
+ return st_init_table(&type_method_hash);
+}
+
+static size_t
+method_table_insert(st_table *table, const prof_method_key_t *key, prof_method_t *val)
+{
+ return st_insert(table, (st_data_t) key, (st_data_t) val);
+}
+
+static prof_method_t *
+method_table_lookup(st_table *table, const prof_method_key_t* key)
+{
+ st_data_t val;
+ if (st_lookup(table, (st_data_t)key, &val))
+ {
+ return (prof_method_t *) val;
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+
+static void
+method_table_free(st_table *table)
+{
+ /* Don't free the contents since they are wrapped by
+ Ruby objects! */
+ st_free_table(table);
+}
+
+
+/* ================ Thread Handling =================*/
+
+/* ---- Keeps track of thread's stack and methods ---- */
+static thread_data_t*
+thread_data_create()
+{
+ thread_data_t* result = ALLOC(thread_data_t);
+ result->stack = stack_create();
+ result->method_table = method_table_create();
+ result->last_switch = get_measurement();
+ return result;
+}
+
+static void
+thread_data_free(thread_data_t* thread_data)
+{
+ method_table_free(thread_data->method_table);
+ stack_free(thread_data->stack);
+ xfree(thread_data);
+}
+
+/* ---- Hash, keyed on thread, that stores thread's stack
+ and methods---- */
+
+static st_table *
+threads_table_create()
+{
+ return st_init_numtable();
+}
+
+static size_t
+threads_table_insert(st_table *table, VALUE thread, thread_data_t *thread_data)
+{
+ /* Its too slow to key on the real thread id so just typecast thread instead. */
+ return st_insert(table, (st_data_t) thread, (st_data_t) thread_data);
+}
+
+static thread_data_t *
+threads_table_lookup(st_table *table, VALUE thread_id)
+{
+ thread_data_t* result;
+ st_data_t val;
+
+ /* Its too slow to key on the real thread id so just typecast thread instead. */
+ if (st_lookup(table, (st_data_t) thread_id, &val))
+ {
+ result = (thread_data_t *) val;
+ }
+ else
+ {
+ result = thread_data_create();
+ result->thread_id = thread_id;
+
+ /* Insert the table */
+ threads_table_insert(threads_tbl, thread_id, result);
+ }
+ return result;
+}
+
+static int
+free_thread_data(st_data_t key, st_data_t value, st_data_t dummy)
+{
+ thread_data_free((thread_data_t*)value);
+ return ST_CONTINUE;
+}
+
+
+static void
+threads_table_free(st_table *table)
+{
+ st_foreach(table, free_thread_data, 0);
+ st_free_table(table);
+}
+
+
+static int
+collect_threads(st_data_t key, st_data_t value, st_data_t result)
+{
+ /* Although threads are keyed on an id, that is actually a
+ pointer to the VALUE object of the thread. So its bogus.
+ However, in thread_data is the real thread id stored
+ as an int. */
+ thread_data_t* thread_data = (thread_data_t*) value;
+ VALUE threads_hash = (VALUE) result;
+
+ VALUE methods = rb_ary_new();
+
+ /* Now collect an array of all the called methods */
+ st_table* method_table = thread_data->method_table;
+ st_foreach(method_table, collect_methods, methods);
+
+ /* Store the results in the threads hash keyed on the thread id. */
+ rb_hash_aset(threads_hash, thread_data->thread_id, methods);
+
+ return ST_CONTINUE;
+}
+
+
+/* ================ Profiling =================*/
+/* Copied from eval.c */
+#ifdef DEBUG
+static char *
+get_event_name(rb_event_flag_t event)
+{
+ switch (event) {
+ case RUBY_EVENT_LINE:
+ return "line";
+ case RUBY_EVENT_CLASS:
+ return "class";
+ case RUBY_EVENT_END:
+ return "end";
+ case RUBY_EVENT_CALL:
+ return "call";
+ case RUBY_EVENT_RETURN:
+ return "return";
+ case RUBY_EVENT_C_CALL:
+ return "c-call";
+ case RUBY_EVENT_C_RETURN:
+ return "c-return";
+ case RUBY_EVENT_RAISE:
+ return "raise";
+ default:
+ return "unknown";
+ }
+}
+#endif
+
+static prof_method_t*
+get_method(rb_event_flag_t event, NODE *node, VALUE klass, ID mid, int depth, st_table* method_table)
+{
+ prof_method_key_t key;
+ prof_method_t *method = NULL;
+
+ method_key(&key, klass, mid, depth);
+ method = method_table_lookup(method_table, &key);
+
+ if (!method)
+ {
+ const char* source_file = rb_sourcefile();
+ int line = rb_sourceline();
+
+ /* Line numbers are not accurate for c method calls */
+ if (event == RUBY_EVENT_C_CALL)
+ {
+ line = 0;
+ source_file = NULL;
+ }
+
+ method = prof_method_create(&key, source_file, line);
+ method_table_insert(method_table, method->key, method);
+ }
+ return method;
+}
+
+static void
+update_result(prof_measure_t total_time,
+ prof_frame_t *parent_frame,
+ prof_frame_t *frame)
+{
+ prof_measure_t self_time = total_time - frame->child_time - frame->wait_time;
+
+ prof_call_info_t *call_info = frame->call_info;
+
+ /* Update information about the current method */
+ call_info->called++;
+ call_info->total_time += total_time;
+ call_info->self_time += self_time;
+ call_info->wait_time += frame->wait_time;
+
+ /* Note where the current method was called from */
+ if (parent_frame)
+ call_info->line = parent_frame->line;
+}
+
+static thread_data_t *
+switch_thread(VALUE thread_id, prof_measure_t now)
+{
+ prof_frame_t *frame = NULL;
+ prof_measure_t wait_time = 0;
+
+ /* Get new thread information. */
+ thread_data_t *thread_data = threads_table_lookup(threads_tbl, thread_id);
+
+ /* How long has this thread been waiting? */
+ wait_time = now - thread_data->last_switch;
+ thread_data->last_switch = 0;
+
+ /* Get the frame at the top of the stack. This may represent
+ the current method (EVENT_LINE, EVENT_RETURN) or the
+ previous method (EVENT_CALL).*/
+ frame = stack_peek(thread_data->stack);
+
+ if (frame)
+ frame->wait_time += wait_time;
+
+ /* Save on the last thread the time of the context switch
+ and reset this thread's last context switch to 0.*/
+ if (last_thread_data)
+ last_thread_data->last_switch = now;
+
+ last_thread_data = thread_data;
+ return thread_data;
+}
+
+static prof_frame_t*
+pop_frame(thread_data_t *thread_data, prof_measure_t now)
+{
+ prof_frame_t *frame = NULL;
+ prof_frame_t* parent_frame = NULL;
+ prof_measure_t total_time;
+
+ frame = stack_pop(thread_data->stack);
+
+ /* Frame can be null. This can happen if RubProf.start is called from
+ a method that exits. And it can happen if an exception is raised
+ in code that is being profiled and the stack unwinds (RubProf is
+ not notified of that by the ruby runtime. */
+ if (frame == NULL) return NULL;
+
+ /* Calculate the total time this method took */
+ total_time = now - frame->start_time;
+
+ /* Now deactivate the method */
+ frame->call_info->target->active = 0;
+
+ parent_frame = stack_peek(thread_data->stack);
+ if (parent_frame)
+ {
+ parent_frame->child_time += total_time;
+ }
+
+ update_result(total_time, parent_frame, frame);
+ return frame;
+}
+
+static int
+pop_frames(st_data_t key, st_data_t value, st_data_t now_arg)
+{
+ VALUE thread_id = (VALUE)key;
+ thread_data_t* thread_data = (thread_data_t *) value;
+ prof_measure_t now = *(prof_measure_t *) now_arg;
+
+ if (!last_thread_data || last_thread_data->thread_id != thread_id)
+ thread_data = switch_thread(thread_id, now);
+ else
+ thread_data = last_thread_data;
+
+ while (pop_frame(thread_data, now))
+ {
+ }
+
+ return ST_CONTINUE;
+}
+
+static void
+prof_pop_threads()
+{
+ /* Get current measurement*/
+ prof_measure_t now = get_measurement();
+ st_foreach(threads_tbl, pop_frames, (st_data_t) &now);
+}
+
+
+#ifdef RUBY_VM
+static void
+prof_event_hook(rb_event_flag_t event, VALUE data, VALUE self, ID mid, VALUE klass)
+#else
+static void
+prof_event_hook(rb_event_flag_t event, NODE *node, VALUE self, ID mid, VALUE klass)
+#endif
+{
+
+ VALUE thread = Qnil;
+ VALUE thread_id = Qnil;
+ prof_measure_t now = 0;
+ thread_data_t* thread_data = NULL;
+ prof_frame_t *frame = NULL;
+
+
+#ifdef RUBY_VM
+
+ if (event != RUBY_EVENT_C_CALL && event != RUBY_EVENT_C_RETURN) {
+ rb_frame_method_id_and_class(&mid, &klass);
+ }
+#endif
+
+#ifdef DEBUG
+ /* This code is here for debug purposes - uncomment it out
+ when debugging to see a print out of exactly what the
+ profiler is tracing. */
+ {
+ char* key = 0;
+ static VALUE last_thread_id = Qnil;
+
+ VALUE thread = rb_thread_current();
+ VALUE thread_id = rb_obj_id(thread);
+ char* class_name = NULL;
+ char* method_name = rb_id2name(mid);
+ char* source_file = rb_sourcefile();
+ unsigned int source_line = rb_sourceline();
+
+ char* event_name = get_event_name(event);
+
+ if (klass != 0)
+ klass = (BUILTIN_TYPE(klass) == T_ICLASS ? RBASIC(klass)->klass : klass);
+
+ class_name = rb_class2name(klass);
+
+ if (last_thread_id != thread_id)
+ printf("\n");
+
+ printf("%2u: %-8s :%2d %s#%s\n",
+ thread_id, event_name, source_line, class_name, method_name);
+ fflush(stdout);
+ last_thread_id = thread_id;
+ }
+#endif
+
+ /* Special case - skip any methods from the mProf
+ module, such as Prof.stop, since they clutter
+ the results but aren't important to them results. */
+ if (self == mProf) return;
+
+ /* Get current measurement*/
+ now = get_measurement();
+
+ /* Get the current thread information. */
+ thread = rb_thread_current();
+ thread_id = rb_obj_id(thread);
+
+ if (exclude_threads_tbl &&
+ st_lookup(exclude_threads_tbl, (st_data_t) thread_id, 0))
+ {
+ return;
+ }
+
+ /* Was there a context switch? */
+ if (!last_thread_data || last_thread_data->thread_id != thread_id)
+ thread_data = switch_thread(thread_id, now);
+ else
+ thread_data = last_thread_data;
+
+ /* Get the current frame for the current thread. */
+ frame = stack_peek(thread_data->stack);
+
+ switch (event) {
+ case RUBY_EVENT_LINE:
+ {
+ /* Keep track of the current line number in this method. When
+ a new method is called, we know what line number it was
+ called from. */
+ if (frame)
+ {
+ frame->line = rb_sourceline();
+ break;
+ }
+
+ /* If we get here there was no frame, which means this is
+ the first method seen for this thread, so fall through
+ to below to create it. */
+ }
+ case RUBY_EVENT_CALL:
+ case RUBY_EVENT_C_CALL:
+ {
+ prof_call_info_t *call_info = NULL;
+ prof_method_t *method = NULL;
+
+ /* Is this an include for a module? If so get the actual
+ module class since we want to combine all profiling
+ results for that module. */
+
+ if (klass != 0)
+ klass = (BUILTIN_TYPE(klass) == T_ICLASS ? RBASIC(klass)->klass : klass);
+
+ /* Assume this is the first time we have called this method. */
+ method = get_method(event, node, klass, mid, 0, thread_data->method_table);
+
+ /* Check for a recursive call */
+ if (method->active)
+ {
+ /* Yes, this method is already active */
+ method = get_method(event, node, klass, mid, method->key->depth + 1, thread_data->method_table);
+ }
+ else
+ {
+ /* No, so make it active */
+ method->active = 1;
+ }
+
+ if (!frame)
+ {
+ call_info = prof_call_info_create(method, NULL);
+ prof_add_call_info(method->call_infos, call_info);
+ }
+ else
+ {
+ call_info = call_info_table_lookup(frame->call_info->call_infos, method->key);
+
+ if (!call_info)
+ {
+ call_info = prof_call_info_create(method, frame->call_info);
+ call_info_table_insert(frame->call_info->call_infos, method->key, call_info);
+ prof_add_call_info(method->call_infos, call_info);
+ }
+ }
+
+ /* Push a new frame onto the stack */
+ frame = stack_push(thread_data->stack);
+ frame->call_info = call_info;
+ frame->start_time = now;
+ frame->wait_time = 0;
+ frame->child_time = 0;
+ frame->line = rb_sourceline();
+
+ break;
+ }
+ case RUBY_EVENT_RETURN:
+ case RUBY_EVENT_C_RETURN:
+ {
+ pop_frame(thread_data, now);
+ break;
+ }
+ }
+}
+
+
+/* ======== ProfResult ============== */
+
+/* Document-class: RubyProf::Result
+The RubyProf::Result class is used to store the results of a
+profiling run. And instace of the class is returned from
+the methods RubyProf#stop and RubyProf#profile.
+
+RubyProf::Result has one field, called threads, which is a hash
+table keyed on thread ID. For each thread id, the hash table
+stores another hash table that contains profiling information
+for each method called during the threads execution. That
+hash table is keyed on method name and contains
+RubyProf::MethodInfo objects. */
+
+
+static void
+prof_result_mark(prof_result_t *prof_result)
+{
+ VALUE threads = prof_result->threads;
+ rb_gc_mark(threads);
+}
+
+static void
+prof_result_free(prof_result_t *prof_result)
+{
+ prof_result->threads = Qnil;
+ xfree(prof_result);
+}
+
+static VALUE
+prof_result_new()
+{
+ prof_result_t *prof_result = ALLOC(prof_result_t);
+
+ /* Wrap threads in Ruby regular Ruby hash table. */
+ prof_result->threads = rb_hash_new();
+ st_foreach(threads_tbl, collect_threads, prof_result->threads);
+
+ return Data_Wrap_Struct(cResult, prof_result_mark, prof_result_free, prof_result);
+}
+
+
+static prof_result_t *
+get_prof_result(VALUE obj)
+{
+ if (BUILTIN_TYPE(obj) != T_DATA ||
+ RDATA(obj)->dfree != (RUBY_DATA_FUNC) prof_result_free)
+ {
+ /* Should never happen */
+ rb_raise(rb_eTypeError, "wrong result object");
+ }
+ return (prof_result_t *) DATA_PTR(obj);
+}
+
+/* call-seq:
+ threads -> Hash
+
+Returns a hash table keyed on thread ID. For each thread id,
+the hash table stores another hash table that contains profiling
+information for each method called during the threads execution.
+That hash table is keyed on method name and contains
+RubyProf::MethodInfo objects. */
+static VALUE
+prof_result_threads(VALUE self)
+{
+ prof_result_t *prof_result = get_prof_result(self);
+ return prof_result->threads;
+}
+
+
+
+/* call-seq:
+ measure_mode -> measure_mode
+
+ Returns what ruby-prof is measuring. Valid values include:
+
+ *RubyProf::PROCESS_TIME - Measure process time. This is default. It is implemented using the clock functions in the C Runtime library.
+ *RubyProf::WALL_TIME - Measure wall time using gettimeofday on Linx and GetLocalTime on Windows
+ *RubyProf::CPU_TIME - Measure time using the CPU clock counter. This mode is only supported on Pentium or PowerPC platforms.
+ *RubyProf::ALLOCATIONS - Measure object allocations. This requires a patched Ruby interpreter.
+ *RubyProf::MEMORY - Measure memory size. This requires a patched Ruby interpreter.
+ *RubyProf::GC_RUNS - Measure number of garbage collections. This requires a patched Ruby interpreter.
+ *RubyProf::GC_TIME - Measure time spent doing garbage collection. This requires a patched Ruby interpreter.*/
+static VALUE
+prof_get_measure_mode(VALUE self)
+{
+ return INT2NUM(measure_mode);
+}
+
+/* call-seq:
+ measure_mode=value -> void
+
+ Specifies what ruby-prof should measure. Valid values include:
+
+ *RubyProf::PROCESS_TIME - Measure process time. This is default. It is implemented using the clock functions in the C Runtime library.
+ *RubyProf::WALL_TIME - Measure wall time using gettimeofday on Linx and GetLocalTime on Windows
+ *RubyProf::CPU_TIME - Measure time using the CPU clock counter. This mode is only supported on Pentium or PowerPC platforms.
+ *RubyProf::ALLOCATIONS - Measure object allocations. This requires a patched Ruby interpreter.
+ *RubyProf::MEMORY - Measure memory size. This requires a patched Ruby interpreter.
+ *RubyProf::GC_RUNS - Measure number of garbage collections. This requires a patched Ruby interpreter.
+ *RubyProf::GC_TIME - Measure time spent doing garbage collection. This requires a patched Ruby interpreter.*/
+static VALUE
+prof_set_measure_mode(VALUE self, VALUE val)
+{
+ long mode = NUM2LONG(val);
+
+ if (threads_tbl)
+ {
+ rb_raise(rb_eRuntimeError, "can't set measure_mode while profiling");
+ }
+
+ switch (mode) {
+ case MEASURE_PROCESS_TIME:
+ get_measurement = measure_process_time;
+ convert_measurement = convert_process_time;
+ break;
+
+ case MEASURE_WALL_TIME:
+ get_measurement = measure_wall_time;
+ convert_measurement = convert_wall_time;
+ break;
+
+ #if defined(MEASURE_CPU_TIME)
+ case MEASURE_CPU_TIME:
+ if (cpu_frequency == 0)
+ cpu_frequency = get_cpu_frequency();
+ get_measurement = measure_cpu_time;
+ convert_measurement = convert_cpu_time;
+ break;
+ #endif
+
+ #if defined(MEASURE_ALLOCATIONS)
+ case MEASURE_ALLOCATIONS:
+ get_measurement = measure_allocations;
+ convert_measurement = convert_allocations;
+ break;
+ #endif
+
+ #if defined(MEASURE_MEMORY)
+ case MEASURE_MEMORY:
+ get_measurement = measure_memory;
+ convert_measurement = convert_memory;
+ break;
+ #endif
+
+ #if defined(MEASURE_GC_RUNS)
+ case MEASURE_GC_RUNS:
+ get_measurement = measure_gc_runs;
+ convert_measurement = convert_gc_runs;
+ break;
+ #endif
+
+ #if defined(MEASURE_GC_TIME)
+ case MEASURE_GC_TIME:
+ get_measurement = measure_gc_time;
+ convert_measurement = convert_gc_time;
+ break;
+ #endif
+
+ default:
+ rb_raise(rb_eArgError, "invalid mode: %ld", mode);
+ break;
+ }
+
+ measure_mode = mode;
+ return val;
+}
+
+/* call-seq:
+ exclude_threads= -> void
+
+ Specifies what threads ruby-prof should exclude from profiling */
+static VALUE
+prof_set_exclude_threads(VALUE self, VALUE threads)
+{
+ int i;
+
+ if (threads_tbl != NULL)
+ {
+ rb_raise(rb_eRuntimeError, "can't set exclude_threads while profiling");
+ }
+
+ /* Stay simple, first free the old hash table */
+ if (exclude_threads_tbl)
+ {
+ st_free_table(exclude_threads_tbl);
+ exclude_threads_tbl = NULL;
+ }
+
+ /* Now create a new one if the user passed in any threads */
+ if (threads != Qnil)
+ {
+ Check_Type(threads, T_ARRAY);
+ exclude_threads_tbl = st_init_numtable();
+
+ for (i=0; i < RARRAY_LEN(threads); ++i)
+ {
+ VALUE thread = rb_ary_entry(threads, i);
+ st_insert(exclude_threads_tbl, (st_data_t) rb_obj_id(thread), 0);
+ }
+ }
+ return threads;
+}
+
+
+/* ========= Profiling ============= */
+void
+prof_install_hook()
+{
+#ifdef RUBY_VM
+ rb_add_event_hook(prof_event_hook,
+ RUBY_EVENT_CALL | RUBY_EVENT_RETURN |
+ RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN
+ | RUBY_EVENT_LINE, Qnil);
+#else
+ rb_add_event_hook(prof_event_hook,
+ RUBY_EVENT_CALL | RUBY_EVENT_RETURN |
+ RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN
+ | RUBY_EVENT_LINE);
+#endif
+
+#if defined(TOGGLE_GC_STATS)
+ rb_gc_enable_stats();
+#endif
+}
+
+void
+prof_remove_hook()
+{
+#if defined(TOGGLE_GC_STATS)
+ rb_gc_disable_stats();
+#endif
+
+ /* Now unregister from event */
+ rb_remove_event_hook(prof_event_hook);
+}
+
+
+
+/* call-seq:
+ running? -> boolean
+
+ Returns whether a profile is currently running.*/
+static VALUE
+prof_running(VALUE self)
+{
+ if (threads_tbl != NULL)
+ return Qtrue;
+ else
+ return Qfalse;
+}
+
+/* call-seq:
+ start -> RubyProf
+
+ Starts recording profile data.*/
+static VALUE
+prof_start(VALUE self)
+{
+ if (threads_tbl != NULL)
+ {
+ rb_raise(rb_eRuntimeError, "RubyProf.start was already called");
+ }
+
+ /* Setup globals */
+ last_thread_data = NULL;
+ threads_tbl = threads_table_create();
+
+ prof_install_hook();
+ return self;
+}
+
+/* call-seq:
+ pause -> RubyProf
+
+ Pauses collecting profile data. */
+static VALUE
+prof_pause(VALUE self)
+{
+ if (threads_tbl == NULL)
+ {
+ rb_raise(rb_eRuntimeError, "RubyProf is not running.");
+ }
+
+ prof_remove_hook();
+ return self;
+}
+
+/* call-seq:
+ resume {block} -> RubyProf
+
+ Resumes recording profile data.*/
+static VALUE
+prof_resume(VALUE self)
+{
+ if (threads_tbl == NULL)
+ {
+ prof_start(self);
+ }
+ else
+ {
+ prof_install_hook();
+ }
+
+ if (rb_block_given_p())
+ {
+ rb_ensure(rb_yield, self, prof_pause, self);
+ }
+
+ return self;
+}
+
+/* call-seq:
+ stop -> RubyProf::Result
+
+ Stops collecting profile data and returns a RubyProf::Result object. */
+static VALUE
+prof_stop(VALUE self)
+{
+ VALUE result = Qnil;
+
+ prof_remove_hook();
+
+ prof_pop_threads();
+
+ /* Create the result */
+ result = prof_result_new();
+
+ /* Unset the last_thread_data (very important!)
+ and the threads table */
+ last_thread_data = NULL;
+ threads_table_free(threads_tbl);
+ threads_tbl = NULL;
+
+ return result;
+}
+
+/* call-seq:
+ profile {block} -> RubyProf::Result
+
+Profiles the specified block and returns a RubyProf::Result object. */
+static VALUE
+prof_profile(VALUE self)
+{
+ int result;
+
+ if (!rb_block_given_p())
+ {
+ rb_raise(rb_eArgError, "A block must be provided to the profile method.");
+ }
+
+ prof_start(self);
+ rb_protect(rb_yield, self, &result);
+ return prof_stop(self);
+}
+
+/* Get arround annoying limitations in RDOC */
+
+/* Document-method: measure_process_time
+ call-seq:
+ measure_process_time -> float
+
+Returns the process time.*/
+
+/* Document-method: measure_wall_time
+ call-seq:
+ measure_wall_time -> float
+
+Returns the wall time.*/
+
+/* Document-method: measure_cpu_time
+ call-seq:
+ measure_cpu_time -> float
+
+Returns the cpu time.*/
+
+/* Document-method: get_cpu_frequency
+ call-seq:
+ cpu_frequency -> int
+
+Returns the cpu's frequency. This value is needed when
+RubyProf::measure_mode is set to CPU_TIME. */
+
+/* Document-method: cpu_frequency
+ call-seq:
+ cpu_frequency -> int
+
+Returns the cpu's frequency. This value is needed when
+RubyProf::measure_mode is set to CPU_TIME. */
+
+/* Document-method: cpu_frequency=
+ call-seq:
+ cpu_frequency = frequency
+
+Sets the cpu's frequency. This value is needed when
+RubyProf::measure_mode is set to CPU_TIME. */
+
+/* Document-method: measure_allocations
+ call-seq:
+ measure_allocations -> int
+
+Returns the total number of object allocations since Ruby started.*/
+
+/* Document-method: measure_memory
+ call-seq:
+ measure_memory -> int
+
+Returns total allocated memory in bytes.*/
+
+/* Document-method: measure_gc_runs
+ call-seq:
+ gc_runs -> Integer
+
+Returns the total number of garbage collections.*/
+
+/* Document-method: measure_gc_time
+ call-seq:
+ gc_time -> Integer
+
+Returns the time spent doing garbage collections in microseconds.*/
+
+
+#if defined(_WIN32)
+__declspec(dllexport)
+#endif
+void
+
+Init_ruby_prof()
+{
+ mProf = rb_define_module("RubyProf");
+ rb_define_const(mProf, "VERSION", rb_str_new2(RUBY_PROF_VERSION));
+ rb_define_module_function(mProf, "start", prof_start, 0);
+ rb_define_module_function(mProf, "stop", prof_stop, 0);
+ rb_define_module_function(mProf, "resume", prof_resume, 0);
+ rb_define_module_function(mProf, "pause", prof_pause, 0);
+ rb_define_module_function(mProf, "running?", prof_running, 0);
+ rb_define_module_function(mProf, "profile", prof_profile, 0);
+
+ rb_define_singleton_method(mProf, "exclude_threads=", prof_set_exclude_threads, 1);
+ rb_define_singleton_method(mProf, "measure_mode", prof_get_measure_mode, 0);
+ rb_define_singleton_method(mProf, "measure_mode=", prof_set_measure_mode, 1);
+
+ rb_define_const(mProf, "CLOCKS_PER_SEC", INT2NUM(CLOCKS_PER_SEC));
+ rb_define_const(mProf, "PROCESS_TIME", INT2NUM(MEASURE_PROCESS_TIME));
+ rb_define_singleton_method(mProf, "measure_process_time", prof_measure_process_time, 0); /* in measure_process_time.h */
+ rb_define_const(mProf, "WALL_TIME", INT2NUM(MEASURE_WALL_TIME));
+ rb_define_singleton_method(mProf, "measure_wall_time", prof_measure_wall_time, 0); /* in measure_wall_time.h */
+
+ #ifndef MEASURE_CPU_TIME
+ rb_define_const(mProf, "CPU_TIME", Qnil);
+ #else
+ rb_define_const(mProf, "CPU_TIME", INT2NUM(MEASURE_CPU_TIME));
+ rb_define_singleton_method(mProf, "measure_cpu_time", prof_measure_cpu_time, 0); /* in measure_cpu_time.h */
+ rb_define_singleton_method(mProf, "cpu_frequency", prof_get_cpu_frequency, 0); /* in measure_cpu_time.h */
+ rb_define_singleton_method(mProf, "cpu_frequency=", prof_set_cpu_frequency, 1); /* in measure_cpu_time.h */
+ #endif
+
+ #ifndef MEASURE_ALLOCATIONS
+ rb_define_const(mProf, "ALLOCATIONS", Qnil);
+ #else
+ rb_define_const(mProf, "ALLOCATIONS", INT2NUM(MEASURE_ALLOCATIONS));
+ rb_define_singleton_method(mProf, "measure_allocations", prof_measure_allocations, 0); /* in measure_allocations.h */
+ #endif
+
+ #ifndef MEASURE_MEMORY
+ rb_define_const(mProf, "MEMORY", Qnil);
+ #else
+ rb_define_const(mProf, "MEMORY", INT2NUM(MEASURE_MEMORY));
+ rb_define_singleton_method(mProf, "measure_memory", prof_measure_memory, 0); /* in measure_memory.h */
+ #endif
+
+ #ifndef MEASURE_GC_RUNS
+ rb_define_const(mProf, "GC_RUNS", Qnil);
+ #else
+ rb_define_const(mProf, "GC_RUNS", INT2NUM(MEASURE_GC_RUNS));
+ rb_define_singleton_method(mProf, "measure_gc_runs", prof_measure_gc_runs, 0); /* in measure_gc_runs.h */
+ #endif
+
+ #ifndef MEASURE_GC_TIME
+ rb_define_const(mProf, "GC_TIME", Qnil);
+ #else
+ rb_define_const(mProf, "GC_TIME", INT2NUM(MEASURE_GC_TIME));
+ rb_define_singleton_method(mProf, "measure_gc_time", prof_measure_gc_time, 0); /* in measure_gc_time.h */
+ #endif
+
+ cResult = rb_define_class_under(mProf, "Result", rb_cObject);
+ rb_undef_method(CLASS_OF(cMethodInfo), "new");
+ rb_define_method(cResult, "threads", prof_result_threads, 0);
+
+ /* MethodInfo */
+ cMethodInfo = rb_define_class_under(mProf, "MethodInfo", rb_cObject);
+ rb_undef_method(CLASS_OF(cMethodInfo), "new");
+
+ rb_define_method(cMethodInfo, "klass", prof_method_klass, 0);
+ rb_define_method(cMethodInfo, "klass_name", prof_klass_name, 0);
+ rb_define_method(cMethodInfo, "method_name", prof_method_name, 0);
+ rb_define_method(cMethodInfo, "full_name", prof_full_name, 0);
+ rb_define_method(cMethodInfo, "method_id", prof_method_id, 0);
+
+ rb_define_method(cMethodInfo, "source_file", prof_method_source_file,0);
+ rb_define_method(cMethodInfo, "line", prof_method_line, 0);
+
+ rb_define_method(cMethodInfo, "call_infos", prof_method_call_infos, 0);
+
+ /* CallInfo */
+ cCallInfo = rb_define_class_under(mProf, "CallInfo", rb_cObject);
+ rb_undef_method(CLASS_OF(cCallInfo), "new");
+ rb_define_method(cCallInfo, "parent", prof_call_info_parent, 0);
+ rb_define_method(cCallInfo, "children", prof_call_info_children, 0);
+ rb_define_method(cCallInfo, "target", prof_call_info_target, 0);
+ rb_define_method(cCallInfo, "called", prof_call_info_called, 0);
+ rb_define_method(cCallInfo, "total_time", prof_call_info_total_time, 0);
+ rb_define_method(cCallInfo, "self_time", prof_call_info_self_time, 0);
+ rb_define_method(cCallInfo, "wait_time", prof_call_info_wait_time, 0);
+ rb_define_method(cCallInfo, "line", prof_call_info_line, 0);
+}
diff --git a/ext/ruby_prof.h b/ext/ruby_prof.h
new file mode 100644
index 0000000..cd6a1eb
--- /dev/null
+++ b/ext/ruby_prof.h
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2008 Shugo Maeda <shugo at ruby-lang.org>
+ * Charlie Savage <cfis at savagexi.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* ruby-prof tracks the time spent executing every method in ruby programming.
+ The main players are:
+
+ prof_result_t - Its one field, values, contains the overall results
+ thread_data_t - Stores data about a single thread.
+ prof_stack_t - The method call stack in a particular thread
+ prof_method_t - Profiling information for each method
+ prof_call_info_t - Keeps track a method's callers and callees.
+
+ The final resulut is a hash table of thread_data_t, keyed on the thread
+ id. Each thread has an hash a table of prof_method_t, keyed on the
+ method id. A hash table is used for quick look up when doing a profile.
+ However, it is exposed to Ruby as an array.
+
+ Each prof_method_t has two hash tables, parent and children, of prof_call_info_t.
+ These objects keep track of a method's callers (who called the method) and its
+ callees (who the method called). These are keyed the method id, but once again,
+ are exposed to Ruby as arrays. Each prof_call_into_t maintains a pointer to the
+ caller or callee method, thereby making it easy to navigate through the call
+ hierarchy in ruby - which is very helpful for creating call graphs.
+*/
+
+/* #define DEBUG */
+
+#ifndef RUBY_PROF_H
+#define RUBY_PROF_H
+
+#include <stdio.h>
+
+#include <ruby.h>
+
+#ifndef RUBY_VM
+#include <node.h>
+#include <st.h>
+typedef rb_event_t rb_event_flag_t;
+#define rb_sourcefile() (node ? node->nd_file : 0)
+#define rb_sourceline() (node ? nd_line(node) : 0)
+#endif
+
+#include "version.h"
+
+/* ================ Constants =================*/
+#define INITIAL_STACK_SIZE 8
+#define INITIAL_CALL_INFOS_SIZE 2
+
+
+/* ================ Measurement =================*/
+#ifdef HAVE_LONG_LONG
+typedef unsigned LONG_LONG prof_measure_t;
+#else
+typedef unsigned long prof_measure_t;
+#endif
+
+#include "measure_process_time.h"
+#include "measure_wall_time.h"
+#include "measure_cpu_time.h"
+#include "measure_allocations.h"
+#include "measure_memory.h"
+#include "measure_gc_runs.h"
+#include "measure_gc_time.h"
+
+static prof_measure_t (*get_measurement)() = measure_process_time;
+static double (*convert_measurement)(prof_measure_t) = convert_process_time;
+
+/* ================ DataTypes =================*/
+static VALUE mProf;
+static VALUE cResult;
+static VALUE cMethodInfo;
+static VALUE cCallInfo;
+
+/* Profiling information for each method. */
+typedef struct {
+ VALUE klass; /* The method's class. */
+ ID mid; /* The method id. */
+ int depth; /* The recursion depth. */
+ int key; /* Cache calculated key */
+} prof_method_key_t;
+
+struct prof_call_infos_t;
+
+/* Profiling information for each method. */
+typedef struct {
+ prof_method_key_t *key; /* Method key */
+ const char *source_file; /* The method's source file */
+ int line; /* The method's line number. */
+ int active; /* Is this recursion depth. */
+ struct prof_call_infos_t *call_infos; /* Call info objects for this method */
+ VALUE object; /* Cahced ruby object */
+} prof_method_t;
+
+/* Callers and callee information for a method. */
+typedef struct prof_call_info_t {
+ prof_method_t *target; /* Use target instead of method to avoid conflict with Ruby method */
+ struct prof_call_info_t *parent;
+ st_table *call_infos;
+ int called;
+ prof_measure_t total_time;
+ prof_measure_t self_time;
+ prof_measure_t wait_time;
+ int line;
+ VALUE object;
+ VALUE children;
+} prof_call_info_t;
+
+/* Array of call_info objects */
+typedef struct prof_call_infos_t {
+ prof_call_info_t **start;
+ prof_call_info_t **end;
+ prof_call_info_t **ptr;
+ VALUE object;
+} prof_call_infos_t;
+
+
+/* Temporary object that maintains profiling information
+ for active methods - there is one per method.*/
+typedef struct {
+ /* Caching prof_method_t values significantly
+ increases performance. */
+ prof_call_info_t *call_info;
+ prof_measure_t start_time;
+ prof_measure_t wait_time;
+ prof_measure_t child_time;
+ unsigned int line;
+} prof_frame_t;
+
+/* Current stack of active methods.*/
+typedef struct {
+ prof_frame_t *start;
+ prof_frame_t *end;
+ prof_frame_t *ptr;
+} prof_stack_t;
+
+/* Profiling information for a thread. */
+typedef struct {
+ VALUE thread_id; /* Thread id */
+ st_table* method_table; /* Methods called in the thread */
+ prof_stack_t* stack; /* Active methods */
+ prof_measure_t last_switch; /* Point of last context switch */
+} thread_data_t;
+
+typedef struct {
+ VALUE threads;
+} prof_result_t;
+
+
+/* ================ Variables =================*/
+static int measure_mode;
+static st_table *threads_tbl = NULL;
+static st_table *exclude_threads_tbl = NULL;
+
+/* TODO - If Ruby become multi-threaded this has to turn into
+ a separate stack since this isn't thread safe! */
+static thread_data_t* last_thread_data = NULL;
+
+
+/* Forward declarations */
+static VALUE prof_call_infos_wrap(prof_call_infos_t *call_infos);
+static VALUE prof_call_info_wrap(prof_call_info_t *call_info);
+static VALUE prof_method_wrap(prof_method_t *result);
+
+#endif
diff --git a/ext/vc/ruby_prof.sln b/ext/vc/ruby_prof.sln
new file mode 100644
index 0000000..2f123b1
--- /dev/null
+++ b/ext/vc/ruby_prof.sln
@@ -0,0 +1,20 @@
+
+Microsoft Visual Studio Solution File, Format Version 10.00
+# Visual Studio 2008
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ruby_prof", "ruby_prof.vcproj", "{DDB3E992-BF4B-4413-B061-288E40AECAD3}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Release|Win32 = Release|Win32
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {DDB3E992-BF4B-4413-B061-288E40AECAD3}.Debug|Win32.ActiveCfg = Debug|Win32
+ {DDB3E992-BF4B-4413-B061-288E40AECAD3}.Debug|Win32.Build.0 = Debug|Win32
+ {DDB3E992-BF4B-4413-B061-288E40AECAD3}.Release|Win32.ActiveCfg = Release|Win32
+ {DDB3E992-BF4B-4413-B061-288E40AECAD3}.Release|Win32.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/ext/vc/ruby_prof.vcproj b/ext/vc/ruby_prof.vcproj
new file mode 100644
index 0000000..d5041e3
--- /dev/null
+++ b/ext/vc/ruby_prof.vcproj
@@ -0,0 +1,241 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9.00"
+ Name="ruby_prof"
+ ProjectGUID="{DDB3E992-BF4B-4413-B061-288E40AECAD3}"
+ RootNamespace="rubyprof"
+ Keyword="Win32Proj"
+ TargetFrameworkVersion="131072"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="2"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories=""C:\Development\ruby\lib\ruby\1.8\i386-mswin32""
+ PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;_USRDLL;RUBYPROF_EXPORTS"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="3"
+ UsePrecompiledHeader="0"
+ BrowseInformation="0"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="true"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="msvcrt-ruby18.lib"
+ OutputFile="C:\Development\ruby\lib\ruby\gems\1.8\gems\ruby-prof-0.7.0-x86-mswin32-60\lib\$(ProjectName).so"
+ LinkIncremental="2"
+ AdditionalLibraryDirectories="C:\Development\ruby\lib"
+ GenerateDebugInformation="true"
+ SubSystem="2"
+ RandomizedBaseAddress="1"
+ DataExecutionPrevention="0"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="2"
+ CharacterSet="1"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories="C:\Development\ruby\lib\ruby\1.8\i386-mswin32"
+ PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;_USRDLL;RUBYPROF_EXPORTS"
+ RuntimeLibrary="2"
+ UsePrecompiledHeader="0"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="true"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="msvcrt-ruby18.lib"
+ OutputFile="$(OutDir)\$(ProjectName).so"
+ LinkIncremental="1"
+ AdditionalLibraryDirectories="C:\Development\ruby\lib"
+ GenerateDebugInformation="true"
+ SubSystem="2"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ RandomizedBaseAddress="1"
+ DataExecutionPrevention="0"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Source Files"
+ Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
+ UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
+ >
+ <File
+ RelativePath="..\ruby_prof.c"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl;inc;xsd"
+ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+ >
+ <File
+ RelativePath="..\measure_allocations.h"
+ >
+ </File>
+ <File
+ RelativePath="..\measure_cpu_time.h"
+ >
+ </File>
+ <File
+ RelativePath="..\measure_gc_runs.h"
+ >
+ </File>
+ <File
+ RelativePath="..\measure_gc_time.h"
+ >
+ </File>
+ <File
+ RelativePath="..\measure_memory.h"
+ >
+ </File>
+ <File
+ RelativePath="..\measure_process_time.h"
+ >
+ </File>
+ <File
+ RelativePath="..\measure_wall_time.h"
+ >
+ </File>
+ <File
+ RelativePath="..\ruby_prof.h"
+ >
+ </File>
+ <File
+ RelativePath="..\version.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Resource Files"
+ Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav"
+ UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
+ >
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/ext/version.h b/ext/version.h
new file mode 100644
index 0000000..3ceb126
--- /dev/null
+++ b/ext/version.h
@@ -0,0 +1,4 @@
+#define RUBY_PROF_VERSION "0.7.3"
+#define RUBY_PROF_VERSION_MAJ 0
+#define RUBY_PROF_VERSION_MIN 7
+#define RUBY_PROF_VERSION_MIC 3
diff --git a/lib/ruby-prof.rb b/lib/ruby-prof.rb
new file mode 100644
index 0000000..8b24a7e
--- /dev/null
+++ b/lib/ruby-prof.rb
@@ -0,0 +1,48 @@
+require "ruby_prof.so"
+
+require "ruby-prof/method_info"
+require "ruby-prof/call_info"
+require "ruby-prof/aggregate_call_info"
+require "ruby-prof/flat_printer"
+require "ruby-prof/graph_printer"
+require "ruby-prof/graph_html_printer"
+require "ruby-prof/call_tree_printer"
+
+require "ruby-prof/test"
+
+module RubyProf
+ # See if the user specified the clock mode via
+ # the RUBY_PROF_MEASURE_MODE environment variable
+ def self.figure_measure_mode
+ case ENV["RUBY_PROF_MEASURE_MODE"]
+ when "wall" || "wall_time"
+ RubyProf.measure_mode = RubyProf::WALL_TIME
+ when "cpu" || "cpu_time"
+ if ENV.key?("RUBY_PROF_CPU_FREQUENCY")
+ RubyProf.cpu_frequency = ENV["RUBY_PROF_CPU_FREQUENCY"].to_f
+ else
+ begin
+ open("/proc/cpuinfo") do |f|
+ f.each_line do |line|
+ s = line.slice(/cpu MHz\s*:\s*(.*)/, 1)
+ if s
+ RubyProf.cpu_frequency = s.to_f * 1000000
+ break
+ end
+ end
+ end
+ rescue Errno::ENOENT
+ end
+ end
+ RubyProf.measure_mode = RubyProf::CPU_TIME
+ when "allocations"
+ RubyProf.measure_mode = RubyProf::ALLOCATIONS
+ when "memory"
+ RubyProf.measure_mode = RubyProf::MEMORY
+ else
+ RubyProf.measure_mode = RubyProf::PROCESS_TIME
+ end
+ end
+end
+
+RubyProf::figure_measure_mode
diff --git a/lib/ruby-prof/abstract_printer.rb b/lib/ruby-prof/abstract_printer.rb
new file mode 100644
index 0000000..63cd60d
--- /dev/null
+++ b/lib/ruby-prof/abstract_printer.rb
@@ -0,0 +1,41 @@
+module RubyProf
+ class AbstractPrinter
+ def initialize(result)
+ @result = result
+ @output = nil
+ @options = {}
+ end
+
+ # Specify print options.
+ #
+ # options - Hash table
+ # :min_percent - Number 0 to 100 that specifes the minimum
+ # %self (the methods self time divided by the
+ # overall total time) that a method must take
+ # for it to be printed out in the report.
+ # Default value is 0.
+ #
+ # :print_file - True or false. Specifies if a method's source
+ # file should be printed. Default value if false.
+ #
+ def setup_options(options = {})
+ @options = options
+ end
+
+ def min_percent
+ @options[:min_percent] || 0
+ end
+
+ def print_file
+ @options[:print_file] || false
+ end
+
+ def method_name(method)
+ name = method.full_name
+ if print_file
+ name += " (#{method.source_file}:#{method.line}}"
+ end
+ name
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/ruby-prof/aggregate_call_info.rb b/lib/ruby-prof/aggregate_call_info.rb
new file mode 100644
index 0000000..c86d2c4
--- /dev/null
+++ b/lib/ruby-prof/aggregate_call_info.rb
@@ -0,0 +1,62 @@
+module RubyProf
+ class AggregateCallInfo
+ attr_reader :call_infos
+ def initialize(call_infos)
+ if call_infos.length == 0
+ raise(ArgumentError, "Must specify at least one call info.")
+ end
+ @call_infos = call_infos
+ end
+
+ def target
+ call_infos.first.target
+ end
+
+ def parent
+ call_infos.first.parent
+ end
+
+ def line
+ call_infos.first.line
+ end
+
+ def children
+ call_infos.inject(Array.new) do |result, call_info|
+ result.concat(call_info.children)
+ end
+ end
+
+ def total_time
+ aggregate(:total_time)
+ end
+
+ def self_time
+ aggregate(:self_time)
+ end
+
+ def wait_time
+ aggregate(:wait_time)
+ end
+
+ def children_time
+ aggregate(:children_time)
+ end
+
+ def called
+ aggregate(:called)
+ end
+
+ def to_s
+ "#{call_infos.first.full_name}"
+ end
+
+ private
+
+ def aggregate(method_name)
+ self.call_infos.inject(0) do |sum, call_info|
+ sum += call_info.send(method_name)
+ sum
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/ruby-prof/call_info.rb b/lib/ruby-prof/call_info.rb
new file mode 100644
index 0000000..0f08d90
--- /dev/null
+++ b/lib/ruby-prof/call_info.rb
@@ -0,0 +1,47 @@
+module RubyProf
+ class CallInfo
+ def depth
+ result = 0
+ call_info = self.parent
+
+ while call_info
+ result += 1
+ call_info = call_info.parent
+ end
+ result
+ end
+
+ def children_time
+ children.inject(0) do |sum, call_info|
+ sum += call_info.total_time
+ end
+ end
+
+ def stack
+ @stack ||= begin
+ methods = Array.new
+ call_info = self
+
+ while call_info
+ methods << call_info.target
+ call_info = call_info.parent
+ end
+ methods.reverse
+ end
+ end
+
+ def call_sequence
+ @call_sequence ||= begin
+ stack.map {|method| method.full_name}.join('->')
+ end
+ end
+
+ def root?
+ self.parent.nil?
+ end
+
+ def to_s
+ "#{call_sequence}"
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/ruby-prof/call_tree_printer.rb b/lib/ruby-prof/call_tree_printer.rb
new file mode 100644
index 0000000..a01648c
--- /dev/null
+++ b/lib/ruby-prof/call_tree_printer.rb
@@ -0,0 +1,84 @@
+require 'ruby-prof/abstract_printer'
+
+module RubyProf
+ # Generate profiling information in calltree format
+ # for use by kcachegrind and similar tools.
+
+ class CallTreePrinter < AbstractPrinter
+ def print(output = STDOUT, options = {})
+ @output = output
+ setup_options(options)
+
+ # add a header - this information is somewhat arbitrary
+ @output << "events: "
+ case RubyProf.measure_mode
+ when RubyProf::PROCESS_TIME
+ @value_scale = RubyProf::CLOCKS_PER_SEC;
+ @output << 'process_time'
+ when RubyProf::WALL_TIME
+ @value_scale = 1_000_000
+ @output << 'wall_time'
+ when RubyProf.const_defined?(:CPU_TIME) && RubyProf::CPU_TIME
+ @value_scale = RubyProf.cpu_frequency
+ @output << 'cpu_time'
+ when RubyProf.const_defined?(:ALLOCATIONS) && RubyProf::ALLOCATIONS
+ @value_scale = 1
+ @output << 'allocations'
+ when RubyProf.const_defined?(:MEMORY) && RubyProf::MEMORY
+ @value_scale = 1
+ @output << 'memory'
+ when RubyProf.const_defined?(:GC_RUNS) && RubyProf::GC_RUNS
+ @value_scale = 1
+ @output << 'gc_runs'
+ when RubyProf.const_defined?(:GC_TIME) && RubyProf::GC_TIME
+ @value_scale = 1000000
+ @output << 'gc_time'
+ else
+ raise "Unknown measure mode: #{RubyProf.measure_mode}"
+ end
+ @output << "\n\n"
+
+ print_threads
+ end
+
+ def print_threads
+ @result.threads.each do |thread_id, methods|
+ print_methods(thread_id, methods)
+ end
+ end
+
+ def convert(value)
+ (value * @value_scale).round
+ end
+
+ def file(method)
+ File.expand_path(method.source_file)
+ end
+
+ def name(method)
+ "#{method.klass_name}::#{method.method_name}"
+ end
+
+ def print_methods(thread_id, methods)
+ methods.reverse_each do |method|
+ # Print out the file and method name
+ @output << "fl=#{file(method)}\n"
+ @output << "fn=#{name(method)}\n"
+
+ # Now print out the function line number and its self time
+ @output << "#{method.line} #{convert(method.self_time)}\n"
+
+ # Now print out all the children methods
+ method.children.each do |callee|
+ @output << "cfl=#{file(callee.target)}\n"
+ @output << "cfn=#{name(callee.target)}\n"
+ @output << "calls=#{callee.called} #{callee.line}\n"
+
+ # Print out total times here!
+ @output << "#{callee.line} #{convert(callee.total_time)}\n"
+ end
+ @output << "\n"
+ end
+ end #end print_methods
+ end # end class
+end # end packages
diff --git a/lib/ruby-prof/flat_printer.rb b/lib/ruby-prof/flat_printer.rb
new file mode 100644
index 0000000..6c8259d
--- /dev/null
+++ b/lib/ruby-prof/flat_printer.rb
@@ -0,0 +1,79 @@
+require 'ruby-prof/abstract_printer'
+
+module RubyProf
+ # Generates flat[link:files/examples/flat_txt.html] profile reports as text.
+ # To use the flat printer:
+ #
+ # result = RubyProf.profile do
+ # [code to profile]
+ # end
+ #
+ # printer = RubyProf::FlatPrinter.new(result)
+ # printer.print(STDOUT, 0)
+ #
+ class FlatPrinter < AbstractPrinter
+ # Print a flat profile report to the provided output.
+ #
+ # output - Any IO oject, including STDOUT or a file.
+ # The default value is STDOUT.
+ #
+ # options - Hash of print options. See #setup_options
+ # for more information.
+ #
+ def print(output = STDOUT, options = {})
+ @output = output
+ setup_options(options)
+ print_threads
+ end
+
+ private
+
+ def print_threads
+ @result.threads.each do |thread_id, methods|
+ print_methods(thread_id, methods)
+ @output << "\n" * 2
+ end
+ end
+
+ def print_methods(thread_id, methods)
+ # Get total time
+ toplevel = methods.sort.last
+ total_time = toplevel.total_time
+ if total_time == 0
+ total_time = 0.01
+ end
+
+ # Now sort methods by largest self time,
+ # not total time like in other printouts
+ methods = methods.sort do |m1, m2|
+ m1.self_time <=> m2.self_time
+ end.reverse
+
+ @output << "Thread ID: %d\n" % thread_id
+ @output << "Total: %0.6f\n" % total_time
+ @output << "\n"
+ @output << " %self total self wait child calls name\n"
+
+ sum = 0
+ methods.each do |method|
+ self_percent = (method.self_time / total_time) * 100
+ next if self_percent < min_percent
+
+ sum += method.self_time
+ #self_time_called = method.called > 0 ? method.self_time/method.called : 0
+ #total_time_called = method.called > 0? method.total_time/method.called : 0
+
+ @output << "%6.2f %8.2f %8.2f %8.2f %8.2f %8d %s\n" % [
+ method.self_time / total_time * 100, # %self
+ method.total_time, # total
+ method.self_time, # self
+ method.wait_time, # wait
+ method.children_time, # children
+ method.called, # calls
+ method_name(method) # name
+ ]
+ end
+ end
+ end
+end
+
diff --git a/lib/ruby-prof/graph_html_printer.rb b/lib/ruby-prof/graph_html_printer.rb
new file mode 100644
index 0000000..7b04d4b
--- /dev/null
+++ b/lib/ruby-prof/graph_html_printer.rb
@@ -0,0 +1,256 @@
+require 'ruby-prof/abstract_printer'
+require 'erb'
+
+module RubyProf
+ # Generates graph[link:files/examples/graph_html.html] profile reports as html.
+ # To use the grap html printer:
+ #
+ # result = RubyProf.profile do
+ # [code to profile]
+ # end
+ #
+ # printer = RubyProf::GraphHtmlPrinter.new(result)
+ # printer.print(STDOUT, :min_percent=>0)
+ #
+ # The constructor takes two arguments. The first is
+ # a RubyProf::Result object generated from a profiling
+ # run. The second is the minimum %total (the methods
+ # total time divided by the overall total time) that
+ # a method must take for it to be printed out in
+ # the report. Use this parameter to eliminate methods
+ # that are not important to the overall profiling results.
+
+ class GraphHtmlPrinter < AbstractPrinter
+ include ERB::Util
+
+ PERCENTAGE_WIDTH = 8
+ TIME_WIDTH = 10
+ CALL_WIDTH = 20
+
+ # Create a GraphPrinter. Result is a RubyProf::Result
+ # object generated from a profiling run.
+ def initialize(result)
+ super(result)
+ @thread_times = Hash.new
+ calculate_thread_times
+ end
+
+ # Print a graph html report to the provided output.
+ #
+ # output - Any IO oject, including STDOUT or a file.
+ # The default value is STDOUT.
+ #
+ # options - Hash of print options. See #setup_options
+ # for more information.
+ #
+ def print(output = STDOUT, options = {})
+ @output = output
+ setup_options(options)
+
+ filename = options[:filename]
+ template = filename ? File.read(filename).untaint : (options[:template] || self.template)
+ _erbout = @output
+ erb = ERB.new(template, nil, nil)
+ erb.filename = filename
+ @output << erb.result(binding)
+ end
+
+ # These methods should be private but then ERB doesn't
+ # work. Turn off RDOC though
+ #--
+ def calculate_thread_times
+ # Cache thread times since this is an expensive
+ # operation with the required sorting
+ @result.threads.each do |thread_id, methods|
+ top = methods.sort.last
+
+ thread_time = 0.01
+ thread_time = top.total_time if top.total_time > 0
+
+ @thread_times[thread_id] = thread_time
+ end
+ end
+
+ def thread_time(thread_id)
+ @thread_times[thread_id]
+ end
+
+ def total_percent(thread_id, method)
+ overall_time = self.thread_time(thread_id)
+ (method.total_time/overall_time) * 100
+ end
+
+ def self_percent(method)
+ overall_time = self.thread_time(method.thread_id)
+ (method.self_time/overall_time) * 100
+ end
+
+ # Creates a link to a method. Note that we do not create
+ # links to methods which are under the min_perecent
+ # specified by the user, since they will not be
+ # printed out.
+ def create_link(thread_id, method)
+ if self.total_percent(thread_id, method) < min_percent
+ # Just return name
+ h method.full_name
+ else
+ href = '#' + method_href(thread_id, method)
+ "<a href=\"#{href}\">#{h method.full_name}</a>"
+ end
+ end
+
+ def method_href(thread_id, method)
+ h(method.full_name.gsub(/[><#\.\?=:]/,"_") + "_" + thread_id.to_s)
+ end
+
+ def template
+'
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <style media="all" type="text/css">
+ table {
+ border-collapse: collapse;
+ border: 1px solid #CCC;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 9pt;
+ line-height: normal;
+ width: 100%;
+ }
+
+ th {
+ text-align: center;
+ border-top: 1px solid #FB7A31;
+ border-bottom: 1px solid #FB7A31;
+ background: #FFC;
+ padding: 0.3em;
+ border-left: 1px solid silver;
+ }
+
+ tr.break td {
+ border: 0;
+ border-top: 1px solid #FB7A31;
+ padding: 0;
+ margin: 0;
+ }
+
+ tr.method td {
+ font-weight: bold;
+ }
+
+ td {
+ padding: 0.3em;
+ }
+
+ td:first-child {
+ width: 190px;
+ }
+
+ td {
+ border-left: 1px solid #CCC;
+ text-align: center;
+ }
+
+ .method_name {
+ text-align: left;
+ }
+ </style>
+ </head>
+ <body>
+ <h1>Profile Report</h1>
+ <!-- Threads Table -->
+ <table>
+ <tr>
+ <th>Thread ID</th>
+ <th>Total Time</th>
+ </tr>
+ <% for thread_id, methods in @result.threads %>
+ <tr>
+ <td><a href="#<%= thread_id %>"><%= thread_id %></a></td>
+ <td><%= thread_time(thread_id) %></td>
+ </tr>
+ <% end %>
+ </table>
+
+ <!-- Methods Tables -->
+ <% for thread_id, methods in @result.threads
+ total_time = thread_time(thread_id) %>
+ <h2><a name="<%= thread_id %>">Thread <%= thread_id %></a></h2>
+
+ <table>
+ <tr>
+ <th><%= sprintf("%#{PERCENTAGE_WIDTH}s", "%Total") %></th>
+ <th><%= sprintf("%#{PERCENTAGE_WIDTH}s", "%Self") %></th>
+ <th><%= sprintf("%#{TIME_WIDTH}s", "Total") %></th>
+ <th><%= sprintf("%#{TIME_WIDTH}s", "Self") %></th>
+ <th><%= sprintf("%#{TIME_WIDTH}s", "Wait") %></th>
+ <th><%= sprintf("%#{TIME_WIDTH+2}s", "Child") %></th>
+ <th><%= sprintf("%#{CALL_WIDTH}s", "Calls") %></th>
+ <th class="method_name">Name</th>
+ <th>Line</th>
+ </tr>
+
+ <% min_time = @options[:min_time] || (@options[:nonzero] ? 0.005 : nil)
+ methods.sort.reverse_each do |method|
+ total_percentage = (method.total_time/total_time) * 100
+ next if total_percentage < min_percent
+ next if min_time && method.total_time < min_time
+ self_percentage = (method.self_time/total_time) * 100 %>
+
+ <!-- Parents -->
+ <% for caller in method.aggregate_parents
+ next unless caller.parent
+ next if min_time && caller.total_time < min_time %>
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", caller.total_time) %></td>
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", caller.self_time) %></td>
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", caller.wait_time) %></td>
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", caller.children_time) %></td>
+ <% called = "#{caller.called}/#{method.called}" %>
+ <td><%= sprintf("%#{CALL_WIDTH}s", called) %></td>
+ <td class="method_name"><%= create_link(thread_id, caller.parent.target) %></td>
+ <td><a href="file://<%=h srcfile=File.expand_path(caller.parent.target.source_file) %>#line=<%= linenum=caller.line %>" title="<%=h srcfile %>:<%= linenum %>"><%= caller.line %></a></td>
+ </tr>
+ <% end %>
+
+ <tr class="method">
+ <td><%= sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", total_percentage) %></td>
+ <td><%= sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", self_percentage) %></td>
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", method.total_time) %></td>
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", method.self_time) %></td>
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", method.wait_time) %></td>
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", method.children_time) %></td>
+ <td><%= sprintf("%#{CALL_WIDTH}i", method.called) %></td>
+ <td class="method_name"><a name="<%= method_href(thread_id, method) %>"><%= h method.full_name %></a></td>
+ <td><a href="file://<%=h srcfile=File.expand_path(method.source_file) %>#line=<%= linenum=method.line %>" title="<%=h srcfile %>:<%= linenum %>"><%= method.line %></a></td>
+ </tr>
+
+ <!-- Children -->
+ <% for callee in method.aggregate_children %>
+ <% next if min_time && callee.total_time < min_time %>
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", callee.total_time) %></td>
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", callee.self_time) %></td>
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", callee.wait_time) %></td>
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", callee.children_time) %></td>
+ <% called = "#{callee.called}/#{callee.target.called}" %>
+ <td><%= sprintf("%#{CALL_WIDTH}s", called) %></td>
+ <td class="method_name"><%= create_link(thread_id, callee.target) %></td>
+ <td><a href="file://<%=h srcfile=File.expand_path(method.source_file) %>#line=<%= linenum=callee.line %>" title="<%=h srcfile %>:<%= linenum %>"><%= callee.line %></a></td>
+ </tr>
+ <% end %>
+ <!-- Create divider row -->
+ <tr class="break"><td colspan="9"></td></tr>
+ <% end %>
+ </table>
+ <% end %>
+ </body>
+</html>'
+ end
+ end
+end
+
diff --git a/lib/ruby-prof/graph_printer.rb b/lib/ruby-prof/graph_printer.rb
new file mode 100644
index 0000000..a03eebc
--- /dev/null
+++ b/lib/ruby-prof/graph_printer.rb
@@ -0,0 +1,164 @@
+require 'ruby-prof/abstract_printer'
+
+module RubyProf
+ # Generates graph[link:files/examples/graph_txt.html] profile reports as text.
+ # To use the graph printer:
+ #
+ # result = RubyProf.profile do
+ # [code to profile]
+ # end
+ #
+ # printer = RubyProf::GraphPrinter.new(result, 5)
+ # printer.print(STDOUT, 0)
+ #
+ # The constructor takes two arguments. The first is
+ # a RubyProf::Result object generated from a profiling
+ # run. The second is the minimum %total (the methods
+ # total time divided by the overall total time) that
+ # a method must take for it to be printed out in
+ # the report. Use this parameter to eliminate methods
+ # that are not important to the overall profiling results.
+
+ class GraphPrinter < AbstractPrinter
+ PERCENTAGE_WIDTH = 8
+ TIME_WIDTH = 10
+ CALL_WIDTH = 17
+
+ # Create a GraphPrinter. Result is a RubyProf::Result
+ # object generated from a profiling run.
+ def initialize(result)
+ super(result)
+ @thread_times = Hash.new
+ calculate_thread_times
+ end
+
+ def calculate_thread_times
+ # Cache thread times since this is an expensive
+ # operation with the required sorting
+ @result.threads.each do |thread_id, methods|
+ top = methods.sort.last
+
+ thread_time = 0.01
+ thread_time = top.total_time if top.total_time > 0
+
+ @thread_times[thread_id] = thread_time
+ end
+ end
+
+ # Print a graph report to the provided output.
+ #
+ # output - Any IO oject, including STDOUT or a file.
+ # The default value is STDOUT.
+ #
+ # options - Hash of print options. See #setup_options
+ # for more information.
+ #
+ def print(output = STDOUT, options = {})
+ @output = output
+ setup_options(options)
+ print_threads
+ end
+
+ private
+ def print_threads
+ # sort assumes that spawned threads have higher object_ids
+ @result.threads.sort.each do |thread_id, methods|
+ print_methods(thread_id, methods)
+ @output << "\n" * 2
+ end
+ end
+
+ def print_methods(thread_id, methods)
+ # Sort methods from longest to shortest total time
+ methods = methods.sort
+
+ toplevel = methods.last
+ total_time = toplevel.total_time
+ if total_time == 0
+ total_time = 0.01
+ end
+
+ print_heading(thread_id)
+
+ # Print each method in total time order
+ methods.reverse_each do |method|
+ total_percentage = (method.total_time/total_time) * 100
+ self_percentage = (method.self_time/total_time) * 100
+
+ next if total_percentage < min_percent
+
+ @output << "-" * 80 << "\n"
+
+ print_parents(thread_id, method)
+
+ # 1 is for % sign
+ @output << sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", total_percentage)
+ @output << sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", self_percentage)
+ @output << sprintf("%#{TIME_WIDTH}.2f", method.total_time)
+ @output << sprintf("%#{TIME_WIDTH}.2f", method.self_time)
+ @output << sprintf("%#{TIME_WIDTH}.2f", method.wait_time)
+ @output << sprintf("%#{TIME_WIDTH}.2f", method.children_time)
+ @output << sprintf("%#{CALL_WIDTH}i", method.called)
+ @output << sprintf(" %s", method_name(method))
+ if print_file
+ @output << sprintf(" %s:%s", method.source_file, method.line)
+ end
+ @output << "\n"
+
+ print_children(method)
+ end
+ end
+
+ def print_heading(thread_id)
+ @output << "Thread ID: #{thread_id}\n"
+ @output << "Total Time: #{@thread_times[thread_id]}\n"
+ @output << "\n"
+
+ # 1 is for % sign
+ @output << sprintf("%#{PERCENTAGE_WIDTH}s", "%total")
+ @output << sprintf("%#{PERCENTAGE_WIDTH}s", "%self")
+ @output << sprintf("%#{TIME_WIDTH}s", "total")
+ @output << sprintf("%#{TIME_WIDTH}s", "self")
+ @output << sprintf("%#{TIME_WIDTH}s", "wait")
+ @output << sprintf("%#{TIME_WIDTH}s", "child")
+ @output << sprintf("%#{CALL_WIDTH}s", "calls")
+ @output << " Name"
+ @output << "\n"
+ end
+
+ def print_parents(thread_id, method)
+ method.aggregate_parents.each do |caller|
+ next unless caller.parent
+ @output << " " * 2 * PERCENTAGE_WIDTH
+ @output << sprintf("%#{TIME_WIDTH}.2f", caller.total_time)
+ @output << sprintf("%#{TIME_WIDTH}.2f", caller.self_time)
+ @output << sprintf("%#{TIME_WIDTH}.2f", caller.wait_time)
+ @output << sprintf("%#{TIME_WIDTH}.2f", caller.children_time)
+
+ call_called = "#{caller.called}/#{method.called}"
+ @output << sprintf("%#{CALL_WIDTH}s", call_called)
+ @output << sprintf(" %s", caller.parent.target.full_name)
+ @output << "\n"
+ end
+ end
+
+ def print_children(method)
+ method.aggregate_children.each do |child|
+ # Get children method
+
+ @output << " " * 2 * PERCENTAGE_WIDTH
+
+ @output << sprintf("%#{TIME_WIDTH}.2f", child.total_time)
+ @output << sprintf("%#{TIME_WIDTH}.2f", child.self_time)
+ @output << sprintf("%#{TIME_WIDTH}.2f", child.wait_time)
+ @output << sprintf("%#{TIME_WIDTH}.2f", child.children_time)
+
+ call_called = "#{child.called}/#{child.target.called}"
+ @output << sprintf("%#{CALL_WIDTH}s", call_called)
+ @output << sprintf(" %s", child.target.full_name)
+ @output << "\n"
+ end
+ end
+ end
+end
+
diff --git a/lib/ruby-prof/method_info.rb b/lib/ruby-prof/method_info.rb
new file mode 100644
index 0000000..9f5ec2e
--- /dev/null
+++ b/lib/ruby-prof/method_info.rb
@@ -0,0 +1,111 @@
+module RubyProf
+ class MethodInfo
+ include Comparable
+
+ def <=>(other)
+ if self.total_time < other.total_time
+ -1
+ elsif self.total_time > other.total_time
+ 1
+ elsif self.min_depth < other.min_depth
+ 1
+ elsif self.min_depth > other.min_depth
+ -1
+ else
+ -1 * (self.full_name <=> other.full_name)
+ end
+ end
+
+ def called
+ @called ||= begin
+ call_infos.inject(0) do |sum, call_info|
+ sum += call_info.called
+ end
+ end
+ end
+
+ def total_time
+ @total_time ||= begin
+ call_infos.inject(0) do |sum, call_info|
+ sum += call_info.total_time
+ end
+ end
+ end
+
+ def self_time
+ @self_time ||= begin
+ call_infos.inject(0) do |sum, call_info|
+ sum += call_info.self_time
+ end
+ end
+ end
+
+ def wait_time
+ @wait_time ||= begin
+ call_infos.inject(0) do |sum, call_info|
+ sum += call_info.wait_time
+ end
+ end
+ end
+
+ def children_time
+ @children_time ||= begin
+ call_infos.inject(0) do |sum, call_info|
+ sum += call_info.children_time
+ end
+ end
+ end
+
+ def min_depth
+ call_infos.map do |call_info|
+ call_info.depth
+ end.min
+ end
+
+ def root?
+ @root ||= begin
+ call_infos.find do |call_info|
+ not call_info.root?
+ end.nil?
+ end
+ end
+
+ def children
+ @children ||= begin
+ call_infos.map do |call_info|
+ call_info.children
+ end.flatten
+ end
+ end
+
+ def aggregate_parents
+ # Group call info's based on their parents
+ groups = self.call_infos.inject(Hash.new) do |hash, call_info|
+ key = call_info.parent ? call_info.parent.target : self
+ (hash[key] ||= []) << call_info
+ hash
+ end
+
+ groups.map do |key, value|
+ AggregateCallInfo.new(value)
+ end
+ end
+
+ def aggregate_children
+ # Group call info's based on their targets
+ groups = self.children.inject(Hash.new) do |hash, call_info|
+ key = call_info.target
+ (hash[key] ||= []) << call_info
+ hash
+ end
+
+ groups.map do |key, value|
+ AggregateCallInfo.new(value)
+ end
+ end
+
+ def to_s
+ full_name
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/ruby-prof/task.rb b/lib/ruby-prof/task.rb
new file mode 100755
index 0000000..a29fe9c
--- /dev/null
+++ b/lib/ruby-prof/task.rb
@@ -0,0 +1,146 @@
+#!/usr/bin/env ruby
+
+require 'rake'
+require 'rake/testtask'
+require 'fileutils'
+
+module RubyProf
+
+ # Define a task library for profiling unit tests with ruby-prof.
+ #
+ # All of the options provided by
+ # the Rake:TestTask are supported except the loader
+ # which is set to ruby-prof. For detailed information
+ # please refer to the Rake:TestTask documentation.
+ #
+ # ruby-prof specific options include:
+ #
+ # output_dir - For each file specified an output
+ # file with profile information will be
+ # written to the output directory.
+ # By default, the output directory is
+ # called "profile" and is created underneath
+ # the current working directory.
+ #
+ # printer - Specifies the output printer. Valid values include
+ # :flat, :graph, :graph_html and :call_tree.
+ #
+ # min_percent - Methods that take less than the specified percent
+ # will not be written out.
+ #
+ # Example:
+ #
+ # require 'ruby-prof/task'
+ #
+ # RubyProf::ProfileTask.new do |t|
+ # t.test_files = FileList['test/test*.rb']
+ # t.output_dir = "c:/temp"
+ # t.printer = :graph
+ # t.min_percent = 10
+ # end
+ #
+ # If rake is invoked with a "TEST=filename" command line option,
+ # then the list of test files will be overridden to include only the
+ # filename specified on the command line. This provides an easy way
+ # to run just one test.
+ #
+ # If rake is invoked with a "TESTOPTS=options" command line option,
+ # then the given options are passed to the test process after a
+ # '--'. This allows Test::Unit options to be passed to the test
+ # suite.
+ #
+ # Examples:
+ #
+ # rake profile # run tests normally
+ # rake profile TEST=just_one_file.rb # run just one test file.
+ # rake profile TESTOPTS="-v" # run in verbose mode
+ # rake profile TESTOPTS="--runner=fox" # use the fox test runner
+
+ class ProfileTask < Rake::TestTask
+ attr_accessor :output_dir
+ attr_accessor :min_percent
+ attr_accessor :printer
+
+ def initialize(name = :profile)
+ super(name)
+ end
+
+ # Create the tasks defined by this task lib.
+ def define
+ lib_path = @libs.join(File::PATH_SEPARATOR)
+ desc "Profile" + (@name==:profile ? "" : " for #{@name}")
+
+ task @name do
+ create_output_directory
+
+ @ruby_opts.unshift( "-I#{lib_path}" )
+ @ruby_opts.unshift( "-w" ) if @warning
+ @ruby_opts.push("-S ruby-prof")
+ @ruby_opts.push("--printer #{@printer}")
+ @ruby_opts.push("--min_percent #{@min_percent}")
+
+ file_list.each do |file_path|
+ run_script(file_path)
+ end
+ end
+ self
+ end
+
+ # Run script
+ def run_script(script_path)
+ run_code = ''
+ RakeFileUtils.verbose(@verbose) do
+ file_name = File.basename(script_path, File.extname(script_path))
+ case @printer
+ when :flat, :graph, :call_tree
+ file_name += ".txt"
+ when :graph_html
+ file_name += ".html"
+ else
+ file_name += ".txt"
+ end
+
+ output_file_path = File.join(output_directory, file_name)
+
+ command_line = @ruby_opts.join(" ") +
+ " --file=" + output_file_path +
+ " " + script_path
+
+ puts "ruby " + command_line
+ # We have to catch the exeption to continue on. However,
+ # the error message will have been output to STDERR
+ # already by the time we get here so we don't have to
+ # do that again
+ begin
+ ruby command_line
+ rescue => e
+ STDOUT << e << "\n"
+ STDOUT.flush
+ end
+ puts ""
+ puts ""
+ end
+ end
+
+ def output_directory
+ File.expand_path(@output_dir)
+ end
+
+ def create_output_directory
+ if not File.exist?(output_directory)
+ Dir.mkdir(output_directory)
+ end
+ end
+
+ def clean_output_directory
+ if File.exist?(output_directory)
+ files = Dir.glob(output_directory + '/*')
+ FileUtils.rm(files)
+ end
+ end
+
+ def option_list # :nodoc:
+ ENV['OPTIONS'] || @options.join(" ") || ""
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/ruby-prof/test.rb b/lib/ruby-prof/test.rb
new file mode 100644
index 0000000..a0691cc
--- /dev/null
+++ b/lib/ruby-prof/test.rb
@@ -0,0 +1,148 @@
+# Now load ruby-prof and away we go
+require 'fileutils'
+require 'ruby-prof'
+require 'benchmark'
+
+module RubyProf
+ module Test
+ PROFILE_OPTIONS = {
+ :measure_modes => [RubyProf::PROCESS_TIME],
+ :count => 10,
+ :printers => [RubyProf::FlatPrinter, RubyProf::GraphHtmlPrinter],
+ :min_percent => 0.05,
+ :output_dir => Dir.pwd }
+
+ def output_dir
+ PROFILE_OPTIONS[:output_dir]
+ end
+
+ def run(result)
+ return if @method_name.to_s == "default_test"
+
+ yield(self.class::STARTED, name)
+ @_result = result
+ run_warmup
+ PROFILE_OPTIONS[:measure_modes].each do |measure_mode|
+ data = run_profile(measure_mode)
+ report_profile(data, measure_mode)
+ result.add_run
+ end
+ yield(self.class::FINISHED, name)
+ end
+
+ def run_test
+ begin
+ setup
+ yield
+ rescue ::Test::Unit::AssertionFailedError => e
+ add_failure(e.message, e.backtrace)
+ rescue StandardError, ScriptError
+ add_error($!)
+ ensure
+ begin
+ teardown
+ rescue ::Test::Unit::AssertionFailedError => e
+ add_failure(e.message, e.backtrace)
+ rescue StandardError, ScriptError
+ add_error($!)
+ end
+ end
+ end
+
+ def run_warmup
+ print "\n#{self.class.name}##{method_name}"
+
+ run_test do
+ bench = Benchmark.realtime do
+ __send__(@method_name)
+ end
+ puts " (%.2fs warmup)" % bench
+ end
+ end
+
+ def run_profile(measure_mode)
+ RubyProf.measure_mode = measure_mode
+
+ print ' '
+ PROFILE_OPTIONS[:count].times do |i|
+ run_test do
+ begin
+ print '.'
+ $stdout.flush
+ GC.disable
+
+ RubyProf.resume do
+ __send__(@method_name)
+ end
+ ensure
+ GC.enable
+ end
+ end
+ end
+
+ data = RubyProf.stop
+ bench = data.threads.values.inject(0) do |total, method_infos|
+ top = method_infos.sort.last
+ total += top.total_time
+ total
+ end
+
+ puts "\n #{measure_mode_name(measure_mode)}: #{format_profile_total(bench, measure_mode)}\n"
+
+ data
+ end
+
+ def format_profile_total(total, measure_mode)
+ case measure_mode
+ when RubyProf::PROCESS_TIME, RubyProf::WALL_TIME
+ "%.2f seconds" % total
+ when RubyProf::MEMORY
+ "%.2f kilobytes" % total
+ when RubyProf::ALLOCATIONS
+ "%d allocations" % total
+ else
+ "%.2f #{measure_mode}"
+ end
+ end
+
+ def report_profile(data, measure_mode)
+ PROFILE_OPTIONS[:printers].each do |printer_klass|
+ printer = printer_klass.new(data)
+
+ # Makes sure the output directory exits
+ FileUtils.mkdir_p(output_dir)
+
+ # Open the file
+ file_name = report_filename(printer, measure_mode)
+
+ File.open(file_name, 'wb') do |file|
+ printer.print(file, PROFILE_OPTIONS)
+ end
+ end
+ end
+
+ # The report filename is test_name + measure_mode + report_type
+ def report_filename(printer, measure_mode)
+ suffix =
+ case printer
+ when RubyProf::FlatPrinter; 'flat.txt'
+ when RubyProf::GraphPrinter; 'graph.txt'
+ when RubyProf::GraphHtmlPrinter; 'graph.html'
+ when RubyProf::CallTreePrinter; 'tree.txt'
+ else printer.to_s.downcase
+ end
+
+ "#{output_dir}/#{method_name}_#{measure_mode_name(measure_mode)}_#{suffix}"
+ end
+
+ def measure_mode_name(measure_mode)
+ case measure_mode
+ when RubyProf::PROCESS_TIME; 'process_time'
+ when RubyProf::WALL_TIME; 'wall_time'
+ when RubyProf::MEMORY; 'memory'
+ when RubyProf::ALLOCATIONS; 'allocations'
+ else "measure#{measure_mode}"
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/unprof.rb b/lib/unprof.rb
new file mode 100644
index 0000000..0838096
--- /dev/null
+++ b/lib/unprof.rb
@@ -0,0 +1,8 @@
+require "ruby-prof"
+
+at_exit {
+ result = RubyProf.stop
+ printer = RubyProf::FlatPrinter.new(result)
+ printer.print(STDOUT)
+}
+RubyProf.start
diff --git a/rails/environment/profile.rb b/rails/environment/profile.rb
new file mode 100644
index 0000000..328100b
--- /dev/null
+++ b/rails/environment/profile.rb
@@ -0,0 +1,24 @@
+# Settings specified here will take precedence over those in config/environment.rb
+# The profile environment should match the same settings
+# as the production environment to give a reasonalbe
+# approximation of performance. However, it should
+# definitely not use the production databse!
+
+
+# Cache classes - otherwise your code
+# will run approximately 5 times slower and the
+# profiling results will be overwhelmed by Rails
+# dependency loading mechanism
+config.cache_classes = true
+
+# Don't check template timestamps - once again this
+# is to avoid IO times overwhelming profile results
+config.action_view.cache_template_loading = true
+
+# This is debatable, but turn off action controller
+# caching to see how long it really takes to run
+# queries and render templates
+config.action_controller.perform_caching = false
+
+# Turn off most logging
+config.log_level = :info
\ No newline at end of file
diff --git a/rails/example/example_test.rb b/rails/example/example_test.rb
new file mode 100644
index 0000000..131f987
--- /dev/null
+++ b/rails/example/example_test.rb
@@ -0,0 +1,9 @@
+require File.dirname(__FILE__) + '../profile_test_helper'
+
+class ExampleTest < Test::Unit::TestCase
+ include RubyProf::Test
+
+ def test_stuff
+ puts "Test method"
+ end
+end
diff --git a/rails/profile_test_helper.rb b/rails/profile_test_helper.rb
new file mode 100644
index 0000000..4692bd9
--- /dev/null
+++ b/rails/profile_test_helper.rb
@@ -0,0 +1,21 @@
+# Load profile environment
+env = ENV["RAILS_ENV"] = "profile"
+require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
+
+# Load Rails testing infrastructure
+require 'test_help'
+
+# Now we can load test_helper since we've already loaded the
+# profile RAILS environment.
+require File.expand_path(File.join(RAILS_ROOT, 'test', 'test_helper'))
+
+# Reset the current environment back to profile
+# since test_helper reset it to test
+ENV["RAILS_ENV"] = env
+
+# Now load ruby-prof and away we go
+require 'ruby-prof'
+
+# Setup output directory to Rails tmp directory
+RubyProf::Test::PROFILE_OPTIONS[:output_dir] =
+ File.expand_path(File.join(RAILS_ROOT, 'tmp', 'profile'))
diff --git a/test/aggregate_test.rb b/test/aggregate_test.rb
new file mode 100755
index 0000000..87769e6
--- /dev/null
+++ b/test/aggregate_test.rb
@@ -0,0 +1,121 @@
+#!/usr/bin/env ruby
+
+require 'test/unit'
+require 'ruby-prof'
+
+# Test data
+# A B C
+# | | |
+# Z A A
+# | |
+# Z Z
+
+class AggClass
+ def z
+ sleep 1
+ end
+
+ def a
+ z
+ end
+
+ def b
+ a
+ end
+
+ def c
+ a
+ end
+end
+
+class AggregateTest < Test::Unit::TestCase
+ def setup
+ # Need to use wall time for this test due to the sleep calls
+ RubyProf::measure_mode = RubyProf::WALL_TIME
+ end
+
+ def test_call_infos
+ c1 = AggClass.new
+ result = RubyProf.profile do
+ c1.a
+ c1.b
+ c1.c
+ end
+
+ methods = result.threads.values.first.sort.reverse
+ method = methods.find {|method| method.full_name == 'AggClass#z'}
+
+ # Check AggClass#z
+ assert_equal('AggClass#z', method.full_name)
+ assert_equal(3, method.called)
+ assert_in_delta(3, method.total_time, 0.01)
+ assert_in_delta(0, method.wait_time, 0.01)
+ assert_in_delta(0, method.self_time, 0.01)
+ assert_in_delta(3, method.children_time, 0.01)
+ assert_equal(3, method.call_infos.length)
+
+ call_info = method.call_infos[0]
+ assert_equal('AggregateTest#test_call_infos->AggClass#a->AggClass#z', call_info.call_sequence)
+ assert_equal(1, call_info.children.length)
+
+ call_info = method.call_infos[1]
+ assert_equal('AggregateTest#test_call_infos->AggClass#b->AggClass#a->AggClass#z', call_info.call_sequence)
+ assert_equal(1, call_info.children.length)
+
+ call_info = method.call_infos[2]
+ assert_equal('AggregateTest#test_call_infos->AggClass#c->AggClass#a->AggClass#z', call_info.call_sequence)
+ assert_equal(1, call_info.children.length)
+ end
+
+ def test_aggregates_parents
+ c1 = AggClass.new
+ result = RubyProf.profile do
+ c1.a
+ c1.b
+ c1.c
+ end
+
+ methods = result.threads.values.first.sort.reverse
+ method = methods.find {|method| method.full_name == 'AggClass#z'}
+
+ # Check AggClass#z
+ assert_equal('AggClass#z', method.full_name)
+
+ call_infos = method.aggregate_parents
+ assert_equal(1, call_infos.length)
+
+ call_info = call_infos.first
+ assert_equal('AggClass#a', call_info.parent.target.full_name)
+ assert_in_delta(3, call_info.total_time, 0.01)
+ assert_in_delta(0, call_info.wait_time, 0.01)
+ assert_in_delta(0, call_info.self_time, 0.01)
+ assert_in_delta(3, call_info.children_time, 0.01)
+ assert_equal(3, call_info.called)
+ end
+
+ def test_aggregates_children
+ c1 = AggClass.new
+ result = RubyProf.profile do
+ c1.a
+ c1.b
+ c1.c
+ end
+
+ methods = result.threads.values.first.sort.reverse
+ method = methods.find {|method| method.full_name == 'AggClass#a'}
+
+ # Check AggClass#a
+ assert_equal('AggClass#a', method.full_name)
+
+ call_infos = method.aggregate_children
+ assert_equal(1, call_infos.length)
+
+ call_info = call_infos.first
+ assert_equal('AggClass#z', call_info.target.full_name)
+ assert_in_delta(3, call_info.total_time, 0.01)
+ assert_in_delta(0, call_info.wait_time, 0.01)
+ assert_in_delta(0, call_info.self_time, 0.01)
+ assert_in_delta(3, call_info.children_time, 0.01)
+ assert_equal(3, call_info.called)
+ end
+end
\ No newline at end of file
diff --git a/test/basic_test.rb b/test/basic_test.rb
new file mode 100755
index 0000000..e3d19cf
--- /dev/null
+++ b/test/basic_test.rb
@@ -0,0 +1,283 @@
+#!/usr/bin/env ruby
+
+require 'test/unit'
+require 'ruby-prof'
+
+class C1
+ def C1.hello
+ sleep(0.1)
+ end
+
+ def hello
+ sleep(0.2)
+ end
+end
+
+module M1
+ def hello
+ sleep(0.3)
+ end
+end
+
+class C2
+ include M1
+ extend M1
+end
+
+class C3
+ def hello
+ sleep(0.4)
+ end
+end
+
+module M4
+ def hello
+ sleep(0.5)
+ end
+end
+
+module M5
+ include M4
+ def goodbye
+ hello
+ end
+end
+
+class C6
+ include M5
+ def test
+ goodbye
+ end
+end
+
+class BasicTest < Test::Unit::TestCase
+ def setup
+ # Need to use wall time for this test due to the sleep calls
+ RubyProf::measure_mode = RubyProf::WALL_TIME
+ end
+
+ def test_running
+ assert(!RubyProf.running?)
+ RubyProf.start
+ assert(RubyProf.running?)
+ RubyProf.stop
+ assert(!RubyProf.running?)
+ end
+
+ def test_double_profile
+ RubyProf.start
+ assert_raise(RuntimeError) do
+ RubyProf.start
+ end
+
+ assert_raise(RuntimeError) do
+ RubyProf.profile do
+ puts 1
+ end
+ end
+ RubyProf.stop
+ end
+
+ def test_no_block
+ assert_raise(ArgumentError) do
+ RubyProf.profile
+ end
+ end
+
+ def test_class_methods
+ result = RubyProf.profile do
+ C1.hello
+ end
+
+ # Length should be 3:
+ # BasicTest#test_class_methods
+ # <Class::C1>#hello
+ # Kernel#sleep
+
+ methods = result.threads.values.first.sort.reverse
+ assert_equal(3, methods.length)
+
+ # Check the names
+ assert_equal('BasicTest#test_class_methods', methods[0].full_name)
+ assert_equal('<Class::C1>#hello', methods[1].full_name)
+ assert_equal('Kernel#sleep', methods[2].full_name)
+
+ # Check times
+ assert_in_delta(0.1, methods[0].total_time, 0.01)
+ assert_in_delta(0, methods[0].wait_time, 0.01)
+ assert_in_delta(0, methods[0].self_time, 0.01)
+
+ assert_in_delta(0.1, methods[1].total_time, 0.01)
+ assert_in_delta(0, methods[1].wait_time, 0.01)
+ assert_in_delta(0, methods[1].self_time, 0.01)
+
+ assert_in_delta(0.1, methods[2].total_time, 0.01)
+ assert_in_delta(0, methods[2].wait_time, 0.01)
+ assert_in_delta(0.1, methods[2].self_time, 0.01)
+ end
+
+ def test_instance_methods
+ result = RubyProf.profile do
+ C1.new.hello
+ end
+
+ # Methods called
+ # BasicTest#test_instance_methods
+ # Class.new
+ # Class:Object#allocate
+ # for Object#initialize
+ # C1#hello
+ # Kernel#sleep
+
+ methods = result.threads.values.first.sort.reverse
+ assert_equal(6, methods.length)
+
+ assert_equal('BasicTest#test_instance_methods', methods[0].full_name)
+ assert_equal('C1#hello', methods[1].full_name)
+ assert_equal('Kernel#sleep', methods[2].full_name)
+ assert_equal('Class#new', methods[3].full_name)
+ assert_equal('<Class::Object>#allocate', methods[4].full_name)
+ assert_equal('Object#initialize', methods[5].full_name)
+
+ # Check times
+ assert_in_delta(0.2, methods[0].total_time, 0.01)
+ assert_in_delta(0, methods[0].wait_time, 0.01)
+ assert_in_delta(0, methods[0].self_time, 0.01)
+
+ assert_in_delta(0.2, methods[1].total_time, 0.01)
+ assert_in_delta(0, methods[1].wait_time, 0.01)
+ assert_in_delta(0, methods[1].self_time, 0.01)
+
+ assert_in_delta(0.2, methods[2].total_time, 0.01)
+ assert_in_delta(0, methods[2].wait_time, 0.01)
+ assert_in_delta(0.2, methods[2].self_time, 0.01)
+
+ assert_in_delta(0, methods[3].total_time, 0.01)
+ assert_in_delta(0, methods[3].wait_time, 0.01)
+ assert_in_delta(0, methods[3].self_time, 0.01)
+
+ assert_in_delta(0, methods[4].total_time, 0.01)
+ assert_in_delta(0, methods[4].wait_time, 0.01)
+ assert_in_delta(0, methods[4].self_time, 0.01)
+
+ assert_in_delta(0, methods[5].total_time, 0.01)
+ assert_in_delta(0, methods[5].wait_time, 0.01)
+ assert_in_delta(0, methods[5].self_time, 0.01)
+ end
+
+ def test_module_methods
+ result = RubyProf.profile do
+ C2.hello
+ end
+
+ # Methods:
+ # BasicTest#test_module_methods
+ # M1#hello
+ # Kernel#sleep
+
+ methods = result.threads.values.first.sort.reverse
+ assert_equal(3, methods.length)
+
+ assert_equal('BasicTest#test_module_methods', methods[0].full_name)
+ assert_equal('M1#hello', methods[1].full_name)
+ assert_equal('Kernel#sleep', methods[2].full_name)
+
+ # Check times
+ assert_in_delta(0.3, methods[0].total_time, 0.01)
+ assert_in_delta(0, methods[0].wait_time, 0.01)
+ assert_in_delta(0, methods[0].self_time, 0.01)
+
+ assert_in_delta(0.3, methods[1].total_time, 0.01)
+ assert_in_delta(0, methods[1].wait_time, 0.01)
+ assert_in_delta(0, methods[1].self_time, 0.01)
+
+ assert_in_delta(0.3, methods[2].total_time, 0.01)
+ assert_in_delta(0, methods[2].wait_time, 0.01)
+ assert_in_delta(0.3, methods[2].self_time, 0.01)
+ end
+
+ def test_module_instance_methods
+ result = RubyProf.profile do
+ C2.new.hello
+ end
+
+ # Methods:
+ # BasicTest#test_module_instance_methods
+ # Class#new
+ # <Class::Object>#allocate
+ # Object#initialize
+ # M1#hello
+ # Kernel#sleep
+
+ methods = result.threads.values.first.sort.reverse
+ assert_equal(6, methods.length)
+
+ assert_equal('BasicTest#test_module_instance_methods', methods[0].full_name)
+ assert_equal('M1#hello', methods[1].full_name)
+ assert_equal('Kernel#sleep', methods[2].full_name)
+ assert_equal('Class#new', methods[3].full_name)
+ assert_equal('<Class::Object>#allocate', methods[4].full_name)
+ assert_equal('Object#initialize', methods[5].full_name)
+
+ # Check times
+ assert_in_delta(0.3, methods[0].total_time, 0.01)
+ assert_in_delta(0, methods[0].wait_time, 0.01)
+ assert_in_delta(0, methods[0].self_time, 0.01)
+
+ assert_in_delta(0.3, methods[1].total_time, 0.01)
+ assert_in_delta(0, methods[1].wait_time, 0.01)
+ assert_in_delta(0, methods[1].self_time, 0.01)
+
+ assert_in_delta(0.3, methods[2].total_time, 0.01)
+ assert_in_delta(0, methods[2].wait_time, 0.01)
+ assert_in_delta(0.3, methods[2].self_time, 0.01)
+
+ assert_in_delta(0, methods[3].total_time, 0.01)
+ assert_in_delta(0, methods[3].wait_time, 0.01)
+ assert_in_delta(0, methods[3].self_time, 0.01)
+
+ assert_in_delta(0, methods[4].total_time, 0.01)
+ assert_in_delta(0, methods[4].wait_time, 0.01)
+ assert_in_delta(0, methods[4].self_time, 0.01)
+
+ assert_in_delta(0, methods[5].total_time, 0.01)
+ assert_in_delta(0, methods[5].wait_time, 0.01)
+ assert_in_delta(0, methods[5].self_time, 0.01)
+ end
+
+ def test_singleton
+ c3 = C3.new
+
+ class << c3
+ def hello
+ end
+ end
+
+ result = RubyProf.profile do
+ c3.hello
+ end
+
+ methods = result.threads.values.first.sort.reverse
+ assert_equal(2, methods.length)
+
+ assert_equal('BasicTest#test_singleton', methods[0].full_name)
+ assert_equal('<Object::C3>#hello', methods[1].full_name)
+
+ assert_in_delta(0, methods[0].total_time, 0.01)
+ assert_in_delta(0, methods[0].wait_time, 0.01)
+ assert_in_delta(0, methods[0].self_time, 0.01)
+
+ assert_in_delta(0, methods[1].total_time, 0.01)
+ assert_in_delta(0, methods[1].wait_time, 0.01)
+ assert_in_delta(0, methods[1].self_time, 0.01)
+ end
+
+ def test_traceback
+ RubyProf.start
+ assert_raise(NoMethodError) do
+ RubyProf.xxx
+ end
+
+ RubyProf.stop
+ end
+end
\ No newline at end of file
diff --git a/test/duplicate_names_test.rb b/test/duplicate_names_test.rb
new file mode 100755
index 0000000..172ff3f
--- /dev/null
+++ b/test/duplicate_names_test.rb
@@ -0,0 +1,32 @@
+#!/usr/bin/env ruby
+
+require 'test/unit'
+require 'ruby-prof'
+
+class DuplicateNames < Test::Unit::TestCase
+ def test_names
+ result = RubyProf::profile do
+ str = %{module Foo; class Bar; def foo; end end end}
+
+ eval str
+ Foo::Bar.new.foo
+ DuplicateNames.class_eval {remove_const :Foo}
+
+ eval str
+ Foo::Bar.new.foo
+ DuplicateNames.class_eval {remove_const :Foo}
+
+ eval str
+ Foo::Bar.new.foo
+ end
+
+ # There should be 3 foo methods
+ methods = result.threads.values.first.sort.reverse
+
+ methods = methods.select do |method|
+ method.full_name == 'DuplicateNames::Foo::Bar#foo'
+ end
+
+ assert_equal(3, methods.length)
+ end
+end
diff --git a/test/exceptions_test.rb b/test/exceptions_test.rb
new file mode 100755
index 0000000..5116c8b
--- /dev/null
+++ b/test/exceptions_test.rb
@@ -0,0 +1,15 @@
+#!/usr/bin/env ruby
+require 'test/unit'
+require 'ruby-prof'
+
+class ExceptionsTest < Test::Unit::TestCase
+ def test_profile
+ result = begin
+ RubyProf.profile do
+ raise(RuntimeError, 'Test error')
+ end
+ rescue => e
+ end
+ assert_not_nil(result)
+ end
+end
diff --git a/test/exclude_threads_test.rb b/test/exclude_threads_test.rb
new file mode 100755
index 0000000..4c2fdb3
--- /dev/null
+++ b/test/exclude_threads_test.rb
@@ -0,0 +1,54 @@
+#!/usr/bin/env ruby
+
+require 'test/unit'
+require 'ruby-prof'
+
+
+# -- Tests ----
+class ExcludeThreadsTest < Test::Unit::TestCase
+ def test_exclude_threads
+
+ def thread1_proc
+ sleep(0.5)
+ sleep(2)
+ end
+
+ def thread2_proc
+ sleep(0.5)
+ sleep(2)
+ end
+
+ thread1 = Thread.new do
+ thread1_proc
+ end
+
+ thread2 = Thread.new do
+ thread2_proc
+ end
+
+ RubyProf::exclude_threads = [ thread2 ]
+
+ RubyProf.start
+
+ thread1.join
+ thread2.join
+
+ result = RubyProf.stop
+
+ RubyProf::exclude_threads = nil
+
+ assert_equal(2, result.threads.length)
+
+ output = Array.new
+ result.threads.each do | thread_id, methods |
+ methods.each do | m |
+ if m.full_name.index("ExcludeThreadsTest#thread") == 0
+ output.push(m.full_name)
+ end
+ end
+ end
+
+ assert_equal(1, output.length)
+ assert_equal("ExcludeThreadsTest#thread1_proc", output[0])
+ end
+end
\ No newline at end of file
diff --git a/test/line_number_test.rb b/test/line_number_test.rb
new file mode 100755
index 0000000..8742f44
--- /dev/null
+++ b/test/line_number_test.rb
@@ -0,0 +1,73 @@
+#!/usr/bin/env ruby
+require 'test/unit'
+require 'ruby-prof'
+require 'prime'
+
+class LineNumbers
+ def method1
+ a = 3
+ end
+
+ def method2
+ a = 3
+ method1
+ end
+
+ def method3
+ sleep(1)
+ end
+end
+
+# -- Tests ----
+class LineNumbersTest < Test::Unit::TestCase
+ def test_function_line_no
+ numbers = LineNumbers.new
+
+ result = RubyProf.profile do
+ numbers.method2
+ end
+
+ methods = result.threads.values.first.sort.reverse
+ assert_equal(3, methods.length)
+
+ method = methods[0]
+ assert_equal('LineNumbersTest#test_function_line_no', method.full_name)
+ assert_equal(27, method.line)
+
+ method = methods[1]
+ assert_equal('LineNumbers#method2', method.full_name)
+ assert_equal(11, method.line)
+
+ method = methods[2]
+ assert_equal('LineNumbers#method1', method.full_name)
+ assert_equal(7, method.line)
+ end
+
+ def test_c_function
+ numbers = LineNumbers.new
+
+ result = RubyProf.profile do
+ numbers.method3
+ end
+
+ methods = result.threads.values.first.sort_by {|method| method.full_name}
+ assert_equal(3, methods.length)
+
+ # Methods:
+ # LineNumbers#method3
+ # LineNumbersTest#test_c_function
+ # Kernel#sleep
+
+ method = methods[0]
+ assert_equal('Kernel#sleep', method.full_name)
+ assert_equal(0, method.line)
+
+ method = methods[1]
+ assert_equal('LineNumbers#method3', method.full_name)
+ assert_equal(16, method.line)
+
+ method = methods[2]
+ assert_equal('LineNumbersTest#test_c_function', method.full_name)
+ assert_equal(50, method.line)
+ end
+end
\ No newline at end of file
diff --git a/test/measurement_test.rb b/test/measurement_test.rb
new file mode 100755
index 0000000..f1df249
--- /dev/null
+++ b/test/measurement_test.rb
@@ -0,0 +1,121 @@
+#!/usr/bin/env ruby
+require 'test/unit'
+require 'ruby-prof'
+
+class MeasurementTest < Test::Unit::TestCase
+ def setup
+ GC.enable_stats if GC.respond_to?(:enable_stats)
+ end
+
+ def teardown
+ GC.disable_stats if GC.respond_to?(:disable_stats)
+ end
+
+ def test_process_time_mode
+ RubyProf::measure_mode = RubyProf::PROCESS_TIME
+ assert_equal(RubyProf::PROCESS_TIME, RubyProf::measure_mode)
+ end
+
+ def test_process_time
+ t = RubyProf.measure_process_time
+ assert_kind_of(Float, t)
+
+ u = RubyProf.measure_process_time
+ assert(u >= t, [t, u].inspect)
+ end
+
+ def test_wall_time_mode
+ RubyProf::measure_mode = RubyProf::WALL_TIME
+ assert_equal(RubyProf::WALL_TIME, RubyProf::measure_mode)
+ end
+
+ def test_wall_time
+ t = RubyProf.measure_wall_time
+ assert_kind_of Float, t
+
+ u = RubyProf.measure_wall_time
+ assert u >= t, [t, u].inspect
+ end
+
+ if RubyProf::CPU_TIME
+ def test_cpu_time_mode
+ RubyProf::measure_mode = RubyProf::CPU_TIME
+ assert_equal(RubyProf::CPU_TIME, RubyProf::measure_mode)
+ end
+
+ def test_cpu_time
+ RubyProf.cpu_frequency = 2.33e9
+
+ t = RubyProf.measure_cpu_time
+ assert_kind_of Float, t
+
+ u = RubyProf.measure_cpu_time
+ assert u > t, [t, u].inspect
+ end
+ end
+
+ if RubyProf::ALLOCATIONS
+ def test_allocations_mode
+ RubyProf::measure_mode = RubyProf::ALLOCATIONS
+ assert_equal(RubyProf::ALLOCATIONS, RubyProf::measure_mode)
+ end
+
+ def test_allocations
+ t = RubyProf.measure_allocations
+ assert_kind_of Integer, t
+
+ u = RubyProf.measure_allocations
+ assert u > t, [t, u].inspect
+ end
+ end
+
+ if RubyProf::MEMORY
+ def test_memory_mode
+ RubyProf::measure_mode = RubyProf::MEMORY
+ assert_equal(RubyProf::MEMORY, RubyProf::measure_mode)
+ end
+
+ def test_memory
+ t = RubyProf.measure_memory
+ assert_kind_of Integer, t
+
+ u = RubyProf.measure_memory
+ assert(u >= t, [t, u].inspect)
+
+ result = RubyProf.profile {Array.new}
+ total = result.threads.values.first.methods.inject(0) { |sum, m| sum + m.total_time }
+
+ assert(total > 0, 'Should measure more than zero kilobytes of memory usage')
+ assert_not_equal(0, total % 1, 'Should not truncate fractional kilobyte measurements')
+ end
+ end
+
+ if RubyProf::GC_RUNS
+ def test_gc_runs_mode
+ RubyProf::measure_mode = RubyProf::GC_RUNS
+ assert_equal(RubyProf::GC_RUNS, RubyProf::measure_mode)
+ end
+
+ def test_gc_runs
+ t = RubyProf.measure_gc_runs
+ assert_kind_of Integer, t
+
+ GC.start
+
+ u = RubyProf.measure_gc_runs
+ assert u > t, [t, u].inspect
+ end
+ end
+
+ if RubyProf::GC_TIME
+ def test_gc_time
+ t = RubyProf.measure_gc_time
+ assert_kind_of Integer, t
+
+ GC.start
+
+ u = RubyProf.measure_gc_time
+ assert u > t, [t, u].inspect
+ end
+ end
+end
\ No newline at end of file
diff --git a/test/module_test.rb b/test/module_test.rb
new file mode 100755
index 0000000..ff94bf9
--- /dev/null
+++ b/test/module_test.rb
@@ -0,0 +1,54 @@
+#!/usr/bin/env ruby
+require 'test/unit'
+require 'ruby-prof'
+
+# Need to use wall time for this test due to the sleep calls
+RubyProf::measure_mode = RubyProf::WALL_TIME
+
+module Foo
+ def Foo::hello
+ sleep(0.5)
+ end
+end
+
+module Bar
+ def Bar::hello
+ sleep(0.5)
+ Foo::hello
+ end
+
+ def hello
+ sleep(0.5)
+ Bar::hello
+ end
+end
+
+include Bar
+
+class ModuleTest < Test::Unit::TestCase
+ def test_nested_modules
+ result = RubyProf.profile do
+ hello
+ end
+
+ methods = result.threads.values.first.sort.reverse
+
+ # Length should be 5
+ assert_equal(5, methods.length)
+
+ method = methods[0]
+ assert_equal('ModuleTest#test_nested_modules', method.full_name)
+
+ method = methods[1]
+ assert_equal('Bar#hello', method.full_name)
+
+ method = methods[2]
+ assert_equal('Kernel#sleep', method.full_name)
+
+ method = methods[3]
+ assert_equal('<Module::Bar>#hello', method.full_name)
+
+ method = methods[4]
+ assert_equal('<Module::Foo>#hello', method.full_name)
+ end
+end
diff --git a/test/no_method_class_test.rb b/test/no_method_class_test.rb
new file mode 100755
index 0000000..9273dd6
--- /dev/null
+++ b/test/no_method_class_test.rb
@@ -0,0 +1,13 @@
+#!/usr/bin/env ruby
+require 'ruby-prof'
+
+# Make sure this works with no class or method
+result = RubyProf.profile do
+ sleep 1
+end
+
+methods = result.threads.values.first
+global_method = methods.sort_by {|method| method.full_name}.first
+if global_method.full_name != 'Global#[No method]'
+ raise(RuntimeError, "Wrong method name. Expected: Global#[No method]. Actual: #{global_method.full_name}")
+end
\ No newline at end of file
diff --git a/test/prime.rb b/test/prime.rb
new file mode 100644
index 0000000..5ef16b6
--- /dev/null
+++ b/test/prime.rb
@@ -0,0 +1,58 @@
+# A silly little test program that finds prime numbers. It
+# is intentionally badly designed to show off the use
+# of ruby-prof.
+#
+# Source from http://people.cs.uchicago.edu/~bomb154/154/maclabs/profilers-lab/
+
+def make_random_array(length, maxnum)
+ result = Array.new(length)
+ result.each_index do |i|
+ result[i] = rand(maxnum)
+ end
+
+ result
+end
+
+def is_prime(x)
+ y = 2
+ y.upto(x-1) do |i|
+ return false if (x % i) == 0
+ end
+ true
+end
+
+def find_primes(arr)
+ result = arr.select do |value|
+ is_prime(value)
+ end
+ result
+end
+
+def find_largest(primes)
+ largest = primes.first
+
+ # Intentionally use upto for example purposes
+ # (upto is also called from is_prime)
+ 0.upto(primes.length-1) do |i|
+ sleep(0.02)
+ prime = primes[i]
+ if prime > largest
+ largest = prime
+ end
+ end
+ largest
+end
+
+def run_primes
+ length = 10
+ maxnum = 10000
+
+ # Create random numbers
+ random_array = make_random_array(length, maxnum)
+
+ # Find the primes
+ primes = find_primes(random_array)
+
+ # Find the largest primes
+ largest = find_largest(primes)
+end
\ No newline at end of file
diff --git a/test/prime_test.rb b/test/prime_test.rb
new file mode 100755
index 0000000..3fbc9db
--- /dev/null
+++ b/test/prime_test.rb
@@ -0,0 +1,13 @@
+#!/usr/bin/env ruby
+require 'test/unit'
+require 'ruby-prof'
+require 'prime'
+
+# -- Tests ----
+class PrimeTest< Test::Unit::TestCase
+ def test_consistency
+ result = RubyProf.profile do
+ run_primes
+ end
+ end
+end
\ No newline at end of file
diff --git a/test/printers_test.rb b/test/printers_test.rb
new file mode 100755
index 0000000..195e61d
--- /dev/null
+++ b/test/printers_test.rb
@@ -0,0 +1,71 @@
+#!/usr/bin/env ruby
+require 'test/unit'
+require 'ruby-prof'
+require 'prime'
+
+# -- Tests ----
+class PrintersTest < Test::Unit::TestCase
+ def setup
+ RubyProf::measure_mode = RubyProf::PROCESS_TIME
+ @result = RubyProf.profile do
+ run_primes
+ end
+ end
+
+ def test_printers
+ printer = RubyProf::FlatPrinter.new(@result)
+ printer.print(STDOUT)
+
+ printer = RubyProf::GraphHtmlPrinter.new(@result)
+ printer.print
+
+ printer = RubyProf::GraphPrinter.new(@result)
+ printer.print
+
+ printer = RubyProf::CallTreePrinter.new(@result)
+ printer.print(STDOUT)
+
+ # we should get here
+ assert(true)
+ end
+
+ def test_flat_string
+ output = ''
+
+ printer = RubyProf::FlatPrinter.new(@result)
+ assert_nothing_raised { printer.print(output) }
+
+ assert_match(/Thread ID: -?\d+/i, output)
+ assert_match(/Total: \d+\.\d+/i, output)
+ assert_match(/Object#run_primes/i, output)
+ end
+
+ def test_graph_html_string
+ output = ''
+ printer = RubyProf::GraphHtmlPrinter.new(@result)
+ assert_nothing_raised { printer.print(output) }
+
+ assert_match( /DTD HTML 4\.01/i, output )
+ assert_match( %r{<th>Total Time</th>}i, output )
+ assert_match( /Object#run_primes/i, output )
+ end
+
+ def test_graph_string
+ output = ''
+ printer = RubyProf::GraphPrinter.new(@result)
+ assert_nothing_raised { printer.print(output) }
+
+ assert_match( /Thread ID: -?\d+/i, output )
+ assert_match( /Total Time: \d+\.\d+/i, output )
+ assert_match( /Object#run_primes/i, output )
+ end
+
+ def test_call_tree_string
+ output = ''
+ printer = RubyProf::CallTreePrinter.new(@result)
+ assert_nothing_raised { printer.print(output) }
+
+ assert_match(/fn=Object::find_primes/i, output)
+ assert_match(/events: process_time/i, output)
+ end
+end
diff --git a/test/recursive_test.rb b/test/recursive_test.rb
new file mode 100755
index 0000000..56f1e0f
--- /dev/null
+++ b/test/recursive_test.rb
@@ -0,0 +1,254 @@
+#!/usr/bin/env ruby
+require 'test/unit'
+require 'ruby-prof'
+
+def simple(n)
+ sleep(1)
+ n -= 1
+ return if n == 0
+ simple(n)
+end
+
+def cycle(n)
+ sub_cycle(n)
+end
+
+def sub_cycle(n)
+ sleep(1)
+ n -= 1
+ return if n == 0
+ cycle(n)
+end
+
+
+# -- Tests ----
+class RecursiveTest < Test::Unit::TestCase
+ def setup
+ # Need to use wall time for this test due to the sleep calls
+ RubyProf::measure_mode = RubyProf::WALL_TIME
+ end
+
+ def test_simple
+ result = RubyProf.profile do
+ simple(2)
+ end
+
+ methods = result.threads.values.first.sort.reverse
+ assert_equal(6, methods.length)
+
+ method = methods[0]
+ assert_equal('RecursiveTest#test_simple', method.full_name)
+ assert_equal(1, method.called)
+ assert_in_delta(2, method.total_time, 0.01)
+ assert_in_delta(0, method.self_time, 0.01)
+ assert_in_delta(0, method.wait_time, 0.01)
+ assert_in_delta(2, method.children_time, 0.01)
+
+ assert_equal(1, method.call_infos.length)
+ call_info = method.call_infos[0]
+ assert_equal('RecursiveTest#test_simple', call_info.call_sequence)
+ assert_equal(1, call_info.children.length)
+
+ method = methods[1]
+ assert_equal('Object#simple', method.full_name)
+ assert_equal(1, method.called)
+ assert_in_delta(2, method.total_time, 0.01)
+ assert_in_delta(0, method.self_time, 0.01)
+ assert_in_delta(0, method.wait_time, 0.01)
+ assert_in_delta(2, method.children_time, 0.01)
+
+ assert_equal(1, method.call_infos.length)
+ call_info = method.call_infos[0]
+ assert_equal('RecursiveTest#test_simple->Object#simple', call_info.call_sequence)
+ assert_equal(4, call_info.children.length)
+
+ method = methods[2]
+ assert_equal('Kernel#sleep', method.full_name)
+ assert_equal(2, method.called)
+ assert_in_delta(2, method.total_time, 0.01)
+ assert_in_delta(2, method.self_time, 0.01)
+ assert_in_delta(0, method.wait_time, 0.01)
+ assert_in_delta(0, method.children_time, 0.01)
+
+ assert_equal(2, method.call_infos.length)
+ call_info = method.call_infos[0]
+ assert_equal('RecursiveTest#test_simple->Object#simple->Kernel#sleep', call_info.call_sequence)
+ assert_equal(0, call_info.children.length)
+
+ call_info = method.call_infos[1]
+ assert_equal('RecursiveTest#test_simple->Object#simple->Object#simple-1->Kernel#sleep', call_info.call_sequence)
+ assert_equal(0, call_info.children.length)
+
+ method = methods[3]
+ assert_equal('Object#simple-1', method.full_name)
+ assert_equal(1, method.called)
+ assert_in_delta(1, method.total_time, 0.01)
+ assert_in_delta(0, method.self_time, 0.01)
+ assert_in_delta(0, method.wait_time, 0.01)
+ assert_in_delta(1, method.children_time, 0.01)
+
+ assert_equal(1, method.call_infos.length)
+ call_info = method.call_infos[0]
+ assert_equal('RecursiveTest#test_simple->Object#simple->Object#simple-1', call_info.call_sequence)
+ assert_equal(3, call_info.children.length)
+
+ method = methods[4]
+ assert_equal('Fixnum#-', method.full_name)
+ assert_equal(2, method.called)
+ assert_in_delta(0, method.total_time, 0.01)
+ assert_in_delta(0, method.self_time, 0.01)
+ assert_in_delta(0, method.wait_time, 0.01)
+ assert_in_delta(0, method.children_time, 0.01)
+
+ assert_equal(2, method.call_infos.length)
+ call_info = method.call_infos[0]
+ assert_equal('RecursiveTest#test_simple->Object#simple->Fixnum#-', call_info.call_sequence)
+ assert_equal(0, call_info.children.length)
+
+ call_info = method.call_infos[1]
+ assert_equal('RecursiveTest#test_simple->Object#simple->Object#simple-1->Fixnum#-', call_info.call_sequence)
+ assert_equal(0, call_info.children.length)
+
+ method = methods[5]
+ assert_equal('Fixnum#==', method.full_name)
+ assert_equal(2, method.called)
+ assert_in_delta(0, method.total_time, 0.01)
+ assert_in_delta(0, method.self_time, 0.01)
+ assert_in_delta(0, method.wait_time, 0.01)
+ assert_in_delta(0, method.children_time, 0.01)
+
+ assert_equal(2, method.call_infos.length)
+ call_info = method.call_infos[0]
+ assert_equal('RecursiveTest#test_simple->Object#simple->Fixnum#==', call_info.call_sequence)
+ assert_equal(0, call_info.children.length)
+
+ call_info = method.call_infos[1]
+ assert_equal('RecursiveTest#test_simple->Object#simple->Object#simple-1->Fixnum#==', call_info.call_sequence)
+ assert_equal(0, call_info.children.length)
+ end
+
+ def test_cycle
+ result = RubyProf.profile do
+ cycle(2)
+ end
+
+ methods = result.threads.values.first.sort.reverse
+ assert_equal(8, methods.length)
+
+ method = methods[0]
+ assert_equal('RecursiveTest#test_cycle', method.full_name)
+ assert_equal(1, method.called)
+ assert_in_delta(2, method.total_time, 0.01)
+ assert_in_delta(0, method.self_time, 0.01)
+ assert_in_delta(0, method.wait_time, 0.01)
+ assert_in_delta(2, method.children_time, 0.01)
+
+ assert_equal(1, method.call_infos.length)
+ call_info = method.call_infos[0]
+ assert_equal('RecursiveTest#test_cycle', call_info.call_sequence)
+ assert_equal(1, call_info.children.length)
+
+ method = methods[1]
+ assert_equal('Object#cycle', method.full_name)
+ assert_equal(1, method.called)
+ assert_in_delta(2, method.total_time, 0.01)
+ assert_in_delta(0, method.self_time, 0.01)
+ assert_in_delta(0, method.wait_time, 0.01)
+ assert_in_delta(2, method.children_time, 0.01)
+
+ assert_equal(1, method.call_infos.length)
+ call_info = method.call_infos[0]
+ assert_equal('RecursiveTest#test_cycle->Object#cycle', call_info.call_sequence)
+ assert_equal(1, call_info.children.length)
+
+ method = methods[2]
+ assert_equal('Object#sub_cycle', method.full_name)
+ assert_equal(1, method.called)
+ assert_in_delta(2, method.total_time, 0.01)
+ assert_in_delta(0, method.self_time, 0.01)
+ assert_in_delta(0, method.wait_time, 0.01)
+ assert_in_delta(2, method.children_time, 0.01)
+
+ assert_equal(1, method.call_infos.length)
+ call_info = method.call_infos[0]
+ assert_equal('RecursiveTest#test_cycle->Object#cycle->Object#sub_cycle', call_info.call_sequence)
+ assert_equal(4, call_info.children.length)
+
+ method = methods[3]
+ assert_equal('Kernel#sleep', method.full_name)
+ assert_equal(2, method.called)
+ assert_in_delta(2, method.total_time, 0.01)
+ assert_in_delta(2, method.self_time, 0.01)
+ assert_in_delta(0, method.wait_time, 0.01)
+ assert_in_delta(0, method.children_time, 0.01)
+
+ assert_equal(2, method.call_infos.length)
+ call_info = method.call_infos[0]
+ assert_equal('RecursiveTest#test_cycle->Object#cycle->Object#sub_cycle->Kernel#sleep', call_info.call_sequence)
+ assert_equal(0, call_info.children.length)
+
+ call_info = method.call_infos[1]
+ assert_equal('RecursiveTest#test_cycle->Object#cycle->Object#sub_cycle->Object#cycle-1->Object#sub_cycle-1->Kernel#sleep', call_info.call_sequence)
+ assert_equal(0, call_info.children.length)
+
+ method = methods[4]
+ assert_equal('Object#cycle-1', method.full_name)
+ assert_equal(1, method.called)
+ assert_in_delta(1, method.total_time, 0.01)
+ assert_in_delta(0, method.self_time, 0.01)
+ assert_in_delta(0, method.wait_time, 0.01)
+ assert_in_delta(1, method.children_time, 0.01)
+
+ assert_equal(1, method.call_infos.length)
+ call_info = method.call_infos[0]
+ assert_equal('RecursiveTest#test_cycle->Object#cycle->Object#sub_cycle->Object#cycle-1', call_info.call_sequence)
+ assert_equal(1, call_info.children.length)
+
+ method = methods[5]
+ assert_equal('Object#sub_cycle-1', method.full_name)
+ assert_equal(1, method.called)
+ assert_in_delta(1, method.total_time, 0.01)
+ assert_in_delta(0, method.self_time, 0.01)
+ assert_in_delta(0, method.wait_time, 0.01)
+ assert_in_delta(1, method.children_time, 0.01)
+
+ assert_equal(1, method.call_infos.length)
+ call_info = method.call_infos[0]
+ assert_equal('RecursiveTest#test_cycle->Object#cycle->Object#sub_cycle->Object#cycle-1->Object#sub_cycle-1', call_info.call_sequence)
+ assert_equal(3, call_info.children.length)
+
+ method = methods[6]
+ assert_equal('Fixnum#-', method.full_name)
+ assert_equal(2, method.called)
+ assert_in_delta(0, method.total_time, 0.01)
+ assert_in_delta(0, method.self_time, 0.01)
+ assert_in_delta(0, method.wait_time, 0.01)
+ assert_in_delta(0, method.children_time, 0.01)
+
+ assert_equal(2, method.call_infos.length)
+ call_info = method.call_infos[0]
+ assert_equal('RecursiveTest#test_cycle->Object#cycle->Object#sub_cycle->Fixnum#-', call_info.call_sequence)
+ assert_equal(0, call_info.children.length)
+
+ call_info = method.call_infos[1]
+ assert_equal('RecursiveTest#test_cycle->Object#cycle->Object#sub_cycle->Object#cycle-1->Object#sub_cycle-1->Fixnum#-', call_info.call_sequence)
+ assert_equal(0, call_info.children.length)
+
+ method = methods[7]
+ assert_equal('Fixnum#==', method.full_name)
+ assert_equal(2, method.called)
+ assert_in_delta(0, method.total_time, 0.01)
+ assert_in_delta(0, method.self_time, 0.01)
+ assert_in_delta(0, method.wait_time, 0.01)
+ assert_in_delta(0, method.children_time, 0.01)
+
+ assert_equal(2, method.call_infos.length)
+ call_info = method.call_infos[0]
+ assert_equal('RecursiveTest#test_cycle->Object#cycle->Object#sub_cycle->Fixnum#==', call_info.call_sequence)
+ assert_equal(0, call_info.children.length)
+
+ call_info = method.call_infos[1]
+ assert_equal('RecursiveTest#test_cycle->Object#cycle->Object#sub_cycle->Object#cycle-1->Object#sub_cycle-1->Fixnum#==', call_info.call_sequence)
+ assert_equal(0, call_info.children.length)
+ end
+end
\ No newline at end of file
diff --git a/test/singleton_test.rb b/test/singleton_test.rb
new file mode 100755
index 0000000..9ac4914
--- /dev/null
+++ b/test/singleton_test.rb
@@ -0,0 +1,37 @@
+#!/usr/bin/env ruby
+
+require 'test/unit'
+require 'ruby-prof'
+require 'timeout'
+
+# -- Test for bug [#5657]
+# http://rubyforge.org/tracker/index.php?func=detail&aid=5657&group_id=1814&atid=7060
+
+
+class A
+ attr_accessor :as
+ def initialize
+ @as = []
+ class << @as
+ def <<(an_a)
+ super
+ end
+ end
+ end
+
+ def <<(an_a)
+ @as << an_a
+ end
+end
+
+class SingletonTest < Test::Unit::TestCase
+ def test_singleton
+ result = RubyProf.profile do
+ a = A.new
+ a << :first_thing
+ assert_equal(1, a.as.size)
+ end
+ printer = RubyProf::FlatPrinter.new(result)
+ printer.print(STDOUT)
+ end
+end
\ No newline at end of file
diff --git a/test/stack_test.rb b/test/stack_test.rb
new file mode 100755
index 0000000..7c991fa
--- /dev/null
+++ b/test/stack_test.rb
@@ -0,0 +1,138 @@
+#!/usr/bin/env ruby
+
+require 'test/unit'
+require 'ruby-prof'
+
+# Test data
+# A
+# / \
+# B C
+# \
+# B
+
+class StackClass
+ def a
+ sleep 1
+ b
+ c
+ end
+
+ def b
+ sleep 2
+ end
+
+ def c
+ sleep 3
+ b
+ end
+end
+
+class StackTest < Test::Unit::TestCase
+ def setup
+ # Need to use wall time for this test due to the sleep calls
+ RubyProf::measure_mode = RubyProf::WALL_TIME
+ end
+
+ def test_call_sequence
+ c = StackClass.new
+ result = RubyProf.profile do
+ c.a
+ end
+
+ # Length should be 5:
+ # StackTest#test_call_sequence
+ # StackClass#a
+ # Kernel#sleep
+ # StackClass#c
+ # StackClass#b
+
+ methods = result.threads.values.first.sort.reverse
+ assert_equal(5, methods.length)
+
+ # Check StackTest#test_call_sequence
+ method = methods[0]
+ assert_equal('StackTest#test_call_sequence', method.full_name)
+ assert_equal(1, method.called)
+ assert_in_delta(8, method.total_time, 0.01)
+ assert_in_delta(0, method.wait_time, 0.01)
+ assert_in_delta(0, method.self_time, 0.01)
+ assert_in_delta(8, method.children_time, 0.01)
+ assert_equal(1, method.call_infos.length)
+
+ call_info = method.call_infos[0]
+ assert_equal('StackTest#test_call_sequence', call_info.call_sequence)
+ assert_equal(1, call_info.children.length)
+
+ # Check StackClass#a
+ method = methods[1]
+ assert_equal('StackClass#a', method.full_name)
+ assert_equal(1, method.called)
+ assert_in_delta(8, method.total_time, 0.01)
+ assert_in_delta(0, method.wait_time, 0.01)
+ assert_in_delta(0, method.self_time, 0.01)
+ assert_in_delta(8, method.children_time, 0.01)
+ assert_equal(1, method.call_infos.length)
+
+ call_info = method.call_infos[0]
+ assert_equal('StackTest#test_call_sequence->StackClass#a', call_info.call_sequence)
+ assert_equal(3, call_info.children.length)
+
+ # Check Kernel#sleep
+ method = methods[2]
+ assert_equal('Kernel#sleep', method.full_name)
+ assert_equal(4, method.called)
+ assert_in_delta(8, method.total_time, 0.01)
+ assert_in_delta(0, method.wait_time, 0.01)
+ assert_in_delta(8, method.self_time, 0.01)
+ assert_in_delta(0, method.children_time, 0.01)
+ assert_equal(4, method.call_infos.length)
+
+ call_info = method.call_infos[0]
+ assert_equal('StackTest#test_call_sequence->StackClass#a->Kernel#sleep', call_info.call_sequence)
+ assert_equal(0, call_info.children.length)
+
+ call_info = method.call_infos[1]
+ assert_equal('StackTest#test_call_sequence->StackClass#a->StackClass#b->Kernel#sleep', call_info.call_sequence)
+ assert_equal(0, call_info.children.length)
+
+ call_info = method.call_infos[2]
+ assert_equal('StackTest#test_call_sequence->StackClass#a->StackClass#c->Kernel#sleep', call_info.call_sequence)
+ assert_equal(0, call_info.children.length)
+
+ call_info = method.call_infos[3]
+ assert_equal('StackTest#test_call_sequence->StackClass#a->StackClass#c->StackClass#b->Kernel#sleep', call_info.call_sequence)
+ assert_equal(0, call_info.children.length)
+
+ # Check StackClass#c
+ method = methods[3]
+ assert_equal('StackClass#c', method.full_name)
+ assert_equal(1, method.called)
+ assert_in_delta(5, method.total_time, 0.01)
+ assert_in_delta(0, method.wait_time, 0.01)
+ assert_in_delta(0, method.self_time, 0.01)
+ assert_in_delta(5, method.children_time, 0.01)
+ assert_equal(1, method.call_infos.length)
+
+ call_info = method.call_infos[0]
+ assert_equal('StackTest#test_call_sequence->StackClass#a->StackClass#c', call_info.call_sequence)
+ assert_equal(2, call_info.children.length)
+
+ # Check StackClass#b
+ method = methods[4]
+ assert_equal('StackClass#b', method.full_name)
+ assert_equal(2, method.called)
+ assert_in_delta(4, method.total_time, 0.01)
+ assert_in_delta(0, method.wait_time, 0.01)
+ assert_in_delta(0, method.self_time, 0.01)
+ assert_in_delta(4, method.children_time, 0.01)
+ assert_equal(2, method.call_infos.length)
+
+ call_info = method.call_infos[0]
+ assert_equal('StackTest#test_call_sequence->StackClass#a->StackClass#b', call_info.call_sequence)
+ assert_equal(1, call_info.children.length)
+
+ call_info = method.call_infos[1]
+ assert_equal('StackTest#test_call_sequence->StackClass#a->StackClass#c->StackClass#b', call_info.call_sequence)
+ assert_equal(1, call_info.children.length)
+ end
+end
\ No newline at end of file
diff --git a/test/start_stop_test.rb b/test/start_stop_test.rb
new file mode 100755
index 0000000..c5976e5
--- /dev/null
+++ b/test/start_stop_test.rb
@@ -0,0 +1,95 @@
+#!/usr/bin/env ruby
+
+require 'test/unit'
+require 'ruby-prof'
+
+class StartStopTest < Test::Unit::TestCase
+ def setup
+ # Need to use wall time for this test due to the sleep calls
+ RubyProf::measure_mode = RubyProf::WALL_TIME
+ end
+
+ def method1
+ RubyProf.start
+ method2
+ end
+
+ def method2
+ method3
+ end
+
+ def method3
+ sleep(2)
+ @result = RubyProf.stop
+ end
+
+ def test_different_methods
+ method1
+
+ # Ruby prof should be stopped
+ assert_equal(false, RubyProf.running?)
+
+
+ # Length should be 4:
+ # StartStopTest#method1
+ # StartStopTest#method2
+ # StartStopTest#method3
+ # Kernel#sleep
+
+ methods = @result.threads.values.first.sort.reverse
+ assert_equal(4, methods.length)
+
+ # Check StackTest#test_call_sequence
+ method = methods[0]
+ assert_equal('StartStopTest#method1', method.full_name)
+ assert_equal(1, method.called)
+ assert_in_delta(2, method.total_time, 0.01)
+ assert_in_delta(0, method.wait_time, 0.01)
+ assert_in_delta(0, method.self_time, 0.01)
+ assert_in_delta(2, method.children_time, 0.01)
+ assert_equal(1, method.call_infos.length)
+
+ call_info = method.call_infos[0]
+ assert_equal('StartStopTest#method1', call_info.call_sequence)
+ assert_equal(1, call_info.children.length)
+
+ method = methods[1]
+ assert_equal('StartStopTest#method2', method.full_name)
+ assert_equal(1, method.called)
+ assert_in_delta(2, method.total_time, 0.01)
+ assert_in_delta(0, method.wait_time, 0.01)
+ assert_in_delta(0, method.self_time, 0.01)
+ assert_in_delta(2, method.children_time, 0.01)
+ assert_equal(1, method.call_infos.length)
+
+ call_info = method.call_infos[0]
+ assert_equal('StartStopTest#method1->StartStopTest#method2', call_info.call_sequence)
+ assert_equal(1, call_info.children.length)
+
+ method = methods[2]
+ assert_equal('StartStopTest#method3', method.full_name)
+ assert_equal(1, method.called)
+ assert_in_delta(2, method.total_time, 0.01)
+ assert_in_delta(0, method.wait_time, 0.01)
+ assert_in_delta(0, method.self_time, 0.01)
+ assert_in_delta(2, method.children_time, 0.01)
+ assert_equal(1, method.call_infos.length)
+
+ call_info = method.call_infos[0]
+ assert_equal('StartStopTest#method1->StartStopTest#method2->StartStopTest#method3', call_info.call_sequence)
+ assert_equal(1, call_info.children.length)
+
+ method = methods[3]
+ assert_equal('Kernel#sleep', method.full_name)
+ assert_equal(1, method.called)
+ assert_in_delta(2, method.total_time, 0.01)
+ assert_in_delta(0, method.wait_time, 0.01)
+ assert_in_delta(2, method.self_time, 0.01)
+ assert_in_delta(0, method.children_time, 0.01)
+ assert_equal(1, method.call_infos.length)
+
+ call_info = method.call_infos[0]
+ assert_equal('StartStopTest#method1->StartStopTest#method2->StartStopTest#method3->Kernel#sleep', call_info.call_sequence)
+ assert_equal(0, call_info.children.length)
+ end
+end
\ No newline at end of file
diff --git a/test/test_suite.rb b/test/test_suite.rb
new file mode 100644
index 0000000..8bc3c01
--- /dev/null
+++ b/test/test_suite.rb
@@ -0,0 +1,23 @@
+require 'test/unit'
+
+require 'aggregate_test'
+require 'basic_test'
+require 'duplicate_names_test'
+require 'exceptions_test'
+require 'line_number_test'
+require 'measurement_test'
+require 'module_test'
+require 'no_method_class_test'
+require 'prime_test'
+require 'printers_test'
+require 'recursive_test'
+require 'singleton_test'
+require 'stack_test'
+require 'start_stop_test'
+require 'thread_test'
+require 'unique_call_path_test'
+
+# Can't use this one here cause it breaks
+# the rest of the unit tets (Ruby Prof gets
+# started twice).
+#require 'profile_unit_test'
diff --git a/test/thread_test.rb b/test/thread_test.rb
new file mode 100755
index 0000000..2af3b25
--- /dev/null
+++ b/test/thread_test.rb
@@ -0,0 +1,159 @@
+#!/usr/bin/env ruby
+require 'test/unit'
+require 'ruby-prof'
+require 'timeout'
+
+# -- Tests ----
+class ThreadTest < Test::Unit::TestCase
+ def setup
+ # Need to use wall time for this test due to the sleep calls
+ RubyProf::measure_mode = RubyProf::WALL_TIME
+ end
+
+ def test_thread_count
+ RubyProf.start
+
+ thread = Thread.new do
+ sleep(1)
+ end
+
+ thread.join
+ result = RubyProf.stop
+
+ assert_equal(2, result.threads.keys.length)
+ end
+
+ def test_thread_identity
+ RubyProf.start
+
+ thread = Thread.new do
+ sleep(1)
+ end
+
+ thread.join
+ result = RubyProf.stop
+
+ thread_ids = result.threads.keys.sort
+ threads = [Thread.current, thread].sort_by {|thread| thread.object_id}
+
+ assert_equal(threads[0].object_id, thread_ids[0])
+ assert_equal(threads[1].object_id, thread_ids[1])
+
+ assert_instance_of(Thread, ObjectSpace._id2ref(thread_ids[0]))
+ assert_equal(threads[0], ObjectSpace._id2ref(thread_ids[0]))
+
+ assert_instance_of(Thread, ObjectSpace._id2ref(thread_ids[1]))
+ assert_equal(threads[1], ObjectSpace._id2ref(thread_ids[1]))
+ end
+
+ def test_thread_timings
+ RubyProf.start
+
+ thread = Thread.new do
+ sleep(1)
+ end
+
+ thread.join
+
+ result = RubyProf.stop
+
+ # Check background thread
+ methods = result.threads[thread.object_id].sort.reverse
+ assert_equal(2, methods.length)
+
+ method = methods[0]
+ assert_equal('ThreadTest#test_thread_timings', method.full_name)
+ assert_equal(1, method.called)
+ assert_in_delta(1, method.total_time, 0.01)
+ assert_in_delta(0, method.self_time, 0.01)
+ assert_in_delta(1, method.wait_time, 0.01)
+ assert_in_delta(0, method.children_time, 0.01)
+ assert_equal(1, method.call_infos.length)
+ call_info = method.call_infos[0]
+ assert_equal('ThreadTest#test_thread_timings', call_info.call_sequence)
+ assert_equal(1, call_info.children.length)
+
+ method = methods[1]
+ assert_equal('Kernel#sleep', method.full_name)
+ assert_equal(1, method.called)
+ assert_in_delta(1, method.total_time, 0.01)
+ assert_in_delta(1.0, method.self_time, 0.01)
+ assert_in_delta(0, method.wait_time, 0.01)
+ assert_in_delta(0, method.children_time, 0.01)
+
+ assert_equal(1, method.call_infos.length)
+ call_info = method.call_infos[0]
+ assert_equal('ThreadTest#test_thread_timings->Kernel#sleep', call_info.call_sequence)
+ assert_equal(0, call_info.children.length)
+
+ # Check foreground thread
+ methods = result.threads[Thread.current.object_id].sort.reverse
+ assert_equal(4, methods.length)
+ methods = methods.sort.reverse
+
+ method = methods[0]
+ assert_equal('ThreadTest#test_thread_timings', method.full_name)
+ assert_equal(0, method.called)
+ assert_in_delta(1, method.total_time, 0.01)
+ assert_in_delta(0, method.self_time, 0.01)
+ assert_in_delta(1.0, method.wait_time, 0.01)
+ assert_in_delta(0, method.children_time, 0.01)
+
+ assert_equal(1, method.call_infos.length)
+ call_info = method.call_infos[0]
+ assert_equal('ThreadTest#test_thread_timings', call_info.call_sequence)
+ assert_equal(2, call_info.children.length)
+
+ method = methods[1]
+ assert_equal('Thread#join', method.full_name)
+ assert_equal(1, method.called)
+ assert_in_delta(1, method.total_time, 0.01)
+ assert_in_delta(0, method.self_time, 0.01)
+ assert_in_delta(1.0, method.wait_time, 0.01)
+ assert_in_delta(0, method.children_time, 0.01)
+
+ assert_equal(1, method.call_infos.length)
+ call_info = method.call_infos[0]
+ assert_equal('ThreadTest#test_thread_timings->Thread#join', call_info.call_sequence)
+ assert_equal(0, call_info.children.length)
+
+ method = methods[2]
+ assert_equal('<Class::Thread>#new', method.full_name)
+ assert_equal(1, method.called)
+ assert_in_delta(0, method.total_time, 0.01)
+ assert_in_delta(0, method.self_time, 0.01)
+ assert_in_delta(0, method.wait_time, 0.01)
+ assert_in_delta(0, method.children_time, 0.01)
+
+ assert_equal(1, method.call_infos.length)
+ call_info = method.call_infos[0]
+ assert_equal('ThreadTest#test_thread_timings-><Class::Thread>#new', call_info.call_sequence)
+ assert_equal(1, call_info.children.length)
+
+ method = methods[3]
+ assert_equal('Thread#initialize', method.full_name)
+ assert_equal(1, method.called)
+ assert_in_delta(0, method.total_time, 0.01)
+ assert_in_delta(0, method.self_time, 0.01)
+ assert_in_delta(0, method.wait_time, 0.01)
+ assert_in_delta(0, method.children_time, 0.01)
+
+ assert_equal(1, method.call_infos.length)
+ call_info = method.call_infos[0]
+ assert_equal('ThreadTest#test_thread_timings-><Class::Thread>#new->Thread#initialize', call_info.call_sequence)
+ assert_equal(0, call_info.children.length)
+ end
+
+ def test_thread
+ result = RubyProf.profile do
+ begin
+ status = Timeout::timeout(2) do
+ while true
+ next
+ end
+ end
+ rescue Timeout::Error
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/test/unique_call_path_test.rb b/test/unique_call_path_test.rb
new file mode 100755
index 0000000..d3f9a04
--- /dev/null
+++ b/test/unique_call_path_test.rb
@@ -0,0 +1,206 @@
+#!/usr/bin/env ruby
+
+require 'test/unit'
+require 'ruby-prof'
+
+class UniqueCallPath
+ def method_a(i)
+ if i==1
+ method_b
+ else
+ method_c
+ end
+ end
+
+ def method_b
+ method_c
+ end
+
+ def method_c
+ c = 3
+ end
+
+ def method_k(i)
+ method_a(i)
+ end
+end
+
+
+# -- Tests ----
+class UniqueCallPathTest < Test::Unit::TestCase
+ def test_root_method
+ unique_call_path = UniqueCallPath.new
+
+ result = RubyProf.profile do
+ unique_call_path.method_a(1)
+ end
+
+ root_methods = Array.new
+ result.threads.each do | thread_id, methods |
+ methods.each do | m |
+ if m.root?
+ root_methods.push(m)
+ end
+ end
+ end
+
+ assert_equal(1, root_methods.length)
+ assert_equal("UniqueCallPathTest#test_root_method", root_methods[0].full_name)
+ end
+
+ def test_root_children
+ unique_call_path = UniqueCallPath.new
+
+ result = RubyProf.profile do
+ unique_call_path.method_a(1)
+ unique_call_path.method_k(2)
+ end
+
+ root_methods = Array.new
+ result.threads.each do | thread_id, methods |
+ methods.each do | m |
+ if m.root?
+ root_methods.push(m)
+ end
+ end
+ end
+
+ assert_equal(1, root_methods.length)
+
+ root_children = Array.new
+ root_methods[0].children.each do | c |
+ if c.parent.target.eql?(root_methods[0])
+ root_children.push(c)
+ end
+ end
+
+ children = root_children.sort do |c1, c2|
+ c1.target.full_name <=> c2.target.full_name
+ end
+
+ assert_equal(2, children.length)
+ assert_equal("UniqueCallPath#method_a", children[0].target.full_name)
+ assert_equal("UniqueCallPath#method_k", children[1].target.full_name)
+ end
+
+ def test_children_of
+ unique_call_path = UniqueCallPath.new
+
+ result = RubyProf.profile do
+ unique_call_path.method_a(1)
+ unique_call_path.method_k(2)
+ end
+
+ root_methods = Array.new
+ result.threads.each do | thread_id, methods |
+ methods.each do | m |
+ if m.root?
+ root_methods.push(m)
+ end
+ end
+ end
+
+ assert_equal(1, root_methods.length)
+ method = root_methods[0]
+ assert_equal('UniqueCallPathTest#test_children_of', method.full_name)
+
+ call_info_a = nil
+ root_methods[0].children.each do | c |
+ if c.target.full_name == "UniqueCallPath#method_a"
+ call_info_a = c
+ break
+ end
+ end
+
+ assert !call_info_a.nil?
+
+ children_of_a = Array.new
+
+ call_info_a.children.each do | c |
+ if c.parent.eql?(call_info_a)
+ children_of_a.push(c)
+ end
+ end
+
+ assert_equal(4, call_info_a.target.children.length)
+
+ children_of_a = children_of_a.sort do |c1, c2|
+ c1.target.full_name <=> c2.target.full_name
+ end
+
+ assert_equal(2, children_of_a.length)
+ assert_equal("Fixnum#==", children_of_a[0].target.full_name)
+ assert_equal("UniqueCallPath#method_b", children_of_a[1].target.full_name)
+ end
+
+ def test_id2ref
+ unique_call_path = UniqueCallPath.new
+
+ result = RubyProf.profile do
+ unique_call_path.method_a(1)
+ end
+
+ root_methods = Array.new
+ result.threads.each do | thread_id, methods |
+ methods.each do | m |
+ if m.root?
+ root_methods.push(m)
+ end
+ end
+ end
+
+ child = root_methods[0].children[0]
+
+ assert_not_equal(0, child.id)
+ #assert_equal(RubyProf::CallInfo.id2ref(child.id).target.full_name, child.target.full_name)
+ end
+
+ def test_unique_path
+ unique_call_path = UniqueCallPath.new
+
+ result = RubyProf.profile do
+ unique_call_path.method_a(1)
+ unique_call_path.method_k(1)
+ end
+
+ root_methods = Array.new
+ result.threads.each do | thread_id, methods |
+ methods.each do | m |
+ if m.root?
+ root_methods.push(m)
+ end
+ end
+ end
+
+ assert_equal(1, root_methods.length)
+
+ call_info_a = nil
+ root_methods[0].children.each do | c |
+ if c.target.full_name == "UniqueCallPath#method_a"
+ call_info_a = c
+ break
+ end
+ end
+
+ assert !call_info_a.nil?
+
+ children_of_a = Array.new
+ call_info_a.children.each do |c|
+ if c.parent.eql?(call_info_a)
+ children_of_a.push(c)
+ end
+ end
+
+ assert_equal(4, call_info_a.target.children.length)
+
+ children_of_a = children_of_a.sort do |c1, c2|
+ c1.target.full_name <=> c2.target.full_name
+ end
+
+ assert_equal(2, children_of_a.length)
+ assert_equal(1, children_of_a[0].called)
+ assert_equal("Fixnum#==", children_of_a[0].target.full_name)
+ assert_equal(1, children_of_a[1].called)
+ assert_equal("UniqueCallPath#method_b", children_of_a[1].target.full_name)
+ end
+end
\ No newline at end of file
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/ruby-prof.git
More information about the Pkg-ruby-extras-commits
mailing list