ruby-prof 1.8.0-x64-mswin64-140

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 (108) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES +665 -0
  3. data/LICENSE +25 -0
  4. data/README.md +5 -0
  5. data/Rakefile +98 -0
  6. data/bin/ruby-prof +341 -0
  7. data/bin/ruby-prof-check-trace +45 -0
  8. data/ext/ruby_prof/extconf.rb +23 -0
  9. data/ext/ruby_prof/rp_allocation.c +327 -0
  10. data/ext/ruby_prof/rp_allocation.h +32 -0
  11. data/ext/ruby_prof/rp_call_tree.c +502 -0
  12. data/ext/ruby_prof/rp_call_tree.h +47 -0
  13. data/ext/ruby_prof/rp_call_trees.c +296 -0
  14. data/ext/ruby_prof/rp_call_trees.h +28 -0
  15. data/ext/ruby_prof/rp_measure_allocations.c +47 -0
  16. data/ext/ruby_prof/rp_measure_memory.c +46 -0
  17. data/ext/ruby_prof/rp_measure_process_time.c +64 -0
  18. data/ext/ruby_prof/rp_measure_wall_time.c +52 -0
  19. data/ext/ruby_prof/rp_measurement.c +359 -0
  20. data/ext/ruby_prof/rp_measurement.h +52 -0
  21. data/ext/ruby_prof/rp_method.c +551 -0
  22. data/ext/ruby_prof/rp_method.h +66 -0
  23. data/ext/ruby_prof/rp_profile.c +933 -0
  24. data/ext/ruby_prof/rp_profile.h +36 -0
  25. data/ext/ruby_prof/rp_stack.c +212 -0
  26. data/ext/ruby_prof/rp_stack.h +53 -0
  27. data/ext/ruby_prof/rp_thread.c +433 -0
  28. data/ext/ruby_prof/rp_thread.h +39 -0
  29. data/ext/ruby_prof/ruby_prof.c +50 -0
  30. data/ext/ruby_prof/ruby_prof.h +35 -0
  31. data/ext/ruby_prof/vc/ruby_prof.sln +39 -0
  32. data/ext/ruby_prof/vc/ruby_prof.vcxproj +158 -0
  33. data/lib/ruby-prof/assets/call_stack_printer.html.erb +711 -0
  34. data/lib/ruby-prof/assets/call_stack_printer.png +0 -0
  35. data/lib/ruby-prof/assets/graph_printer.html.erb +355 -0
  36. data/lib/ruby-prof/call_tree.rb +57 -0
  37. data/lib/ruby-prof/call_tree_visitor.rb +36 -0
  38. data/lib/ruby-prof/compatibility.rb +113 -0
  39. data/lib/ruby-prof/exclude_common_methods.rb +204 -0
  40. data/lib/ruby-prof/measurement.rb +17 -0
  41. data/lib/ruby-prof/method_info.rb +87 -0
  42. data/lib/ruby-prof/printers/abstract_printer.rb +156 -0
  43. data/lib/ruby-prof/printers/call_info_printer.rb +53 -0
  44. data/lib/ruby-prof/printers/call_stack_printer.rb +180 -0
  45. data/lib/ruby-prof/printers/call_tree_printer.rb +145 -0
  46. data/lib/ruby-prof/printers/dot_printer.rb +132 -0
  47. data/lib/ruby-prof/printers/flat_printer.rb +53 -0
  48. data/lib/ruby-prof/printers/graph_html_printer.rb +63 -0
  49. data/lib/ruby-prof/printers/graph_printer.rb +113 -0
  50. data/lib/ruby-prof/printers/multi_printer.rb +127 -0
  51. data/lib/ruby-prof/profile.rb +70 -0
  52. data/lib/ruby-prof/rack.rb +105 -0
  53. data/lib/ruby-prof/task.rb +147 -0
  54. data/lib/ruby-prof/thread.rb +20 -0
  55. data/lib/ruby-prof/version.rb +3 -0
  56. data/lib/ruby-prof.rb +52 -0
  57. data/lib/unprof.rb +10 -0
  58. data/ruby-prof.gemspec +67 -0
  59. data/test/abstract_printer_test.rb +27 -0
  60. data/test/alias_test.rb +117 -0
  61. data/test/call_tree_builder.rb +126 -0
  62. data/test/call_tree_test.rb +94 -0
  63. data/test/call_tree_visitor_test.rb +27 -0
  64. data/test/call_trees_test.rb +66 -0
  65. data/test/compatibility_test.rb +49 -0
  66. data/test/duplicate_names_test.rb +32 -0
  67. data/test/dynamic_method_test.rb +50 -0
  68. data/test/enumerable_test.rb +23 -0
  69. data/test/exceptions_test.rb +24 -0
  70. data/test/exclude_methods_test.rb +363 -0
  71. data/test/exclude_threads_test.rb +48 -0
  72. data/test/fiber_test.rb +195 -0
  73. data/test/gc_test.rb +104 -0
  74. data/test/inverse_call_tree_test.rb +174 -0
  75. data/test/line_number_test.rb +426 -0
  76. data/test/marshal_test.rb +145 -0
  77. data/test/measure_allocations.rb +26 -0
  78. data/test/measure_allocations_test.rb +1172 -0
  79. data/test/measure_process_time_test.rb +3330 -0
  80. data/test/measure_times.rb +56 -0
  81. data/test/measure_wall_time_test.rb +635 -0
  82. data/test/measurement_test.rb +82 -0
  83. data/test/merge_test.rb +146 -0
  84. data/test/method_info_test.rb +100 -0
  85. data/test/multi_printer_test.rb +66 -0
  86. data/test/no_method_class_test.rb +15 -0
  87. data/test/pause_resume_test.rb +171 -0
  88. data/test/prime.rb +54 -0
  89. data/test/prime_script.rb +6 -0
  90. data/test/printer_call_stack_test.rb +27 -0
  91. data/test/printer_call_tree_test.rb +30 -0
  92. data/test/printer_flat_test.rb +99 -0
  93. data/test/printer_graph_html_test.rb +59 -0
  94. data/test/printer_graph_test.rb +40 -0
  95. data/test/printers_test.rb +178 -0
  96. data/test/printing_recursive_graph_test.rb +81 -0
  97. data/test/profile_test.rb +101 -0
  98. data/test/rack_test.rb +93 -0
  99. data/test/recursive_test.rb +796 -0
  100. data/test/scheduler.rb +363 -0
  101. data/test/singleton_test.rb +38 -0
  102. data/test/stack_printer_test.rb +61 -0
  103. data/test/start_stop_test.rb +106 -0
  104. data/test/test_helper.rb +21 -0
  105. data/test/thread_test.rb +229 -0
  106. data/test/unique_call_path_test.rb +123 -0
  107. data/test/yarv_test.rb +56 -0
  108. metadata +228 -0
