ruby-prof 1.7.2 → 2.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 (121) hide show
  1. checksums.yaml +4 -4
  2. data/{CHANGES → CHANGELOG.md} +112 -178
  3. data/README.md +5 -5
  4. data/bin/ruby-prof +1 -4
  5. data/docs/advanced-usage.md +132 -0
  6. data/docs/alternatives.md +98 -0
  7. data/docs/architecture.md +122 -0
  8. data/docs/best-practices.md +27 -0
  9. data/docs/getting-started.md +130 -0
  10. data/docs/history.md +11 -0
  11. data/docs/index.md +45 -0
  12. data/docs/profiling-rails.md +64 -0
  13. data/docs/public/examples/example.rb +33 -0
  14. data/docs/public/examples/generate_reports.rb +92 -0
  15. data/docs/public/examples/reports/call_info.txt +27 -0
  16. data/docs/public/examples/reports/call_stack.html +835 -0
  17. data/docs/public/examples/reports/callgrind.out +150 -0
  18. data/docs/public/examples/reports/flame_graph.html +408 -0
  19. data/docs/public/examples/reports/flat.txt +45 -0
  20. data/docs/public/examples/reports/graph.dot +129 -0
  21. data/docs/public/examples/reports/graph.html +1319 -0
  22. data/docs/public/examples/reports/graph.txt +100 -0
  23. data/docs/public/examples/reports/graphviz_viewer.html +1 -0
  24. data/docs/public/images/call_stack.png +0 -0
  25. data/docs/public/images/class_diagram.png +0 -0
  26. data/docs/public/images/dot_printer.png +0 -0
  27. data/docs/public/images/flame_graph.png +0 -0
  28. data/docs/public/images/flat.png +0 -0
  29. data/docs/public/images/graph.png +0 -0
  30. data/docs/public/images/graph_html.png +0 -0
  31. data/docs/public/images/ruby-prof-logo.svg +1 -0
  32. data/docs/reports.md +150 -0
  33. data/docs/stylesheets/extra.css +80 -0
  34. data/ext/ruby_prof/rp_allocation.c +0 -15
  35. data/ext/ruby_prof/rp_allocation.h +29 -33
  36. data/ext/ruby_prof/rp_call_tree.c +3 -0
  37. data/ext/ruby_prof/rp_call_tree.h +1 -4
  38. data/ext/ruby_prof/rp_call_trees.h +1 -4
  39. data/ext/ruby_prof/rp_measurement.c +0 -5
  40. data/ext/ruby_prof/rp_measurement.h +49 -53
  41. data/ext/ruby_prof/rp_method.c +3 -0
  42. data/ext/ruby_prof/rp_method.h +1 -4
  43. data/ext/ruby_prof/rp_profile.c +1 -1
  44. data/ext/ruby_prof/rp_profile.h +1 -5
  45. data/ext/ruby_prof/rp_stack.h +50 -53
  46. data/ext/ruby_prof/rp_thread.h +1 -4
  47. data/ext/ruby_prof/ruby_prof.h +1 -4
  48. data/ext/ruby_prof/vc/ruby_prof.vcxproj +7 -8
  49. data/lib/ruby-prof/assets/call_stack_printer.html.erb +746 -711
  50. data/lib/ruby-prof/assets/flame_graph_printer.html.erb +412 -0
  51. data/lib/ruby-prof/assets/graph_printer.html.erb +355 -355
  52. data/lib/ruby-prof/call_tree.rb +57 -57
  53. data/lib/ruby-prof/call_tree_visitor.rb +36 -36
  54. data/lib/ruby-prof/measurement.rb +17 -17
  55. data/lib/ruby-prof/printers/abstract_printer.rb +19 -33
  56. data/lib/ruby-prof/printers/call_info_printer.rb +53 -53
  57. data/lib/ruby-prof/printers/call_stack_printer.rb +168 -180
  58. data/lib/ruby-prof/printers/call_tree_printer.rb +132 -145
  59. data/lib/ruby-prof/printers/dot_printer.rb +177 -132
  60. data/lib/ruby-prof/printers/flame_graph_printer.rb +79 -0
  61. data/lib/ruby-prof/printers/flat_printer.rb +52 -52
  62. data/lib/ruby-prof/printers/graph_html_printer.rb +62 -63
  63. data/lib/ruby-prof/printers/graph_printer.rb +112 -113
  64. data/lib/ruby-prof/printers/multi_printer.rb +134 -127
  65. data/lib/ruby-prof/profile.rb +13 -0
  66. data/lib/ruby-prof/rack.rb +114 -105
  67. data/lib/ruby-prof/task.rb +147 -147
  68. data/lib/ruby-prof/thread.rb +20 -20
  69. data/lib/ruby-prof/version.rb +1 -1
  70. data/lib/ruby-prof.rb +50 -52
  71. data/lib/unprof.rb +10 -10
  72. data/ruby-prof.gemspec +5 -5
  73. data/test/abstract_printer_test.rb +25 -27
  74. data/test/alias_test.rb +203 -117
  75. data/test/call_tree_builder.rb +126 -126
  76. data/test/call_tree_visitor_test.rb +27 -27
  77. data/test/call_trees_test.rb +66 -66
  78. data/test/duplicate_names_test.rb +32 -32
  79. data/test/dynamic_method_test.rb +50 -50
  80. data/test/exceptions_test.rb +24 -24
  81. data/test/exclude_threads_test.rb +48 -48
  82. data/test/fiber_test.rb +72 -72
  83. data/test/inverse_call_tree_test.rb +174 -174
  84. data/test/line_number_test.rb +138 -1
  85. data/test/marshal_test.rb +144 -145
  86. data/test/measure_allocations.rb +26 -26
  87. data/test/measure_allocations_test.rb +340 -1
  88. data/test/measure_process_time_test.rb +3098 -3142
  89. data/test/measure_times.rb +56 -56
  90. data/test/measure_wall_time_test.rb +511 -372
  91. data/test/measurement_test.rb +82 -82
  92. data/test/merge_test.rb +48 -48
  93. data/test/multi_printer_test.rb +52 -66
  94. data/test/no_method_class_test.rb +15 -15
  95. data/test/pause_resume_test.rb +171 -171
  96. data/test/prime.rb +54 -54
  97. data/test/prime_script.rb +5 -5
  98. data/test/printer_call_stack_test.rb +28 -27
  99. data/test/printer_call_tree_test.rb +30 -30
  100. data/test/printer_flame_graph_test.rb +82 -0
  101. data/test/printer_flat_test.rb +99 -99
  102. data/test/printer_graph_html_test.rb +62 -59
  103. data/test/printer_graph_test.rb +42 -40
  104. data/test/printers_test.rb +28 -44
  105. data/test/printing_recursive_graph_test.rb +81 -81
  106. data/test/profile_test.rb +101 -101
  107. data/test/rack_test.rb +103 -93
  108. data/test/recursive_test.rb +139 -139
  109. data/test/scheduler.rb +4 -0
  110. data/test/singleton_test.rb +39 -38
  111. data/test/stack_printer_test.rb +61 -61
  112. data/test/start_stop_test.rb +106 -106
  113. data/test/test_helper.rb +4 -0
  114. data/test/thread_test.rb +29 -29
  115. data/test/unique_call_path_test.rb +123 -123
  116. data/test/yarv_test.rb +56 -56
  117. metadata +53 -11
  118. data/ext/ruby_prof/rp_measure_memory.c +0 -46
  119. data/lib/ruby-prof/compatibility.rb +0 -113
  120. data/test/compatibility_test.rb +0 -49
  121. data/test/measure_memory_test.rb +0 -1193
