ruby-prof 0.11.0.rc1 → 0.11.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/CHANGES +20 -5
  2. data/README.rdoc +10 -3
  3. data/ext/ruby_prof/rp_call_info.c +108 -79
  4. data/ext/ruby_prof/rp_call_info.h +1 -0
  5. data/ext/ruby_prof/rp_measure_cpu_time.c +111 -111
  6. data/ext/ruby_prof/rp_measure_gc_runs.c +1 -1
  7. data/ext/ruby_prof/rp_measure_memory.c +1 -1
  8. data/ext/ruby_prof/rp_measure_process_time.c +71 -71
  9. data/ext/ruby_prof/rp_measure_wall_time.c +1 -1
  10. data/ext/ruby_prof/rp_method.c +143 -73
  11. data/ext/ruby_prof/rp_method.h +7 -4
  12. data/ext/ruby_prof/rp_stack.c +16 -1
  13. data/ext/ruby_prof/rp_stack.h +4 -1
  14. data/ext/ruby_prof/rp_thread.c +165 -35
  15. data/ext/ruby_prof/rp_thread.h +8 -2
  16. data/ext/ruby_prof/ruby_prof.c +164 -171
  17. data/ext/ruby_prof/ruby_prof.h +53 -54
  18. data/ext/ruby_prof/vc/ruby_prof.sln +26 -0
  19. data/ext/ruby_prof/vc/ruby_prof.vcxproj +109 -0
  20. data/ext/ruby_prof/vc/ruby_prof_18.vcxproj +105 -0
  21. data/lib/ruby-prof/aggregate_call_info.rb +9 -7
  22. data/lib/ruby-prof/call_info.rb +2 -27
  23. data/lib/ruby-prof/call_info_visitor.rb +42 -0
  24. data/lib/ruby-prof/{empty.png → images/empty.png} +0 -0
  25. data/lib/ruby-prof/{minus.png → images/minus.png} +0 -0
  26. data/lib/ruby-prof/{plus.png → images/plus.png} +0 -0
  27. data/lib/ruby-prof/method_info.rb +13 -15
  28. data/lib/ruby-prof/{abstract_printer.rb → printers/abstract_printer.rb} +36 -2
  29. data/lib/ruby-prof/printers/call_info_printer.rb +40 -0
  30. data/lib/ruby-prof/{call_stack_printer.rb → printers/call_stack_printer.rb} +11 -16
  31. data/lib/ruby-prof/{call_tree_printer.rb → printers/call_tree_printer.rb} +4 -4
  32. data/lib/ruby-prof/{dot_printer.rb → printers/dot_printer.rb} +11 -31
  33. data/lib/ruby-prof/{flat_printer.rb → printers/flat_printer.rb} +26 -35
  34. data/lib/ruby-prof/{flat_printer_with_line_numbers.rb → printers/flat_printer_with_line_numbers.rb} +14 -25
  35. data/lib/ruby-prof/printers/graph_html_printer.rb +248 -0
  36. data/lib/ruby-prof/{graph_printer.rb → printers/graph_printer.rb} +31 -73
  37. data/lib/ruby-prof/{multi_printer.rb → printers/multi_printer.rb} +0 -0
  38. data/lib/ruby-prof/profile.rb +27 -22
  39. data/lib/ruby-prof/rack.rb +22 -12
  40. data/ruby-prof.gemspec +58 -0
  41. data/test/aggregate_test.rb +6 -6
  42. data/test/call_info_visitor_test.rb +31 -0
  43. data/test/duplicate_names_test.rb +1 -1
  44. data/test/dynamic_method_test.rb +1 -1
  45. data/test/enumerable_test.rb +1 -1
  46. data/test/exclude_threads_test.rb +2 -2
  47. data/test/gc_test.rb +35 -0
  48. data/test/line_number_test.rb +2 -2
  49. data/test/measure_cpu_time_test.rb +5 -5
  50. data/test/measure_process_time_test.rb +5 -5
  51. data/test/measure_wall_time_test.rb +5 -5
  52. data/test/method_elimination_test.rb +3 -3
  53. data/test/module_test.rb +1 -1
  54. data/test/no_method_class_test.rb +1 -1
  55. data/test/printers_test.rb +16 -8
  56. data/test/recursive_test.rb +115 -91
  57. data/test/stack_test.rb +1 -1
  58. data/test/start_stop_test.rb +13 -13
  59. data/test/summarize_test.rb +48 -0
  60. data/test/test_suite.rb +1 -0
  61. data/test/thread_test.rb +16 -12
  62. data/test/unique_call_path_test.rb +10 -10
  63. metadata +64 -85
  64. data/lib/1.8/ruby_prof.so +0 -0
  65. data/lib/1.9/ruby_prof.exp +0 -0
  66. data/lib/1.9/ruby_prof.ilk +0 -0
  67. data/lib/1.9/ruby_prof.lib +0 -0
  68. data/lib/1.9/ruby_prof.pdb +0 -0
  69. data/lib/1.9/ruby_prof.so +0 -0
  70. data/lib/ruby-prof.rb +0 -70
  71. data/lib/ruby-prof/graph_html_printer.rb +0 -286
  72. data/lib/ruby-prof/symbol_to_proc.rb +0 -10
  73. data/lib/ruby_prof.exp +0 -0
  74. data/lib/ruby_prof.ilk +0 -0
  75. data/lib/ruby_prof.lib +0 -0
  76. data/lib/ruby_prof.pdb +0 -0
  77. data/lib/ruby_prof.so +0 -0
  78. data/lib/unprof.rb +0 -10
  79. data/test/do_nothing.rb +0 -0
