1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
|
# frozen_string_literal: true
# Released under the MIT License.
# Copyright, 2019-2023, by Samuel Williams.
# Copyright, 2021, by Daniel Leidert.
require 'objspace'
module RSpec
module Memory
Allocation = Struct.new(:count, :size) do
SLOT_SIZE = ObjectSpace.memsize_of(Object.new)
def << object
self.count += 1
# We don't want to force specs to take the slot size into account.
self.size += ObjectSpace.memsize_of(object) - SLOT_SIZE
end
def self.default_hash
Hash.new{|h,k| h[k] = Allocation.new(0, 0)}
end
end
class Trace
def self.supported?
# There are issues on truffleruby-1.0.0rc9
return false if RUBY_ENGINE == "truffleruby"
ObjectSpace.respond_to?(:trace_object_allocations)
end
if supported?
def self.capture(*args, &block)
self.new(*args).tap do |trace|
trace.capture(&block)
end
end
else
def self.capture(*args, &block)
yield
return nil
end
end
def initialize(klasses)
@klasses = klasses
@allocated = Allocation.default_hash
@retained = Allocation.default_hash
@ignored = Allocation.default_hash
@total = Allocation.new(0, 0)
end
attr :allocated
attr :retained
attr :ignored
attr :total
def current_objects(generation)
allocations = []
ObjectSpace.each_object do |object|
if ObjectSpace.allocation_generation(object) == generation
allocations << object
end
end
return allocations
end
def find_base(object)
@klasses.find{|klass| object.is_a? klass}
end
def capture(&block)
GC.start
begin
GC.disable
generation = GC.count
ObjectSpace.trace_object_allocations(&block)
allocated = current_objects(generation)
ensure
GC.enable
end
GC.start
retained = current_objects(generation)
# All allocated objects, including those freed in the last GC:
allocated.each do |object|
if klass = find_base(object)
@allocated[klass] << object
else
# If the user specified classes, but we can't pin this allocation to a specific class, we issue a warning.
if @klasses.any?
warn "Ignoring allocation of #{object.class} at #{ObjectSpace.allocation_sourcefile(object)}:#{ObjectSpace.allocation_sourceline(object)}"
end
@ignored[object.class] << object
end
@total << object
end
# Retained objects are still alive after a final GC:
retained.each do |object|
if klass = find_base(object)
@retained[klass] << object
end
end
end
end
end
end
|