acunote-ruby-prof 0.9.2

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 (84) hide show
  1. data/CHANGES +240 -0
  2. data/LICENSE +23 -0
  3. data/README.rdoc +439 -0
  4. data/Rakefile +148 -0
  5. data/bin/ruby-prof +236 -0
  6. data/examples/empty.png +0 -0
  7. data/examples/flat.txt +55 -0
  8. data/examples/graph.dot +106 -0
  9. data/examples/graph.html +823 -0
  10. data/examples/graph.png +0 -0
  11. data/examples/graph.txt +170 -0
  12. data/examples/minus.png +0 -0
  13. data/examples/multi.flat.txt +23 -0
  14. data/examples/multi.graph.html +906 -0
  15. data/examples/multi.grind.dat +194 -0
  16. data/examples/multi.stack.html +573 -0
  17. data/examples/plus.png +0 -0
  18. data/examples/stack.html +573 -0
  19. data/ext/ruby_prof/extconf.rb +43 -0
  20. data/ext/ruby_prof/measure_allocations.h +58 -0
  21. data/ext/ruby_prof/measure_cpu_time.h +152 -0
  22. data/ext/ruby_prof/measure_gc_runs.h +76 -0
  23. data/ext/ruby_prof/measure_gc_time.h +57 -0
  24. data/ext/ruby_prof/measure_memory.h +101 -0
  25. data/ext/ruby_prof/measure_process_time.h +52 -0
  26. data/ext/ruby_prof/measure_wall_time.h +53 -0
  27. data/ext/ruby_prof/mingw/Rakefile +23 -0
  28. data/ext/ruby_prof/mingw/build.rake +38 -0
  29. data/ext/ruby_prof/ruby_prof.c +1834 -0
  30. data/ext/ruby_prof/ruby_prof.h +190 -0
  31. data/ext/ruby_prof/version.h +4 -0
  32. data/lib/ruby-prof.rb +62 -0
  33. data/lib/ruby-prof/abstract_printer.rb +41 -0
  34. data/lib/ruby-prof/aggregate_call_info.rb +68 -0
  35. data/lib/ruby-prof/call_info.rb +112 -0
  36. data/lib/ruby-prof/call_stack_printer.rb +751 -0
  37. data/lib/ruby-prof/call_tree_printer.rb +133 -0
  38. data/lib/ruby-prof/dot_printer.rb +153 -0
  39. data/lib/ruby-prof/empty.png +0 -0
  40. data/lib/ruby-prof/flat_printer.rb +78 -0
  41. data/lib/ruby-prof/flat_printer_with_line_numbers.rb +72 -0
  42. data/lib/ruby-prof/graph_html_printer.rb +278 -0
  43. data/lib/ruby-prof/graph_printer.rb +245 -0
  44. data/lib/ruby-prof/method_info.rb +131 -0
  45. data/lib/ruby-prof/minus.png +0 -0
  46. data/lib/ruby-prof/multi_printer.rb +54 -0
  47. data/lib/ruby-prof/plus.png +0 -0
  48. data/lib/ruby-prof/rack.rb +30 -0
  49. data/lib/ruby-prof/result.rb +70 -0
  50. data/lib/ruby-prof/symbol_to_proc.rb +8 -0
  51. data/lib/ruby-prof/task.rb +146 -0
  52. data/lib/ruby-prof/test.rb +148 -0
  53. data/lib/unprof.rb +8 -0
  54. data/rails/environment/profile.rb +24 -0
  55. data/rails/example/example_test.rb +9 -0
  56. data/rails/profile_test_helper.rb +21 -0
  57. data/test/aggregate_test.rb +136 -0
  58. data/test/basic_test.rb +290 -0
  59. data/test/current_failures_windows +8 -0
  60. data/test/do_nothing.rb +0 -0
  61. data/test/duplicate_names_test.rb +32 -0
  62. data/test/enumerable_test.rb +16 -0
  63. data/test/exceptions_test.rb +15 -0
  64. data/test/exclude_threads_test.rb +54 -0
  65. data/test/exec_test.rb +14 -0
  66. data/test/line_number_test.rb +73 -0
  67. data/test/measurement_test.rb +122 -0
  68. data/test/method_elimination_test.rb +74 -0
  69. data/test/module_test.rb +44 -0
  70. data/test/multi_printer_test.rb +81 -0
  71. data/test/no_method_class_test.rb +13 -0
  72. data/test/prime.rb +55 -0
  73. data/test/prime_test.rb +13 -0
  74. data/test/printers_test.rb +164 -0
  75. data/test/recursive_test.rb +236 -0
  76. data/test/ruby-prof-bin +20 -0
  77. data/test/singleton_test.rb +38 -0
  78. data/test/stack_printer_test.rb +74 -0
  79. data/test/stack_test.rb +138 -0
  80. data/test/start_stop_test.rb +112 -0
  81. data/test/test_suite.rb +32 -0
  82. data/test/thread_test.rb +173 -0
  83. data/test/unique_call_path_test.rb +225 -0
  84. metadata +185 -0
