jeremy-ruby-prof 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/CHANGES +158 -0
  2. data/LICENSE +23 -0
  3. data/README +416 -0
  4. data/Rakefile +141 -0
  5. data/bin/ruby-prof +196 -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/extconf.rb +24 -0
  10. data/ext/measure_allocations.h +58 -0
  11. data/ext/measure_cpu_time.h +149 -0
  12. data/ext/measure_memory.h +104 -0
  13. data/ext/measure_process_time.h +52 -0
  14. data/ext/measure_wall_time.h +53 -0
  15. data/ext/ruby_prof.c +1680 -0
  16. data/lib/ruby-prof.rb +45 -0
  17. data/lib/ruby-prof/abstract_printer.rb +42 -0
  18. data/lib/ruby-prof/call_tree_printer.rb +76 -0
  19. data/lib/ruby-prof/flat_printer.rb +76 -0
  20. data/lib/ruby-prof/graph_html_printer.rb +255 -0
  21. data/lib/ruby-prof/graph_printer.rb +163 -0
  22. data/lib/ruby-prof/profile_test.rb +147 -0
  23. data/lib/ruby-prof/task.rb +147 -0
  24. data/lib/unprof.rb +8 -0
  25. data/test/basic_test.rb +190 -0
  26. data/test/duplicate_names_test.rb +33 -0
  27. data/test/exceptions_test.rb +19 -0
  28. data/test/line_number_test.rb +69 -0
  29. data/test/measure_mode_test.rb +91 -0
  30. data/test/measurement_test.rb +61 -0
  31. data/test/module_test.rb +57 -0
  32. data/test/no_method_class_test.rb +14 -0
  33. data/test/prime.rb +60 -0
  34. data/test/prime1.rb +17 -0
  35. data/test/prime2.rb +26 -0
  36. data/test/prime3.rb +17 -0
  37. data/test/prime_test.rb +24 -0
  38. data/test/printers_test.rb +74 -0
  39. data/test/profile_unit_test.rb +24 -0
  40. data/test/recursive_test.rb +144 -0
  41. data/test/singleton_test.rb +38 -0
  42. data/test/start_test.rb +24 -0
  43. data/test/test_helper.rb +55 -0
  44. data/test/test_suite.rb +20 -0
  45. data/test/thread_test.rb +135 -0
  46. data/test/timing_test.rb +133 -0
  47. metadata +112 -0
