ruby-prof 1.3.0 → 1.4.2

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.
@@ -4,6 +4,7 @@ require 'erb'
4
4
  require 'fileutils'
5
5
  require 'base64'
6
6
  require 'set'
7
+ require 'stringio'
7
8
 
8
9
  module RubyProf
9
10
  # Prints a HTML visualization of the call tree.
@@ -29,8 +29,9 @@ module RubyProf
29
29
 
30
30
  sum = 0
31
31
  methods.each do |method|
32
- self_percent = (method.self_time / total_time) * 100
33
- next if self_percent < min_percent
32
+ percent = (method.send(filter_by) / total_time) * 100
33
+ next if percent < min_percent
34
+ next if percent > max_percent
34
35
 
35
36
  sum += method.self_time
36
37
  #self_time_called = method.called > 0 ? method.self_time/method.called : 0
@@ -25,7 +25,7 @@ module RubyProf
25
25
  end
26
26
 
27
27
  def print_header(thread)
28
- @output << "Measure Mode: %s\n" % RubyProf.measure_mode_string
28
+ @output << "Measure Mode: %s\n" % @result.measure_mode_string
29
29
  @output << "Thread ID: #{thread.id}\n"
30
30
  @output << "Fiber ID: #{thread.fiber_id}\n"
31
31
  @output << "Total Time: #{thread.total_time}\n"
@@ -7,10 +7,14 @@ module RubyProf
7
7
  # :nodoc:
8
8
  def measure_mode_string
9
9
  case self.measure_mode
10
- when WALL_TIME then "wall_time"
11
- when PROCESS_TIME then "process_time"
12
- when ALLOCATIONS then "allocations"
13
- when MEMORY then "memory"
10
+ when WALL_TIME
11
+ "wall_time"
12
+ when PROCESS_TIME
13
+ "process_time"
14
+ when ALLOCATIONS
15
+ "allocations"
16
+ when MEMORY
17
+ "memory"
14
18
  end
15
19
  end
16
20
 
@@ -5,43 +5,37 @@ module Rack
5
5
  class RubyProf
6
6
  def initialize(app, options = {})
7
7
  @app = app
8
+ @options = options
9
+ @options[:min_percent] ||= 1
8
10
 
9
- options[:min_percent] ||= 1
11
+ @tmpdir = options[:path] || Dir.tmpdir
12
+ FileUtils.mkdir_p(@tmpdir)
10
13
 
11
- options[:path] ||= Dir.tmpdir
12
- FileUtils.mkdir_p(options[:path])
14
+ @printer_klasses = @options[:printers] || {::RubyProf::FlatPrinter => 'flat.txt',
15
+ ::RubyProf::GraphPrinter => 'graph.txt',
16
+ ::RubyProf::GraphHtmlPrinter => 'graph.html',
17
+ ::RubyProf::CallStackPrinter => 'call_stack.html'}
13
18
 
14
19
  @skip_paths = options[:skip_paths] || [%r{^/assets}, %r{\.(css|js|png|jpeg|jpg|gif)$}]
15
20
  @only_paths = options[:only_paths]
16
-
17
- @max_requests = options[:max_requests]
18
-
19
- @options = options
20
21
  end
21
22
 
22
23
  def call(env)
23
24
  request = Rack::Request.new(env)
24
25
 
25
26
  if should_profile?(request.path)
26
- profiler.resume
27
27
  begin
28
- result = @app.call(env)
29
- ensure
30
- profiler.pause
31
- end
28
+ result = nil
29
+ data = ::RubyProf::Profile.profile(profiling_options) do
30
+ result = @app.call(env)
31
+ end
32
32
 
33
- if profiler.max_requests_reached?
34
- prefix = if aggregate_requests?
35
- nil
36
- else
37
- request.path.gsub('/', '-')[1..-1]
38
- end
33
+ path = request.path.gsub('/', '-')
34
+ path.slice!(0)
39
35
 
40
- profiler.print!(prefix)
41
- delete_profiler!
36
+ print(data, path)
37
+ result
42
38
  end
