skaes-ruby-prof 0.7.3

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 (67) hide show
  1. data/CHANGES +202 -0
  2. data/LICENSE +23 -0
  3. data/README +436 -0
  4. data/Rakefile +129 -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/extconf.rb +34 -0
  10. data/ext/measure_allocations.h +58 -0
  11. data/ext/measure_cpu_time.h +152 -0
  12. data/ext/measure_gc_runs.h +76 -0
  13. data/ext/measure_gc_time.h +57 -0
  14. data/ext/measure_memory.h +101 -0
  15. data/ext/measure_process_time.h +52 -0
  16. data/ext/measure_wall_time.h +53 -0
  17. data/ext/mingw/Rakefile +23 -0
  18. data/ext/mingw/build.rake +38 -0
  19. data/ext/ruby_prof.c +1747 -0
  20. data/ext/ruby_prof.h +185 -0
  21. data/ext/vc/ruby_prof.sln +20 -0
  22. data/ext/vc/ruby_prof.vcproj +241 -0
  23. data/ext/version.h +4 -0
  24. data/lib/ruby-prof.rb +51 -0
  25. data/lib/ruby-prof/abstract_printer.rb +41 -0
  26. data/lib/ruby-prof/aggregate_call_info.rb +68 -0
  27. data/lib/ruby-prof/call_info.rb +112 -0
  28. data/lib/ruby-prof/call_stack_printer.rb +746 -0
  29. data/lib/ruby-prof/call_tree_printer.rb +84 -0
  30. data/lib/ruby-prof/empty.png +0 -0
  31. data/lib/ruby-prof/flat_printer.rb +79 -0
  32. data/lib/ruby-prof/graph_html_printer.rb +272 -0
  33. data/lib/ruby-prof/graph_printer.rb +164 -0
  34. data/lib/ruby-prof/method_info.rb +131 -0
  35. data/lib/ruby-prof/minus.png +0 -0
  36. data/lib/ruby-prof/multi_printer.rb +55 -0
  37. data/lib/ruby-prof/plus.png +0 -0
  38. data/lib/ruby-prof/result.rb +70 -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 +136 -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/method_elimination_test.rb +74 -0
  53. data/test/module_test.rb +54 -0
  54. data/test/multi_printer_test.rb +81 -0
  55. data/test/no_method_class_test.rb +13 -0
  56. data/test/prime.rb +58 -0
  57. data/test/prime_test.rb +13 -0
  58. data/test/printers_test.rb +73 -0
  59. data/test/recursive_test.rb +215 -0
  60. data/test/singleton_test.rb +38 -0
  61. data/test/stack_printer_test.rb +74 -0
  62. data/test/stack_test.rb +138 -0
  63. data/test/start_stop_test.rb +95 -0
  64. data/test/test_suite.rb +26 -0
  65. data/test/thread_test.rb +159 -0
  66. data/test/unique_call_path_test.rb +206 -0
  67. metadata +128 -0
