ruby-prof 0.16.2 → 1.1.0

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.
Files changed (203) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGES +532 -467
  3. data/LICENSE +24 -24
  4. data/README.rdoc +5 -454
  5. data/Rakefile +110 -113
  6. data/bin/ruby-prof +380 -340
  7. data/bin/ruby-prof-check-trace +45 -45
  8. data/ext/ruby_prof/extconf.rb +36 -64
  9. data/ext/ruby_prof/rp_allocation.c +279 -0
  10. data/ext/ruby_prof/rp_allocation.h +31 -0
  11. data/ext/ruby_prof/rp_call_info.c +271 -407
  12. data/ext/ruby_prof/rp_call_info.h +35 -48
  13. data/ext/ruby_prof/rp_measure_allocations.c +52 -76
  14. data/ext/ruby_prof/rp_measure_memory.c +42 -77
  15. data/ext/ruby_prof/rp_measure_process_time.c +67 -71
  16. data/ext/ruby_prof/rp_measure_wall_time.c +62 -45
  17. data/ext/ruby_prof/rp_measurement.c +230 -0
  18. data/ext/ruby_prof/rp_measurement.h +50 -0
  19. data/ext/ruby_prof/rp_method.c +630 -411
  20. data/ext/ruby_prof/rp_method.h +70 -52
  21. data/ext/ruby_prof/rp_profile.c +895 -0
  22. data/ext/ruby_prof/rp_profile.h +37 -0
  23. data/ext/ruby_prof/rp_stack.c +196 -128
  24. data/ext/ruby_prof/rp_stack.h +56 -51
  25. data/ext/ruby_prof/rp_thread.c +337 -273
  26. data/ext/ruby_prof/rp_thread.h +36 -27
  27. data/ext/ruby_prof/ruby_prof.c +48 -671
  28. data/ext/ruby_prof/ruby_prof.h +17 -56
  29. data/ext/ruby_prof/vc/ruby_prof.sln +20 -21
  30. data/ext/ruby_prof/vc/{ruby_prof_20.vcxproj → ruby_prof.vcxproj} +38 -5
  31. data/lib/ruby-prof.rb +52 -58
  32. data/lib/ruby-prof/assets/call_stack_printer.html.erb +713 -0
  33. data/lib/ruby-prof/assets/call_stack_printer.png +0 -0
  34. data/lib/ruby-prof/assets/graph_printer.html.erb +356 -0
  35. data/lib/ruby-prof/call_info.rb +57 -126
  36. data/lib/ruby-prof/call_info_visitor.rb +38 -40
  37. data/lib/ruby-prof/compatibility.rb +109 -178
  38. data/lib/ruby-prof/exclude_common_methods.rb +198 -0
  39. data/lib/ruby-prof/measurement.rb +14 -0
  40. data/lib/ruby-prof/method_info.rb +90 -129
  41. data/lib/ruby-prof/printers/abstract_printer.rb +127 -85
  42. data/lib/ruby-prof/printers/call_info_printer.rb +51 -41
  43. data/lib/ruby-prof/printers/call_stack_printer.rb +182 -260
  44. data/lib/ruby-prof/printers/call_tree_printer.rb +151 -130
  45. data/lib/ruby-prof/printers/dot_printer.rb +132 -132
  46. data/lib/ruby-prof/printers/flat_printer.rb +52 -70
  47. data/lib/ruby-prof/printers/graph_html_printer.rb +63 -244
  48. data/lib/ruby-prof/printers/graph_printer.rb +114 -116
  49. data/lib/ruby-prof/printers/multi_printer.rb +127 -58
  50. data/lib/ruby-prof/profile.rb +33 -55
  51. data/lib/ruby-prof/rack.rb +171 -95
  52. data/lib/ruby-prof/task.rb +147 -147
  53. data/lib/ruby-prof/thread.rb +35 -41
  54. data/lib/ruby-prof/version.rb +3 -3
  55. data/lib/unprof.rb +10 -10
  56. data/ruby-prof.gemspec +58 -57
  57. data/test/abstract_printer_test.rb +26 -0
  58. data/test/alias_test.rb +129 -0
  59. data/test/basic_test.rb +129 -128
  60. data/test/call_info_visitor_test.rb +31 -31
  61. data/test/duplicate_names_test.rb +32 -32
  62. data/test/dynamic_method_test.rb +53 -55
  63. data/test/enumerable_test.rb +21 -21
  64. data/test/exceptions_test.rb +24 -16
  65. data/test/exclude_methods_test.rb +146 -0
  66. data/test/exclude_threads_test.rb +53 -53
  67. data/test/fiber_test.rb +73 -79
  68. data/test/gc_test.rb +96 -0
  69. data/test/line_number_test.rb +161 -71
  70. data/test/marshal_test.rb +119 -0
  71. data/test/measure_allocations.rb +30 -0
  72. data/test/measure_allocations_test.rb +385 -26
  73. data/test/measure_allocations_trace_test.rb +385 -0
  74. data/test/measure_memory_trace_test.rb +756 -0
  75. data/test/measure_process_time_test.rb +849 -63
  76. data/test/measure_times.rb +54 -0
  77. data/test/measure_wall_time_test.rb +459 -255
  78. data/test/multi_printer_test.rb +71 -83
  79. data/test/no_method_class_test.rb +15 -15
  80. data/test/parser_timings.rb +24 -0
  81. data/test/pause_resume_test.rb +166 -166
  82. data/test/prime.rb +56 -54
  83. data/test/printer_call_stack_test.rb +28 -0
  84. data/test/printer_call_tree_test.rb +31 -0
  85. data/test/printer_flat_test.rb +68 -0
  86. data/test/printer_graph_html_test.rb +60 -0
  87. data/test/printer_graph_test.rb +41 -0
  88. data/test/printers_test.rb +141 -255
  89. data/test/printing_recursive_graph_test.rb +81 -127
  90. data/test/rack_test.rb +157 -93
  91. data/test/recursive_test.rb +210 -215
  92. data/test/singleton_test.rb +38 -38
  93. data/test/stack_printer_test.rb +64 -78
  94. data/test/start_stop_test.rb +109 -112
  95. data/test/test_helper.rb +24 -264
  96. data/test/thread_test.rb +144 -187
  97. data/test/unique_call_path_test.rb +190 -202
  98. data/test/yarv_test.rb +56 -55
  99. metadata +34 -114
  100. data/doc/LICENSE.html +0 -114
  101. data/doc/README_rdoc.html +0 -603
  102. data/doc/Rack.html +0 -95
  103. data/doc/Rack/RubyProf.html +0 -226
  104. data/doc/RubyProf.html +0 -962
  105. data/doc/RubyProf/AbstractPrinter.html +0 -546
  106. data/doc/RubyProf/AggregateCallInfo.html +0 -551
  107. data/doc/RubyProf/CallInfo.html +0 -639
  108. data/doc/RubyProf/CallInfoPrinter.html +0 -120
  109. data/doc/RubyProf/CallInfoVisitor.html +0 -198
  110. data/doc/RubyProf/CallStackPrinter.html +0 -1121
  111. data/doc/RubyProf/CallTreePrinter.html +0 -641
  112. data/doc/RubyProf/Cmd.html +0 -631
  113. data/doc/RubyProf/DotPrinter.html +0 -257
  114. data/doc/RubyProf/FlatPrinter.html +0 -163
  115. data/doc/RubyProf/FlatPrinterWithLineNumbers.html +0 -208
  116. data/doc/RubyProf/GraphHtmlPrinter.html +0 -552
  117. data/doc/RubyProf/GraphPrinter.html +0 -139
  118. data/doc/RubyProf/MethodInfo.html +0 -745
  119. data/doc/RubyProf/MultiPrinter.html +0 -360
  120. data/doc/RubyProf/Profile.html +0 -763
  121. data/doc/RubyProf/ProfileTask.html +0 -490
  122. data/doc/RubyProf/Thread.html +0 -310
  123. data/doc/created.rid +0 -31
  124. data/doc/css/fonts.css +0 -167
  125. data/doc/css/rdoc.css +0 -590
  126. data/doc/examples/flat_txt.html +0 -138
  127. data/doc/examples/graph_html.html +0 -909
  128. data/doc/examples/graph_txt.html +0 -247
  129. data/doc/fonts/Lato-Light.ttf +0 -0
  130. data/doc/fonts/Lato-LightItalic.ttf +0 -0
  131. data/doc/fonts/Lato-Regular.ttf +0 -0
  132. data/doc/fonts/Lato-RegularItalic.ttf +0 -0
  133. data/doc/fonts/SourceCodePro-Bold.ttf +0 -0
  134. data/doc/fonts/SourceCodePro-Regular.ttf +0 -0
  135. data/doc/images/add.png +0 -0
  136. data/doc/images/arrow_up.png +0 -0
  137. data/doc/images/brick.png +0 -0
  138. data/doc/images/brick_link.png +0 -0
  139. data/doc/images/bug.png +0 -0
  140. data/doc/images/bullet_black.png +0 -0
  141. data/doc/images/bullet_toggle_minus.png +0 -0
  142. data/doc/images/bullet_toggle_plus.png +0 -0
  143. data/doc/images/date.png +0 -0
  144. data/doc/images/delete.png +0 -0
  145. data/doc/images/find.png +0 -0
  146. data/doc/images/loadingAnimation.gif +0 -0
  147. data/doc/images/macFFBgHack.png +0 -0
  148. data/doc/images/package.png +0 -0
  149. data/doc/images/page_green.png +0 -0
  150. data/doc/images/page_white_text.png +0 -0
  151. data/doc/images/page_white_width.png +0 -0
  152. data/doc/images/plugin.png +0 -0
  153. data/doc/images/ruby.png +0 -0
  154. data/doc/images/tag_blue.png +0 -0
  155. data/doc/images/tag_green.png +0 -0
  156. data/doc/images/transparent.png +0 -0
  157. data/doc/images/wrench.png +0 -0
  158. data/doc/images/wrench_orange.png +0 -0
  159. data/doc/images/zoom.png +0 -0
  160. data/doc/index.html +0 -626
  161. data/doc/js/darkfish.js +0 -161
  162. data/doc/js/jquery.js +0 -4
  163. data/doc/js/navigation.js +0 -142
  164. data/doc/js/navigation.js.gz +0 -0
  165. data/doc/js/search.js +0 -109
  166. data/doc/js/search_index.js +0 -1
  167. data/doc/js/search_index.js.gz +0 -0
  168. data/doc/js/searcher.js +0 -228
  169. data/doc/js/searcher.js.gz +0 -0
  170. data/doc/table_of_contents.html +0 -942
  171. data/examples/cachegrind.out.1 +0 -114
  172. data/examples/cachegrind.out.1.32313213 +0 -114
  173. data/examples/flat.txt +0 -50
  174. data/examples/graph.dot +0 -84
  175. data/examples/graph.html +0 -823
  176. data/examples/graph.txt +0 -139
  177. data/examples/multi.flat.txt +0 -23
  178. data/examples/multi.graph.html +0 -760
  179. data/examples/multi.grind.dat +0 -114
  180. data/examples/multi.stack.html +0 -547
  181. data/examples/stack.html +0 -547
  182. data/ext/ruby_prof/rp_measure.c +0 -40
  183. data/ext/ruby_prof/rp_measure.h +0 -45
  184. data/ext/ruby_prof/rp_measure_cpu_time.c +0 -136
  185. data/ext/ruby_prof/rp_measure_gc_runs.c +0 -73
  186. data/ext/ruby_prof/rp_measure_gc_time.c +0 -60
  187. data/ext/ruby_prof/vc/ruby_prof_18.vcxproj +0 -108
  188. data/ext/ruby_prof/vc/ruby_prof_19.vcxproj +0 -110
  189. data/lib/ruby-prof/aggregate_call_info.rb +0 -76
  190. data/lib/ruby-prof/assets/call_stack_printer.css.html +0 -117
  191. data/lib/ruby-prof/assets/call_stack_printer.js.html +0 -385
  192. data/lib/ruby-prof/printers/flat_printer_with_line_numbers.rb +0 -64
  193. data/test/aggregate_test.rb +0 -136
  194. data/test/block_test.rb +0 -74
  195. data/test/call_info_test.rb +0 -78
  196. data/test/issue137_test.rb +0 -63
  197. data/test/measure_cpu_time_test.rb +0 -213
  198. data/test/measure_gc_runs_test.rb +0 -32
  199. data/test/measure_gc_time_test.rb +0 -36
  200. data/test/measure_memory_test.rb +0 -33
  201. data/test/method_elimination_test.rb +0 -84
  202. data/test/module_test.rb +0 -45
  203. data/test/stack_test.rb +0 -138
