ruby-prof 1.0.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 (97) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES +523 -0
  3. data/LICENSE +25 -0
  4. data/README.rdoc +5 -0
  5. data/Rakefile +110 -0
  6. data/bin/ruby-prof +380 -0
  7. data/bin/ruby-prof-check-trace +45 -0
  8. data/ext/ruby_prof/extconf.rb +36 -0
  9. data/ext/ruby_prof/rp_allocation.c +292 -0
  10. data/ext/ruby_prof/rp_allocation.h +31 -0
  11. data/ext/ruby_prof/rp_call_info.c +283 -0
  12. data/ext/ruby_prof/rp_call_info.h +35 -0
  13. data/ext/ruby_prof/rp_measure_allocations.c +52 -0
  14. data/ext/ruby_prof/rp_measure_memory.c +42 -0
  15. data/ext/ruby_prof/rp_measure_process_time.c +63 -0
  16. data/ext/ruby_prof/rp_measure_wall_time.c +62 -0
  17. data/ext/ruby_prof/rp_measurement.c +236 -0
  18. data/ext/ruby_prof/rp_measurement.h +49 -0
  19. data/ext/ruby_prof/rp_method.c +642 -0
  20. data/ext/ruby_prof/rp_method.h +70 -0
  21. data/ext/ruby_prof/rp_profile.c +881 -0
  22. data/ext/ruby_prof/rp_profile.h +36 -0
  23. data/ext/ruby_prof/rp_stack.c +196 -0
  24. data/ext/ruby_prof/rp_stack.h +56 -0
  25. data/ext/ruby_prof/rp_thread.c +338 -0
  26. data/ext/ruby_prof/rp_thread.h +36 -0
  27. data/ext/ruby_prof/ruby_prof.c +48 -0
  28. data/ext/ruby_prof/ruby_prof.h +17 -0
  29. data/ext/ruby_prof/vc/ruby_prof.sln +31 -0
  30. data/ext/ruby_prof/vc/ruby_prof.vcxproj +143 -0
  31. data/lib/ruby-prof.rb +53 -0
  32. data/lib/ruby-prof/assets/call_stack_printer.css.html +117 -0
  33. data/lib/ruby-prof/assets/call_stack_printer.js.html +385 -0
  34. data/lib/ruby-prof/assets/call_stack_printer.png +0 -0
  35. data/lib/ruby-prof/assets/graph_printer.html.erb +356 -0
  36. data/lib/ruby-prof/call_info.rb +57 -0
  37. data/lib/ruby-prof/call_info_visitor.rb +38 -0
  38. data/lib/ruby-prof/compatibility.rb +109 -0
  39. data/lib/ruby-prof/exclude_common_methods.rb +198 -0
  40. data/lib/ruby-prof/measurement.rb +14 -0
  41. data/lib/ruby-prof/method_info.rb +90 -0
  42. data/lib/ruby-prof/printers/abstract_printer.rb +118 -0
  43. data/lib/ruby-prof/printers/call_info_printer.rb +51 -0
  44. data/lib/ruby-prof/printers/call_stack_printer.rb +269 -0
  45. data/lib/ruby-prof/printers/call_tree_printer.rb +151 -0
  46. data/lib/ruby-prof/printers/dot_printer.rb +132 -0
  47. data/lib/ruby-prof/printers/flat_printer.rb +52 -0
  48. data/lib/ruby-prof/printers/graph_html_printer.rb +64 -0
  49. data/lib/ruby-prof/printers/graph_printer.rb +114 -0
  50. data/lib/ruby-prof/printers/multi_printer.rb +127 -0
  51. data/lib/ruby-prof/profile.rb +33 -0
  52. data/lib/ruby-prof/rack.rb +171 -0
  53. data/lib/ruby-prof/task.rb +147 -0
  54. data/lib/ruby-prof/thread.rb +35 -0
  55. data/lib/ruby-prof/version.rb +3 -0
  56. data/lib/unprof.rb +10 -0
  57. data/ruby-prof.gemspec +58 -0
  58. data/test/abstract_printer_test.rb +26 -0
  59. data/test/alias_test.rb +129 -0
  60. data/test/basic_test.rb +129 -0
  61. data/test/call_info_visitor_test.rb +31 -0
  62. data/test/duplicate_names_test.rb +32 -0
  63. data/test/dynamic_method_test.rb +53 -0
  64. data/test/enumerable_test.rb +21 -0
  65. data/test/exceptions_test.rb +24 -0
  66. data/test/exclude_methods_test.rb +146 -0
  67. data/test/exclude_threads_test.rb +53 -0
  68. data/test/line_number_test.rb +161 -0
  69. data/test/marshal_test.rb +119 -0
  70. data/test/measure_allocations.rb +30 -0
  71. data/test/measure_allocations_test.rb +385 -0
  72. data/test/measure_allocations_trace_test.rb +385 -0
  73. data/test/measure_memory_trace_test.rb +756 -0
  74. data/test/measure_process_time_test.rb +849 -0
  75. data/test/measure_times.rb +54 -0
  76. data/test/measure_wall_time_test.rb +459 -0
  77. data/test/multi_printer_test.rb +71 -0
  78. data/test/no_method_class_test.rb +15 -0
  79. data/test/parser_timings.rb +24 -0
  80. data/test/pause_resume_test.rb +166 -0
  81. data/test/prime.rb +56 -0
  82. data/test/printer_call_tree_test.rb +31 -0
  83. data/test/printer_flat_test.rb +68 -0
  84. data/test/printer_graph_html_test.rb +60 -0
  85. data/test/printer_graph_test.rb +41 -0
  86. data/test/printers_test.rb +141 -0
  87. data/test/printing_recursive_graph_test.rb +81 -0
  88. data/test/rack_test.rb +157 -0
  89. data/test/recursive_test.rb +210 -0
  90. data/test/singleton_test.rb +38 -0
  91. data/test/stack_printer_test.rb +64 -0
  92. data/test/start_stop_test.rb +109 -0
  93. data/test/test_helper.rb +24 -0
  94. data/test/thread_test.rb +144 -0
  95. data/test/unique_call_path_test.rb +190 -0
  96. data/test/yarv_test.rb +56 -0
  97. metadata +189 -0
