From b7f5fd7e45ee915f12018c49f11e10c5f1c33393 Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Thu, 31 Oct 2024 18:39:17 -0700 Subject: [PATCH] Keep gauges in sync with GC.stat (#6) Omit defunct stats and support new ones automatically. Stats without a verbose comment default to their own name since the Prometheus exporter requires one. --- Gemfile.lock | 14 ++++--- lib/yabeda/gc.rb | 85 ++++++++++++++++++++---------------------- spec/yabeda/gc_spec.rb | 54 ++------------------------- 3 files changed, 52 insertions(+), 101 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index d9ae94e..e15e575 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -7,11 +7,11 @@ PATH GEM remote: https://rubygems.org/ specs: - anyway_config (2.5.4) - ruby-next-core (>= 0.14.0) + anyway_config (2.6.4) + ruby-next-core (~> 1.0) ast (2.4.2) coderay (1.1.3) - concurrent-ruby (1.2.2) + concurrent-ruby (1.3.4) diff-lcs (1.5.0) dry-initializer (3.1.1) json (2.7.1) @@ -55,16 +55,18 @@ GEM unicode-display_width (>= 2.4.0, < 3.0) rubocop-ast (1.30.0) parser (>= 3.2.1.0) - ruby-next-core (1.0.0) + ruby-next-core (1.0.3) ruby-progressbar (1.13.0) unicode-display_width (2.5.0) - yabeda (0.12.0) + yabeda (0.13.1) anyway_config (>= 1.0, < 3) concurrent-ruby dry-initializer PLATFORMS - arm64-darwin-21 + aarch64-linux + arm64-darwin + x86_64-darwin x86_64-linux DEPENDENCIES diff --git a/lib/yabeda/gc.rb b/lib/yabeda/gc.rb index 887fb69..92512bf 100644 --- a/lib/yabeda/gc.rb +++ b/lib/yabeda/gc.rb @@ -8,54 +8,51 @@ module Yabeda module GC EMPTY_HASH = {}.freeze - Yabeda.configure do - group :gc + # Don't use a constant. No need to retain this after registering gauges. + comments = { + count: "Count of all GCs", + compact_count: "Count of all GC compactions", + minor_gc_count: "Count of minor GCs", + major_gc_count: "Count of major GCs", + heap_allocated_pages: "Total number of pages allocated for the heap", + heap_sorted_length: "Length of the sorted heap", + heap_allocatable_pages: "Total number of allocatable heap pages", + heap_available_slots: "Total number of slots in heap pages", + heap_live_slots: "Number of live objects slots", + heap_free_slots: "Number of free object slots", + heap_final_slots: "Number of object slots with finalizers attached to them", + heap_marked_slots: "Count of old objects which survived more than 3 GC cycles and number of write-barrier unprotected objects", + heap_eden_pages: "Number of pages allocated for the eden heap", + heap_tomb_pages: "Number of pages allocated for the tomb heap", + total_allocated_pages: "Total number of allocated pages over the lifetime of this process", + total_freed_pages: "Total number of freed pages over the lifetime of this process", + total_allocated_objects: "Total number of allocated objects over the lifetime of this process", + total_freed_objects: "Total number of freed objects over the lifetime of this process", + malloc_increase_bytes: "Total bytes allocated to objects", + malloc_increase_bytes_limit: "Bytes limit that will trigger garbage collection of objects", + remembered_wb_unprotected_objects: "Number of write-barrier unprotected objects in the remembered set", + remembered_wb_unprotected_objects_limit: "Limit on write-barrier unprotected objects allowed in the remembered set", + old_objects: "Number of old objects", + old_objects_limit: "Limit of old objects", + oldmalloc_increase_bytes: "Total bytes allocated to old objects", + oldmalloc_increase_bytes_limit: "Bytes limit that will trigger garbage collection of old objects", - gauge :count, tags: [], comment: "Count of all GCs" - gauge :compact_count, tags: [], comment: "Count of all GC compactions" - gauge :minor_gc_count, tags: [], comment: "Count of minor GCs" - gauge :major_gc_count, tags: [], comment: "Count of major GCs" - gauge :heap_allocated_pages, tags: [], comment: "Total number of pages allocated for the heap" - gauge :heap_sorted_length, tags: [], comment: "Length of the sorted heap" - gauge :heap_allocatable_pages, tags: [], comment: "Total number of allocatable heap pages" - gauge :heap_available_slots, tags: [], comment: "Total number of slots in heap pages" - gauge :heap_live_slots, tags: [], comment: "Number of live objects slots" - gauge :heap_free_slots, tags: [], comment: "Number of free object slots" - gauge :heap_final_slots, tags: [], comment: "Number of object slots with finalizers attached to them" - gauge :heap_marked_slots, tags: [], - comment: "Count of old objects which survived more than 3 GC cycles and number of write-barrier unprotected objects" - gauge :heap_eden_pages, tags: [], comment: "Number of pages allocated for the eden heap" - gauge :heap_tomb_pages, tags: [], comment: "Number of pages allocated for the tomb heap" - gauge :total_allocated_pages, tags: [], - comment: "Total number of allocated pages over the lifetime of this process" - gauge :total_freed_pages, tags: [], comment: "Total number of freed pages over the lifetime of this process" - gauge :total_allocated_objects, tags: [], - comment: "Total number of allocated objects over the lifetime of this process" - gauge :total_freed_objects, tags: [], comment: "Total number of freed objects over the lifetime of this process" - gauge :malloc_increase_bytes, tags: [], comment: "Total bytes allocated to objects" - gauge :malloc_increase_bytes_limit, tags: [], - comment: "Bytes limit that will trigger garbage collection of objects" - gauge :remembered_wb_unprotected_objects, tags: [], - comment: "Number of write-barrier unprotected objects in the remembered set" - gauge :remembered_wb_unprotected_objects_limit, tags: [], - comment: "Limit on write-barrier unprotected objects allowed in the remembered set" - gauge :old_objects, tags: [], comment: "Number of old objects" - gauge :old_objects_limit, tags: [], comment: "Limit of old objects" - gauge :oldmalloc_increase_bytes, tags: [], comment: "Total bytes allocated to old objects" - gauge :oldmalloc_increase_bytes_limit, tags: [], - comment: "Bytes limit that will trigger garbage collection of old objects" + # Ruby 3.0 + time: "The total time spent in garbage collections", + read_barrier_faults: "The total number of times the read barrier was triggered during compaction", + total_moved_objects: "The total number of objects compaction has moved", - if RUBY_VERSION >= "3.0" - gauge :time, tags: [], comment: "The total time spent in garbage collections" - gauge :read_barrier_faults, tags: [], comment: "The total number of times the read barrier was triggered during compaction" - gauge :total_moved_objects, tags: [], comment: "The total number of objects compaction has moved" - end + # Ruby 3.3 + marking_time: "Time spent in the marking phase", + sweeping_time: "Time spent in the sweeping phase" + }.freeze - gauge :time, tags: [], comment: "The total time spent in garbage collections" if RUBY_VERSION >= "3.1" + Yabeda.configure do + group :gc - if RUBY_VERSION >= "3.3" - gauge :marking_time, tags: [], comment: "Time spent in the marking phase" - gauge :sweeping_time, tags: [], comment: "Time spent in the sweeping phase" + # Register gauges for all GC stats. Include our optional commentary. + ::GC.stat.each_key do |stat_name| + gauge stat_name, tags: [], comment: comments.fetch(stat_name, stat_name) end collect do diff --git a/spec/yabeda/gc_spec.rb b/spec/yabeda/gc_spec.rb index d144eec..b40a04d 100644 --- a/spec/yabeda/gc_spec.rb +++ b/spec/yabeda/gc_spec.rb @@ -7,57 +7,9 @@ subject { Yabeda.collect! } - it "tracks metrics for GC" do - expect { subject }.to( - update_yabeda_gauge(Yabeda.gc.count).with(be_a(Integer)) - .and(update_yabeda_gauge(Yabeda.gc.heap_allocatable_pages).with(be_a(Integer))) - .and(update_yabeda_gauge(Yabeda.gc.heap_allocated_pages).with(be_a(Integer))) - .and(update_yabeda_gauge(Yabeda.gc.heap_available_slots).with(be_a(Integer))) - .and(update_yabeda_gauge(Yabeda.gc.heap_eden_pages).with(be_a(Integer))) - .and(update_yabeda_gauge(Yabeda.gc.heap_final_slots).with(be_a(Integer))) - .and(update_yabeda_gauge(Yabeda.gc.heap_free_slots).with(be_a(Integer))) - .and(update_yabeda_gauge(Yabeda.gc.heap_live_slots).with(be_a(Integer))) - .and(update_yabeda_gauge(Yabeda.gc.heap_marked_slots).with(be_a(Integer))) - .and(update_yabeda_gauge(Yabeda.gc.heap_sorted_length).with(be_a(Integer))) - .and(update_yabeda_gauge(Yabeda.gc.heap_tomb_pages).with(be_a(Integer))) - .and(update_yabeda_gauge(Yabeda.gc.major_gc_count).with(be_a(Integer))) - .and(update_yabeda_gauge(Yabeda.gc.malloc_increase_bytes).with(be_a(Integer))) - .and(update_yabeda_gauge(Yabeda.gc.malloc_increase_bytes_limit).with(be_a(Integer))) - .and(update_yabeda_gauge(Yabeda.gc.minor_gc_count).with(be_a(Integer))) - .and(update_yabeda_gauge(Yabeda.gc.old_objects).with(be_a(Integer))) - .and(update_yabeda_gauge(Yabeda.gc.old_objects_limit).with(be_a(Integer))) - .and(update_yabeda_gauge(Yabeda.gc.oldmalloc_increase_bytes).with(be_a(Integer))) - .and(update_yabeda_gauge(Yabeda.gc.oldmalloc_increase_bytes_limit).with(be_a(Integer))) - .and(update_yabeda_gauge(Yabeda.gc.remembered_wb_unprotected_objects).with(be_a(Integer))) - .and(update_yabeda_gauge(Yabeda.gc.remembered_wb_unprotected_objects_limit).with(be_a(Integer))) - .and(update_yabeda_gauge(Yabeda.gc.total_allocated_objects).with(be_a(Integer))) - .and(update_yabeda_gauge(Yabeda.gc.total_allocated_pages).with(be_a(Integer))) - .and(update_yabeda_gauge(Yabeda.gc.total_freed_objects).with(be_a(Integer))) - .and(update_yabeda_gauge(Yabeda.gc.total_freed_pages).with(be_a(Integer))) - ) - end - - if RUBY_VERSION >= "3.0" - it "tracks ruby3 metrics for GC" do - expect { subject }.to( - update_yabeda_gauge(Yabeda.gc.read_barrier_faults).with(be_a(Integer)) - .and(update_yabeda_gauge(Yabeda.gc.total_moved_objects).with(be_a(Integer))) - ) - end - end - - if RUBY_VERSION >= "3.1" - it "tracks Ruby 3.1 time metrics for GC" do - expect { subject }.to update_yabeda_gauge(Yabeda.gc.time).with(be_a(Integer)) - end - end - - if RUBY_VERSION >= "3.3" - it "tracks Ruby 3.3 time metrics for GC" do - expect { subject }.to( - update_yabeda_gauge(Yabeda.gc.marking_time).with(be_a(Integer)) - .and(update_yabeda_gauge(Yabeda.gc.sweeping_time).with(be_a(Integer))) - ) + ::GC.stat.each_key do |stat_name| + it "tracks #{stat_name}" do + expect { subject }.to(update_yabeda_gauge(Yabeda.gc.__send__(stat_name)).with(be_a(Integer))) end end end