ruby-prof 0.8.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. data/CHANGES +23 -13
  2. data/{README → README.rdoc} +118 -111
  3. data/Rakefile +14 -26
  4. data/bin/ruby-prof +16 -4
  5. data/examples/empty.png +0 -0
  6. data/examples/graph.dot +106 -0
  7. data/examples/graph.png +0 -0
  8. data/examples/minus.png +0 -0
  9. data/examples/multi.flat.txt +23 -0
  10. data/examples/multi.graph.html +906 -0
  11. data/examples/multi.grind.dat +194 -0
  12. data/examples/multi.stack.html +573 -0
  13. data/examples/plus.png +0 -0
  14. data/examples/stack.html +573 -0
  15. data/ext/ruby_prof/extconf.rb +0 -1
  16. data/ext/ruby_prof/measure_allocations.h +6 -6
  17. data/ext/ruby_prof/measure_cpu_time.h +5 -5
  18. data/ext/ruby_prof/measure_gc_runs.h +1 -1
  19. data/ext/ruby_prof/measure_gc_time.h +1 -1
  20. data/ext/ruby_prof/measure_memory.h +4 -4
  21. data/ext/ruby_prof/measure_process_time.h +1 -1
  22. data/ext/ruby_prof/measure_wall_time.h +1 -1
  23. data/ext/ruby_prof/ruby_prof.c +240 -167
  24. data/ext/ruby_prof/ruby_prof.h +12 -10
  25. data/ext/ruby_prof/version.h +3 -3
  26. data/lib/ruby-prof.rb +7 -1
  27. data/lib/ruby-prof/abstract_printer.rb +5 -5
  28. data/lib/ruby-prof/aggregate_call_info.rb +9 -3
  29. data/lib/ruby-prof/call_info.rb +66 -1
  30. data/lib/ruby-prof/call_stack_printer.rb +751 -0
  31. data/lib/ruby-prof/call_tree_printer.rb +2 -2
  32. data/lib/ruby-prof/dot_printer.rb +151 -0
  33. data/lib/ruby-prof/empty.png +0 -0
  34. data/lib/ruby-prof/flat_printer.rb +16 -16
  35. data/lib/ruby-prof/flat_printer_with_line_numbers.rb +13 -13
  36. data/lib/ruby-prof/graph_html_printer.rb +58 -36
  37. data/lib/ruby-prof/graph_printer.rb +30 -30
  38. data/lib/ruby-prof/method_info.rb +24 -4
  39. data/lib/ruby-prof/minus.png +0 -0
  40. data/lib/ruby-prof/multi_printer.rb +54 -0
  41. data/lib/ruby-prof/plus.png +0 -0
  42. data/lib/ruby-prof/rack.rb +28 -0
  43. data/lib/ruby-prof/result.rb +70 -0
  44. data/lib/ruby-prof/symbol_to_proc.rb +1 -1
  45. data/lib/ruby-prof/task.rb +19 -19
  46. data/lib/ruby-prof/test.rb +3 -3
  47. data/lib/ruby_prof.so +0 -0
  48. data/rails/environment/profile.rb +2 -2
  49. data/rails/example/example_test.rb +2 -2
  50. data/rails/profile_test_helper.rb +1 -1
  51. data/test/aggregate_test.rb +21 -6
  52. data/test/basic_test.rb +3 -3
  53. data/test/duplicate_names_test.rb +2 -2
  54. data/test/enumerable_test.rb +2 -2
  55. data/test/exceptions_test.rb +2 -2
  56. data/test/exclude_threads_test.rb +45 -45
  57. data/test/exec_test.rb +2 -2
  58. data/test/line_number_test.rb +11 -11
  59. data/test/measurement_test.rb +4 -3
  60. data/test/method_elimination_test.rb +74 -0
  61. data/test/module_test.rb +7 -7
  62. data/test/multi_printer_test.rb +81 -0
  63. data/test/no_method_class_test.rb +2 -2
  64. data/test/prime.rb +7 -10
  65. data/test/printers_test.rb +57 -47
  66. data/test/recursive_test.rb +23 -62
  67. data/test/singleton_test.rb +3 -2
  68. data/test/stack_printer_test.rb +74 -0
  69. data/test/stack_test.rb +1 -1
  70. data/test/start_stop_test.rb +2 -2
  71. data/test/test_suite.rb +9 -0
  72. data/test/thread_test.rb +17 -17
  73. data/test/unique_call_path_test.rb +4 -4
  74. metadata +29 -8
@@ -1,7 +1,7 @@
1
1
  require 'ruby-prof/abstract_printer'
2
2
 
3
3
  module RubyProf
4
- # Generates graph[link:files/examples/graph_txt.html] profile reports as text.
4
+ # Generates graph[link:files/examples/graph_txt.html] profile reports as text.
5
5
  # To use the graph printer:
