github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/sentry/control/usage.go (about)

     1  // Copyright 2021 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package control
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"os"
    21  	"runtime"
    22  
    23  	"golang.org/x/sys/unix"
    24  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/fsmetric"
    25  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/kernel"
    26  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/usage"
    27  	"github.com/nicocha30/gvisor-ligolo/pkg/urpc"
    28  )
    29  
    30  // Usage includes usage-related RPC stubs.
    31  type Usage struct {
    32  	Kernel *kernel.Kernel
    33  }
    34  
    35  // MemoryUsageOpts contains usage options.
    36  type MemoryUsageOpts struct {
    37  	// Full indicates that a full accounting should be done. If Full is not
    38  	// specified, then a partial accounting will be done, and Unknown will
    39  	// contain a majority of memory. See Collect for more information.
    40  	Full bool `json:"Full"`
    41  }
    42  
    43  // MemoryUsage is a memory usage structure.
    44  type MemoryUsage struct {
    45  	Unknown   uint64 `json:"Unknown"`
    46  	System    uint64 `json:"System"`
    47  	Anonymous uint64 `json:"Anonymous"`
    48  	PageCache uint64 `json:"PageCache"`
    49  	Mapped    uint64 `json:"Mapped"`
    50  	Tmpfs     uint64 `json:"Tmpfs"`
    51  	Ramdiskfs uint64 `json:"Ramdiskfs"`
    52  	Total     uint64 `json:"Total"`
    53  }
    54  
    55  // MemoryUsageFileOpts contains usage file options.
    56  type MemoryUsageFileOpts struct {
    57  	// Version is used to ensure both sides agree on the format of the
    58  	// shared memory buffer.
    59  	Version uint64 `json:"Version"`
    60  }
    61  
    62  // MemoryUsageFile contains the file handle to the usage file.
    63  type MemoryUsageFile struct {
    64  	urpc.FilePayload
    65  }
    66  
    67  // UsageFD returns the file that tracks the memory usage of the application.
    68  func (u *Usage) UsageFD(opts *MemoryUsageFileOpts, out *MemoryUsageFile) error {
    69  	// Only support version 1 for now.
    70  	if opts.Version != 1 {
    71  		return fmt.Errorf("unsupported version requested: %d", opts.Version)
    72  	}
    73  
    74  	mf := u.Kernel.MemoryFile()
    75  	*out = MemoryUsageFile{
    76  		FilePayload: urpc.FilePayload{
    77  			Files: []*os.File{
    78  				usage.MemoryAccounting.File,
    79  				mf.File(),
    80  			},
    81  		},
    82  	}
    83  
    84  	return nil
    85  }
    86  
    87  // Collect returns memory used by the sandboxed application.
    88  func (u *Usage) Collect(opts *MemoryUsageOpts, out *MemoryUsage) error {
    89  	if opts.Full {
    90  		// Ensure everything is up to date.
    91  		if err := u.Kernel.MemoryFile().UpdateUsage(); err != nil {
    92  			return err
    93  		}
    94  
    95  		// Copy out a snapshot.
    96  		snapshot, total := usage.MemoryAccounting.Copy()
    97  		*out = MemoryUsage{
    98  			System:    snapshot.System,
    99  			Anonymous: snapshot.Anonymous,
   100  			PageCache: snapshot.PageCache,
   101  			Mapped:    snapshot.Mapped,
   102  			Tmpfs:     snapshot.Tmpfs,
   103  			Ramdiskfs: snapshot.Ramdiskfs,
   104  			Total:     total,
   105  		}
   106  	} else {
   107  		// Get total usage from the MemoryFile implementation.
   108  		total, err := u.Kernel.MemoryFile().TotalUsage()
   109  		if err != nil {
   110  			return err
   111  		}
   112  
   113  		// The memory accounting is guaranteed to be accurate only when
   114  		// UpdateUsage is called. If UpdateUsage is not called, then only Mapped
   115  		// will be up-to-date.
   116  		snapshot, _ := usage.MemoryAccounting.Copy()
   117  		*out = MemoryUsage{
   118  			Unknown: total,
   119  			Mapped:  snapshot.Mapped,
   120  			Total:   total + snapshot.Mapped,
   121  		}
   122  
   123  	}
   124  
   125  	return nil
   126  }
   127  
   128  // UsageReduceOpts contains options to Usage.Reduce().
   129  type UsageReduceOpts struct {
   130  	// If Wait is `true`, Reduce blocks until all activity initiated by
   131  	// Usage.Reduce() has completed.
   132  	// If Wait is `false`, Go garbage collection is still performed and may
   133  	// still block for some time, unless `DoNotGC` is `true`.
   134  	Wait bool `json:"wait"`
   135  
   136  	// If DoNotGC is true, Reduce does not explicitly run Go garbage collection.
   137  	// Garbage collection may block for an indeterminate amount of time.
   138  	// Note that the runtime Go may still perform routine garbage collection at
   139  	// any time during program execution, so a routine GC is still possible even
   140  	// when this option set to `true`.
   141  	DoNotGC bool `json:"do_not_gc"`
   142  }
   143  
   144  // UsageReduceOutput contains output from Usage.Reduce().
   145  type UsageReduceOutput struct{}
   146  
   147  // Reduce requests that the sentry attempt to reduce its memory usage.
   148  func (u *Usage) Reduce(opts *UsageReduceOpts, out *UsageReduceOutput) error {
   149  	mf := u.Kernel.MemoryFile()
   150  	mf.StartEvictions()
   151  	if opts.Wait {
   152  		mf.WaitForEvictions()
   153  	}
   154  	if !opts.DoNotGC {
   155  		runtime.GC()
   156  	}
   157  	return nil
   158  }
   159  
   160  // MemoryUsageRecord contains the mapping and platform memory file.
   161  type MemoryUsageRecord struct {
   162  	mmap  uintptr
   163  	stats *usage.RTMemoryStats
   164  	mf    os.File
   165  }
   166  
   167  // NewMemoryUsageRecord creates a new MemoryUsageRecord from usageFile and
   168  // platformFile.
   169  func NewMemoryUsageRecord(usageFile, platformFile os.File) (*MemoryUsageRecord, error) {
   170  	mmap, _, e := unix.RawSyscall6(unix.SYS_MMAP, 0, usage.RTMemoryStatsSize, unix.PROT_READ, unix.MAP_SHARED, usageFile.Fd(), 0)
   171  	if e != 0 {
   172  		return nil, fmt.Errorf("mmap returned %d, want 0", e)
   173  	}
   174  
   175  	m := MemoryUsageRecord{
   176  		mmap:  mmap,
   177  		stats: usage.RTMemoryStatsPointer(mmap),
   178  		mf:    platformFile,
   179  	}
   180  
   181  	runtime.SetFinalizer(&m, finalizer)
   182  	return &m, nil
   183  }
   184  
   185  // GetFileIoStats writes the read times in nanoseconds to out.
   186  func (*Usage) GetFileIoStats(_ *struct{}, out *string) error {
   187  	fileIoStats := struct {
   188  		// The total amount of time spent reading. The map maps gopher prefixes
   189  		// to the total time spent reading. Times not included in a known prefix
   190  		// are placed in the "/" prefix.
   191  		ReadWait map[string]uint64 `json:"ReadWait"`
   192  		// The total amount of time spent reading. The map maps gopher prefixes
   193  		// to the total time spent reading. Times not included in a known prefix
   194  		// are placed in the "/" prefix.
   195  		ReadWait9P map[string]uint64 `json:"ReadWait9P"`
   196  	}{
   197  		ReadWait:   map[string]uint64{"/": fsmetric.ReadWait.Value()},
   198  		ReadWait9P: map[string]uint64{"/": fsmetric.GoferReadWait9P.Value()},
   199  	}
   200  
   201  	m, err := json.Marshal(fileIoStats)
   202  	if err != nil {
   203  		return err
   204  	}
   205  	*out = string(m)
   206  	return nil
   207  }
   208  
   209  func finalizer(m *MemoryUsageRecord) {
   210  	unix.RawSyscall(unix.SYS_MUNMAP, m.mmap, usage.RTMemoryStatsSize, 0)
   211  }
   212  
   213  // Fetch fetches the usage info from a MemoryUsageRecord.
   214  func (m *MemoryUsageRecord) Fetch() (mapped, unknown, total uint64, err error) {
   215  	var stat unix.Stat_t
   216  	if err := unix.Fstat(int(m.mf.Fd()), &stat); err != nil {
   217  		return 0, 0, 0, err
   218  	}
   219  	fmem := uint64(stat.Blocks) * 512
   220  	rtmapped := m.stats.RTMapped.Load()
   221  	return rtmapped, fmem, rtmapped + fmem, nil
   222  }