rdp-ruby-prof 0.7.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/CHANGES +202 -0
  2. data/LICENSE +23 -0
  3. data/README +445 -0
  4. data/Rakefile +123 -0
  5. data/bin/ruby-prof +207 -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.c# +1679 -0
  10. data/ext/Makefile +180 -0
  11. data/ext/extconf.rb +40 -0
  12. data/ext/measure_allocations.h +58 -0
  13. data/ext/measure_cpu_time.h +152 -0
  14. data/ext/measure_gc_runs.h +76 -0
  15. data/ext/measure_gc_time.h +57 -0
  16. data/ext/measure_memory.h +101 -0
  17. data/ext/measure_process_time.h +52 -0
  18. data/ext/measure_wall_time.h +53 -0
  19. data/ext/mingw/Rakefile +23 -0
  20. data/ext/mingw/build.rake +38 -0
  21. data/ext/ruby_prof.c +1707 -0
  22. data/ext/ruby_prof.e +19984 -0
  23. data/ext/ruby_prof.h +188 -0
  24. data/ext/vc/ruby_prof.sln +20 -0
  25. data/ext/vc/ruby_prof.vcproj +241 -0
  26. data/ext/version.h +4 -0
  27. data/lib/ruby-prof.rb +48 -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_printer.rb +84 -0
  32. data/lib/ruby-prof/flat_printer.rb +79 -0
  33. data/lib/ruby-prof/graph_html_printer.rb +256 -0
  34. data/lib/ruby-prof/graph_html_printer.rb.orig +256 -0
  35. data/lib/ruby-prof/graph_html_printer.rb.rej +34 -0
  36. data/lib/ruby-prof/graph_printer.rb +164 -0
  37. data/lib/ruby-prof/graph_printer.rb.orig +164 -0
  38. data/lib/ruby-prof/method_info.rb +111 -0
  39. data/lib/ruby-prof/task.rb +146 -0
  40. data/lib/ruby-prof/test.rb +148 -0
  41. data/lib/unprof.rb +8 -0
  42. data/rails/environment/profile.rb +24 -0
  43. data/rails/example/example_test.rb +9 -0
  44. data/rails/profile_test_helper.rb +21 -0
  45. data/test/aggregate_test.rb +121 -0
  46. data/test/basic_test.rb +283 -0
  47. data/test/duplicate_names_test.rb +32 -0
  48. data/test/exceptions_test.rb +15 -0
  49. data/test/exclude_threads_test.rb +54 -0
  50. data/test/line_number_test.rb +73 -0
  51. data/test/measurement_test.rb +121 -0
  52. data/test/module_test.rb +54 -0
  53. data/test/no_method_class_test.rb +13 -0
  54. data/test/prime.rb +58 -0
  55. data/test/prime_test.rb +13 -0
  56. data/test/printers_test.rb +71 -0
  57. data/test/recursive_test.rb +254 -0
  58. data/test/singleton_test.rb +37 -0
  59. data/test/stack_test.rb +138 -0
  60. data/test/start_stop_test.rb +95 -0
  61. data/test/test_suite.rb +23 -0
  62. data/test/thread_test.rb +159 -0
  63. data/test/unique_call_path_test.rb +206 -0
  64. metadata +124 -0