@@ -3,16 +3,31 @@
3
3
  require 'set'
4
4
  module RubyProf
5
5
  class Profile
6
- # this method gets called internally when profiling is stopped.
7
- # it determines for each call_info whether it is minimal: a
8
- # call_info is minimal in a call tree if the call_info is not a
9
- # descendant of a call_info of the same method
10
- def compute_minimality
11
- threads.each do |threadid, method_infos|
12
- root_methods = method_infos.select{|mi| mi.root?}
13
- root_methods.each do |mi|
14
- mi.call_infos.select{|ci| ci.root?}.each do |call_info_root|
15
- call_info_root.compute_minimality(Set.new)
6
+ # This method gets called once profiling has been completed
7
+ # but before results are returned to the user. Thus it provides
8
+ # a hook to do any necessary post-processing on the call graph.
9
+ def post_process
10
+ self.threads.each do |thread|
11
+ detect_recursion(thread)
12
+ end
13
+ end
14
+
15
+ # This method detect recursive calls in the call graph.
16
+ def detect_recursion(thread)
17
+ visited_methods = Hash.new do |hash, key|
18
+ hash[key] = 0
19
+ end
20
+
21
+ visitor = CallInfoVisitor.new(thread)
22
+ visitor.visit do |call_info, event|
23
+ case event
24
+ when :enter
25
+ visited_methods[call_info.target] += 1
26
+ call_info.recursive = (visited_methods[call_info.target] > 1)
27
+ when :exit
28
+ visited_methods[call_info.target] -= 1
29
+ if visited_methods[call_info.target] == 0
30
+ visited_methods.delete(call_info.target)
16
31
  end
17
32
  end
18
33
  end
@@ -23,22 +38,12 @@ module RubyProf
23
38
  def eliminate_methods!(matchers)
24
39
  matchers = read_regexps_from_file(matchers) if matchers.is_a?(String)
25
40
  eliminated = []
26
- threads.each do |thread_id, methods|
27
- matchers.each{ |matcher| eliminated.concat(eliminate_methods(methods, matcher)) }
41
+ threads.each do |thread|
42
+ matchers.each{ |matcher| eliminated.concat(eliminate_methods(thread.methods, matcher)) }
28
43
  end
29
- compute_minimality # is this really necessary?
30
44
  eliminated
31
45
  end
32
46
 
33
- def dump
34
- threads.each do |thread_id, methods|
35
- $stderr.puts "Call Info Dump for thread id #{thread_id}"
36
- methods.each do |method_info|
37
- $stderr.puts method_info.dump
38
- end
39
- end
40
- end
41
-
42
47
  private
