acunote-ruby-prof 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. data/CHANGES +240 -0
  2. data/LICENSE +23 -0
  3. data/README.rdoc +439 -0
  4. data/Rakefile +148 -0
  5. data/bin/ruby-prof +236 -0
  6. data/examples/empty.png +0 -0
  7. data/examples/flat.txt +55 -0
  8. data/examples/graph.dot +106 -0
  9. data/examples/graph.html +823 -0
  10. data/examples/graph.png +0 -0
  11. data/examples/graph.txt +170 -0
  12. data/examples/minus.png +0 -0
  13. data/examples/multi.flat.txt +23 -0
  14. data/examples/multi.graph.html +906 -0
  15. data/examples/multi.grind.dat +194 -0
  16. data/examples/multi.stack.html +573 -0
  17. data/examples/plus.png +0 -0
  18. data/examples/stack.html +573 -0
  19. data/ext/ruby_prof/extconf.rb +43 -0
  20. data/ext/ruby_prof/measure_allocations.h +58 -0
  21. data/ext/ruby_prof/measure_cpu_time.h +152 -0
  22. data/ext/ruby_prof/measure_gc_runs.h +76 -0
  23. data/ext/ruby_prof/measure_gc_time.h +57 -0
  24. data/ext/ruby_prof/measure_memory.h +101 -0
  25. data/ext/ruby_prof/measure_process_time.h +52 -0
  26. data/ext/ruby_prof/measure_wall_time.h +53 -0
  27. data/ext/ruby_prof/mingw/Rakefile +23 -0
  28. data/ext/ruby_prof/mingw/build.rake +38 -0
  29. data/ext/ruby_prof/ruby_prof.c +1834 -0
  30. data/ext/ruby_prof/ruby_prof.h +190 -0
  31. data/ext/ruby_prof/version.h +4 -0
  32. data/lib/ruby-prof.rb +62 -0
  33. data/lib/ruby-prof/abstract_printer.rb +41 -0
  34. data/lib/ruby-prof/aggregate_call_info.rb +68 -0
  35. data/lib/ruby-prof/call_info.rb +112 -0
  36. data/lib/ruby-prof/call_stack_printer.rb +751 -0
  37. data/lib/ruby-prof/call_tree_printer.rb +133 -0
  38. data/lib/ruby-prof/dot_printer.rb +153 -0
  39. data/lib/ruby-prof/empty.png +0 -0
  40. data/lib/ruby-prof/flat_printer.rb +78 -0
  41. data/lib/ruby-prof/flat_printer_with_line_numbers.rb +72 -0
  42. data/lib/ruby-prof/graph_html_printer.rb +278 -0
  43. data/lib/ruby-prof/graph_printer.rb +245 -0
  44. data/lib/ruby-prof/method_info.rb +131 -0
  45. data/lib/ruby-prof/minus.png +0 -0
  46. data/lib/ruby-prof/multi_printer.rb +54 -0
  47. data/lib/ruby-prof/plus.png +0 -0
  48. data/lib/ruby-prof/rack.rb +30 -0
  49. data/lib/ruby-prof/result.rb +70 -0
  50. data/lib/ruby-prof/symbol_to_proc.rb +8 -0
  51. data/lib/ruby-prof/task.rb +146 -0
  52. data/lib/ruby-prof/test.rb +148 -0
  53. data/lib/unprof.rb +8 -0
  54. data/rails/environment/profile.rb +24 -0
  55. data/rails/example/example_test.rb +9 -0
  56. data/rails/profile_test_helper.rb +21 -0
  57. data/test/aggregate_test.rb +136 -0
  58. data/test/basic_test.rb +290 -0
  59. data/test/current_failures_windows +8 -0
  60. data/test/do_nothing.rb +0 -0
  61. data/test/duplicate_names_test.rb +32 -0
  62. data/test/enumerable_test.rb +16 -0
  63. data/test/exceptions_test.rb +15 -0
  64. data/test/exclude_threads_test.rb +54 -0
  65. data/test/exec_test.rb +14 -0
  66. data/test/line_number_test.rb +73 -0
  67. data/test/measurement_test.rb +122 -0
  68. data/test/method_elimination_test.rb +74 -0
  69. data/test/module_test.rb +44 -0
  70. data/test/multi_printer_test.rb +81 -0
  71. data/test/no_method_class_test.rb +13 -0
  72. data/test/prime.rb +55 -0
  73. data/test/prime_test.rb +13 -0
  74. data/test/printers_test.rb +164 -0
  75. data/test/recursive_test.rb +236 -0
  76. data/test/ruby-prof-bin +20 -0
  77. data/test/singleton_test.rb +38 -0
  78. data/test/stack_printer_test.rb +74 -0
  79. data/test/stack_test.rb +138 -0
  80. data/test/start_stop_test.rb +112 -0
  81. data/test/test_suite.rb +32 -0
  82. data/test/thread_test.rb +173 -0
  83. data/test/unique_call_path_test.rb +225 -0
  84. metadata +185 -0