6
6
  #
7
7
  # result = RubyProf.profile do
@@ -17,8 +17,8 @@ module RubyProf
17
17
  PERCENTAGE_WIDTH = 8
18
18
  TIME_WIDTH = 10
19
19
  CALL_WIDTH = 17
20
-
21
- # Create a GraphPrinter. Result is a RubyProf::Result
20
+
21
+ # Create a GraphPrinter. Result is a RubyProf::Result
22
22
  # object generated from a profiling run.
23
23
  def initialize(result)
24
24
  super(result)
@@ -28,22 +28,22 @@ module RubyProf
28
28
 
29
29
  def calculate_thread_times
30
30
  # Cache thread times since this is an expensive
31
- # operation with the required sorting
31
+ # operation with the required sorting
32
32
  @result.threads.each do |thread_id, methods|
33
33
  top = methods.max
34
-
34
+
35
35
  thread_time = [top.total_time, 0.01].max
36
-
37
- @thread_times[thread_id] = thread_time
36
+
37
+ @thread_times[thread_id] = thread_time
38
38
  end
39
39
  end
40
-
40
+
41
41
  # Print a graph report to the provided output.
42
- #
43
- # output - Any IO oject, including STDOUT or a file.
42
+ #
43
+ # output - Any IO oject, including STDOUT or a file.
44
44
  # The default value is STDOUT.
45
- #
46
- # options - Hash of print options. See #setup_options
45
+ #
46
+ # options - Hash of print options. See #setup_options
47
47
  # for more information.
48
48
  #
49
49
  def print(output = STDOUT, options = {})
@@ -52,7 +52,7 @@ module RubyProf
52
52
  print_threads
53
53
  end
54
54
 
55
- private
55
+ private
56
56
  def print_threads
57
57
  # sort assumes that spawned threads have higher object_ids
58
58
  @result.threads.sort.each do |thread_id, methods|
@@ -60,30 +60,30 @@ module RubyProf
60
60
  @output << "\n" * 2
61
61
  end
62
62
  end
63
-
63
+
64
64
  def print_methods(thread_id, methods)
65
65
  # Sort methods from longest to shortest total time
66
66
  methods = methods.sort
67
-
67
+
68
68
  toplevel = methods.last
69
69
  total_time = toplevel.total_time
70
70
  if total_time == 0
71
71
  total_time = 0.01
72
72
  end
73
-
73
+
74
74
  print_heading(thread_id)
75
-
75
+
76
76
  # Print each method in total time order
77
77
  methods.reverse_each do |method|
78
78
  total_percentage = (method.total_time/total_time) * 100
79
79
  self_percentage = (method.self_time/total_time) * 100
80
-
80
+
81
81
  next if total_percentage < min_percent
82
-
82
+
83
83
  @output << "-" * 80 << "\n"
84
84
 
85
85
  print_parents(thread_id, method)
86
-
86
+
87
87
  # 1 is for % sign
88
88
  @output << sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", total_percentage)
89
89
  @output << sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", self_percentage)
@@ -95,18 +95,18 @@ module RubyProf
95
95
  @output << sprintf(" %s", method_name(method))
96
96
  if print_file
97
97
  @output << sprintf(" %s:%s", method.source_file, method.line)
98
- end
98
+ end
99
99
  @output << "\n"
100
-
100
+
101
101
  print_children(method)
102
102
  end
103
103
  end
104
-
104
+
105
105
  def print_heading(thread_id)
106
106
  @output << "Thread ID: #{thread_id}\n"
107
107
  @output << "Total Time: #{@thread_times[thread_id]}\n"
108
108
  @output << "\n"
109
-
109
+
110
110
  # 1 is for % sign
111
111
  @output << sprintf("%#{PERCENTAGE_WIDTH}s", "%total")
112
112
  @output << sprintf("%#{PERCENTAGE_WIDTH}s", "%self")
@@ -118,7 +118,7 @@ module RubyProf
118
118
  @output << " Name"
119
119
  @output << "\n"
120
120
  end
121
-
121
+
122
122
  def print_parents(thread_id, method)
123
123
  method.aggregate_parents.sort_by(&:total_time).each do |caller|
124
124
  next unless caller.parent
@@ -127,20 +127,20 @@ module RubyProf
127
127
  @output << sprintf("%#{TIME_WIDTH}.2f", caller.self_time)
128
128
  @output << sprintf("%#{TIME_WIDTH}.2f", caller.wait_time)
129
129
  @output << sprintf("%#{TIME_WIDTH}.2f", caller.children_time)
130
-
130
+
131
131
  call_called = "#{caller.called}/#{method.called}"
132
132
  @output << sprintf("%#{CALL_WIDTH}s", call_called)