@@ -1,180 +1,168 @@
1
- # encoding: utf-8
2
-
3
- require 'erb'
4
- require 'fileutils'
5
- require 'base64'
6
- require 'set'
7
- require 'stringio'
8
-
9
- module RubyProf
10
- # Prints a HTML visualization of the call tree.
11
- #
12
- # To use the printer:
13
- #
14
- # result = RubyProf.profile do
15
- # [code to profile]
16
- # end
17
- #
18
- # printer = RubyProf::CallStackPrinter.new(result)
19
- # printer.print(STDOUT)
20
-
21
- class CallStackPrinter < AbstractPrinter
22
- include ERB::Util
23
-
24
- # Specify print options.
25
- #
26
- # options - Hash table
27
- # :min_percent - Number 0 to 100 that specifes the minimum
28
- # %self (the methods self time divided by the
29
- # overall total time) that a method must take
30
- # for it to be printed out in the report.
31
- # Default value is 0.
32
- #
33
- # :threshold - a float from 0 to 100 that sets the threshold of
34
- # results displayed.
35
- # Default value is 1.0
36
- #
37
- # :title - a String to overide the default "ruby-prof call tree"
38
- # title of the report.
39
- #
40
- # :expansion - a float from 0 to 100 that sets the threshold of
41
- # results that are expanded, if the percent_total
42
- # exceeds it.
43
- # Default value is 10.0
44
- #
45
- # :application - a String to overide the name of the application,
46
- # as it appears on the report.
47
- def print(output = STDOUT, options = {})
48
- setup_options(options)
49
- output << @erb.result(binding)
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_tree, parent_time)
59
- total_time = call_tree.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
- visible = percent_total >= threshold
65
- expanded = percent_total >= expansion
66
- display = visible ? "block" : "none"
67
-
68
- output << "<li class=\"color#{color}\" style=\"display:#{display}\">" << "\n"
69
-
70
- if visited.include?(call_tree)
71
- output << "<a href=\"#\" class=\"toggle empty\" ></a>" << "\n"
72
- output << "<span>%s %s</span>" % [link(call_tree.target, true), graph_link(call_tree)] << "\n"
73
- else
74
- visited << call_tree
75
-
76
- if call_tree.children.empty?
77
- output << "<a href=\"#\" class=\"toggle empty\" ></a>" << "\n"
78
- else
79
- visible_children = call_tree.children.any?{|ci| (ci.total_time/@overall_time)*100 >= threshold}
80
- image = visible_children ? (expanded ? "minus" : "plus") : "empty"
81
- output << "<a href=\"#\" class=\"toggle #{image}\" ></a>" << "\n"
82
- end
83
- output << "<span>%4.2f%% (%4.2f%%) %s %s</span>" % [percent_total, percent_parent,
84
- link(call_tree.target, false), graph_link(call_tree)] << "\n"
85
-
86
- unless call_tree.children.empty?
87
- output << (expanded ? '<ul>' : '<ul style="display:none">') << "\n"
88
- call_tree.children.sort_by{|c| -c.total_time}.each do |child_call_tree|
89
- print_stack(output, visited, child_call_tree, total_time)
90
- end
91
- output << '</ul>' << "\n"
92
- end
93
-
94
- visited.delete(call_tree)
95
- end
96
- output << '</li>' << "\n"
97
- end
98
-
99
- def name(call_tree)
100
- method = call_tree.target
101
- method.full_name
102
- end
103
-
104
- def link(method, recursive)
105
- method_name = "#{recursive ? '*' : ''}#{method.full_name}"
106
- if method.source_file.nil?
107
- h method_name
108
- else
109
- file = File.expand_path(method.source_file)
110
- "<a href=\"file://#{file}##{method.line}\">#{h method_name}</a>"
111
- end
112
- end
113
-
114
- def graph_link(call_tree)
115
- total_calls = call_tree.target.called
116
- totals = total_calls.to_s
117
- "[#{call_tree.called} calls, #{totals} total]"
118
- end
119
-
120
- def method_href(method)
121
- h(method.full_name.gsub(/[><#\.\?=:]/,"_"))
122
- end
123
-
124
- def total_time(call_trees)
125
- sum(call_trees.map{|ci| ci.total_time})
126
- end
127
-
128
- def sum(a)
129
- a.inject(0.0){|s,t| s+=t}
130
- end
131
-
132
- def dump(ci)
133
- $stderr.printf "%s/%d t:%f s:%f w:%f \n", ci, ci.object_id, ci.total_time, ci.self_time, ci.wait_time
134
- end
135
-
136
- def color(p)
137
- case i = p.to_i
138
- when 0..5
139
- "01"
140
- when 5..10
141
- "05"
142
- when 100
143
- "9"
144
- else
145
- "#{i/10}"
146
- end
147
- end
148
-
149
- def application
150
- @options[:application] || $PROGRAM_NAME
151
- end
152
-
153
- def arguments
154
- ARGV.join(' ')
155
- end
156
-
157
- def title
158
- @title ||= @options.delete(:title) || "ruby-prof call tree"
159
- end
160
-
161
- def threshold
162
- @options[:threshold] || 1.0
163
- end
164
-
165
- def expansion
166
- @options[:expansion] || 10.0
167
- end
168
-
169
- def base64_image
170
- @data ||= begin
171
- file = open_asset('call_stack_printer.png')
172
- Base64.encode64(file).gsub(/\n/, '')
173
- end
174
- end
175
-
176
- def template
177
- open_asset('call_stack_printer.html.erb')
178
- end
179
- end
180
- end
1
+ # encoding: utf-8
2
+
3
+ require 'erb'
4
+ require 'fileutils'
5
+ require 'base64'
6
+ require 'set'
7
+ require 'stringio'
8
+
9
+ module RubyProf
10
+ # Prints a HTML visualization of the call tree.
11
+ #
12
+ # To use the printer:
13
+ #
14
+ # result = RubyProf.profile do
15
+ # [code to profile]
16
+ # end
17
+ #
18
+ # printer = RubyProf::CallStackPrinter.new(result)
19
+ # printer.print(STDOUT)
20
+
21
+ class CallStackPrinter < AbstractPrinter
22
+ include ERB::Util
23
+
24
+ # Specify print options.
25
+ #
26
+ # output - Any IO object, including STDOUT or a file.
27
+ #
28
+ # Keyword arguments:
29
+ # title: - a String to override the default "ruby-prof call stack"
30
+ # title of the report.
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
+ # expansion: - a float from 0 to 100 that sets the threshold of
37
+ # results that are expanded, if the percent_total
38
+ # exceeds it.
39
+ # Default value is 10.0
40
+ #
41
+ # application: - a String to override the name of the application,
42
+ # as it appears on the report.
43
+ #
44
+ # Also accepts min_percent:, max_percent:, filter_by:, and sort_method:
45
+ # from AbstractPrinter.
46
+ def print(output = STDOUT, title: "ruby-prof call stack", threshold: 1.0,
47
+ expansion: 10.0, application: $PROGRAM_NAME,
48
+ min_percent: 0, max_percent: 100, filter_by: :self_time, sort_method: nil, **)
49
+ @min_percent = min_percent
50
+ @max_percent = max_percent
51
+ @filter_by = filter_by
52
+ @sort_method = sort_method
53
+ @title = title
54
+ @threshold = threshold
55
+ @expansion = expansion
56
+ @application = application
57
+ output << ERB.new(self.template).result(binding)
58
+ end
59
+
60
+ def print_stack(output, visited, call_tree, parent_time)
61
+ total_time = call_tree.total_time
62
+ percent_parent = (total_time/parent_time)*100
63
+ percent_total = (total_time/@overall_time)*100
64
+ return unless percent_total > min_percent
65
+ color = self.color(percent_total)
66
+ visible = percent_total >= threshold
67
+ expanded = percent_total >= expansion
68
+ display = visible ? "block" : "none"
69
+
70
+ output << "<li class=\"color#{color}\" style=\"display:#{display}\">" << "\n"
71
+
72
+ if visited.include?(call_tree)
73
+ output << "<a href=\"#\" class=\"toggle empty\" ></a>" << "\n"
74
+ output << "<span>%s %s</span>" % [link(call_tree.target, true), graph_link(call_tree)] << "\n"
75
+ else
76
+ visited << call_tree
77
+
78
+ if call_tree.children.empty?
79
+ output << "<a href=\"#\" class=\"toggle empty\" ></a>" << "\n"
80
+ else
81
+ visible_children = call_tree.children.any?{|ci| (ci.total_time/@overall_time)*100 >= threshold}
82
+ image = visible_children ? (expanded ? "minus" : "plus") : "empty"
83
+ output << "<a href=\"#\" class=\"toggle #{image}\" ></a>" << "\n"
84
+ end
85
+ output << "<span>%4.2f%% (%4.2f%%) %s %s</span>" % [percent_total, percent_parent,
86
+ link(call_tree.target, false), graph_link(call_tree)] << "\n"
87
+
88
+ unless call_tree.children.empty?
89
+ output << (expanded ? '<ul>' : '<ul style="display:none">') << "\n"
90
+ call_tree.children.sort_by{|c| -c.total_time}.each do |child_call_tree|
91
+ print_stack(output, visited, child_call_tree, total_time)
92
+ end
93
+ output << '</ul>' << "\n"
94
+ end
95
+
96
+ visited.delete(call_tree)
97
+ end
98
+ output << '</li>' << "\n"
99
+ end
100
+
101
+ def name(call_tree)
102
+ method = call_tree.target
103
+ method.full_name
104
+ end
105
+
106
+ def link(method, recursive)
107
+ method_name = "#{recursive ? '*' : ''}#{method.full_name}"
108
+ if method.source_file.nil?
109
+ h method_name
110
+ else
111
+ file = File.expand_path(method.source_file)
112
+ "<a href=\"file://#{file}##{method.line}\">#{h method_name}</a>"
113
+ end
114
+ end
115
+
116
+ def graph_link(call_tree)
117
+ total_calls = call_tree.target.called
118
+ totals = total_calls.to_s
119
+ "[#{call_tree.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_trees)
127
+ sum(call_trees.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
+ attr_reader :application, :title, :threshold, :expansion
152
+
153
+ def arguments
154
+ ARGV.join(' ')
155
+ end
156
+
157
+ def base64_image
158
+ @data ||= begin
159
+ file = open_asset('call_stack_printer.png')
160
+ Base64.encode64(file).gsub(/\n/, '')
161
+ end
162
+ end
163
+
164
+ def template
165
+ open_asset('call_stack_printer.html.erb')
166
+ end
167
+ end
168
+ end
@@ -1,145 +1,132 @@
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 @result.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
- @result.threads.each do |thread|
72
- print_thread(thread)
73
- end
74
- end
75
-
76
- def convert(value)
77
- (value * @value_scale).round
78
- end
79
-
80
- def file(method)
81
- method.source_file ? File.expand_path(method.source_file) : ''
82
- end
83
-
84
- def print_thread(thread)
85
- File.open(file_path_for_thread(thread), "w") do |f|
86
- print_headers(f, thread)
87
- thread.methods.reverse_each do |method|
88
- print_method(f, method)
89
- end
90
- end
91
- end
92
-
93
- def path
94
- @options[:path] || "."
95
- end
96
-
97
- def self.needs_dir?
98
- true
99
- end
100
-
101
- def remove_subsidiary_files_from_previous_profile_runs
102
- pattern = ["callgrind.out", $$, "*"].join(".")
103
- files = Dir.glob(File.join(path, pattern))
104
- FileUtils.rm_f(files)
105
- end
106
-
107
- def file_name_for_thread(thread)
108
- if thread.fiber_id == Fiber.current.object_id
109
- ["callgrind.out", $$].join(".")
110
- else
111
- ["callgrind.out", $$, thread.fiber_id].join(".")
112
- end
113
- end
114
-
115
- def file_path_for_thread(thread)
116
- File.join(path, file_name_for_thread(thread))
117
- end
118
-
119
- def print_headers(output, thread)
120
- output << "#{@event_specification}\n\n"
121
- # this doesn't work. kcachegrind does not fully support the spec.
122
- # output << "thread: #{thread.id}\n\n"
123
- end
124
-
125
- def print_method(output, method)
126
- # Print out the file and method name
127
- output << "fl=#{file(method)}\n"
128
- output << "fn=#{self.calltree_name(method)}\n"
129
-
130
- # Now print out the function line number and its self time
131
- output << "#{method.line} #{convert(method.self_time)}\n"
132
-
133
- # Now print out all the children methods
134
- method.call_trees.callees.each do |callee|
135
- output << "cfl=#{file(callee.target)}\n"
136
- output << "cfn=#{self.calltree_name(callee.target)}\n"
137
- output << "calls=#{callee.called} #{callee.line}\n"
138
-
139
- # Print out total times here!
140
- output << "#{callee.line} #{convert(callee.total_time)}\n"
141
- end
142
- output << "\n"
143
- end
144
- end
145
- end
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
4
+ require 'fiber'
5
+ require 'thread'
6
+ require 'fileutils'
7
+
8
+ module RubyProf
9
+ # Generates profiling information in callgrind format for use by
10
+ # kcachegrind and similar tools.
11
+
12
+ class CallTreePrinter < AbstractPrinter
13
+ def calltree_name(method_info)
14
+ klass_path = method_info.klass_name.gsub("::", '/')
15
+ result = "#{klass_path}::#{method_info.method_name}"
16
+
17
+ case method_info.klass_flags
18
+ when 0x2
19
+ "#{result}^"
20
+ when 0x4
21
+ "#{result}^"
22
+ when 0x8
23
+ "#{result}*"
24
+ else
25
+ result
26
+ end
27
+ end
28
+
29
+ def determine_event_specification_and_value_scale
30
+ @event_specification = String.new("events: ")
31
+ case @result.measure_mode
32
+ when RubyProf::PROCESS_TIME
33
+ @value_scale = RubyProf::CLOCKS_PER_SEC
34
+ @event_specification << 'process_time'
35
+ when RubyProf::WALL_TIME
36
+ @value_scale = 1_000_000
37
+ @event_specification << 'wall_time'
38
+ when RubyProf.const_defined?(:ALLOCATIONS) && RubyProf::ALLOCATIONS
39
+ @value_scale = 1
40
+ @event_specification << 'allocations'
41
+ when RubyProf.const_defined?(:GC_RUNS) && RubyProf::GC_RUNS
42
+ @value_scale = 1
43
+ @event_specification << 'gc_runs'
44
+ when RubyProf.const_defined?(:GC_TIME) && RubyProf::GC_TIME
45
+ @value_scale = 1000000
46
+ @event_specification << 'gc_time'
47
+ else
48
+ raise "Unknown measure mode: #{RubyProf.measure_mode}"
49
+ end
50
+ end
51
+
52
+ def print(path: ".", **)
53
+ @path = path
54
+ determine_event_specification_and_value_scale
55
+ print_threads
56
+ end
57
+
58
+ def print_threads
59
+ remove_subsidiary_files_from_previous_profile_runs
60
+ @result.threads.each do |thread|
61
+ print_thread(thread)
62
+ end
63
+ end
64
+
65
+ def convert(value)
66
+ (value * @value_scale).round
67
+ end
68
+
69
+ def file(method)
70
+ method.source_file ? File.expand_path(method.source_file) : ''
71
+ end
72
+
73
+ def print_thread(thread)
74
+ File.open(file_path_for_thread(thread), "w") do |f|
75
+ print_headers(f, thread)
76
+ thread.methods.reverse_each do |method|
77
+ print_method(f, method)
78
+ end
79
+ end
80
+ end
81
+
82
+ attr_reader :path
83
+
84
+ def self.needs_dir?
85
+ true
86
+ end
87
+
88
+ def remove_subsidiary_files_from_previous_profile_runs
89
+ pattern = ["callgrind.out", $$, "*"].join(".")
90
+ files = Dir.glob(File.join(path, pattern))
91
+ FileUtils.rm_f(files)
92
+ end
93
+
94
+ def file_name_for_thread(thread)
95
+ if thread.fiber_id == Fiber.current.object_id
96
+ ["callgrind.out", $$].join(".")
97
+ else
98
+ ["callgrind.out", $$, thread.fiber_id].join(".")
99
+ end
100
+ end
101
+
102
+ def file_path_for_thread(thread)
103
+ File.join(path, file_name_for_thread(thread))
104
+ end
105
+
106
+ def print_headers(output, thread)
107
+ output << "#{@event_specification}\n\n"
108
+ # this doesn't work. kcachegrind does not fully support the spec.
109
+ # output << "thread: #{thread.id}\n\n"
110
+ end
111
+
112
+ def print_method(output, method)
113
+ # Print out the file and method name
114
+ output << "fl=#{file(method)}\n"
115
+ output << "fn=#{self.calltree_name(method)}\n"
116
+
117
+ # Now print out the function line number and its self time
118
+ output << "#{method.line} #{convert(method.self_time)}\n"
119
+
120
+ # Now print out all the children methods
121
+ method.call_trees.callees.each do |callee|
122
+ output << "cfl=#{file(callee.target)}\n"
123
+ output << "cfn=#{self.calltree_name(callee.target)}\n"
124
+ output << "calls=#{callee.called} #{callee.line}\n"
125
+
126
+ # Print out total times here!
127
+ output << "#{callee.line} #{convert(callee.total_time)}\n"
128
+ end
129
+ output << "\n"
130
+ end
131
+ end
132
+ end