43
48
 
44
49
  # read regexps from file
@@ -1,29 +1,39 @@
1
1
  # encoding: utf-8
2
+ require 'tmpdir'
2
3
 
3
4
  module Rack
4
5
  class RubyProf
5
- def initialize(app)
6
+ def initialize(app, options = {})
6
7
  @app = app
8
+ @options = options
9
+ @options[:min_percent] ||= 1
10
+ @tmpdir = options[:path] || Dir.tmpdir
11
+ @printer_klasses = {::RubyProf::FlatPrinter => 'flat.txt',
12
+ ::RubyProf::GraphPrinter => 'graph.txt',
13
+ ::RubyProf::GraphHtmlPrinter => 'graph.html',
14
+ ::RubyProf::CallStackPrinter => 'call_stack.html'}
7
15
  end
8
16
 
9
17
  def call(env)
10
- ::RubyProf.start
11
- result = @app.call(env)
12
- data = ::RubyProf.stop
18
+ result = nil
19
+ data = ::RubyProf::Profile.profile do
20
+ result = @app.call(env)
21
+ end
22
+
23
+ request = Rack::Request.new(env)
24
+ path = request.path.gsub('/', '-')
25
+ path.slice!(0)
13
26
 
14
- print(data)
27
+ print(data, path)
15
28
  result
16
29
  end
17
30
 
18
- def print(data)
19
- require 'tmpdir' # late require so we load on demand only
20
- printers = {::RubyProf::FlatPrinter => ::File.join(Dir.tmpdir, 'profile.txt'),
21
- ::RubyProf::GraphHtmlPrinter => ::File.join(Dir.tmpdir, 'profile.html')}
22
-
23
- printers.each do |printer_klass, file_name|
31
+ def print(data, path)
32
+ @printer_klasses.each do |printer_klass, base_name|
24
33
  printer = printer_klass.new(data)
34
+ file_name = ::File.join(@tmpdir, "#{path}-#{base_name}")
25
35
  ::File.open(file_name, 'wb') do |file|
26
- printer.print(file, :min_percent => 0.00000001 )
36
+ printer.print(file, @options)
27
37
  end
28
38
  end
29
39
  end
data/ruby-prof.gemspec ADDED
@@ -0,0 +1,58 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Read version from header file
4
+ version_header = File.read(File.expand_path('../ext/ruby_prof/version.h', __FILE__))
5
+ #match = version_header.match(/RUBY_PROF_VERSION\s*"(\.+)"/)
6
+ match = version_header.match(/RUBY_PROF_VERSION\s*"([^"]+)"/)
7
+ raise(RuntimeError, "Could not determine RUBY_PROF_VERSION") if not match
8
+
9
+ # For now make this an rc1
10
+ RUBY_PROF_VERSION = "#{match[1]}.rc2"
11
+
12
+ Gem::Specification.new do |spec|
13
+ spec.name = "ruby-prof"
14
+
15
+ spec.homepage = "http://rubyforge.org/projects/ruby-prof/"
16
+ spec.summary = "Fast Ruby profiler"
17
+ spec.description = <<-EOF
18
+ ruby-prof is a fast code profiler for Ruby. It is a C extension and
19
+ therefore is many times faster than the standard Ruby profiler. It
20
+ supports both flat and graph profiles. For each method, graph profiles
21
+ show how long the method ran, which methods called it and which
22
+ methods it called. RubyProf generate both text and html and can output
23
+ it to standard out or to a file.
24
+ EOF
25
+
26
+ spec.version = RUBY_PROF_VERSION
27
+
28
+ spec.author = "Shugo Maeda, Charlie Savage, Roger Pack, Stefan Kaes"
29
+ spec.email = "shugo@ruby-lang.org, cfis@savagexi.com, rogerdpack@gmail.com, skaes@railsexpress.de"
30
+ spec.platform = Gem::Platform::RUBY
31
+ spec.require_path = "lib"
32
+ spec.bindir = "bin"
33
+ spec.executables = ["ruby-prof"]
34
+ spec.extensions = ["ext/ruby_prof/extconf.rb"]
35
+ spec.files = Dir['CHANGES',
36
+ 'LICENSE',
37
+ 'Rakefile',
38
+ 'README.rdoc',
39
+ 'ruby-prof.gemspec',
40
+ 'bin/ruby-prof',
41
+ 'doc/**/*',
42
+ 'examples/*',
43
+ 'ext/ruby_prof/extconf.rb',
44
+ 'ext/ruby_prof/*.c',
45
+ 'ext/ruby_prof/*.h',
46
+ 'ext/ruby_prof/vc/*.sln',
47
+ 'ext/ruby_prof/vc/*.vcxproj',
48
+ 'lib/ruby-prof/*.rb',
49
+ 'lib/ruby-prof/images/*.png',
50
+ 'lib/ruby-prof/printers/*.rb',
51
+ 'test/*.rb']
52
+
53
+ spec.test_files = Dir["test/test_*.rb"]
54
+ spec.required_ruby_version = '>= 1.8.7'
55
+ spec.date = DateTime.now
56
+ spec.homepage = 'https://github.com/rdp/ruby-prof'
57
+ spec.add_development_dependency 'rake-compiler'
58
+ end
@@ -34,17 +34,17 @@ class AggregateTest < Test::Unit::TestCase
34
34
  RubyProf::measure_mode = RubyProf::WALL_TIME
