ruby-prof 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/CHANGES +30 -0
  2. data/README +65 -25
  3. data/Rakefile +33 -32
  4. data/bin/ruby-prof +100 -83
  5. data/examples/graph.html +65 -69
  6. data/ext/measure_allocations.h +43 -0
  7. data/ext/measure_cpu_time.h +138 -0
  8. data/ext/measure_process_time.h +41 -0
  9. data/ext/measure_wall_time.h +42 -0
  10. data/ext/ruby_prof.c +737 -653
  11. data/lib/ruby-prof.rb +41 -38
  12. data/lib/ruby-prof/abstract_printer.rb +42 -0
  13. data/lib/ruby-prof/call_tree_printer.rb +69 -0
  14. data/lib/ruby-prof/flat_printer.rb +78 -75
  15. data/lib/ruby-prof/graph_html_printer.rb +241 -228
  16. data/lib/ruby-prof/graph_printer.rb +160 -141
  17. data/lib/ruby-prof/profile_test_case.rb +80 -0
  18. data/lib/ruby-prof/rails_plugin/ruby-prof/init.rb +6 -0
  19. data/lib/ruby-prof/rails_plugin/ruby-prof/lib/profiling.rb +52 -0
  20. data/lib/ruby-prof/task.rb +147 -0
  21. data/test/basic_test.rb +65 -35
  22. data/test/duplicate_names_test.rb +20 -24
  23. data/test/gc.log +5 -0
  24. data/test/measure_mode_test.rb +79 -0
  25. data/test/module_test.rb +31 -18
  26. data/test/no_method_class_test.rb +14 -0
  27. data/test/prime1.rb +17 -0
  28. data/test/prime2.rb +26 -0
  29. data/test/prime3.rb +17 -0
  30. data/test/prime_test.rb +10 -10
  31. data/test/printers_test.rb +14 -12
  32. data/test/profile_unit_test.rb +24 -0
  33. data/test/recursive_test.rb +105 -17
  34. data/test/singleton_test.rb +38 -0
  35. data/test/start_test.rb +24 -0
  36. data/test/test_helper.rb +33 -29
  37. data/test/test_suite.rb +10 -2
  38. data/test/thread_test.rb +123 -17
  39. data/test/timing_test.rb +70 -29
  40. metadata +28 -30
  41. data/doc/created.rid +0 -1
  42. data/doc/files/LICENSE.html +0 -0
  43. data/doc/files/README.html +0 -376
  44. data/doc/files/bin/ruby-prof.html +0 -143
  45. data/doc/files/examples/flat_txt.html +0 -179
  46. data/doc/files/examples/graph_html.html +0 -948
  47. data/doc/files/examples/graph_txt.html +0 -297
  48. data/doc/files/ext/ruby_prof_c.html +0 -101
  49. data/doc/files/lib/ruby-prof/flat_printer_rb.html +0 -101
  50. data/doc/files/lib/ruby-prof/graph_html_printer_rb.html +0 -108
  51. data/doc/files/lib/ruby-prof/graph_printer_rb.html +0 -101
  52. data/doc/files/lib/ruby-prof/profiletask_rb.html +0 -109
  53. data/doc/files/lib/ruby-prof_rb.html +0 -111
  54. data/doc/files/lib/unprof_rb.html +0 -108
  55. data/doc/rdoc-style.css +0 -208
  56. data/lib/ruby-prof/profiletask.rb +0 -150
  57. data/test/clock_mode_test.rb +0 -73
