ruby-prof 0.18.0-x64-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES +500 -0
  3. data/LICENSE +25 -0
  4. data/README.rdoc +487 -0
  5. data/Rakefile +113 -0
  6. data/bin/ruby-prof +345 -0
  7. data/bin/ruby-prof-check-trace +45 -0
  8. data/examples/flat.txt +50 -0
  9. data/examples/graph.dot +84 -0
  10. data/examples/graph.html +823 -0
  11. data/examples/graph.txt +139 -0
  12. data/examples/multi.flat.txt +23 -0
  13. data/examples/multi.graph.html +760 -0
  14. data/examples/multi.grind.dat +114 -0
  15. data/examples/multi.stack.html +547 -0
  16. data/examples/stack.html +547 -0
  17. data/ext/ruby_prof/extconf.rb +68 -0
  18. data/ext/ruby_prof/rp_call_info.c +425 -0
  19. data/ext/ruby_prof/rp_call_info.h +53 -0
  20. data/ext/ruby_prof/rp_measure.c +40 -0
  21. data/ext/ruby_prof/rp_measure.h +45 -0
  22. data/ext/ruby_prof/rp_measure_allocations.c +76 -0
  23. data/ext/ruby_prof/rp_measure_cpu_time.c +136 -0
  24. data/ext/ruby_prof/rp_measure_gc_runs.c +73 -0
  25. data/ext/ruby_prof/rp_measure_gc_time.c +60 -0
  26. data/ext/ruby_prof/rp_measure_memory.c +77 -0
  27. data/ext/ruby_prof/rp_measure_process_time.c +71 -0
  28. data/ext/ruby_prof/rp_measure_wall_time.c +45 -0
  29. data/ext/ruby_prof/rp_method.c +630 -0
  30. data/ext/ruby_prof/rp_method.h +75 -0
  31. data/ext/ruby_prof/rp_stack.c +173 -0
  32. data/ext/ruby_prof/rp_stack.h +63 -0
  33. data/ext/ruby_prof/rp_thread.c +277 -0
  34. data/ext/ruby_prof/rp_thread.h +27 -0
  35. data/ext/ruby_prof/ruby_prof.c +794 -0
  36. data/ext/ruby_prof/ruby_prof.h +60 -0
  37. data/ext/ruby_prof/vc/ruby_prof.sln +31 -0
  38. data/ext/ruby_prof/vc/ruby_prof.vcxproj +141 -0
  39. data/lib/2.6.3/ruby_prof.so +0 -0
  40. data/lib/ruby-prof.rb +68 -0
  41. data/lib/ruby-prof/aggregate_call_info.rb +76 -0
  42. data/lib/ruby-prof/assets/call_stack_printer.css.html +117 -0
  43. data/lib/ruby-prof/assets/call_stack_printer.js.html +385 -0
  44. data/lib/ruby-prof/assets/call_stack_printer.png +0 -0
  45. data/lib/ruby-prof/call_info.rb +115 -0
  46. data/lib/ruby-prof/call_info_visitor.rb +40 -0
  47. data/lib/ruby-prof/compatibility.rb +179 -0
  48. data/lib/ruby-prof/method_info.rb +121 -0
  49. data/lib/ruby-prof/printers/abstract_printer.rb +104 -0
  50. data/lib/ruby-prof/printers/call_info_printer.rb +41 -0
  51. data/lib/ruby-prof/printers/call_stack_printer.rb +265 -0
  52. data/lib/ruby-prof/printers/call_tree_printer.rb +143 -0
  53. data/lib/ruby-prof/printers/dot_printer.rb +132 -0
  54. data/lib/ruby-prof/printers/flat_printer.rb +70 -0
  55. data/lib/ruby-prof/printers/flat_printer_with_line_numbers.rb +83 -0
  56. data/lib/ruby-prof/printers/graph_html_printer.rb +249 -0
  57. data/lib/ruby-prof/printers/graph_printer.rb +116 -0
  58. data/lib/ruby-prof/printers/multi_printer.rb +84 -0
  59. data/lib/ruby-prof/profile.rb +26 -0
  60. data/lib/ruby-prof/profile/exclude_common_methods.rb +207 -0
  61. data/lib/ruby-prof/profile/legacy_method_elimination.rb +50 -0
  62. data/lib/ruby-prof/rack.rb +174 -0
  63. data/lib/ruby-prof/task.rb +147 -0
  64. data/lib/ruby-prof/thread.rb +35 -0
  65. data/lib/ruby-prof/version.rb +3 -0
  66. data/lib/unprof.rb +10 -0
  67. data/ruby-prof.gemspec +58 -0
  68. data/test/abstract_printer_test.rb +53 -0
  69. data/test/aggregate_test.rb +136 -0
  70. data/test/basic_test.rb +128 -0
  71. data/test/block_test.rb +74 -0
  72. data/test/call_info_test.rb +78 -0
  73. data/test/call_info_visitor_test.rb +31 -0
  74. data/test/duplicate_names_test.rb +32 -0
  75. data/test/dynamic_method_test.rb +55 -0
  76. data/test/enumerable_test.rb +21 -0
  77. data/test/exceptions_test.rb +24 -0
  78. data/test/exclude_methods_test.rb +146 -0
  79. data/test/exclude_threads_test.rb +53 -0
  80. data/test/fiber_test.rb +79 -0
  81. data/test/issue137_test.rb +63 -0
  82. data/test/line_number_test.rb +80 -0
  83. data/test/measure_allocations_test.rb +26 -0
  84. data/test/measure_cpu_time_test.rb +212 -0
  85. data/test/measure_gc_runs_test.rb +32 -0
  86. data/test/measure_gc_time_test.rb +36 -0
  87. data/test/measure_memory_test.rb +33 -0
  88. data/test/measure_process_time_test.rb +61 -0
  89. data/test/measure_wall_time_test.rb +255 -0
  90. data/test/method_elimination_test.rb +84 -0
  91. data/test/module_test.rb +45 -0
  92. data/test/multi_printer_test.rb +104 -0
  93. data/test/no_method_class_test.rb +15 -0
  94. data/test/pause_resume_test.rb +166 -0
  95. data/test/prime.rb +54 -0
  96. data/test/printers_test.rb +275 -0
  97. data/test/printing_recursive_graph_test.rb +127 -0
  98. data/test/rack_test.rb +157 -0
  99. data/test/recursive_test.rb +215 -0
  100. data/test/singleton_test.rb +38 -0
  101. data/test/stack_printer_test.rb +77 -0
  102. data/test/stack_test.rb +138 -0
  103. data/test/start_stop_test.rb +112 -0
  104. data/test/test_helper.rb +267 -0
  105. data/test/thread_test.rb +187 -0
  106. data/test/unique_call_path_test.rb +202 -0
  107. data/test/yarv_test.rb +55 -0
  108. metadata +199 -0
