ruby-prof 0.15.9 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +27 -1
  3. data/README.rdoc +83 -31
  4. data/bin/ruby-prof +4 -4
  5. data/doc/LICENSE.html +1 -1
  6. data/doc/README_rdoc.html +92 -33
  7. data/doc/Rack.html +1 -1
  8. data/doc/Rack/RubyProf.html +17 -14
  9. data/doc/RubyProf.html +30 -29
  10. data/doc/RubyProf/AbstractPrinter.html +1 -1
  11. data/doc/RubyProf/AggregateCallInfo.html +1 -1
  12. data/doc/RubyProf/CallInfo.html +1 -1
  13. data/doc/RubyProf/CallInfoPrinter.html +1 -1
  14. data/doc/RubyProf/CallInfoVisitor.html +1 -1
  15. data/doc/RubyProf/CallStackPrinter.html +1 -1
  16. data/doc/RubyProf/CallTreePrinter.html +349 -67
  17. data/doc/RubyProf/Cmd.html +5 -5
  18. data/doc/RubyProf/DotPrinter.html +2 -2
  19. data/doc/RubyProf/FlatPrinter.html +1 -1
  20. data/doc/RubyProf/FlatPrinterWithLineNumbers.html +1 -1
  21. data/doc/RubyProf/GraphHtmlPrinter.html +1 -1
  22. data/doc/RubyProf/GraphPrinter.html +1 -1
  23. data/doc/RubyProf/MethodInfo.html +2 -2
  24. data/doc/RubyProf/MultiPrinter.html +11 -9
  25. data/doc/RubyProf/Profile.html +94 -44
  26. data/doc/RubyProf/ProfileTask.html +1 -1
  27. data/doc/RubyProf/Thread.html +43 -1
  28. data/doc/created.rid +16 -16
  29. data/doc/examples/flat_txt.html +1 -1
  30. data/doc/examples/graph_html.html +1 -1
  31. data/doc/examples/graph_txt.html +3 -3
  32. data/doc/index.html +85 -30
  33. data/doc/js/navigation.js.gz +0 -0
  34. data/doc/js/search_index.js +1 -1
  35. data/doc/js/search_index.js.gz +0 -0
  36. data/doc/js/searcher.js +2 -2
  37. data/doc/js/searcher.js.gz +0 -0
  38. data/doc/table_of_contents.html +117 -68
  39. data/examples/cachegrind.out.1 +114 -0
  40. data/examples/cachegrind.out.1.32313213 +114 -0
  41. data/examples/graph.txt +1 -1
  42. data/ext/ruby_prof/extconf.rb +6 -2
  43. data/ext/ruby_prof/rp_measure_cpu_time.c +29 -31
  44. data/ext/ruby_prof/rp_method.c +1 -1
  45. data/ext/ruby_prof/rp_thread.c +57 -52
  46. data/ext/ruby_prof/ruby_prof.c +122 -66
  47. data/ext/ruby_prof/ruby_prof.h +2 -0
  48. data/lib/ruby-prof.rb +14 -13
  49. data/lib/ruby-prof/assets/call_stack_printer.js.html +1 -1
  50. data/lib/ruby-prof/compatibility.rb +9 -8
  51. data/lib/ruby-prof/method_info.rb +1 -1
  52. data/lib/ruby-prof/printers/call_tree_printer.rb +88 -50
  53. data/lib/ruby-prof/printers/dot_printer.rb +1 -1
  54. data/lib/ruby-prof/printers/multi_printer.rb +6 -4
  55. data/lib/ruby-prof/profile.rb +0 -1
  56. data/lib/ruby-prof/rack.rb +53 -16
  57. data/lib/ruby-prof/thread.rb +11 -0
  58. data/lib/ruby-prof/version.rb +1 -1
  59. data/test/exclude_threads_test.rb +2 -3
  60. data/test/fiber_test.rb +21 -7
  61. data/test/measure_cpu_time_test.rb +84 -24
  62. data/test/multi_printer_test.rb +5 -4
  63. data/test/pause_resume_test.rb +7 -7
  64. data/test/printers_test.rb +6 -4
  65. data/test/rack_test.rb +26 -1
  66. data/test/test_helper.rb +28 -3
  67. data/test/thread_test.rb +1 -0
  68. metadata +5 -3
@@ -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