@@ -0,0 +1,164 @@
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. The first is
15
+ # a RubyProf::Result object generated from a profiling
16
+ # run. The second is the minimum %total (the methods
17
+ # total time divided by the overall total time) that
18
+ # a method must take for it to be printed out in
19
+ # the report. Use this parameter to eliminate methods
20
+ # that are not important to the overall profiling results.
21
+
22
+ class GraphPrinter < AbstractPrinter
23
+ PERCENTAGE_WIDTH = 8
24
+ TIME_WIDTH = 10
25
+ CALL_WIDTH = 17
26
+
27
+ # Create a GraphPrinter. Result is a RubyProf::Result
28
+ # object generated from a profiling run.
29
+ def initialize(result)
30
+ super(result)
31
+ @thread_times = Hash.new
32
+ calculate_thread_times
33
+ end
34
+
35
+ def calculate_thread_times
36
+ # Cache thread times since this is an expensive
37
+ # operation with the required sorting
38
+ @result.threads.each do |thread_id, methods|
39
+ top = methods.sort.last
40
+
41
+ thread_time = 0.01
42
+ thread_time = top.total_time if top.total_time > 0
43
+
44
+ @thread_times[thread_id] = thread_time
45
+ end
46
+ end
47
+
48
+ # Print a graph report to the provided output.
49
+ #
50
+ # output - Any IO oject, including STDOUT or a file.
51
+ # The default value is STDOUT.
52
+ #
53
+ # options - Hash of print options. See #setup_options
54
+ # for more information.
55
+ #
56
+ def print(output = STDOUT, options = {})
57
+ @output = output
58
+ setup_options(options)
59
+ print_threads
60
+ end
61
+
62
+ private
63
+ def print_threads
64
+ # sort assumes that spawned threads have higher object_ids
65
+ @result.threads.sort.each do |thread_id, methods|
66
+ print_methods(thread_id, methods)
67
+ @output << "\n" * 2
68
+ end
69
+ end
70
+
71
+ def print_methods(thread_id, methods)
72
+ # Sort methods from longest to shortest total time
73
+ methods = methods.sort
74
+
75
+ toplevel = methods.last
76
+ total_time = toplevel.total_time
77
+ if total_time == 0
78
+ total_time = 0.01
79
+ end
80
+
81
+ print_heading(thread_id)
82
+
83
+ # Print each method in total time order
84
+ methods.reverse_each do |method|
85
+ total_percentage = (method.total_time/total_time) * 100
86
+ self_percentage = (method.self_time/total_time) * 100
87
+
88
+ next if total_percentage < min_percent
89
+
90
+ @output << "-" * 80 << "\n"
91
+
92
+ print_parents(thread_id, method)
93
+
94
+ # 1 is for % sign
95
+ @output << sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", total_percentage)
96
+ @output << sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", self_percentage)
97
+ @output << sprintf("%#{TIME_WIDTH}.2f", method.total_time)
98
+ @output << sprintf("%#{TIME_WIDTH}.2f", method.self_time)
99
+ @output << sprintf("%#{TIME_WIDTH}.2f", method.wait_time)
100
+ @output << sprintf("%#{TIME_WIDTH}.2f", method.children_time)
101
+ @output << sprintf("%#{CALL_WIDTH}i", method.called)
102
+ @output << sprintf(" %s", method_name(method))
103
+ if print_file
104
+ @output << sprintf(" %s:%s", method.source_file, method.line)
105
+ end
106
+ @output << "\n"
107
+
108
+ print_children(method)
109
+ end
110
+ end
111
+
112
+ def print_heading(thread_id)
113
+ @output << "Thread ID: #{thread_id}\n"
114
+ @output << "Total Time: #{@thread_times[thread_id]}\n"
115
+ @output << "\n"
116
+
117
+ # 1 is for % sign
118
+ @output << sprintf("%#{PERCENTAGE_WIDTH}s", "%total")
119
+ @output << sprintf("%#{PERCENTAGE_WIDTH}s", "%self")
120
+ @output << sprintf("%#{TIME_WIDTH}s", "total")
121
+ @output << sprintf("%#{TIME_WIDTH}s", "self")
122
+ @output << sprintf("%#{TIME_WIDTH}s", "wait")
123
+ @output << sprintf("%#{TIME_WIDTH}s", "child")
124
+ @output << sprintf("%#{CALL_WIDTH}s", "calls")
125
+ @output << " Name"
126
+ @output << "\n"
127
+ end
128
+
129
+ def print_parents(thread_id, method)
130
+ method.aggregate_parents.each do |caller|
131
+ next unless caller.parent
132
+ @output << " " * 2 * PERCENTAGE_WIDTH
133
+ @output << sprintf("%#{TIME_WIDTH}.2f", caller.total_time)
134
+ @output << sprintf("%#{TIME_WIDTH}.2f", caller.self_time)
135
+ @output << sprintf("%#{TIME_WIDTH}.2f", caller.wait_time)
136
+ @output << sprintf("%#{TIME_WIDTH}.2f", caller.children_time)
137
+
138
+ call_called = "#{caller.called}/#{method.called}"
139
+ @output << sprintf("%#{CALL_WIDTH}s", call_called)
140
+ @output << sprintf(" %s", caller.parent.target.full_name)
141
+ @output << "\n"
142
+ end
143
+ end
144
+
145
+ def print_children(method)
146
+ method.aggregate_children.each do |child|
147
+ # Get children method
148
+
149
+ @output << " " * 2 * PERCENTAGE_WIDTH
150
+
151
+ @output << sprintf("%#{TIME_WIDTH}.2f", child.total_time)
152
+ @output << sprintf("%#{TIME_WIDTH}.2f", child.self_time)
153
+ @output << sprintf("%#{TIME_WIDTH}.2f", child.wait_time)
154
+ @output << sprintf("%#{TIME_WIDTH}.2f", child.children_time)
155
+
156
+ call_called = "#{child.called}/#{child.target.called}"
157
+ @output << sprintf("%#{CALL_WIDTH}s", call_called)
158
+ @output << sprintf(" %s", child.target.full_name)
159
+ @output << "\n"
160
+ end
161
+ end
162
+ end
163
+ end
164
+
@@ -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
+ 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,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.sort.last
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