DEV: Seperate concerns of tracking GC stat from `MethodProfiler` (#22921)

Why this change?

This is a follow up to e8f7b62752.
Tracking of GC stats didn't really belong in the `MethodProfiler` class
so we want to extract that concern into its own class.

As part of this PR, the `track_gc_stat_per_request` site setting has
also been renamed to `instrument_gc_stat_per_request`.
This commit is contained in:
Alan Guo Xiang Tan 2023-08-02 10:46:37 +08:00 committed by GitHub
parent e8f7b62752
commit 773b22e8d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 83 additions and 39 deletions

View File

@ -43,6 +43,7 @@ Rails.autoloaders.each do |autoloader|
"json" => "JSON",
"ssrf_detector" => "SSRFDetector",
"http" => "HTTP",
"gc_stat_instrumenter" => "GCStatInstrumenter",
)
end
Rails.autoloaders.main.ignore(

View File

@ -2148,7 +2148,7 @@ developer:
max_sidebar_section_links:
default: 50
hidden: true
track_gc_stat_per_request:
instrument_gc_stat_per_request:
default: false
hidden: true

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class GCStatInstrumenter
def self.instrument
start_gc_stat = GC.stat
yield
end_gc_stat = GC.stat
{
gc: {
time: (end_gc_stat[:time] - start_gc_stat[:time]) / 1000.0,
major_count: end_gc_stat[:major_gc_count] - start_gc_stat[:major_gc_count],
minor_count: end_gc_stat[:minor_gc_count] - start_gc_stat[:minor_gc_count],
},
}
end
end

View File

@ -89,13 +89,8 @@ class MethodProfiler
end
def self.start(transfer = nil)
Thread.current[:_method_profiler] = if transfer
transfer
else
data = { __start: Process.clock_gettime(Process::CLOCK_MONOTONIC) }
data[:gc_stat] = GC.stat if SiteSetting.track_gc_stat_per_request
data
end
Thread.current[:_method_profiler] = transfer ||
{ __start: Process.clock_gettime(Process::CLOCK_MONOTONIC) }
end
def self.clear
@ -109,15 +104,6 @@ class MethodProfiler
Thread.current[:_method_profiler] = nil
start = data.delete(:__start)
data[:total_duration] = finish - start
if start_gc_stat = data.delete(:gc_stat)
end_gc_stat = GC.stat
data[:gc] ||= {}
data[:gc][:time] = (end_gc_stat[:time] - start_gc_stat[:time]) / 1000.0
data[:gc][:major_count] = end_gc_stat[:major_gc_count] - start_gc_stat[:major_gc_count]
data[:gc][:minor_count] = end_gc_stat[:minor_gc_count] - start_gc_stat[:minor_gc_count]
end
end
data

View File

@ -201,6 +201,7 @@ class Middleware::RequestTracker
def call(env)
result = nil
info = nil
gc_stat_timing = nil
# doing this as early as possible so we have an
# accurate counter
@ -225,7 +226,13 @@ class Middleware::RequestTracker
env["discourse.request_tracker"] = self
MethodProfiler.start
result = @app.call(env)
if SiteSetting.instrument_gc_stat_per_request
gc_stat_timing = GCStatInstrumenter.instrument { result = @app.call(env) }
else
result = @app.call(env)
end
info = MethodProfiler.stop
# possibly transferred?
@ -263,7 +270,11 @@ class Middleware::RequestTracker
end
end
end
log_request_info(env, result, info, request) if !env["discourse.request_tracker.skip"]
if !env["discourse.request_tracker.skip"]
info.merge!(gc_stat_timing) if gc_stat_timing
log_request_info(env, result, info, request)
end
end
def log_later(data)

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
RSpec.describe GCStatInstrumenter do
describe ".instrument" do
it "returns a hash with the GC time, major and minor GC count for executing the given block" do
result =
described_class.instrument do
GC.start(full_mark: true) # Major GC
GC.start(full_mark: false) # Minor GC
end
expect(result[:gc]).to be_present
expect(result[:gc][:time]).to be >= 0.0
expect(result[:gc][:major_count]).to eq(1)
expect(result[:gc][:minor_count]).to eq(1)
end
end
end

View File

@ -37,24 +37,4 @@ RSpec.describe MethodProfiler do
expect(result[:at_beach][:calls]).to eq(2)
end
it "profiles GC stat information when `track_gc_stat_per_request` site setting has been enabled" do
MethodProfiler.start
GC.start(full_mark: false) # Minor GC
result = MethodProfiler.stop
expect(result[:gc]).not_to be_present
SiteSetting.track_gc_stat_per_request = true
MethodProfiler.start
GC.start(full_mark: true) # Major GC
GC.start(full_mark: false) # Minor GC
result = MethodProfiler.stop
expect(result[:gc]).to be_present
expect(result[:gc][:time]).to be >= 0.0
expect(result[:gc][:major_count]).to eq(1)
expect(result[:gc][:minor_count]).to eq(1)
end
end

View File

@ -661,6 +661,7 @@ RSpec.describe Middleware::RequestTracker do
lambda do |env|
sql_calls.times { User.where(id: -100).pluck(:id) }
redis_calls.times { Discourse.redis.get("x") }
yield if block_given?
result
end
end
@ -749,6 +750,36 @@ RSpec.describe Middleware::RequestTracker do
expect(headers["X-Runtime"].to_f).to be > 0
end
it "correctly logs GC stats when `instrument_gc_stat_per_request` site setting has been enabled" do
tracker =
Middleware::RequestTracker.new(
app([200, {}, []]) do
GC.start(full_mark: true) # Major GC
GC.start(full_mark: false) # Minor GC
end,
)
tracker.call(env)
expect(@data[:timing][:gc]).to eq(nil)
SiteSetting.instrument_gc_stat_per_request = true
tracker =
Middleware::RequestTracker.new(
app([200, {}, []]) do
GC.start(full_mark: true) # Major GC
GC.start(full_mark: false) # Minor GC
end,
)
tracker.call(env)
expect(@data[:timing][:gc][:time]).to be > 0.0
expect(@data[:timing][:gc][:major_count]).to eq(1)
expect(@data[:timing][:gc][:minor_count]).to eq(1)
end
it "can correctly log messagebus request types" do
tracker = Middleware::RequestTracker.new(app([200, {}, []]))