ruby-prof 0.15.9 → 0.16.0

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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +27 -1
  3. data/README.rdoc +83 -31
  4. data/bin/ruby-prof +4 -4
  5. data/doc/LICENSE.html +1 -1
  6. data/doc/README_rdoc.html +92 -33
  7. data/doc/Rack.html +1 -1
  8. data/doc/Rack/RubyProf.html +17 -14
  9. data/doc/RubyProf.html +30 -29
  10. data/doc/RubyProf/AbstractPrinter.html +1 -1
  11. data/doc/RubyProf/AggregateCallInfo.html +1 -1
  12. data/doc/RubyProf/CallInfo.html +1 -1
  13. data/doc/RubyProf/CallInfoPrinter.html +1 -1
  14. data/doc/RubyProf/CallInfoVisitor.html +1 -1
  15. data/doc/RubyProf/CallStackPrinter.html +1 -1
  16. data/doc/RubyProf/CallTreePrinter.html +349 -67
  17. data/doc/RubyProf/Cmd.html +5 -5
  18. data/doc/RubyProf/DotPrinter.html +2 -2
  19. data/doc/RubyProf/FlatPrinter.html +1 -1
  20. data/doc/RubyProf/FlatPrinterWithLineNumbers.html +1 -1
  21. data/doc/RubyProf/GraphHtmlPrinter.html +1 -1
  22. data/doc/RubyProf/GraphPrinter.html +1 -1
  23. data/doc/RubyProf/MethodInfo.html +2 -2
  24. data/doc/RubyProf/MultiPrinter.html +11 -9
  25. data/doc/RubyProf/Profile.html +94 -44
  26. data/doc/RubyProf/ProfileTask.html +1 -1
  27. data/doc/RubyProf/Thread.html +43 -1
  28. data/doc/created.rid +16 -16
  29. data/doc/examples/flat_txt.html +1 -1
  30. data/doc/examples/graph_html.html +1 -1
  31. data/doc/examples/graph_txt.html +3 -3
  32. data/doc/index.html +85 -30
  33. data/doc/js/navigation.js.gz +0 -0
  34. data/doc/js/search_index.js +1 -1
  35. data/doc/js/search_index.js.gz +0 -0
  36. data/doc/js/searcher.js +2 -2
  37. data/doc/js/searcher.js.gz +0 -0
  38. data/doc/table_of_contents.html +117 -68
  39. data/examples/cachegrind.out.1 +114 -0
  40. data/examples/cachegrind.out.1.32313213 +114 -0
  41. data/examples/graph.txt +1 -1
  42. data/ext/ruby_prof/extconf.rb +6 -2
  43. data/ext/ruby_prof/rp_measure_cpu_time.c +29 -31
  44. data/ext/ruby_prof/rp_method.c +1 -1
  45. data/ext/ruby_prof/rp_thread.c +57 -52
  46. data/ext/ruby_prof/ruby_prof.c +122 -66
  47. data/ext/ruby_prof/ruby_prof.h +2 -0
  48. data/lib/ruby-prof.rb +14 -13
  49. data/lib/ruby-prof/assets/call_stack_printer.js.html +1 -1
  50. data/lib/ruby-prof/compatibility.rb +9 -8
  51. data/lib/ruby-prof/method_info.rb +1 -1
  52. data/lib/ruby-prof/printers/call_tree_printer.rb +88 -50
  53. data/lib/ruby-prof/printers/dot_printer.rb +1 -1
  54. data/lib/ruby-prof/printers/multi_printer.rb +6 -4
  55. data/lib/ruby-prof/profile.rb +0 -1
  56. data/lib/ruby-prof/rack.rb +53 -16
  57. data/lib/ruby-prof/thread.rb +11 -0
  58. data/lib/ruby-prof/version.rb +1 -1
  59. data/test/exclude_threads_test.rb +2 -3
  60. data/test/fiber_test.rb +21 -7
  61. data/test/measure_cpu_time_test.rb +84 -24
  62. data/test/multi_printer_test.rb +5 -4
  63. data/test/pause_resume_test.rb +7 -7
  64. data/test/printers_test.rb +6 -4
  65. data/test/rack_test.rb +26 -1
  66. data/test/test_helper.rb +28 -3
  67. data/test/thread_test.rb +1 -0
  68. metadata +5 -3
