airbnb-ruby-prof 0.0.1

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 (116) hide show
  1. data/CHANGES +483 -0
  2. data/LICENSE +25 -0
  3. data/README.rdoc +426 -0
  4. data/Rakefile +51 -0
  5. data/bin/ruby-prof +279 -0
  6. data/bin/ruby-prof-check-trace +45 -0
  7. data/examples/flat.txt +50 -0
  8. data/examples/graph.dot +84 -0
  9. data/examples/graph.html +823 -0
  10. data/examples/graph.txt +139 -0
  11. data/examples/multi.flat.txt +23 -0
  12. data/examples/multi.graph.html +760 -0
  13. data/examples/multi.grind.dat +114 -0
  14. data/examples/multi.stack.html +547 -0
  15. data/examples/stack.html +547 -0
  16. data/ext/ruby_prof/extconf.rb +67 -0
  17. data/ext/ruby_prof/rp_call_info.c +374 -0
  18. data/ext/ruby_prof/rp_call_info.h +59 -0
  19. data/ext/ruby_prof/rp_fast_call_tree_printer.c +247 -0
  20. data/ext/ruby_prof/rp_fast_call_tree_printer.h +10 -0
  21. data/ext/ruby_prof/rp_measure.c +71 -0
  22. data/ext/ruby_prof/rp_measure.h +56 -0
  23. data/ext/ruby_prof/rp_measure_allocations.c +74 -0
  24. data/ext/ruby_prof/rp_measure_cpu_time.c +134 -0
  25. data/ext/ruby_prof/rp_measure_gc_runs.c +71 -0
  26. data/ext/ruby_prof/rp_measure_gc_time.c +58 -0
  27. data/ext/ruby_prof/rp_measure_memory.c +75 -0
  28. data/ext/ruby_prof/rp_measure_process_time.c +69 -0
  29. data/ext/ruby_prof/rp_measure_wall_time.c +43 -0
  30. data/ext/ruby_prof/rp_method.c +717 -0
  31. data/ext/ruby_prof/rp_method.h +79 -0
  32. data/ext/ruby_prof/rp_stack.c +221 -0
  33. data/ext/ruby_prof/rp_stack.h +81 -0
  34. data/ext/ruby_prof/rp_thread.c +312 -0
  35. data/ext/ruby_prof/rp_thread.h +36 -0
  36. data/ext/ruby_prof/ruby_prof.c +800 -0
  37. data/ext/ruby_prof/ruby_prof.h +64 -0
  38. data/ext/ruby_prof/vc/ruby_prof.sln +32 -0
  39. data/ext/ruby_prof/vc/ruby_prof_18.vcxproj +108 -0
  40. data/ext/ruby_prof/vc/ruby_prof_19.vcxproj +110 -0
  41. data/ext/ruby_prof/vc/ruby_prof_20.vcxproj +110 -0
  42. data/lib/ruby-prof.rb +63 -0
  43. data/lib/ruby-prof/aggregate_call_info.rb +76 -0
  44. data/lib/ruby-prof/assets/call_stack_printer.css.html +117 -0
  45. data/lib/ruby-prof/assets/call_stack_printer.js.html +385 -0
  46. data/lib/ruby-prof/assets/call_stack_printer.png +0 -0
  47. data/lib/ruby-prof/assets/flame_graph_printer.lib.css.html +149 -0
  48. data/lib/ruby-prof/assets/flame_graph_printer.lib.js.html +707 -0
  49. data/lib/ruby-prof/assets/flame_graph_printer.page.js.html +56 -0
  50. data/lib/ruby-prof/assets/flame_graph_printer.tmpl.html.erb +39 -0
  51. data/lib/ruby-prof/call_info.rb +111 -0
  52. data/lib/ruby-prof/call_info_visitor.rb +40 -0
  53. data/lib/ruby-prof/compatibility.rb +186 -0
  54. data/lib/ruby-prof/method_info.rb +109 -0
  55. data/lib/ruby-prof/printers/abstract_printer.rb +85 -0
  56. data/lib/ruby-prof/printers/call_info_printer.rb +41 -0
  57. data/lib/ruby-prof/printers/call_stack_printer.rb +260 -0
  58. data/lib/ruby-prof/printers/call_tree_printer.rb +130 -0
  59. data/lib/ruby-prof/printers/dot_printer.rb +132 -0
  60. data/lib/ruby-prof/printers/fast_call_tree_printer.rb +87 -0
  61. data/lib/ruby-prof/printers/flame_graph_html_printer.rb +59 -0
  62. data/lib/ruby-prof/printers/flame_graph_json_printer.rb +157 -0
  63. data/lib/ruby-prof/printers/flat_printer.rb +70 -0
  64. data/lib/ruby-prof/printers/flat_printer_with_line_numbers.rb +64 -0
  65. data/lib/ruby-prof/printers/graph_html_printer.rb +244 -0
  66. data/lib/ruby-prof/printers/graph_printer.rb +116 -0
  67. data/lib/ruby-prof/printers/multi_printer.rb +58 -0
  68. data/lib/ruby-prof/profile.rb +22 -0
  69. data/lib/ruby-prof/profile/exclude_common_methods.rb +201 -0
  70. data/lib/ruby-prof/rack.rb +95 -0
  71. data/lib/ruby-prof/task.rb +147 -0
  72. data/lib/ruby-prof/thread.rb +35 -0
  73. data/lib/ruby-prof/version.rb +4 -0
  74. data/lib/ruby-prof/walker.rb +95 -0
  75. data/lib/unprof.rb +10 -0
  76. data/ruby-prof.gemspec +56 -0
  77. data/test/aggregate_test.rb +136 -0
  78. data/test/basic_test.rb +128 -0
  79. data/test/block_test.rb +74 -0
  80. data/test/call_info_test.rb +78 -0
  81. data/test/call_info_visitor_test.rb +31 -0
  82. data/test/duplicate_names_test.rb +32 -0
  83. data/test/dynamic_method_test.rb +55 -0
  84. data/test/enumerable_test.rb +21 -0
  85. data/test/exceptions_test.rb +16 -0
  86. data/test/exclude_methods_test.rb +146 -0
  87. data/test/exclude_threads_test.rb +53 -0
  88. data/test/fiber_test.rb +79 -0
  89. data/test/issue137_test.rb +63 -0
  90. data/test/line_number_test.rb +71 -0
  91. data/test/measure_allocations_test.rb +26 -0
  92. data/test/measure_cpu_time_test.rb +213 -0
  93. data/test/measure_gc_runs_test.rb +32 -0
  94. data/test/measure_gc_time_test.rb +36 -0
  95. data/test/measure_memory_test.rb +33 -0
  96. data/test/measure_process_time_test.rb +63 -0
  97. data/test/measure_wall_time_test.rb +255 -0
  98. data/test/module_test.rb +45 -0
  99. data/test/multi_measure_test.rb +38 -0
  100. data/test/multi_printer_test.rb +83 -0
  101. data/test/no_method_class_test.rb +15 -0
  102. data/test/pause_resume_test.rb +166 -0
  103. data/test/prime.rb +54 -0
  104. data/test/printers_test.rb +255 -0
  105. data/test/printing_recursive_graph_test.rb +127 -0
  106. data/test/rack_test.rb +93 -0
  107. data/test/recursive_test.rb +212 -0
  108. data/test/singleton_test.rb +38 -0
  109. data/test/stack_printer_test.rb +65 -0
  110. data/test/stack_test.rb +138 -0
  111. data/test/start_stop_test.rb +112 -0
  112. data/test/test_helper.rb +264 -0
  113. data/test/thread_test.rb +187 -0
  114. data/test/unique_call_path_test.rb +202 -0
  115. data/test/yarv_test.rb +55 -0
  116. metadata +211 -0
