ruby-prof 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/CHANGES +30 -0
  2. data/README +65 -25
  3. data/Rakefile +33 -32
  4. data/bin/ruby-prof +100 -83
  5. data/examples/graph.html +65 -69
  6. data/ext/measure_allocations.h +43 -0
  7. data/ext/measure_cpu_time.h +138 -0
  8. data/ext/measure_process_time.h +41 -0
  9. data/ext/measure_wall_time.h +42 -0
  10. data/ext/ruby_prof.c +737 -653
  11. data/lib/ruby-prof.rb +41 -38
  12. data/lib/ruby-prof/abstract_printer.rb +42 -0
  13. data/lib/ruby-prof/call_tree_printer.rb +69 -0
  14. data/lib/ruby-prof/flat_printer.rb +78 -75
  15. data/lib/ruby-prof/graph_html_printer.rb +241 -228
  16. data/lib/ruby-prof/graph_printer.rb +160 -141
  17. data/lib/ruby-prof/profile_test_case.rb +80 -0
  18. data/lib/ruby-prof/rails_plugin/ruby-prof/init.rb +6 -0
  19. data/lib/ruby-prof/rails_plugin/ruby-prof/lib/profiling.rb +52 -0
  20. data/lib/ruby-prof/task.rb +147 -0
  21. data/test/basic_test.rb +65 -35
  22. data/test/duplicate_names_test.rb +20 -24
  23. data/test/gc.log +5 -0
  24. data/test/measure_mode_test.rb +79 -0
  25. data/test/module_test.rb +31 -18
  26. data/test/no_method_class_test.rb +14 -0
  27. data/test/prime1.rb +17 -0
  28. data/test/prime2.rb +26 -0
  29. data/test/prime3.rb +17 -0
  30. data/test/prime_test.rb +10 -10
  31. data/test/printers_test.rb +14 -12
  32. data/test/profile_unit_test.rb +24 -0
  33. data/test/recursive_test.rb +105 -17
  34. data/test/singleton_test.rb +38 -0
  35. data/test/start_test.rb +24 -0
  36. data/test/test_helper.rb +33 -29
  37. data/test/test_suite.rb +10 -2
  38. data/test/thread_test.rb +123 -17
  39. data/test/timing_test.rb +70 -29
  40. metadata +28 -30
  41. data/doc/created.rid +0 -1
  42. data/doc/files/LICENSE.html +0 -0
  43. data/doc/files/README.html +0 -376
  44. data/doc/files/bin/ruby-prof.html +0 -143
  45. data/doc/files/examples/flat_txt.html +0 -179
  46. data/doc/files/examples/graph_html.html +0 -948
  47. data/doc/files/examples/graph_txt.html +0 -297
  48. data/doc/files/ext/ruby_prof_c.html +0 -101
  49. data/doc/files/lib/ruby-prof/flat_printer_rb.html +0 -101
  50. data/doc/files/lib/ruby-prof/graph_html_printer_rb.html +0 -108
  51. data/doc/files/lib/ruby-prof/graph_printer_rb.html +0 -101
  52. data/doc/files/lib/ruby-prof/profiletask_rb.html +0 -109
  53. data/doc/files/lib/ruby-prof_rb.html +0 -111
  54. data/doc/files/lib/unprof_rb.html +0 -108
  55. data/doc/rdoc-style.css +0 -208
  56. data/lib/ruby-prof/profiletask.rb +0 -150
  57. data/test/clock_mode_test.rb +0 -73