43
-
44
- result
45
39
  else
46
40
  @app.call(env)
47
41
  end
@@ -49,126 +43,53 @@ module Rack
49
43
 
50
44
  private
51
45
 
52
- class RackProfiler
53
- def initialize(options)
54
- @options = options
55
-
56
- @profile = ::RubyProf::Profile.new(profiling_options)
57
- @profile.start
58
- @profile.pause
59
-
60
- @printer_klasses = options[:printers] || default_printers
61
-
62
- @tmpdir = options[:path]
63
-
64
- @max_requests = options[:max_requests] || 1
65
- @requests_count = 0
46
+ def should_profile?(path)
47
+ return false if paths_match?(path, @skip_paths)
66
48
 
67
- @printed = false
68
- # if running across multiple requests, we want to make sure that the
69
- # ongoing profile is not lost if the process shuts down before the
70
- # max request count is reached
71
- ObjectSpace.define_finalizer(self, proc { print! })
72
- end
49
+ @only_paths ? paths_match?(path, @only_paths) : true
50
+ end
73
51
 
74
- def resume
75
- @profile.resume
76
- end
52
+ def paths_match?(path, paths)
53
+ paths.any? { |skip_path| skip_path =~ path }
54
+ end
77
55
 
78
- def pause
79
- @profile.pause
80
- @requests_count += 1
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]
81
67
  end
82
-
83
- def max_requests_reached?
84
- @requests_count >= @max_requests
68
+ if @options[:merge_fibers]
69
+ options[:merge_fibers] = true
85
70
  end
71
+ options
72
+ end
86
73
 
87
- def print!(prefix = nil)
88
- return false if @printed || @requests_count == 0
89
-
90
- data = @profile.stop
91
-
92
- prefix ||= "multi-requests-#{@requests_count}"
93
-
94
- @printer_klasses.each do |printer_klass, base_name|
95
- printer = printer_klass.new(data)
96
-
97
- if base_name.respond_to?(:call)
98
- base_name = base_name.call
99
- end
74
+ def print(data, path)
75
+ @printer_klasses.each do |printer_klass, base_name|
76
+ printer = printer_klass.new(data)
100
77
 
101
- if printer_klass == ::RubyProf::MultiPrinter \
102
- || printer_klass == ::RubyProf::CallTreePrinter
103
- printer.print(@options.merge(:profile => "#{prefix}-#{base_name}"))
104
- else
105
- file_name = ::File.join(@tmpdir, "#{prefix}-#{base_name}")
106
- ::File.open(file_name, 'wb') do |file|
107
- printer.print(file, @options)
108
- end
109
- end
78
+ if base_name.respond_to?(:call)
79
+ base_name = base_name.call
110
80
  end
111
81
 
112
- @printed = true
113
- end
114
-
115
- private
116
-
117
- def profiling_options
118
- options = {}
119
- options[:measure_mode] = ::RubyProf.measure_mode
120
- options[:exclude_threads] =
121
- if @options[:ignore_existing_threads]
122
- Thread.list.select{|t| t != Thread.current}
123
- else
124
- ::RubyProf.exclude_threads
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)
125
90
  end
126
- if @options[:request_thread_only]
127
- options[:include_threads] = [Thread.current]
128
91
  end
129
- if @options[:merge_fibers]
130
- options[:merge_fibers] = true
131
- end
132
- options
133
- end
134
-
135
- def default_printers
136
- {::RubyProf::FlatPrinter => 'flat.txt',
137
- ::RubyProf::GraphPrinter => 'graph.txt',
138
- ::RubyProf::GraphHtmlPrinter => 'graph.html',
139
- ::RubyProf::CallStackPrinter => 'call_stack.html'}
140
92
  end
141
93
  end
