33module GCStats
44 module_function
55
6- SCALAR_FIELDS = [
7- [ "gc_count" , :count ] ,
8- [ "gc_global_count" , :global_gc_count ] ,
6+ STAT_FIELDS = [
7+ [ "gc_global_count" , :global_gc_invocation_count ] ,
98 [ "gc_major_count" , :major_gc_count ] ,
109 [ "gc_minor_count" , :minor_gc_count ] ,
1110 [ "gc_marking_time" , :marking_time ] ,
@@ -14,7 +13,7 @@ module GCStats
1413
1514 TOTAL_TIME_FIELD = "gc_total_time_ns"
1615
17- SCALAR_FIELD_NAMES = ( SCALAR_FIELDS . map ( &:first ) + [ TOTAL_TIME_FIELD ] ) . freeze
16+ SCALAR_FIELD_NAMES = ( [ "gc_count" ] + STAT_FIELDS . map ( &:first ) + [ TOTAL_TIME_FIELD ] ) . freeze
1817
1918 RACTOR_SERIES = {
2019 "gc_count_bench" => "gc_count" ,
@@ -44,11 +43,15 @@ def global_gc_attributed?
4443 Warning [ :experimental ] = false
4544 $stderr. reopen ( File ::NULL , "w" )
4645 begin
47- main_before = GC . stat ( :global_gc_count )
48- process_before = GC . stat ( :global_gc_count , scope : :global )
49- Ractor . new { GC . start ( full_mark : true , immediate_mark : true , immediate_sweep : true , global : true ) } . join
50- attributed = GC . stat ( :global_gc_count ) == main_before
51- ran = GC . stat ( :global_gc_count , scope : :global ) > process_before
46+ main_before = GC . stat ( :global_gc_invocation_count )
47+ process_before = GC . stat ( :global_gc_count , process : true )
48+ started = Ractor . new do
49+ before = GC . stat ( :global_gc_invocation_count )
50+ GC . start ( full_mark : true , immediate_mark : true , immediate_sweep : true , global : true )
51+ GC . stat ( :global_gc_invocation_count ) - before
52+ end . value
53+ attributed = started == 1 && GC . stat ( :global_gc_invocation_count ) == main_before
54+ ran = GC . stat ( :global_gc_count , process : true ) > process_before
5255 exit! ( attributed && ran ? 0 : 1 )
5356 rescue StandardError
5457 exit! ( 2 )
@@ -66,7 +69,7 @@ def check_ractor_gc_support!
6669 raise NotImplementedError , "Ractor GC metrics require GC.total_time and GC.measure_total_time="
6770 end
6871 unless global_gc_attributed?
69- raise NotImplementedError , "Ractor GC metrics require per-Ractor global GC attribution (ruby/ruby#19147 )"
72+ raise NotImplementedError , "Ractor GC metrics require per-Ractor global GC attribution (ruby/ruby#19383 )"
7073 end
7174 end
7275
@@ -93,17 +96,27 @@ def numeric_delta(before, after)
9396 after - before if before . is_a? ( Numeric ) && after . is_a? ( Numeric )
9497 end
9598
99+ # :count includes global GCs that other Ractors started. Count only the ones
100+ # this Ractor started, so that sums across Ractors count each GC once.
101+ def started_gc_count ( stat )
102+ count , swept , started = stat . values_at ( :count , :global_gc_count , :global_gc_invocation_count )
103+ return count unless [ count , swept , started ] . all? ( Numeric )
104+
105+ count - swept + started
106+ end
107+
96108 def snapshot
97109 {
98- stat : GC . stat ( scope : :ractor ) ,
110+ stat : GC . stat ,
99111 heap : heap_snapshot ,
100112 total_time_ns : GC . respond_to? ( :total_time ) ? GC . total_time : nil ,
101113 }
102114 end
103115
104116 def delta ( before , after )
105- sample = SCALAR_FIELDS . each_with_object ( { } ) do |( name , key ) , result |
106- result [ name ] = numeric_delta ( before [ :stat ] [ key ] , after [ :stat ] [ key ] )
117+ sample = { "gc_count" => numeric_delta ( started_gc_count ( before [ :stat ] ) , started_gc_count ( after [ :stat ] ) ) }
118+ STAT_FIELDS . each do |name , key |
119+ sample [ name ] = numeric_delta ( before [ :stat ] [ key ] , after [ :stat ] [ key ] )
107120 end
108121 sample [ TOTAL_TIME_FIELD ] = numeric_delta ( before [ :total_time_ns ] , after [ :total_time_ns ] )
109122 sample [ "gc_stat_heap_delta" ] = heap_delta ( before [ :heap ] , after [ :heap ] )
0 commit comments