35
35
  end
36
36
 
37
- def test_all_call_infos_are_minimal_as_there_is_no_recursion
37
+ def test_all_call_infos_are_not_recursive
38
38
  c1 = AggClass.new
39
39
  result = RubyProf.profile do
40
40
  c1.a
41
41
  c1.b
42
42
  c1.c
43
43
  end
44
- methods = result.threads.values.first.sort.reverse
44
+ methods = result.threads.first.methods.sort.reverse
45
45
  methods.each do |m|
46
46
  m.call_infos.each do |ci|
47
- assert ci.minimal?, "#{ci.call_sequence} should be minimal in the call tree"
47
+ assert(!ci.recursive)
48
48
  end
49
49
  end
50
50
  end
@@ -57,7 +57,7 @@ class AggregateTest < Test::Unit::TestCase
57
57
  c1.c
58
58
  end
59
59
 
60
- methods = result.threads.values.first.sort.reverse
60
+ methods = result.threads.first.methods.sort.reverse
61
61
  method = methods.find {|meth| meth.full_name == 'AggClass#z'}
62
62
 
63
63
  # Check AggClass#z
@@ -90,7 +90,7 @@ class AggregateTest < Test::Unit::TestCase
90
90
  c1.c
91
91
  end
92
92
 
93
- methods = result.threads.values.first.sort.reverse
93
+ methods = result.threads.first.methods.sort.reverse
94
94
  method = methods.find {|meth| meth.full_name == 'AggClass#z'}
95
95
 
96
96
  # Check AggClass#z
@@ -116,7 +116,7 @@ class AggregateTest < Test::Unit::TestCase
116
116
  c1.c
117
117
  end
118
118
 
119
- methods = result.threads.values.first.sort.reverse
119
+ methods = result.threads.first.methods.sort.reverse
120
120
  method = methods.find {|meth| meth.full_name == 'AggClass#a'}
121
121
 
122
122
  # Check AggClass#a
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ require File.expand_path('../test_helper', __FILE__)
5
+
6
+ class CallInfoVisitorTest < Test::Unit::TestCase
7
+ def setup
8
+ # Need to use wall time for this test due to the sleep calls
9
+ RubyProf::measure_mode = RubyProf::CPU_TIME
10
+ end
11
+
12
+ def test_visit
13
+ result = RubyProf.profile do
14
+ RubyProf::C1.hello
15
+ end
16
+
17
+ visitor = RubyProf::CallInfoVisitor.new(result.threads.first)
18
+
19
+ method_names = Array.new
20
+
21
+ visitor.visit do |call_info, event|
22
+ method_names << call_info.target.full_name if event == :enter
23
+ end
24
+
25
+ assert_equal(3, method_names.length)
26
+ assert_equal("CallInfoVisitorTest#test_visit", method_names[0])
27
+ assert_equal("<Class::RubyProf::C1>#hello", method_names[1])
28
+ assert_equal("Kernel#sleep", method_names[2])
29
+ end
30
+ end
31
+
@@ -21,7 +21,7 @@ class DuplicateNames < Test::Unit::TestCase
21
21
  end
