MemorySourceMemoryUsage

class Usage

Tracks memory usage statistics including size and object count.

Can be used to measure allocations or compute usage of object graphs.

Definitions

def initialize(size = 0, count = 0)

Initialize a new usage tracker.

Signature

parameter size Integer

The total size in bytes.

parameter count Integer

The total count of objects.

Implementation

def initialize(size = 0, count = 0)
	@size = size
	@count = count
end

attr_accessor :size

attr_accessor :count

def [](key)

Access usage attributes by key.

Signature

parameter key Symbol

The attribute name (:size, :count, :memory, :memsize).

Implementation

def [](key)
	public_send(key)
end

def <<(allocation)

Add an allocation to this usage.

Signature

parameter allocation Allocation

The allocation to add.

Implementation

def << allocation
	self.size += allocation.memsize
	self.count += 1
	
	return self
end

def add!(other)

Add another usage to this usage.

Signature

parameter other Usage

The usage to add.

Implementation

def add!(other)
	self.size += other.size
	self.count += other.count
	
	return self
end

def self.of(root, seen: Set.new.compare_by_identity, ignore: IGNORE)

Compute the usage of an object and all reachable objects from it.

The root is always visited even if it is in seen.

Signature

parameter root Object

The root object to start traversal from.

parameter seen Hash(Object, Integer)

The seen objects (should be compare_by_identity).

returns Usage

The usage of the object and all reachable objects from it.

Implementation

def self.of(root, seen: Set.new.compare_by_identity, ignore: IGNORE)
	count = 0
	size = 0
	
	queue = [root]
	while object = queue.shift
		# Add the object to the seen set:
		seen.add(object)
		
		# Update the count and size:
		count += 1
		size += ObjectSpace.memsize_of(object)
		
		# Add the object's reachable objects to the queue:
		ObjectSpace.reachable_objects_from(object)&.each do |reachable_object|
			# Skip ignored types:
			next if ignore.any?{|type| reachable_object.is_a?(type)}
			
			# Skip internal objects - they don't behave correctly when added to `seen` and create unbounded recursion:
			next if reachable_object.is_a?(ObjectSpace::InternalObjectWrapper)
			
			# Skip objects we have already seen:
			next if seen.include?(reachable_object)
			
			queue << reachable_object
		end
	end
	
	return new(size, count)
end

def as_json(...)

Convert this usage to a JSON-compatible hash.

Signature

parameter options Hash | Nil

Optional JSON serialization options.

returns Hash

Hash with :size and :count keys.

Implementation

def as_json(...)
	{
		size: @size,
			count: @count
	}
end

def to_json(...)

Convert this usage to a JSON string.

Signature

returns String

JSON representation of this usage.

Implementation

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

def to_s

Generate a human-readable string representation.

Signature

returns String

Formatted string showing size and allocation count.

Implementation

def to_s
	"(#{Memory.formatted_bytes(@size)} in #{@count} allocations)"
end