133
133
  @output << sprintf(" %s", caller.parent.target.full_name)
134
134
  @output << "\n"
135
135
  end
136
136
  end
137
-
137
+
138
138
  def print_children(method)
139
139
  method.aggregate_children.sort_by(&:total_time).reverse.each do |child|
140
140
  # Get children method
141
-
141
+
142
142
  @output << " " * 2 * PERCENTAGE_WIDTH
143
-
143
+
144
144
  @output << sprintf("%#{TIME_WIDTH}.2f", child.total_time)
145
145
  @output << sprintf("%#{TIME_WIDTH}.2f", child.self_time)
146
146
  @output << sprintf("%#{TIME_WIDTH}.2f", child.wait_time)
@@ -153,5 +153,5 @@ module RubyProf
153
153
  end
154
154
  end
155
155
  end
156
- end
156
+ end
157
157
 
@@ -27,11 +27,12 @@ module RubyProf
27
27
  def total_time
28
28
  @total_time ||= begin
29
29
  call_infos.inject(0) do |sum, call_info|
30
- sum += call_info.total_time
30
+ sum += call_info.total_time if call_info.minimal?
31
+ sum
31
32
  end
32
33
  end
33
34
  end
34
-
35
+
35
36
  def self_time
36
37
  @self_time ||= begin
37
38
  call_infos.inject(0) do |sum, call_info|
@@ -51,7 +52,8 @@ module RubyProf
51
52
  def children_time
52
53
  @children_time ||= begin
53
54
  call_infos.inject(0) do |sum, call_info|
54
- sum += call_info.children_time
55
+ sum += call_info.children_time if call_info.minimal?
56
+ sum
55
57
  end
56
58
  end
57
59
  end
@@ -107,5 +109,23 @@ module RubyProf
107
109
  def to_s
108
110
  full_name
109
111
  end
112
+
113
+ def dump
114
+ res = ""
115
+ res << "MINFO: #{klass_name}##{method_name} total_time: #{total_time} (#{full_name})\n"
116
+ call_infos.each do |ci|
117
+ pinfo = ci.root? ? "TOPLEVEL" : (p=ci.parent.target; "#{p.klass_name}##{p.method_name} (#{ci.parent.object_id}) (#{p.full_name})")
118
+ res << "CINFO[#{ci.object_id}] called #{ci.called} times from #{pinfo}\n"
119
+ end
120
+ res
121
+ end
122
+
123
+ # remove method from the call graph. should not be called directly.
124
+ def eliminate!
125
+ # $stderr.puts "eliminating #{self}"
126
+ call_infos.each{ |call_info| call_info.eliminate! }
127
+ call_infos.clear
128
+ end
129
+
110
130
  end
111
- end
131
+ end
Binary file
@@ -0,0 +1,54 @@
1
+ module RubyProf
2
+ # Helper class to simplify printing profiles of several types from
3
+ # one profiling run. Currently prints a flat profile, a callgrind
4
+ # profile, a call stack profile and a graph profile.
5
+ class MultiPrinter
6
+ def initialize(result)
7
+ @stack_printer = CallStackPrinter.new(result)
8
+ @graph_printer = GraphHtmlPrinter.new(result)
9
+ @tree_printer = CallTreePrinter.new(result)
10
+ @flat_printer = FlatPrinter.new(result)
11
+ end
12
+
13
+ # create profile files under options[:path] or the current
14
+ # directory. options[:profile] is used as the base name for the
15
+ # pofile file, defaults to "profile".
16
+ def print(options)
17
+ @profile = options.delete(:profile) || "profile"
18
+ @directory = options.delete(:path) || File.expand_path(".")
19
+ File.open(stack_profile, "w") do |f|
20
+ @stack_printer.print(f, options.merge(:graph => "#{@profile}.graph.html"))
21
+ end
22
+ File.open(graph_profile, "w") do |f|
23
+ @graph_printer.print(f, options)
24
+ end
25
+ File.open(tree_profile, "w") do |f|
26
+ @tree_printer.print(f, options)
27
+ end
28
+ File.open(flat_profile, "w") do |f|
29
+ @flat_printer.print(f, options)
30
+ end
31
+ end
32
+
33
+ # the name of the call stack profile file
34
+ def stack_profile
35
+ "#{@directory}/#{@profile}.stack.html"
36
+ end
37
+
38
+ # the name of the graph profile file
39
+ def graph_profile
40
+ "#{@directory}/#{@profile}.graph.html"
41
+ end
42
+
43
+ # the name of the callgrind profile file
44
+ def tree_profile
45
+ "#{@directory}/#{@profile}.grind.dat"
46
+ end
47
+
48
+ # the name of the flat profile file
49
+ def flat_profile
50
+ "#{@directory}/#{@profile}.flat.txt"
51
+ end
52
+
53
+ end
54
+ end
Binary file
@@ -0,0 +1,28 @@
1
+ module Rack
2
+ class RubyProf
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def call(env)
8
+ ::RubyProf.start
9
+ result = @app.call(env)
10
+ data = ::RubyProf.stop
11
+
12
+ print(data)
13
+ result
14
+ end
15
+
16
+ def print
17
+ printers = {::RubyProf::FlatPrinter => 'c:/temp/profile.txt',
18
+ ::RubyProf::GraphHtmlPrinter => 'c:/temp/profile.html'}
19
+
20
+ printers.each do |printer_klass, file_name|
21
+ printer = printer_klass.new(result)
22
+ ::File.open(file_name, 'wb') do |file|
23
+ printer.print(file, :min_percent => 0.00000001 )
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -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
@@ -5,4 +5,4 @@ unless (:a.respond_to?(:to_proc))
5
5
  end
