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.
- checksums.yaml +4 -4
- data/CHANGES +4 -1
- data/doc/RubyProf.html +5 -6
- data/doc/RubyProf/CallInfo.html +259 -11
- data/doc/RubyProf/CallInfoPrinter.html +2 -2
- data/doc/RubyProf/CallInfoVisitor.html +7 -9
- data/doc/RubyProf/DotPrinter.html +2 -2
- data/doc/RubyProf/GraphHtmlPrinter.html +62 -58
- data/doc/RubyProf/MethodInfo.html +230 -30
- data/doc/RubyProf/Profile.html +2 -53
- data/doc/RubyProf/Thread.html +108 -4
- data/doc/created.rid +11 -11
- data/doc/js/search_index.js +1 -1
- data/doc/js/search_index.js.gz +0 -0
- data/doc/table_of_contents.html +120 -50
- data/ext/ruby_prof/rp_call_info.h +1 -1
- data/lib/ruby-prof/call_info.rb +54 -2
- data/lib/ruby-prof/call_info_visitor.rb +10 -12
- data/lib/ruby-prof/method_info.rb +31 -17
- data/lib/ruby-prof/printers/call_info_printer.rb +2 -2
- data/lib/ruby-prof/printers/dot_printer.rb +27 -27
- data/lib/ruby-prof/printers/graph_html_printer.rb +62 -58
- data/lib/ruby-prof/printers/graph_printer.rb +4 -3
- data/lib/ruby-prof/profile.rb +1 -22
- data/lib/ruby-prof/thread.rb +15 -3
- data/lib/ruby-prof/version.rb +1 -1
- data/test/call_info_visitor_test.rb +1 -1
- metadata +1 -1
data/lib/ruby-prof/call_info.rb
CHANGED
@@ -2,7 +2,38 @@
|
|
2
2
|
|
3
3
|
module RubyProf
|
4
4
|
class CallInfo
|
5
|
-
|
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
|
-
"#{
|
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
|
-
#
|
3
|
-
# the
|
4
|
-
#
|
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(
|
22
|
-
@
|
20
|
+
def initialize(call_infos)
|
21
|
+
@call_infos = CallInfo.roots_of(call_infos)
|
23
22
|
end
|
24
23
|
|
25
24
|
def visit(&block)
|
26
|
-
@
|
27
|
-
|
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
|
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
|
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
|
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
|
81
|
-
|
82
|
-
|
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 ||=
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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.
|
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.
|
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.
|
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
|
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
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
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> </td>
|
189
|
+
<td> </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> </td>
|
223
|
+
<td> </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>
|