@@ -22,7 +22,7 @@ module RubyProf
22
22
  CLASS_COLOR = '"#666666"'
23
23
  EDGE_COLOR = '"#666666"'
24
24
 
25
- # Creates the DotPrinter using a RubyProf::Result.
25
+ # Creates the DotPrinter using a RubyProf::Proile.
26
26
  def initialize(result)
27
27
  super(result)
28
28
  @seen_methods = Set.new
@@ -18,15 +18,17 @@ module RubyProf
18
18
  def print(options)
19
19
  @profile = options.delete(:profile) || "profile"
20
20
  @directory = options.delete(:path) || File.expand_path(".")
21
+
21
22
  File.open(stack_profile, "w") do |f|
22
23
  @stack_printer.print(f, options.merge(:graph => "#{@profile}.graph.html"))
23
24
  end
25
+
24
26
  File.open(graph_profile, "w") do |f|
25
27
  @graph_printer.print(f, options)
26
28
  end
27
- File.open(tree_profile, "w") do |f|
28
- @tree_printer.print(f, options)
29
- end
29
+
30
+ @tree_printer.print(options.merge(:path => @directory, :profile => @profile))
31
+
30
32
  File.open(flat_profile, "w") do |f|
31
33
  @flat_printer.print(f, options)
32
34
  end
@@ -44,7 +46,7 @@ module RubyProf
44
46
 
45
47
  # the name of the callgrind profile file
46
48
  def tree_profile
47
- "#{@directory}/#{@profile}.grind.dat"
49
+ "#{@directory}/#{@profile}.callgrind.out.#{$$}"
48
50
  end
49
51
 
50
52
  # the name of the flat profile file
@@ -1,6 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
- require 'set'
4
3
  module RubyProf
5
4
  class Profile
6
5
  # This method gets called once profiling has been completed
@@ -16,30 +16,61 @@ module Rack
16
16
  ::RubyProf::GraphHtmlPrinter => 'graph.html',
17
17
  ::RubyProf::CallStackPrinter => 'call_stack.html'}
18
18
 
19
- @skip_paths = options[:skip_paths] || [%r{^/assets}, %r{\.css$}, %r{\.js$}, %r{\.png$}, %r{\.jpeg$}, %r{\.jpg$}, %r{\.gif$}]
19
+ @skip_paths = options[:skip_paths] || [%r{^/assets}, %r{\.(css|js|png|jpeg|jpg|gif)$}]
20
+ @only_paths = options[:only_paths]
20
21
  end
21
22
 
22
23
  def call(env)
23
24
  request = Rack::Request.new(env)
24
25
 
25
- if @skip_paths.any? {|skip_path| skip_path =~ request.path}
26
- @app.call(env)
27
- else
28
- result = nil
29
- data = ::RubyProf::Profile.profile do
30
- result = @app.call(env)
31
- end
26
+ if should_profile?(request.path)
27
+ begin
28
+ result = nil
29
+ data = ::RubyProf::Profile.profile(profiling_options) do
30
+ result = @app.call(env)
31
+ end
32
32
 
33
- path = request.path.gsub('/', '-')
34
- path.slice!(0)
33
+ path = request.path.gsub('/', '-')
34
+ path.slice!(0)
35
35
 
36
- print(data, path)
37
- result
36
+ print(data, path)
37
+ result
38
+ end
39
+ else
40
+ @app.call(env)
38
41
  end
39
42
  end
40
43
 
41
44
  private
42
45
 
