diff options
-rw-r--r-- | gc.c | 122 | ||||
-rw-r--r-- | gc.rb | 34 | ||||
-rw-r--r-- | test/ruby/test_gc.rb | 66 |
3 files changed, 222 insertions, 0 deletions
@@ -10643,6 +10643,128 @@ rb_gc_stat(VALUE key) } } + +enum gc_stat_heap_sym { + gc_stat_heap_sym_slot_size, + gc_stat_heap_sym_heap_allocatable_pages, + gc_stat_heap_sym_heap_eden_pages, + gc_stat_heap_sym_heap_eden_slots, + gc_stat_heap_sym_heap_tomb_pages, + gc_stat_heap_sym_heap_tomb_slots, + gc_stat_heap_sym_last +}; + +static VALUE gc_stat_heap_symbols[gc_stat_heap_sym_last]; + +static void +setup_gc_stat_heap_symbols(void) +{ + if (gc_stat_heap_symbols[0] == 0) { +#define S(s) gc_stat_heap_symbols[gc_stat_heap_sym_##s] = ID2SYM(rb_intern_const(#s)) + S(slot_size); + S(heap_allocatable_pages); + S(heap_eden_pages); + S(heap_eden_slots); + S(heap_tomb_pages); + S(heap_tomb_slots); +#undef S + } +} + +static size_t +gc_stat_heap_internal(int size_pool_idx, VALUE hash_or_sym) +{ + rb_objspace_t *objspace = &rb_objspace; + VALUE hash = Qnil, key = Qnil; + + setup_gc_stat_heap_symbols(); + + if (RB_TYPE_P(hash_or_sym, T_HASH)) { + hash = hash_or_sym; + } + else if (SYMBOL_P(hash_or_sym)) { + key = hash_or_sym; + } + else { + rb_raise(rb_eTypeError, "non-hash or symbol argument"); + } + + if (size_pool_idx < 0 || size_pool_idx >= SIZE_POOL_COUNT) { + rb_raise(rb_eArgError, "size pool index out of range"); + } + + rb_size_pool_t *size_pool = &size_pools[size_pool_idx]; + +#define SET(name, attr) \ + if (key == gc_stat_heap_symbols[gc_stat_heap_sym_##name]) \ + return attr; \ + else if (hash != Qnil) \ + rb_hash_aset(hash, gc_stat_heap_symbols[gc_stat_heap_sym_##name], SIZET2NUM(attr)); + + SET(slot_size, size_pool->slot_size); + SET(heap_allocatable_pages, size_pool->allocatable_pages); + SET(heap_eden_pages, SIZE_POOL_EDEN_HEAP(size_pool)->total_pages); + SET(heap_eden_slots, SIZE_POOL_EDEN_HEAP(size_pool)->total_slots); + SET(heap_tomb_pages, SIZE_POOL_TOMB_HEAP(size_pool)->total_pages); + SET(heap_tomb_slots, SIZE_POOL_TOMB_HEAP(size_pool)->total_slots); +#undef SET + + if (!NIL_P(key)) { /* matched key should return above */ + rb_raise(rb_eArgError, "unknown key: %"PRIsVALUE, rb_sym2str(key)); + } + + return 0; +} + +static VALUE +gc_stat_heap(rb_execution_context_t *ec, VALUE self, VALUE heap_name, VALUE arg) +{ + if (NIL_P(heap_name)) { + if (NIL_P(arg)) { + arg = rb_hash_new(); + } + else if (RB_TYPE_P(arg, T_HASH)) { + // ok + } + else { + rb_raise(rb_eTypeError, "non-hash given"); + } + + for (int i = 0; i < SIZE_POOL_COUNT; i++) { + VALUE hash = rb_hash_aref(arg, INT2FIX(i)); + if (NIL_P(hash)) { + hash = rb_hash_new(); + rb_hash_aset(arg, INT2FIX(i), hash); + } + gc_stat_heap_internal(i, hash); + } + } + else if (FIXNUM_P(heap_name)) { + int size_pool_idx = FIX2INT(heap_name); + + if (NIL_P(arg)) { + arg = rb_hash_new(); + } + else if (SYMBOL_P(arg)) { + size_t value = gc_stat_heap_internal(size_pool_idx, arg); + return SIZET2NUM(value); + } + else if (RB_TYPE_P(arg, T_HASH)) { + // ok + } + else { + rb_raise(rb_eTypeError, "non-hash or symbol given"); + } + + gc_stat_heap_internal(size_pool_idx, arg); + } + else { + rb_raise(rb_eTypeError, "heap_name must be nil or an Integer"); + } + + return arg; +} + static VALUE gc_stress_get(rb_execution_context_t *ec, VALUE self) { @@ -205,6 +205,40 @@ module GC Primitive.gc_stat hash_or_key end + # call-seq: + # GC.stat_heap -> Hash + # GC.stat_heap(nil, hash) -> Hash + # GC.stat_heap(heap_name) -> Hash + # GC.stat_heap(heap_name, hash) -> Hash + # GC.stat_heap(heap_name, :key) -> Numeric + # + # Returns information for memory pools in the GC. + # + # If the first optional argument, +heap_name+, is passed in and not +nil+, it + # returns a +Hash+ containing information about the particular memory pool. + # Otherwise, it will return a +Hash+ with memory pool names as keys and + # a +Hash+ containing information about the memory pool as values. + # + # If the second optional argument, +hash_or_key+, is given as +Hash+, it will + # be overwritten and returned. This is intended to avoid the probe effect. + # + # If both optional arguments are passed in and the second optional argument is + # a symbol, it will return a +Numeric+ of the value for the particular memory + # pool. + # + # On CRuby, +heap_name+ is of the type +Integer+ but may be of type +String+ + # on other implementations. + # + # The contents of the hash are implementation specific and may change in + # the future without notice. + # + # If the optional argument, hash, is given, it is overwritten and returned. + # + # This method is only expected to work on CRuby. + def self.stat_heap heap_name = nil, hash_or_key = nil + Primitive.gc_stat_heap heap_name, hash_or_key + end + # call-seq: # GC.latest_gc_info -> {:gc_by=>:newobj} # GC.latest_gc_info(hash) -> hash diff --git a/test/ruby/test_gc.rb b/test/ruby/test_gc.rb index b7f906bbdd..2e91b5eb2d 100644 --- a/test/ruby/test_gc.rb +++ b/test/ruby/test_gc.rb @@ -139,6 +139,72 @@ class TestGc < Test::Unit::TestCase end end + def test_stat_heap + skip 'stress' if GC.stress + + stat_heap = {} + stat = {} + # Initialize to prevent GC in future calls + GC.stat_heap(0, stat_heap) + GC.stat(stat) + + GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT].times do |i| + GC.stat_heap(i, stat_heap) + GC.stat(stat) + + assert_equal GC::INTERNAL_CONSTANTS[:RVALUE_SIZE] * (2**i), stat_heap[:slot_size] + assert_operator stat_heap[:heap_allocatable_pages], :<=, stat[:heap_allocatable_pages] + assert_operator stat_heap[:heap_eden_pages], :<=, stat[:heap_eden_pages] + assert_operator stat_heap[:heap_eden_slots], :>=, 0 + assert_operator stat_heap[:heap_tomb_pages], :<=, stat[:heap_tomb_pages] + assert_operator stat_heap[:heap_tomb_slots], :>=, 0 + end + + GC.stat_heap(0, stat_heap) + assert_equal stat_heap[:slot_size], GC.stat_heap(0, :slot_size) + assert_equal stat_heap[:slot_size], GC.stat_heap(0)[:slot_size] + + assert_raise(ArgumentError) { GC.stat_heap(-1) } + assert_raise(ArgumentError) { GC.stat_heap(GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT]) } + end + + def test_stat_heap_all + stat_heap_all = {} + stat_heap = {} + + 2.times do + GC.stat_heap(0, stat_heap) + GC.stat_heap(nil, stat_heap_all) + end + + GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT].times do |i| + GC.stat_heap(i, stat_heap) + + assert_equal stat_heap, stat_heap_all[i] + end + + assert_raise(TypeError) { GC.stat_heap(nil, :slot_size) } + end + + def test_stat_heap_constraints + skip 'stress' if GC.stress + + stat = GC.stat + stat_heap = GC.stat_heap + GC.stat(stat) + GC.stat_heap(nil, stat_heap) + + stat_heap_sum = Hash.new(0) + stat_heap.values.each do |hash| + hash.each { |k, v| stat_heap_sum[k] += v } + end + + assert_equal stat[:heap_allocatable_pages], stat_heap_sum[:heap_allocatable_pages] + assert_equal stat[:heap_eden_pages], stat_heap_sum[:heap_eden_pages] + assert_equal stat[:heap_tomb_pages], stat_heap_sum[:heap_tomb_pages] + assert_equal stat[:heap_available_slots], stat_heap_sum[:heap_eden_slots] + stat_heap_sum[:heap_tomb_slots] + end + def test_latest_gc_info omit 'stress' if GC.stress |