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.
- 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>
|