@@ -1,38 +1,41 @@
1
- require "ruby_prof.so"
2
-
3
- require "ruby-prof/flat_printer"
4
- require "ruby-prof/graph_printer"
5
- require "ruby-prof/graph_html_printer"
6
-
7
- module RubyProf
8
- # See if the user specified the clock mode via
9
- # the RUBY_PROF_CLOCK_MODE environment variable
10
- def self.figure_clock_mode
11
- case ENV["RUBY_PROF_CLOCK_MODE"]
12
- when "wall" || "wall_time"
13
- RubyProf.clock_mode = RubyProf::WALL_TIME
14
- when "cpu" || "cpu_time"
15
- if ENV.key?("RUBY_PROF_CPU_FREQUENCY")
16
- RubyProf.cpu_frequency = ENV["RUBY_PROF_CPU_FREQUENCY"].to_f
17
- else
18
- begin
19
- open("/proc/cpuinfo") do |f|
20
- f.each_line do |line|
21
- s = line.slice(/cpu MHz\s*:\s*(.*)/, 1)
22
- if s
23
- RubyProf.cpu_frequency = s.to_f * 1000000
24
- break
25
- end
26
- end
27
- end
28
- rescue Errno::ENOENT
29
- end
30
- end
31
- RubyProf.clock_mode = RubyProf::CPU_TIME
32
- else
33
- RubyProf.clock_mode = RubyProf::PROCESS_TIME
34
- end
35
- end
36
- end
37
-
38
- RubyProf::figure_clock_mode
1
+ require "ruby_prof.so"
2
+
3
+ require "ruby-prof/flat_printer"
4
+ require "ruby-prof/graph_printer"
5
+ require "ruby-prof/graph_html_printer"
6
+ require "ruby-prof/call_tree_printer"
7
+
8
+ module RubyProf
9
+ # See if the user specified the clock mode via
10
+ # the RUBY_PROF_MEASURE_MODE environment variable
11
+ def self.figure_measure_mode
12
+ case ENV["RUBY_PROF_MEASURE_MODE"]
13
+ when "wall" || "wall_time"
14
+ RubyProf.measure_mode = RubyProf::WALL_TIME
15
+ when "cpu" || "cpu_time"
16
+ if ENV.key?("RUBY_PROF_CPU_FREQUENCY")
17
+ RubyProf.cpu_frequency = ENV["RUBY_PROF_CPU_FREQUENCY"].to_f
18
+ else
19
+ begin
20
+ open("/proc/cpuinfo") do |f|
21
+ f.each_line do |line|
22
+ s = line.slice(/cpu MHz\s*:\s*(.*)/, 1)
23
+ if s
24
+ RubyProf.cpu_frequency = s.to_f * 1000000
25
+ break
26
+ end
27
+ end
28
+ end
29
+ rescue Errno::ENOENT
30
+ end
31
+ end
32
+ RubyProf.measure_mode = RubyProf::CPU_TIME
33
+ when "allocations"
34
+ RubyProf.measure_mode = RubyProf::ALLOCATIONS
35
+ else
36
+ RubyProf.measure_mode = RubyProf::PROCESS_TIME
37
+ end
38
+ end
39
+ end
40
+
41
+ RubyProf::figure_measure_mode
@@ -0,0 +1,42 @@
1
+ module RubyProf
2
+ class AbstractPrinter
3
+ def initialize(result)
4
+ @result = result
5
+ @output = nil
6
+ @options = {}
7
+ end
8
+
9
+ # Specify print options.
10
+ #
11
+ # options - Hash table
12
+ # :min_percent - Number 0 to 100 that specifes the minimum
13
+ # %self (the methods self time divided by the
14
+ # overall total time) that a method must take
15
+ # for it to be printed out in the report.
16
+ # Default value is 0.
17
+ #
18
+ # :print_file - True or false. Specifies if a method's source
19
+ # file should be printed. Default value if false.
20
+ #
21
+ def setup_options(options = {})
22
+ @options = options
23
+ end
24
+
25
+ def min_percent
26
+ @options[:min_percent] || 0
27
+ end
28
+
29
+ def print_file
30
+ @options[:print_file] || false
31
+ end
32
+
33
+ def method_name(method)
34
+ name = method.full_name
35
+ if print_file
36
+ name += " (#{method.source_file}:#{method.line}}"
37
+ end
38
+ name
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,69 @@
1
+ require 'ruby-prof/abstract_printer'
2
+
3
+ module RubyProf
4
+ # Generate profiling information in calltree format
5
+ # for use by kcachegrind and similar tools.
6
+
7
+ class CallTreePrinter < AbstractPrinter
8
+ def print(output = STDOUT, options = {})
9
+ @output = output
10
+ setup_options(options)
11
+
12
+ # add a header - this information is somewhat arbitrary
13
+ @output << "events: "
14
+ case RubyProf.measure_mode
15
+ when RubyProf::PROCESS_TIME
16
+ @output << 'process_time'
17
+ when RubyProf::WALL_TIME
18
+ @output << 'wall_time'
19
+ when RubyProf::CPU_TIME
20
+ @output << 'cpu_time'
21
+ when RubyProf::ALLOCATIONS
22
+ @output << 'allocations'
23
+ end
24
+ @output << "\n\n"
25
+
26
+ print_threads
27
+ end
28
+
29
+ def print_threads
30
+ @result.threads.each do |thread_id, methods|
31
+ print_methods(thread_id ,methods)
32
+ end
33
+ end
34
+
35
+ def convert(value)
36
+ (value * 1000).round
37
+ end
38
+
39
+ def file(method)
40
+ File.expand_path(method.source_file)
41
+ end
42
+
43
+ def name(method)
44
+ "#{method.klass_name}::#{method.method_name}"
45
+ end
46
+
47
+ def print_methods(thread_id, methods)
48
+ methods.reverse_each do |method|
49
+ # Print out the file and method name
50
+ @output << "fl=#{file(method)}\n"
51
+ @output << "fn=#{name(method)}\n"
52
+
53
+ # Now print out the function line number and its self time
54
+ @output << "#{method.line} #{convert(method.self_time)}\n"
55
+
56
+ # Now print out all the children methods
57
+ method.children.each do |callee|
58
+ @output << "cfl=#{file(callee.target)}\n"
59
+ @output << "cfn=#{name(callee.target)}\n"
60
+ @output << "calls=#{callee.called} #{callee.line}\n"
61
+
62
+ # Print out total times here!
63
+ @output << "#{callee.line} #{convert(callee.total_time)}\n"
64
+ end
65
+ @output << "\n"
66
+ end
67
+ end #end print_methods
68
+ end # end class
69
+ end # end packages
@@ -1,75 +1,78 @@
1
- module RubyProf
2
- # Generates flat[link:files/examples/flat_txt.html] profile reports as text.
3
- # To use the flat printer:
4
- #
5
- # result = RubyProf.profile do
6
- # [code to profile]
7
- # end
8
- #
9
- # printer = RubyProf::FlatPrinter.new(result)
10
- # printer.print(STDOUT, 0)
11
- #
12
- class FlatPrinter
13
- # Create a FlatPrinter. Result is a RubyProf::Result
14
- # object generated from a profiling run.
15
- def initialize(result)
16
- @result = result
17
- end
18
-
19
- # Print a flat profile report to the provided output.
20
- #
21
- # output - Any IO oject, including STDOUT or a file.
22
- # The default value is STDOUT.
23
- #
24
- # min_percent - The minimum %self (the methods
25
- # self time divided by the overall total time) that
26
- # a method must take for it to be printed out in
27
- # the report. Default value is 0.
28
- def print(output = STDOUT, min_percent = 0)
29
- @min_percent = min_percent
30
- @output = output
31
- print_threads
32
- end
33
-
34
- private
35
-
36
- def print_threads
37
- # sort assumes that spawned threads have higher object_ids
38
- @result.threads.sort.each do |thread_id, methods|
39
- print_methods(thread_id, methods)
40
- @output << "\n" * 2
41
- end
42
- end
43
-
44
- def print_methods(thread_id, methods)
45
- toplevel = @result.toplevel(thread_id)
46
- total_time = toplevel.total_time
47
-
48
-
49
- sum = 0
50
- @output << "Thread ID: " << thread_id << "\n"
51
- @output << " %self cumulative total self children calls self/call total/call name\n"
52
-
53
- methods.sort.reverse.each do |pair|
54
- method_name = pair[0]
55
- method = pair[1]
56
-
57
- self_percent = (method.self_time / total_time) * 100
58
- next if self_percent < @min_percent
59
-
60
- sum += method.self_time
61
- @output.printf("%6.2f %8.2f %8.2f %8.2f %8.2f %8d %8.2f %8.2f %s\n",
62
- method.self_time / total_time * 100, # %self
63
- sum, # cumulative
64
- method.total_time, # total
65
- method.self_time, # self
66
- method.children_time, # children
67
- method.called, # calls
68
- method.self_time / method.called, # self/call
69
- method.total_time / method.called, # total/call
70
- method_name) # name
71
- end
72
- end
73
- end
74
- end
75
-
1
+ require 'ruby-prof/abstract_printer'
2
+
3
+ module RubyProf
4
+ # Generates flat[link:files/examples/flat_txt.html] profile reports as text.
5
+ # To use the flat printer:
6
+ #
7
+ # result = RubyProf.profile do
8
+ # [code to profile]
9
+ # end
10
+ #
11
+ # printer = RubyProf::FlatPrinter.new(result)
12
+ # printer.print(STDOUT, 0)
13
+ #
14
+ class FlatPrinter < AbstractPrinter
15
+ # Print a flat profile report to the provided output.
16
+ #
17
+ # output - Any IO oject, including STDOUT or a file.
18
+ # The default value is STDOUT.
19
+ #
20
+ # options - Hash of print options. See #setup_options
21
+ # for more information.
22
+ #
23
+ def print(output = STDOUT, options = {})
24
+ @output = output
25
+ setup_options(options)
26
+ print_threads
27
+ end
28
+
29
+ private
30
+
31
+ def print_threads
32
+ @result.threads.each do |thread_id, methods|
33
+ print_methods(thread_id, methods)
34
+ @output << "\n" * 2
35
+ end
36
+ end
37
+
38
+ def print_methods(thread_id, methods)
39
+ # Get total time
40
+ toplevel = methods.sort.last
41
+ total_time = toplevel.total_time
42
+ if total_time == 0
43
+ total_time = 0.01
44
+ end
45
+
46
+ # Now sort methods by largest self time,
47
+ # not total time like in other printouts
48
+ methods = methods.sort do |m1, m2|
49
+ m1.self_time <=> m2.self_time
50
+ end.reverse
51
+
52
+ @output << "Thread ID: " << thread_id << "\n"
53
+ @output << "Total: " << total_time << "\n"
54
+ @output << "\n"
55
+ @output << " %self total self wait child calls name\n"
56
+
57
+ sum = 0
58
+ methods.each do |method|
59
+ self_percent = (method.self_time / total_time) * 100
60
+ next if self_percent < min_percent
61
+
62
+ sum += method.self_time
63
+ #self_time_called = method.called > 0 ? method.self_time/method.called : 0
64
+ #total_time_called = method.called > 0? method.total_time/method.called : 0
65
+
66
+ @output.printf("%6.2f %8.2f %8.2f %8.2f %8.2f %8d %s\n",
67
+ method.self_time / total_time * 100, # %self
68
+ method.total_time, # total
69
+ method.self_time, # self
70
+ method.wait_time, # wait
71
+ method.children_time, # children
72
+ method.called, # calls
73
+ method_name(method)) # name
74
+ end
75
+ end
76
+ end
77
+ end
78
+
@@ -1,228 +1,241 @@
1
- require "erb"
2
-
3
- module RubyProf
4
- # Generates graph[link:files/examples/graph_html.html] profile reports as html.
5
- # To use the grap html printer:
6
- #
7
- # result = RubyProf.profile do
8
- # [code to profile]
9
- # end
10
- #
11
- # printer = RubyProf::GraphHtmlPrinter.new(result, 5)
12
- # printer.print(STDOUT, 0)
13
- #
14
- # The constructor takes two arguments. The first is
15
- # a RubyProf::Result object generated from a profiling
16
- # run. The second is the minimum %total (the methods
17
- # total time divided by the overall total time) that
18
- # a method must take for it to be printed out in
19
- # the report. Use this parameter to eliminate methods
20
- # that are not important to the overall profiling results.
21
-
22
- class GraphHtmlPrinter
23
- PERCENTAGE_WIDTH = 8
24
- TIME_WIDTH = 10
25
- CALL_WIDTH = 20
26
-
27
- # Create a GraphPrinter. Result is a RubyProf::Result
28
- # object generated from a profiling run.
29
- def initialize(result)
30
- @result = result
31
- end
32
-
33
- # Print a graph html report to the provided output.
34
- #
35
- # output - Any IO oject, including STDOUT or a file.
36
- # The default value is STDOUT.
37
- #
38
- # min_percent - The minimum %total (the methods
39
- # total time divided by the overall total time) that
40
- # a method must take for it to be printed out in
41
- # the report. Default value is 0.
42
- def print(output = STDOUT, min_percent = 0)
43
- @output = output
44
- @min_percent = min_percent
45
-
46
- _erbout = @output
47
- erb = ERB.new(template, nil, nil)
48
- @output << erb.result(binding)
49
- end
50
-
51
- # These methods should be private but then ERB doesn't
52
- # work. Turn off RDOC though
53
- #--
54
- def total_time(thread_id)
55
- toplevel = @result.toplevel(thread_id)
56
- total_time = toplevel.total_time
57
- total_time = 0.01 if total_time == 0
58
- return total_time
59
- end
60
-
61
- def total_percent(method)
62
- overall_time = self.total_time(method.thread_id)
63
- (method.total_time/overall_time) * 100
64
- end
65
-
66
- def self_percent(method)
67
- overall_time = self.total_time(method.thread_id)
68
- (method.self_time/overall_time) * 100
69
- end
70
-
71
- # Creates a link to a method. Note that we do not create
72
- # links to methods which are under the min_perecent
73
- # specified by the user, since they will not be
74
- # printed out.
75
- def create_link(thread_id, name)
76
- # Get method
77
- method = @result.threads[thread_id][name]
78
-
79
- if self.total_percent(method) < @min_percent
80
- # Just return name
81
- name
82
- else
83
- # Create link
84
- "<a href=\"##{link_name(thread_id, name)}\">#{name}</a>"
85
- end
86
- end
87
-
88
- def link_name(thread_id, name)\
89
- name.gsub(/[><#\.\?=:]/,"_") + "_" + thread_id.to_s
90
- end
91
-
92
- def template
93
- '
94
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
95
- <html>
96
- <head>
97
- <style media="all" type="text/css">
98
- table {
99
- border-collapse: collapse;
100
- border: 1px solid #CCC;
101
- font-family: Verdana, Arial, Helvetica, sans-serif;
102
- font-size: 9pt;
103
- line-height: normal;
104
- }
105
-
106
- th {
107
- text-align: center;
108
- border-top: 1px solid #FB7A31;
109
- border-bottom: 1px solid #FB7A31;
110
- background: #FFC;
111
- padding: 0.3em;
112
- border-left: 1px solid silver;
113
- }
114
-
115
- tr.break td {
116
- border: 0;
117
- border-top: 1px solid #FB7A31;
118
- padding: 0;
119
- margin: 0;
120
- }
121
-
122
- tr.method td {
123
- font-weight: bold;
124
- }
125
-
126
- td {
127
- padding: 0.3em;
128
- }
129
-
130
- td:first-child {
131
- width: 190px;
132
- }
133
-
134
- td {
135
- border-left: 1px solid #CCC;
136
- text-align: center;
137
- }
138
- </style>
139
- </head>
140
- <body>
141
- <h1>Profile Report</h1>
142
- <!-- Threads Table -->
143
- <table>
144
- <tr>
145
- <th>Thread ID</th>
146
- <th>Total Time</th>
147
- </tr>
148
- <% for thread_id, methods in @result.threads %>
149
- <tr>
150
- <td><a href="#<%= thread_id %>"><%= thread_id %></a></td>
151
- <td><%= @result.toplevel(thread_id).total_time %></td>
152
- </tr>
153
- <% end %>
154
- </table>
155
-
156
- <!-- Methods Tables -->
157
- <% for thread_id, methods in @result.threads %>
158
- <h2><a name="<%= thread_id %>">Thread <%= thread_id %></a></h2>
159
-
160
- <table>
161
- <tr>
162
- <th><%= sprintf("%#{PERCENTAGE_WIDTH}s", "%Total") %></th>
163
- <th><%= sprintf("%#{PERCENTAGE_WIDTH}s", "%Self") %></th>
164
- <th><%= sprintf("%#{TIME_WIDTH}s", "Total") %></th>
165
- <th><%= sprintf("%#{TIME_WIDTH}s", "Self") %></th>
166
- <th><%= sprintf("%#{TIME_WIDTH+2}s", "Children") %></th>
167
- <th><%= sprintf("%#{CALL_WIDTH}s", "Calls") %></th>
168
- <th>Name</th>
169
- </tr>
170
-
171
- <% methods.sort.reverse.each do |pair| %>
172
- <% name = pair[0] %>
173
- <% method = pair[1] %>
174
- <% method_total_percent = self.total_percent(method) %>
175
- <% next if method_total_percent < @min_percent %>
176
- <% method_self_percent = self.self_percent(method) %>
177
-
178
- <!-- Parents -->
179
- <% for name, call_info in method.parents %>
180
- <tr>
181
- <td>&nbsp;</td>
182
- <td>&nbsp;</td>
183
- <td><%= sprintf("%#{TIME_WIDTH}.2f", call_info.total_time) %></td>
184
- <td><%= sprintf("%#{TIME_WIDTH}.2f", call_info.self_time) %></td>
185
- <td><%= sprintf("%#{TIME_WIDTH}.2f", call_info.children_time) %></td>
186
- <% called = "#{call_info.called}/#{method.called}" %>
187
- <td><%= sprintf("%#{CALL_WIDTH}s", called) %></td>
188
- <td><%= create_link(thread_id, name) %></td>
189
- </tr>
190
- <% end %>
191
-
192
- <tr class="method">
193
- <td><%= sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", method_total_percent) %></td>
194
- <td><%= sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", method_self_percent) %></td>
195
- <td><%= sprintf("%#{TIME_WIDTH}.2f", method.total_time) %></td>
196
- <td><%= sprintf("%#{TIME_WIDTH}.2f", method.self_time) %></td>
197
- <td><%= sprintf("%#{TIME_WIDTH}.2f", method.children_time) %></td>
198
- <td><%= sprintf("%#{CALL_WIDTH}i", method.called) %></td>
199
- <td><a name="<%= link_name(thread_id, method.name) %>"><%= method.name %></a></td>
200
- </tr>
201
-
202
- <!-- Children -->
203
- <% for name, call_info in method.children %>
204
- <% methods = @result.threads[thread_id] %>
205
- <% child = methods[name] %>
206
-
207
- <tr>
208
- <td>&nbsp;</td>
209
- <td>&nbsp;</td>
210
- <td><%= sprintf("%#{TIME_WIDTH}.2f", call_info.total_time) %></td>
211
- <td><%= sprintf("%#{TIME_WIDTH}.2f", call_info.self_time) %></td>
212
- <td><%= sprintf("%#{TIME_WIDTH}.2f", call_info.children_time) %></td>
213
- <% called = "#{call_info.called}/#{child.called}" %>
214
- <td><%= sprintf("%#{CALL_WIDTH}s", called) %></td>
215
- <td><%= create_link(thread_id, name) %></td>
216
- </tr>
217
- <% end %>
218
- <!-- Create divider row -->
219
- <tr class="break"><td colspan="7"></td></tr>
220
- <% end %>
221
- </table>
222
- <% end %>
223
- </body>
224
- </html>'
225
- end
226
- end
227
- end
228
-
1
+ require 'ruby-prof/abstract_printer'
2
+ require "erb"
3
+
4
+ module RubyProf
5
+ # Generates graph[link:files/examples/graph_html.html] profile reports as html.
6
+ # To use the grap html printer:
7
+ #
8
+ # result = RubyProf.profile do
9
+ # [code to profile]
10
+ # end
11
+ #
12
+ # printer = RubyProf::GraphHtmlPrinter.new(result, 5)
13
+ # printer.print(STDOUT, 0)
14
+ #
15
+ # The constructor takes two arguments. The first is
16
+ # a RubyProf::Result object generated from a profiling
17
+ # run. The second is the minimum %total (the methods
18
+ # total time divided by the overall total time) that
19
+ # a method must take for it to be printed out in
20
+ # the report. Use this parameter to eliminate methods
21
+ # that are not important to the overall profiling results.
22
+
23
+ class GraphHtmlPrinter < AbstractPrinter
24
+ PERCENTAGE_WIDTH = 8
25
+ TIME_WIDTH = 10
26
+ CALL_WIDTH = 20
27
+
28
+ # Create a GraphPrinter. Result is a RubyProf::Result
29
+ # object generated from a profiling run.
30
+ def initialize(result)
31
+ super(result)
32
+ @thread_times = Hash.new
33
+ calculate_thread_times
34
+ end
35
+
36
+ # Print a graph html report to the provided output.
37
+ #
38
+ # output - Any IO oject, including STDOUT or a file.
39
+ # The default value is STDOUT.
40
+ #
41
+ # options - Hash of print options. See #setup_options
42
+ # for more information.
43
+ #
44
+ def print(output = STDOUT, options = {})
45
+ @output = output
46
+ setup_options(options)
47
+
48
+ _erbout = @output
49
+ erb = ERB.new(template, nil, nil)
50
+ @output << erb.result(binding)
51
+ end
52
+
53
+ # These methods should be private but then ERB doesn't
54
+ # work. Turn off RDOC though
55
+ #--
56
+ def calculate_thread_times
57
+ # Cache thread times since this is an expensive
58
+ # operation with the required sorting
59
+ @result.threads.each do |thread_id, methods|
60
+ top = methods.sort.last
61
+
62
+ thread_time = 0.01
63
+ thread_time = top.total_time if top.total_time > 0
64
+
65
+ @thread_times[thread_id] = thread_time
66
+ end
67
+ end
68
+
69
+ def thread_time(thread_id)
70
+ @thread_times[thread_id]
71
+ end
72
+
73
+ def total_percent(thread_id, method)
74
+ overall_time = self.thread_time(thread_id)
75
+ (method.total_time/overall_time) * 100
76
+ end
77
+
78
+ def self_percent(method)
79
+ overall_time = self.thread_time(method.thread_id)
80
+ (method.self_time/overall_time) * 100
81
+ end
82
+
83
+ # Creates a link to a method. Note that we do not create
84
+ # links to methods which are under the min_perecent
85
+ # specified by the user, since they will not be
86
+ # printed out.
87
+ def create_link(thread_id, method)
88
+ if self.total_percent(thread_id, method) < min_percent
89
+ # Just return name
90
+ method.full_name
91
+ else
92
+ href = '#' + method_href(thread_id, method)
93
+ "<a href=\"#{href}\">#{method.full_name}</a>"
94
+ end
95
+ end
96
+
97
+ def method_href(thread_id, method)
98
+ method.full_name.gsub(/[><#\.\?=:]/,"_") + "_" + thread_id.to_s
99
+ end
100
+
101
+ def template
102
+ '
103
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
104
+ <html>
105
+ <head>
106
+ <style media="all" type="text/css">
107
+ table {
108
+ border-collapse: collapse;
109
+ border: 1px solid #CCC;
110
+ font-family: Verdana, Arial, Helvetica, sans-serif;
111
+ font-size: 9pt;
112
+ line-height: normal;
113
+ }
114
+
115
+ th {
116
+ text-align: center;
117
+ border-top: 1px solid #FB7A31;
118
+ border-bottom: 1px solid #FB7A31;
119
+ background: #FFC;
120
+ padding: 0.3em;
121
+ border-left: 1px solid silver;
122
+ }
123
+
124
+ tr.break td {
125
+ border: 0;
126
+ border-top: 1px solid #FB7A31;
127
+ padding: 0;
128
+ margin: 0;
129
+ }
130
+
131
+ tr.method td {
132
+ font-weight: bold;
133
+ }
134
+
135
+ td {
136
+ padding: 0.3em;
137
+ }
138
+
139
+ td:first-child {
140
+ width: 190px;
141
+ }
142
+
143
+ td {
144
+ border-left: 1px solid #CCC;
145
+ text-align: center;
146
+ }
147
+ </style>
148
+ </head>
149
+ <body>
150
+ <h1>Profile Report</h1>
151
+ <!-- Threads Table -->
152
+ <table>
153
+ <tr>
154
+ <th>Thread ID</th>
155
+ <th>Total Time</th>
156
+ </tr>
157
+ <% for thread_id, methods in @result.threads %>
158
+ <tr>
159
+ <td><a href="#<%= thread_id %>"><%= thread_id %></a></td>
160
+ <td><%= thread_time(thread_id) %></td>
161
+ </tr>
162
+ <% end %>
163
+ </table>
164
+
165
+ <!-- Methods Tables -->
166
+ <% for thread_id, methods in @result.threads
167
+ total_time = thread_time(thread_id) %>
168
+ <h2><a name="<%= thread_id %>">Thread <%= thread_id %></a></h2>
169
+
170
+ <table>
171
+ <tr>
172
+ <th><%= sprintf("%#{PERCENTAGE_WIDTH}s", "%Total") %></th>
173
+ <th><%= sprintf("%#{PERCENTAGE_WIDTH}s", "%Self") %></th>
174
+ <th><%= sprintf("%#{TIME_WIDTH}s", "Total") %></th>
175
+ <th><%= sprintf("%#{TIME_WIDTH}s", "Self") %></th>
176
+ <th><%= sprintf("%#{TIME_WIDTH}s", "Wait") %></th>
177
+ <th><%= sprintf("%#{TIME_WIDTH+2}s", "Child") %></th>
178
+ <th><%= sprintf("%#{CALL_WIDTH}s", "Calls") %></th>
179
+ <th>Name</th>
180
+ <th>Line</th>
181
+ </tr>
182
+
183
+ <% methods.sort.reverse_each do |method|
184
+ total_percentage = (method.total_time/total_time) * 100
185
+ next if total_percentage < min_percent
186
+ self_percentage = (method.self_time/total_time) * 100 %>
187
+
188
+ <!-- Parents -->
189
+ <% for caller in method.parents %>
190
+ <tr>
191
+ <td>&nbsp;</td>
192
+ <td>&nbsp;</td>
193
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", caller.total_time) %></td>
194
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", caller.self_time) %></td>
195
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", caller.wait_time) %></td>
196
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", caller.children_time) %></td>
197
+ <% called = "#{caller.called}/#{method.called}" %>
198
+ <td><%= sprintf("%#{CALL_WIDTH}s", called) %></td>
199
+ <td><%= create_link(thread_id, caller.target) %></td>
200
+ <td><a href="file://<%= File.expand_path(caller.target.source_file) %>#line=<%= caller.line %>"><%= caller.line %></a></td>
201
+ </tr>
202
+ <% end %>
203
+
204
+ <tr class="method">
205
+ <td><%= sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", total_percentage) %></td>
206
+ <td><%= sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", self_percentage) %></td>
207
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", method.total_time) %></td>
208
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", method.self_time) %></td>
209
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", method.wait_time) %></td>
210
+ <td><%= sprintf("%#{TIME_WIDTH}.2f", method.children_time) %></td>
211
+ <td><%= sprintf("%#{CALL_WIDTH}i", method.called) %></td>
212
+ <td><a name="<%= method_href(thread_id, method) %>"><%= method.full_name %></a></td>
213
+ <td><a href="file://<%= File.expand_path(method.source_file) %>#line=<%= method.line %>"><%= method.line %></a></td>
214
+ </tr>
215
+
216
+ <!-- Children -->
217
+ <% for callee in method.children %>
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><%= create_link(thread_id, callee.target) %></td>
228
+ <td><a href="file://<%= File.expand_path(method.source_file) %>#line=<%= callee.line %>"><%= callee.line %></a></td>
229
+ </tr>
230
+ <% end %>
231
+ <!-- Create divider row -->
232
+ <tr class="break"><td colspan="8"></td></tr>
233
+ <% end %>
234
+ </table>
235
+ <% end %>
236
+ </body>
237
+ </html>'
238
+ end
239
+ end
240
+ end
241
+