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.
- checksums.yaml +4 -4
- data/CHANGES +27 -1
- data/README.rdoc +83 -31
- data/bin/ruby-prof +4 -4
- data/doc/LICENSE.html +1 -1
- data/doc/README_rdoc.html +92 -33
- data/doc/Rack.html +1 -1
- data/doc/Rack/RubyProf.html +17 -14
- data/doc/RubyProf.html +30 -29
- data/doc/RubyProf/AbstractPrinter.html +1 -1
- data/doc/RubyProf/AggregateCallInfo.html +1 -1
- data/doc/RubyProf/CallInfo.html +1 -1
- data/doc/RubyProf/CallInfoPrinter.html +1 -1
- data/doc/RubyProf/CallInfoVisitor.html +1 -1
- data/doc/RubyProf/CallStackPrinter.html +1 -1
- data/doc/RubyProf/CallTreePrinter.html +349 -67
- data/doc/RubyProf/Cmd.html +5 -5
- data/doc/RubyProf/DotPrinter.html +2 -2
- data/doc/RubyProf/FlatPrinter.html +1 -1
- data/doc/RubyProf/FlatPrinterWithLineNumbers.html +1 -1
- data/doc/RubyProf/GraphHtmlPrinter.html +1 -1
- data/doc/RubyProf/GraphPrinter.html +1 -1
- data/doc/RubyProf/MethodInfo.html +2 -2
- data/doc/RubyProf/MultiPrinter.html +11 -9
- data/doc/RubyProf/Profile.html +94 -44
- data/doc/RubyProf/ProfileTask.html +1 -1
- data/doc/RubyProf/Thread.html +43 -1
- data/doc/created.rid +16 -16
- data/doc/examples/flat_txt.html +1 -1
- data/doc/examples/graph_html.html +1 -1
- data/doc/examples/graph_txt.html +3 -3
- data/doc/index.html +85 -30
- data/doc/js/navigation.js.gz +0 -0
- data/doc/js/search_index.js +1 -1
- data/doc/js/search_index.js.gz +0 -0
- data/doc/js/searcher.js +2 -2
- data/doc/js/searcher.js.gz +0 -0
- data/doc/table_of_contents.html +117 -68
- data/examples/cachegrind.out.1 +114 -0
- data/examples/cachegrind.out.1.32313213 +114 -0
- data/examples/graph.txt +1 -1
- data/ext/ruby_prof/extconf.rb +6 -2
- data/ext/ruby_prof/rp_measure_cpu_time.c +29 -31
- data/ext/ruby_prof/rp_method.c +1 -1
- data/ext/ruby_prof/rp_thread.c +57 -52
- data/ext/ruby_prof/ruby_prof.c +122 -66
- data/ext/ruby_prof/ruby_prof.h +2 -0
- data/lib/ruby-prof.rb +14 -13
- data/lib/ruby-prof/assets/call_stack_printer.js.html +1 -1
- data/lib/ruby-prof/compatibility.rb +9 -8
- data/lib/ruby-prof/method_info.rb +1 -1
- data/lib/ruby-prof/printers/call_tree_printer.rb +88 -50
- data/lib/ruby-prof/printers/dot_printer.rb +1 -1
- data/lib/ruby-prof/printers/multi_printer.rb +6 -4
- data/lib/ruby-prof/profile.rb +0 -1
- data/lib/ruby-prof/rack.rb +53 -16
- data/lib/ruby-prof/thread.rb +11 -0
- data/lib/ruby-prof/version.rb +1 -1
- data/test/exclude_threads_test.rb +2 -3
- data/test/fiber_test.rb +21 -7
- data/test/measure_cpu_time_test.rb +84 -24
- data/test/multi_printer_test.rb +5 -4
- data/test/pause_resume_test.rb +7 -7
- data/test/printers_test.rb +6 -4
- data/test/rack_test.rb +26 -1
- data/test/test_helper.rb +28 -3
- data/test/thread_test.rb +1 -0
- metadata +5 -3
data/ext/ruby_prof/ruby_prof.c
CHANGED
@@ -107,19 +107,21 @@ get_method(rb_event_flag_t event, VALUE klass, ID mid, thread_data_t* thread_dat
|
|
107
107
|
static int
|
108
108
|
pop_frames(st_data_t key, st_data_t value, st_data_t data)
|
109
109
|
{
|
110
|
-
VALUE fiber_id = (VALUE)key;
|
111
110
|
thread_data_t* thread_data = (thread_data_t *) value;
|
112
111
|
prof_profile_t* profile = (prof_profile_t*) data;
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
112
|
+
VALUE thread_id = thread_data->thread_id;
|
113
|
+
VALUE fiber_id = thread_data->fiber_id;
|
114
|
+
double measurement = profile->measurer->measure();
|
115
|
+
|
116
|
+
if (!profile->last_thread_data
|
117
|
+
|| (!profile->merge_fibers && profile->last_thread_data->fiber_id != fiber_id)
|
118
|
+
|| profile->last_thread_data->thread_id != thread_id
|
119
|
+
)
|
120
|
+
thread_data = switch_thread(profile, thread_id, fiber_id);
|
117
121
|
else
|
118
|
-
|
122
|
+
thread_data = profile->last_thread_data;
|
119
123
|
|
120
|
-
while (prof_stack_pop(thread_data->stack, measurement))
|
121
|
-
{
|
122
|
-
}
|
124
|
+
while (prof_stack_pop(thread_data->stack, measurement));
|
123
125
|
|
124
126
|
return ST_CONTINUE;
|
125
127
|
}
|
@@ -188,7 +190,7 @@ prof_event_hook(rb_event_flag_t event, VALUE data, VALUE self, ID mid, VALUE kla
|
|
188
190
|
module or cProfile class since they clutter
|
189
191
|
the results but aren't important to them results. */
|
190
192
|
if (self == mProf || klass == cProfile)
|
191
|
-
|
193
|
+
return;
|
192
194
|
|
193
195
|
if (trace_file != NULL)
|
194
196
|
{
|
@@ -201,16 +203,31 @@ prof_event_hook(rb_event_flag_t event, VALUE data, VALUE self, ID mid, VALUE kla
|
|
201
203
|
fiber = rb_fiber_current();
|
202
204
|
fiber_id = rb_obj_id(fiber);
|
203
205
|
|
204
|
-
if
|
206
|
+
/* Don't measure anything if the include_threads option has been specified
|
207
|
+
and the current thread is not in the list
|
208
|
+
*/
|
209
|
+
if (profile->include_threads_tbl && !st_lookup(profile->include_threads_tbl, (st_data_t) thread_id, 0))
|
210
|
+
{
|
211
|
+
return;
|
212
|
+
}
|
213
|
+
|
214
|
+
/* Don't measure anything if the current thread is in the excluded thread table
|
215
|
+
*/
|
216
|
+
if (profile->exclude_threads_tbl && st_lookup(profile->exclude_threads_tbl, (st_data_t) thread_id, 0))
|
205
217
|
{
|
206
|
-
|
218
|
+
return;
|
207
219
|
}
|
208
220
|
|
209
|
-
/*
|
210
|
-
|
211
|
-
|
221
|
+
/* We need to switch the profiling context if we either had none before,
|
222
|
+
we don't merge fibers and the fiber ids differ, or the thread ids differ.
|
223
|
+
*/
|
224
|
+
if (!profile->last_thread_data
|
225
|
+
|| (!profile->merge_fibers && profile->last_thread_data->fiber_id != fiber_id)
|
226
|
+
|| profile->last_thread_data->thread_id != thread_id
|
227
|
+
)
|
228
|
+
thread_data = switch_thread(profile, thread_id, fiber_id);
|
212
229
|
else
|
213
|
-
|
230
|
+
thread_data = profile->last_thread_data;
|
214
231
|
|
215
232
|
/* Get the current frame for the current thread. */
|
216
233
|
frame = prof_stack_peek(thread_data->stack);
|
@@ -301,7 +318,7 @@ collect_threads(st_data_t key, st_data_t value, st_data_t result)
|
|
301
318
|
{
|
302
319
|
thread_data_t* thread_data = (thread_data_t*) value;
|
303
320
|
VALUE threads_array = (VALUE) result;
|
304
|
-
|
321
|
+
rb_ary_push(threads_array, prof_thread_wrap(thread_data));
|
305
322
|
return ST_CONTINUE;
|
306
323
|
}
|
307
324
|
|
@@ -317,7 +334,7 @@ mark_threads(st_data_t key, st_data_t value, st_data_t result)
|
|
317
334
|
static void
|
318
335
|
prof_mark(prof_profile_t *profile)
|
319
336
|
{
|
320
|
-
|
337
|
+
st_foreach(profile->threads_tbl, mark_threads, 0);
|
321
338
|
}
|
322
339
|
|
323
340
|
/* Freeing the profile creates a cascade of freeing.
|
@@ -326,16 +343,23 @@ prof_mark(prof_profile_t *profile)
|
|
326
343
|
static void
|
327
344
|
prof_free(prof_profile_t *profile)
|
328
345
|
{
|
329
|
-
|
346
|
+
profile->last_thread_data = NULL;
|
330
347
|
|
331
|
-
|
348
|
+
threads_table_free(profile->threads_tbl);
|
332
349
|
profile->threads_tbl = NULL;
|
333
350
|
|
334
|
-
|
335
|
-
|
351
|
+
if (profile->exclude_threads_tbl) {
|
352
|
+
st_free_table(profile->exclude_threads_tbl);
|
353
|
+
profile->exclude_threads_tbl = NULL;
|
354
|
+
}
|
355
|
+
|
356
|
+
if (profile->include_threads_tbl) {
|
357
|
+
st_free_table(profile->include_threads_tbl);
|
358
|
+
profile->include_threads_tbl = NULL;
|
359
|
+
}
|
336
360
|
|
337
|
-
|
338
|
-
|
361
|
+
xfree(profile->measurer);
|
362
|
+
profile->measurer = NULL;
|
339
363
|
|
340
364
|
xfree(profile);
|
341
365
|
}
|
@@ -347,58 +371,86 @@ prof_allocate(VALUE klass)
|
|
347
371
|
prof_profile_t* profile;
|
348
372
|
result = Data_Make_Struct(klass, prof_profile_t, prof_mark, prof_free, profile);
|
349
373
|
profile->threads_tbl = threads_table_create();
|
350
|
-
|
374
|
+
profile->exclude_threads_tbl = NULL;
|
375
|
+
profile->include_threads_tbl = NULL;
|
351
376
|
profile->running = Qfalse;
|
377
|
+
profile->merge_fibers = 0;
|
352
378
|
return result;
|
353
379
|
}
|
354
380
|
|
355
381
|
/* call-seq:
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
exclude_threads:: Threads to exclude from the profiling results
|
382
|
+
new()
|
383
|
+
new(options)
|
384
|
+
|
385
|
+
Returns a new profiler. Possible options for the options hash are:
|
386
|
+
|
387
|
+
measure_mode:: Measure mode. Specifies the profile measure mode.
|
388
|
+
If not specified, defaults to RubyProf::WALL_TIME.
|
389
|
+
exclude_threads:: Threads to exclude from the profiling results.
|
390
|
+
include_threads:: Focus profiling on only the given threads. This will ignore
|
391
|
+
all other threads.
|
392
|
+
merge_fibers:: Whether to merge all fibers under a given thread. This should be
|
393
|
+
used when profiling for a callgrind printer.
|
394
|
+
*/
|
364
395
|
static VALUE
|
365
396
|
prof_initialize(int argc, VALUE *argv, VALUE self)
|
366
397
|
{
|
367
398
|
prof_profile_t* profile = prof_get_profile(self);
|
368
|
-
VALUE
|
369
|
-
|
370
|
-
VALUE exclude_threads;
|
399
|
+
VALUE mode_or_options;
|
400
|
+
VALUE mode = Qnil;
|
401
|
+
VALUE exclude_threads = Qnil;
|
402
|
+
VALUE include_threads = Qnil;
|
403
|
+
VALUE merge_fibers = Qnil;
|
371
404
|
int i;
|
372
|
-
|
373
|
-
switch (rb_scan_args(argc, argv, "02", &
|
374
|
-
|
375
|
-
case 0:
|
376
|
-
{
|
377
|
-
measurer = MEASURE_WALL_TIME;
|
378
|
-
exclude_threads = rb_ary_new();
|
405
|
+
|
406
|
+
switch (rb_scan_args(argc, argv, "02", &mode_or_options, &exclude_threads)) {
|
407
|
+
case 0:
|
379
408
|
break;
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
409
|
+
case 1:
|
410
|
+
if (FIXNUM_P(mode_or_options)) {
|
411
|
+
mode = mode_or_options;
|
412
|
+
}
|
413
|
+
else {
|
414
|
+
Check_Type(mode_or_options, T_HASH);
|
415
|
+
mode = rb_hash_aref(mode_or_options, ID2SYM(rb_intern("measure_mode")));
|
416
|
+
merge_fibers = rb_hash_aref(mode_or_options, ID2SYM(rb_intern("merge_fibers")));
|
417
|
+
exclude_threads = rb_hash_aref(mode_or_options, ID2SYM(rb_intern("exclude_threads")));
|
418
|
+
include_threads = rb_hash_aref(mode_or_options, ID2SYM(rb_intern("include_threads")));
|
419
|
+
}
|
385
420
|
break;
|
386
|
-
|
387
|
-
case 2:
|
388
|
-
{
|
421
|
+
case 2:
|
389
422
|
Check_Type(exclude_threads, T_ARRAY);
|
390
|
-
measurer = (prof_measure_mode_t)NUM2INT(mode);
|
391
423
|
break;
|
392
|
-
}
|
393
424
|
}
|
394
425
|
|
395
|
-
|
426
|
+
if (mode == Qnil) {
|
427
|
+
mode = INT2NUM(MEASURE_WALL_TIME);
|
428
|
+
} else {
|
429
|
+
Check_Type(mode, T_FIXNUM);
|
430
|
+
}
|
431
|
+
profile->measurer = prof_get_measurer(NUM2INT(mode));
|
432
|
+
profile->merge_fibers = merge_fibers != Qnil && merge_fibers != Qfalse;
|
396
433
|
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
434
|
+
if (exclude_threads != Qnil) {
|
435
|
+
Check_Type(exclude_threads, T_ARRAY);
|
436
|
+
assert(profile->exclude_threads_tbl == NULL);
|
437
|
+
profile->exclude_threads_tbl = threads_table_create();
|
438
|
+
for (i = 0; i < RARRAY_LEN(exclude_threads); i++) {
|
439
|
+
VALUE thread = rb_ary_entry(exclude_threads, i);
|
440
|
+
VALUE thread_id = rb_obj_id(thread);
|
441
|
+
st_insert(profile->exclude_threads_tbl, thread_id, Qtrue);
|
442
|
+
}
|
443
|
+
}
|
444
|
+
|
445
|
+
if (include_threads != Qnil) {
|
446
|
+
Check_Type(include_threads, T_ARRAY);
|
447
|
+
assert(profile->include_threads_tbl == NULL);
|
448
|
+
profile->include_threads_tbl = threads_table_create();
|
449
|
+
for (i = 0; i < RARRAY_LEN(include_threads); i++) {
|
450
|
+
VALUE thread = rb_ary_entry(include_threads, i);
|
451
|
+
VALUE thread_id = rb_obj_id(thread);
|
452
|
+
st_insert(profile->include_threads_tbl, thread_id, Qtrue);
|
453
|
+
}
|
402
454
|
}
|
403
455
|
|
404
456
|
return self;
|
@@ -427,7 +479,7 @@ prof_running(VALUE self)
|
|
427
479
|
}
|
428
480
|
|
429
481
|
/* call-seq:
|
430
|
-
start ->
|
482
|
+
start -> self
|
431
483
|
|
432
484
|
Starts recording profile data.*/
|
433
485
|
static VALUE
|
@@ -470,7 +522,7 @@ prof_start(VALUE self)
|
|
470
522
|
}
|
471
523
|
|
472
524
|
/* call-seq:
|
473
|
-
pause ->
|
525
|
+
pause -> self
|
474
526
|
|
475
527
|
Pauses collecting profile data. */
|
476
528
|
static VALUE
|
@@ -493,7 +545,8 @@ prof_pause(VALUE self)
|
|
493
545
|
}
|
494
546
|
|
495
547
|
/* call-seq:
|
496
|
-
resume
|
548
|
+
resume -> self
|
549
|
+
resume(&block) -> self
|
497
550
|
|
498
551
|
Resumes recording profile data.*/
|
499
552
|
static VALUE
|
@@ -559,9 +612,12 @@ prof_stop(VALUE self)
|
|
559
612
|
}
|
560
613
|
|
561
614
|
/* call-seq:
|
562
|
-
profile
|
615
|
+
profile(&block) -> self
|
616
|
+
profile(options, &block) -> self
|
563
617
|
|
564
|
-
Profiles the specified block and returns a RubyProf::
|
618
|
+
Profiles the specified block and returns a RubyProf::Profile
|
619
|
+
object. Arguments are passed to Profile initialize method.
|
620
|
+
*/
|
565
621
|
static VALUE
|
566
622
|
prof_profile(int argc, VALUE *argv, VALUE klass)
|
567
623
|
{
|
@@ -579,14 +635,14 @@ prof_profile(int argc, VALUE *argv, VALUE klass)
|
|
579
635
|
}
|
580
636
|
|
581
637
|
/* call-seq:
|
582
|
-
threads ->
|
638
|
+
threads -> array of RubyProf::Thread
|
583
639
|
|
584
640
|
Returns an array of RubyProf::Thread instances that were executed
|
585
641
|
while the the program was being run. */
|
586
642
|
static VALUE
|
587
643
|
prof_threads(VALUE self)
|
588
644
|
{
|
589
|
-
|
645
|
+
VALUE result = rb_ary_new();
|
590
646
|
prof_profile_t* profile = prof_get_profile(self);
|
591
647
|
st_foreach(profile->threads_tbl, collect_threads, result);
|
592
648
|
return result;
|
data/ext/ruby_prof/ruby_prof.h
CHANGED
data/lib/ruby-prof.rb
CHANGED
@@ -9,27 +9,28 @@ rescue LoadError
|
|
9
9
|
end
|
10
10
|
|
11
11
|
require 'ruby-prof/version'
|
12
|
-
require 'ruby-prof/aggregate_call_info'
|
13
12
|
require 'ruby-prof/call_info'
|
14
|
-
require 'ruby-prof/call_info_visitor'
|
15
13
|
require 'ruby-prof/compatibility'
|
16
14
|
require 'ruby-prof/method_info'
|
17
15
|
require 'ruby-prof/profile'
|
18
16
|
require 'ruby-prof/rack'
|
19
17
|
require 'ruby-prof/thread'
|
20
18
|
|
21
|
-
require 'ruby-prof/printers/abstract_printer'
|
22
|
-
require 'ruby-prof/printers/call_info_printer'
|
23
|
-
require 'ruby-prof/printers/call_stack_printer'
|
24
|
-
require 'ruby-prof/printers/call_tree_printer'
|
25
|
-
require 'ruby-prof/printers/dot_printer'
|
26
|
-
require 'ruby-prof/printers/flat_printer'
|
27
|
-
require 'ruby-prof/printers/flat_printer_with_line_numbers'
|
28
|
-
require 'ruby-prof/printers/graph_html_printer'
|
29
|
-
require 'ruby-prof/printers/graph_printer'
|
30
|
-
require 'ruby-prof/printers/multi_printer'
|
31
|
-
|
32
19
|
module RubyProf
|
20
|
+
autoload :AggregateCallInfo, 'ruby-prof/aggregate_call_info'
|
21
|
+
autoload :CallInfoVisitor, 'ruby-prof/call_info_visitor'
|
22
|
+
|
23
|
+
autoload :AbstractPrinter, 'ruby-prof/printers/abstract_printer'
|
24
|
+
autoload :CallInfoPrinter, 'ruby-prof/printers/call_info_printer'
|
25
|
+
autoload :CallStackPrinter, 'ruby-prof/printers/call_stack_printer'
|
26
|
+
autoload :CallTreePrinter, 'ruby-prof/printers/call_tree_printer'
|
27
|
+
autoload :DotPrinter, 'ruby-prof/printers/dot_printer'
|
28
|
+
autoload :FlatPrinter, 'ruby-prof/printers/flat_printer'
|
29
|
+
autoload :FlatPrinterWithLineNumbers, 'ruby-prof/printers/flat_printer_with_line_numbers'
|
30
|
+
autoload :GraphHtmlPrinter, 'ruby-prof/printers/graph_html_printer'
|
31
|
+
autoload :GraphPrinter, 'ruby-prof/printers/graph_printer'
|
32
|
+
autoload :MultiPrinter, 'ruby-prof/printers/multi_printer'
|
33
|
+
|
33
34
|
# Checks if the user specified the clock mode via
|
34
35
|
# the RUBY_PROF_MEASURE_MODE environment variable
|
35
36
|
def self.figure_measure_mode
|
@@ -40,8 +40,8 @@ module RubyProf
|
|
40
40
|
#
|
41
41
|
# Returns what ruby-prof is measuring. Valid values include:
|
42
42
|
#
|
43
|
-
# *RubyProf::
|
44
|
-
# *RubyProf::
|
43
|
+
# *RubyProf::WALL_TIME - Measure wall time using gettimeofday on Linx and GetLocalTime on Windows. This is default.
|
44
|
+
# *RubyProf::PROCESS_TIME - Measure process time. It is implemented using the clock functions in the C Runtime library.
|
45
45
|
# *RubyProf::CPU_TIME - Measure time using the CPU clock counter. This mode is only supported on Pentium or PowerPC platforms.
|
46
46
|
# *RubyProf::ALLOCATIONS - Measure object allocations. This requires a patched Ruby interpreter.
|
47
47
|
# *RubyProf::MEMORY - Measure memory size. This requires a patched Ruby interpreter.
|
@@ -57,8 +57,8 @@ module RubyProf
|
|
57
57
|
#
|
58
58
|
# Specifies what ruby-prof should measure. Valid values include:
|
59
59
|
#
|
60
|
-
# *RubyProf::
|
61
|
-
# *RubyProf::
|
60
|
+
# *RubyProf::WALL_TIME - Measure wall time using gettimeofday on Linx and GetLocalTime on Windows. This is default.
|
61
|
+
# *RubyProf::PROCESS_TIME - Measure process time. It is implemented using the clock functions in the C Runtime library.
|
62
62
|
# *RubyProf::CPU_TIME - Measure time using the CPU clock counter. This mode is only supported on Pentium or PowerPC platforms.
|
63
63
|
# *RubyProf::ALLOCATIONS - Measure object allocations. This requires a patched Ruby interpreter.
|
64
64
|
# *RubyProf::MEMORY - Measure memory size. This requires a patched Ruby interpreter.
|
@@ -106,7 +106,7 @@ module RubyProf
|
|
106
106
|
|
107
107
|
def self.start
|
108
108
|
ensure_not_running!
|
109
|
-
@profile = Profile.new(
|
109
|
+
@profile = Profile.new(measure_mode: measure_mode, exclude_threads: exclude_threads)
|
110
110
|
enable_gc_stats_if_needed
|
111
111
|
@profile.start
|
112
112
|
end
|
@@ -140,12 +140,13 @@ module RubyProf
|
|
140
140
|
end
|
141
141
|
|
142
142
|
# Profile a block
|
143
|
-
def self.profile(&block)
|
143
|
+
def self.profile(options = {}, &block)
|
144
144
|
ensure_not_running!
|
145
145
|
gc_stat_was_enabled = enable_gc_stats_if_needed
|
146
|
-
|
146
|
+
options = { measure_mode: measure_mode, exclude_threads: exclude_threads }.merge!(options)
|
147
|
+
result = Profile.profile(options, &block)
|
147
148
|
disable_gc_stats_if_needed(gc_stat_was_enabled)
|
148
|
-
|
149
|
+
result
|
149
150
|
end
|
150
151
|
|
151
152
|
|
@@ -115,7 +115,7 @@ module RubyProf
|
|
115
115
|
end
|
116
116
|
|
117
117
|
def to_s
|
118
|
-
"#{self.full_name} (c: #{self.called}, tt: #{self.total_time}, st: #{self.self_time}, ct: #{self.children_time})"
|
118
|
+
"#{self.full_name} (c: #{self.called}, tt: #{self.total_time}, st: #{self.self_time}, wt: #{wait_time}, ct: #{self.children_time})"
|
119
119
|
end
|
120
120
|
|
121
121
|
# remove method from the call graph. should not be called directly.
|
@@ -1,59 +1,58 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
+
require 'fiber'
|
4
|
+
require 'thread'
|
5
|
+
require 'fileutils'
|
6
|
+
|
3
7
|
module RubyProf
|
4
|
-
# Generate profiling information in
|
5
|
-
#
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
# options - Hash table
|
11
|
-
# :min_percent - Number 0 to 100 that specifes the minimum
|
12
|
-
# %self (the methods self time divided by the
|
13
|
-
# overall total time) that a method must take
|
14
|
-
# for it to be printed out in the report.
|
15
|
-
# Default value is 0.
|
16
|
-
#
|
17
|
-
# :print_file - True or false. Specifies if a method's source
|
18
|
-
# file should be printed. Default value if false.
|
19
|
-
#
|
20
|
-
def print(output = STDOUT, options = {})
|
21
|
-
@output = output
|
22
|
-
setup_options(options)
|
8
|
+
# Generate profiling information in callgrind format for use by
|
9
|
+
# kcachegrind and similar tools.
|
10
|
+
#
|
11
|
+
# Note: when profiling for a callgrind printer, one should use the
|
12
|
+
# merge_fibers: true option when creating the profile. Otherwise
|
13
|
+
# each fiber would appear as a separate profile.
|
23
14
|
|
24
|
-
|
25
|
-
|
15
|
+
class CallTreePrinter < AbstractPrinter
|
16
|
+
|
17
|
+
def determine_event_specification_and_value_scale
|
18
|
+
@event_specification = "events: "
|
26
19
|
case RubyProf.measure_mode
|
27
20
|
when RubyProf::PROCESS_TIME
|
28
|
-
@value_scale = RubyProf::CLOCKS_PER_SEC
|
29
|
-
@
|
21
|
+
@value_scale = RubyProf::CLOCKS_PER_SEC
|
22
|
+
@event_specification << 'process_time'
|
30
23
|
when RubyProf::WALL_TIME
|
31
24
|
@value_scale = 1_000_000
|
32
|
-
@
|
25
|
+
@event_specification << 'wall_time'
|
33
26
|
when RubyProf.const_defined?(:CPU_TIME) && RubyProf::CPU_TIME
|
34
27
|
@value_scale = RubyProf.cpu_frequency
|
35
|
-
@
|
28
|
+
@event_specification << 'cpu_time'
|
36
29
|
when RubyProf.const_defined?(:ALLOCATIONS) && RubyProf::ALLOCATIONS
|
37
30
|
@value_scale = 1
|
38
|
-
@
|
31
|
+
@event_specification << 'allocations'
|
39
32
|
when RubyProf.const_defined?(:MEMORY) && RubyProf::MEMORY
|
40
33
|
@value_scale = 1
|
41
|
-
@
|
34
|
+
@event_specification << 'memory'
|
42
35
|
when RubyProf.const_defined?(:GC_RUNS) && RubyProf::GC_RUNS
|
43
36
|
@value_scale = 1
|
44
|
-
@
|
37
|
+
@event_specification << 'gc_runs'
|
45
38
|
when RubyProf.const_defined?(:GC_TIME) && RubyProf::GC_TIME
|
46
39
|
@value_scale = 1000000
|
47
|
-
@
|
40
|
+
@event_specification << 'gc_time'
|
48
41
|
else
|
49
42
|
raise "Unknown measure mode: #{RubyProf.measure_mode}"
|
50
43
|
end
|
51
|
-
|
44
|
+
end
|
52
45
|
|
46
|
+
def print(options = {})
|
47
|
+
setup_options(options)
|
48
|
+
determine_event_specification_and_value_scale
|
53
49
|
print_threads
|
54
50
|
end
|
55
51
|
|
56
52
|
def print_threads
|
53
|
+
remove_subsidiary_files_from_previous_profile_runs
|
54
|
+
# TODO: merge fibers of a given thread here, instead of relying
|
55
|
+
# on the profiler to merge fibers.
|
57
56
|
@result.threads.each do |thread|
|
58
57
|
print_thread(thread)
|
59
58
|
end
|
@@ -68,25 +67,64 @@ module RubyProf
|
|
68
67
|
end
|
69
68
|
|
70
69
|
def print_thread(thread)
|
71
|
-
thread
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
# Now print out the function line number and its self time
|
77
|
-
@output << "#{method.line} #{convert(method.self_time)}\n"
|
78
|
-
|
79
|
-
# Now print out all the children methods
|
80
|
-
method.children.each do |callee|
|
81
|
-
@output << "cfl=#{file(callee.target)}\n"
|
82
|
-
@output << "cfn=#{method_name(callee.target)}\n"
|
83
|
-
@output << "calls=#{callee.called} #{callee.line}\n"
|
84
|
-
|
85
|
-
# Print out total times here!
|
86
|
-
@output << "#{callee.line} #{convert(callee.total_time)}\n"
|
70
|
+
File.open(file_path_for_thread(thread), "w") do |f|
|
71
|
+
print_headers(f, thread)
|
72
|
+
thread.methods.reverse_each do |method|
|
73
|
+
print_method(f, method)
|
87
74
|
end
|
88
|
-
@output << "\n"
|
89
75
|
end
|
90
|
-
end
|
91
|
-
|
92
|
-
|
76
|
+
end
|
77
|
+
|
78
|
+
def path
|
79
|
+
@options[:path] || "."
|
80
|
+
end
|
81
|
+
|
82
|
+
def base_name
|
83
|
+
@options[:profile] || "profile"
|
84
|
+
end
|
85
|
+
|
86
|
+
def remove_subsidiary_files_from_previous_profile_runs
|
87
|
+
pattern = [base_name, "callgrind.out", $$, "*"].join(".")
|
88
|
+
files = Dir.glob(File.join(path, pattern))
|
89
|
+
FileUtils.rm_f(files)
|
90
|
+
end
|
91
|
+
|
92
|
+
def file_name_for_thread(thread)
|
93
|
+
if thread.fiber_id == Fiber.current.object_id
|
94
|
+
[base_name, "callgrind.out", $$].join(".")
|
95
|
+
else
|
96
|
+
[base_name, "callgrind.out", $$, thread.fiber_id].join(".")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def file_path_for_thread(thread)
|
101
|
+
File.join(path, file_name_for_thread(thread))
|
102
|
+
end
|
103
|
+
|
104
|
+
def print_headers(output, thread)
|
105
|
+
output << "#{@event_specification}\n\n"
|
106
|
+
# this doesn't work. kcachegrind does not fully support the spec.
|
107
|
+
# output << "thread: #{thread.id}\n\n"
|
108
|
+
end
|
109
|
+
|
110
|
+
def print_method(output, method)
|
111
|
+
# Print out the file and method name
|
112
|
+
output << "fl=#{file(method)}\n"
|
113
|
+
output << "fn=#{method_name(method)}\n"
|
114
|
+
|
115
|
+
# Now print out the function line number and its self time
|
116
|
+
output << "#{method.line} #{convert(method.self_time)}\n"
|
117
|
+
|
118
|
+
# Now print out all the children methods
|
119
|
+
method.children.each do |callee|
|
120
|
+
output << "cfl=#{file(callee.target)}\n"
|
121
|
+
output << "cfn=#{method_name(callee.target)}\n"
|
122
|
+
output << "calls=#{callee.called} #{callee.line}\n"
|
123
|
+
|
124
|
+
# Print out total times here!
|
125
|
+
output << "#{callee.line} #{convert(callee.total_time)}\n"
|
126
|
+
end
|
127
|
+
output << "\n"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|