@@ -0,0 +1,145 @@
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
@@ -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
+ #
8
+ # To use the dot printer:
9
+ #
10
+ # result = RubyProf.profile do
11
+ # [code to profile]
12
+ # end
13
+ #
14
+ # printer = RubyProf::DotPrinter.new(result)
15
+ # printer.print(STDOUT)
16
+ #
17
+ # You can use either dot viewer such as GraphViz, or the dot command line tool
18
+ # to reformat the output into a wide variety of outputs:
19
+ #
20
+ # dot -Tpng graph.dot > graph.png
21
+ #
22
+ class DotPrinter < RubyProf::AbstractPrinter
23
+ CLASS_COLOR = '"#666666"'
24
+ EDGE_COLOR = '"#666666"'
25
+
26
+ # Creates the DotPrinter using a RubyProf::Proile.
27
+ def initialize(result)
28
+ super(result)
29
+ @seen_methods = Set.new
30
+ end
31
+
32
+ # Print a graph report to the provided output.
33
+ #
34
+ # output - Any IO object, including STDOUT or a file. The default value is
35
+ # STDOUT.
36
+ #
37
+ # options - Hash of print options. See #setup_options
38
+ # for more information.
39
+ #
40
+ # When profiling results that cover a large number of method calls it
41
+ # helps to use the :min_percent option, for example:
42
+ #
43
+ # DotPrinter.new(result).print(STDOUT, :min_percent=>5)
44
+ #
45
+ def print(output = STDOUT, options = {})
46
+ @output = output
47
+ setup_options(options)
48
+
49
+ puts 'digraph "Profile" {'
50
+ #puts "label=\"#{mode_name} >=#{min_percent}%\\nTotal: #{total_time}\";"
51
+ puts "labelloc=t;"
52
+ puts "labeljust=l;"
53
+ print_threads
54
+ puts '}'
55
+ end
56
+
57
+ private
58
+
59
+ # Something of a hack, figure out which constant went with the
60
+ # RubyProf.measure_mode so that we can display it. Otherwise it's easy to
61
+ # forget what measurement was made.
62
+ def mode_name
63
+ RubyProf.constants.find{|c| RubyProf.const_get(c) == RubyProf.measure_mode}
64
+ end
65
+
66
+ def print_threads
67
+ @result.threads.each do |thread|
68
+ puts "subgraph \"Thread #{thread.id}\" {"
69
+
70
+ print_thread(thread)
71
+ puts "}"
72
+
73
+ print_classes(thread)
74
+ end
75
+ end
76
+
77
+ # Determines an ID to use to represent the subject in the Dot file.
78
+ def dot_id(subject)
79
+ subject.object_id
80
+ end
81
+
82
+ def print_thread(thread)
83
+ total_time = thread.total_time
84
+ thread.methods.sort_by(&sort_method).reverse_each do |method|
85
+ total_percentage = (method.total_time/total_time) * 100
86
+
87
+ next if total_percentage < min_percent
88
+ name = method.full_name.split("#").last
89
+ puts "#{dot_id(method)} [label=\"#{name}\\n(#{total_percentage.round}%)\"];"
90
+ @seen_methods << method
91
+ print_edges(total_time, method)
92
+ end
93
+ end
94
+
95
+ def print_classes(thread)
96
+ grouped = {}
97
+ thread.methods.each{|m| grouped[m.klass_name] ||= []; grouped[m.klass_name] << m}
98
+ grouped.each do |cls, methods2|
99
+ # Filter down to just seen methods
100
+ big_methods = methods2.select{|m| @seen_methods.include? m}
101
+
102
+ if !big_methods.empty?
103
+ puts "subgraph cluster_#{cls.object_id} {"
104
+ puts "label = \"#{cls}\";"
105
+ puts "fontcolor = #{CLASS_COLOR};"
106
+ puts "fontsize = 16;"
107
+ puts "color = #{CLASS_COLOR};"
108
+ big_methods.each do |m|
109
+ puts "#{m.object_id};"
110
+ end
111
+ puts "}"
112
+ end
113
+ end
114
+ end
115
+
116
+ def print_edges(total_time, method)
117
+ method.call_trees.callers.sort_by(&:total_time).reverse.each do |call_tree|
118
+ target_percentage = (call_tree.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(call_tree.target)} [label=\"#{call_tree.called}/#{call_tree.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
@@ -0,0 +1,53 @@
1
+ # encoding: utf-8
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, {})
13
+ #
14
+ class FlatPrinter < AbstractPrinter
15
+ # Override for this printer to sort by self time by default
16
+ def sort_method
17
+ @options[:sort_method] || :self_time
18
+ end
19
+
20
+ private
21
+
22
+ def print_column_headers
23
+ @output << " %self total self wait child calls name location\n"
24
+ end
25
+
26
+ def print_methods(thread)
27
+ total_time = thread.total_time
28
+ methods = thread.methods.sort_by(&sort_method).reverse
29
+
30
+ sum = 0
31
+ methods.each do |method|
32
+ percent = (method.send(filter_by) / total_time) * 100
33
+ next if percent < min_percent
34
+ next if percent > max_percent
35
+
36
+ sum += method.self_time
37
+ #self_time_called = method.called > 0 ? method.self_time/method.called : 0
38
+ #total_time_called = method.called > 0? method.total_time/method.called : 0
39
+
40
+ @output << "%6.2f %9.3f %9.3f %9.3f %9.3f %8d %s%-30s %s\n" % [
41
+ method.self_time / total_time * 100, # %self
42
+ method.total_time, # total
43
+ method.self_time, # self
44
+ method.wait_time, # wait
45
+ method.children_time, # children
46
+ method.called, # calls
47
+ method.recursive? ? "*" : " ", # cycle
48
+ method.full_name, # method_name]
49
+ method_location(method)] # location]
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,63 @@
1
+ # encoding: utf-8
2
+
3
+ require 'erb'
4
+
5
+ module RubyProf
6
+ # Generates graph[link:files/examples/graph_html.html] profile reports as html.
7
+ # To use the graph html printer:
8
+ #
9
+ # result = RubyProf.profile do
10
+ # [code to profile]
11
+ # end
12
+ #
13
+ # printer = RubyProf::GraphHtmlPrinter.new(result)
14
+ # printer.print(STDOUT, :min_percent=>0)
15
+ #
16
+ # The Graph printer takes the following options in its print methods:
17
+
18
+ class GraphHtmlPrinter < AbstractPrinter
19
+ include ERB::Util
20
+
21
+ def setup_options(options)
22
+ super(options)
23
+ @erb = ERB.new(self.template)
24
+ end
25
+
26
+ def print(output = STDOUT, options = {})
27
+ setup_options(options)
28
+ output << @erb.result(binding)
29
+ end
30
+
31
+ # Creates a link to a method. Note that we do not create
32
+ # links to methods which are under the min_percent
33
+ # specified by the user, since they will not be
34
+ # printed out.
35
+ def create_link(thread, overall_time, method)
36
+ total_percent = (method.total_time/overall_time) * 100
37
+ if total_percent < min_percent
38
+ # Just return name
39
+ h method.full_name
40
+ else
41
+ href = '#' + method_href(thread, method)
42
+ "<a href=\"#{href}\">#{h method.full_name}</a>"
43
+ end
44
+ end
45
+
46
+ def method_href(thread, method)
47
+ h(method.full_name.gsub(/[><#\.\?=:]/,"_") + "_" + thread.fiber_id.to_s)
48
+ end
49
+
50
+ def file_link(path, linenum)
51
+ if path.nil?
52
+ ""
53
+ else
54
+ srcfile = File.expand_path(path)
55
+ "<a href=\"file://#{h srcfile}##{linenum}\" title=\"#{h srcfile}:#{linenum}\">#{linenum}</a>"
56
+ end
57
+ end
58
+
59
+ def template
60
+ open_asset('graph_printer.html.erb')
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,113 @@
1
+ # encoding: utf-8
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)
12
+ # printer.print(STDOUT, {})
13
+ #
14
+ # The constructor takes two arguments. See the README
15
+
16
+ class GraphPrinter < AbstractPrinter
17
+ PERCENTAGE_WIDTH = 8
18
+ TIME_WIDTH = 11
19
+ CALL_WIDTH = 17
20
+
21
+ private
22
+
23
+ def sort_method
24
+ @options[:sort_method] || :total_time
25
+ end
26
+
27
+ def print_header(thread)
28
+ @output << "Measure Mode: %s\n" % @result.measure_mode_string
29
+ @output << "Thread ID: #{thread.id}\n"
30
+ @output << "Fiber ID: #{thread.fiber_id}\n"
31
+ @output << "Total Time: #{thread.total_time}\n"
32
+ @output << "Sort by: #{sort_method}\n"
33
+ @output << "\n"
34
+
35
+ # 1 is for % sign
36
+ @output << sprintf("%#{PERCENTAGE_WIDTH}s", "%total")
37
+ @output << sprintf("%#{PERCENTAGE_WIDTH}s", "%self")
38
+ @output << sprintf("%#{TIME_WIDTH}s", "total")
39
+ @output << sprintf("%#{TIME_WIDTH}s", "self")
40
+ @output << sprintf("%#{TIME_WIDTH}s", "wait")
41
+ @output << sprintf("%#{TIME_WIDTH}s", "child")
42
+ @output << sprintf("%#{CALL_WIDTH}s", "calls")
43
+ @output << " name"
44
+ @output << " location"
45
+ @output << "\n"
46
+ end
47
+
48
+ def print_methods(thread)
49
+ total_time = thread.total_time
50
+ # Sort methods from longest to shortest total time
51
+ methods = thread.methods.sort_by(&sort_method)
52
+
53
+ # Print each method in total time order
54
+ methods.reverse_each do |method|
55
+ total_percentage = (method.total_time/total_time) * 100
56
+ next if total_percentage < min_percent
57
+
58
+ self_percentage = (method.self_time/total_time) * 100
59
+
60
+ @output << "-" * 150 << "\n"
61
+ print_parents(thread, method)
62
+
63
+ # 1 is for % sign
64
+ @output << sprintf("%#{PERCENTAGE_WIDTH-1}.2f%%", total_percentage)
65
+ @output << sprintf("%#{PERCENTAGE_WIDTH-1}.2f%%", self_percentage)
66
+ @output << sprintf("%#{TIME_WIDTH}.3f", method.total_time)
67
+ @output << sprintf("%#{TIME_WIDTH}.3f", method.self_time)
68
+ @output << sprintf("%#{TIME_WIDTH}.3f", method.wait_time)
69
+ @output << sprintf("%#{TIME_WIDTH}.3f", method.children_time)
70
+ @output << sprintf("%#{CALL_WIDTH}i", method.called)
71
+ @output << sprintf(" %s", method.recursive? ? "*" : " ")
72
+ @output << sprintf("%-30s", method.full_name)
73
+ @output << sprintf(" %s", method_location(method))
74
+ @output << "\n"
75
+
76
+ print_children(method)
77
+ end
78
+ end
79
+
80
+ def print_parents(thread, method)
81
+ method.call_trees.callers.sort_by(&:total_time).each do |caller|
82
+ @output << " " * 2 * PERCENTAGE_WIDTH
83
+ @output << sprintf("%#{TIME_WIDTH}.3f", caller.total_time)
84
+ @output << sprintf("%#{TIME_WIDTH}.3f", caller.self_time)
85
+ @output << sprintf("%#{TIME_WIDTH}.3f", caller.wait_time)
86
+ @output << sprintf("%#{TIME_WIDTH}.3f", caller.children_time)
87
+
88
+ call_called = "#{caller.called}/#{method.called}"
89
+ @output << sprintf("%#{CALL_WIDTH}s", call_called)
90
+ @output << sprintf(" %s", caller.parent.target.full_name)
91
+ @output << "\n"
92
+ end
93
+ end
94
+
95
+ def print_children(method)
96
+ method.call_trees.callees.sort_by(&:total_time).reverse.each do |child|
97
+ # Get children method
98
+
99
+ @output << " " * 2 * PERCENTAGE_WIDTH
100
+
101
+ @output << sprintf("%#{TIME_WIDTH}.3f", child.total_time)
102
+ @output << sprintf("%#{TIME_WIDTH}.3f", child.self_time)
103
+ @output << sprintf("%#{TIME_WIDTH}.3f", child.wait_time)
104
+ @output << sprintf("%#{TIME_WIDTH}.3f", child.children_time)
105
+
106
+ call_called = "#{child.called}/#{child.target.called}"
107
+ @output << sprintf("%#{CALL_WIDTH}s", call_called)
108
+ @output << sprintf(" %s", child.target.full_name)
109
+ @output << "\n"
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,127 @@
1
+ # encoding: utf-8
2
+
3
+ module RubyProf
4
+ # Helper class to simplify printing profiles of several types from
5
+ # one profiling run. Currently prints a flat profile, a callgrind
6
+ # profile, a call stack profile and a graph profile.
7
+ class MultiPrinter
8
+ def initialize(result, printers = [:flat, :graph_html])
9
+ @flat_printer = printers.include?(:flat) ? FlatPrinter.new(result) : nil
10
+
11
+ @graph_printer = printers.include?(:graph) ? GraphPrinter.new(result) : nil
12
+ @graph_html_printer = printers.include?(:graph_html) ? GraphHtmlPrinter.new(result) : nil
13
+
14
+ @tree_printer = printers.include?(:tree) ? CallTreePrinter.new(result) : nil
15
+ @call_info_printer = printers.include?(:call_tree) ? CallInfoPrinter.new(result) : nil
16
+
17
+ @stack_printer = printers.include?(:stack) ? CallStackPrinter.new(result) : nil
18
+ @dot_printer = printers.include?(:dot) ? DotPrinter.new(result) : nil
19
+ end
20
+
21
+ def self.needs_dir?
22
+ true
23
+ end
24
+
25
+ # create profile files under options[:path] or the current
26
+ # directory. options[:profile] is used as the base name for the
27
+ # profile file, defaults to "profile".
28
+ def print(options)
29
+ validate_print_params(options)
30
+
31
+ @file_name = options.delete(:profile) || "profile"
32
+ @directory = options.delete(:path) || File.expand_path(".")
33
+
34
+ print_to_flat(options) if @flat_printer
35
+
36
+ print_to_graph(options) if @graph_printer
37
+ print_to_graph_html(options) if @graph_html_printer
38
+
39
+ print_to_stack(options) if @stack_printer
40
+ print_to_call_info(options) if @call_info_printer
41
+ print_to_tree(options) if @tree_printer
42
+ print_to_dot(options) if @dot_printer
43
+ end
44
+
45
+ # the name of the flat profile file
46
+ def flat_report
47
+ "#{@directory}/#{@file_name}.flat.txt"
48
+ end
49
+
50
+ # the name of the graph profile file
51
+ def graph_report
52
+ "#{@directory}/#{@file_name}.graph.txt"
53
+ end
54
+
55
+ def graph_html_report
56
+ "#{@directory}/#{@file_name}.graph.html"
57
+ end
58
+
59
+ # the name of the callinfo profile file
60
+ def call_info_report
61
+ "#{@directory}/#{@file_name}.call_tree.txt"
62
+ end
63
+
64
+ # the name of the callgrind profile file
65
+ def tree_report
66
+ "#{@directory}/#{@file_name}.callgrind.out.#{$$}"
67
+ end
68
+
69
+ # the name of the call stack profile file
70
+ def stack_report
71
+ "#{@directory}/#{@file_name}.stack.html"
72
+ end
73
+
74
+ # the name of the call stack profile file
75
+ def dot_report
76
+ "#{@directory}/#{@file_name}.dot"
77
+ end
78
+
79
+ def print_to_flat(options)
80
+ File.open(flat_report, "wb") do |file|
81
+ @flat_printer.print(file, options)
82
+ end
83
+ end
84
+
85
+ def print_to_graph(options)
86
+ File.open(graph_report, "wb") do |file|
87
+ @graph_printer.print(file, options)
88
+ end
89
+ end
90
+
91
+ def print_to_graph_html(options)
92
+ File.open(graph_html_report, "wb") do |file|
93
+ @graph_html_printer.print(file, options)
94
+ end
95
+ end
96
+
97
+ def print_to_call_info(options)
98
+ File.open(call_info_report, "wb") do |file|
99
+ @call_info_printer.print(file, options)
100
+ end
101
+ end
102
+
103
+ def print_to_tree(options)
104
+ @tree_printer.print(options.merge(:path => @directory, :profile => @file_name))
105
+ end
106
+
107
+ def print_to_stack(options)
108
+ File.open(stack_report, "wb") do |file|
109
+ @stack_printer.print(file, options.merge(:graph => "#{@file_name}.graph.html"))
110
+ end
111
+ end
112
+
113
+ def print_to_dot(options)
114
+ File.open(dot_report, "wb") do |file|
115
+ @dot_printer.print(file, options)
116
+ end
117
+ end
118
+
119
+ def validate_print_params(options)
120
+ if options.is_a?(IO)
121
+ raise ArgumentError, "#{self.class.name}#print cannot print to IO objects"
122
+ elsif !options.is_a?(Hash)
123
+ raise ArgumentError, "#{self.class.name}#print requires an options hash"
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,70 @@
1
+ # encoding: utf-8
2
+
3
+ require 'ruby-prof/exclude_common_methods'
4
+
5
+ module RubyProf
6
+ class Profile
7
+ def measure_mode_string
8
+ case self.measure_mode
9
+ when WALL_TIME
10
+ "wall_time"
11
+ when PROCESS_TIME
12
+ "process_time"
13
+ when ALLOCATIONS
14
+ "allocations"
15
+ when MEMORY
16
+ "memory"
17
+ end
18
+ end
19
+
20
+ # Hides methods that, when represented as a call graph, have
21
+ # extremely large in and out degrees and make navigation impossible.
22
+ def exclude_common_methods!
23
+ ExcludeCommonMethods.apply!(self)
24
+ end
25
+
26
+ def exclude_methods!(mod, *method_names)
27
+ [method_names].flatten.each do |method_name|
28
+ exclude_method!(mod, method_name)
29
+ end
30
+ end
31
+
32
+ def exclude_singleton_methods!(mod, *method_names)
33
+ exclude_methods!(mod.singleton_class, *method_names)
34
+ end
35
+
36
+ # call-seq:
37
+ # merge! -> self
38
+ #
39
+ # Merges RubyProf threads whose root call_trees reference the same target method. This is useful
40
+ # when profiling code that uses a main thread/fiber to distribute work to multiple workers.
41
+ # If there are tens or hundreds of workers, viewing results per worker thread/fiber can be
42
+ # overwhelming. Using +merge!+ will combine the worker times together into one result.
43
+ #
44
+ # Note the reported time will be much greater than the actual wall time. For example, if there
45
+ # are 10 workers that each run for 5 seconds, merged results will show one thread that
46
+ # ran for 50 seconds.
47
+ #
48
+ def merge!
49
+ # First group threads by their root call tree target (method). If the methods are
50
+ # different than there is nothing to merge
51
+ grouped = threads.group_by do |thread|
52
+ thread.call_tree.target
53
+ end
54
+
55
+ # For each target, get the first thread. Then loop over the remaining threads,
56
+ # and merge them into the first one and ten delete them. So we will be left with
57
+ # one thread per target.
58
+ grouped.each do |target, threads|
59
+ thread = threads.shift
60
+ threads.each do |other_thread|
61
+ thread.merge!(other_thread)
62
+ remove_thread(other_thread)
63
+ end
64
+ thread
65
+ end
66
+
67
+ self
68
+ end
69
+ end
70
+ end