Skip to content

Commit

Permalink
Keep gauges in sync with GC.stat
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.

Example on Ruby 3.4.0-preview2:
```ruby
>> GC.stat.keys - Yabeda.gc.methods(false)
=> [:heap_empty_pages, :heap_allocatable_slots]
>> Yabeda.gc.methods(false) - GC.stat.keys
=> [:heap_sorted_length, :heap_allocatable_pages, :heap_tomb_pages]
```
  • Loading branch information
jeremy committed Oct 31, 2024
1 parent d8a9fa2 commit f5fad94
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 f5fad94

Please sign in to comment.