@@ -0,0 +1,118 @@
1
+ # encoding: utf-8
2
+
3
+ module RubyProf
4
+ # This is the base class for all Printers. It is never used directly.
5
+ class AbstractPrinter
6
+ # :stopdoc:
7
+ def self.needs_dir?
8
+ false
9
+ end
10
+ # :startdoc:
11
+
12
+ # Create a new printer.
13
+ #
14
+ # result should be the output generated from a profiling run
15
+ def initialize(result)
16
+ @result = result
17
+ @output = nil
18
+ end
19
+
20
+ # Returns the min_percent of total time a method must take to be included in a profiling report
21
+ def min_percent
22
+ @options[:min_percent] || 0
23
+ end
24
+
25
+ # Returns the time format used to show when a profile was run
26
+ def time_format
27
+ '%A, %B %-d at %l:%M:%S %p (%Z)'
28
+ end
29
+
30
+ # Returns how profile data should be sorted
31
+ def sort_method
32
+ @options[:sort_method]
33
+ end
34
+
35
+ # Prints a report to the provided output.
36
+ #
37
+ # output - Any IO object, including STDOUT or a file.
38
+ # The default value is STDOUT.
39
+ #
40
+ # options - Hash of print options. Note that each printer can
41
+ # define its own set of options.
42
+ #
43
+ # :min_percent - Number 0 to 100 that specifes the minimum
44
+ # %self (the methods self time divided by the
45
+ # overall total time) that a method must take
46
+ # for it to be printed out in the report.
47
+ # Default value is 0.
48
+ #
49
+ # :sort_method - Specifies method used for sorting method infos.
50
+ # Available values are :total_time, :self_time,
51
+ # :wait_time, :children_time
52
+ # Default value is :total_time
53
+ def print(output = STDOUT, options = {})
54
+ @output = output
55
+ setup_options(options)
56
+ print_threads
57
+ end
58
+
59
+ # :nodoc:
60
+ def setup_options(options = {})
61
+ @options = options
62
+ end
63
+
64
+ def method_location(method)
65
+ if method.source_file
66
+ "#{method.source_file}:#{method.line}"
67
+ end
68
+ end
69
+
70
+ def print_threads
71
+ @result.threads.each do |thread|
72
+ print_thread(thread)
73
+ end
74
+ end
75
+
76
+ def print_thread(thread)
77
+ print_header(thread)
78
+ print_methods(thread)
79
+ print_footer(thread)
80
+ end
81
+
82
+ def print_header(thread)
83
+ @output << "Measure Mode: %s\n" % RubyProf.measure_mode_string
84
+ @output << "Thread ID: %d\n" % thread.id
85
+ @output << "Fiber ID: %d\n" % thread.fiber_id unless thread.id == thread.fiber_id
86
+ @output << "Total: %0.6f\n" % thread.total_time
87
+ @output << "Sort by: #{sort_method}\n"
88
+ @output << "\n"
89
+ print_column_headers
90
+ end
91
+
92
+ def print_column_headers
93
+ end
94
+
95
+ def print_footer(thread)
96
+ @output << <<~EOT
97
+
98
+ * recursively called methods
99
+
100
+ Columns are:
101
+
102
+ %self - The percentage of time spent in this method, derived from self_time/total_time.
103
+ total - The time spent in this method and its children.
104
+ self - The time spent in this method.
105
+ wait - The amount of time this method waited for other threads.
106
+ child - The time spent in this method's children.
107
+ calls - The number of times this method was called.
108
+ name - The name of the method.
109
+ location - The location of the method.
110
+
111
+ The interpretation of method names is:
112
+
113
+ * MyObject#test - An instance method "test" of the class "MyObject"
114
+ * <Object:MyObject>#test - The <> characters indicate a method on a singleton class.
115
+ EOT
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,51 @@
1
+ # encoding: utf-8
2
+
3
+ module RubyProf
4
+ # Prints out the call graph based on CallInfo instances. This
5
+ # is mainly for debugging purposes as it provides access into
6
+ # into RubyProf's internals.
7
+ #
8
+ # To use the printer:
9
+ #
10
+ # result = RubyProf.profile do
11
+ # [code to profile]
12
+ # end
13
+ #
14
+ # printer = RubyProf::CallInfoPrinter.new(result)
15
+ # printer.print(STDOUT)
16
+ class CallInfoPrinter < AbstractPrinter
17
+ TIME_WIDTH = 0
18
+
19
+ private
20
+
21
+ def print_header(thread)
22
+ @output << "Thread ID: #{thread.id}\n"
23
+ @output << "Fiber ID: #{thread.fiber_id}\n"
24
+ @output << "Total Time: #{thread.total_time}\n"
25
+ @output << "Sort by: #{sort_method}\n"
26
+ @output << "\n"
27
+ end
28
+
29
+ def print_methods(thread)
30
+ visitor = CallInfoVisitor.new(thread.root_methods)
31
+
32
+ visitor.visit do |call_info, event|
33
+ if event == :enter
34
+ @output << " " * call_info.depth
35
+ @output << call_info.target.full_name
36
+ @output << " ("
37
+ @output << "tt:#{sprintf("%#{TIME_WIDTH}.2f", call_info.total_time)}, "
38
+ @output << "st:#{sprintf("%#{TIME_WIDTH}.2f", call_info.self_time)}, "
39
+ @output << "wt:#{sprintf("%#{TIME_WIDTH}.2f", call_info.wait_time)}, "
40
+ @output << "ct:#{sprintf("%#{TIME_WIDTH}.2f", call_info.children_time)}, "
41
+ @output << "call:#{call_info.called}, "
42
+ @output << ")"
43
+ @output << "\n"
44
+ end
45
+ end
46
+ end
47
+
48
+ def print_footer(thread)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,269 @@
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
+ @output = output
48
+ setup_options(options)
49
+ if @graph_html = options.delete(:graph)
50
+ @graph_html = "file://" + @graph_html if @graph_html[0]=="/"
51
+ end
52
+
53
+ print_header
54
+
55
+ @overall_threads_time = @result.threads.inject(0) do |val, thread|
56
+ val += thread.total_time
57
+ end
58
+
59
+ @result.threads.each do |thread|
60
+ @current_thread_id = thread.fiber_id
61
+ @overall_time = thread.total_time
62
+ thread_info = String.new("Thread: #{thread.id}")
63
+ thread_info << ", Fiber: #{thread.fiber_id}" unless thread.id == thread.fiber_id
64
+ thread_info << " (#{"%4.2f%%" % ((@overall_time/@overall_threads_time)*100)} ~ #{@overall_time})"
65
+ @output.print "<div class=\"thread\">#{thread_info}</div>"
66
+ @output.print "<ul name=\"thread\">"
67
+ thread.methods.each do |m|
68
+ # $stderr.print m.dump
69
+ next unless m.root?
70
+ m.callers.each do |call_info|
71
+ visited = Set.new
72
+ print_stack(visited, call_info, thread.total_time)
73
+ end
74
+ end
75
+ @output.print "</ul>"
76
+ end
77
+
78
+ print_footer
79
+ end
80
+
81
+ def print_stack(visited, call_info, parent_time)
82
+ total_time = call_info.total_time
83
+ percent_parent = (total_time/parent_time)*100
84
+ percent_total = (total_time/@overall_time)*100
85
+ return unless percent_total > min_percent
86
+ color = self.color(percent_total)
87
+ kids = call_info.target.callees
88
+ visible = percent_total >= threshold
89
+ expanded = percent_total >= expansion
90
+ display = visible ? "block" : "none"
91
+ @output.print "<li class=\"color#{color}\" style=\"display:#{display}\">"
92
+
93
+ if kids.empty?
94
+ @output.print "<a href=\"#\" class=\"toggle empty\" ></a>"
95
+ else
96
+ visible_children = kids.any?{|ci| (ci.total_time/@overall_time)*100 >= threshold}
97
+ image = visible_children ? (expanded ? "minus" : "plus") : "empty"
98
+ @output.print "<a href=\"#\" class=\"toggle #{image}\" ></a>"
99
+ end
100
+ @output.printf "<span> %4.2f%% (%4.2f%%) %s %s</span>\n", percent_total, percent_parent, link(call_info), graph_link(call_info)
101
+
102
+ if visited.include?(call_info)
103
+ return
104
+ else
105
+ visited << call_info
106
+ end
107
+
108
+ unless kids.empty?
109
+ if expanded
110
+ @output.print "<ul>"
111
+ else
112
+ @output.print '<ul style="display:none">'
113
+ end
114
+ kids.sort_by{|c| -c.total_time}.each do |child_call_info|
115
+ print_stack(visited, child_call_info, total_time)
116
+ end
117
+ @output.print "</ul>"
118
+ end
119
+ @output.print "</li>"
120
+ end
121
+
122
+ def name(call_info)
123
+ method = call_info.target
124
+ method.full_name
125
+ end
126
+
127
+ def link(call_info)
128
+ method = call_info.target
129
+ if method.source_file.nil?
130
+ h(name(call_info))
131
+ else
132
+ file = File.expand_path(method.source_file)
133
+ "<a href=\"file://#{file}##{method.line}\">#{h(name(call_info))}</a>"
134
+ end
135
+ end
136
+
137
+ def graph_link(call_info)
138
+ total_calls = call_info.target.callers.inject(0){|t, ci| t += ci.called}
139
+ href = "#{@graph_html}##{method_href(call_info.target)}"
140
+ totals = @graph_html ? "<a href='#{href}'>#{total_calls}</a>" : total_calls.to_s
141
+ "[#{call_info.called} calls, #{totals} total]"
142
+ end
143
+
144
+ def method_href(method)
145
+ h(method.full_name.gsub(/[><#\.\?=:]/,"_") + "_" + @current_thread_id.to_s)
146
+ end
147
+
148
+ def total_time(call_infos)
149
+ sum(call_infos.map{|ci| ci.total_time})
150
+ end
151
+
152
+ def sum(a)
153
+ a.inject(0.0){|s,t| s+=t}
154
+ end
155
+
156
+ def dump(ci)
157
+ $stderr.printf "%s/%d t:%f s:%f w:%f \n", ci, ci.object_id, ci.total_time, ci.self_time, ci.wait_time
158
+ end
159
+
160
+ def color(p)
161
+ case i = p.to_i
162
+ when 0..5
163
+ "01"
164
+ when 5..10
165
+ "05"
166
+ when 100
167
+ "9"
168
+ else
169
+ "#{i/10}"
170
+ end
171
+ end
172
+
173
+ def application
174
+ @options[:application] || $PROGRAM_NAME
175
+ end
176
+
177
+ def arguments
178
+ ARGV.join(' ')
179
+ end
180
+
181
+ def title
182
+ @title ||= @options.delete(:title) || "ruby-prof call tree"
183
+ end
184
+
185
+ def threshold
186
+ @options[:threshold] || 1.0
187
+ end
188
+
189
+ def expansion
190
+ @options[:expansion] || 10.0
191
+ end
192
+
193
+ def print_header
194
+ @output.puts "<html><head>"
195
+ @output.puts '<meta http-equiv="content-type" content="text/html; charset=utf-8">'
196
+ @output.puts "<title>#{h title}</title>"
197
+ print_css
198
+ print_java_script
199
+ @output.puts '</head><body><div style="display: inline-block;">'
200
+ print_title_bar
201
+ print_commands
202
+ print_help
203
+ end
204
+
205
+ def print_footer
206
+ @output.puts '<div id="sentinel"></div></div></body></html>'
207
+ end
208
+
209
+ def open_asset(file)
210
+ path = File.join(File.expand_path('../../assets', __FILE__), file)
211
+ File.open(path, 'rb').read
212
+ end
213
+
214
+ def print_css
215
+ html = open_asset('call_stack_printer.css.html')
216
+ @output.puts html.gsub('%s', base64_image)
217
+ end
218
+
219
+ def base64_image
220
+ file = open_asset('call_stack_printer.png')
221
+ Base64.encode64(file).gsub(/\n/, '')
222
+ end
223
+
224
+ def print_java_script
225
+ html = open_asset('call_stack_printer.js.html')
226
+ @output.puts html
227
+ end
228
+
229
+ def print_title_bar
230
+ @output.puts <<-"end_title_bar"
231
+ <div id="titlebar">
232
+ Call tree for application <b>#{h application} #{h arguments}</b><br/>
233
+ Generated on #{Time.now} with options #{h @options.inspect}<br/>
234
+ </div>
235
+ end_title_bar
236
+ end
237
+
238
+ def print_commands
239
+ @output.puts <<-"end_commands"
240
+ <div id=\"commands\">
241
+ <span style=\"font-size: 11pt; font-weight: bold;\">Threshold:</span>
242
+ <input value=\"#{h threshold}\" size=\"3\" id=\"threshold\" type=\"text\">
243
+ <input value=\"Apply\" onclick=\"setThreshold();\" type=\"submit\">
244
+ <input value=\"Expand All\" onclick=\"expandAll(event);\" type=\"submit\">
245
+ <input value=\"Collapse All\" onclick=\"collapseAll(event);\" type=\"submit\">
246
+ <input value=\"Show Help\" onclick=\"toggleHelp(this);\" type=\"submit\">
247
+ </div>
248
+ end_commands
249
+ end
250
+
251
+ def print_help
252
+ @output.puts <<-'end_help'
253
+ <div style="display: none;" id="help">
254
+ &#8226; Enter a decimal value <i>d</i> into the threshold field and click "Apply"
255
+ to hide all nodes marked with time values lower than <i>d</i>.<br>
256
+ &#8226; Click on "Expand All" for full tree expansion.<br>
257
+ &#8226; Click on "Collapse All" to show only top level nodes.<br>
258
+ &#8226; Use a, s, d, w as in Quake or Urban Terror to navigate the tree.<br>
259
+ &#8226; Use f and b to navigate the tree in preorder forward and backwards.<br>
260
+ &#8226; Use x to toggle visibility of a subtree.<br>
261
+ &#8226; Use * to expand/collapse a whole subtree.<br>
262
+ &#8226; Use h to navigate to thread root.<br>
263
+ &#8226; Use n and p to navigate between threads.<br>
264
+ &#8226; Click on background to move focus to a subtree.<br>
265
+ </div>
266
+ end_help
267
+ end
268
+ end
269
+ end
@@ -0,0 +1,151 @@
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