Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable GC profiling when it's enabled in an app #879

Merged
merged 1 commit into from
Aug 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changesets/listen-if-garbage-collection-is-enabled.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
bump: "patch"
type: "change"
---

Listen if the Ruby Garbage Collection profiler is enabled and collect how long the GC is running for the Ruby VM magic dashboard. An app will need to call `GC::Profiler.enable` to enable the GC profiler. Do not enable this in production environments, or at least not for long, because this can negatively impact performance of apps.
23 changes: 19 additions & 4 deletions lib/appsignal/garbage_collection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,34 @@ module Appsignal
module GarbageCollection
# Return the GC profiler wrapper.
#
# Temporarily always returns the {NilProfiler}.
# Returns {Profiler} if the Ruby Garbage Collection profiler is enabled.
# This is checked by calling `GC::Profiler.enabled?`.
#
# GC profiling is disabled by default due to the overhead it causes. Do not
# enable this in production for long periods of time.
def self.profiler
@profiler ||= NilProfiler.new
# Cached instances so it doesn't create a new object every time this
# method is called. Especially necessary for the {Profiler} because a new
# instance will have a new internal time counter.
@real_profiler ||= Profiler.new
@nil_profiler ||= NilProfiler.new

enabled? ? @real_profiler : @nil_profiler
end

# Check if Garbage Collection is enabled at the moment.
#
# @return [Boolean]
def self.enabled?
GC::Profiler.enabled?
end

# Unset the currently cached profiler
# Unset the currently cached profilers.
#
# @return [void]
def self.clear_profiler!
@profiler = nil
@real_profiler = nil
@nil_profiler = nil
end

# A wrapper around Ruby's `GC::Profiler` that tracks garbage collection
Expand Down
2 changes: 1 addition & 1 deletion lib/appsignal/probes/mri.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def call
)

set_gauge("thread_count", Thread.list.size)
if GC::Profiler.enabled?
if Appsignal::GarbageCollection.enabled?
gauge_delta(:gc_time, @gc_profiler.total_time) do |gc_time|
set_gauge("gc_time", gc_time) if gc_time > 0
end
Expand Down
5 changes: 4 additions & 1 deletion spec/lib/appsignal/garbage_collection_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@
end

context "when GC profiling is enabled" do
before { GC::Profiler.enable }
after { GC::Profiler.disable }

it "returns the Profiler" do
expect(described_class.profiler).to be_a(Appsignal::GarbageCollection::NilProfiler)
expect(described_class.profiler).to be_a(Appsignal::GarbageCollection::Profiler)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion spec/lib/appsignal/probes/mri_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def set_gauge(*args) # rubocop:disable Naming/AccessorMethodName
expect(metrics).to_not include("gc_time")
end

it "does not report a gc_time metric while disable" do
it "does not report a gc_time metric while temporarily disabled" do
# While enabled
allow(GC::Profiler).to receive(:enabled?).and_return(true)
expect(gc_profiler_mock).to receive(:total_time).and_return(10, 15)
Expand Down