@@ -0,0 +1,163 @@
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_by(&:total_time).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_by(&:total_time)
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.parents.each do |caller|
131
+ @output << " " * 2 * PERCENTAGE_WIDTH
132
+ @output << sprintf("%#{TIME_WIDTH}.2f", caller.total_time)
133
+ @output << sprintf("%#{TIME_WIDTH}.2f", caller.self_time)
134
+ @output << sprintf("%#{TIME_WIDTH}.2f", caller.wait_time)
135
+ @output << sprintf("%#{TIME_WIDTH}.2f", caller.children_time)
136
+
137
+ call_called = "#{caller.called}/#{method.called}"
138
+ @output << sprintf("%#{CALL_WIDTH}s", call_called)
139
+ @output << sprintf(" %s", caller.target.full_name)
140
+ @output << "\n"
141
+ end
142
+ end
143
+
144
+ def print_children(method)
145
+ method.children.each do |child|
146
+ # Get children method
147
+
148
+ @output << " " * 2 * PERCENTAGE_WIDTH
149
+
150
+ @output << sprintf("%#{TIME_WIDTH}.2f", child.total_time)
151
+ @output << sprintf("%#{TIME_WIDTH}.2f", child.self_time)
152
+ @output << sprintf("%#{TIME_WIDTH}.2f", child.wait_time)
153
+ @output << sprintf("%#{TIME_WIDTH}.2f", child.children_time)
154
+
155
+ call_called = "#{child.called}/#{child.target.called}"
156
+ @output << sprintf("%#{CALL_WIDTH}s", call_called)
157
+ @output << sprintf(" %s", child.target.full_name)
158
+ @output << "\n"
159
+ end
160
+ end
161
+ end
162
+ end
163
+
@@ -0,0 +1,147 @@
1
+ # Now load ruby-prof and away we go
2
+ require 'ruby-prof'
3
+
4
+ module RubyProf
5
+ module Test
6
+ PROFILE_OPTIONS = {
7
+ :measure_modes => [RubyProf::PROCESS_TIME],
8
+ :count => 10,
9
+ :printers => [RubyProf::FlatPrinter, RubyProf::GraphHtmlPrinter],
10
+ :min_percent => 0.05,
11
+ :output_dir => Dir.pwd }
12
+
13
+ def run(result)
14
+ return if @method_name.to_s == "default_test"
15
+
16
+ yield(self.class::STARTED, name)
17
+ @_result = result
18
+ run_warmup
19
+ PROFILE_OPTIONS[:measure_modes].each do |measure_mode|
20
+ data = run_profile(measure_mode)
21
+ report_profile(data, measure_mode)
22
+ result.add_run
23
+ end
24
+ yield(self.class::FINISHED, name)
25
+ end
26
+
27
+ def run_test
28
+ begin
29
+ run_setup
30
+ yield
31
+ rescue ::Test::Unit::AssertionFailedError => e
32
+ add_failure(e.message, e.backtrace)
33
+ rescue StandardError, ScriptError
34
+ add_error($!)
35
+ ensure
36
+ begin
37
+ run_teardown
38
+ rescue ::Test::Unit::AssertionFailedError => e
39
+ add_failure(e.message, e.backtrace)
40
+ rescue StandardError, ScriptError
41
+ add_error($!)
42
+ end
43
+ end
44
+ end
45
+
46
+ def run_warmup
47
+ print "\n#{self.class.name}##{method_name}"
48
+
49
+ run_test do
50
+ bench = Benchmark.realtime do
51
+ __send__(@method_name)
52
+ end
53
+ puts " (%.2fs warmup)" % bench
54
+ end
55
+ end
56
+
57
+ def run_setup
58
+ setup
59
+ end
60
+
61
+ def run_teardown
62
+ teardown
63
+ end
64
+
65
+ def run_profile(measure_mode)
66
+ RubyProf.measure_mode = measure_mode
67
+
68
+ print ' '
69
+ PROFILE_OPTIONS[:count].times do |i|
70
+ run_test do
71
+ begin
72
+ print '.'
73
+ $stdout.flush
74
+ GC.disable
75
+
76
+ RubyProf.resume do
77
+ __send__(@method_name)
78
+ end
79
+ ensure
80
+ GC.enable
81
+ end
82
+ end
83
+ end
84
+
85
+ data = RubyProf.stop
86
+ bench = data.threads.values.inject(0) do |total, method_infos|
87
+ top = method_infos.sort.last
88
+ total += top.total_time
89
+ total
90
+ end
91
+
92
+ puts "\n #{measure_mode_name(measure_mode)}: #{format_profile_total(bench, measure_mode)}\n"
93
+
94
+ data
95
+ end
96
+
97
+ def format_profile_total(total, measure_mode)
98
+ case measure_mode
99
+ when RubyProf::PROCESS_TIME, RubyProf::WALL_TIME
100
+ "%.2f seconds" % total
101
+ when RubyProf::MEMORY
102
+ "%.2f kilobytes" % total
103
+ when RubyProf::ALLOCATIONS
104
+ "%d allocations" % total
105
+ else
106
+ "%.2f #{measure_mode}"
107
+ end
108
+ end
109
+
110
+ def report_profile(data, measure_mode)
111
+ PROFILE_OPTIONS[:printers].each do |printer_klass|
112
+ printer = printer_klass.new(data)
113
+
114
+ # Open the file
115
+ file_name = report_filename(printer, measure_mode)
116
+
117
+ File.open(file_name, 'wb') do |file|
118
+ printer.print(file, PROFILE_OPTIONS)
119
+ end
120
+ end
121
+ end
122
+
123
+ # The report filename is test_name + measure_mode + report_type
124
+ def report_filename(printer, measure_mode)
125
+ suffix =
126
+ case printer
127
+ when RubyProf::FlatPrinter; 'flat.txt'
128
+ when RubyProf::GraphPrinter; 'graph.txt'
129
+ when RubyProf::GraphHtmlPrinter; 'graph.html'
130
+ when RubyProf::CallTreePrinter; 'tree.txt'
131
+ else printer.to_s.downcase
132
+ end
133
+
134
+ "#{PROFILE_OPTIONS[:output_dir]}/#{method_name}_#{measure_mode_name(measure_mode)}_#{suffix}"
135
+ end
136
+
137
+ def measure_mode_name(measure_mode)
138
+ case measure_mode
139
+ when RubyProf::PROCESS_TIME; 'process_time'
140
+ when RubyProf::WALL_TIME; 'wall_time'
141
+ when RubyProf::MEMORY; 'memory'
142
+ when RubyProf::ALLOCATIONS; 'allocations'
143
+ else "measure#{measure_mode}"
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,147 @@
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
147
+