22
22
 
23
23
  # There should be 3 foo methods
24
- methods = result.threads.values.first.sort.reverse
24
+ methods = result.threads.first.methods.sort.reverse
25
25
 
26
26
  methods = methods.select do |method|
27
27
  method.full_name == 'DuplicateNames::Foo::Bar#foo'
@@ -19,7 +19,7 @@ class DynamicMethodTest < Test::Unit::TestCase
19
19
  # Integer#times
20
20
  # DynamicMethodTest#test_dynamic_method
21
21
 
22
- methods = result.threads.values.first.sort.reverse
22
+ methods = result.threads.first.methods.sort.reverse
23
23
  assert_equal(7, methods.length)
24
24
 
25
25
  # Check times
@@ -11,6 +11,6 @@ class EnumerableTest < Test::Unit::TestCase
11
11
  result = RubyProf.profile do
12
12
  3.times { [1,2,3].any? {|n| n} }
13
13
  end
14
- assert result.threads.to_a.first[1].length == 4
14
+ assert_equal(result.threads.first.methods.length, 4)
15
15
  end
16
16
  end
@@ -40,8 +40,8 @@ class ExcludeThreadsTest < Test::Unit::TestCase
40
40
  assert_equal(2, result.threads.length)
41
41
 
42
42
  output = Array.new
43
- result.threads.each do | thread_id, methods |
44
- methods.each do | m |
43
+ result.threads.each do |thread|
44
+ thread.methods.each do | m |
45
45
  if m.full_name.index("ExcludeThreadsTest#thread") == 0
46
46
  output.push(m.full_name)
47
47
  end
data/test/gc_test.rb ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ require File.expand_path('../test_helper', __FILE__)
5
+
6
+ class GcTest < Test::Unit::TestCase
7
+ def setup
8
+ # Need to use wall time for this test due to the sleep calls
9
+ RubyProf::measure_mode = RubyProf::WALL_TIME
10
+ end
11
+
12
+ def run_profile
13
+ RubyProf.profile do
14
+ RubyProf::C1.hello
15
+ end
16
+ end
17
+
18
+ def test_gc
19
+ result = run_profile
20
+
21
+ thread = result.threads.first
22
+ method = thread.methods.first
23
+ call_info = method.call_infos.first
24
+
25
+ result = nil
26
+
27
+ 1000.times do
28
+ GC.start
29
+ Array.new(1000)
30
+ end
31
+
32
+ puts thread.methods
33
+ puts method.full_name
34
+ end
35
+ end
@@ -27,7 +27,7 @@ class LineNumbersTest < Test::Unit::TestCase
27
27
  numbers.method2
28
28
  end
29
29
 
30
- methods = result.threads.values.first.sort.reverse
30
+ methods = result.threads.first.methods.sort.reverse
31
31
  assert_equal(3, methods.length)
32
32
 
33
33
  method = methods[0]
@@ -50,7 +50,7 @@ class LineNumbersTest < Test::Unit::TestCase
50
50
  numbers.method3
51
51
  end
52
52
 
53
- methods = result.threads.values.first.sort_by {|method| method.full_name}
53
+ methods = result.threads.first.methods.sort_by {|method| method.full_name}
54
54
  assert_equal(3, methods.length)
55
55
 
56
56
  # Methods:
@@ -31,7 +31,7 @@ class MeasureCpuTimeTest < Test::Unit::TestCase
31
31
  # <Class::RubyProf::C1>#hello
32
32
  # Kernel#sleep
33
33
 
34
- methods = result.threads.values.first.sort.reverse
34
+ methods = result.threads.first.methods.sort.reverse
35
35
  assert_equal(3, methods.length)
