ruby-prof 0.15.9 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
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