@@ -0,0 +1,245 @@
1
+ require 'ruby-prof/abstract_printer'
2
+
3
+ module RubyProf
4
+ # Generates graph[link:files/examples/graph_txt.html] profile reports as text.
5
+ # To use the graph printer:
6
+ #
7
+ # result = RubyProf.profile do
8
+ # [code to profile]
9
+ # end
10
+ #
11
+ # printer = RubyProf::GraphPrinter.new(result, 5)
12
+ # printer.print(STDOUT, 0)
13
+ #
14
+ # The constructor takes two arguments. See the README
15
+
16
+ class GraphPrinter < AbstractPrinter
17
+ PERCENTAGE_WIDTH = 8
18
+ TIME_WIDTH = 10
19
+ CALL_WIDTH = 17
20
+
21
+ # Create a GraphPrinter. Result is a RubyProf::Result
22
+ # object generated from a profiling run.
23
+ def initialize(result)
24
+ super(result)
25
+ @thread_times = Hash.new
26
+ calculate_thread_times
27
+ end
28
+
29
+ def calculate_thread_times
30
+ # Cache thread times since this is an expensive
31
+ # operation with the required sorting
32
+ @result.threads.each do |thread_id, methods|
33
+ top = methods.max
34
+
35
+ thread_time = [top.total_time, 0.01].max
36
+
37
+ @thread_times[thread_id] = thread_time
38
+ end
39
+ end
40
+
41
+ # Print a graph report to the provided output.
42
+ #
43
+ # output - Any IO oject, including STDOUT or a file.
44
+ # The default value is STDOUT.
45
+ #
46
+ # options - Hash of print options. See #setup_options
47
+ # for more information.
48
+ #
49
+ def print(output = STDOUT, options = {})
50
+ @output = output
51
+ setup_options(options)
52
+ print_threads
53
+ end
54
+
55
+ private
56
+ def print_threads
57
+ # sort assumes that spawned threads have higher object_ids
58
+ @result.threads.sort.each do |thread_id, methods|
59
+ print_methods(thread_id, methods)
60
+ @output << "\n" * 2
61
+ end
62
+ end
63
+
64
+ def print_methods(thread_id, methods)
65
+ # Sort methods from longest to shortest total time
66
+ methods = methods.sort
67
+
68
+ toplevel = methods.last
69
+ total_time = toplevel.total_time
70
+ if total_time == 0
71
+ total_time = 0.01
72
+ end
73
+
74
+ print_heading(thread_id)
75
+
76
+ # Print each method in total time order
77
+ methods.reverse_each do |method|
78
+ total_percentage = (method.total_time/total_time) * 100
79
+ self_percentage = (method.self_time/total_time) * 100
80
+
81
+ next if total_percentage < min_percent
82
+
83
+ @output << "-" * 80 << "\n"
84
+
85
+ print_parents(thread_id, method)
86
+
87
+ # 1 is for % sign
88
+ @output << sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", total_percentage)
89
+ @output << sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", self_percentage)
90
+ @output << sprintf("%#{TIME_WIDTH}.2f", method.total_time)
91
+ @output << sprintf("%#{TIME_WIDTH}.2f", method.self_time)
92
+ @output << sprintf("%#{TIME_WIDTH}.2f", method.wait_time)
93
+ @output << sprintf("%#{TIME_WIDTH}.2f", method.children_time)
94
+ @output << sprintf("%#{CALL_WIDTH}i", method.called)
95
+ @output << sprintf(" %s", method_name(method))
96
+ if print_file
97
+ @output << sprintf(" %s:%s", method.source_file, method.line)
98
+ end
99
+ @output << "\n"
100
+
101
+ print_children(method)
102
+ end
103
+ end
104
+
105
+ def print_heading(thread_id)
106
+ @output << "Thread ID: #{thread_id}\n"
107
+ @output << "Total Time: #{@thread_times[thread_id]}\n"
108
+ @output << "\n"
109
+
110
+ # 1 is for % sign
111
+ @output << sprintf("%#{PERCENTAGE_WIDTH}s", "%total")
112
+ @output << sprintf("%#{PERCENTAGE_WIDTH}s", "%self")
113
+ @output << sprintf("%#{TIME_WIDTH}s", "total")
114
+ @output << sprintf("%#{TIME_WIDTH}s", "self")
115
+ @output << sprintf("%#{TIME_WIDTH}s", "wait")
116
+ @output << sprintf("%#{TIME_WIDTH}s", "child")
117
+ @output << sprintf("%#{CALL_WIDTH}s", "calls")
118
+ @output << " Name"
119
+ @output << "\n"
120
+ end
121
+
122
+ def print_parents(thread_id, method)
123
+ method.aggregate_parents.sort_by(&:total_time).each do |caller|
124
+ next unless caller.parent
125
+ @output << " " * 2 * PERCENTAGE_WIDTH
126
+ @output << sprintf("%#{TIME_WIDTH}.2f", caller.total_time)
127
+ @output << sprintf("%#{TIME_WIDTH}.2f", caller.self_time)
128
+ @output << sprintf("%#{TIME_WIDTH}.2f", caller.wait_time)
129
+ @output << sprintf("%#{TIME_WIDTH}.2f", caller.children_time)
130
+
131
+ call_called = "#{caller.called}/#{method.called}"
132
+ @output << sprintf("%#{CALL_WIDTH}s", call_called)
133
+ @output << sprintf(" %s", caller.parent.target.full_name)
134
+ @output << "\n"
135
+ end
136
+ end
137
+
138
+ def print_children(method)
139
+ method.aggregate_children.sort_by(&:total_time).reverse.each do |child|
140
+ # Get children method
141
+
142
+ @output << " " * 2 * PERCENTAGE_WIDTH
143
+
144
+ @output << sprintf("%#{TIME_WIDTH}.2f", child.total_time)
145
+ @output << sprintf("%#{TIME_WIDTH}.2f", child.self_time)
146
+ @output << sprintf("%#{TIME_WIDTH}.2f", child.wait_time)
147
+ @output << sprintf("%#{TIME_WIDTH}.2f", child.children_time)
148
+
149
+ call_called = "#{child.called}/#{child.target.called}"
150
+ @output << sprintf("%#{CALL_WIDTH}s", call_called)
151
+ @output << sprintf(" %s", child.target.full_name)
152
+ @output << "\n"
153
+ end
154
+ end
155
+ end
156
+
157
+ class GraphPrinterWithoutAggregation < GraphPrinter
158
+ private
159
+ def print_methods(thread_id, methods)
160
+ # Sort methods from longest to shortest total time
161
+ methods = methods.sort
162
+
163
+ toplevel = methods.last
164
+ total_time = toplevel.total_time
165
+ total_time = 0.01 if total_time == 0
166
+
167
+ print_heading(thread_id)
168
+
169
+ # Print each method in total time order
170
+ methods.reverse_each do |method|
171
+ total_percentage = (method.total_time/total_time) * 100
172
+ self_percentage = (method.self_time/total_time) * 100
173
+
174
+ next if total_percentage < min_percent
175
+
176
+ parents = method.call_infos.map{|caller| caller if caller.parent}
177
+ children = method.children
178
+ call_tree = Hash.new
179
+ parents.each do |parent|
180
+ call_tree[parent] = Array.new
181
+ if parent
182
+ children.each do |child|
183
+ call_tree[parent] << child if child.call_sequence.include?(parent.call_sequence)
184
+ end
185
+ else
186
+ call_tree[parent] = children
187
+ end
188
+ end
189
+
190
+ call_tree.each do |parent, children|
191
+ @output << "-" * 80 << "\n"
192
+ print_parent(parent, method) if parent
193
+
194
+ # 1 is for % sign
195
+ @output << sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", total_percentage)
196
+ @output << sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", self_percentage)
197
+ @output << sprintf("%#{TIME_WIDTH}.2f", method.total_time)
198
+ @output << sprintf("%#{TIME_WIDTH}.2f", method.self_time)
199
+ @output << sprintf("%#{TIME_WIDTH}.2f", method.wait_time)
200
+ @output << sprintf("%#{TIME_WIDTH}.2f", method.children_time)
201
+ @output << sprintf("%#{CALL_WIDTH}i", method.called)
202
+ @output << sprintf(" %s", method_name(method))
203
+ if print_file
204
+ @output << sprintf(" %s:%s", method.source_file, method.line)
205
+ end
206
+ @output << "\n"
207
+
208
+ print_children(children)
209
+ end
210
+ end
211
+ end
212
+
213
+ def print_parent(parent, method)
214
+ @output << " " * 2 * PERCENTAGE_WIDTH
215
+ @output << sprintf("%#{TIME_WIDTH}.2f", parent.total_time)
216
+ @output << sprintf("%#{TIME_WIDTH}.2f", parent.self_time)
217
+ @output << sprintf("%#{TIME_WIDTH}.2f", parent.wait_time)
218
+ @output << sprintf("%#{TIME_WIDTH}.2f", parent.children_time)
219
+
220
+ call_called = "#{parent.called}/#{method.called}"
221
+ @output << sprintf("%#{CALL_WIDTH}s", call_called)
222
+ @output << sprintf(" %s", parent.parent.target.full_name)
223
+ @output << "\n"
224
+ end
225
+
226
+ def print_children(children)
227
+ children.each do |child|
228
+ @output << " " * 2 * PERCENTAGE_WIDTH
229
+
230
+ @output << sprintf("%#{TIME_WIDTH}.2f", child.total_time)
231
+ @output << sprintf("%#{TIME_WIDTH}.2f", child.self_time)
232
+ @output << sprintf("%#{TIME_WIDTH}.2f", child.wait_time)
233
+ @output << sprintf("%#{TIME_WIDTH}.2f", child.children_time)
234
+
235
+ call_called = "#{child.called}/#{child.target.called}"
236
+ @output << sprintf("%#{CALL_WIDTH}s", call_called)
237
+ @output << sprintf(" %s", child.target.full_name)
238
+ @output << "\n"
239
+ end
240
+ end
241
+
242
+ end #end class
243
+
244
+ end #end module
245
+
@@ -0,0 +1,131 @@
1
+ module RubyProf
2
+ class MethodInfo
3
+ include Comparable
4
+
5
+ def <=>(other)
6
+ if self.total_time < other.total_time
7
+ -1
8
+ elsif self.total_time > other.total_time
9
+ 1
10
+ elsif self.min_depth < other.min_depth
11
+ 1
12
+ elsif self.min_depth > other.min_depth
13
+ -1
14
+ else
15
+ -1 * (self.full_name <=> other.full_name)
16
+ end
17
+ end
18
+
19
+ def called
20
+ @called ||= begin
21
+ call_infos.inject(0) do |sum, call_info|
22
+ sum += call_info.called
23
+ end
24
+ end
25
+ end
26
+
27
+ def total_time
28
+ @total_time ||= begin
29
+ call_infos.inject(0) do |sum, call_info|
30
+ sum += call_info.total_time if call_info.minimal?
31
+ sum
32
+ end
33
+ end
34
+ end
35
+
36
+ def self_time
37
+ @self_time ||= begin
38
+ call_infos.inject(0) do |sum, call_info|
39
+ sum += call_info.self_time
40
+ end
41
+ end
42
+ end
43
+
44
+ def wait_time
45
+ @wait_time ||= begin
46
+ call_infos.inject(0) do |sum, call_info|
47
+ sum += call_info.wait_time
48
+ end
49
+ end
50
+ end
51
+
52
+ def children_time
53
+ @children_time ||= begin
54
+ call_infos.inject(0) do |sum, call_info|
55
+ sum += call_info.children_time if call_info.minimal?
56
+ sum
57
+ end
58
+ end
59
+ end
60
+
61
+ def min_depth
62
+ @min_depth ||= call_infos.map do |call_info|
63
+ call_info.depth
64
+ end.min
65
+ end
66
+
67
+ def root?
68
+ @root ||= begin
69
+ call_infos.find do |call_info|
70
+ not call_info.root?
71
+ end.nil?
72
+ end
73
+ end
74
+
75
+ def children
76
+ @children ||= begin
77
+ call_infos.map do |call_info|
78
+ call_info.children
79
+ end.flatten
80
+ end
81
+ end
82
+
83
+ def aggregate_parents
84
+ # Group call info's based on their parents
85
+ groups = self.call_infos.inject(Hash.new) do |hash, call_info|
86
+ key = call_info.parent ? call_info.parent.target : self
87
+ (hash[key] ||= []) << call_info
88
+ hash
89
+ end
90
+
91
+ groups.map do |key, value|
92
+ AggregateCallInfo.new(value)
93
+ end
94
+ end
95
+
96
+ def aggregate_children
97
+ # Group call info's based on their targets
98
+ groups = self.children.inject(Hash.new) do |hash, call_info|
99
+ key = call_info.target
100
+ (hash[key] ||= []) << call_info
101
+ hash
102
+ end
103
+
104
+ groups.map do |key, value|
105
+ AggregateCallInfo.new(value)
106
+ end
107
+ end
108
+
109
+ def to_s
110
+ full_name
111
+ end
112
+
113
+ def dump
114
+ res = ""
115
+ res << "MINFO: #{klass_name}##{method_name} total_time: #{total_time} (#{full_name})\n"
116
+ call_infos.each do |ci|
117
+ pinfo = ci.root? ? "TOPLEVEL" : (p=ci.parent.target; "#{p.klass_name}##{p.method_name} (#{ci.parent.object_id}) (#{p.full_name})")
118
+ res << "CINFO[#{ci.object_id}] called #{ci.called} times from #{pinfo}\n"
119
+ end
120
+ res
121
+ end
122
+
123
+ # remove method from the call graph. should not be called directly.
124
+ def eliminate!
125
+ # $stderr.puts "eliminating #{self}"
126
+ call_infos.each{ |call_info| call_info.eliminate! }
127
+ call_infos.clear
128
+ end
129
+
130
+ end
131
+ end
Binary file
@@ -0,0 +1,54 @@
1
+ module RubyProf
2
+ # Helper class to simplify printing profiles of several types from
3
+ # one profiling run. Currently prints a flat profile, a callgrind
4
+ # profile, a call stack profile and a graph profile.
5
+ class MultiPrinter
6
+ def initialize(result)
7
+ @stack_printer = CallStackPrinter.new(result)
8
+ @graph_printer = GraphHtmlPrinter.new(result)
9
+ @tree_printer = CallTreePrinter.new(result)
10
+ @flat_printer = FlatPrinter.new(result)
11
+ end
12
+
13
+ # create profile files under options[:path] or the current
14
+ # directory. options[:profile] is used as the base name for the
15
+ # pofile file, defaults to "profile".
16
+ def print(options)
17
+ @profile = options.delete(:profile) || "profile"
18
+ @directory = options.delete(:path) || File.expand_path(".")
19
+ File.open(stack_profile, "w") do |f|
20
+ @stack_printer.print(f, options.merge(:graph => "#{@profile}.graph.html"))
21
+ end
22
+ File.open(graph_profile, "w") do |f|
23
+ @graph_printer.print(f, options)
24
+ end
25
+ File.open(tree_profile, "w") do |f|
26
+ @tree_printer.print(f, options)
27
+ end
28
+ File.open(flat_profile, "w") do |f|
29
+ @flat_printer.print(f, options)
30
+ end
31
+ end
32
+
33
+ # the name of the call stack profile file
34
+ def stack_profile
35
+ "#{@directory}/#{@profile}.stack.html"
36
+ end
37
+
38
+ # the name of the graph profile file
39
+ def graph_profile
40
+ "#{@directory}/#{@profile}.graph.html"
41
+ end
42
+
43
+ # the name of the callgrind profile file
44
+ def tree_profile
45
+ "#{@directory}/#{@profile}.grind.dat"
46
+ end
47
+
48
+ # the name of the flat profile file
49
+ def flat_profile
50
+ "#{@directory}/#{@profile}.flat.txt"
51
+ end
52
+
53
+ end
54
+ end
Binary file
@@ -0,0 +1,30 @@
1
+ require 'tmpdir'
2
+
3
+ module Rack
4
+ class RubyProf
5
+ def initialize(app)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ ::RubyProf.start
11
+ result = @app.call(env)
12
+ data = ::RubyProf.stop
13
+
14
+ print(data)
15
+ result
16
+ end
17
+
18
+ def print(data)
19
+ printers = {::RubyProf::FlatPrinter => ::File.join(Dir.tmpdir, 'profile.txt'),
20
+ ::RubyProf::GraphHtmlPrinter => ::File.join(Dir.tmpdir, 'profile.html')}
21
+
22
+ printers.each do |printer_klass, file_name|
23
+ printer = printer_klass.new(data)
24
+ ::File.open(file_name, 'wb') do |file|
25
+ printer.print(file, :min_percent => 0.00000001 )
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end