ruby-prof 1.4.3 → 1.6.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.
- checksums.yaml +4 -4
- data/CHANGES +59 -9
- data/{README.rdoc → README.md} +2 -2
- data/Rakefile +4 -4
- data/bin/ruby-prof +100 -87
- data/ext/ruby_prof/rp_allocation.c +140 -85
- data/ext/ruby_prof/rp_allocation.h +8 -6
- data/ext/ruby_prof/rp_call_tree.c +502 -369
- data/ext/ruby_prof/rp_call_tree.h +47 -43
- data/ext/ruby_prof/rp_call_trees.c +16 -8
- data/ext/ruby_prof/rp_measure_allocations.c +10 -13
- data/ext/ruby_prof/rp_measure_memory.c +8 -4
- data/ext/ruby_prof/rp_measure_process_time.c +7 -6
- data/ext/ruby_prof/rp_measurement.c +147 -20
- data/ext/ruby_prof/rp_measurement.h +4 -1
- data/ext/ruby_prof/rp_method.c +142 -83
- data/ext/ruby_prof/rp_method.h +63 -62
- data/ext/ruby_prof/rp_profile.c +933 -900
- data/ext/ruby_prof/rp_profile.h +1 -0
- data/ext/ruby_prof/rp_thread.c +433 -362
- data/ext/ruby_prof/rp_thread.h +39 -39
- data/ext/ruby_prof/ruby_prof.c +0 -2
- data/ext/ruby_prof/ruby_prof.h +8 -0
- data/ext/ruby_prof/vc/ruby_prof.vcxproj +11 -8
- data/lib/ruby-prof/assets/call_stack_printer.html.erb +2 -1
- data/lib/ruby-prof/compatibility.rb +14 -0
- data/lib/ruby-prof/method_info.rb +8 -1
- data/lib/ruby-prof/printers/abstract_printer.rb +2 -1
- data/lib/ruby-prof/printers/call_tree_printer.rb +4 -10
- data/lib/ruby-prof/printers/graph_html_printer.rb +1 -1
- data/lib/ruby-prof/printers/multi_printer.rb +17 -17
- data/lib/ruby-prof/profile.rb +70 -37
- data/lib/ruby-prof/rack.rb +31 -21
- data/lib/ruby-prof/version.rb +1 -1
- data/lib/ruby-prof.rb +1 -1
- data/ruby-prof.gemspec +2 -3
- data/test/abstract_printer_test.rb +1 -0
- data/test/alias_test.rb +97 -106
- data/test/call_tree_builder.rb +126 -0
- data/test/call_tree_test.rb +94 -0
- data/test/call_tree_visitor_test.rb +1 -6
- data/test/call_trees_test.rb +6 -6
- data/test/{basic_test.rb → compatibility_test.rb} +8 -2
- data/test/duplicate_names_test.rb +5 -5
- data/test/dynamic_method_test.rb +24 -15
- data/test/enumerable_test.rb +1 -1
- data/test/exceptions_test.rb +2 -2
- data/test/exclude_methods_test.rb +3 -8
- data/test/exclude_threads_test.rb +4 -9
- data/test/fiber_test.rb +74 -8
- data/test/gc_test.rb +11 -9
- data/test/inverse_call_tree_test.rb +33 -34
- data/test/line_number_test.rb +37 -61
- data/test/marshal_test.rb +16 -3
- data/test/measure_allocations.rb +1 -5
- data/test/measure_allocations_test.rb +642 -357
- data/test/{measure_memory_trace_test.rb → measure_memory_test.rb} +180 -616
- data/test/measure_process_time_test.rb +1566 -741
- data/test/measure_wall_time_test.rb +179 -193
- data/test/measurement_test.rb +82 -0
- data/test/merge_test.rb +146 -0
- data/test/method_info_test.rb +95 -0
- data/test/multi_printer_test.rb +0 -5
- data/test/no_method_class_test.rb +1 -1
- data/test/pause_resume_test.rb +12 -16
- data/test/printer_call_stack_test.rb +2 -2
- data/test/printer_call_tree_test.rb +4 -4
- data/test/printer_flat_test.rb +1 -1
- data/test/printer_graph_html_test.rb +2 -2
- data/test/printer_graph_test.rb +2 -2
- data/test/printers_test.rb +14 -20
- data/test/printing_recursive_graph_test.rb +2 -2
- data/test/profile_test.rb +85 -0
- data/test/recursive_test.rb +374 -155
- data/test/scheduler.rb +363 -0
- data/test/singleton_test.rb +1 -1
- data/test/stack_printer_test.rb +5 -8
- data/test/start_stop_test.rb +11 -14
- data/test/test_helper.rb +11 -8
- data/test/thread_test.rb +106 -15
- data/test/unique_call_path_test.rb +28 -12
- data/test/yarv_test.rb +11 -7
- metadata +17 -29
- data/ext/ruby_prof/rp_aggregate_call_tree.c +0 -59
- data/ext/ruby_prof/rp_aggregate_call_tree.h +0 -13
- data/test/measure_allocations_trace_test.rb +0 -375
- data/test/temp.rb +0 -20
data/test/scheduler.rb
ADDED
@@ -0,0 +1,363 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This is an example and simplified scheduler for test purposes.
|
4
|
+
# It is not efficient for a large number of file descriptors as it uses IO.select().
|
5
|
+
# Production Fiber schedulers should use epoll/kqueue/etc.
|
6
|
+
|
7
|
+
require 'fiber'
|
8
|
+
require 'socket'
|
9
|
+
|
10
|
+
begin
|
11
|
+
require 'io/nonblock'
|
12
|
+
rescue LoadError
|
13
|
+
# Ignore.
|
14
|
+
end
|
15
|
+
|
16
|
+
class Scheduler
|
17
|
+
experimental = Warning[:experimental]
|
18
|
+
begin
|
19
|
+
Warning[:experimental] = false
|
20
|
+
IO::Buffer.new(0)
|
21
|
+
ensure
|
22
|
+
Warning[:experimental] = experimental
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
@readable = {}
|
27
|
+
@writable = {}
|
28
|
+
@waiting = {}
|
29
|
+
|
30
|
+
@closed = false
|
31
|
+
|
32
|
+
@lock = Thread::Mutex.new
|
33
|
+
@blocking = Hash.new.compare_by_identity
|
34
|
+
@ready = []
|
35
|
+
|
36
|
+
@urgent = IO.pipe
|
37
|
+
end
|
38
|
+
|
39
|
+
attr :readable
|
40
|
+
attr :writable
|
41
|
+
attr :waiting
|
42
|
+
|
43
|
+
def next_timeout
|
44
|
+
_fiber, timeout = @waiting.min_by{|key, value| value}
|
45
|
+
|
46
|
+
if timeout
|
47
|
+
offset = timeout - current_time
|
48
|
+
|
49
|
+
if offset < 0
|
50
|
+
return 0
|
51
|
+
else
|
52
|
+
return offset
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def run
|
58
|
+
# $stderr.puts [__method__, Fiber.current].inspect
|
59
|
+
|
60
|
+
while @readable.any? or @writable.any? or @waiting.any? or @blocking.any?
|
61
|
+
# Can only handle file descriptors up to 1024...
|
62
|
+
readable, writable = IO.select(@readable.keys + [@urgent.first], @writable.keys, [], next_timeout)
|
63
|
+
|
64
|
+
# puts "readable: #{readable}" if readable&.any?
|
65
|
+
# puts "writable: #{writable}" if writable&.any?
|
66
|
+
|
67
|
+
selected = {}
|
68
|
+
|
69
|
+
readable&.each do |io|
|
70
|
+
if fiber = @readable.delete(io)
|
71
|
+
@writable.delete(io) if @writable[io] == fiber
|
72
|
+
selected[fiber] = IO::READABLE
|
73
|
+
elsif io == @urgent.first
|
74
|
+
@urgent.first.read_nonblock(1024)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
writable&.each do |io|
|
79
|
+
if fiber = @writable.delete(io)
|
80
|
+
@readable.delete(io) if @readable[io] == fiber
|
81
|
+
selected[fiber] = selected.fetch(fiber, 0) | IO::WRITABLE
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
selected.each do |fiber, events|
|
86
|
+
fiber.resume(events)
|
87
|
+
end
|
88
|
+
|
89
|
+
if @waiting.any?
|
90
|
+
time = current_time
|
91
|
+
waiting, @waiting = @waiting, {}
|
92
|
+
|
93
|
+
waiting.each do |fiber, timeout|
|
94
|
+
if fiber.alive?
|
95
|
+
if timeout <= time
|
96
|
+
fiber.resume
|
97
|
+
else
|
98
|
+
@waiting[fiber] = timeout
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
if @ready.any?
|
105
|
+
ready = nil
|
106
|
+
|
107
|
+
@lock.synchronize do
|
108
|
+
ready, @ready = @ready, []
|
109
|
+
end
|
110
|
+
|
111
|
+
ready.each do |fiber|
|
112
|
+
fiber.resume
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def scheduler_close
|
119
|
+
close(true)
|
120
|
+
end
|
121
|
+
|
122
|
+
def close(internal = false)
|
123
|
+
# $stderr.puts [__method__, Fiber.current].inspect
|
124
|
+
|
125
|
+
unless internal
|
126
|
+
if Fiber.scheduler == self
|
127
|
+
return Fiber.set_scheduler(nil)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
if @closed
|
132
|
+
raise "Scheduler already closed!"
|
133
|
+
end
|
134
|
+
|
135
|
+
self.run
|
136
|
+
ensure
|
137
|
+
if @urgent
|
138
|
+
@urgent.each(&:close)
|
139
|
+
@urgent = nil
|
140
|
+
end
|
141
|
+
|
142
|
+
@closed ||= true
|
143
|
+
|
144
|
+
# We freeze to detect any unintended modifications after the scheduler is closed:
|
145
|
+
self.freeze
|
146
|
+
end
|
147
|
+
|
148
|
+
def closed?
|
149
|
+
@closed
|
150
|
+
end
|
151
|
+
|
152
|
+
def current_time
|
153
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
154
|
+
end
|
155
|
+
|
156
|
+
def timeout_after(duration, klass, message, &block)
|
157
|
+
fiber = Fiber.current
|
158
|
+
|
159
|
+
self.fiber do
|
160
|
+
sleep(duration)
|
161
|
+
|
162
|
+
if fiber&.alive?
|
163
|
+
fiber.raise(klass, message)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
begin
|
168
|
+
yield(duration)
|
169
|
+
ensure
|
170
|
+
fiber = nil
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def process_wait(pid, flags)
|
175
|
+
# $stderr.puts [__method__, pid, flags, Fiber.current].inspect
|
176
|
+
|
177
|
+
# This is a very simple way to implement a non-blocking wait:
|
178
|
+
Thread.new do
|
179
|
+
Process::Status.wait(pid, flags)
|
180
|
+
end.value
|
181
|
+
end
|
182
|
+
|
183
|
+
def io_wait(io, events, duration)
|
184
|
+
# $stderr.puts [__method__, io, events, duration, Fiber.current].inspect
|
185
|
+
|
186
|
+
unless (events & IO::READABLE).zero?
|
187
|
+
@readable[io] = Fiber.current
|
188
|
+
end
|
189
|
+
|
190
|
+
unless (events & IO::WRITABLE).zero?
|
191
|
+
@writable[io] = Fiber.current
|
192
|
+
end
|
193
|
+
|
194
|
+
Fiber.yield
|
195
|
+
ensure
|
196
|
+
@readable.delete(io)
|
197
|
+
@writable.delete(io)
|
198
|
+
end
|
199
|
+
|
200
|
+
def io_select(...)
|
201
|
+
# Emulate the operation using a non-blocking thread:
|
202
|
+
Thread.new do
|
203
|
+
IO.select(...)
|
204
|
+
end.value
|
205
|
+
end
|
206
|
+
|
207
|
+
# Used for Kernel#sleep and Thread::Mutex#sleep
|
208
|
+
def kernel_sleep(duration = nil)
|
209
|
+
# $stderr.puts [__method__, duration, Fiber.current].inspect
|
210
|
+
|
211
|
+
self.block(:sleep, duration)
|
212
|
+
|
213
|
+
return true
|
214
|
+
end
|
215
|
+
|
216
|
+
# Used when blocking on synchronization (Thread::Mutex#lock,
|
217
|
+
# Thread::Queue#pop, Thread::SizedQueue#push, ...)
|
218
|
+
def block(blocker, timeout = nil)
|
219
|
+
# $stderr.puts [__method__, blocker, timeout].inspect
|
220
|
+
|
221
|
+
fiber = Fiber.current
|
222
|
+
|
223
|
+
if timeout
|
224
|
+
@waiting[fiber] = current_time + timeout
|
225
|
+
begin
|
226
|
+
Fiber.yield
|
227
|
+
ensure
|
228
|
+
# Remove from @waiting in the case #unblock was called before the timeout expired:
|
229
|
+
@waiting.delete(fiber)
|
230
|
+
end
|
231
|
+
else
|
232
|
+
@blocking[fiber] = true
|
233
|
+
begin
|
234
|
+
Fiber.yield
|
235
|
+
ensure
|
236
|
+
@blocking.delete(fiber)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
# Used when synchronization wakes up a previously-blocked fiber
|
242
|
+
# (Thread::Mutex#unlock, Thread::Queue#push, ...).
|
243
|
+
# This might be called from another thread.
|
244
|
+
def unblock(blocker, fiber)
|
245
|
+
# $stderr.puts [__method__, blocker, fiber].inspect
|
246
|
+
# $stderr.puts blocker.backtrace.inspect
|
247
|
+
# $stderr.puts fiber.backtrace.inspect
|
248
|
+
|
249
|
+
@lock.synchronize do
|
250
|
+
@ready << fiber
|
251
|
+
end
|
252
|
+
|
253
|
+
io = @urgent.last
|
254
|
+
io.write_nonblock('.')
|
255
|
+
end
|
256
|
+
|
257
|
+
def fiber(&block)
|
258
|
+
fiber = Fiber.new(blocking: false, &block)
|
259
|
+
|
260
|
+
fiber.resume
|
261
|
+
|
262
|
+
return fiber
|
263
|
+
end
|
264
|
+
|
265
|
+
def address_resolve(hostname)
|
266
|
+
Thread.new do
|
267
|
+
Addrinfo.getaddrinfo(hostname, nil).map(&:ip_address).uniq
|
268
|
+
end.value
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
class IOBufferScheduler < Scheduler
|
273
|
+
EAGAIN = -Errno::EAGAIN::Errno
|
274
|
+
|
275
|
+
def io_read(io, buffer, length, offset)
|
276
|
+
total = 0
|
277
|
+
io.nonblock = true
|
278
|
+
|
279
|
+
while true
|
280
|
+
maximum_size = buffer.size - offset
|
281
|
+
result = blocking{buffer.read(io, maximum_size, offset)}
|
282
|
+
|
283
|
+
if result > 0
|
284
|
+
total += result
|
285
|
+
offset += result
|
286
|
+
break if total >= length
|
287
|
+
elsif result == 0
|
288
|
+
break
|
289
|
+
elsif result == EAGAIN
|
290
|
+
if length > 0
|
291
|
+
self.io_wait(io, IO::READABLE, nil)
|
292
|
+
else
|
293
|
+
return result
|
294
|
+
end
|
295
|
+
elsif result < 0
|
296
|
+
return result
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
return total
|
301
|
+
end
|
302
|
+
|
303
|
+
def io_write(io, buffer, length, offset)
|
304
|
+
total = 0
|
305
|
+
io.nonblock = true
|
306
|
+
|
307
|
+
while true
|
308
|
+
maximum_size = buffer.size - offset
|
309
|
+
result = blocking{buffer.write(io, maximum_size, offset)}
|
310
|
+
|
311
|
+
if result > 0
|
312
|
+
total += result
|
313
|
+
offset += result
|
314
|
+
break if total >= length
|
315
|
+
elsif result == 0
|
316
|
+
break
|
317
|
+
elsif result == EAGAIN
|
318
|
+
if length > 0
|
319
|
+
self.io_wait(io, IO::WRITABLE, nil)
|
320
|
+
else
|
321
|
+
return result
|
322
|
+
end
|
323
|
+
elsif result < 0
|
324
|
+
return result
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
return total
|
329
|
+
end
|
330
|
+
|
331
|
+
def blocking(&block)
|
332
|
+
Fiber.blocking(&block)
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
class BrokenUnblockScheduler < Scheduler
|
337
|
+
def unblock(blocker, fiber)
|
338
|
+
super
|
339
|
+
|
340
|
+
raise "Broken unblock!"
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
class SleepingUnblockScheduler < Scheduler
|
345
|
+
# This method is invoked when the thread is exiting.
|
346
|
+
def unblock(blocker, fiber)
|
347
|
+
super
|
348
|
+
|
349
|
+
# This changes the current thread state to `THREAD_RUNNING` which causes `thread_join_sleep` to hang.
|
350
|
+
sleep(0.1)
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
class SleepingBlockingScheduler < Scheduler
|
355
|
+
def kernel_sleep(duration = nil)
|
356
|
+
# Deliberaly sleep in a blocking state which can trigger a deadlock if the implementation is not correct.
|
357
|
+
Fiber.blocking{sleep 0.0001}
|
358
|
+
|
359
|
+
self.block(:sleep, duration)
|
360
|
+
|
361
|
+
return true
|
362
|
+
end
|
363
|
+
end
|
data/test/singleton_test.rb
CHANGED
data/test/stack_printer_test.rb
CHANGED
@@ -27,16 +27,12 @@ class STPT
|
|
27
27
|
end
|
28
28
|
|
29
29
|
class StackPrinterTest < TestCase
|
30
|
-
def setup
|
31
|
-
# Need to use wall time for this test due to the sleep calls
|
32
|
-
RubyProf::measure_mode = RubyProf::WALL_TIME
|
33
|
-
end
|
34
|
-
|
35
30
|
def test_stack_can_be_printed
|
36
31
|
start_time = Time.now
|
37
|
-
RubyProf.
|
38
|
-
|
39
|
-
|
32
|
+
result = RubyProf::Profile.profile(measure_mode: RubyProf::WALL_TIME) do
|
33
|
+
5.times{STPT.new.a}
|
34
|
+
end
|
35
|
+
|
40
36
|
end_time = Time.now
|
41
37
|
expected_time = end_time - start_time
|
42
38
|
|
@@ -50,6 +46,7 @@ class StackPrinterTest < TestCase
|
|
50
46
|
end
|
51
47
|
|
52
48
|
private
|
49
|
+
|
53
50
|
def print(result)
|
54
51
|
test = caller.first =~ /in `(.*)'/ ? $1 : "test"
|
55
52
|
testfile_name = "#{Dir.tmpdir}/ruby_prof_#{test}.html"
|
data/test/start_stop_test.rb
CHANGED
@@ -5,37 +5,34 @@ require File.expand_path('../test_helper', __FILE__)
|
|
5
5
|
|
6
6
|
class StartStopTest < TestCase
|
7
7
|
def setup
|
8
|
+
super
|
8
9
|
# Need to use wall time for this test due to the sleep calls
|
9
|
-
RubyProf::measure_mode
|
10
|
+
@profile = RubyProf::Profile.new(measure_mode: RubyProf::WALL_TIME)
|
10
11
|
end
|
11
12
|
|
12
13
|
def method1
|
13
|
-
|
14
|
-
|
14
|
+
@profile.start
|
15
|
+
method2
|
15
16
|
end
|
16
17
|
|
17
18
|
def method2
|
18
|
-
|
19
|
+
method3
|
19
20
|
end
|
20
21
|
|
21
22
|
def method3
|
22
23
|
sleep(2)
|
23
|
-
@result =
|
24
|
+
@result = @profile.stop
|
24
25
|
end
|
25
26
|
|
26
27
|
def test_extra_stop_should_raise
|
27
|
-
|
28
|
+
@profile.start
|
28
29
|
assert_raises(RuntimeError) do
|
29
|
-
|
30
|
+
@profile.start
|
30
31
|
end
|
31
32
|
|
33
|
+
@profile.stop # ok
|
32
34
|
assert_raises(RuntimeError) do
|
33
|
-
|
34
|
-
end
|
35
|
-
|
36
|
-
RubyProf.stop # ok
|
37
|
-
assert_raises(RuntimeError) do
|
38
|
-
RubyProf.stop
|
35
|
+
@profile.stop
|
39
36
|
end
|
40
37
|
end
|
41
38
|
|
@@ -43,7 +40,7 @@ class StartStopTest < TestCase
|
|
43
40
|
method1
|
44
41
|
|
45
42
|
# Ruby prof should be stopped
|
46
|
-
assert_equal(false,
|
43
|
+
assert_equal(false, @profile.running?)
|
47
44
|
|
48
45
|
methods = @result.threads.first.methods.sort.reverse
|
49
46
|
assert_equal(4, methods.length)
|
data/test/test_helper.rb
CHANGED
@@ -1,17 +1,20 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
+
# To make testing/debugging easier test within this source tree versus an installed gem
|
3
4
|
require 'bundler/setup'
|
4
|
-
require 'minitest/autorun'
|
5
5
|
|
6
|
-
#
|
7
|
-
|
8
|
-
|
9
|
-
ENV["MT_CPU"] = "0" # Newer versions minitest
|
10
|
-
else
|
11
|
-
ENV["N"] = "0" # Older versions of minitest
|
12
|
-
end
|
6
|
+
# Add ext directory to load path to make it easier to test locally built extensions
|
7
|
+
ext_path = File.expand_path(File.join(__dir__, '..', 'ext', 'ruby_prof'))
|
8
|
+
$LOAD_PATH.unshift(File.expand_path(ext_path))
|
13
9
|
|
10
|
+
# Now load code
|
14
11
|
require 'ruby-prof'
|
15
12
|
|
13
|
+
# Disable minitest parallel tests. The problem is the thread switching will change test results
|
14
|
+
# (self vs wait time)
|
15
|
+
ENV["MT_CPU"] = "0" # New versions of minitest
|
16
|
+
ENV["N"] = "0" # Older versions of minitest
|
17
|
+
|
18
|
+
require 'minitest/autorun'
|
16
19
|
class TestCase < Minitest::Test
|
17
20
|
end
|
data/test/thread_test.rb
CHANGED
@@ -4,33 +4,122 @@
|
|
4
4
|
require File.expand_path('../test_helper', __FILE__)
|
5
5
|
require 'timeout'
|
6
6
|
require 'benchmark'
|
7
|
+
require_relative './call_tree_builder'
|
7
8
|
|
8
9
|
# -- Tests ----
|
9
10
|
class ThreadTest < TestCase
|
10
|
-
def
|
11
|
-
|
12
|
-
|
11
|
+
def test_initialize
|
12
|
+
method_info = RubyProf::MethodInfo.new(Array, :size)
|
13
|
+
call_tree = RubyProf::CallTree.new(method_info)
|
14
|
+
thread = RubyProf::Thread.new(call_tree, Thread.current, Fiber.current)
|
15
|
+
|
16
|
+
assert_equal(call_tree, thread.call_tree)
|
17
|
+
assert(thread)
|
18
|
+
assert(thread.id)
|
19
|
+
assert(thread.fiber_id)
|
20
|
+
|
21
|
+
assert_equal(1, thread.methods.size)
|
22
|
+
assert_same(method_info, thread.methods[0])
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_merge
|
26
|
+
call_tree_1 = create_call_tree_1
|
27
|
+
thread_1 = RubyProf::Thread.new(call_tree_1, Thread.current, Fiber.current)
|
28
|
+
assert_equal(6, thread_1.methods.size)
|
29
|
+
|
30
|
+
call_tree_2 = create_call_tree_2
|
31
|
+
thread_2 = RubyProf::Thread.new(call_tree_2, Thread.current, Fiber.current)
|
32
|
+
assert_equal(6, thread_2.methods.size)
|
33
|
+
|
34
|
+
thread_1.merge!(thread_2)
|
35
|
+
assert_equal(7, thread_1.methods.size)
|
36
|
+
|
37
|
+
# Method times
|
38
|
+
assert_in_delta(11.6, thread_1.methods[0].total_time, 0.00001) # root
|
39
|
+
assert_in_delta(4.1, thread_1.methods[1].total_time, 0.00001) # a
|
40
|
+
assert_in_delta(1.5, thread_1.methods[2].total_time, 0.00001) # aa
|
41
|
+
assert_in_delta(2.6, thread_1.methods[3].total_time, 0.00001) # ab
|
42
|
+
assert_in_delta(7.5, thread_1.methods[4].total_time, 0.00001) # b
|
43
|
+
assert_in_delta(6.6, thread_1.methods[5].total_time, 0.00001) # bb
|
44
|
+
assert_in_delta(0.9, thread_1.methods[6].total_time, 0.00001) # ba
|
45
|
+
|
46
|
+
# Root
|
47
|
+
call_tree = call_tree_1
|
48
|
+
assert_equal(:root, call_tree.target.method_name)
|
49
|
+
assert_in_delta(11.6, call_tree.total_time, 0.00001)
|
50
|
+
assert_in_delta(0, call_tree.self_time, 0.00001)
|
51
|
+
assert_in_delta(0.0, call_tree.wait_time, 0.00001)
|
52
|
+
assert_in_delta(11.6, call_tree.children_time, 0.00001)
|
53
|
+
|
54
|
+
# a
|
55
|
+
call_tree = call_tree_1.children[0]
|
56
|
+
assert_equal(:a, call_tree.target.method_name)
|
57
|
+
assert_in_delta(4.1, call_tree.total_time, 0.00001)
|
58
|
+
assert_in_delta(0, call_tree.self_time, 0.00001)
|
59
|
+
assert_in_delta(0.0, call_tree.wait_time, 0.00001)
|
60
|
+
assert_in_delta(4.1, call_tree.children_time, 0.00001)
|
61
|
+
|
62
|
+
# aa
|
63
|
+
call_tree = call_tree_1.children[0].children[0]
|
64
|
+
assert_equal(:aa, call_tree.target.method_name)
|
65
|
+
assert_in_delta(1.5, call_tree.total_time, 0.00001)
|
66
|
+
assert_in_delta(1.5, call_tree.self_time, 0.00001)
|
67
|
+
assert_in_delta(0.0, call_tree.wait_time, 0.00001)
|
68
|
+
assert_in_delta(0.0, call_tree.children_time, 0.00001)
|
69
|
+
|
70
|
+
# ab
|
71
|
+
call_tree = call_tree_1.children[0].children[1]
|
72
|
+
assert_equal(:ab, call_tree.target.method_name)
|
73
|
+
assert_in_delta(2.6, call_tree.total_time, 0.00001)
|
74
|
+
assert_in_delta(2.6, call_tree.self_time, 0.00001)
|
75
|
+
assert_in_delta(0.0, call_tree.wait_time, 0.00001)
|
76
|
+
assert_in_delta(0.0, call_tree.children_time, 0.00001)
|
77
|
+
|
78
|
+
# # b
|
79
|
+
# call_tree = call_tree_1.children[1]
|
80
|
+
# assert_equal(:b, call_tree.target.method_name)
|
81
|
+
# assert_in_delta(7.5, call_tree.total_time, 0.00001)
|
82
|
+
# assert_in_delta(0, call_tree.self_time, 0.00001)
|
83
|
+
# assert_in_delta(0.0, call_tree.wait_time, 0.00001)
|
84
|
+
# assert_in_delta(7.5, call_tree.children_time, 0.00001)
|
85
|
+
|
86
|
+
# bb
|
87
|
+
# call_tree = call_tree_1.children[1].children[0]
|
88
|
+
# assert_equal(:bb, call_tree.target.method_name)
|
89
|
+
# assert_in_delta(6.6, call_tree.total_time, 0.00001)
|
90
|
+
# assert_in_delta(6.6, call_tree.self_time, 0.00001)
|
91
|
+
# assert_in_delta(0.0, call_tree.wait_time, 0.00001)
|
92
|
+
# assert_in_delta(0.0, call_tree.children_time, 0.00001)
|
93
|
+
|
94
|
+
# ba
|
95
|
+
call_tree = call_tree_1.children[1].children[1]
|
96
|
+
assert_equal(:ba, call_tree.target.method_name)
|
97
|
+
assert_in_delta(0.9, call_tree.total_time, 0.00001)
|
98
|
+
assert_in_delta(0.7, call_tree.self_time, 0.00001)
|
99
|
+
assert_in_delta(0.2, call_tree.wait_time, 0.00001)
|
100
|
+
assert_in_delta(0.0, call_tree.children_time, 0.00001)
|
13
101
|
end
|
14
102
|
|
15
103
|
def test_thread_count
|
16
|
-
RubyProf.
|
104
|
+
result = RubyProf::Profile.profile(measure_mode: RubyProf::WALL_TIME) do
|
105
|
+
thread = Thread.new do
|
106
|
+
sleep(1)
|
107
|
+
end
|
17
108
|
|
18
|
-
|
19
|
-
sleep(1)
|
109
|
+
thread.join
|
20
110
|
end
|
21
|
-
|
22
|
-
thread.join
|
23
|
-
result = RubyProf.stop
|
24
111
|
assert_equal(2, result.threads.length)
|
25
112
|
end
|
26
113
|
|
27
114
|
def test_thread_identity
|
28
|
-
RubyProf.
|
115
|
+
profile = RubyProf::Profile.new(measure_mode: RubyProf::WALL_TIME)
|
116
|
+
profile.start
|
117
|
+
|
29
118
|
sleep_thread = Thread.new do
|
30
119
|
sleep(1)
|
31
120
|
end
|
32
121
|
sleep_thread.join
|
33
|
-
result =
|
122
|
+
result = profile.stop
|
34
123
|
|
35
124
|
thread_ids = result.threads.map {|thread| thread.id}.sort
|
36
125
|
threads = [Thread.current, sleep_thread]
|
@@ -47,7 +136,9 @@ class ThreadTest < TestCase
|
|
47
136
|
end
|
48
137
|
|
49
138
|
def test_thread_timings
|
50
|
-
RubyProf.
|
139
|
+
profile = RubyProf::Profile.new(measure_mode: RubyProf::WALL_TIME)
|
140
|
+
profile.start
|
141
|
+
|
51
142
|
thread = Thread.new do
|
52
143
|
sleep 0
|
53
144
|
# force it to hit thread.join, below, first
|
@@ -57,7 +148,7 @@ class ThreadTest < TestCase
|
|
57
148
|
sleep(1)
|
58
149
|
end
|
59
150
|
thread.join
|
60
|
-
result =
|
151
|
+
result = profile.stop
|
61
152
|
|
62
153
|
# Check background thread
|
63
154
|
assert_equal(2, result.threads.length)
|
@@ -71,10 +162,10 @@ class ThreadTest < TestCase
|
|
71
162
|
method = methods[0]
|
72
163
|
assert_equal('ThreadTest#test_thread_timings', method.full_name)
|
73
164
|
assert_equal(1, method.called)
|
74
|
-
assert_in_delta(1, method.total_time, 0.
|
165
|
+
assert_in_delta(1, method.total_time, 0.1)
|
75
166
|
assert_in_delta(0, method.self_time, 0.05)
|
76
167
|
assert_in_delta(0, method.wait_time, 0.05)
|
77
|
-
assert_in_delta(1, method.children_time, 0.
|
168
|
+
assert_in_delta(1, method.children_time, 0.1)
|
78
169
|
assert_equal(0, method.call_trees.callers.length)
|
79
170
|
|
80
171
|
method = methods[1]
|