ruby-prof 0.15.5 → 0.15.6

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.
@@ -26,7 +26,7 @@ typedef struct prof_call_info_t
26
26
  } prof_call_info_t;
27
27
 
28
28
  /* Array of call_info objects */
29
- typedef struct prof_call_infos_t
29
+ typedef struct prof_call_infos_t
30
30
  {
31
31
  prof_call_info_t **start;
32
32
  prof_call_info_t **end;
@@ -2,7 +2,38 @@
2
2
 
3
3
  module RubyProf
4
4
  class CallInfo
5
- attr_accessor :recursive
5
+ # part of this class is defined in C code.
6
+ # it provides the following attributes pertaining to tree structure:
7
+ # depth: tree level (0 == root)
8
+ # parent: parent call info (can be nil)
9
+ # children: array of call info children (can be empty)
10
+ # target: method info (containing an array of call infos)
11
+
12
+ attr_reader :recursive
13
+
14
+ def non_recursive?
15
+ @non_recursive
16
+ end
17
+
18
+ def detect_recursion(visited_methods = Hash.new(0))
19
+ @recursive = (visited_methods[target] += 1) > 1
20
+ @non_recursive = true
21
+ children.each do |child|
22
+ @non_recursive = false if child.detect_recursion(visited_methods)
23
+ end
24
+ visited_methods.delete(target) if (visited_methods[target] -= 1) == 0
25
+ return !@non_recursive
26
+ end
27
+
28
+ def recalc_recursion(visited_methods = Hash.new(0))
29
+ return if @non_recursive
30
+ target.clear_cached_values_which_depend_on_recursiveness
31
+ @recursive = (visited_methods[target] += 1) > 1
32
+ children.each do |child|
33
+ child.recalc_recursion(visited_methods)
34
+ end
35
+ visited_methods.delete(target) if (visited_methods[target] -= 1) == 0
36
+ end
6
37
 
7
38
  def children_time
8
39
  children.inject(0) do |sum, call_info|
@@ -33,8 +64,29 @@ module RubyProf
33
64
  self.parent.nil?
34
65
  end
35
66
 
67
+ def descendent_of(other)
68
+ p = self.parent
69
+ while p && p != other && p.depth > other.depth
70
+ p = p.parent
71
+ end
72
+ p == other
73
+ end
74
+
75
+ def self.roots_of(call_infos)
76
+ roots = []
77
+ sorted = call_infos.sort_by(&:depth).reverse
78
+ while call_info = sorted.shift
79
+ roots << call_info unless sorted.any?{|p| call_info.descendent_of(p)}
80
+ end
81
+ roots
82
+ end
83
+
36
84
  def to_s
37
- "#{self.target.full_name} (c: #{self.called}, tt: #{self.total_time}, st: #{self.self_time}, ct: #{self.children_time})"
85
+ "#{target.full_name} (c: #{called}, tt: #{total_time}, st: #{self_time}, ct: #{children_time})"
86
+ end
87
+
88
+ def inspect
89
+ super + "(#{target.full_name}, d: #{depth}, c: #{called}, tt: #{total_time}, st: #{self_time}, ct: #{children_time})"
38
90
  end
39
91
 
40
92
  # eliminate call info from the call tree.
@@ -1,11 +1,10 @@
1
- # The call info visitor class does a depth-first traversal
2
- # across a thread's call stack. At each call_info node,
3
- # the visitor executes the block provided in the
4
- # #visit method. The block is passed two parameters, the
5
- # event and the call_info instance. Event will be
1
+ # The call info visitor class does a depth-first traversal across a
2
+ # list of method infos. At each call_info node, the visitor executes
3
+ # the block provided in the #visit method. The block is passed two
4
+ # parameters, the event and the call_info instance. Event will be
6
5
  # either :enter or :exit.
7
6
  #
8
- # visitor = RubyProf::CallInfoVisitor.new(result.threads.first)
7
+ # visitor = RubyProf::CallInfoVisitor.new(result.threads.first.top_call_infos)
9
8
  #
10
9
  # method_names = Array.new
11
10
  #
@@ -18,15 +17,13 @@
18
17
  module RubyProf
19
18
  class CallInfoVisitor
20
19
 
21
- def initialize(thread)
22
- @thread = thread
20
+ def initialize(call_infos)
21
+ @call_infos = CallInfo.roots_of(call_infos)
23
22
  end
24
23
 
25
24
  def visit(&block)
26
- @thread.top_methods.each do |method_info|
27
- method_info.call_infos.each do |call_info|
28
- visit_call_info(call_info, &block)
29
- end
25
+ @call_infos.each do |call_info|
26
+ visit_call_info(call_info, &block)
30
27
  end
31
28
  end
32
29
 
@@ -39,4 +36,5 @@ module RubyProf
39
36
  yield call_info, :exit
40
37
  end
41
38
  end
39
+
42
40
  end
@@ -18,6 +18,18 @@ module RubyProf
18
18
  end
19
19
  end
20
20
 
21
+ def detect_recursion
22
+ call_infos.each(&:detect_recursion)
23
+ end
24
+
25
+ def recalc_recursion
26
+ call_infos.each(&:recalc_recursion)
27
+ end
28
+
29
+ def clear_cached_values_which_depend_on_recursiveness
30
+ @total_time = @self_time = @wait_time = @children_time = nil
31
+ end
32
+
21
33
  def called
22
34
  @called ||= begin
23
35
  call_infos.inject(0) do |sum, call_info|
@@ -38,7 +50,7 @@ module RubyProf
38
50
  def self_time
39
51
  @self_time ||= begin
40
52
  call_infos.inject(0) do |sum, call_info|
41
- sum += call_info.self_time unless call_info.recursive
53
+ sum += call_info.self_time unless call_info.recursive
42
54
  sum
43
55
  end
44
56
  end
@@ -56,16 +68,14 @@ module RubyProf
56
68
  def children_time
57
69
  @children_time ||= begin
58
70
  call_infos.inject(0) do |sum, call_info|
59
- sum += call_info.children_time unless call_info.recursive
71
+ sum += call_info.children_time unless call_info.recursive
60
72
  sum
61
73
  end
62
74
  end
63
75
  end
64
76
 
65
77
  def min_depth
66
- @min_depth ||= call_infos.map do |call_info|
67
- call_info.depth
68
- end.min
78
+ @min_depth ||= call_infos.map(&:depth).min
69
79
  end
70
80
 
71
81
  def root?
@@ -77,25 +87,30 @@ module RubyProf
77
87
  end
78
88
 
79
89
  def recursive?
80
- call_infos.detect do |call_info|
81
- call_info.recursive
82
- end
90
+ call_infos.detect(&:recursive)
91
+ end
92
+
93
+ def non_recursive?
94
+ non_recursive == 1
95
+ end
96
+
97
+ def non_recursive
98
+ @non_recursive ||= call_infos.all?(&:non_recursive?) ? 1 : 0
83
99
  end
84
100
 
85
101
  def children
86
- @children ||= begin
87
- call_infos.map do |call_info|
88
- call_info.children
89
- end.flatten
90
- end
102
+ @children ||= call_infos.map(&:children).flatten
103
+ end
104
+
105
+ def parents
106
+ @parents ||= call_infos.map(&:parent)
91
107
  end
92
108
 
93
109
  def aggregate_parents
94
110
  # Group call info's based on their parents
95
- groups = self.call_infos.inject(Hash.new) do |hash, call_info|
111
+ groups = self.call_infos.each_with_object({}) do |call_info, hash|
96
112
  key = call_info.parent ? call_info.parent.target : self
97
113
  (hash[key] ||= []) << call_info
98
- hash
99
114
  end
100
115
 
101
116
  groups.map do |key, value|
@@ -105,10 +120,9 @@ module RubyProf
105
120
 
106
121
  def aggregate_children
107
122
  # Group call info's based on their targets
108
- groups = self.children.inject(Hash.new) do |hash, call_info|
123
+ groups = self.children.each_with_object({}) do |call_info, hash|
109
124
  key = call_info.target
110
125
  (hash[key] ||= []) << call_info
111
- hash
112
126
  end
113
127
 
114
128
  groups.map do |key, value|
@@ -1,7 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module RubyProf
4
- # Prints out the call graph based on CallInfo instances. This
4
+ # Prints out the call graph based on CallInfo instances. This
5
5
  # is mainly for debugging purposes as it provides access into
6
6
  # into RubyProf's internals.
7
7
 
@@ -19,7 +19,7 @@ module RubyProf
19
19
  end
20
20
 
21
21
  def print_methods(thread)
22
- visitor = CallInfoVisitor.new(thread)
22
+ visitor = CallInfoVisitor.new(thread.top_call_infos)
23
23
 
24
24
  visitor.visit do |call_info, event|
25
25
  if event == :enter
@@ -12,39 +12,39 @@ module RubyProf
12
12
  #
13
13
  # printer = RubyProf::DotPrinter.new(result)
14
14
  # printer.print(STDOUT)
15
- #
15
+ #
16
16
  # You can use either dot viewer such as GraphViz, or the dot command line tool
17
17
  # to reformat the output into a wide variety of outputs:
18
- #
18
+ #
19
19
  # dot -Tpng graph.dot > graph.png
20
- #
21
- class DotPrinter < RubyProf::AbstractPrinter
20
+ #
21
+ class DotPrinter < RubyProf::AbstractPrinter
22
22
  CLASS_COLOR = '"#666666"'
23
23
  EDGE_COLOR = '"#666666"'
24
-
24
+
25
25
  # Creates the DotPrinter using a RubyProf::Result.
26
26
  def initialize(result)
27
27
  super(result)
28
28
  @seen_methods = Set.new
29
29
  end
30
-
30
+
31
31
  # Print a graph report to the provided output.
32
- #
32
+ #
33
33
  # output - Any IO object, including STDOUT or a file. The default value is
34
34
  # STDOUT.
35
- #
36
- # options - Hash of print options. See #setup_options
35
+ #
36
+ # options - Hash of print options. See #setup_options
37
37
  # for more information.
38
38
  #
39
39
  # When profiling results that cover a large number of method calls it
40
40
  # helps to use the :min_percent option, for example:
41
- #
41
+ #
42
42
  # DotPrinter.new(result).print(STDOUT, :min_percent=>5)
43
- #
43
+ #
44
44
  def print(output = STDOUT, options = {})
45
45
  @output = output
46
46
  setup_options(options)
47
-
47
+
48
48
  puts 'digraph "Profile" {'
49
49
  #puts "label=\"#{mode_name} >=#{min_percent}%\\nTotal: #{total_time}\";"
50
50
  puts "labelloc=t;"
@@ -53,31 +53,31 @@ module RubyProf
53
53
  puts '}'
54
54
  end
55
55
 
56
- private
57
-
56
+ private
57
+
58
58
  # Something of a hack, figure out which constant went with the
59
59
  # RubyProf.measure_mode so that we can display it. Otherwise it's easy to
60
60
  # forget what measurement was made.
61
61
  def mode_name
62
62
  RubyProf.constants.find{|c| RubyProf.const_get(c) == RubyProf.measure_mode}
63
63
  end
64
-
64
+
65
65
  def print_threads
66
66
  @result.threads.each do |thread|
67
67
  puts "subgraph \"Thread #{thread.id}\" {"
68
68
 
69
69
  print_thread(thread)
70
70
  puts "}"
71
-
71
+
72
72
  print_classes(thread)
73
73
  end
74
74
  end
75
-
75
+
76
76
  # Determines an ID to use to represent the subject in the Dot file.
77
77
  def dot_id(subject)
78
78
  subject.object_id
79
79
  end
80
-
80
+
81
81
  def print_thread(thread)
82
82
  total_time = thread.total_time
83
83
  thread.methods.sort_by(&sort_method).reverse_each do |method|
@@ -90,14 +90,14 @@ module RubyProf
90
90
  print_edges(total_time, method)
91
91
  end
92
92
  end
93
-
93
+
94
94
  def print_classes(thread)
95
95
  grouped = {}
96
96
  thread.methods.each{|m| grouped[m.klass_name] ||= []; grouped[m.klass_name] << m}
97
97
  grouped.each do |cls, methods2|
98
98
  # Filter down to just seen methods
99
99
  big_methods = methods2.select{|m| @seen_methods.include? m}
100
-
100
+
101
101
  if !big_methods.empty?
102
102
  puts "subgraph cluster_#{cls.object_id} {"
103
103
  puts "label = \"#{cls}\";"
@@ -107,26 +107,26 @@ module RubyProf
107
107
  big_methods.each do |m|
108
108
  puts "#{m.object_id};"
109
109
  end
110
- puts "}"
111
- end
110
+ puts "}"
111
+ end
112
112
  end
113
113
  end
114
-
114
+
115
115
  def print_edges(total_time, method)
116
116
  method.aggregate_children.sort_by(&:total_time).reverse.each do |child|
117
-
117
+
118
118
  target_percentage = (child.target.total_time / total_time) * 100.0
119
119
  next if target_percentage < min_percent
120
-
120
+
121
121
  # Get children method
122
122
  puts "#{dot_id(method)} -> #{dot_id(child.target)} [label=\"#{child.called}/#{child.target.called}\" fontsize=10 fontcolor=#{EDGE_COLOR}];"
123
123
  end
124
124
  end
125
-
125
+
126
126
  # Silly little helper for printing to the @output
127
127
  def puts(str)
128
128
  @output.puts(str)
129
129
  end
130
-
130
+
131
131
  end
132
132
  end
@@ -133,7 +133,7 @@ module RubyProf
133
133
  </style>
134
134
  </head>
135
135
  <body>
136
- <h1>Profile Report</h1>
136
+ <h1>Profile Report: <%= RubyProf.measure_mode_string %></h1>
137
137
  <!-- Threads Table -->
138
138
  <table>
139
139
  <tr>
@@ -173,63 +173,67 @@ module RubyProf
173
173
  <tbody>
174
174
  <% min_time = @options[:min_time] || (@options[:nonzero] ? 0.005 : nil)
175
175
  methods.sort_by(&sort_method).reverse_each do |method|
176
- total_percentage = (method.total_time/total_time) * 100
177
- next if total_percentage < min_percent
178
- next if min_time && method.total_time < min_time
179
- self_percentage = (method.self_time/total_time) * 100 %>
180
-
181
- <!-- Parents -->
182
- <% for caller in method.aggregate_parents.sort_by(&:total_time)
183
- next unless caller.parent
184
- next if min_time && caller.total_time < min_time %>
185
- <tr>
186
- <td>&nbsp;</td>
187
- <td>&nbsp;</td>
188
- <td><%= sprintf("%#{TIME_WIDTH}.2f", caller.total_time) %></td>
189
- <td><%= sprintf("%#{TIME_WIDTH}.2f", caller.self_time) %></td>
190
- <td><%= sprintf("%#{TIME_WIDTH}.2f", caller.wait_time) %></td>
191
- <td><%= sprintf("%#{TIME_WIDTH}.2f", caller.children_time) %></td>
192
- <% called = "#{caller.called}/#{method.called}" %>
193
- <td><%= sprintf("%#{CALL_WIDTH}s", called) %></td>
194
- <td class="method_name"><%= create_link(thread, total_time, caller.parent.target) %></td>
195
- <td><%= file_link(caller.parent.target.source_file, caller.line) %></td>
196
- </tr>
197
- <% end %>
198
-
199
- <tr class="method">
200
- <td><%= sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", total_percentage) %></td>
201
- <td><%= sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", self_percentage) %></td>
202
- <td><%= sprintf("%#{TIME_WIDTH}.2f", method.total_time) %></td>
203
- <td><%= sprintf("%#{TIME_WIDTH}.2f", method.self_time) %></td>
204
- <td><%= sprintf("%#{TIME_WIDTH}.2f", method.wait_time) %></td>
205
- <td><%= sprintf("%#{TIME_WIDTH}.2f", method.children_time) %></td>
206
- <td><%= sprintf("%#{CALL_WIDTH}i", method.called) %></td>
207
- <td class="method_name">
208
- <a name="<%= method_href(thread, method) %>">
209
- <%= method.recursive? ? "*" : " "%><%= h method.full_name %>
210
- </a>
211
- </td>
212
- <td><%= file_link(method.source_file, method.line) %></td>
213
- </tr>
214
-
215
- <!-- Children -->
216
- <% for callee in method.aggregate_children.sort_by(&:total_time).reverse %>
217
- <% next if min_time && callee.total_time < min_time %>
218
- <tr>
219
- <td>&nbsp;</td>
220
- <td>&nbsp;</td>
221
- <td><%= sprintf("%#{TIME_WIDTH}.2f", callee.total_time) %></td>
222
- <td><%= sprintf("%#{TIME_WIDTH}.2f", callee.self_time) %></td>
223
- <td><%= sprintf("%#{TIME_WIDTH}.2f", callee.wait_time) %></td>
224
- <td><%= sprintf("%#{TIME_WIDTH}.2f", callee.children_time) %></td>
225
- <% called = "#{callee.called}/#{callee.target.called}" %>
226
- <td><%= sprintf("%#{CALL_WIDTH}s", called) %></td>
227
- <td class="method_name"><%= create_link(thread, total_time, callee.target) %></td>
228
- <td><%= file_link(method.source_file, callee.line) %></td>
229
- </tr>
230
- <% end %>
231
- <!-- Create divider row -->
232
- <tr class="break"><td colspan="9"></td></tr>
176
+ total_percentage = (method.total_time/total_time) * 100
177
+
178
+ next if total_percentage < min_percent
179
+ next if min_time && method.total_time < min_time
180
+
181
+ self_percentage = (method.self_time/total_time) * 100 %>
182
+
183
+ <!-- Parents -->
184
+ <% for caller in method.aggregate_parents.sort_by(&:total_time)
185
+ next unless caller.parent
186
+ next if min_time && caller.total_time < min_time %>
187
+ <tr>
188
+ <td>&nbsp;</td>
189
+ <td>&nbsp;</td>
190
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", caller.total_time) %></td>
191
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", caller.self_time) %></td>
192
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", caller.wait_time) %></td>
193
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", caller.children_time) %></td>
194
+ <% called = "#{caller.called}/#{method.called}" %>
195
+ <td><%= sprintf("%#{CALL_WIDTH}s", called) %></td>
196
+ <td class="method_name"><%= create_link(thread, total_time, caller.parent.target) %></td>
197
+ <td><%= file_link(caller.parent.target.source_file, caller.line) %></td>
198
+ </tr>
199
+ <% end %>
200
+
201
+ <tr class="method">
202
+ <td><%= sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", total_percentage) %></td>
203
+ <td><%= sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", self_percentage) %></td>
204
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", method.total_time) %></td>
205
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", method.self_time) %></td>
206
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", method.wait_time) %></td>
207
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", method.children_time) %></td>
208
+ <td><%= sprintf("%#{CALL_WIDTH}i", method.called) %></td>
209
+ <td class="method_name">
210
+ <a name="<%= method_href(thread, method) %>">
211
+ <%= method.recursive? ? "*" : " "%><%= h method.full_name %>
212
+ </a>
213
+ </td>
214
+ <td><%= file_link(method.source_file, method.line) %></td>
215
+ </tr>
216
+
217
+ <!-- Children -->
218
+ <% method.recalc_recursion unless method.non_recursive? %>
219
+ <% for callee in method.aggregate_children.sort_by(&:total_time).reverse %>
220
+ <% next if min_time && callee.total_time < min_time %>
221
+ <tr>
222
+ <td>&nbsp;</td>
223
+ <td>&nbsp;</td>
224
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", callee.total_time) %></td>
225
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", callee.self_time) %></td>
226
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", callee.wait_time) %></td>
227
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", callee.children_time) %></td>
228
+ <% called = "#{callee.called}/#{callee.target.called}" %>
229
+ <td><%= sprintf("%#{CALL_WIDTH}s", called) %></td>
230
+ <td class="method_name"><%= create_link(thread, total_time, callee.target) %></td>
231
+ <td><%= file_link(method.source_file, callee.line) %></td>
232
+ </tr>
233
+ <% end %>
234
+ <!-- Create divider row -->
235
+ <tr class="break"><td colspan="9"></td></tr>
236
+ <% thread.recalc_recursion unless method.non_recursive? %>
233
237
  <% end %>
234
238
  </tbody>
235
239
  <tfoot>