142
-
143
- def profiler
144
- if aggregate_requests?
145
- @@_shared_profiler ||= RackProfiler.new(@options)
146
- else
147
- @_profiler ||= RackProfiler.new(@options)
148
- end
149
- end
150
-
151
- def delete_profiler!
152
- if aggregate_requests?
153
- @@_shared_profiler.print! if @@_shared_profiler
154
- @@_shared_profiler = nil
155
- else
156
- @_profiler = nil
157
- end
158
- end
159
-
160
- def aggregate_requests?
161
- !@max_requests.nil?
162
- end
163
-
164
- def should_profile?(path)
165
- return false if paths_match?(path, @skip_paths)
166
-
167
- @only_paths ? paths_match?(path, @only_paths) : true
168
- end
169
-
170
- def paths_match?(path, paths)
171
- paths.any? { |skip_path| skip_path =~ path }
172
- end
173
94
  end
174
95
  end
@@ -1,3 +1,3 @@
1
1
  module RubyProf
2
- VERSION = "1.3.0"
2
+ VERSION = "1.4.2"
3
3
  end
@@ -8,254 +8,122 @@ require 'set'
8
8
 
9
9
  # -- Tests ----
10
10
  class FiberTest < TestCase
11
+
11
12
  def enumerator_with_fibers
12
- @fiber_ids << Fiber.current.object_id
13
13
  enum = Enumerator.new do |yielder|
14
14
  [1,2].each do |x|
15
- @fiber_ids << Fiber.current.object_id
16
- sleep 0.1
17
15
  yielder.yield x
18
16
  end
19
17
  end
20
- while true
21
- begin
22
- enum.next
23
- rescue StopIteration
24
- break
25
- end
26
- end
27
- sleep 0.1
18
+
19
+ enum.next
20
+ enum.next
21
+ end
22
+
23
+ def fiber_yield_resume
24
+ fiber = Fiber.new do
25
+ Fiber.yield 1
26
+ Fiber.yield 2
27
+ end
28
+
29
+ fiber.resume
30
+ fiber.resume
28
31
  end
29
32
 
30
33
  def setup
31
34
  # Need to use wall time for this test due to the sleep calls
32
35
  RubyProf::measure_mode = RubyProf::WALL_TIME
33
- @fiber_ids = Set.new
34
- @root_fiber = Fiber.current.object_id
35
- @thread_id = Thread.current.object_id
36
36
  end
37
37
 
38
38
  def test_fibers
39
39
  result = RubyProf.profile { enumerator_with_fibers }
40
40
 
41
- profiled_fiber_ids = result.threads.map(&:fiber_id)
42
- assert_equal(2, result.threads.length)
43
- assert_equal([@thread_id], result.threads.map(&:id).uniq)
44
- assert_equal(@fiber_ids, Set.new(profiled_fiber_ids))
41
+ assert_equal(2, result.threads.size)
45
42
 
46
- assert profiled_fiber_ids.include?(@root_fiber)
47
- assert(root_fiber_profile = result.threads.detect{|t| t.fiber_id == @root_fiber})
48
- assert(enum_fiber_profile = result.threads.detect{|t| t.fiber_id != @root_fiber})
49
- assert_in_delta(0.33, root_fiber_profile.total_time, 0.05)
50
- assert_in_delta(0.33, enum_fiber_profile.total_time, 0.05)
51
-
52
- methods = result.threads[0].methods.sort.reverse
53
- assert_equal(12, methods.count)
43
+ thread1 = result.threads[0]
44
+ methods = thread1.methods.sort.reverse
45
+ assert_equal(5, methods.count)
54
46
 
55
47
  method = methods[0]
56
48
  assert_equal('FiberTest#test_fibers', method.full_name)
57
49
  assert_equal(1, method.called)
58
- assert_in_delta(0.33, method.total_time, 0.05)
59
- assert_in_delta(0, method.self_time, 0.05)
60
- assert_in_delta(0, method.wait_time, 0.05)
61
- assert_in_delta(0.33, method.children_time, 0.05)
62
50
 
63
51
  method = methods[1]
64
52
  assert_equal('FiberTest#enumerator_with_fibers', method.full_name)
65
53
  assert_equal(1, method.called)
66
- assert_in_delta(0.33, method.total_time, 0.05)
67
- assert_in_delta(0, method.self_time, 0.05)
68
- assert_in_delta(0, method.wait_time, 0.05)
69
- assert_in_delta(0.33, method.children_time, 0.05)
70
54
 
