ruby-prof-danielhoey 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/CHANGES +221 -0
  2. data/LICENSE +23 -0
  3. data/README +432 -0
  4. data/Rakefile +158 -0
  5. data/bin/ruby-prof +224 -0
  6. data/examples/flat.txt +55 -0
  7. data/examples/graph.html +823 -0
  8. data/examples/graph.txt +170 -0
  9. data/ext/ruby_prof/call_tree.c +392 -0
  10. data/ext/ruby_prof/call_tree.h +32 -0
  11. data/ext/ruby_prof/extconf.rb +40 -0
  12. data/ext/ruby_prof/list.c +66 -0
  13. data/ext/ruby_prof/list.h +10 -0
  14. data/ext/ruby_prof/measure_allocations.h +58 -0
  15. data/ext/ruby_prof/measure_cpu_time.h +152 -0
  16. data/ext/ruby_prof/measure_gc_runs.h +76 -0
  17. data/ext/ruby_prof/measure_gc_time.h +57 -0
  18. data/ext/ruby_prof/measure_memory.h +101 -0
  19. data/ext/ruby_prof/measure_process_time.h +52 -0
  20. data/ext/ruby_prof/measure_wall_time.h +53 -0
  21. data/ext/ruby_prof/measurement.h +13 -0
  22. data/ext/ruby_prof/mingw/Rakefile +23 -0
  23. data/ext/ruby_prof/mingw/build.rake +38 -0
  24. data/ext/ruby_prof/ruby_prof.c +1943 -0
  25. data/ext/ruby_prof/ruby_prof.h +183 -0
  26. data/ext/ruby_prof/version.h +4 -0
  27. data/lib/ruby-prof.rb +59 -0
  28. data/lib/ruby-prof/abstract_printer.rb +41 -0
  29. data/lib/ruby-prof/aggregate_call_info.rb +62 -0
  30. data/lib/ruby-prof/call_info.rb +47 -0
  31. data/lib/ruby-prof/call_tree/abstract_printer.rb +24 -0
  32. data/lib/ruby-prof/call_tree/html_printer.rb +89 -0
  33. data/lib/ruby-prof/call_tree/html_printer_output.html.erb +99 -0
  34. data/lib/ruby-prof/call_tree/text_printer.rb +28 -0
  35. data/lib/ruby-prof/call_tree_printer.rb +84 -0
  36. data/lib/ruby-prof/flat_printer.rb +78 -0
  37. data/lib/ruby-prof/flat_printer_with_line_numbers.rb +72 -0
  38. data/lib/ruby-prof/graph_html_printer.rb +256 -0
  39. data/lib/ruby-prof/graph_printer.rb +157 -0
  40. data/lib/ruby-prof/method_info.rb +111 -0
  41. data/lib/ruby-prof/symbol_to_proc.rb +8 -0
  42. data/lib/ruby-prof/task.rb +146 -0
  43. data/lib/ruby-prof/test.rb +148 -0
  44. data/lib/unprof.rb +8 -0
  45. data/rails/environment/profile.rb +24 -0
  46. data/rails/example/example_test.rb +9 -0
  47. data/rails/profile_test_helper.rb +21 -0
  48. data/test/aggregate_test.rb +121 -0
  49. data/test/basic_test.rb +290 -0
  50. data/test/current_failures_windows +8 -0
  51. data/test/do_nothing.rb +0 -0
  52. data/test/duplicate_names_test.rb +32 -0
  53. data/test/enumerable_test.rb +16 -0
  54. data/test/exceptions_test.rb +15 -0
  55. data/test/exclude_threads_test.rb +54 -0
  56. data/test/exec_test.rb +14 -0
  57. data/test/line_number_test.rb +73 -0
  58. data/test/measurement_test.rb +121 -0
  59. data/test/module_test.rb +54 -0
  60. data/test/no_method_class_test.rb +14 -0
  61. data/test/prime.rb +58 -0
  62. data/test/prime_test.rb +13 -0
  63. data/test/printers_test.rb +130 -0
  64. data/test/recursive_test.rb +275 -0
  65. data/test/ruby-prof-bin +20 -0
  66. data/test/singleton_test.rb +37 -0
  67. data/test/stack_test.rb +138 -0
  68. data/test/start_stop_test.rb +95 -0
  69. data/test/test_suite.rb +23 -0
  70. data/test/thread_test.rb +173 -0
  71. data/test/unique_call_path_test.rb +225 -0
  72. metadata +163 -0