6
6
  end
7
7
  end
8
-
8
+
@@ -15,7 +15,7 @@ module RubyProf
15
15
  #
16
16
  # ruby-prof specific options include:
17
17
  #
18
- # output_dir - For each file specified an output
18
+ # output_dir - For each file specified an output
19
19
  # file with profile information will be
20
20
  # written to the output directory.
21
21
  # By default, the output directory is
@@ -29,9 +29,9 @@ module RubyProf
29
29
  # will not be written out.
30
30
  #
31
31
  # Example:
32
- #
32
+ #
33
33
  # require 'ruby-prof/task'
34
- #
34
+ #
35
35
  # RubyProf::ProfileTask.new do |t|
36
36
  # t.test_files = FileList['test/test*.rb']
37
37
  # t.output_dir = "c:/temp"
@@ -55,37 +55,37 @@ module RubyProf
55
55
  # rake profile TEST=just_one_file.rb # run just one test file.
56
56
  # rake profile TESTOPTS="-v" # run in verbose mode
57
57
  # rake profile TESTOPTS="--runner=fox" # use the fox test runner
58
-
58
+
59
59
  class ProfileTask < Rake::TestTask
60
- attr_accessor :output_dir
61
- attr_accessor :min_percent
60
+ attr_accessor :output_dir
61
+ attr_accessor :min_percent
62
62
  attr_accessor :printer
63
-
63
+
64
64
  def initialize(name = :profile)
65
65
  super(name)
66
66
  end
67
-
67
+
68
68
  # Create the tasks defined by this task lib.
69
69
  def define
70
70
  lib_path = @libs.join(File::PATH_SEPARATOR)
71
71
  desc "Profile" + (@name==:profile ? "" : " for #{@name}")
72
-
72
+
73
73
  task @name do
74
74
  create_output_directory
75
-
75
+
76
76
  @ruby_opts.unshift( "-I#{lib_path}" )
77
77
  @ruby_opts.unshift( "-w" ) if @warning
78
78
  @ruby_opts.push("-S ruby-prof")
79
79
  @ruby_opts.push("--printer #{@printer}")
80
80
  @ruby_opts.push("--min_percent #{@min_percent}")
81
81
 
82
- file_list.each do |file_path|
82
+ file_list.each do |file_path|
83
83
  run_script(file_path)
84
84
  end
85
85
  end
86
86
  self
87
87
  end
88
-
88
+
89
89
  # Run script
90
90
  def run_script(script_path)
91
91
  run_code = ''
@@ -99,14 +99,14 @@ module RubyProf
99
99
  else
100
100
  file_name += ".txt"
101
101
  end
102
-
102
+
103
103
  output_file_path = File.join(output_directory, file_name)
104
-
105
- command_line = @ruby_opts.join(" ") +
104
+
105
+ command_line = @ruby_opts.join(" ") +
106
106
  " --file=" + output_file_path +
107
107
  " " + script_path
108
108
 
109
- puts "ruby " + command_line
109
+ puts "ruby " + command_line
110
110
  # We have to catch the exeption to continue on. However,
111
111
  # the error message will have been output to STDERR
112
112
  # already by the time we get here so we don't have to
@@ -125,7 +125,7 @@ module RubyProf
125
125
  def output_directory
126
126
  File.expand_path(@output_dir)
127
127
  end
128
-
128
+
129
129
  def create_output_directory
130
130
  if not File.exist?(output_directory)
131
131
  Dir.mkdir(output_directory)
@@ -138,9 +138,9 @@ module RubyProf
138
138
  FileUtils.rm(files)
139
139
  end
140
140
  end
141
-
141
+
142
142
  def option_list # :nodoc:
143
143
  ENV['OPTIONS'] || @options.join(" ") || ""
144
144
  end
145
145
  end
146
- end
146
+ end