mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
0fd906d7e0
The total(sandbox) memory usage using the GetContainerMemoryUsage API will return incorrect usage when called before calling the API for each individual containers in the sandbox. This is because the memory usage for the containers cgroup is not updated while calculating the total usage. This CL fixes it by updating the usage for every child cgroup, which will return the correct memory usage for the parent cgroup. PiperOrigin-RevId: 574300913
223 lines
6.5 KiB
Go
223 lines
6.5 KiB
Go
// Copyright 2021 The gVisor Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package control
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"runtime"
|
|
|
|
"golang.org/x/sys/unix"
|
|
"gvisor.dev/gvisor/pkg/sentry/fsmetric"
|
|
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
|
"gvisor.dev/gvisor/pkg/sentry/usage"
|
|
"gvisor.dev/gvisor/pkg/urpc"
|
|
)
|
|
|
|
// Usage includes usage-related RPC stubs.
|
|
type Usage struct {
|
|
Kernel *kernel.Kernel
|
|
}
|
|
|
|
// MemoryUsageOpts contains usage options.
|
|
type MemoryUsageOpts struct {
|
|
// Full indicates that a full accounting should be done. If Full is not
|
|
// specified, then a partial accounting will be done, and Unknown will
|
|
// contain a majority of memory. See Collect for more information.
|
|
Full bool `json:"Full"`
|
|
}
|
|
|
|
// MemoryUsage is a memory usage structure.
|
|
type MemoryUsage struct {
|
|
Unknown uint64 `json:"Unknown"`
|
|
System uint64 `json:"System"`
|
|
Anonymous uint64 `json:"Anonymous"`
|
|
PageCache uint64 `json:"PageCache"`
|
|
Mapped uint64 `json:"Mapped"`
|
|
Tmpfs uint64 `json:"Tmpfs"`
|
|
Ramdiskfs uint64 `json:"Ramdiskfs"`
|
|
Total uint64 `json:"Total"`
|
|
}
|
|
|
|
// MemoryUsageFileOpts contains usage file options.
|
|
type MemoryUsageFileOpts struct {
|
|
// Version is used to ensure both sides agree on the format of the
|
|
// shared memory buffer.
|
|
Version uint64 `json:"Version"`
|
|
}
|
|
|
|
// MemoryUsageFile contains the file handle to the usage file.
|
|
type MemoryUsageFile struct {
|
|
urpc.FilePayload
|
|
}
|
|
|
|
// UsageFD returns the file that tracks the memory usage of the application.
|
|
func (u *Usage) UsageFD(opts *MemoryUsageFileOpts, out *MemoryUsageFile) error {
|
|
// Only support version 1 for now.
|
|
if opts.Version != 1 {
|
|
return fmt.Errorf("unsupported version requested: %d", opts.Version)
|
|
}
|
|
|
|
mf := u.Kernel.MemoryFile()
|
|
*out = MemoryUsageFile{
|
|
FilePayload: urpc.FilePayload{
|
|
Files: []*os.File{
|
|
usage.MemoryAccounting.File,
|
|
mf.File(),
|
|
},
|
|
},
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Collect returns memory used by the sandboxed application.
|
|
func (u *Usage) Collect(opts *MemoryUsageOpts, out *MemoryUsage) error {
|
|
if opts.Full {
|
|
// Ensure everything is up to date.
|
|
if err := u.Kernel.MemoryFile().UpdateUsage(nil); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Copy out a snapshot.
|
|
snapshot, total := usage.MemoryAccounting.Copy()
|
|
*out = MemoryUsage{
|
|
System: snapshot.System,
|
|
Anonymous: snapshot.Anonymous,
|
|
PageCache: snapshot.PageCache,
|
|
Mapped: snapshot.Mapped,
|
|
Tmpfs: snapshot.Tmpfs,
|
|
Ramdiskfs: snapshot.Ramdiskfs,
|
|
Total: total,
|
|
}
|
|
} else {
|
|
// Get total usage from the MemoryFile implementation.
|
|
total, err := u.Kernel.MemoryFile().TotalUsage()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// The memory accounting is guaranteed to be accurate only when
|
|
// UpdateUsage is called. If UpdateUsage is not called, then only Mapped
|
|
// will be up-to-date.
|
|
snapshot, _ := usage.MemoryAccounting.Copy()
|
|
*out = MemoryUsage{
|
|
Unknown: total,
|
|
Mapped: snapshot.Mapped,
|
|
Total: total + snapshot.Mapped,
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// UsageReduceOpts contains options to Usage.Reduce().
|
|
type UsageReduceOpts struct {
|
|
// If Wait is `true`, Reduce blocks until all activity initiated by
|
|
// Usage.Reduce() has completed.
|
|
// If Wait is `false`, Go garbage collection is still performed and may
|
|
// still block for some time, unless `DoNotGC` is `true`.
|
|
Wait bool `json:"wait"`
|
|
|
|
// If DoNotGC is true, Reduce does not explicitly run Go garbage collection.
|
|
// Garbage collection may block for an indeterminate amount of time.
|
|
// Note that the runtime Go may still perform routine garbage collection at
|
|
// any time during program execution, so a routine GC is still possible even
|
|
// when this option set to `true`.
|
|
DoNotGC bool `json:"do_not_gc"`
|
|
}
|
|
|
|
// UsageReduceOutput contains output from Usage.Reduce().
|
|
type UsageReduceOutput struct{}
|
|
|
|
// Reduce requests that the sentry attempt to reduce its memory usage.
|
|
func (u *Usage) Reduce(opts *UsageReduceOpts, out *UsageReduceOutput) error {
|
|
mf := u.Kernel.MemoryFile()
|
|
mf.StartEvictions()
|
|
if opts.Wait {
|
|
mf.WaitForEvictions()
|
|
}
|
|
if !opts.DoNotGC {
|
|
runtime.GC()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// MemoryUsageRecord contains the mapping and platform memory file.
|
|
type MemoryUsageRecord struct {
|
|
mmap uintptr
|
|
stats *usage.RTMemoryStats
|
|
mf os.File
|
|
}
|
|
|
|
// NewMemoryUsageRecord creates a new MemoryUsageRecord from usageFile and
|
|
// platformFile.
|
|
func NewMemoryUsageRecord(usageFile, platformFile os.File) (*MemoryUsageRecord, error) {
|
|
mmap, _, e := unix.RawSyscall6(unix.SYS_MMAP, 0, usage.RTMemoryStatsSize, unix.PROT_READ, unix.MAP_SHARED, usageFile.Fd(), 0)
|
|
if e != 0 {
|
|
return nil, fmt.Errorf("mmap returned %d, want 0", e)
|
|
}
|
|
|
|
m := MemoryUsageRecord{
|
|
mmap: mmap,
|
|
stats: usage.RTMemoryStatsPointer(mmap),
|
|
mf: platformFile,
|
|
}
|
|
|
|
runtime.SetFinalizer(&m, finalizer)
|
|
return &m, nil
|
|
}
|
|
|
|
// GetFileIoStats writes the read times in nanoseconds to out.
|
|
func (*Usage) GetFileIoStats(_ *struct{}, out *string) error {
|
|
fileIoStats := struct {
|
|
// The total amount of time spent reading. The map maps gopher prefixes
|
|
// to the total time spent reading. Times not included in a known prefix
|
|
// are placed in the "/" prefix.
|
|
ReadWait map[string]uint64 `json:"ReadWait"`
|
|
// The total amount of time spent reading. The map maps gopher prefixes
|
|
// to the total time spent reading. Times not included in a known prefix
|
|
// are placed in the "/" prefix.
|
|
ReadWait9P map[string]uint64 `json:"ReadWait9P"`
|
|
}{
|
|
ReadWait: map[string]uint64{"/": fsmetric.ReadWait.Value()},
|
|
ReadWait9P: map[string]uint64{"/": fsmetric.GoferReadWait9P.Value()},
|
|
}
|
|
|
|
m, err := json.Marshal(fileIoStats)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
*out = string(m)
|
|
return nil
|
|
}
|
|
|
|
func finalizer(m *MemoryUsageRecord) {
|
|
unix.RawSyscall(unix.SYS_MUNMAP, m.mmap, usage.RTMemoryStatsSize, 0)
|
|
}
|
|
|
|
// Fetch fetches the usage info from a MemoryUsageRecord.
|
|
func (m *MemoryUsageRecord) Fetch() (mapped, unknown, total uint64, err error) {
|
|
var stat unix.Stat_t
|
|
if err := unix.Fstat(int(m.mf.Fd()), &stat); err != nil {
|
|
return 0, 0, 0, err
|
|
}
|
|
fmem := uint64(stat.Blocks) * 512
|
|
rtmapped := m.stats.RTMapped.Load()
|
|
return rtmapped, fmem, rtmapped + fmem, nil
|
|
}
|