71
55
  method = methods[2]
72
56
  assert_equal('Enumerator#next', method.full_name)
73
- assert_equal(3, method.called)
74
- assert_in_delta(0.22, method.total_time, 0.05)
75
- assert_in_delta(0, method.self_time, 0.05)
76
- assert_in_delta(0.22, method.wait_time, 0.05)
77
- assert_in_delta(0, method.children_time, 0.05)
57
+ assert_equal(2, method.called)
78
58
 
79
59
  method = methods[3]
80
- assert_equal('Kernel#sleep', method.full_name)
81
- assert_equal(1, method.called)
82
- assert_in_delta(0.11, method.total_time, 0.05)
83
- assert_in_delta(0.11, method.self_time, 0.05)
84
- assert_in_delta(0, method.wait_time, 0.05)
85
- assert_in_delta(0, method.children_time, 0.05)
86
-
87
- # Since these methods have such short times their order is a bit indeterminate
88
- method = methods.detect {|a_method| a_method.full_name == 'Class#new'}
89
60
  assert_equal('Class#new', method.full_name)
90
61
  assert_equal(1, method.called)
91
- assert_in_delta(0, method.total_time, 0.05)
92
- assert_in_delta(0, method.self_time, 0.05)
93
- assert_in_delta(0, method.wait_time, 0.05)
94
- assert_in_delta(0, method.children_time, 0.05)
95
-
96
- if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.5.0')
97
- method = methods.detect {|a_method| a_method.full_name == 'Set#<<'}
98
- assert_equal('Set#<<', method.full_name)
99
- assert_equal(1, method.called)
100
- assert_in_delta(0, method.total_time, 0.05)
101
- assert_in_delta(0, method.self_time, 0.05)
102
- assert_in_delta(0, method.wait_time, 0.05)
103
- assert_in_delta(0, method.children_time, 0.05)
104
- end
105
62
 
106
- method = methods.detect {|a_method| a_method.full_name == 'Module#==='}
107
- assert_equal('Module#===', method.full_name)
63
+ method = methods[4]
64
+ assert_equal('Enumerator#initialize', method.full_name)
108
65
  assert_equal(1, method.called)
109
- assert_in_delta(0, method.total_time, 0.05)
110
- assert_in_delta(0, method.self_time, 0.05)
111
- assert_in_delta(0, method.wait_time, 0.05)
112
- assert_in_delta(0, method.children_time, 0.05)
113
66
 
114
- method = methods.detect {|a_method| a_method.full_name == 'Kernel#object_id'}
115
- assert_equal('Kernel#object_id', method.full_name)
116
- assert_equal(1, method.called)
117
- assert_in_delta(0, method.total_time, 0.05)
118
- assert_in_delta(0, method.self_time, 0.05)
119
- assert_in_delta(0, method.wait_time, 0.05)
120
- assert_in_delta(0, method.children_time, 0.05)
67
+ thread2 = result.threads[1]
68
+ methods = thread2.methods.sort.reverse
69
+ assert_equal(4, methods.count)
121
70
 
122
- method = methods.detect {|a_method| a_method.full_name == '<Class::Fiber>#current'}
123
- assert_equal('<Class::Fiber>#current', method.full_name)
71
+ method = methods[0]
72
+ assert_equal('Enumerator#each', method.full_name)
124
73
  assert_equal(1, method.called)
125
- assert_in_delta(0, method.total_time, 0.05)
126
- assert_in_delta(0, method.self_time, 0.05)
127
- assert_in_delta(0, method.wait_time, 0.05)
128
- assert_in_delta(0, method.children_time, 0.05)
129
74
 
130
- method = methods.detect {|a_method| a_method.full_name == 'Exception#exception'}
131
- assert_equal('Exception#exception', method.full_name)
75
+ method = methods[1]
76
+ assert_equal('Enumerator::Generator#each', method.full_name)
132
77
  assert_equal(1, method.called)