46
+ def should_profile?(path)
47
+ return false if paths_match?(path, @skip_paths)
48
+
49
+ @only_paths ? paths_match?(path, @only_paths) : true
50
+ end
51
+
52
+ def paths_match?(path, paths)
53
+ paths.any? { |skip_path| skip_path =~ path }
54
+ end
55
+
56
+ def profiling_options
57
+ options = {}
58
+ options[:measure_mode] = ::RubyProf.measure_mode
59
+ options[:exclude_threads] =
60
+ if @options[:ignore_existing_threads]
61
+ Thread.list.select{|t| t != Thread.current}
62
+ else
63
+ ::RubyProf.exclude_threads
64
+ end
65
+ if @options[:request_thread_only]
66
+ options[:include_threads] = [Thread.current]
67
+ end
68
+ if @options[:merge_fibers]
69
+ options[:merge_fibers] = true
70
+ end
71
+ options
72
+ end
73
+
43
74
  def print(data, path)
44
75
  @printer_klasses.each do |printer_klass, base_name|
45
76
  printer = printer_klass.new(data)
@@ -48,11 +79,17 @@ module Rack
48
79
  base_name = base_name.call
49
80
  end
50
81
 
51
- file_name = ::File.join(@tmpdir, "#{path}-#{base_name}")
52
- ::File.open(file_name, 'wb') do |file|
53
- printer.print(file, @options)
82
+ if printer_klass == ::RubyProf::MultiPrinter
83
+ printer.print(@options.merge(:profile => "#{path}-#{base_name}"))
84
+ elsif printer_klass == ::RubyProf::CallTreePrinter
85
+ printer.print(@options.merge(:profile => "#{path}-#{base_name}"))
86
+ else
87
+ file_name = ::File.join(@tmpdir, "#{path}-#{base_name}")
88
+ ::File.open(file_name, 'wb') do |file|
89
+ printer.print(file, @options)
90
+ end
54
91
  end
55
92
  end
56
93
  end
57
94
  end
58
- end
95
+ end
@@ -26,5 +26,16 @@ module RubyProf
26
26
  sum
27
27
  end
28
28
  end
29
+
30
+ def wait_time
31
+ # wait_time, like self:time, is always method local
32
+ # thus we need to sum over all methods and call infos
33
+ self.methods.inject(0) do |sum, method_info|
34
+ method_info.call_infos.each do |call_info|
35
+ sum += call_info.wait_time
36
+ end
37
+ sum
38
+ end
39
+ end
29
40
  end
30
41
  end
@@ -1,3 +1,3 @@
1
1
  module RubyProf
2
- VERSION = "0.15.9"
2
+ VERSION = "0.16.0"
3
3
  end
@@ -26,7 +26,8 @@ class ExcludeThreadsTest < TestCase
26
26
  thread2_proc
27
27
  end
28
28
 
29
- RubyProf::exclude_threads = [ thread2 ]
29
+ # exclude_threads already includes the minitest thread pool
30
+ RubyProf.exclude_threads += [ thread2 ]
30
31
 
31
32
  RubyProf.start
32
33
 
@@ -35,8 +36,6 @@ class ExcludeThreadsTest < TestCase
35
36
 
36
37
  result = RubyProf.stop
37
38
 
38
- RubyProf::exclude_threads = nil
39
-
40
39
  assert_equal(2, result.threads.length)
41
40
 
42
41
  output = Array.new
@@ -37,18 +37,18 @@ class FiberTest < TestCase
37
37
  @fiber_ids = Set.new
38
38
  @root_fiber = Fiber.current.object_id
39
39
  @thread_id = Thread.current.object_id
40
- @result = RubyProf.profile { fiber_test }
41
40
  end
42
41
 
43
42
  def test_fibers
44
- profiled_fiber_ids = @result.threads.map(&:fiber_id)
45
- assert_equal(2, @result.threads.length)
46
- assert_equal([@thread_id], @result.threads.map(&:id).uniq)
43
+ result = RubyProf.profile { fiber_test }
44
+ profiled_fiber_ids = result.threads.map(&:fiber_id)
45
+ assert_equal(2, result.threads.length)
46
+ assert_equal([@thread_id], result.threads.map(&:id).uniq)
47
47
  assert_equal(@fiber_ids, Set.new(profiled_fiber_ids))
48
48
 
49
49
  assert profiled_fiber_ids.include?(@root_fiber)
