Skip to content

Commit

Permalink
Keep gauges in sync with GC.stat (#6)
Browse files Browse the repository at this point in the history
Omit defunct stats and support new ones automatically.

Stats without a verbose comment default to their own name since the Prometheus exporter requires one.
  • Loading branch information
jeremy authored Nov 1, 2024
1 parent d8a9fa2 commit b7f5fd7
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 101 deletions.
14 changes: 8 additions & 6 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
85 changes: 41 additions & 44 deletions lib/yabeda/gc.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
54 changes: 3 additions & 51 deletions spec/yabeda/gc_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit b7f5fd7

Please sign in to comment.