@@ -1,141 +1,160 @@
1
- module RubyProf
2
- # Generates graph[link:files/examples/graph_txt.html] profile reports as text.
3
- # To use the graph printer:
4
- #
5
- # result = RubyProf.profile do
6
- # [code to profile]
7
- # end
8
- #
9
- # printer = RubyProf::GraphPrinter.new(result, 5)
10
- # printer.print(STDOUT, 0)
11
- #
12
- # The constructor takes two arguments. The first is
13
- # a RubyProf::Result object generated from a profiling
14
- # run. The second is the minimum %total (the methods
15
- # total time divided by the overall total time) that
16
- # a method must take for it to be printed out in
17
- # the report. Use this parameter to eliminate methods
18
- # that are not important to the overall profiling results.
19
-
20
- class GraphPrinter
21
- PERCENTAGE_WIDTH = 8
22
- TIME_WIDTH = 10
23
- CALL_WIDTH = 20
24
-
25
- # Create a GraphPrinter. Result is a RubyProf::Result
26
- # object generated from a profiling run.
27
- def initialize(result, min_percent = 0)
28
- @result = result
29
- @min_percent = min_percent
30
- end
31
-
32
- # Print a graph report to the provided output.
33
- #
34
- # output - Any IO oject, including STDOUT or a file.
35
- # The default value is STDOUT.
36
- #
37
- # min_percent - The minimum %total (the methods
38
- # total time divided by the overall total time) that
39
- # a method must take for it to be printed out in
40
- # the report. Default value is 0.
41
- def print(output = STDOUT, min_percent = 0)
42
- @output = output
43
- @min_percent = min_percent
44
-
45
- print_threads
46
- end
47
-
48
- private
49
- def print_threads
50
- # sort assumes that spawned threads have higher object_ids
51
- @result.threads.sort.each do |thread_id, methods|
52
- print_methods(thread_id, methods)
53
- @output << "\n" * 2
54
- end
55
- end
56
-
57
- def print_methods(thread_id, methods)
58
- toplevel = @result.toplevel(thread_id)
59
- total_time = toplevel.total_time
60
- if total_time == 0
61
- total_time = 0.01
62
- end
63
-
64
- print_heading(thread_id)
65
-
66
- # Print each method
67
- methods.sort.reverse.each do |pair|
68
- name = pair[0]
69
- method = pair[1]
70
- total_percentage = (method.total_time/total_time) * 100
71
- self_percentage = (method.self_time/total_time) * 100
72
-
73
- next if total_percentage < @min_percent
74
-
75
- @output << "-" * 80 << "\n"
76
-
77
- print_parents(thread_id, method)
78
-
79
- # 1 is for % sign
80
- @output << sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", total_percentage)
81
- @output << sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", self_percentage)
82
- @output << sprintf("%#{TIME_WIDTH}.2f", method.total_time)
83
- @output << sprintf("%#{TIME_WIDTH}.2f", method.self_time)
84
- @output << sprintf("%#{TIME_WIDTH}.2f", method.children_time)
85
- @output << sprintf("%#{CALL_WIDTH}i", method.called)
86
- @output << sprintf(" %s", name)
87
- @output << "\n"
88
-
89
- print_children(thread_id, method)
90
- end
91
- end
92
-
93
- def print_heading(thread_id)
94
- @output << "Thread ID: #{thread_id}\n"
95
- # 1 is for % sign
96
- @output << sprintf("%#{PERCENTAGE_WIDTH}s", "%total")
97
- @output << sprintf("%#{PERCENTAGE_WIDTH}s", "%self")
98
- @output << sprintf("%#{TIME_WIDTH}s", "total")
99
- @output << sprintf("%#{TIME_WIDTH}s", "self")
100
- @output << sprintf("%#{TIME_WIDTH+2}s", "children")
101
- @output << sprintf("%#{CALL_WIDTH-2}s", "calls")
102
- @output << " Name"
103
- @output << "\n"
104
- end
105
-
106
- def print_parents(thread_id, method)
107
- method.parents.each do |name, call_info|
108
- @output << " " * 2 * PERCENTAGE_WIDTH
109
- @output << sprintf("%#{TIME_WIDTH}.2f", call_info.total_time)
110
- @output << sprintf("%#{TIME_WIDTH}.2f", call_info.self_time)
111
- @output << sprintf("%#{TIME_WIDTH}.2f", call_info.children_time)
112
-
113
- call_called = "#{call_info.called}/#{method.called}"
114
- @output << sprintf("%#{CALL_WIDTH}s", call_called)
115
- @output << sprintf(" %s", name)
116
- @output << "\n"
117
- end
118
- end
119
-
120
- def print_children(thread_id, method)
121
- a = method.children
122
- method.children.each do |name, call_info|
123
- # Get children method
124
- methods = @result.threads[thread_id]
125
- children = methods[name]
126
-
127
- @output << " " * 2 * PERCENTAGE_WIDTH
128
-
129
- @output << sprintf("%#{TIME_WIDTH}.2f", call_info.total_time)
130
- @output << sprintf("%#{TIME_WIDTH}.2f", call_info.self_time)
131
- @output << sprintf("%#{TIME_WIDTH}.2f", call_info.children_time)
132
-
133
- call_called = "#{call_info.called}/#{children.called}"
134
- @output << sprintf("%#{CALL_WIDTH}s", call_called)
135
- @output << sprintf(" %s", name)
136
- @output << "\n"
137
- end
138
- end
139
- end
140
- end
141
-
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
+ @output << "\n"
104
+
105
+ print_children(method)
106
+ end
107
+ end
108
+
109
+ def print_heading(thread_id)
110
+ @output << "Thread ID: #{thread_id}\n"
111
+ @output << "Total Time: #{@thread_times[thread_id]}\n"
112
+ @output << "\n"
113
+
114
+ # 1 is for % sign
115
+ @output << sprintf("%#{PERCENTAGE_WIDTH}s", "%total")
116
+ @output << sprintf("%#{PERCENTAGE_WIDTH}s", "%self")
117
+ @output << sprintf("%#{TIME_WIDTH}s", "total")
118
+ @output << sprintf("%#{TIME_WIDTH}s", "self")
119
+ @output << sprintf("%#{TIME_WIDTH}s", "wait")
120
+ @output << sprintf("%#{TIME_WIDTH}s", "child")
121
+ @output << sprintf("%#{CALL_WIDTH}s", "calls")
122
+ @output << " Name"
123
+ @output << "\n"
124
+ end
125
+
126
+ def print_parents(thread_id, method)
127
+ method.parents.each do |caller|
128
+ @output << " " * 2 * PERCENTAGE_WIDTH
129
+ @output << sprintf("%#{TIME_WIDTH}.2f", caller.total_time)
130
+ @output << sprintf("%#{TIME_WIDTH}.2f", caller.self_time)
131
+ @output << sprintf("%#{TIME_WIDTH}.2f", caller.wait_time)
132
+ @output << sprintf("%#{TIME_WIDTH}.2f", caller.children_time)
133
+
134
+ call_called = "#{caller.called}/#{method.called}"
135
+ @output << sprintf("%#{CALL_WIDTH}s", call_called)
136
+ @output << sprintf(" %s", caller.target.full_name)
137
+ @output << "\n"
138
+ end
139
+ end
140
+
141
+ def print_children(method)
142
+ method.children.each do |child|
143
+ # Get children method
144
+
145
+ @output << " " * 2 * PERCENTAGE_WIDTH
146
+
147
+ @output << sprintf("%#{TIME_WIDTH}.2f", child.total_time)
148
+ @output << sprintf("%#{TIME_WIDTH}.2f", child.self_time)
149
+ @output << sprintf("%#{TIME_WIDTH}.2f", child.wait_time)
150
+ @output << sprintf("%#{TIME_WIDTH}.2f", child.children_time)
151
+
152
+ call_called = "#{child.called}/#{child.target.called}"
153
+ @output << sprintf("%#{CALL_WIDTH}s", call_called)
154
+ @output << sprintf(" %s", child.target.full_name)
155
+ @output << "\n"
156
+ end
157
+ end
158
+ end
159
+ end
160
+
@@ -0,0 +1,80 @@
1
+ # Make sure to first load the libraries we will override
2
+ require 'test/unit'
3
+ require 'ruby-prof'
4
+
5
+ module Test
6
+ module Unit
7
+ class TestCase
8
+
9
+ alias :run__profile__ :run
10
+
11
+ def run(result, &block)
12
+ test_name = @method_name.to_sym
13
+ alias_test_name = (@method_name + '__profile__').to_sym
14
+
15
+ self.class.class_eval("alias :#{alias_test_name} :#{test_name}")
16
+
17
+ self.class.send(:define_method, test_name) do
18
+ # Run the profiler
19
+ RubyProf.start
20
+ __send__(alias_test_name)
21
+ result = RubyProf.stop
22
+
23
+ create_output_directory
24
+
25
+ # Get the result file name
26
+ file_name = name.gsub(/\(/, '_').gsub(/\)/, '')
27
+ file_name = self.underscore(file_name)
28
+ file_path = File.join(output_directory, file_name)
29
+ file_path += file_extension
30
+
31
+ # Create a printer
32
+ printer = self.printer.new(result)
33
+
34
+ # Write the results
35
+ File.open(file_path, 'w') do |file|
36
+ printer.print(file, min_percent)
37
+ end
38
+ end
39
+
40
+ self.run__profile__(result, &block)
41
+ end
42
+
43
+ # Taken from rails
44
+ def underscore(camel_cased_word)
45
+ camel_cased_word.to_s.gsub(/::/, '/').
46
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
47
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
48
+ tr("-", "_").downcase
49
+ end
50
+
51
+ # Add some additional methods
52
+ def min_percent
53
+ 1
54
+ end
55
+
56
+ def output_directory
57
+ # Put results in subdirectory called profile
58
+ File.join(Dir.getwd, 'profile')
59
+ end
60
+
61
+ def create_output_directory
62
+ if not File.exist?(output_directory)
63
+ Dir.mkdir(output_directory)
64
+ end
65
+ end
66
+
67
+ def file_extension
68
+ if printer == RubyProf::FlatPrinter
69
+ '.html'
70
+ else
71
+ '.txt'
72
+ end
73
+ end
74
+
75
+ def printer
76
+ RubyProf::GraphHtmlPrinter
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,6 @@
1
+ require 'profiling'
2
+
3
+ ActionController::Base.class_eval do
4
+ include ActionController::Profiling
5
+ end
6
+
@@ -0,0 +1,52 @@
1
+ require 'ruby-prof'
2
+
3
+ module ActionController #:nodoc:
4
+ # The ruby-prof module times the performance of actions and reports to the logger. If the Active Record
5
+ # package has been included, a separate timing section for database calls will be added as well.
6
+ module Profiling #:nodoc:
7
+ def self.included(base)
8
+ base.class_eval do
9
+ alias_method_chain :perform_action, :profiling
10
+ end
11
+ end
12
+
13
+ def perform_action_with_profiling
14
+ if not logger or
15
+ not logger.level == Logger::DEBUG
16
+ perform_action_without_profiling
17
+ else
18
+ result = RubyProf.profile do
19
+ perform_action_without_profiling
20
+ end
21
+
22
+ output = StringIO.new
23
+ output << " [#{complete_request_uri rescue "unknown"}]"
24
+ output << "\n\n"
25
+
26
+ # Create a flat printer
27
+ printer = RubyProf::FlatPrinter.new(result)
28
+
29
+ # Skip anything less than 1% - which is a lot of
30
+ # stuff in Rails. Don't print the source file
31
+ # its too noisy.
32
+ printer.print(output, {:min_percent => 1,
33
+ :print_file => false})
34
+ logger.info(output.string)
35
+
36
+ ## Example for Graph html printer
37
+ #printer = RubyProf::GraphHtmlPrinter.new(result)
38
+ #File.open('c:/temp/request.html', 'w') do |file|
39
+ #printer.print(file, {:min_percent => 1,
40
+ #:print_file => true})
41
+ #end
42
+
43
+ ## Used for KCacheGrind visualizations
44
+ #printer = RubyProf::CallTreePrinter.new(result)
45
+ #File.open('c:/temp/callgrind.out', 'w') do |file|
46
+ #printer.print(file, {:min_percent => 1,
47
+ #:print_file => true})
48
+ #end
49
+ end
50
+ end
51
+ end
52
+ 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
+