mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
eb31c9ed59
PiperOrigin-RevId: 547942962
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(); 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
|
|
}
|