class CallTree
Efficient tree structure for tracking allocation call paths. Each node represents a frame in the call stack, with counts of how many allocations occurred at this point in the call path.
Nested
Definitions
def initialize
Create a new call tree for tracking allocation paths.
Implementation
def initialize
@root = Node.new
@insertion_count = 0
end
attr_accessor :insertion_count
Signature
-
attribute
Integer Number of insertions (allocations) recorded in this tree.
def record(caller_locations)
Record an allocation with the given caller locations.
Signature
-
parameter
caller_locationsArray<Thread::Backtrace::Location> The call stack.
-
returns
Node The leaf node representing this allocation path.
Implementation
def record(caller_locations)
return nil if caller_locations.empty?
current = @root
# Build tree path from root to leaf:
caller_locations.each do |location|
current = current.find_or_create_child(location)
end
# Increment counts for entire path (from leaf back to root):
current.increment_path!
# Track total insertions
@insertion_count += 1
# Return leaf node for object tracking:
current
end
def top_paths(limit = 10, by: :retained)
Get the top N call paths by allocation count.
Signature
-
parameter
limitInteger Maximum number of paths to return.
-
parameter
bySymbol Sort by :total or :retained count.
-
returns
Array(Array) Array of [locations, total_count, retained_count].
Implementation
def top_paths(limit = 10, by: :retained)
paths = []
@root.each_path do |path, total_count, retained_count|
# Filter out root node (has nil location) and map to location strings
locations = path.select(&:location).map {|node| node.location.to_s}
paths << [locations, total_count, retained_count] unless locations.empty?
end
# Sort by the requested metric (default: retained, since that's what matters for leaks)
sort_index = (by == :total) ? 1 : 2
paths.sort_by {|path_data| -path_data[sort_index]}.first(limit)
end
def hotspots(limit = 20, by: :retained)
Get hotspot locations (individual frames with highest counts).
Signature
-
parameter
limitInteger Maximum number of hotspots to return.
-
parameter
bySymbol Sort by :total or :retained count.
-
returns
Hash Map of location => [total_count, retained_count].
Implementation
def hotspots(limit = 20, by: :retained)
frames = Hash.new {|h, k| h[k] = [0, 0]}
collect_frames(@root, frames)
# Sort by the requested metric
sort_index = (by == :total) ? 0 : 1
frames.sort_by {|_, counts| -counts[sort_index]}.first(limit).to_h
end
def total_allocations
Total number of allocations tracked.
Signature
-
returns
Integer Total allocation count.
Implementation
def total_allocations
@root.total_count
end
def retained_allocations
Number of currently retained (live) allocations.
Signature
-
returns
Integer Retained allocation count.
Implementation
def retained_allocations
@root.retained_count
end
def clear!
Clear all tracking data
Implementation
def clear!
@root = Node.new
@insertion_count = 0
end
def prune!(limit = 5)
Prune the tree to keep only the top N children at each level. This controls memory usage by removing low-retained branches.
Signature
-
parameter
limitInteger Number of children to keep per node (default: 5).
-
returns
Integer Total number of nodes pruned (discarded).
Implementation
def prune!(limit = 5)
@root.prune!(limit)
end