36
36
 
37
37
  # Check the names
@@ -66,7 +66,7 @@ class MeasureCpuTimeTest < Test::Unit::TestCase
66
66
  # C1#hello
67
67
  # Kernel#sleep
68
68
 
69
- methods = result.threads.values.first.sort.reverse
69
+ methods = result.threads.first.methods.sort.reverse
70
70
  assert_equal(6, methods.length)
71
71
  names = methods.map(&:full_name)
72
72
  assert_equal('MeasureCpuTimeTest#test_instance_methods', names[0])
@@ -113,7 +113,7 @@ class MeasureCpuTimeTest < Test::Unit::TestCase
113
113
  # M1#hello
114
114
  # Kernel#sleep
115
115
 
116
- methods = result.threads.values.first.sort.reverse
116
+ methods = result.threads.first.methods.sort.reverse
117
117
  assert_equal(3, methods.length)
118
118
 
119
119
  assert_equal('MeasureCpuTimeTest#test_module_methods', methods[0].full_name)
@@ -147,7 +147,7 @@ class MeasureCpuTimeTest < Test::Unit::TestCase
147
147
  # M1#hello
148
148
  # Kernel#sleep
149
149
 
150
- methods = result.threads.values.first.sort.reverse
150
+ methods = result.threads.first.methods.sort.reverse
151
151
  assert_equal(6, methods.length)
152
152
  names = methods.map(&:full_name)
153
153
  assert_equal('MeasureCpuTimeTest#test_module_instance_methods', names[0])
@@ -195,7 +195,7 @@ class MeasureCpuTimeTest < Test::Unit::TestCase
195
195
  c3.hello
196
196
  end
197
197
 
198
- methods = result.threads.values.first.sort.reverse
198
+ methods = result.threads.first.methods.sort.reverse
199
199
  assert_equal(2, methods.length)
200
200
 
201
201
  assert_equal('MeasureCpuTimeTest#test_singleton', methods[0].full_name)
@@ -28,7 +28,7 @@ class MeasureProcessTimeTest < Test::Unit::TestCase
28
28
  # <Class::RubyProf::C1>#hello
29
29
  # Kernel#sleep
30
30
 
31
- methods = result.threads.values.first.sort.reverse
31
+ methods = result.threads.first.methods.sort.reverse
32
32
  puts methods[0].total_time
33
33
 
34
34
  assert_equal(3, methods.length)
@@ -63,7 +63,7 @@ class MeasureProcessTimeTest < Test::Unit::TestCase
63
63
  # C1#hello
64
64
  # Kernel#sleep
65
65
 
66
- methods = result.threads.values.first.sort.reverse
66
+ methods = result.threads.first.methods.sort.reverse
67
67
  assert_equal(6, methods.length)
68
68
 
69
69
  # Check times
@@ -108,7 +108,7 @@ class MeasureProcessTimeTest < Test::Unit::TestCase
108
108
  # M1#hello
109
109
  # Kernel#sleep
110
110
 
111
- methods = result.threads.values.first.sort.reverse
111
+ methods = result.threads.first.methods.sort.reverse
112
112
  assert_equal(3, methods.length)
113
113
 
114
114
  # Check times
@@ -141,7 +141,7 @@ class MeasureProcessTimeTest < Test::Unit::TestCase
141
141
  # M1#hello
142
142
  # Kernel#sleep
143
143
 
144
- methods = result.threads.values.first.sort.reverse
144
+ methods = result.threads.first.methods.sort.reverse
145
145
  assert_equal(6, methods.length)
146
146
 
147
147
  # Check times
@@ -188,7 +188,7 @@ class MeasureProcessTimeTest < Test::Unit::TestCase
188
188
  c3.hello
189
189
  end
190
190
 
191
- methods = result.threads.values.first.sort.reverse
191
+ methods = result.threads.first.methods.sort.reverse
192
192
  assert_equal(2, methods.length)
193
193
 
194
194
  assert_equal("MeasureProcessTimeTest#test_singleton", methods[0].full_name)