@@ -0,0 +1,265 @@
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
+ # :editor_uri - Specifies editor uri scheme used for opening files
40
+ # e.g. :atm or :mvim. For OS X default is :txmt.
41
+ # Use RUBY_PROF_EDITOR_URI environment variable to overide.
42
+ def print(output = STDOUT, options = {})
43
+ @output = output
44
+ setup_options(options)
45
+ @editor = editor_uri
46
+ if @graph_html = options.delete(:graph)
47
+ @graph_html = "file://" + @graph_html if @graph_html[0]=="/"
48
+ end
49
+
50
+ print_header
51
+
52
+ @overall_threads_time = @result.threads.inject(0) do |val, thread|
53
+ val += thread.total_time
54
+ end
55
+
56
+ @result.threads.each do |thread|
57
+ @current_thread_id = thread.fiber_id
58
+ @overall_time = thread.total_time
59
+ thread_info = String.new("Thread: #{thread.id}")
60
+ thread_info << ", Fiber: #{thread.fiber_id}" unless thread.id == thread.fiber_id
61
+ thread_info << " (#{"%4.2f%%" % ((@overall_time/@overall_threads_time)*100)} ~ #{@overall_time})"
62
+ @output.print "<div class=\"thread\">#{thread_info}</div>"
63
+ @output.print "<ul name=\"thread\">"
64
+ thread.methods.each do |m|
65
+ # $stderr.print m.dump
66
+ next unless m.root?
67
+ m.call_infos.each do |ci|
68
+ next unless ci.root?
69
+ print_stack ci, thread.total_time
70
+ end
71
+ end
72
+ @output.print "</ul>"
73
+ end
74
+
75
+ print_footer
76
+
77
+ end
78
+
79
+ def print_stack(call_info, parent_time)
80
+ total_time = call_info.total_time
81
+ percent_parent = (total_time/parent_time)*100
82
+ percent_total = (total_time/@overall_time)*100
83
+ return unless percent_total > min_percent
84
+ color = self.color(percent_total)
85
+ kids = call_info.children
86
+ visible = percent_total >= threshold
87
+ expanded = percent_total >= expansion
88
+ display = visible ? "block" : "none"
89
+ @output.print "<li class=\"color#{color}\" style=\"display:#{display}\">"
90
+ if kids.empty?
91
+ @output.print "<a href=\"#\" class=\"toggle empty\" ></a>"
92
+ else
93
+ visible_children = kids.any?{|ci| (ci.total_time/@overall_time)*100 >= threshold}
94
+ image = visible_children ? (expanded ? "minus" : "plus") : "empty"
95
+ @output.print "<a href=\"#\" class=\"toggle #{image}\" ></a>"
96
+ end
97
+ @output.printf "<span> %4.2f%% (%4.2f%%) %s %s</span>\n", percent_total, percent_parent, link(call_info), graph_link(call_info)
98
+ unless kids.empty?
99
+ if expanded
100
+ @output.print "<ul>"
101
+ else
102
+ @output.print '<ul style="display:none">'
103
+ end
104
+ kids.sort_by{|c| -c.total_time}.each do |callinfo|
105
+ print_stack callinfo, total_time
106
+ end
107
+ @output.print "</ul>"
108
+ end
109
+ @output.print "</li>"
110
+ end
111
+
112
+ def name(call_info)
113
+ method = call_info.target
114
+ method.full_name
115
+ end
116
+
117
+ def link(call_info)
118
+ method = call_info.target
119
+ file = File.expand_path(method.source_file)
120
+ if file =~ /\/ruby_runtime$/
121
+ h(name(call_info))
122
+ else
123
+ if @editor
124
+ "<a href=\"#{@editor}://" \
125
+ "open?url=file://#{file}&line=#{method.line}\">" \
126
+ "#{h(name(call_info))}</a>"
127
+ else
128
+ "<a href=\"file://#{file}##{method.line}\">#{h(name(call_info))}</a>"
129
+ end
130
+ end
131
+ end
132
+
133
+ def graph_link(call_info)
134
+ total_calls = call_info.target.call_infos.inject(0){|t, ci| t += ci.called}
135
+ href = "#{@graph_html}##{method_href(call_info.target)}"
136
+ totals = @graph_html ? "<a href='#{href}'>#{total_calls}</a>" : total_calls.to_s
137
+ "[#{call_info.called} calls, #{totals} total]"
138
+ end
139
+
140
+ def method_href(method)
141
+ h(method.full_name.gsub(/[><#\.\?=:]/,"_") + "_" + @current_thread_id.to_s)
142
+ end
143
+
144
+ def total_time(call_infos)
145
+ sum(call_infos.map{|ci| ci.total_time})
146
+ end
147
+
148
+ def sum(a)
149
+ a.inject(0.0){|s,t| s+=t}
150
+ end
151
+
152
+ def dump(ci)
153
+ $stderr.printf "%s/%d t:%f s:%f w:%f \n", ci, ci.object_id, ci.total_time, ci.self_time, ci.wait_time
154
+ end
155
+
156
+ def color(p)
157
+ case i = p.to_i
158
+ when 0..5
159
+ "01"
160
+ when 5..10
161
+ "05"
162
+ when 100
163
+ "9"
164
+ else
165
+ "#{i/10}"
166
+ end
167
+ end
168
+
169
+ def application
170
+ @options[:application] || $PROGRAM_NAME
171
+ end
172
+
173
+ def arguments
174
+ ARGV.join(' ')
175
+ end
176
+
177
+ def title
178
+ @title ||= @options.delete(:title) || "ruby-prof call tree"
179
+ end
180
+
181
+ def threshold
182
+ @options[:threshold] || 1.0
183
+ end
184
+
185
+ def expansion
186
+ @options[:expansion] || 10.0
187
+ end
188
+
189
+ def print_header
190
+ @output.puts "<html><head>"
191
+ @output.puts '<meta http-equiv="content-type" content="text/html; charset=utf-8">'
192
+ @output.puts "<title>#{h title}</title>"
193
+ print_css
194
+ print_java_script
195
+ @output.puts '</head><body><div style="display: inline-block;">'
196
+ print_title_bar
197
+ print_commands
198
+ print_help
199
+ end
200
+
201
+ def print_footer
202
+ @output.puts '<div id="sentinel"></div></div></body></html>'
203
+ end
204
+
205
+ def open_asset(file)
206
+ path = File.join(File.expand_path('../../assets', __FILE__), file)
207
+ File.open(path, 'rb').read
208
+ end
209
+
210
+ def print_css
211
+ html = open_asset('call_stack_printer.css.html')
212
+ @output.puts html.gsub('%s', base64_image)
213
+ end
214
+
215
+ def base64_image
216
+ file = open_asset('call_stack_printer.png')
217
+ Base64.encode64(file).gsub(/\n/, '')
218
+ end
219
+
220
+ def print_java_script
221
+ html = open_asset('call_stack_printer.js.html')
222
+ @output.puts html
223
+ end
224
+
225
+ def print_title_bar
226
+ @output.puts <<-"end_title_bar"
227
+ <div id="titlebar">
228
+ Call tree for application <b>#{h application} #{h arguments}</b><br/>
229
+ Generated on #{Time.now} with options #{h @options.inspect}<br/>
230
+ </div>
231
+ end_title_bar
232
+ end
233
+
234
+ def print_commands
235
+ @output.puts <<-"end_commands"
236
+ <div id=\"commands\">
237
+ <span style=\"font-size: 11pt; font-weight: bold;\">Threshold:</span>
238
+ <input value=\"#{h threshold}\" size=\"3\" id=\"threshold\" type=\"text\">
239
+ <input value=\"Apply\" onclick=\"setThreshold();\" type=\"submit\">
240
+ <input value=\"Expand All\" onclick=\"expandAll(event);\" type=\"submit\">
241
+ <input value=\"Collapse All\" onclick=\"collapseAll(event);\" type=\"submit\">
242
+ <input value=\"Show Help\" onclick=\"toggleHelp(this);\" type=\"submit\">
243
+ </div>
244
+ end_commands
245
+ end
246
+
247
+ def print_help
248
+ @output.puts <<-'end_help'
249
+ <div style="display: none;" id="help">
250
+ &#8226; Enter a decimal value <i>d</i> into the threshold field and click "Apply"
251
+ to hide all nodes marked with time values lower than <i>d</i>.<br>
252
+ &#8226; Click on "Expand All" for full tree expansion.<br>
253
+ &#8226; Click on "Collapse All" to show only top level nodes.<br>
254
+ &#8226; Use a, s, d, w as in Quake or Urban Terror to navigate the tree.<br>
255
+ &#8226; Use f and b to navigate the tree in preorder forward and backwards.<br>
256
+ &#8226; Use x to toggle visibility of a subtree.<br>
257
+ &#8226; Use * to expand/collapse a whole subtree.<br>
258
+ &#8226; Use h to navigate to thread root.<br>
259
+ &#8226; Use n and p to navigate between threads.<br>
260
+ &#8226; Click on background to move focus to a subtree.<br>
261
+ </div>
262
+ end_help
263
+ end
264
+ end
265
+ end
@@ -0,0 +1,143 @@
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
+ validate_print_params(options)
48
+ setup_options(options)
49
+ determine_event_specification_and_value_scale
50
+ print_threads
51
+ end
52
+
53
+ def validate_print_params(options)
54
+ if options.is_a?(IO)
55
+ raise ArgumentError, "#{self.class.name}#print cannot print to IO objects"
56
+ elsif !options.is_a?(Hash)
57
+ raise ArgumentError, "#{self.class.name}#print requires an options hash"
58
+ end
59
+ end
60
+
61
+ def print_threads
62
+ remove_subsidiary_files_from_previous_profile_runs
63
+ # TODO: merge fibers of a given thread here, instead of relying
64
+ # on the profiler to merge fibers.
65
+ @result.threads.each do |thread|
66
+ print_thread(thread)
67
+ end
68
+ end
69
+
70
+ def convert(value)
71
+ (value * @value_scale).round
72
+ end
73
+
74
+ def file(method)
75
+ File.expand_path(method.source_file)
76
+ end
77
+
78
+ def print_thread(thread)
79
+ File.open(file_path_for_thread(thread), "w") do |f|
80
+ print_headers(f, thread)
81
+ thread.methods.reverse_each do |method|
82
+ print_method(f, method)
83
+ end
84
+ end
85
+ end
86
+
87
+ def path
88
+ @options[:path] || "."
89
+ end
90
+
91
+ def self.needs_dir?
92
+ true
93
+ end
94
+
95
+ def base_name
96
+ @options[:profile] || "profile"
97
+ end
98
+
99
+ def remove_subsidiary_files_from_previous_profile_runs
100
+ pattern = [base_name, "callgrind.out", $$, "*"].join(".")
101
+ files = Dir.glob(File.join(path, pattern))
102
+ FileUtils.rm_f(files)
103
+ end
104
+
105
+ def file_name_for_thread(thread)
106
+ if thread.fiber_id == Fiber.current.object_id
107
+ [base_name, "callgrind.out", $$].join(".")
108
+ else
109
+ [base_name, "callgrind.out", $$, thread.fiber_id].join(".")
110
+ end
111
+ end
112
+
113
+ def file_path_for_thread(thread)
114
+ File.join(path, file_name_for_thread(thread))
115
+ end
116
+
117
+ def print_headers(output, thread)
118
+ output << "#{@event_specification}\n\n"
119
+ # this doesn't work. kcachegrind does not fully support the spec.
120
+ # output << "thread: #{thread.id}\n\n"
121
+ end
122
+
123
+ def print_method(output, method)
124
+ # Print out the file and method name
125
+ output << "fl=#{file(method)}\n"
126
+ output << "fn=#{method.calltree_name}\n"
127
+
128
+ # Now print out the function line number and its self time
129
+ output << "#{method.line} #{convert(method.self_time)}\n"
130
+
131
+ # Now print out all the children methods
132
+ method.children.each do |callee|
133
+ output << "cfl=#{file(callee.target)}\n"
134
+ output << "cfn=#{callee.target.calltree_name}\n"
135
+ output << "calls=#{callee.called} #{callee.line}\n"
136
+
137
+ # Print out total times here!
138
+ output << "#{callee.line} #{convert(callee.total_time)}\n"
139
+ end
140
+ output << "\n"
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,132 @@
1
+ # encoding: utf-8
2
+
3
+ require 'set'
4
+
5
+ module RubyProf
6
+ # Generates a graphviz graph in dot format.
7
+ # To use the dot printer:
8
+ #
9
+ # result = RubyProf.profile do
10
+ # [code to profile]
11
+ # end
12
+ #
13
+ # printer = RubyProf::DotPrinter.new(result)
14
+ # printer.print(STDOUT)
15
+ #
16
+ # You can use either dot viewer such as GraphViz, or the dot command line tool
17
+ # to reformat the output into a wide variety of outputs:
18
+ #
19
+ # dot -Tpng graph.dot > graph.png
20
+ #
21
+ class DotPrinter < RubyProf::AbstractPrinter
22
+ CLASS_COLOR = '"#666666"'
23
+ EDGE_COLOR = '"#666666"'
24
+
25
+ # Creates the DotPrinter using a RubyProf::Proile.
26
+ def initialize(result)
27
+ super(result)
28
+ @seen_methods = Set.new
29
+ end
30
+
31
+ # Print a graph report to the provided output.
32
+ #
33
+ # output - Any IO object, including STDOUT or a file. The default value is
34
+ # STDOUT.
35
+ #
36
+ # options - Hash of print options. See #setup_options
37
+ # for more information.
38
+ #
39
+ # When profiling results that cover a large number of method calls it
40
+ # helps to use the :min_percent option, for example:
41
+ #
42
+ # DotPrinter.new(result).print(STDOUT, :min_percent=>5)
43
+ #
44
+ def print(output = STDOUT, options = {})
45
+ @output = output
46
+ setup_options(options)
47
+
48
+ puts 'digraph "Profile" {'
49
+ #puts "label=\"#{mode_name} >=#{min_percent}%\\nTotal: #{total_time}\";"
50
+ puts "labelloc=t;"
51
+ puts "labeljust=l;"
52
+ print_threads
53
+ puts '}'
54
+ end
55
+
56
+ private
57
+
58
+ # Something of a hack, figure out which constant went with the
59
+ # RubyProf.measure_mode so that we can display it. Otherwise it's easy to
60
+ # forget what measurement was made.
61
+ def mode_name
62
+ RubyProf.constants.find{|c| RubyProf.const_get(c) == RubyProf.measure_mode}
63
+ end
64
+
65
+ def print_threads
66
+ @result.threads.each do |thread|
67
+ puts "subgraph \"Thread #{thread.id}\" {"
68
+
69
+ print_thread(thread)
70
+ puts "}"
71
+
72
+ print_classes(thread)
73
+ end
74
+ end
75
+
76
+ # Determines an ID to use to represent the subject in the Dot file.
77
+ def dot_id(subject)
78
+ subject.object_id
79
+ end
80
+
81
+ def print_thread(thread)
82
+ total_time = thread.total_time
83
+ thread.methods.sort_by(&sort_method).reverse_each do |method|
84
+ total_percentage = (method.total_time/total_time) * 100
85
+
86
+ next if total_percentage < min_percent
87
+ name = method_name(method).split("#").last
88
+ puts "#{dot_id(method)} [label=\"#{name}\\n(#{total_percentage.round}%)\"];"
89
+ @seen_methods << method
90
+ print_edges(total_time, method)
91
+ end
92
+ end
93
+
94
+ def print_classes(thread)
95
+ grouped = {}
96
+ thread.methods.each{|m| grouped[m.klass_name] ||= []; grouped[m.klass_name] << m}
97
+ grouped.each do |cls, methods2|
98
+ # Filter down to just seen methods
99
+ big_methods = methods2.select{|m| @seen_methods.include? m}
100
+
101
+ if !big_methods.empty?
102
+ puts "subgraph cluster_#{cls.object_id} {"
103
+ puts "label = \"#{cls}\";"
104
+ puts "fontcolor = #{CLASS_COLOR};"
105
+ puts "fontsize = 16;"
106
+ puts "color = #{CLASS_COLOR};"
107
+ big_methods.each do |m|
108
+ puts "#{m.object_id};"
109
+ end
110
+ puts "}"
111
+ end
112
+ end
113
+ end
114
+
115
+ def print_edges(total_time, method)
116
+ method.aggregate_children.sort_by(&:total_time).reverse.each do |child|
117
+
118
+ target_percentage = (child.target.total_time / total_time) * 100.0
119
+ next if target_percentage < min_percent
120
+
121
+ # Get children method
122
+ puts "#{dot_id(method)} -> #{dot_id(child.target)} [label=\"#{child.called}/#{child.target.called}\" fontsize=10 fontcolor=#{EDGE_COLOR}];"
123
+ end
124
+ end
125
+
126
+ # Silly little helper for printing to the @output
127
+ def puts(str)
128
+ @output.puts(str)
129
+ end
130
+
131
+ end
132
+ end