50
- assert(root_fiber_profile = @result.threads.detect{|t| t.fiber_id == @root_fiber})
51
- assert(enum_fiber_profile = @result.threads.detect{|t| t.fiber_id != @root_fiber})
50
+ assert(root_fiber_profile = result.threads.detect{|t| t.fiber_id == @root_fiber})
51
+ assert(enum_fiber_profile = result.threads.detect{|t| t.fiber_id != @root_fiber})
52
52
 
53
53
  assert_in_delta(0.3, root_fiber_profile.total_time, 0.05)
54
54
  assert_in_delta(0.2, enum_fiber_profile.total_time, 0.05)
@@ -58,8 +58,22 @@ class FiberTest < TestCase
58
58
 
59
59
  assert_in_delta(0.2, method_next.total_time, 0.05)
60
60
  assert_in_delta(0.2, method_each.total_time, 0.05)
61
+ end
62
+
63
+ def test_merged_fibers
64
+ result = RubyProf.profile(merge_fibers: true) { fiber_test }
65
+ assert_equal(1, result.threads.length)
66
+
67
+ profile = result.threads.first
68
+ assert_equal 0, profile.fiber_id
69
+
70
+ assert_in_delta(0.3, profile.total_time, 0.05)
61
71
 
62
- # RubyProf::CallInfoPrinter.new(@result).print
72
+ assert(method_next = profile.methods.detect{|m| m.full_name == "Enumerator#next"})
73
+ assert(method_each = profile.methods.detect{|m| m.full_name == "Enumerator#each"})
74
+
75
+ assert_in_delta(0.2, method_next.total_time, 0.05)
76
+ assert_in_delta(0.2, method_each.total_time, 0.05)
63
77
  end
64
78
 
65
79
  end
@@ -8,8 +8,11 @@ class MeasureCpuTimeTest < TestCase
8
8
  RubyProf::measure_mode = RubyProf::CPU_TIME
9
9
  end
10
10
 
11
+ def teardown
12
+ RubyProf::measure_mode = RubyProf::WALL_TIME
13
+ end
14
+
11
15
  def test_mode
12
- RubyProf::measure_mode = RubyProf::CPU_TIME
13
16
  assert_equal(RubyProf::CPU_TIME, RubyProf::measure_mode)
14
17
  end
15
18
 
@@ -19,12 +22,12 @@ class MeasureCpuTimeTest < TestCase
19
22
 
20
23
  def test_class_methods
21
24
  result = RubyProf.profile do
22
- RubyProf::C7.hello
25
+ RubyProf::C7.busy_wait
23
26
  end
24
27
 
25
28
  # Length should be greater 2:
26
29
  # MeasureCpuTimeTest#test_class_methods
27
- # <Class::RubyProf::C1>#hello
30
+ # <Class::RubyProf::C1>#busy_wait
28
31
  # ....
29
32
 
30
33
  methods = result.threads.first.methods.sort.reverse[0..1]
@@ -32,21 +35,21 @@ class MeasureCpuTimeTest < TestCase
32
35
 
33
36
  # Check the names
34
37
  assert_equal('MeasureCpuTimeTest#test_class_methods', methods[0].full_name)
35
- assert_equal('<Class::RubyProf::C7>#hello', methods[1].full_name)
38
+ assert_equal('<Class::RubyProf::C7>#busy_wait', methods[1].full_name)
36
39
 
37
40
  # Check times
38
- assert_in_delta(0.1, methods[0].total_time, 0.03)
39
- assert_in_delta(0, methods[0].wait_time, 0.03)
40
- assert_in_delta(0, methods[0].self_time, 0.03)
41
+ assert_in_delta(0.1, methods[0].total_time, 0.05)
42
+ assert_in_delta(0, methods[0].wait_time, 0.05)
43
+ assert_in_delta(0, methods[0].self_time, 0.05)
41
44
 
42
- assert_in_delta(0.1, methods[1].total_time, 0.03)
43
- assert_in_delta(0, methods[1].wait_time, 0.03)
44
- assert_in_delta(0, methods[1].self_time, 0.03)
45
+ assert_in_delta(0.1, methods[1].total_time, 0.05)
46
+ assert_in_delta(0, methods[1].wait_time, 0.05)
47
+ assert_in_delta(0, methods[1].self_time, 0.05)
45
48
  end