@@ -0,0 +1,56 @@
1
+ <script type="text/javascript">
2
+
3
+ // Flamegraph
4
+
5
+ var cellHeight = 14;
6
+ var height = cellHeight * data.depth + 40;
7
+
8
+ var flameGraph = d3.flameGraph()
9
+ .width(1250)
10
+ .height(height)
11
+ .cellHeight(cellHeight)
12
+ .transitionDuration(750)
13
+ .transitionEase('cubic-in-out')
14
+ .sort(true)
15
+ .title("");
16
+
17
+ // Tooltip
18
+
19
+ var tip = d3.tip()
20
+ .direction("s")
21
+ .offset([8, 0])
22
+ .attr('class', 'd3-flame-graph-tip')
23
+ .html(function(d) {
24
+ return d.name + " (" +
25
+ d3.round(d.called, 0) + "; " +
26
+ d3.round(100 * d.dx, 3) + "%)";
27
+ });
28
+
29
+ flameGraph.tooltip(tip);
30
+
31
+ // Searching and Zooming
32
+
33
+ document.getElementById("form").addEventListener("submit", function(event) {
34
+ event.preventDefault();
35
+ search();
36
+ });
37
+
38
+ function search() {
39
+ var term = document.getElementById("term").value;
40
+ flameGraph.search(term);
41
+ }
42
+
43
+ function clear() {
44
+ document.getElementById('term').value = '';
45
+ flameGraph.clear();
46
+ }
47
+
48
+ function resetZoom() {
49
+ flameGraph.resetZoom();
50
+ }
51
+
52
+ d3.select("#chart")
53
+ .datum(data.root)
54
+ .call(flameGraph);
55
+
56
+ </script>
@@ -0,0 +1,39 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+
8
+ <%= css_libraries_html %>
9
+
10
+ <title>Flame Graph</title>
11
+ </head>
12
+ <body>
13
+ <div class="container">
14
+ <div class="header clearfix">
15
+ <nav>
16
+ <div class="pull-right">
17
+ <form class="form-inline" id="form">
18
+ <a class="btn" href="javascript: resetZoom();">Reset zoom</a>
19
+ <a class="btn" href="javascript: clear();">Clear</a>
20
+ <div class="form-group">
21
+ <input type="text" class="form-control" id="term">
22
+ </div>
23
+ <a class="btn btn-primary" href="javascript: search();">Search</a>
24
+ </form>
25
+ </div>
26
+ </nav>
27
+ <h3 class="text-muted">Flame Graph</h3>
28
+ </div>
29
+ <div id="chart">
30
+ </div>
31
+ </div>
32
+
33
+ <%= js_libraries_html %>
34
+
35
+ <%= js_data_html %>
36
+ <%= js_code_html %>
37
+
38
+ </body>
39
+ </html>
@@ -0,0 +1,111 @@
1
+ # encoding: utf-8
2
+
3
+ module RubyProf
4
+ class CallInfo
5
+ # part of this class is defined in C code.
6
+ # it provides the following attributes pertaining to tree structure:
7
+ # depth: tree level (0 == root)
8
+ # parent: parent call info (can be nil)
9
+ # children: array of call info children (can be empty)
10
+ # target: method info (containing an array of call infos)
11
+
12
+ def measure_values_memoized
13
+ @measure_values ||= measure_values
14
+ end
15
+
16
+ def total_time(i = 0)
17
+ measure_values_memoized[i][0]
18
+ end
19
+
20
+ def self_time(i = 0)
21
+ measure_values_memoized[i][1]
22
+ end
23
+
24
+ def wait_time(i = 0)
25
+ measure_values_memoized[i][2]
26
+ end
27
+
28
+ def children_time(i = 0)
29
+ children.inject(0) do |sum, call_info|
30
+ sum += call_info.total_time(i)
31
+ end
32
+ end
33
+
34
+ def stack
35
+ @stack ||= begin
36
+ methods = Array.new
37
+ call_info = self
38
+
39
+ while call_info
40
+ methods << call_info.target
41
+ call_info = call_info.parent
42
+ end
43
+ methods.reverse
44
+ end
45
+ end
46
+
47
+ def call_sequence
48
+ @call_sequence ||= begin
49
+ stack.map {|method| method.full_name}.join('->')
50
+ end
51
+ end
52
+
53
+ def root?
54
+ self.parent.nil?
55
+ end
56
+
57
+ def descendent_of(other)
58
+ p = self.parent
59
+ while p && p != other && p.depth > other.depth
60
+ p = p.parent
61
+ end
62
+ p == other
63
+ end
64
+
65
+ def self.roots_of(call_infos)
66
+ roots = []
67
+ sorted = call_infos.sort_by(&:depth).reverse
68
+ while call_info = sorted.shift
69
+ roots << call_info unless sorted.any?{|p| call_info.descendent_of(p)}
70
+ end
71
+ roots
72
+ end
73
+
74
+ def to_s
75
+ "#{target.full_name} (c: #{called}, tt: #{total_time}, st: #{self_time}, ct: #{children_time})"
76
+ end
77
+
78
+ def inspect
79
+ super + "(#{target.full_name}, d: #{depth}, c: #{called}, tt: #{total_time}, st: #{self_time}, ct: #{children_time})"
80
+ end
81
+
82
+ # find a specific call in list of children. returns nil if not found.
83
+ # note: there can't be more than one child with a given target method. in other words:
84
+ # x.children.grep{|y|y.target==m}.size <= 1 for all method infos m and call infos x
85
+ def find_call(other)
86
+ matching = children.select { |kid| kid.target == other.target }
87
+ raise "inconsistent call tree" unless matching.size <= 1
88
+ matching.first
89
+ end
90
+
91
+ # merge two call trees. adds self, wait, and total time of other to self and merges children of other into children of self.
92
+ def merge_call_tree(other)
93
+ # $stderr.puts "merging #{self}\nand #{other}"
94
+ self.called += other.called
95
+ add_self_time(other)
96
+ add_wait_time(other)
97
+ add_total_time(other)
98
+ other.children.each do |other_kid|
99
+ if kid = find_call(other_kid)
100
+ # $stderr.puts "merging kids"
101
+ kid.merge_call_tree(other_kid)
102
+ else
103
+ other_kid.parent = self
104
+ children << other_kid
105
+ end
106
+ end
107
+ other.children.clear
108
+ other.target.call_infos.delete(other)
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,40 @@
1
+ # The call info visitor class does a depth-first traversal across a
2
+ # list of method infos. At each call_info node, the visitor executes
3
+ # the block provided in the #visit method. The block is passed two
4
+ # parameters, the event and the call_info instance. Event will be
5
+ # either :enter or :exit.
6
+ #
7
+ # visitor = RubyProf::CallInfoVisitor.new(result.threads.first.top_call_infos)
8
+ #
9
+ # method_names = Array.new
10
+ #
11
+ # visitor.visit do |call_info, event|
12
+ # method_names << call_info.target.full_name if event == :enter
13
+ # end
14
+ #
15
+ # puts method_names
16
+
17
+ module RubyProf
18
+ class CallInfoVisitor
19
+
20
+ def initialize(call_infos)
21
+ @call_infos = CallInfo.roots_of(call_infos)
22
+ end
23
+
24
+ def visit(&block)
25
+ @call_infos.each do |call_info|
26
+ visit_call_info(call_info, &block)
27
+ end
28
+ end
29
+
30
+ private
31
+ def visit_call_info(call_info, &block)
32
+ yield call_info, :enter
33
+ call_info.children.each do |child|
34
+ visit_call_info(child, &block)
35
+ end
36
+ yield call_info, :exit
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,186 @@
1
+ # encoding: utf-8
2
+
3
+ # These methods are here for backwards compatability with previous RubyProf releases
4
+ module RubyProf
5
+ # Measurements
6
+ def self.cpu_frequency
7
+ Measure::CpuTime.frequency
8
+ end
9
+
10
+ def self.measure_allocations
11
+ Measure::Allocations.measure
12
+ end
13
+
14
+ def self.measure_cpu_time
15
+ Measure::CpuTime.measure
16
+ end
17
+
18
+ def self.measure_gc_runs
19
+ Measure::GcRuns.measure
20
+ end
21
+
22
+ def self.measure_gc_time
23
+ Measure::GcTime.measure
24
+ end
25
+
26
+ def self.measure_memory
27
+ Measure::Memory.measure
28
+ end
29
+
30
+ def self.measure_process_time
31
+ Measure::ProcessTime.measure
32
+ end
33
+
34
+ def self.measure_wall_time
35
+ Measure::WallTime.measure
36
+ end
37
+
38
+ # call-seq:
39
+ # measure_mode -> measure_mode
40
+ #
41
+ # Returns what ruby-prof is measuring. Valid values include:
42
+ #
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
+ # *RubyProf::CPU_TIME - Measure time using the CPU clock counter. This mode is only supported on Pentium or PowerPC platforms.
46
+ # *RubyProf::ALLOCATIONS - Measure object allocations. This requires a patched Ruby interpreter.
47
+ # *RubyProf::MEMORY - Measure memory size. This requires a patched Ruby interpreter.
48
+ # *RubyProf::GC_RUNS - Measure number of garbage collections. This requires a patched Ruby interpreter.
49
+ # *RubyProf::GC_TIME - Measure time spent doing garbage collection. This requires a patched Ruby interpreter.*/
50
+
51
+ def self.measure_mode
52
+ measure_modes.first
53
+ end
54
+
55
+ # call-seq:
56
+ # measure_mode=value -> void
57
+ #
58
+ # Specifies what ruby-prof should measure. Valid values include:
59
+ #
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
+ # *RubyProf::CPU_TIME - Measure time using the CPU clock counter. This mode is only supported on Pentium or PowerPC platforms.
63
+ # *RubyProf::ALLOCATIONS - Measure object allocations. This requires a patched Ruby interpreter.
64
+ # *RubyProf::MEMORY - Measure memory size. This requires a patched Ruby interpreter.
65
+ # *RubyProf::GC_RUNS - Measure number of garbage collections. This requires a patched Ruby interpreter.
66
+ # *RubyProf::GC_TIME - Measure time spent doing garbage collection. This requires a patched Ruby interpreter.*/
67
+ def self.measure_mode=(value)
68
+ self.measure_modes = [value]
69
+ end
70
+
71
+ def self.measure_modes
72
+ @measure_modes ||= [RubyProf::WALL_TIME]
73
+ end
74
+
75
+ def self.measure_modes=(value)
76
+ @measure_modes = value
77
+ end
78
+
79
+ def self.measure_mode_string
80
+ case measure_mode
81
+ when WALL_TIME then "wall_time"
82
+ when CPU_TIME then "cpu_time"
83
+ when PROCESS_TIME then "process_time_time"
84
+ when ALLOCATIONS then "allocations"
85
+ when MEMORY then "memory"
86
+ when GC_TIME then "gc_time"
87
+ when GC_RUNS then "gc_runs"
88
+ end
89
+ end
90
+
91
+ # call-seq:
92
+ # exclude_threads -> exclude_threads
93
+ #
94
+ # Returns threads ruby-prof should exclude from profiling
95
+
96
+ def self.exclude_threads
97
+ @exclude_threads ||= Array.new
98
+ end
99
+
100
+ # call-seq:
101
+ # exclude_threads= -> void
102
+ #
103
+ # Specifies what threads ruby-prof should exclude from profiling
104
+
105
+ def self.exclude_threads=(value)
106
+ @exclude_threads = value
107
+ end
108
+
109
+ # Profiling
110
+ def self.start_script(script)
111
+ start
112
+ load script
113
+ end
114
+
115
+ def self.start
116
+ ensure_not_running!
117
+ @profile = Profile.new(measure_modes: measure_modes, exclude_threads: exclude_threads)
118
+ enable_gc_stats_if_needed
119
+ @profile.start
120
+ end
121
+
122
+ def self.pause
123
+ ensure_running!
124
+ disable_gc_stats_if_needed
125
+ @profile.pause
126
+ end
127
+
128
+ def self.running?
129
+ if defined?(@profile) and @profile
130
+ @profile.running?
131
+ else
132
+ false
133
+ end
134
+ end
135
+
136
+ def self.resume
137
+ ensure_running!
138
+ enable_gc_stats_if_needed
139
+ @profile.resume
140
+ end
141
+
142
+ def self.stop
143
+ ensure_running!
144
+ result = @profile.stop
145
+ disable_gc_stats_if_needed
146
+ @profile = nil
147
+ result
148
+ end
149
+
150
+ # Profile a block
151
+ def self.profile(options = {}, &block)
152
+ ensure_not_running!
153
+ gc_stat_was_enabled = enable_gc_stats_if_needed
154
+ options = { measure_modes: [measure_mode], exclude_threads: exclude_threads }.merge!(options)
155
+ result = Profile.profile(options, &block)
156
+ disable_gc_stats_if_needed(gc_stat_was_enabled)
157
+ result
158
+ end
159
+
160
+
161
+ private
162
+ def self.ensure_running!
163
+ raise(RuntimeError, "RubyProf.start was not yet called") unless running?
164
+ end
165
+
166
+ def self.ensure_not_running!
167
+ raise(RuntimeError, "RubyProf is already running") if running?
168
+ end
169
+
170
+ # for GC.allocated_size to work GC statistics should be enabled
171
+ def self.enable_gc_stats_if_needed
172
+ if measure_mode_requires_gc_stats_enabled?
173
+ @gc_stat_was_enabled = GC.enable_stats
174
+ end
175
+ end
176
+
177
+ def self.disable_gc_stats_if_needed(was_enabled=nil)
178
+ was_enabled ||= defined?(@gc_stat_was_enabled) && @gc_stat_was_enabled
179
+ GC.disable_stats if measure_mode_requires_gc_stats_enabled? && !was_enabled
180
+ end
181
+
182
+ def self.measure_mode_requires_gc_stats_enabled?
183
+ GC.respond_to?(:enable_stats) &&
184
+ [RubyProf::MEMORY, RubyProf::GC_TIME, RubyProf::GC_RUNS].include?(measure_mode)
185
+ end
186
+ end
@@ -0,0 +1,109 @@
1
+ # encoding: utf-8
2
+
3
+ module RubyProf
4
+ class MethodInfo
5
+ include Comparable
6
+
7
+ def <=>(other)
8
+ if self.total_time < other.total_time
9
+ -1
10
+ elsif self.total_time > other.total_time
11
+ 1
12
+ elsif self.min_depth < other.min_depth
13
+ 1
14
+ elsif self.min_depth > other.min_depth
15
+ -1
16
+ else
17
+ self.full_name <=> other.full_name
18
+ end
19
+ end
20
+
21
+ def called
22
+ @called ||= begin
23
+ call_infos.inject(0) do |sum, call_info|
24
+ sum + call_info.called
25
+ end
26
+ end
27
+ end
28
+
29
+ def total_time(i = 0)
30
+ @total_time ||= begin
31
+ call_infos.inject(0) do |sum, call_info|
32
+ sum += call_info.total_time(i) if !call_info.recursive?
33
+ sum
34
+ end
35
+ end
36
+ end
37
+
38
+ def self_time(i = 0)
39
+ @self_time ||= []
40
+ @self_time[i] ||= self_time_unmemoized(i)
41
+ end
42
+
43
+ def wait_time(i = 0)
44
+ @wait_time ||= begin
45
+ call_infos.inject(0) do |sum, call_info|
46
+ sum += call_info.wait_time(i) if !call_info.recursive?
47
+ sum
48
+ end
49
+ end
50
+ end
51
+
52
+ def children_time(i = 0)
53
+ @children_time ||= begin
54
+ call_infos.inject(0) do |sum, call_info|
55
+ sum += call_info.children_time(i) if !call_info.recursive?
56
+ sum
57
+ end
58
+ end
59
+ end
60
+
61
+ def min_depth
62
+ @min_depth ||= call_infos.map(&:depth).min
63
+ end
64
+
65
+ def root?
66
+ @root ||= begin
67
+ call_infos.find do |call_info|
68
+ not call_info.root?
69
+ end.nil?
70
+ end
71
+ end
72
+
73
+ def children
74
+ @children ||= call_infos.map(&:children).flatten
75
+ end
76
+
77
+ def parents
78
+ @parents ||= call_infos.map(&:parent)
79
+ end
80
+
81
+ def aggregate_parents
82
+ # group call infos based on their parents
83
+ groups = self.call_infos.each_with_object({}) do |call_info, hash|
84
+ key = call_info.parent ? call_info.parent.target : self
85
+ (hash[key] ||= []) << call_info
86
+ end
87
+
88
+ groups.map do |key, value|
89
+ AggregateCallInfo.new(value, self)
90
+ end
91
+ end
92
+
93
+ def aggregate_children
94
+ # group call infos based on their targets
95
+ groups = self.children.each_with_object({}) do |call_info, hash|
96
+ key = call_info.target
97
+ (hash[key] ||= []) << call_info
98
+ end
99
+
100
+ groups.map do |key, value|
101
+ AggregateCallInfo.new(value, self)
102
+ end
103
+ end
104
+
105
+ def to_s
106
+ "#{self.full_name} (c: #{self.called}, tt: #{self.total_time}, st: #{self.self_time}, wt: #{wait_time}, ct: #{self.children_time})"
107
+ end
108
+ end
109
+ end