133
- assert_in_delta(0, method.total_time, 0.05)
134
- assert_in_delta(0, method.self_time, 0.05)
135
- assert_in_delta(0, method.wait_time, 0.05)
136
- assert_in_delta(0, method.children_time, 0.05)
137
78
 
138
- method = methods.detect {|a_method| a_method.full_name == 'Exception#backtrace'}
139
- assert_equal('Exception#backtrace', method.full_name)
79
+ method = methods[2]
80
+ assert_equal('Array#each', method.full_name)
140
81
  assert_equal(1, method.called)
141
- assert_in_delta(0, method.total_time, 0.05)
142
- assert_in_delta(0, method.self_time, 0.05)
143
- assert_in_delta(0, method.wait_time, 0.05)
144
- assert_in_delta(0, method.children_time, 0.05)
145
82
 
146
- method = methods.detect {|a_method| a_method.full_name == 'Enumerator#initialize'}
147
- assert_equal('Enumerator#initialize', method.full_name)
148
- assert_equal(1, method.called)
149
- assert_in_delta(0, method.total_time, 0.05)
150
- assert_in_delta(0, method.self_time, 0.05)
151
- assert_in_delta(0, method.wait_time, 0.05)
152
- assert_in_delta(0, method.children_time, 0.05)
83
+ method = methods[3]
84
+ assert_equal('Enumerator::Yielder#yield', method.full_name)
85
+ assert_equal(2, method.called)
86
+ end
153
87
 
154
- methods = result.threads[1].methods.sort.reverse
88
+ def test_fiber_resume
89
+ result = RubyProf.profile { fiber_yield_resume }
155
90
 
156
- if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.6.0')
157
- assert_equal(10, methods.count)
158
- else
159
- assert_equal(11, methods.count)
160
- end
91
+ assert_equal(2, result.threads.size)
92
+
93
+ thread1 = result.threads[0]
94
+ methods = thread1.methods.sort.reverse
95
+ assert_equal(5, methods.count)
161
96
 
162
97
  method = methods[0]
163
- assert_equal('RubyProf::Profile#_inserted_parent_', method.full_name)
98
+ assert_equal('FiberTest#test_fiber_resume', method.full_name)
164
99
  assert_equal(1, method.called)
165
- assert_in_delta(0.33, method.total_time, 0.05)
166
- assert_in_delta(0, method.self_time, 0.05)
167
- assert_in_delta(0.11, method.wait_time, 0.05)
168
- assert_in_delta(0.22, method.children_time, 0.05)
169
100
 
170
101
  method = methods[1]
171
- assert_equal('Enumerator#each', method.full_name)
102
+ assert_equal('FiberTest#fiber_yield_resume', method.full_name)
172
103
  assert_equal(1, method.called)
173
- assert_in_delta(0.22, method.total_time, 0.05)
174
- assert_in_delta(0, method.self_time, 0.05)
175
- assert_in_delta(0, method.wait_time, 0.05)
176
- assert_in_delta(0.22, method.children_time, 0.05)
177
104
 
178
105
  method = methods[2]
179
- assert_equal('Enumerator::Generator#each', method.full_name)
180
- assert_equal(1, method.called)
181
- assert_in_delta(0.22, method.total_time, 0.05)
182
- assert_in_delta(0, method.self_time, 0.05)
183
- assert_in_delta(0, method.wait_time, 0.05)
184
- assert_in_delta(0.22, method.children_time, 0.05)
106
+ assert_equal('Fiber#resume', method.full_name)
107
+ assert_equal(2, method.called)
185
108
 
186
109
  method = methods[3]
187
- assert_equal('Array#each', method.full_name)
110
+ assert_equal('Class#new', method.full_name)
188
111
  assert_equal(1, method.called)
189
- assert_in_delta(0.22, method.total_time, 0.05)
190
- assert_in_delta(0, method.self_time, 0.05)
191
- assert_in_delta(0, method.wait_time, 0.05)
192
- assert_in_delta(0.22, method.children_time, 0.05)
193
112
 
194
113
  method = methods[4]