46
49
 
47
50
  def test_instance_methods
48
51
  result = RubyProf.profile do
49
- RubyProf::C7.new.hello
52
+ RubyProf::C7.new.busy_wait
50
53
  end
51
54
 
52
55
  methods = result.threads.first.methods.sort.reverse[0..1]
@@ -54,12 +57,12 @@ class MeasureCpuTimeTest < TestCase
54
57
 
55
58
  # Methods at this point:
56
59
  # MeasureCpuTimeTest#test_instance_methods
57
- # C7#hello
60
+ # C7#busy_wait
58
61
  # ...
59
62
 
60
63
  names = methods.map(&:full_name)
61
64
  assert_equal('MeasureCpuTimeTest#test_instance_methods', names[0])
62
- assert_equal('RubyProf::C7#hello', names[1])
65
+ assert_equal('RubyProf::C7#busy_wait', names[1])
63
66
 
64
67
 
65
68
  # Check times
@@ -69,24 +72,24 @@ class MeasureCpuTimeTest < TestCase
69
72
 
70
73
  assert_in_delta(0.2, methods[1].total_time, 0.03)
71
74
  assert_in_delta(0, methods[1].wait_time, 0.03)
72
- assert_in_delta(0, methods[1].self_time, 0.1)
75
+ assert_in_delta(0, methods[1].self_time, 0.2)
73
76
  end
74
77
 
75
78
  def test_module_methods
76
79
  result = RubyProf.profile do
77
- RubyProf::C8.hello
80
+ RubyProf::C8.busy_wait
78
81
  end
79
82
 
80
83
  # Methods:
81
84
  # MeasureCpuTimeTest#test_module_methods
82
- # M1#hello
85
+ # M1#busy_wait
83
86
  # ...
84
87
 
85
88
  methods = result.threads.first.methods.sort.reverse[0..1]
86
89
  assert_equal(2, methods.length)
87
90
 
88
91
  assert_equal('MeasureCpuTimeTest#test_module_methods', methods[0].full_name)
89
- assert_equal('RubyProf::M7#hello', methods[1].full_name)
92
+ assert_equal('RubyProf::M7#busy_wait', methods[1].full_name)
90
93
 
91
94
  # Check times
92
95
  assert_in_delta(0.3, methods[0].total_time, 0.1)
@@ -100,26 +103,26 @@ class MeasureCpuTimeTest < TestCase
100
103
 
101
104
  def test_module_instance_methods
102
105
  result = RubyProf.profile do
103
- RubyProf::C8.new.hello
106
+ RubyProf::C8.new.busy_wait
104
107
  end
105
108
 
106
109
  # Methods:
107
110
  # MeasureCpuTimeTest#test_module_instance_methods
108
- # M7#hello
111
+ # M7#busy_wait
109
112
  # ...
110
113
 
111
114
  methods = result.threads.first.methods.sort.reverse[0..1]
112
115
  assert_equal(2, methods.length)
113
116
  names = methods.map(&:full_name)
114
117
  assert_equal('MeasureCpuTimeTest#test_module_instance_methods', names[0])
115
- assert_equal('RubyProf::M7#hello', names[1])
118
+ assert_equal('RubyProf::M7#busy_wait', names[1])
116
119
 
117
120
  # Check times
118
121
  assert_in_delta(0.3, methods[0].total_time, 0.1)
119
122
  assert_in_delta(0, methods[0].wait_time, 0.1)
120
123
  assert_in_delta(0, methods[0].self_time, 0.1)
121
124
 
122
- assert_in_delta(0.3, methods[1].total_time, 0.02)
125
+ assert_in_delta(0.3, methods[1].total_time, 0.1)
123
126
  assert_in_delta(0, methods[1].wait_time, 0.01)
124
127
  assert_in_delta(0, methods[1].self_time, 0.1)
125
128
  end
@@ -128,19 +131,19 @@ class MeasureCpuTimeTest < TestCase
128
131
  c3 = RubyProf::C3.new