@@ -0,0 +1,157 @@
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
+ end
157
+
@@ -0,0 +1,111 @@
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
31
+ end
32
+ end
33
+ end
34
+
35
+ def self_time
36
+ @self_time ||= begin
37
+ call_infos.inject(0) do |sum, call_info|
38
+ sum += call_info.self_time
39
+ end
40
+ end
41
+ end
42
+
43
+ def wait_time
44
+ @wait_time ||= begin
45
+ call_infos.inject(0) do |sum, call_info|
46
+ sum += call_info.wait_time
47
+ end
48
+ end
49
+ end
50
+
51
+ def children_time
52
+ @children_time ||= begin
53
+ call_infos.inject(0) do |sum, call_info|
54
+ sum += call_info.children_time
55
+ end
56
+ end
57
+ end
58
+
59
+ def min_depth
60
+ @min_depth ||= call_infos.map do |call_info|
61
+ call_info.depth
62
+ end.min
63
+ end
64
+
65
+ def root?
66
+ @root ||= begin
67
+ call_infos.find do |call_info|
68
+ not call_info.root?
69
+ end.nil?
70
+ end
71
+ end
72
+
73
+ def children
74
+ @children ||= begin
75
+ call_infos.map do |call_info|
76
+ call_info.children
77
+ end.flatten
78
+ end
79
+ end
80
+
81
+ def aggregate_parents
82
+ # Group call info's based on their parents
83
+ groups = self.call_infos.inject(Hash.new) do |hash, call_info|
84
+ key = call_info.parent ? call_info.parent.target : self
85
+ (hash[key] ||= []) << call_info
86
+ hash
87
+ end
88
+
89
+ groups.map do |key, value|
90
+ AggregateCallInfo.new(value)
91
+ end
92
+ end
93
+
94
+ def aggregate_children
95
+ # Group call info's based on their targets
96
+ groups = self.children.inject(Hash.new) do |hash, call_info|
97
+ key = call_info.target
98
+ (hash[key] ||= []) << call_info
99
+ hash
100
+ end
101
+
102
+ groups.map do |key, value|
103
+ AggregateCallInfo.new(value)
104
+ end
105
+ end
106
+
107
+ def to_s
108
+ full_name
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,8 @@
1
+ unless (:a.respond_to?(:to_proc))
2
+ class Symbol
3
+ def to_proc
4
+ proc {|stuff| stuff.send(self)}
5
+ end
6
+ end
7
+ end
8
+
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rake'
4
+ require 'rake/testtask'
5
+ require 'fileutils'
6
+
7
+ module RubyProf
8
+
9
+ # Define a task library for profiling unit tests with ruby-prof.
10
+ #
11
+ # All of the options provided by
12
+ # the Rake:TestTask are supported except the loader
13
+ # which is set to ruby-prof. For detailed information
14
+ # please refer to the Rake:TestTask documentation.
15
+ #
16
+ # ruby-prof specific options include:
17
+ #
18
+ # output_dir - For each file specified an output
19
+ # file with profile information will be
20
+ # written to the output directory.
21
+ # By default, the output directory is
22
+ # called "profile" and is created underneath
23
+ # the current working directory.
24
+ #
25
+ # printer - Specifies the output printer. Valid values include
26
+ # :flat, :graph, :graph_html and :call_tree.
27
+ #
28
+ # min_percent - Methods that take less than the specified percent
29
+ # will not be written out.
30
+ #
31
+ # Example:
32
+ #
33
+ # require 'ruby-prof/task'
34
+ #
35
+ # RubyProf::ProfileTask.new do |t|
36
+ # t.test_files = FileList['test/test*.rb']
37
+ # t.output_dir = "c:/temp"
38
+ # t.printer = :graph
39
+ # t.min_percent = 10
40
+ # end
41
+ #
42
+ # If rake is invoked with a "TEST=filename" command line option,
43
+ # then the list of test files will be overridden to include only the
44
+ # filename specified on the command line. This provides an easy way
45
+ # to run just one test.
46
+ #
47
+ # If rake is invoked with a "TESTOPTS=options" command line option,
48
+ # then the given options are passed to the test process after a
49
+ # '--'. This allows Test::Unit options to be passed to the test
50
+ # suite.
51
+ #
52
+ # Examples:
53
+ #
54
+ # rake profile # run tests normally
55
+ # rake profile TEST=just_one_file.rb # run just one test file.
56
+ # rake profile TESTOPTS="-v" # run in verbose mode
57
+ # rake profile TESTOPTS="--runner=fox" # use the fox test runner
58
+
59
+ class ProfileTask < Rake::TestTask
60
+ attr_accessor :output_dir
61
+ attr_accessor :min_percent
62
+ attr_accessor :printer
63
+
64
+ def initialize(name = :profile)
65
+ super(name)
66
+ end
67
+
68
+ # Create the tasks defined by this task lib.
69
+ def define
70
+ lib_path = @libs.join(File::PATH_SEPARATOR)
71
+ desc "Profile" + (@name==:profile ? "" : " for #{@name}")
72
+
73
+ task @name do
74
+ create_output_directory
75
+
76
+ @ruby_opts.unshift( "-I#{lib_path}" )
77
+ @ruby_opts.unshift( "-w" ) if @warning
78
+ @ruby_opts.push("-S ruby-prof")
79
+ @ruby_opts.push("--printer #{@printer}")
80
+ @ruby_opts.push("--min_percent #{@min_percent}")
81
+
82
+ file_list.each do |file_path|
83
+ run_script(file_path)
84
+ end
85
+ end
86
+ self
87
+ end
88
+
89
+ # Run script
90
+ def run_script(script_path)
91
+ run_code = ''
92
+ RakeFileUtils.verbose(@verbose) do
93
+ file_name = File.basename(script_path, File.extname(script_path))
94
+ case @printer
95
+ when :flat, :graph, :call_tree
96
+ file_name += ".txt"
97
+ when :graph_html
98
+ file_name += ".html"
99
+ else
100
+ file_name += ".txt"
101
+ end
102
+
103
+ output_file_path = File.join(output_directory, file_name)
104
+
105
+ command_line = @ruby_opts.join(" ") +
106
+ " --file=" + output_file_path +
107
+ " " + script_path
108
+
109
+ puts "ruby " + command_line
110
+ # We have to catch the exeption to continue on. However,
111
+ # the error message will have been output to STDERR
112
+ # already by the time we get here so we don't have to
113
+ # do that again
114
+ begin
115
+ ruby command_line
116
+ rescue => e
117
+ STDOUT << e << "\n"
118
+ STDOUT.flush
119
+ end
120
+ puts ""
121
+ puts ""
122
+ end
123
+ end
124
+
125
+ def output_directory
126
+ File.expand_path(@output_dir)
127
+ end
128
+
129
+ def create_output_directory
130
+ if not File.exist?(output_directory)
131
+ Dir.mkdir(output_directory)
132
+ end
133
+ end
134
+
135
+ def clean_output_directory
136
+ if File.exist?(output_directory)
137
+ files = Dir.glob(output_directory + '/*')
138
+ FileUtils.rm(files)
139
+ end
140
+ end
141
+
142
+ def option_list # :nodoc:
143
+ ENV['OPTIONS'] || @options.join(" ") || ""
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,148 @@
1
+ # Now load ruby-prof and away we go
2
+ require 'fileutils'
3
+ require 'ruby-prof'
4
+ require 'benchmark'
5
+
6
+ module RubyProf
7
+ module Test
8
+ PROFILE_OPTIONS = {
9
+ :measure_modes => [RubyProf::PROCESS_TIME],
10
+ :count => 10,
11
+ :printers => [RubyProf::FlatPrinter, RubyProf::GraphHtmlPrinter],
12
+ :min_percent => 0.05,
13
+ :output_dir => Dir.pwd }
14
+
15
+ def output_dir
16
+ PROFILE_OPTIONS[:output_dir]
17
+ end
18
+
19
+ def run(result)
20
+ return if @method_name.to_s == "default_test"
21
+
22
+ yield(self.class::STARTED, name)
23
+ @_result = result
24
+ run_warmup
25
+ PROFILE_OPTIONS[:measure_modes].each do |measure_mode|
26
+ data = run_profile(measure_mode)
27
+ report_profile(data, measure_mode)
28
+ result.add_run
29
+ end
30
+ yield(self.class::FINISHED, name)
31
+ end
32
+
33
+ def run_test
34
+ begin
35
+ setup
36
+ yield
37
+ rescue ::Test::Unit::AssertionFailedError => e
38
+ add_failure(e.message, e.backtrace)
39
+ rescue StandardError, ScriptError
40
+ add_error($!)
41
+ ensure
42
+ begin
43
+ teardown
44
+ rescue ::Test::Unit::AssertionFailedError => e
45
+ add_failure(e.message, e.backtrace)
46
+ rescue StandardError, ScriptError
47
+ add_error($!)
48
+ end
49
+ end
50
+ end
51
+
52
+ def run_warmup
53
+ print "\n#{self.class.name}##{method_name}"
54
+
55
+ run_test do
56
+ bench = Benchmark.realtime do
57
+ __send__(@method_name)
58
+ end
59
+ puts " (%.2fs warmup)" % bench
60
+ end
61
+ end
62
+
63
+ def run_profile(measure_mode)
64
+ RubyProf.measure_mode = measure_mode
65
+
66
+ print ' '
67
+ PROFILE_OPTIONS[:count].times do |i|
68
+ run_test do
69
+ begin
70
+ print '.'
71
+ $stdout.flush
72
+ GC.disable
73
+
74
+ RubyProf.resume do
75
+ __send__(@method_name)
76
+ end
77
+ ensure
78
+ GC.enable
79
+ end
80
+ end
81
+ end
82
+
83
+ data = RubyProf.stop
84
+ bench = data.threads.values.inject(0) do |total, method_infos|
85
+ top = method_infos.max
86
+ total += top.total_time
87
+ total
88
+ end
89
+
90
+ puts "\n #{measure_mode_name(measure_mode)}: #{format_profile_total(bench, measure_mode)}\n"
91
+
92
+ data
93
+ end
94
+
95
+ def format_profile_total(total, measure_mode)
96
+ case measure_mode
97
+ when RubyProf::PROCESS_TIME, RubyProf::WALL_TIME
98
+ "%.2f seconds" % total
99
+ when RubyProf::MEMORY
100
+ "%.2f kilobytes" % total
101
+ when RubyProf::ALLOCATIONS
102
+ "%d allocations" % total
103
+ else
104
+ "%.2f #{measure_mode}"
105
+ end
106
+ end
107
+
108
+ def report_profile(data, measure_mode)
109
+ PROFILE_OPTIONS[:printers].each do |printer_klass|
110
+ printer = printer_klass.new(data)
111
+
112
+ # Makes sure the output directory exits
113
+ FileUtils.mkdir_p(output_dir)
114
+
115
+ # Open the file
116
+ file_name = report_filename(printer, measure_mode)
117
+
118
+ File.open(file_name, 'wb') do |file|
119
+ printer.print(file, PROFILE_OPTIONS)
120
+ end
121
+ end
122
+ end
123
+
124
+ # The report filename is test_name + measure_mode + report_type
125
+ def report_filename(printer, measure_mode)
126
+ suffix =
127
+ case printer
128
+ when RubyProf::FlatPrinter; 'flat.txt'
129
+ when RubyProf::GraphPrinter; 'graph.txt'
130
+ when RubyProf::GraphHtmlPrinter; 'graph.html'
131
+ when RubyProf::CallTreePrinter; 'tree.txt'
132
+ else printer.to_s.downcase
133
+ end
134
+
135
+ "#{output_dir}/#{method_name}_#{measure_mode_name(measure_mode)}_#{suffix}"
136
+ end
137
+
138
+ def measure_mode_name(measure_mode)
139
+ case measure_mode
140
+ when RubyProf::PROCESS_TIME; 'process_time'
141
+ when RubyProf::WALL_TIME; 'wall_time'
142
+ when RubyProf::MEMORY; 'memory'
143
+ when RubyProf::ALLOCATIONS; 'allocations'
144
+ else "measure#{measure_mode}"
145
+ end
146
+ end
147
+ end
148
+ end