diff --git a/core/gc.rbs b/core/gc.rbs index 457fd0b468..efd2ee2ad5 100644 --- a/core/gc.rbs +++ b/core/gc.rbs @@ -7,6 +7,151 @@ # You may obtain information about the operation of the GC through GC::Profiler. # module GC + # + # The GC profiler provides access to information on GC runs including time, + # length and object space size. + # + # Example: + # + # GC::Profiler.enable + # + # require 'rdoc/rdoc' + # + # GC::Profiler.report + # + # GC::Profiler.disable + # + # See also GC.count, GC.malloc_allocated_size and GC.malloc_allocations + # + module Profiler + # + # Clears the GC profiler data. + # + def self.clear: () -> nil + + # + # Stops the GC profiler. + # + def self.disable: () -> nil + + # + # Starts the GC profiler. + # + def self.enable: () -> nil + + # + # The current status of GC profile mode. + # + def self.enabled?: () -> bool + + # + # Returns an Array of individual raw profile data Hashes ordered from earliest + # to latest by `:GC_INVOKE_TIME`. + # + # For example: + # + # [ + # { + # :GC_TIME=>1.3000000000000858e-05, + # :GC_INVOKE_TIME=>0.010634999999999999, + # :HEAP_USE_SIZE=>289640, + # :HEAP_TOTAL_SIZE=>588960, + # :HEAP_TOTAL_OBJECTS=>14724, + # :GC_IS_MARKED=>false + # }, + # # ... + # ] + # + # The keys mean: + # + # `:GC_TIME` + # : Time elapsed in seconds for this GC run + # `:GC_INVOKE_TIME` + # : Time elapsed in seconds from startup to when the GC was invoked + # `:HEAP_USE_SIZE` + # : Total bytes of heap used + # `:HEAP_TOTAL_SIZE` + # : Total size of heap in bytes + # `:HEAP_TOTAL_OBJECTS` + # : Total number of objects + # `:GC_IS_MARKED` + # : Returns `true` if the GC is in mark phase + # + # + # If ruby was built with `GC_PROFILE_MORE_DETAIL`, you will also have access to + # the following hash keys: + # + # `:GC_MARK_TIME` + # `:GC_SWEEP_TIME` + # `:ALLOCATE_INCREASE` + # `:ALLOCATE_LIMIT` + # `:HEAP_USE_PAGES` + # `:HEAP_LIVE_OBJECTS` + # `:HEAP_FREE_OBJECTS` + # `:HAVE_FINALIZE` + # : + # + def self.raw_data: () -> Array[Hash[Symbol, untyped]] + + # + # Writes the GC::Profiler.result to `$stdout` or the given IO object. + # + def self.report: (?_Reporter io) -> nil + + interface _Reporter + def write: (String msg) -> void + end + + # + # Returns a profile data report such as: + # + # GC 1 invokes. + # Index Invoke Time(sec) Use Size(byte) Total Size(byte) Total Object GC time(ms) + # 1 0.012 159240 212940 10647 0.00000000000001530000 + # + def self.result: () -> String + + # + # The total time used for garbage collection in seconds + # + def self.total_time: () -> Float + end + + # + # Internal constants in the garbage collector. + # + INTERNAL_CONSTANTS: Hash[Symbol, untyped] + + # + # GC build options + # + OPTS: Array[String] + # + # Enable to measure GC time. You can get the result with `GC.stat(:time)`. Note + # that GC time measurement can cause some performance overhead. + # + def self.measure_total_time=: [T] (T enable) -> T + + # + # Return measure_total_time flag (default: `true`). Note that measurement can + # affect the application performance. + # + def self.measure_total_time: () -> bool + + # + # Updates automatic compaction mode. + # + # When enabled, the compactor will execute on every major collection. + # + # Enabling compaction will degrade performance on major collections. + # + def self.auto_compact=: [T] (T enable) -> T + + # + # Returns whether or not automatic compaction has been enabled. + # + def self.auto_compact: () -> bool + + # + # Returns information for heaps 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 heap. Otherwise, + # it will return a `Hash` with heap names as keys and a `Hash` containing + # information about the heap 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 heap. + # + # 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. + # + # The hash includes the following keys about the internal information in the GC: + # + # slot_size + # : The slot size of the heap in bytes. + # heap_allocatable_pages + # : The number of pages that can be allocated without triggering a new garbage + # collection cycle. + # heap_eden_pages + # : The number of pages in the eden heap. + # heap_eden_slots + # : The total number of slots in all of the pages in the eden heap. + # heap_tomb_pages + # : The number of pages in the tomb heap. The tomb heap only contains pages + # that do not have any live objects. + # heap_tomb_slots + # : The total number of slots in all of the pages in the tomb heap. + # total_allocated_pages + # : The total number of pages that have been allocated in the heap. + # total_freed_pages + # : The total number of pages that have been freed and released back to the + # system in the heap. + # force_major_gc_count + # : The number of times major garbage collection cycles this heap has forced + # to start due to running out of free slots. + # force_incremental_marking_finish_count + # : The number of times this heap has forced incremental marking to complete + # due to running out of pooled slots. + # + def self.stat_heap: (?Integer? heap_name, ?Hash[Symbol, untyped]? hash) -> Hash[Symbol, untyped] + | (Integer heap_name, Symbol key) -> Integer + + # + # Returns information about object moved in the most recent GC compaction. + # + # The returned hash has two keys :considered and :moved. The hash for + # :considered lists the number of objects that were considered for movement by + # the compactor, and the :moved hash lists the number of objects that were + # actually moved. Some objects can't be moved (maybe they were pinned) so these + # numbers can be used to calculate compaction efficiency. + # + def self.latest_compact_info: () -> compact_info # # Returns current status of GC stress mode. # - def self.stress: () -> (Integer | TrueClass | FalseClass) + def self.stress: () -> (Integer | bool) # -# Internal constants in the garbage collector. -# -GC::INTERNAL_CONSTANTS: Hash[Symbol, Integer] - -# -# GC build options -# -GC::OPTS: Array[String] - -# -# The GC profiler provides access to information on GC runs including time, -# length and object space size. -# -# Example: -# -# GC::Profiler.enable -# -# require 'rdoc/rdoc' -# -# GC::Profiler.report -# -# GC::Profiler.disable -# -# See also GC.count, GC.malloc_allocated_size and GC.malloc_allocations -# -module GC::Profiler - # - # Clears the GC profiler data. - # - def self.clear: () -> void - - # - # Stops the GC profiler. - # - def self.disable: () -> void - - # - # Starts the GC profiler. - # - def self.enable: () -> void - - # - # The current status of GC profile mode. - # - def self.enabled?: () -> bool - - # - # Returns an Array of individual raw profile data Hashes ordered from earliest - # to latest by `:GC_INVOKE_TIME`. - # - # For example: - # - # [ - # { - # :GC_TIME=>1.3000000000000858e-05, - # :GC_INVOKE_TIME=>0.010634999999999999, - # :HEAP_USE_SIZE=>289640, - # :HEAP_TOTAL_SIZE=>588960, - # :HEAP_TOTAL_OBJECTS=>14724, - # :GC_IS_MARKED=>false - # }, - # # ... - # ] - # - # The keys mean: - # - # `:GC_TIME` - # : Time elapsed in seconds for this GC run - # `:GC_INVOKE_TIME` - # : Time elapsed in seconds from startup to when the GC was invoked - # `:HEAP_USE_SIZE` - # : Total bytes of heap used - # `:HEAP_TOTAL_SIZE` - # : Total size of heap in bytes - # `:HEAP_TOTAL_OBJECTS` - # : Total number of objects - # `:GC_IS_MARKED` - # : Returns `true` if the GC is in mark phase - # - # - # If ruby was built with `GC_PROFILE_MORE_DETAIL`, you will also have access to - # the following hash keys: - # - # `:GC_MARK_TIME` - # `:GC_SWEEP_TIME` - # `:ALLOCATE_INCREASE` - # `:ALLOCATE_LIMIT` - # `:HEAP_USE_PAGES` - # `:HEAP_LIVE_OBJECTS` - # `:HEAP_FREE_OBJECTS` - # `:HAVE_FINALIZE` - # : - # - def self.raw_data: () -> ::Array[::Hash[Symbol, untyped]] - - # - # Writes the GC::Profiler.result to `$stdout` or the given IO object. - # - def self.report: (?IO io) -> void - - # - # Returns a profile data report such as: - # - # GC 1 invokes. - # Index Invoke Time(sec) Use Size(byte) Total Size(byte) Total Object GC time(ms) - # 1 0.012 159240 212940 10647 0.00000000000001530000 - # - def self.result: () -> String - - # - # The total time used for garbage collection in seconds - # - def self.total_time: () -> Float -end diff --git a/test/stdlib/GC_test.rb b/test/stdlib/GC_test.rb index df631eca92..e056cbeaa1 100644 --- a/test/stdlib/GC_test.rb +++ b/test/stdlib/GC_test.rb @@ -1,65 +1,240 @@ -require_relative "test_helper" +require_relative 'test_helper' -class GCTest < StdlibTest - target GC +class GCSingletonTest < Test::Unit::TestCase + include TestHelper - include GC + testing "singleton(::GC)" - def test_garbage_collect - garbage_collect - garbage_collect(full_mark: true) - garbage_collect(full_mark: false) - garbage_collect(immediate_mark: true) - garbage_collect(immediate_mark: false) - garbage_collect(immediate_sweep: true) - garbage_collect(immediate_sweep: false) + def test_INTERNAL_CONSTANTS + assert_const_type 'Hash[Symbol, untyped]', + 'GC::INTERNAL_CONSTANTS' + end + + def test_OPTS + assert_const_type 'Array[String]', + 'GC::OPTS' + end + + def test_count + assert_send_type '() -> Integer', + GC, :count + end + + def test_disable + was_disabled = GC.disable + + assert_send_type '() -> bool', + GC, :disable + ensure + GC.enable unless was_disabled + end + + def test_enable + was_enabled = GC.enable + + assert_send_type '() -> bool', + GC, :enable + ensure + GC.disable unless was_enabled end def test_start - GC.start - GC.start(full_mark: true) - GC.start(full_mark: false) - GC.start(immediate_mark: true) - GC.start(immediate_mark: false) - GC.start(immediate_sweep: true) - GC.start(immediate_sweep: false) + assert_send_type '() -> nil', + GC, :start + + # Don't test all combinations of passing args or not, just use them all + with_boolish do |boolish| + assert_send_type '(immediate_sweep: boolish, immediate_mark: boolish, full_mark: boolish) -> nil', + GC, :start, immediate_sweep: boolish, immediate_mark: boolish, full_mark: boolish + end + end + + def test_stat + assert_send_type '() -> Hash[Symbol, untyped]', + GC, :stat + assert_send_type '(Hash[Symbol, untyped]) -> Hash[Symbol, untyped]', + GC, :stat, {} + assert_send_type '(nil) -> Hash[Symbol, untyped]', + GC, :stat, nil + assert_send_type '(Symbol) -> Integer', + GC, :stat, :count + end + + def test_stress_and_stress= + old_stress = GC.stress + + assert_send_type '() -> (Integer | bool)', + GC, :stress + assert_send_type '(Integer) -> Integer', + GC, :stress=, 0 + assert_send_type '() -> Integer', + GC, :stress + + with true, false do |bool| + assert_send_type '(bool) -> bool', + GC, :stress=, bool + assert_send_type '() -> bool', + GC, :stress + end + ensure + GC.stress = old_stress + end + + def test_total_time + assert_send_type '() -> Integer', + GC, :total_time end def test_compact - GC.compact + assert_send_type '() -> GC::compact_info', + GC, :compact end def test_verify_compaction_references - GC.verify_compaction_references + assert_send_type '() -> GC::compact_info', + GC, :verify_compaction_references end def test_verify_internal_consistency - GC.verify_internal_consistency + assert_send_type '() -> nil', + GC, :verify_internal_consistency end def test_latest_gc_info - GC.latest_gc_info - GC.latest_gc_info({}) - GC.latest_gc_info(:state) + assert_send_type '() -> Hash[Symbol, untyped]', + GC, :latest_gc_info + assert_send_type '(nil) -> Hash[Symbol, untyped]', + GC, :latest_gc_info, nil + assert_send_type '(Hash[Symbol, untyped]) -> Hash[Symbol, untyped]', + GC, :latest_gc_info, {} + + assert_send_type '(Symbol) -> untyped', + GC, :latest_gc_info, :major_by + end + + def test_auto_compact + assert_send_type '() -> bool', + GC, :auto_compact + end + + def test_auto_compact= + old = GC.auto_compact + + + with_untyped do |untyped| + assert_send_type '[T] (T) -> T', + GC, :auto_compact=, untyped + end + ensure + GC.auto_compact = old + end + + def test_latest_compact_info + assert_send_type '() -> GC::compact_info', + GC, :latest_compact_info + end + + def test_measure_total_time + assert_send_type '() -> bool', + GC, :measure_total_time end - def test_set_stress - GC.stress = 0 - GC.stress = true - GC.stress = false + def test_measure_total_time= + old = GC.measure_total_time + + with_untyped do |untyped| + assert_send_type '[T] (T) -> T', + GC, :measure_total_time=, untyped + end + ensure + GC.measure_total_time = old end end +class GCIncludeTest < Test::Unit::TestCase + include TestHelper -class GCSingletonTest < Test::Unit::TestCase + testing '::GC' + + class Foo + extend GC + end + + def test_garbage_collect + assert_send_type '() -> nil', + Foo, :garbage_collect + + # Don't test all combinations of passing args or not, just use them all + with_boolish do |boolish| + assert_send_type '(immediate_sweep: boolish, immediate_mark: boolish, full_mark: boolish) -> nil', + Foo, :garbage_collect, immediate_sweep: boolish, immediate_mark: boolish, full_mark: boolish + end + end +end + +class GC_ProfilerSingletonTest < Test::Unit::TestCase include TestHelper - testing "singleton(::GC)" + testing 'singleton(::GC::Profiler)' + + def test_clear + assert_send_type '() -> nil', + GC::Profiler, :clear + end + + def test_enabled? + assert_send_type '() -> bool', + GC::Profiler, :enabled? + end + + def test_enable_and_disable + was_enabled = GC::Profiler.enabled? + + assert_send_type '() -> nil', + GC::Profiler, :enable + assert_send_type '() -> nil', + GC::Profiler, :disable + ensure + GC::Profiler.enable if was_enabled + end + + def test_raw_data + was_enabled = GC::Profiler.enabled? + GC::Profiler.disable + + assert_send_type '() -> nil', + GC::Profiler, :raw_data + + GC::Profiler.enable + GC.start + assert_send_type '() -> Array[Hash[Symbol, untyped]]', + GC::Profiler, :raw_data + ensure + GC::Profiler.enable if was_enabled + end + + def test_report + old_stdout = $stdout + new_stdout = BlankSlate.new + def new_stdout.write(x) = nil + $stdout = new_stdout + + assert_send_type '() -> nil', + GC::Profiler, :report + + assert_send_type '(GC::Profiler::_Reporter) -> nil', + GC::Profiler, :report, Writer.new + ensure + $stdout = old_stdout + end + + def test_result + assert_send_type '() -> String', + GC::Profiler, :result + end def test_total_time - assert_send_type( - "() -> Integer", - GC, :total_time - ) + assert_send_type '() -> Float', + GC::Profiler, :total_time end end