129
132
 
130
133
  class << c3
131
- def hello
134
+ def busy_wait
132
135
  end
133
136
  end
134
137
 
135
138
  result = RubyProf.profile do
136
- c3.hello
139
+ c3.busy_wait
137
140
  end
138
141
 
139
142
  methods = result.threads.first.methods.sort.reverse
140
143
  assert_equal(2, methods.length)
141
144
 
142
145
  assert_equal('MeasureCpuTimeTest#test_singleton', methods[0].full_name)
143
- assert_equal('<Object::RubyProf::C3>#hello', methods[1].full_name)
146
+ assert_equal('<Object::RubyProf::C3>#busy_wait', methods[1].full_name)
144
147
 
145
148
  assert_in_delta(0, methods[0].total_time, 0.01)
146
149
  assert_in_delta(0, methods[0].wait_time, 0.01)
@@ -150,4 +153,61 @@ class MeasureCpuTimeTest < TestCase
150
153
  assert_in_delta(0, methods[1].wait_time, 0.01)
151
154
  assert_in_delta(0, methods[1].self_time, 0.01)
152
155
  end
156
+
157
+
158
+ def test_sleeping_does_accumulate_wall_time
159
+ RubyProf::measure_mode = RubyProf::WALL_TIME
160
+ result = RubyProf.profile do
161
+ sleep 0.1
162
+ end
163
+ methods = result.threads.first.methods.sort.reverse
164
+ assert_equal(["MeasureCpuTimeTest#test_sleeping_does_accumulate_wall_time", "Kernel#sleep"], methods.map(&:full_name))
165
+ assert_in_delta(0.1, methods[1].total_time, 0.01)
166
+ assert_equal(0, methods[1].wait_time)
167
+ assert_in_delta(0.1, methods[1].self_time, 0.01)
168
+ end
169
+
170
+ def test_sleeping_does_not_accumulate_significant_cpu_time
171
+ result = RubyProf.profile do
172
+ sleep 0.1
173
+ end
174
+ methods = result.threads.first.methods.sort.reverse
175
+ assert_equal(["MeasureCpuTimeTest#test_sleeping_does_not_accumulate_significant_cpu_time", "Kernel#sleep"], methods.map(&:full_name))
176
+ assert_in_delta(0, methods[1].total_time, 0.01)
177
+ assert_equal(0, methods[1].wait_time)
178
+ assert_in_delta(0, methods[1].self_time, 0.01)
179
+ end
180
+
181
+ def test_waiting_for_threads_does_not_accumulate_cpu_time
182
+ background_thread = nil
183
+ result = RubyProf.profile do
184
+ background_thread = Thread.new{ sleep 0.1 }
185
+ background_thread.join
186
+ end
187
+ # check number of threads
188
+ assert_equal(2, result.threads.length)
189
+ fg, bg = result.threads
190
+ assert(fg.methods.map(&:full_name).include?("Thread#join"))
191
+ assert(bg.methods.map(&:full_name).include?("Kernel#sleep"))
192
+ assert_in_delta(0, fg.total_time, 0.01)
193
+ assert_in_delta(0, bg.total_time, 0.01)
194
+ end
195
+
196
+ def test_waiting_for_threads_does_accumulate_wall_time
197
+ RubyProf::measure_mode = RubyProf::WALL_TIME
198
+ background_thread = nil
199
+ result = RubyProf.profile do
200
+ background_thread = Thread.new{ sleep 0.1 }
201
+ background_thread.join
202
+ end
203
+ # check number of threads
204
+ assert_equal(2, result.threads.length)
205
+ fg, bg = result.threads
206
+ assert(fg.methods.map(&:full_name).include?("Thread#join"))
207
+ assert(bg.methods.map(&:full_name).include?("Kernel#sleep"))
208
+ assert_in_delta(0.1, fg.total_time, 0.01)
209
+ assert_in_delta(0.1, fg.wait_time, 0.01)
210
+ assert_in_delta(0.1, bg.total_time, 0.01)
211
+ end
212
+
153
213
  end