ruby-prof 0.8.2 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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