mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
ef76e3cfb6
IMEMO objects have many types. Without this change, we cannot see what types of IMEMO objects are being used when dumping the heap. Adding the type to the IMEMO object will allow us to gather statistics about IMEMO objects being used. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@57486 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
431 lines
13 KiB
Ruby
431 lines
13 KiB
Ruby
# frozen_string_literal: false
|
|
require "test/unit"
|
|
require "objspace"
|
|
begin
|
|
require "json"
|
|
rescue LoadError
|
|
end
|
|
|
|
class TestObjSpace < Test::Unit::TestCase
|
|
def test_memsize_of
|
|
assert_equal(0, ObjectSpace.memsize_of(true))
|
|
assert_equal(0, ObjectSpace.memsize_of(nil))
|
|
assert_equal(0, ObjectSpace.memsize_of(1))
|
|
assert_kind_of(Integer, ObjectSpace.memsize_of(Object.new))
|
|
assert_kind_of(Integer, ObjectSpace.memsize_of(Class))
|
|
assert_kind_of(Integer, ObjectSpace.memsize_of(""))
|
|
assert_kind_of(Integer, ObjectSpace.memsize_of([]))
|
|
assert_kind_of(Integer, ObjectSpace.memsize_of({}))
|
|
assert_kind_of(Integer, ObjectSpace.memsize_of(//))
|
|
f = File.new(__FILE__)
|
|
assert_kind_of(Integer, ObjectSpace.memsize_of(f))
|
|
f.close
|
|
assert_kind_of(Integer, ObjectSpace.memsize_of(/a/.match("a")))
|
|
assert_kind_of(Integer, ObjectSpace.memsize_of(Struct.new(:a)))
|
|
|
|
assert_operator(ObjectSpace.memsize_of(Regexp.new("(a)"*1000).match("a"*1000)),
|
|
:>,
|
|
ObjectSpace.memsize_of(//.match("")))
|
|
end
|
|
|
|
def test_memsize_of_root_shared_string
|
|
a = "hello" * 5
|
|
b = a.dup
|
|
c = nil
|
|
ObjectSpace.each_object(String) {|x| break c = x if x == a and x.frozen?}
|
|
rv_size = GC::INTERNAL_CONSTANTS[:RVALUE_SIZE]
|
|
assert_equal([rv_size, rv_size, 26 + rv_size], [a, b, c].map {|x| ObjectSpace.memsize_of(x)})
|
|
end
|
|
|
|
def test_argf_memsize
|
|
size = ObjectSpace.memsize_of(ARGF)
|
|
assert_kind_of(Integer, size)
|
|
assert_operator(size, :>, 0)
|
|
argf = ARGF.dup
|
|
argf.inplace_mode = nil
|
|
size = ObjectSpace.memsize_of(argf)
|
|
argf.inplace_mode = "inplace_mode_suffix"
|
|
assert_equal(size + 20, ObjectSpace.memsize_of(argf))
|
|
end
|
|
|
|
def test_memsize_of_all
|
|
assert_kind_of(Integer, a = ObjectSpace.memsize_of_all)
|
|
assert_kind_of(Integer, b = ObjectSpace.memsize_of_all(String))
|
|
assert_operator(a, :>, b)
|
|
assert_operator(a, :>, 0)
|
|
assert_operator(b, :>, 0)
|
|
assert_raise(TypeError) {ObjectSpace.memsize_of_all('error')}
|
|
end
|
|
|
|
def test_count_objects_size
|
|
res = ObjectSpace.count_objects_size
|
|
assert_not_empty(res)
|
|
assert_operator(res[:TOTAL], :>, 0)
|
|
arg = {}
|
|
ObjectSpace.count_objects_size(arg)
|
|
assert_not_empty(arg)
|
|
end
|
|
|
|
def test_count_nodes
|
|
res = ObjectSpace.count_nodes
|
|
assert_not_empty(res)
|
|
arg = {}
|
|
ObjectSpace.count_nodes(arg)
|
|
assert_not_empty(arg)
|
|
bug8014 = '[ruby-core:53130] [Bug #8014]'
|
|
assert_empty(arg.select {|k, v| !(Symbol === k && Integer === v)}, bug8014)
|
|
end if false
|
|
|
|
def test_count_tdata_objects
|
|
res = ObjectSpace.count_tdata_objects
|
|
assert_not_empty(res)
|
|
arg = {}
|
|
ObjectSpace.count_tdata_objects(arg)
|
|
assert_not_empty(arg)
|
|
end
|
|
|
|
def test_count_imemo_objects
|
|
res = ObjectSpace.count_imemo_objects
|
|
assert_not_empty(res)
|
|
assert_not_nil(res[:imemo_cref])
|
|
arg = {}
|
|
res = ObjectSpace.count_imemo_objects(arg)
|
|
assert_not_empty(res)
|
|
end
|
|
|
|
def test_memsize_of_iseq
|
|
iseqw = RubyVM::InstructionSequence.compile('def a; a = :b; end')
|
|
base_obj_size = ObjectSpace.memsize_of(Object.new)
|
|
assert_operator(ObjectSpace.memsize_of(iseqw), :>, base_obj_size)
|
|
end
|
|
|
|
def test_reachable_objects_from
|
|
assert_separately %w[--disable-gem -robjspace], "#{<<-"begin;"}\n#{<<-'end;'}"
|
|
begin;
|
|
assert_equal(nil, ObjectSpace.reachable_objects_from(nil))
|
|
assert_equal([Array, 'a', 'b', 'c'], ObjectSpace.reachable_objects_from(['a', 'b', 'c']))
|
|
|
|
assert_equal([Array, 'a', 'a', 'a'], ObjectSpace.reachable_objects_from(['a', 'a', 'a']))
|
|
assert_equal([Array, 'a', 'a'], ObjectSpace.reachable_objects_from(['a', v = 'a', v]))
|
|
assert_equal([Array, 'a'], ObjectSpace.reachable_objects_from([v = 'a', v, v]))
|
|
|
|
long_ary = Array.new(1_000){''}
|
|
max = 0
|
|
|
|
ObjectSpace.each_object{|o|
|
|
refs = ObjectSpace.reachable_objects_from(o)
|
|
max = [refs.size, max].max
|
|
|
|
unless refs.nil?
|
|
refs.each_with_index {|ro, i|
|
|
assert_not_nil(ro, "#{i}: this referenced object is internal object")
|
|
}
|
|
end
|
|
}
|
|
assert_operator(max, :>=, long_ary.size+1, "1000 elems + Array class")
|
|
end;
|
|
end
|
|
|
|
def test_reachable_objects_from_root
|
|
root_objects = ObjectSpace.reachable_objects_from_root
|
|
|
|
assert_operator(root_objects.size, :>, 0)
|
|
|
|
root_objects.each{|category, objects|
|
|
assert_kind_of(String, category)
|
|
assert_kind_of(Array, objects)
|
|
assert_operator(objects.size, :>, 0)
|
|
}
|
|
end
|
|
|
|
def test_reachable_objects_size
|
|
assert_separately %w[--disable-gem -robjspace], "#{<<~"begin;"}\n#{<<~'end;'}"
|
|
begin;
|
|
ObjectSpace.each_object{|o|
|
|
ObjectSpace.reachable_objects_from(o).each{|reached_obj|
|
|
size = ObjectSpace.memsize_of(reached_obj)
|
|
assert_kind_of(Integer, size)
|
|
assert_operator(size, :>=, 0)
|
|
}
|
|
}
|
|
end;
|
|
end
|
|
|
|
def test_trace_object_allocations
|
|
Class.name
|
|
o0 = Object.new
|
|
ObjectSpace.trace_object_allocations{
|
|
o1 = Object.new; line1 = __LINE__; c1 = GC.count
|
|
o2 = "xyzzy" ; line2 = __LINE__; c2 = GC.count
|
|
o3 = [1, 2] ; line3 = __LINE__; c3 = GC.count
|
|
|
|
assert_equal(nil, ObjectSpace.allocation_sourcefile(o0))
|
|
assert_equal(nil, ObjectSpace.allocation_sourceline(o0))
|
|
assert_equal(nil, ObjectSpace.allocation_generation(o0))
|
|
|
|
assert_equal(line1, ObjectSpace.allocation_sourceline(o1))
|
|
assert_equal(__FILE__, ObjectSpace.allocation_sourcefile(o1))
|
|
assert_equal(c1, ObjectSpace.allocation_generation(o1))
|
|
assert_equal(Class.name, ObjectSpace.allocation_class_path(o1))
|
|
assert_equal(:new, ObjectSpace.allocation_method_id(o1))
|
|
|
|
assert_equal(__FILE__, ObjectSpace.allocation_sourcefile(o2))
|
|
assert_equal(line2, ObjectSpace.allocation_sourceline(o2))
|
|
assert_equal(c2, ObjectSpace.allocation_generation(o2))
|
|
assert_equal(self.class.name, ObjectSpace.allocation_class_path(o2))
|
|
assert_equal(__method__, ObjectSpace.allocation_method_id(o2))
|
|
|
|
assert_equal(__FILE__, ObjectSpace.allocation_sourcefile(o3))
|
|
assert_equal(line3, ObjectSpace.allocation_sourceline(o3))
|
|
assert_equal(c3, ObjectSpace.allocation_generation(o3))
|
|
assert_equal(self.class.name, ObjectSpace.allocation_class_path(o3))
|
|
assert_equal(__method__, ObjectSpace.allocation_method_id(o3))
|
|
}
|
|
end
|
|
|
|
def test_trace_object_allocations_start_stop_clear
|
|
ObjectSpace.trace_object_allocations_clear # clear object_table to get rid of erroneous detection for obj3
|
|
GC.disable # suppress potential object reuse. see [Bug #11271]
|
|
begin
|
|
ObjectSpace.trace_object_allocations_start
|
|
begin
|
|
ObjectSpace.trace_object_allocations_start
|
|
begin
|
|
ObjectSpace.trace_object_allocations_start
|
|
obj0 = Object.new
|
|
ensure
|
|
ObjectSpace.trace_object_allocations_stop
|
|
obj1 = Object.new
|
|
end
|
|
ensure
|
|
ObjectSpace.trace_object_allocations_stop
|
|
obj2 = Object.new
|
|
end
|
|
ensure
|
|
ObjectSpace.trace_object_allocations_stop
|
|
obj3 = Object.new
|
|
end
|
|
|
|
assert_equal(__FILE__, ObjectSpace.allocation_sourcefile(obj0))
|
|
assert_equal(__FILE__, ObjectSpace.allocation_sourcefile(obj1))
|
|
assert_equal(__FILE__, ObjectSpace.allocation_sourcefile(obj2))
|
|
assert_equal(nil , ObjectSpace.allocation_sourcefile(obj3)) # after tracing
|
|
|
|
ObjectSpace.trace_object_allocations_clear
|
|
assert_equal(nil, ObjectSpace.allocation_sourcefile(obj0))
|
|
assert_equal(nil, ObjectSpace.allocation_sourcefile(obj1))
|
|
assert_equal(nil, ObjectSpace.allocation_sourcefile(obj2))
|
|
assert_equal(nil, ObjectSpace.allocation_sourcefile(obj3))
|
|
ensure
|
|
GC.enable
|
|
end
|
|
|
|
def test_dump_flags
|
|
info = ObjectSpace.dump("foo".freeze)
|
|
assert_match /"wb_protected":true, "old":true/, info
|
|
assert_match /"fstring":true/, info
|
|
JSON.parse(info) if defined?(JSON)
|
|
end
|
|
|
|
def test_dump_to_default
|
|
line = nil
|
|
info = nil
|
|
ObjectSpace.trace_object_allocations do
|
|
line = __LINE__ + 1
|
|
str = "hello world"
|
|
info = ObjectSpace.dump(str)
|
|
end
|
|
assert_dump_object(info, line)
|
|
end
|
|
|
|
def test_dump_to_io
|
|
line = nil
|
|
info = IO.pipe do |r, w|
|
|
th = Thread.start {r.read}
|
|
ObjectSpace.trace_object_allocations do
|
|
line = __LINE__ + 1
|
|
str = "hello world"
|
|
ObjectSpace.dump(str, output: w)
|
|
end
|
|
w.close
|
|
th.value
|
|
end
|
|
assert_dump_object(info, line)
|
|
end
|
|
|
|
def assert_dump_object(info, line)
|
|
loc = caller_locations(1, 1)[0]
|
|
assert_match /"type":"STRING"/, info
|
|
assert_match /"embedded":true, "bytesize":11, "value":"hello world", "encoding":"UTF-8"/, info
|
|
assert_match /"file":"#{Regexp.escape __FILE__}", "line":#{line}/, info
|
|
assert_match /"method":"#{loc.base_label}"/, info
|
|
JSON.parse(info) if defined?(JSON)
|
|
end
|
|
|
|
def test_dump_special_consts
|
|
# [ruby-core:69692] [Bug #11291]
|
|
assert_equal('null', ObjectSpace.dump(nil))
|
|
assert_equal('true', ObjectSpace.dump(true))
|
|
assert_equal('false', ObjectSpace.dump(false))
|
|
assert_equal('0', ObjectSpace.dump(0))
|
|
assert_equal('{"type":"SYMBOL", "value":"foo"}', ObjectSpace.dump(:foo))
|
|
end
|
|
|
|
def test_dump_dynamic_symbol
|
|
dump = ObjectSpace.dump(("foobar%x" % rand(0x10000)).to_sym)
|
|
assert_match /"type":"SYMBOL"/, dump
|
|
assert_match /"value":"foobar\h+"/, dump
|
|
end
|
|
|
|
def test_dump_includes_imemo_type
|
|
assert_in_out_err(%w[-robjspace], "#{<<-"begin;"}\n#{<<-'end;'}") do |output, error|
|
|
begin;
|
|
def dump_my_heap_please
|
|
ObjectSpace.dump_all(output: :stdout)
|
|
end
|
|
|
|
dump_my_heap_please
|
|
end;
|
|
heap = output.find_all { |l|
|
|
obj = JSON.parse(l)
|
|
obj['type'] == "IMEMO" && obj['imemo_type']
|
|
}
|
|
assert_operator heap.length, :>, 0
|
|
end
|
|
end
|
|
|
|
def test_dump_all_full
|
|
assert_in_out_err(%w[-robjspace], "#{<<-"begin;"}\n#{<<-'end;'}") do |output, error|
|
|
begin;
|
|
def dump_my_heap_please
|
|
ObjectSpace.dump_all(output: :stdout, full: true)
|
|
end
|
|
|
|
dump_my_heap_please
|
|
end;
|
|
heap = output.find_all { |l| JSON.parse(l)['type'] == "NONE" }
|
|
assert_operator heap.length, :>, 0
|
|
end
|
|
end
|
|
|
|
def test_dump_all
|
|
entry = /"bytesize":11, "value":"TEST STRING", "encoding":"UTF-8", "file":"-", "line":4, "method":"dump_my_heap_please", "generation":/
|
|
|
|
assert_in_out_err(%w[-robjspace], "#{<<-"begin;"}#{<<-'end;'}") do |output, error|
|
|
begin;
|
|
def dump_my_heap_please
|
|
ObjectSpace.trace_object_allocations_start
|
|
GC.start
|
|
str = "TEST STRING".force_encoding("UTF-8")
|
|
ObjectSpace.dump_all(output: :stdout)
|
|
end
|
|
|
|
dump_my_heap_please
|
|
end;
|
|
assert_match(entry, output.grep(/TEST STRING/).join("\n"))
|
|
end
|
|
|
|
assert_in_out_err(%w[-robjspace], "#{<<-"begin;"}#{<<-'end;'}") do |(output), (error)|
|
|
begin;
|
|
def dump_my_heap_please
|
|
ObjectSpace.trace_object_allocations_start
|
|
GC.start
|
|
str = "TEST STRING".force_encoding("UTF-8")
|
|
ObjectSpace.dump_all().path
|
|
end
|
|
|
|
puts dump_my_heap_please
|
|
end;
|
|
skip if /is not supported/ =~ error
|
|
skip error unless output
|
|
assert_match(entry, File.readlines(output).grep(/TEST STRING/).join("\n"))
|
|
File.unlink(output)
|
|
end
|
|
|
|
if defined?(JSON)
|
|
args = [
|
|
"-rjson", "-",
|
|
EnvUtil.rubybin,
|
|
"--disable=gems", "-robjspace", "-eObjectSpace.dump_all(output: :stdout)",
|
|
]
|
|
assert_ruby_status(args, "#{<<~"begin;"}\n#{<<~"end;"}")
|
|
begin;
|
|
IO.popen(ARGV) do |f|
|
|
f.each_line.map { |x| JSON.load(x) }
|
|
end
|
|
end;
|
|
end
|
|
end
|
|
|
|
def test_dump_uninitialized_file
|
|
assert_in_out_err(%[-robjspace], <<-RUBY) do |(output), (error)|
|
|
puts ObjectSpace.dump(File.allocate)
|
|
RUBY
|
|
assert_nil error
|
|
assert_match /"type":"FILE"/, output
|
|
assert_not_match /"fd":/, output
|
|
end
|
|
end
|
|
|
|
def traverse_classes klass
|
|
h = {}
|
|
while klass && !h.has_key?(klass)
|
|
h[klass] = true
|
|
klass = ObjectSpace.internal_class_of(klass)
|
|
end
|
|
end
|
|
|
|
def test_internal_class_of
|
|
i = 0
|
|
ObjectSpace.each_object{|o|
|
|
traverse_classes ObjectSpace.internal_class_of(o)
|
|
i += 1
|
|
}
|
|
assert_operator i, :>, 0
|
|
end
|
|
|
|
def traverse_super_classes klass
|
|
while klass
|
|
klass = ObjectSpace.internal_super_of(klass)
|
|
end
|
|
end
|
|
|
|
def all_super_classes klass
|
|
klasses = []
|
|
while klass
|
|
klasses << klass
|
|
klass = ObjectSpace.internal_super_of(klass)
|
|
end
|
|
klasses
|
|
end
|
|
|
|
def test_internal_super_of
|
|
klasses = all_super_classes(String)
|
|
String.ancestors.each{|k|
|
|
case k
|
|
when Class
|
|
assert_equal(true, klasses.include?(k), k.inspect)
|
|
when Module
|
|
assert_equal(false, klasses.include?(k), k.inspect) # Internal object (T_ICLASS)
|
|
end
|
|
}
|
|
|
|
i = 0
|
|
ObjectSpace.each_object(Module){|o|
|
|
traverse_super_classes ObjectSpace.internal_super_of(o)
|
|
i += 1
|
|
}
|
|
assert_operator i, :>, 0
|
|
end
|
|
|
|
def test_count_symbols
|
|
syms = (1..128).map{|i| ("xyzzy#{i}" * 128).to_sym}
|
|
c = Class.new{define_method(syms[-1]){}}
|
|
|
|
h = ObjectSpace.count_symbols
|
|
assert_operator h[:mortal_dynamic_symbol], :>=, 128, h.inspect
|
|
assert_operator h[:immortal_dynamic_symbol], :>=, 1, h.inspect
|
|
assert_operator h[:immortal_static_symbol], :>=, Object.methods.size, h.inspect
|
|
assert_equal h[:immortal_symbol], h[:immortal_dynamic_symbol] + h[:immortal_static_symbol], h.inspect
|
|
end
|
|
end
|