@@ -0,0 +1,131 @@
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 if call_info.minimal?
31
+ sum
32
+ end
33
+ end
34
+ end
35
+
36
+ def self_time
37
+ @self_time ||= begin
38
+ call_infos.inject(0) do |sum, call_info|
39
+ sum += call_info.self_time
40
+ end
41
+ end
42
+ end
43
+
44
+ def wait_time
45
+ @wait_time ||= begin
46
+ call_infos.inject(0) do |sum, call_info|
47
+ sum += call_info.wait_time
48
+ end
49
+ end
50
+ end
51
+
52
+ def children_time
53
+ @children_time ||= begin
54
+ call_infos.inject(0) do |sum, call_info|
55
+ sum += call_info.children_time if call_info.minimal?
56
+ sum
57
+ end
58
+ end
59
+ end
60
+
61
+ def min_depth
62
+ call_infos.map do |call_info|
63
+ call_info.depth
64
+ end.min
65
+ end
66
+
67
+ def root?
68
+ @root ||= begin
69
+ call_infos.find do |call_info|
70
+ not call_info.root?
71
+ end.nil?
72
+ end
73
+ end
74
+
75
+ def children
76
+ @children ||= begin
77
+ call_infos.map do |call_info|
78
+ call_info.children
79
+ end.flatten
80
+ end
81
+ end
82
+
83
+ def aggregate_parents
84
+ # Group call info's based on their parents
85
+ groups = self.call_infos.inject(Hash.new) do |hash, call_info|
86
+ key = call_info.parent ? call_info.parent.target : self
87
+ (hash[key] ||= []) << call_info
88
+ hash
89
+ end
90
+
91
+ groups.map do |key, value|
92
+ AggregateCallInfo.new(value)
93
+ end
94
+ end
95
+
96
+ def aggregate_children
97
+ # Group call info's based on their targets
98
+ groups = self.children.inject(Hash.new) do |hash, call_info|
99
+ key = call_info.target
100
+ (hash[key] ||= []) << call_info
101
+ hash
102
+ end
103
+
104
+ groups.map do |key, value|
105
+ AggregateCallInfo.new(value)
106
+ end
107
+ end
108
+
109
+ def to_s
110
+ full_name
111
+ end
112
+
113
+ def dump
114
+ res = ""
115
+ res << "MINFO: #{klass_name}##{method_name} total_time: #{total_time} (#{full_name})\n"
116
+ call_infos.each do |ci|
117
+ pinfo = ci.root? ? "TOPLEVEL" : (p=ci.parent.target; "#{p.klass_name}##{p.method_name} (#{ci.parent.object_id}) (#{p.full_name})")
118
+ res << "CINFO[#{ci.object_id}] called #{ci.called} times from #{pinfo}\n"
119
+ end
120
+ res
121
+ end
122
+
123
+ # remove method from the call graph. should not be called directly.
124
+ def eliminate!
125
+ # $stderr.puts "eliminating #{self}"
126
+ call_infos.each{ |call_info| call_info.eliminate! }
127
+ call_infos.clear
128
+ end
129
+
130
+ end
131
+ end
Binary file
@@ -0,0 +1,55 @@
1
+ module RubyProf
2
+ class MultiPrinter
3
+ # Helper class to simplify printing profiles of several types from
4
+ # one profiling run. Currently prints a flat profile, a callgrind
5
+ # profile, a call stack profile and a graph profile.
6
+
7
+ def initialize(result)
8
+ @stack_printer = CallStackPrinter.new(result)
9
+ @graph_printer = GraphHtmlPrinter.new(result)
10
+ @tree_printer = CallTreePrinter.new(result)
11
+ @flat_printer = FlatPrinter.new(result)
12
+ end
13
+
14
+ # create profile files under options[:path] or the current
15
+ # directory. options[:profile] is used as the base name for the
16
+ # pofile file, defaults to "profile".
17
+ def print(options)
18
+ @profile = options.delete(:profile) || "profile"
19
+ @directory = options.delete(:path) || File.expand_path(".")
20
+ File.open(stack_profile, "w") do |f|
21
+ @stack_printer.print(f, options.merge(:graph => "#{@profile}.graph.html"))
22
+ end
23
+ File.open(graph_profile, "w") do |f|
24
+ @graph_printer.print(f, options)
25
+ end
26
+ File.open(tree_profile, "w") do |f|
27
+ @tree_printer.print(f, options)
28
+ end
29
+ File.open(flat_profile, "w") do |f|
30
+ @flat_printer.print(f, options)
31
+ end
32
+ end
33
+
34
+ # the name of the call stack profile file
35
+ def stack_profile
36
+ "#{@directory}/#{@profile}.stack.html"
37
+ end
38
+
39
+ # the name of the graph profile file
40
+ def graph_profile
41
+ "#{@directory}/#{@profile}.graph.html"
42
+ end
43
+
44
+ # the name of the callgrind profile file
45
+ def tree_profile
46
+ "#{@directory}/#{@profile}.grind.dat"
47
+ end
48
+
49
+ # the name of the flat profile file
50
+ def flat_profile
51
+ "#{@directory}/#{@profile}.flat.txt"
52
+ end
53
+
54
+ end
55
+ end
Binary file
@@ -0,0 +1,70 @@
1
+ require 'set'
2
+ module RubyProf
3
+ class Result
4
+ # this method gets called internally when profiling is stopped.
5
+ # it determines for each call_info whether it is minimal: a
6
+ # call_info is minimal in a call tree if the call_info is not a
7
+ # descendant of a call_info of the same method
8
+ def compute_minimality
9
+ threads.each do |threadid, method_infos|
10
+ root_methods = method_infos.select{|mi| mi.root?}
11
+ root_methods.each do |mi|
12
+ mi.call_infos.select{|ci| ci.root?}.each do |call_info_root|
13
+ call_info_root.compute_minimality(Set.new)
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ # eliminate some calls from the graph by merging the information into callers.
20
+ # matchers can be a list of strings or regular expressions or the name of a file containing regexps.
21
+ def eliminate_methods!(matchers)
22
+ matchers = read_regexps_from_file(matchers) if matchers.is_a?(String)
23
+ eliminated = []
24
+ threads.each do |thread_id, methods|
25
+ matchers.each{ |matcher| eliminated.concat(eliminate_methods(methods, matcher)) }
26
+ end
27
+ compute_minimality # is this really necessary?
28
+ eliminated
29
+ end
30
+
31
+ def dump
32
+ threads.each do |thread_id, methods|
33
+ $stderr.puts "Call Info Dump for thread id #{thread_id}"
34
+ methods.each do |method_info|
35
+ $stderr.puts method_info.dump
36
+ end
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ # read regexps from file
43
+ def read_regexps_from_file(file_name)
44
+ matchers = []
45
+ File.open(matchers).each_line do |l|
46
+ next if (l =~ /^(#.*|\s*)$/) # emtpy lines and lines starting with #
47
+ matchers << Regexp.new(l.strip)
48
+ end
49
+ end
50
+
51
+ # eliminate methods matching matcher
52
+ def eliminate_methods(methods, matcher)
53
+ eliminated = []
54
+ i = 0
55
+ while i < methods.size
56
+ method_info = methods[i]
57
+ method_name = method_info.full_name
58
+ if matcher === method_name
59
+ raise "can't eliminate root method" if method_info.root?
60
+ eliminated << methods.delete_at(i)
61
+ method_info.eliminate!
62
+ else
63
+ i += 1
64
+ end
65
+ end
66
+ eliminated
67
+ end
68
+
69
+ end
70
+ 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