Memory::ProfilerSourceMemoryProfilerCallTree

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_locations Array<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 limit Integer

Maximum number of paths to return.

parameter by Symbol

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 limit Integer

Maximum number of hotspots to return.

parameter by Symbol

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 limit Integer

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