github.com/metacubex/gvisor@v0.0.0-20240320004321-933faba989ec/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/metacubex/gvisor/pkg/sentry/fsmetric" 25 "github.com/metacubex/gvisor/pkg/sentry/kernel" 26 "github.com/metacubex/gvisor/pkg/sentry/usage" 27 "github.com/metacubex/gvisor/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(nil); 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 }