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
@@ -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
- double measurement = profile->measurer->measure();
114
-
115
- if (!profile->last_thread_data || profile->last_thread_data->fiber_id != fiber_id)
116
- thread_data = switch_thread(profile, Qnil, fiber_id);
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
- thread_data = profile->last_thread_data;
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
- return;
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 (st_lookup(profile->exclude_threads_tbl, (st_data_t) thread_id, 0))
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
- return;
218
+ return;
207
219
  }
208
220
 
209
- /* Was there a context switch? */
210
- if (!profile->last_thread_data || profile->last_thread_data->fiber_id != fiber_id)
211
- thread_data = switch_thread(profile, thread_id, fiber_id);
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
- thread_data = profile->last_thread_data;
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
- rb_ary_push(threads_array, prof_thread_wrap(thread_data));
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
- st_foreach(profile->threads_tbl, mark_threads, 0);
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
- profile->last_thread_data = NULL;
346
+ profile->last_thread_data = NULL;
330
347
 
331
- threads_table_free(profile->threads_tbl);
348
+ threads_table_free(profile->threads_tbl);
332
349
  profile->threads_tbl = NULL;
333
350
 
334
- st_free_table(profile->exclude_threads_tbl);
335
- profile->exclude_threads_tbl = NULL;
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
- xfree(profile->measurer);
338
- profile->measurer = NULL;
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
- profile->exclude_threads_tbl = threads_table_create();
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
- RubyProf::Profile.new(mode, exclude_threads) -> instance
357
-
358
- Returns a new profiler.
359
-
360
- == Parameters
361
- mode:: Measure mode (optional). Specifies the profile measure mode. If not specified, defaults
362
- to RubyProf::WALL_TIME.
363
- exclude_threads:: Threads to exclude from the profiling results (optional). */
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 mode;
369
- prof_measure_mode_t measurer = MEASURE_WALL_TIME;
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", &mode, &exclude_threads))
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
- case 1:
382
- {
383
- measurer = (prof_measure_mode_t)NUM2INT(mode);
384
- exclude_threads = rb_ary_new();
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
- profile->measurer = prof_get_measurer(measurer);
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
- for (i = 0; i < RARRAY_LEN(exclude_threads); i++)
398
- {
399
- VALUE thread = rb_ary_entry(exclude_threads, i);
400
- VALUE thread_id = rb_obj_id(thread);
401
- st_insert(profile->exclude_threads_tbl, thread_id, Qtrue);
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 -> RubyProf
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 -> RubyProf
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 {block} -> RubyProf
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 {block} -> RubyProf::Result
615
+ profile(&block) -> self
616
+ profile(options, &block) -> self
563
617
 
564
- Profiles the specified block and returns a RubyProf::Result object. */
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 -> Array of RubyProf::Thread
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
- VALUE result = rb_ary_new();
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;
@@ -46,8 +46,10 @@ typedef struct
46
46
  VALUE threads;
47
47
  st_table* threads_tbl;
48
48
  st_table* exclude_threads_tbl;
49
+ st_table* include_threads_tbl;
49
50
  thread_data_t* last_thread_data;
50
51
  double measurement_at_pause_resume;
52
+ int merge_fibers;
51
53
  } prof_profile_t;
52
54
 
53
55
 
@@ -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
@@ -61,7 +61,7 @@
61
61
 
62
62
  function setThresholdLI(li, threshold) {
63
63
  var img = li.firstChild;
64
- var text = img.nextSibling;
64
+ var text = img.nextSibling.firstChild;
65
65
  var ul = findUlChild(li);
66
66
 
67
67
  var visible = aboveThreshold(text.nodeValue, threshold) ? 1 : 0;
@@ -40,8 +40,8 @@ module RubyProf
40
40
  #
41
41
  # Returns what ruby-prof is measuring. Valid values include:
42
42
  #
43
- # *RubyProf::PROCESS_TIME - Measure process time. This is default. It is implemented using the clock functions in the C Runtime library.
44
- # *RubyProf::WALL_TIME - Measure wall time using gettimeofday on Linx and GetLocalTime on Windows
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::PROCESS_TIME - Measure process time. This is default. It is implemented using the clock functions in the C Runtime library.
61
- # *RubyProf::WALL_TIME - Measure wall time using gettimeofday on Linx and GetLocalTime on Windows
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(self.measure_mode, self.exclude_threads)
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
- res = Profile.profile(self.measure_mode, self.exclude_threads, &block)
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
- res
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 calltree format
5
- # for use by kcachegrind and similar tools.
6
-
7
- class CallTreePrinter < AbstractPrinter
8
- # Specify print options.
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
- # add a header - this information is somewhat arbitrary
25
- @output << "events: "
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
- @output << 'process_time'
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
- @output << 'wall_time'
25
+ @event_specification << 'wall_time'
33
26
  when RubyProf.const_defined?(:CPU_TIME) && RubyProf::CPU_TIME
34
27
  @value_scale = RubyProf.cpu_frequency
35
- @output << 'cpu_time'
28
+ @event_specification << 'cpu_time'
36
29
  when RubyProf.const_defined?(:ALLOCATIONS) && RubyProf::ALLOCATIONS
37
30
  @value_scale = 1
38
- @output << 'allocations'
31
+ @event_specification << 'allocations'
39
32
  when RubyProf.const_defined?(:MEMORY) && RubyProf::MEMORY
40
33
  @value_scale = 1
41
- @output << 'memory'
34
+ @event_specification << 'memory'
42
35
  when RubyProf.const_defined?(:GC_RUNS) && RubyProf::GC_RUNS
43
36
  @value_scale = 1
44
- @output << 'gc_runs'
37
+ @event_specification << 'gc_runs'
45
38
  when RubyProf.const_defined?(:GC_TIME) && RubyProf::GC_TIME
46
39
  @value_scale = 1000000
47
- @output << 'gc_time'
40
+ @event_specification << 'gc_time'
48
41
  else
49
42
  raise "Unknown measure mode: #{RubyProf.measure_mode}"
50
43
  end
51
- @output << "\n\n"
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.methods.reverse_each do |method|
72
- # Print out the file and method name
73
- @output << "fl=#{file(method)}\n"
74
- @output << "fn=#{method_name(method)}\n"
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 #end print_methods
91
- end # end class
92
- end # end packages
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