195
- assert_equal('Kernel#sleep', method.full_name)
196
- assert_equal(2, method.called)
197
- assert_in_delta(0.22, method.total_time, 0.05)
198
- assert_in_delta(0.22, method.self_time, 0.05)
199
- assert_in_delta(0, method.wait_time, 0.05)
200
- assert_in_delta(0, method.children_time, 0.05)
201
-
202
- # Since these methods have such short times their order is a bit indeterminate
203
- method = methods.detect {|a_method| a_method.full_name == 'Exception#initialize'}
204
- assert_equal('Exception#initialize', method.full_name)
114
+ assert_equal('Fiber#initialize', method.full_name)
205
115
  assert_equal(1, method.called)
206
- assert_in_delta(0, method.total_time, 0.05)
207
- assert_in_delta(0, method.self_time, 0.05)
208
- assert_in_delta(0, method.wait_time, 0.05)
209
- assert_in_delta(0, method.children_time, 0.05)
210
-
211
- if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.5.0')
212
- method = methods.detect {|a_method| a_method.full_name == 'Set#<<'}
213
- assert_equal('Set#<<', method.full_name)
214
- assert_equal(2, method.called)
215
- assert_in_delta(0, method.total_time, 0.05)
216
- assert_in_delta(0, method.self_time, 0.05)
217
- assert_in_delta(0, method.wait_time, 0.05)
218
- assert_in_delta(0, method.children_time, 0.05)
219
- end
220
116
 
221
- method = methods.detect {|a_method| a_method.full_name == 'Kernel#object_id'}
222
- assert_equal('Kernel#object_id', method.full_name)
223
- assert_equal(2, method.called)
224
- assert_in_delta(0, method.total_time, 0.05)
225
- assert_in_delta(0, method.self_time, 0.05)
226
- assert_in_delta(0, method.wait_time, 0.05)
227
- assert_in_delta(0, method.children_time, 0.05)
117
+ thread1 = result.threads[1]
118
+ methods = thread1.methods.sort.reverse
119
+ assert_equal(2, methods.count)
228
120
 
229
- method = methods.detect {|a_method| a_method.full_name == 'Enumerator::Yielder#yield'}
230
- assert_equal('Enumerator::Yielder#yield', method.full_name)
231
- assert_equal(2, method.called)
232
- assert_in_delta(0, method.total_time, 0.05)
233
- assert_in_delta(0, method.self_time, 0.05)
234
- assert_in_delta(0, method.wait_time, 0.05)
235
- assert_in_delta(0, method.children_time, 0.05)
121
+ method = methods[0]
122
+ assert_equal('FiberTest#fiber_yield_resume', method.full_name)
123
+ assert_equal(1, method.called)
236
124
 
237
- method = methods.detect {|a_method| a_method.full_name == '<Class::Fiber>#current'}
238
- assert_equal('<Class::Fiber>#current', method.full_name)
125
+ method = methods[1]
126
+ assert_equal('<Class::Fiber>#yield', method.full_name)
239
127
  assert_equal(2, method.called)
240
- assert_in_delta(0, method.total_time, 0.05)
241
- assert_in_delta(0, method.self_time, 0.05)
242
- assert_in_delta(0, method.wait_time, 0.05)
243
- assert_in_delta(0, method.children_time, 0.05)
244
- end
245
-
246
- def test_merged_fibers
247
- result = RubyProf.profile(merge_fibers: true) { enumerator_with_fibers }
248
-
249
- assert_equal(1, result.threads.length)
250
-
251
- thread = result.threads.first
252
- assert_equal(thread.id, thread.fiber_id)
253
- assert_in_delta(0.3, thread.total_time, 0.05)
254
-
255
- assert(method_next = thread.methods.detect{|m| m.full_name == "Enumerator#next"})
256
- assert(method_each = thread.methods.detect{|m| m.full_name == "Enumerator#each"})
257
-
258
- assert_in_delta(0.2, method_next.total_time, 0.05)
259
- assert_in_delta(0.2, method_each.total_time, 0.05)
260
128
  end
261
129
  end