Skip to content
Open
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
9 changes: 9 additions & 0 deletions .chloggen/memorylimiter-disable-gc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
change_type: enhancement

component: processor/memory_limiter

note: Add `disable_gc` option to allow disabling forced garbage collection.

issues: [15081]

change_logs: [user]
3 changes: 3 additions & 0 deletions internal/memorylimiter/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ type Config struct {
// GCs is a CPU-heavy operation and executing it too frequently may affect the recovery capabilities of the collector.
MinGCIntervalWhenHardLimited time.Duration `mapstructure:"min_gc_interval_when_hard_limited"`

// DisableGC disables forced garbage collection.
DisableGC bool `mapstructure:"disable_gc"`

// MemoryLimitMiB is the maximum amount of memory, in MiB, targeted to be
// allocated by the process.
MemoryLimitMiB uint32 `mapstructure:"limit_mib"`
Expand Down
1 change: 1 addition & 0 deletions internal/memorylimiter/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func TestUnmarshalConfig(t *testing.T) {
CheckInterval: 5 * time.Second,
MemoryLimitMiB: 4000,
MemorySpikeLimitMiB: 500,
DisableGC: true,
}, cfg)
}

Expand Down
6 changes: 4 additions & 2 deletions internal/memorylimiter/memorylimiter.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type MemoryLimiter struct {
minGCIntervalWhenSoftLimited time.Duration
minGCIntervalWhenHardLimited time.Duration
lastGCDone time.Time
gcDisabled bool

// The functions to read the mem values and run GC are set as a reference to help with
// testing different values.
Expand Down Expand Up @@ -80,6 +81,7 @@ func NewMemoryLimiter(cfg *Config, logger *zap.Logger) (*MemoryLimiter, error) {
minGCIntervalWhenSoftLimited: cfg.MinGCIntervalWhenSoftLimited,
minGCIntervalWhenHardLimited: cfg.MinGCIntervalWhenHardLimited,
lastGCDone: time.Now(),
gcDisabled: cfg.DisableGC,
readMemStatsFn: ReadMemStatsFn,
runGCFn: runtime.GC,
logger: logger,
Expand Down Expand Up @@ -186,7 +188,7 @@ func (ml *MemoryLimiter) CheckMemLimits() {
if ml.usageChecker.aboveHardLimit(ms) {
// We are above hard limit, do a GC if it wasn't done recently and see if
// it brings memory usage below the soft limit.
if time.Since(ml.lastGCDone) > ml.minGCIntervalWhenHardLimited {
if !ml.gcDisabled && time.Since(ml.lastGCDone) > ml.minGCIntervalWhenHardLimited {
ml.logger.Warn("Memory usage is above hard limit. Forcing a GC.", memstatToZapField(ms))
ms = ml.doGCandReadMemStats()
// Check the limit again to see if GC helped.
Expand All @@ -195,7 +197,7 @@ func (ml *MemoryLimiter) CheckMemLimits() {
} else {
// We are above soft limit, do a GC if it wasn't done recently and see if
// it brings memory usage below the soft limit.
if time.Since(ml.lastGCDone) > ml.minGCIntervalWhenSoftLimited {
if !ml.gcDisabled && time.Since(ml.lastGCDone) > ml.minGCIntervalWhenSoftLimited {
ml.logger.Info("Memory usage is above soft limit. Forcing a GC.", memstatToZapField(ms))
ms = ml.doGCandReadMemStats()
// Check the limit again to see if GC helped.
Expand Down
24 changes: 24 additions & 0 deletions internal/memorylimiter/memorylimiter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,30 @@ func TestCallGCWhenSoftLimit(t *testing.T) {
memAllocMiB: [2]uint64{45, 55},
numGCs: 2,
},
{
name: "No GC when soft limited and GC disabled",
mlCfg: &Config{
CheckInterval: 1 * time.Minute,
MinGCIntervalWhenSoftLimited: 0,
MemoryLimitMiB: 50,
MemorySpikeLimitMiB: 10,
DisableGC: true,
},
memAllocMiB: [2]uint64{45, 45},
numGCs: 0,
},
{
name: "No GC when hard limited and GC disabled",
mlCfg: &Config{
CheckInterval: 1 * time.Minute,
MinGCIntervalWhenHardLimited: 0,
MemoryLimitMiB: 50,
MemorySpikeLimitMiB: 10,
DisableGC: true,
},
memAllocMiB: [2]uint64{55, 55},
numGCs: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
3 changes: 3 additions & 0 deletions internal/memorylimiter/testdata/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ limit_mib: 4000

# The maximum, in MiB, spike expected between the measurements of memory usage.
spike_limit_mib: 500

# When set to true, disables forced garbage collection.
disable_gc: true
2 changes: 2 additions & 0 deletions processor/memorylimiterprocessor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ measurements of memory usage. The value must be less than `limit_percentage`.
This option is used to calculate `spike_limit_mib` from the total available memory.
For instance setting of 25% with the total memory of 1GiB will result in the spike limit of 250MiB.
This option is intended to be used only with `limit_percentage`.
- `disable_gc` (default = false): When set to true, disables forced garbage collection.
The processor will still refuse data when memory limits are exceeded but will not trigger GC.

Examples:

Expand Down