@@ -0,0 +1,70 @@
1
+ require 'set'
2
+ module RubyProf
3
+ class Result
4
+ # this method gets called internally when profiling is stopped.
5
+ # it determines for each call_info whether it is minimal: a
6
+ # call_info is minimal in a call tree if the call_info is not a
7
+ # descendant of a call_info of the same method
8
+ def compute_minimality
9
+ threads.each do |threadid, method_infos|
10
+ root_methods = method_infos.select{|mi| mi.root?}
11
+ root_methods.each do |mi|
12
+ mi.call_infos.select{|ci| ci.root?}.each do |call_info_root|
13
+ call_info_root.compute_minimality(Set.new)
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ # eliminate some calls from the graph by merging the information into callers.
20
+ # matchers can be a list of strings or regular expressions or the name of a file containing regexps.
21
+ def eliminate_methods!(matchers)
22
+ matchers = read_regexps_from_file(matchers) if matchers.is_a?(String)
23
+ eliminated = []
24
+ threads.each do |thread_id, methods|
25
+ matchers.each{ |matcher| eliminated.concat(eliminate_methods(methods, matcher)) }
26
+ end
27
+ compute_minimality # is this really necessary?
28
+ eliminated
29
+ end
30
+
31
+ def dump
32
+ threads.each do |thread_id, methods|
33
+ $stderr.puts "Call Info Dump for thread id #{thread_id}"
34
+ methods.each do |method_info|
35
+ $stderr.puts method_info.dump
36
+ end
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ # read regexps from file
43
+ def read_regexps_from_file(file_name)
44
+ matchers = []
45
+ File.open(matchers).each_line do |l|
46
+ next if (l =~ /^(#.*|\s*)$/) # emtpy lines and lines starting with #
47
+ matchers << Regexp.new(l.strip)
48
+ end
49
+ end
50
+
51
+ # eliminate methods matching matcher
52
+ def eliminate_methods(methods, matcher)
53
+ eliminated = []
54
+ i = 0
55
+ while i < methods.size
56
+ method_info = methods[i]
57
+ method_name = method_info.full_name
58
+ if matcher === method_name
59
+ raise "can't eliminate root method" if method_info.root?
60
+ eliminated << methods.delete_at(i)
61
+ method_info.eliminate!
62
+ else
63
+ i += 1
64
+ end
65
+ end
66
+ eliminated
67
+ end
68
+
69
+ end
70
+ end
@@ -0,0 +1,8 @@
1
+ unless (:a.respond_to?(:to_proc))
2
+ class Symbol
3
+ def to_proc
4
+ proc {|stuff| stuff.send(self)}
5
+ end
6
+ end
7
+ end
8
+
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rake'
4
+ require 'rake/testtask'
5
+ require 'fileutils'
6
+
7
+ module RubyProf
8
+
9
+ # Define a task library for profiling unit tests with ruby-prof.
10
+ #
11
+ # All of the options provided by
12
+ # the Rake:TestTask are supported except the loader
13
+ # which is set to ruby-prof. For detailed information
14
+ # please refer to the Rake:TestTask documentation.
15
+ #
16
+ # ruby-prof specific options include:
17
+ #
18
+ # output_dir - For each file specified an output
19
+ # file with profile information will be
20
+ # written to the output directory.
21
+ # By default, the output directory is
22
+ # called "profile" and is created underneath
23
+ # the current working directory.
24
+ #
25
+ # printer - Specifies the output printer. Valid values include
26
+ # :flat, :graph, :graph_html and :call_tree.
27
+ #
28
+ # min_percent - Methods that take less than the specified percent
29
+ # will not be written out.
30
+ #
31
+ # Example:
32
+ #
33
+ # require 'ruby-prof/task'
34
+ #
35
+ # RubyProf::ProfileTask.new do |t|
36
+ # t.test_files = FileList['test/test*.rb']
37
+ # t.output_dir = "c:/temp"
38
+ # t.printer = :graph
39
+ # t.min_percent = 10
40
+ # end
41
+ #
42
+ # If rake is invoked with a "TEST=filename" command line option,
43
+ # then the list of test files will be overridden to include only the
44
+ # filename specified on the command line. This provides an easy way
45
+ # to run just one test.
46
+ #
47
+ # If rake is invoked with a "TESTOPTS=options" command line option,
48
+ # then the given options are passed to the test process after a
49
+ # '--'. This allows Test::Unit options to be passed to the test
50
+ # suite.
51
+ #
52
+ # Examples:
53
+ #
54
+ # rake profile # run tests normally
55
+ # rake profile TEST=just_one_file.rb # run just one test file.
56
+ # rake profile TESTOPTS="-v" # run in verbose mode
57
+ # rake profile TESTOPTS="--runner=fox" # use the fox test runner
58
+
59
+ class ProfileTask < Rake::TestTask
60
+ attr_accessor :output_dir
61
+ attr_accessor :min_percent
62
+ attr_accessor :printer
63
+
64
+ def initialize(name = :profile)
65
+ super(name)
66
+ end
67
+
68
+ # Create the tasks defined by this task lib.
69
+ def define
70
+ lib_path = @libs.join(File::PATH_SEPARATOR)
71
+ desc "Profile" + (@name==:profile ? "" : " for #{@name}")
72
+
73
+ task @name do
74
+ create_output_directory
75
+
76
+ @ruby_opts.unshift( "-I#{lib_path}" )
77
+ @ruby_opts.unshift( "-w" ) if @warning
78
+ @ruby_opts.push("-S ruby-prof")
79
+ @ruby_opts.push("--printer #{@printer}")
80
+ @ruby_opts.push("--min_percent #{@min_percent}")
81
+
82
+ file_list.each do |file_path|
83
+ run_script(file_path)
84
+ end
85
+ end
86
+ self
87
+ end
88
+
89
+ # Run script
90
+ def run_script(script_path)
91
+ run_code = ''
92
+ RakeFileUtils.verbose(@verbose) do
93
+ file_name = File.basename(script_path, File.extname(script_path))
94
+ case @printer
95
+ when :flat, :graph, :call_tree
96
+ file_name += ".txt"
97
+ when :graph_html
98
+ file_name += ".html"
99
+ else
100
+ file_name += ".txt"
101
+ end
102
+
103
+ output_file_path = File.join(output_directory, file_name)
104
+
105
+ command_line = @ruby_opts.join(" ") +
106
+ " --file=" + output_file_path +
107
+ " " + script_path
108
+
109
+ puts "ruby " + command_line
110
+ # We have to catch the exeption to continue on. However,
111
+ # the error message will have been output to STDERR
112
+ # already by the time we get here so we don't have to
113
+ # do that again
114
+ begin
115
+ ruby command_line
116
+ rescue => e
117
+ STDOUT << e << "\n"
118
+ STDOUT.flush
119
+ end
120
+ puts ""
121
+ puts ""
122
+ end
123
+ end
124
+
125
+ def output_directory
126
+ File.expand_path(@output_dir)
127
+ end
128
+
129
+ def create_output_directory
130
+ if not File.exist?(output_directory)
131
+ Dir.mkdir(output_directory)
132
+ end
133
+ end
134
+
135
+ def clean_output_directory
136
+ if File.exist?(output_directory)
137
+ files = Dir.glob(output_directory + '/*')
138
+ FileUtils.rm(files)
139
+ end
140
+ end
141
+
142
+ def option_list # :nodoc:
143
+ ENV['OPTIONS'] || @options.join(" ") || ""
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,148 @@
1
+ # Now load ruby-prof and away we go
2
+ require 'fileutils'
3
+ require 'ruby-prof'
4
+ require 'benchmark'
5
+
6
+ module RubyProf
7
+ module Test
8
+ PROFILE_OPTIONS = {
9
+ :measure_modes => [RubyProf::PROCESS_TIME],
10
+ :count => 10,
11
+ :printers => [RubyProf::FlatPrinter, RubyProf::GraphHtmlPrinter],
12
+ :min_percent => 0.05,
13
+ :output_dir => Dir.pwd }
14
+
15
+ def output_dir
16
+ PROFILE_OPTIONS[:output_dir]
17
+ end
18
+
19
+ def run(result)
20
+ return if @method_name.to_s == "default_test"
21
+
22
+ yield(self.class::STARTED, name)
23
+ @_result = result
24
+ run_warmup
25
+ PROFILE_OPTIONS[:measure_modes].each do |measure_mode|
26
+ data = run_profile(measure_mode)
27
+ report_profile(data, measure_mode)
28
+ result.add_run
29
+ end
30
+ yield(self.class::FINISHED, name)
31
+ end
32
+
33
+ def run_test
34
+ begin
35
+ setup
36
+ yield
37
+ rescue ::Test::Unit::AssertionFailedError => e
38
+ add_failure(e.message, e.backtrace)
39
+ rescue StandardError, ScriptError
40
+ add_error($!)
41
+ ensure
42
+ begin
43
+ teardown
44
+ rescue ::Test::Unit::AssertionFailedError => e
45
+ add_failure(e.message, e.backtrace)
46
+ rescue StandardError, ScriptError
47
+ add_error($!)
48
+ end
49
+ end
50
+ end
51
+
52
+ def run_warmup
53
+ print "\n#{self.class.name}##{method_name}"
54
+
55
+ run_test do
56
+ bench = Benchmark.realtime do
57
+ __send__(@method_name)
58
+ end
59
+ puts " (%.2fs warmup)" % bench
60
+ end
61
+ end
62
+
63
+ def run_profile(measure_mode)
64
+ RubyProf.measure_mode = measure_mode
65
+
66
+ print ' '
67
+ PROFILE_OPTIONS[:count].times do |i|
68
+ run_test do
69
+ begin
70
+ print '.'
71
+ $stdout.flush
72
+ GC.disable
73
+
74
+ RubyProf.resume do
75
+ __send__(@method_name)
76
+ end
77
+ ensure
78
+ GC.enable
79
+ end
80
+ end
81
+ end
82
+
83
+ data = RubyProf.stop
84
+ bench = data.threads.values.inject(0) do |total, method_infos|
85
+ top = method_infos.max
86
+ total += top.total_time
87
+ total
88
+ end
89
+
90
+ puts "\n #{measure_mode_name(measure_mode)}: #{format_profile_total(bench, measure_mode)}\n"
91
+
92
+ data
93
+ end
94
+
95
+ def format_profile_total(total, measure_mode)
96
+ case measure_mode
97
+ when RubyProf::PROCESS_TIME, RubyProf::WALL_TIME
98
+ "%.2f seconds" % total
99
+ when RubyProf::MEMORY
100
+ "%.2f kilobytes" % total
101
+ when RubyProf::ALLOCATIONS
102
+ "%d allocations" % total
103
+ else
104
+ "%.2f #{measure_mode}"
105
+ end
106
+ end
107
+
108
+ def report_profile(data, measure_mode)
109
+ PROFILE_OPTIONS[:printers].each do |printer_klass|
110
+ printer = printer_klass.new(data)
111
+
112
+ # Makes sure the output directory exits
113
+ FileUtils.mkdir_p(output_dir)
114
+
115
+ # Open the file
116
+ file_name = report_filename(printer, measure_mode)
117
+
118
+ File.open(file_name, 'wb') do |file|
119
+ printer.print(file, PROFILE_OPTIONS)
120
+ end
121
+ end
122
+ end
123
+
124
+ # The report filename is test_name + measure_mode + report_type
125
+ def report_filename(printer, measure_mode)
126
+ suffix =
127
+ case printer
128
+ when RubyProf::FlatPrinter; 'flat.txt'
129
+ when RubyProf::GraphPrinter; 'graph.txt'
130
+ when RubyProf::GraphHtmlPrinter; 'graph.html'
131
+ when RubyProf::CallTreePrinter; 'tree.txt'
132
+ else printer.to_s.downcase
133
+ end
134
+
135
+ "#{output_dir}/#{method_name}_#{measure_mode_name(measure_mode)}_#{suffix}"
136
+ end
137
+
138
+ def measure_mode_name(measure_mode)
139
+ case measure_mode
140
+ when RubyProf::PROCESS_TIME; 'process_time'
141
+ when RubyProf::WALL_TIME; 'wall_time'
142
+ when RubyProf::MEMORY; 'memory'
143
+ when RubyProf::ALLOCATIONS; 'allocations'
144
+ else "measure#{measure_mode}"
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,8 @@
1
+ require "ruby-prof"
2
+
3
+ at_exit {
4
+ result = RubyProf.stop
5
+ printer = RubyProf::FlatPrinter.new(result)
6
+ printer.print(STDOUT)
7
+ }
8
+ RubyProf.start
@@ -0,0 +1,24 @@
1
+ # Settings specified here will take precedence over those in config/environment.rb
2
+ # The profile environment should match the same settings
3
+ # as the production environment to give a reasonalbe
4
+ # approximation of performance. However, it should
5
+ # definitely not use the production databse!
6
+
7
+
8
+ # Cache classes - otherwise your code
9
+ # will run approximately 5 times slower and the
10
+ # profiling results will be overwhelmed by Rails
11
+ # dependency loading mechanism
12
+ config.cache_classes = true
13
+
14
+ # Don't check template timestamps - once again this
15
+ # is to avoid IO times overwhelming profile results
16
+ config.action_view.cache_template_loading = true
17
+
18
+ # This is debatable, but turn off action controller
19
+ # caching to see how long it really takes to run
20
+ # queries and render templates
21
+ config.action_controller.perform_caching = false
22
+
23
+ # Turn off most logging
24
+ config.log_level = :info
@@ -0,0 +1,9 @@
1
+ require File.dirname(__FILE__) + '../profile_test_helper'
2
+
3
+ class ExampleTest < Test::Unit::TestCase
4
+ include RubyProf::Test
5
+
6
+ def test_stuff
7
+ puts "Test method"
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ # Load profile environment
2
+ env = ENV["RAILS_ENV"] = "profile"
3
+ require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
4
+
5
+ # Load Rails testing infrastructure
6
+ require 'test_help'
7
+
8
+ # Now we can load test_helper since we've already loaded the
9
+ # profile RAILS environment.
10
+ require File.expand_path(File.join(RAILS_ROOT, 'test', 'test_helper'))
11
+
12
+ # Reset the current environment back to profile
13
+ # since test_helper reset it to test
14
+ ENV["RAILS_ENV"] = env
15
+
16
+ # Now load ruby-prof and away we go
17
+ require 'ruby-prof'
18
+
19
+ # Setup output directory to Rails tmp directory
20
+ RubyProf::Test::PROFILE_OPTIONS[:output_dir] =
21
+ File.expand_path(File.join(RAILS_ROOT, 'tmp', 'profile'))
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'test/unit'
4
+ require 'ruby-prof'
5
+
6
+ # Test data
7
+ # A B C
8
+ # | | |
9
+ # Z A A
10
+ # | |
11
+ # Z Z
12
+
13
+ class AggClass
14
+ def z
15
+ sleep 1
16
+ end
17
+
18
+ def a
19
+ z
20
+ end
21
+
22
+ def b
23
+ a
24
+ end
25
+
26
+ def c
27
+ a
28
+ end
29
+ end
30
+
31
+ class AggregateTest < Test::Unit::TestCase
32
+ def setup
33
+ # Need to use wall time for this test due to the sleep calls
34
+ RubyProf::measure_mode = RubyProf::WALL_TIME
35
+ end
36
+
37
+ def test_all_call_infos_are_minimal_as_there_is_no_recursion
38
+ c1 = AggClass.new
39
+ result = RubyProf.profile do
40
+ c1.a
41
+ c1.b
42
+ c1.c
43
+ end
44
+ methods = result.threads.values.first.sort.reverse
45
+ methods.each do |m|
46
+ m.call_infos.each do |ci|
47
+ assert ci.minimal?, "#{ci.call_sequence} should be minimal in the call tree"
48
+ end
49
+ end
50
+ end
51
+
52
+ def test_call_infos
53
+ c1 = AggClass.new
54
+ result = RubyProf.profile do
55
+ c1.a
56
+ c1.b
57
+ c1.c
58
+ end
59
+
60
+ methods = result.threads.values.first.sort.reverse
61
+ method = methods.find {|meth| meth.full_name == 'AggClass#z'}
62
+
63
+ # Check AggClass#z
64
+ assert_equal('AggClass#z', method.full_name)
65
+ assert_equal(3, method.called)
66
+ assert_in_delta(3, method.total_time, 0.01)
67
+ assert_in_delta(0, method.wait_time, 0.01)
68
+ assert_in_delta(0, method.self_time, 0.01)
69
+ assert_in_delta(3, method.children_time, 0.01)
70
+ assert_equal(3, method.call_infos.length)
71
+
72
+ call_info = method.call_infos[0]
73
+ assert_equal('AggregateTest#test_call_infos->AggClass#a->AggClass#z', call_info.call_sequence)
74
+ assert_equal(1, call_info.children.length)
75
+
76
+ call_info = method.call_infos[1]
77
+ assert_equal('AggregateTest#test_call_infos->AggClass#b->AggClass#a->AggClass#z', call_info.call_sequence)
78
+ assert_equal(1, call_info.children.length)
79
+
80
+ call_info = method.call_infos[2]
81
+ assert_equal('AggregateTest#test_call_infos->AggClass#c->AggClass#a->AggClass#z', call_info.call_sequence)
82
+ assert_equal(1, call_info.children.length)
83
+ end
84
+
85
+ def test_aggregates_parents
86
+ c1 = AggClass.new
87
+ result = RubyProf.profile do
88
+ c1.a
89
+ c1.b
90
+ c1.c
91
+ end
92
+
93
+ methods = result.threads.values.first.sort.reverse
94
+ method = methods.find {|meth| meth.full_name == 'AggClass#z'}
95
+
96
+ # Check AggClass#z
97
+ assert_equal('AggClass#z', method.full_name)
98
+
99
+ call_infos = method.aggregate_parents
100
+ assert_equal(1, call_infos.length)
101
+
102
+ call_info = call_infos.first
103
+ assert_equal('AggClass#a', call_info.parent.target.full_name)
104
+ assert_in_delta(3, call_info.total_time, 0.05)
105
+ assert_in_delta(0, call_info.wait_time, 0.01)
106
+ assert_in_delta(0, call_info.self_time, 0.05)
107
+ assert_in_delta(3, call_info.children_time, 0.05)
108
+ assert_equal(3, call_info.called)
109
+ end
110
+
111
+ def test_aggregates_children
112
+ c1 = AggClass.new
113
+ result = RubyProf.profile do
114
+ c1.a
115
+ c1.b
116
+ c1.c
117
+ end
118
+
119
+ methods = result.threads.values.first.sort.reverse
120
+ method = methods.find {|meth| meth.full_name == 'AggClass#a'}
121
+
122
+ # Check AggClass#a
123
+ assert_equal('AggClass#a', method.full_name)
124
+
125
+ call_infos = method.aggregate_children
126
+ assert_equal(1, call_infos.length)
127
+
128
+ call_info = call_infos.first
129
+ assert_equal('AggClass#z', call_info.target.full_name)
130
+ assert_in_delta(3, call_info.total_time, 0.05)
131
+ assert_in_delta(0, call_info.wait_time, 0.01)
132
+ assert_in_delta(0, call_info.self_time, 0.05)
133
+ assert_in_delta(3, call_info.children_time, 0.05)
134
+ assert_equal(3, call_info.called)
135
+ end
136
+ end