ruby-prof 0.15.5 → 0.15.6

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