MemorySourceMemoryGraphNode

class Node

Represents a node in the object graph with usage information.

Definitions

attr_accessor :object

Signature

attribute Object

The object this node represents.

attr_accessor :usage

Signature

attribute Usage

The memory usage of this object (not including children).

attr_accessor :parent

Signature

attribute Node | Nil

The parent node (nil for root).

attr_accessor :children

Signature

attribute Hash(String, Node) | Nil

Child nodes reachable from this object (hash of reference => node).

attr_accessor :reference

Signature

attribute String | Nil

The reference to the parent object (nil for root).

def add(child)

Add a child node to this node.

Signature

parameter child Node

The child node to add.

returns self

Returns self for chaining.

Implementation

def add(child)
	@children ||= {}
	
	# Use the reference as the key, or a fallback if not found:
	key = child.reference || "(#{@children.size})"
	
	@children[key] = child
	
	return self
end

def total_usage

Compute total usage including all children.

Implementation

def total_usage
	unless @total_usage 
		@total_usage = Usage.new(@usage.size, @usage.count)
		
		@children&.each_value do |child|
			child_total = child.total_usage
			@total_usage.add!(child_total)
		end
	end
	
	return @total_usage
end

def find_reference(child)

Find how this node references a child object.

Signature

parameter child Object

The child object to find.

returns String | Nil

A human-readable description of the reference, or nil if not found.

Implementation

def find_reference(child)
	# Check instance variables:
	@object.instance_variables.each do |ivar|
		value = @object.instance_variable_get(ivar)
		if value.equal?(child)
			return ivar.to_s
		end
	end
	
	# Check array elements:
	if @object.is_a?(Array)
		@object.each_with_index do |element, index|
			if element.equal?(child)
				return "[#{index}]"
			end
		end
	end
	
	# Check hash keys and values:
	if @object.is_a?(Hash)
		@object.each do |key, value|
			if value.equal?(child)
				return "[#{key.inspect}]"
			end
			if key.equal?(child)
				return "(key: #{key.inspect})"
			end
		end
	end
	
	# Check struct members:
	if @object.is_a?(Struct)
		@object.each_pair do |member, value|
			if value.equal?(child)
				return ".#{member}"
			end
		end
	end
	
	# Could not determine the reference:
	return nil
end

def path

Get the path string from root to this node (cached).

Signature

returns String | Nil

The formatted path string, or nil if no graph available.

Implementation

def path
	unless @path
		# Build object path from root to this node:
		object_path = []
		current = self
		
		while current
			object_path.unshift(current)
			current = current.parent
		end
		
		# Format the path:
		parts = ["#<#{object_path.first.object.class}:0x%016x>" % (object_path.first.object.object_id << 1)]
		
		# Append each reference in the path:
		(1...object_path.size).each do |i|
			parent_node = object_path[i - 1]
			child_node = object_path[i]
			
			parts << (parent_node.find_reference(child_node.object) || "<??>")
		end
		
		@path = parts.join
	end
	
	return @path
end

def as_json(*)

Convert this node to a JSON-compatible hash.

Signature

parameter options Hash

Options for JSON serialization.

returns Hash

A hash representation of this node.

Implementation

def as_json(*)
	json = {
		path: path,
			object: {
				class: @object.class.name,
				object_id: @object.object_id
			},
			usage: @usage.as_json,
	}

	if @children&.any?
		json[:total_usage] = total_usage.as_json
		json[:children] = @children.transform_values(&:as_json)
	end

	return json
end

def to_json(...)

Convert this node to a JSON string.

Signature

parameter options Hash

Options for JSON serialization.

returns String

A JSON string representation of this node.

Implementation

def to_json(...)
	as_json.to_json(...)
end