@@ -1,260 +1,182 @@
1
- # encoding: utf-8
2
-
3
- require 'erb'
4
- require 'fileutils'
5
- require 'base64'
6
-
7
- module RubyProf
8
- # prints a HTML visualization of the call tree
9
- class CallStackPrinter < AbstractPrinter
10
- include ERB::Util
11
-
12
- # Specify print options.
13
- #
14
- # options - Hash table
15
- # :min_percent - Number 0 to 100 that specifes the minimum
16
- # %self (the methods self time divided by the
17
- # overall total time) that a method must take
18
- # for it to be printed out in the report.
19
- # Default value is 0.
20
- #
21
- # :print_file - True or false. Specifies if a method's source
22
- # file should be printed. Default value if false.
23
- #
24
- # :threshold - a float from 0 to 100 that sets the threshold of
25
- # results displayed.
26
- # Default value is 1.0
27
- #
28
- # :title - a String to overide the default "ruby-prof call tree"
29
- # title of the report.
30
- #
31
- # :expansion - a float from 0 to 100 that sets the threshold of
32
- # results that are expanded, if the percent_total
33
- # exceeds it.
34
- # Default value is 10.0
35
- #
36
- # :application - a String to overide the name of the application,
37
- # as it appears on the report.
38
- #
39
- def print(output = STDOUT, options = {})
40
- @output = output
41
- setup_options(options)
42
- if @graph_html = options.delete(:graph)
43
- @graph_html = "file://" + @graph_html if @graph_html[0]=="/"
44
- end
45
-
46
- print_header
47
-
48
- @overall_threads_time = @result.threads.inject(0) do |val, thread|
49
- val += thread.total_time
50
- end
51
-
52
- @result.threads.each do |thread|
53
- @current_thread_id = thread.fiber_id
54
- @overall_time = thread.total_time
55
- thread_info = "Thread: #{thread.id}"
56
- thread_info << ", Fiber: #{thread.fiber_id}" unless thread.id == thread.fiber_id
57
- thread_info << " (#{"%4.2f%%" % ((@overall_time/@overall_threads_time)*100)} ~ #{@overall_time})"
58
- @output.print "<div class=\"thread\">#{thread_info}</div>"
59
- @output.print "<ul name=\"thread\">"
60
- thread.methods.each do |m|
61
- # $stderr.print m.dump
62
- next unless m.root?
63
- m.call_infos.each do |ci|
64
- next unless ci.root?
65
- print_stack ci, thread.total_time
66
- end
67
- end
68
- @output.print "</ul>"
69
- end
70
-
71
- print_footer
72
-
73
- end
74
-
75
- def print_stack(call_info, parent_time)
76
- total_time = call_info.total_time
77
- percent_parent = (total_time/parent_time)*100
78
- percent_total = (total_time/@overall_time)*100
79
- return unless percent_total > min_percent
80
- color = self.color(percent_total)
81
- kids = call_info.children
82
- visible = percent_total >= threshold
83
- expanded = percent_total >= expansion
84
- display = visible ? "block" : "none"
85
- @output.print "<li class=\"color#{color}\" style=\"display:#{display}\">"
86
- if kids.empty?
87
- @output.print "<a href=\"#\" class=\"toggle empty\" ></a>"
88
- else
89
- visible_children = kids.any?{|ci| (ci.total_time/@overall_time)*100 >= threshold}
90
- image = visible_children ? (expanded ? "minus" : "plus") : "empty"
91
- @output.print "<a href=\"#\" class=\"toggle #{image}\" ></a>"
92
- end
93
- @output.printf "<span> %4.2f%% (%4.2f%%) %s %s</span>\n", percent_total, percent_parent, link(call_info), graph_link(call_info)
94
- unless kids.empty?
95
- if expanded
96
- @output.print "<ul>"
97
- else
98
- @output.print '<ul style="display:none">'
99
- end
100
- kids.sort_by{|c| -c.total_time}.each do |callinfo|
101
- print_stack callinfo, total_time
102
- end
103
- @output.print "</ul>"
104
- end
105
- @output.print "</li>"
106
- end
107
-
108
- def name(call_info)
109
- method = call_info.target
110
- method.full_name
111
- end
112
-
113
- def link(call_info)
114
- method = call_info.target
115
- file = File.expand_path(method.source_file)
116
- if file =~ /\/ruby_runtime$/
117
- h(name(call_info))
118
- else
119
- if RUBY_PLATFORM =~ /darwin/
120
- "<a href=\"txmt://open?url=file://#{file}&line=#{method.line}\">#{h(name(call_info))}</a>"
121
- else
122
- "<a href=\"file://#{file}##{method.line}\">#{h(name(call_info))}</a>"
123
- end
124
- end
125
- end
126
-
127
- def graph_link(call_info)
128
- total_calls = call_info.target.call_infos.inject(0){|t, ci| t += ci.called}
129
- href = "#{@graph_html}##{method_href(call_info.target)}"
130
- totals = @graph_html ? "<a href='#{href}'>#{total_calls}</a>" : total_calls.to_s
131
- "[#{call_info.called} calls, #{totals} total]"
132
- end
133
-
134
- def method_href(method)
135
- h(method.full_name.gsub(/[><#\.\?=:]/,"_") + "_" + @current_thread_id.to_s)
136
- end
137
-
138
- def total_time(call_infos)
139
- sum(call_infos.map{|ci| ci.total_time})
140
- end
141
-
142
- def sum(a)
143
- a.inject(0.0){|s,t| s+=t}
144
- end
145
-
146
- def dump(ci)
147
- $stderr.printf "%s/%d t:%f s:%f w:%f \n", ci, ci.object_id, ci.total_time, ci.self_time, ci.wait_time
148
- end
149
-
150
- def color(p)
151
- case i = p.to_i
152
- when 0..5
153
- "01"
154
- when 5..10
155
- "05"
156
- when 100
157
- "9"
158
- else
159
- "#{i/10}"
160
- end
161
- end
162
-
163
- def application
164
- @options[:application] || $PROGRAM_NAME
165
- end
166
-
167
- def arguments
168
- ARGV.join(' ')
169
- end
170
-
171
- def title
172
- @title ||= @options.delete(:title) || "ruby-prof call tree"
173
- end
174
-
175
- def threshold
176
- @options[:threshold] || 1.0
177
- end
178
-
179
- def expansion
180
- @options[:expansion] || 10.0
181
- end
182
-
183
- def print_header
184
- @output.puts "<html><head>"
185
- @output.puts '<meta http-equiv="content-type" content="text/html; charset=utf-8">'
186
- @output.puts "<title>#{h title}</title>"
187
- print_css
188
- print_java_script
189
- @output.puts '</head><body><div style="display: inline-block;">'
190
- print_title_bar
191
- print_commands
192
- print_help
193
- end
194
-
195
- def print_footer
196
- @output.puts '<div id="sentinel"></div></div></body></html>'
197
- end
198
-
199
- def open_asset(file)
200
- path = File.join(File.expand_path('../../assets', __FILE__), file)
201
- File.open(path, 'rb').read
202
- end
203
-
204
- def print_css
205
- html = open_asset('call_stack_printer.css.html')
206
- @output.puts html.gsub('%s', base64_image)
207
- end
208
-
209
- def base64_image
210
- file = open_asset('call_stack_printer.png')
211
- Base64.encode64(file).gsub(/\n/, '')
212
- end
213
-
214
- def print_java_script
215
- html = open_asset('call_stack_printer.js.html')
216
- @output.puts html
217
- end
218
-
219
- def print_title_bar
220
- @output.puts <<-"end_title_bar"
221
- <div id="titlebar">
222
- Call tree for application <b>#{h application} #{h arguments}</b><br/>
223
- Generated on #{Time.now} with options #{h @options.inspect}<br/>
224
- </div>
225
- end_title_bar
226
- end
227
-
228
- def print_commands
229
- @output.puts <<-"end_commands"
230
- <div id=\"commands\">
231
- <span style=\"font-size: 11pt; font-weight: bold;\">Threshold:</span>
232
- <input value=\"#{h threshold}\" size=\"3\" id=\"threshold\" type=\"text\">
233
- <input value=\"Apply\" onclick=\"setThreshold();\" type=\"submit\">
234
- <input value=\"Expand All\" onclick=\"expandAll(event);\" type=\"submit\">
235
- <input value=\"Collapse All\" onclick=\"collapseAll(event);\" type=\"submit\">
236
- <input value=\"Show Help\" onclick=\"toggleHelp(this);\" type=\"submit\">
237
- </div>
238
- end_commands
239
- end
240
-
241
- def print_help
242
- @output.puts <<-'end_help'
243
- <div style="display: none;" id="help">
244
- &#8226; Enter a decimal value <i>d</i> into the threshold field and click "Apply"
245
- to hide all nodes marked with time values lower than <i>d</i>.<br>
246
- &#8226; Click on "Expand All" for full tree expansion.<br>
247
- &#8226; Click on "Collapse All" to show only top level nodes.<br>
248
- &#8226; Use a, s, d, w as in Quake or Urban Terror to navigate the tree.<br>
249
- &#8226; Use f and b to navigate the tree in preorder forward and backwards.<br>
250
- &#8226; Use x to toggle visibility of a subtree.<br>
251
- &#8226; Use * to expand/collapse a whole subtree.<br>
252
- &#8226; Use h to navigate to thread root.<br>
253
- &#8226; Use n and p to navigate between threads.<br>
254
- &#8226; Click on background to move focus to a subtree.<br>
255
- </div>
256
- end_help
257
- end
258
- end
259
- end
260
-
1
+ # encoding: utf-8
2
+
3
+ require 'erb'
4
+ require 'fileutils'
5
+ require 'base64'
6
+ require 'set'
7
+
8
+ module RubyProf
9
+ # Prints a HTML visualization of the call tree.
10
+ #
11
+ # To use the printer:
12
+ #
13
+ # result = RubyProf.profile do
14
+ # [code to profile]
15
+ # end
16
+ #
17
+ # printer = RubyProf::CallStackPrinter.new(result)
18
+ # printer.print(STDOUT)
19
+
20
+ class CallStackPrinter < AbstractPrinter
21
+ include ERB::Util
22
+
23
+ # Specify print options.
24
+ #
25
+ # options - Hash table
26
+ # :min_percent - Number 0 to 100 that specifes the minimum
27
+ # %self (the methods self time divided by the
28
+ # overall total time) that a method must take
29
+ # for it to be printed out in the report.
30
+ # Default value is 0.
31
+ #
32
+ # :threshold - a float from 0 to 100 that sets the threshold of
33
+ # results displayed.
34
+ # Default value is 1.0
35
+ #
36
+ # :title - a String to overide the default "ruby-prof call tree"
37
+ # title of the report.
38
+ #
39
+ # :expansion - a float from 0 to 100 that sets the threshold of
40
+ # results that are expanded, if the percent_total
41
+ # exceeds it.
42
+ # Default value is 10.0
43
+ #
44
+ # :application - a String to overide the name of the application,
45
+ # as it appears on the report.
46
+ def print(output = STDOUT, options = {})
47
+ setup_options(options)
48
+ output << @erb.result(binding)
49
+ a = 1
50
+ end
51
+
52
+ # :enddoc:
53
+ def setup_options(options)
54
+ super(options)
55
+ @erb = ERB.new(self.template)
56
+ end
57
+
58
+ def print_stack(output, visited, call_info, parent_time)
59
+ total_time = call_info.total_time
60
+ percent_parent = (total_time/parent_time)*100
61
+ percent_total = (total_time/@overall_time)*100
62
+ return unless percent_total > min_percent
63
+ color = self.color(percent_total)
64
+ kids = call_info.target.callees
65
+ visible = percent_total >= threshold
66
+ expanded = percent_total >= expansion
67
+ display = visible ? "block" : "none"
68
+
69
+ output << "<li class=\"color#{color}\" style=\"display:#{display}\">" << "\n"
70
+
71
+ if visited.include?(call_info)
72
+ output << "<a href=\"#\" class=\"toggle empty\" ></a>" << "\n"
73
+ output << "<span>%s %s</span>" % [link(call_info.target, true), graph_link(call_info)] << "\n"
74
+ else
75
+ visited << call_info
76
+
77
+ if kids.empty?
78
+ output << "<a href=\"#\" class=\"toggle empty\" ></a>" << "\n"
79
+ else
80
+ visible_children = kids.any?{|ci| (ci.total_time/@overall_time)*100 >= threshold}
81
+ image = visible_children ? (expanded ? "minus" : "plus") : "empty"
82
+ output << "<a href=\"#\" class=\"toggle #{image}\" ></a>" << "\n"
83
+ end
84
+ output << "<span>%4.2f%% (%4.2f%%) %s %s</span>" % [percent_total, percent_parent,
85
+ link(call_info.target, false), graph_link(call_info)] << "\n"
86
+
87
+ unless kids.empty?
88
+ output << (expanded ? '<ul>' : '<ul style="display:none">') << "\n"
89
+ kids.sort_by{|c| -c.total_time}.each do |child_call_info|
90
+ print_stack(output, visited, child_call_info, total_time)
91
+ end
92
+ output << '</ul>' << "\n"
93
+ end
94
+
95
+ visited.delete(call_info)
96
+ end
97
+ output << '</li>' << "\n"
98
+ end
99
+
100
+ def name(call_info)
101
+ method = call_info.target
102
+ method.full_name
103
+ end
104
+
105
+ def link(method, recursive)
106
+ method_name = "#{recursive ? '*' : ''}#{method.full_name}"
107
+ if method.source_file.nil?
108
+ h method_name
109
+ else
110
+ file = File.expand_path(method.source_file)
111
+ "<a href=\"file://#{file}##{method.line}\">#{h method_name}</a>"
112
+ end
113
+ end
114
+
115
+ def graph_link(call_info)
116
+ total_calls = call_info.target.called
117
+ href = "#{method_href(call_info.target)}"
118
+ totals = total_calls.to_s
119
+ "[#{call_info.called} calls, #{totals} total]"
120
+ end
121
+
122
+ def method_href(method)
123
+ h(method.full_name.gsub(/[><#\.\?=:]/,"_"))
124
+ end
125
+
126
+ def total_time(call_infos)
127
+ sum(call_infos.map{|ci| ci.total_time})
128
+ end
129
+
130
+ def sum(a)
131
+ a.inject(0.0){|s,t| s+=t}
132
+ end
133
+
134
+ def dump(ci)
135
+ $stderr.printf "%s/%d t:%f s:%f w:%f \n", ci, ci.object_id, ci.total_time, ci.self_time, ci.wait_time
136
+ end
137
+
138
+ def color(p)
139
+ case i = p.to_i
140
+ when 0..5
141
+ "01"
142
+ when 5..10
143
+ "05"
144
+ when 100
145
+ "9"
146
+ else
147
+ "#{i/10}"
148
+ end
149
+ end
150
+
151
+ def application
152
+ @options[:application] || $PROGRAM_NAME
153
+ end
154
+
155
+ def arguments
156
+ ARGV.join(' ')
157
+ end
158
+
159
+ def title
160
+ @title ||= @options.delete(:title) || "ruby-prof call tree"
161
+ end
162
+
163
+ def threshold
164
+ @options[:threshold] || 1.0
165
+ end
166
+
167
+ def expansion
168
+ @options[:expansion] || 10.0
169
+ end
170
+
171
+ def base64_image
172
+ @data ||= begin
173
+ file = open_asset('call_stack_printer.png')
174
+ Base64.encode64(file).gsub(/\n/, '')
175
+ end
176
+ end
177
+
178
+ def template
179
+ open_asset('call_stack_printer.html.erb')
180
+ end
181
+ end
182
+ end
@@ -1,130 +1,151 @@
1
- # encoding: utf-8
2
-
3
- require 'fiber'
4
- require 'thread'
5
- require 'fileutils'
6
-
7
- module RubyProf
8
- # Generate profiling information in callgrind format for use by
9
- # kcachegrind and similar tools.
10
- #
11
- # Note: when profiling for a callgrind printer, one should use the
12
- # merge_fibers: true option when creating the profile. Otherwise
13
- # each fiber would appear as a separate profile.
14
-
15
- class CallTreePrinter < AbstractPrinter
16
-
17
- def determine_event_specification_and_value_scale
18
- @event_specification = "events: "
19
- case RubyProf.measure_mode
20
- when RubyProf::PROCESS_TIME
21
- @value_scale = RubyProf::CLOCKS_PER_SEC
22
- @event_specification << 'process_time'
23
- when RubyProf::WALL_TIME
24
- @value_scale = 1_000_000
25
- @event_specification << 'wall_time'
26
- when RubyProf.const_defined?(:CPU_TIME) && RubyProf::CPU_TIME
27
- @value_scale = RubyProf.cpu_frequency
28
- @event_specification << 'cpu_time'
29
- when RubyProf.const_defined?(:ALLOCATIONS) && RubyProf::ALLOCATIONS
30
- @value_scale = 1
31
- @event_specification << 'allocations'
32
- when RubyProf.const_defined?(:MEMORY) && RubyProf::MEMORY
33
- @value_scale = 1
34
- @event_specification << 'memory'
35
- when RubyProf.const_defined?(:GC_RUNS) && RubyProf::GC_RUNS
36
- @value_scale = 1
37
- @event_specification << 'gc_runs'
38
- when RubyProf.const_defined?(:GC_TIME) && RubyProf::GC_TIME
39
- @value_scale = 1000000
40
- @event_specification << 'gc_time'
41
- else
42
- raise "Unknown measure mode: #{RubyProf.measure_mode}"
43
- end
44
- end
45
-
46
- def print(options = {})
47
- setup_options(options)
48
- determine_event_specification_and_value_scale
49
- print_threads
50
- end
51
-
52
- def print_threads
53
- remove_subsidiary_files_from_previous_profile_runs
54
- # TODO: merge fibers of a given thread here, instead of relying
55
- # on the profiler to merge fibers.
56
- @result.threads.each do |thread|
57
- print_thread(thread)
58
- end
59
- end
60
-
61
- def convert(value)
62
- (value * @value_scale).round
63
- end
64
-
65
- def file(method)
66
- File.expand_path(method.source_file)
67
- end
68
-
69
- def print_thread(thread)
70
- File.open(file_path_for_thread(thread), "w") do |f|
71
- print_headers(f, thread)
72
- thread.methods.reverse_each do |method|
73
- print_method(f, method)
74
- end
75
- end
76
- end
77
-
78
- def path
79
- @options[:path] || "."
80
- end
81
-
82
- def base_name
83
- @options[:profile] || "profile"
84
- end
85
-
86
- def remove_subsidiary_files_from_previous_profile_runs
87
- pattern = [base_name, "callgrind.out", $$, "*"].join(".")
88
- files = Dir.glob(File.join(path, pattern))
89
- FileUtils.rm_f(files)
90
- end
91
-
92
- def file_name_for_thread(thread)
93
- if thread.fiber_id == Fiber.current.object_id
94
- [base_name, "callgrind.out", $$].join(".")
95
- else
96
- [base_name, "callgrind.out", $$, thread.fiber_id].join(".")
97
- end
98
- end
99
-
100
- def file_path_for_thread(thread)
101
- File.join(path, file_name_for_thread(thread))
102
- end
103
-
104
- def print_headers(output, thread)
105
- output << "#{@event_specification}\n\n"
106
- # this doesn't work. kcachegrind does not fully support the spec.
107
- # output << "thread: #{thread.id}\n\n"
108
- end
109
-
110
- def print_method(output, method)
111
- # Print out the file and method name
112
- output << "fl=#{file(method)}\n"
113
- output << "fn=#{method_name(method)}\n"
114
-
115
- # Now print out the function line number and its self time
116
- output << "#{method.line} #{convert(method.self_time)}\n"
117
-
118
- # Now print out all the children methods
119
- method.children.each do |callee|
120
- output << "cfl=#{file(callee.target)}\n"
121
- output << "cfn=#{method_name(callee.target)}\n"
122
- output << "calls=#{callee.called} #{callee.line}\n"
123
-
124
- # Print out total times here!
125
- output << "#{callee.line} #{convert(callee.total_time)}\n"
126
- end
127
- output << "\n"
128
- end
129
- end
130
- end
1
+ # encoding: utf-8
2
+
3
+ require 'fiber'
4
+ require 'thread'
5
+ require 'fileutils'
6
+
7
+ module RubyProf
8
+ # Generates profiling information in callgrind format for use by
9
+ # kcachegrind and similar tools.
10
+
11
+ class CallTreePrinter < AbstractPrinter
12
+ def calltree_name(method_info)
13
+ klass_path = method_info.klass_name.gsub("::", '/')
14
+ result = "#{klass_path}::#{method_info.method_name}"
15
+
16
+ case method_info.klass_flags
17
+ when 0x2
18
+ "#{result}^"
19
+ when 0x4
20
+ "#{result}^"
21
+ when 0x8
22
+ "#{result}*"
23
+ else
24
+ result
25
+ end
26
+ end
27
+
28
+ def determine_event_specification_and_value_scale
29
+ @event_specification = "events: "
30
+ case RubyProf.measure_mode
31
+ when RubyProf::PROCESS_TIME
32
+ @value_scale = RubyProf::CLOCKS_PER_SEC
33
+ @event_specification << 'process_time'
34
+ when RubyProf::WALL_TIME
35
+ @value_scale = 1_000_000
36
+ @event_specification << 'wall_time'
37
+ when RubyProf.const_defined?(:ALLOCATIONS) && RubyProf::ALLOCATIONS
38
+ @value_scale = 1
39
+ @event_specification << 'allocations'
40
+ when RubyProf.const_defined?(:MEMORY) && RubyProf::MEMORY
41
+ @value_scale = 1
42
+ @event_specification << 'memory'
43
+ when RubyProf.const_defined?(:GC_RUNS) && RubyProf::GC_RUNS
44
+ @value_scale = 1
45
+ @event_specification << 'gc_runs'
46
+ when RubyProf.const_defined?(:GC_TIME) && RubyProf::GC_TIME
47
+ @value_scale = 1000000
48
+ @event_specification << 'gc_time'
49
+ else
50
+ raise "Unknown measure mode: #{RubyProf.measure_mode}"
51
+ end
52
+ end
53
+
54
+ def print(options = {})
55
+ validate_print_params(options)
56
+ setup_options(options)
57
+ determine_event_specification_and_value_scale
58
+ print_threads
59
+ end
60
+
61
+ def validate_print_params(options)
62
+ if options.is_a?(IO)
63
+ raise ArgumentError, "#{self.class.name}#print cannot print to IO objects"
64
+ elsif !options.is_a?(Hash)
65
+ raise ArgumentError, "#{self.class.name}#print requires an options hash"
66
+ end
67
+ end
68
+
69
+ def print_threads
70
+ remove_subsidiary_files_from_previous_profile_runs
71
+ # TODO: merge fibers of a given thread here, instead of relying
72
+ # on the profiler to merge fibers.
73
+ @result.threads.each do |thread|
74
+ print_thread(thread)
75
+ end
76
+ end
77
+
78
+ def convert(value)
79
+ (value * @value_scale).round
80
+ end
81
+
82
+ def file(method)
83
+ method.source_file ? File.expand_path(method.source_file) : ''
84
+ end
85
+
86
+ def print_thread(thread)
87
+ File.open(file_path_for_thread(thread), "w") do |f|
88
+ print_headers(f, thread)
89
+ thread.methods.reverse_each do |method|
90
+ print_method(f, method)
91
+ end
92
+ end
93
+ end
94
+
95
+ def path
96
+ @options[:path] || "."
97
+ end
98
+
99
+ def self.needs_dir?
100
+ true
101
+ end
102
+
103
+ def base_name
104
+ @options[:profile] || "profile"
105
+ end
106
+
107
+ def remove_subsidiary_files_from_previous_profile_runs
108
+ pattern = [base_name, "callgrind.out", $$, "*"].join(".")
109
+ files = Dir.glob(File.join(path, pattern))
110
+ FileUtils.rm_f(files)
111
+ end
112
+
113
+ def file_name_for_thread(thread)
114
+ if thread.fiber_id == Fiber.current.object_id
115
+ [base_name, "callgrind.out", $$].join(".")
116
+ else
117
+ [base_name, "callgrind.out", $$, thread.fiber_id].join(".")
118
+ end
119
+ end
120
+
121
+ def file_path_for_thread(thread)
122
+ File.join(path, file_name_for_thread(thread))
123
+ end
124
+
125
+ def print_headers(output, thread)
126
+ output << "#{@event_specification}\n\n"
127
+ # this doesn't work. kcachegrind does not fully support the spec.
128
+ # output << "thread: #{thread.id}\n\n"
129
+ end
130
+
131
+ def print_method(output, method)
132
+ # Print out the file and method name
133
+ output << "fl=#{file(method)}\n"
134
+ output << "fn=#{self.calltree_name(method)}\n"
135
+
136
+ # Now print out the function line number and its self time
137
+ output << "#{method.line} #{convert(method.self_time)}\n"
138
+
139
+ # Now print out all the children methods
140
+ method.callees.each do |callee|
141
+ output << "cfl=#{file(callee.target)}\n"
142
+ output << "cfn=#{self.calltree_name(callee.target)}\n"
143
+ output << "calls=#{callee.called} #{callee.line}\n"
144
+
145
+ # Print out total times here!
146
+ output << "#{callee.line} #{convert(callee.total_time)}\n"
147
+ end
148
+ output << "\n"
149
+ end
150
+ end
151
+ end