Skip to content
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
29 changes: 21 additions & 8 deletions manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,13 @@ type Options struct {
// `RLIMIT_MEMLOCK` If a limit is provided here it will be applied when the manager is initialized.
RemoveRlimit bool

// KeepKernelBTF - Defines if the kernel types defined in VerifierOptions.Programs.KernelTypes should be cleaned up
// once the manager is done using them. By default, the manager will clean them up to save up space. DISCLAIMER: if
// your program uses "manager.CloneProgram", you might want to enable "KeepKernelBTF". As a workaround, you can also
// try to strip as much as possible the content of "KernelTypes" to reduce the memory overhead.
// KeepKernelBTF - Defines if the kernel BTF used while loading programs should be cleaned up once the manager is done
// using them. This governs both VerifierOptions.Programs.KernelTypes and the shared VerifierOptions.Cache. By default,
// the manager will clean both up after Start() to save up space: otherwise a long-lived manager keeps the parsed
// kernel BTF (vmlinux) pinned for the whole process lifetime, even though it is only needed while loading programs.
// DISCLAIMER: if your program uses "manager.CloneProgram", you might want to enable "KeepKernelBTF", as cloning
// reloads a program and needs "KernelTypes" for its CO-RE relocations. As a workaround, you can also try to strip as
// much as possible the content of "KernelTypes" to reduce the memory overhead.
KeepKernelBTF bool

// SkipPerfMapReaderStartup - Perf maps whose name is set to true with this option will not have their reader goroutine started when calling the manager.Start() function.
Expand Down Expand Up @@ -770,6 +773,18 @@ func (m *Manager) setupBypass() (*Map, error) {
return bypassMap, nil
}

// releaseKernelBTF drops the references to the kernel BTF that are only needed while loading programs, so a long-lived
// manager does not pin the parsed kernel BTF (vmlinux) for the whole process lifetime. Both the KernelTypes spec passed
// to the verifier and the shared BTF cache are cleared, symmetrically. This is a no-op when KeepKernelBTF is set (for
// example when the caller relies on CloneProgram, which reloads programs and needs KernelTypes for CO-RE relocations).
func (m *Manager) releaseKernelBTF() {
if m.options.KeepKernelBTF {
return
}
m.options.VerifierOptions.Programs.KernelTypes = nil
m.options.VerifierOptions.Cache = nil
}

// Start - Attach eBPF programs, start perf ring readers and apply maps and tail calls routing.
func (m *Manager) Start() error {
m.stateLock.Lock()
Expand All @@ -782,10 +797,8 @@ func (m *Manager) Start() error {
return nil
}

if !m.options.KeepKernelBTF {
// release kernel BTF. It should no longer be needed
m.options.VerifierOptions.Programs.KernelTypes = nil
}
// release kernel BTF: it is only needed while loading programs and should no longer be needed now
m.releaseKernelBTF()

// clean up tracefs
if err := m.cleanupTraceFS(); err != nil {
Expand Down
34 changes: 34 additions & 0 deletions manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/cilium/ebpf"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/btf"
"github.com/cilium/ebpf/rlimit"
)

Expand Down Expand Up @@ -274,3 +275,36 @@ func TestLoadELF(t *testing.T) {
t.Errorf("LoadELF() error = %v, expected: %v", err, ErrManagerELFLoaded)
}
}

func TestReleaseKernelBTF(t *testing.T) {
// releaseKernelBTF (called by Start) must drop both the KernelTypes spec and the shared BTF
// cache once loading is done, unless the caller opted into keeping them via KeepKernelBTF.
// Otherwise a long-lived manager keeps the parsed kernel BTF (vmlinux) pinned for the whole
// process lifetime, which regressed idle memory for a downstream consumer.
tests := []struct {
name string
keepKernelBTF bool
wantCleared bool
}{
{name: "released by default", keepKernelBTF: false, wantCleared: true},
{name: "retained when KeepKernelBTF is set", keepKernelBTF: true, wantCleared: false},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := &Manager{}
m.options.KeepKernelBTF = tt.keepKernelBTF
m.options.VerifierOptions.Programs.KernelTypes = &btf.Spec{}
m.options.VerifierOptions.Cache = btf.NewCache()

m.releaseKernelBTF()

if cleared := m.options.VerifierOptions.Programs.KernelTypes == nil; cleared != tt.wantCleared {
t.Errorf("KernelTypes cleared = %v, want %v", cleared, tt.wantCleared)
}
if cleared := m.options.VerifierOptions.Cache == nil; cleared != tt.wantCleared {
t.Errorf("Cache cleared = %v, want %v", cleared, tt.wantCleared)
}
})
}
}
Loading