github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/usage/memory.go (about) 1 // Copyright 2018 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 usage 16 17 import ( 18 "fmt" 19 "os" 20 "sync/atomic" 21 22 "golang.org/x/sys/unix" 23 "github.com/SagerNet/gvisor/pkg/bits" 24 "github.com/SagerNet/gvisor/pkg/memutil" 25 "github.com/SagerNet/gvisor/pkg/sync" 26 ) 27 28 // MemoryKind represents a type of memory used by the application. 29 // 30 // For efficiency reasons, it is assumed that the Memory implementation is 31 // responsible for specific stats (documented below), and those may be reported 32 // in aggregate independently. See the platform.Memory interface as well as the 33 // control.Usage.Collect method for more information. 34 type MemoryKind int 35 36 const ( 37 // System represents miscellaneous system memory. This may include 38 // memory that is in the process of being reclaimed, system caches, 39 // page tables, swap, etc. 40 // 41 // This memory kind is backed by platform memory. 42 System MemoryKind = iota 43 44 // Anonymous represents anonymous application memory. 45 // 46 // This memory kind is backed by platform memory. 47 Anonymous 48 49 // PageCache represents memory allocated to back sandbox-visible files that 50 // do not have a local fd. The contents of these files are buffered in 51 // memory to support application mmaps. 52 // 53 // This memory kind is backed by platform memory. 54 PageCache 55 56 // Tmpfs represents memory used by the sandbox-visible tmpfs. 57 // 58 // This memory kind is backed by platform memory. 59 Tmpfs 60 61 // Ramdiskfs represents memory used by the ramdiskfs. 62 // 63 // This memory kind is backed by platform memory. 64 Ramdiskfs 65 66 // Mapped represents memory related to files which have a local fd on the 67 // host, and thus can be directly mapped. Typically these are files backed 68 // by gofers with donated-fd support. Note that this value may not track the 69 // exact amount of memory used by mapping on the host, because we don't have 70 // any visibility into the host kernel memory management. In particular, 71 // once we map some part of a host file, the host kernel is free to 72 // abitrarily populate/decommit the pages, which it may do for various 73 // reasons (ex. host memory reclaim, NUMA balancing). 74 // 75 // This memory kind is backed by the host pagecache, via host mmaps. 76 Mapped 77 ) 78 79 // MemoryStats tracks application memory usage in bytes. All fields correspond to the 80 // memory category with the same name. This object is thread-safe if accessed 81 // through the provided methods. The public fields may be safely accessed 82 // directly on a copy of the object obtained from Memory.Copy(). 83 type MemoryStats struct { 84 System uint64 85 Anonymous uint64 86 PageCache uint64 87 Tmpfs uint64 88 // Lazily updated based on the value in RTMapped. 89 Mapped uint64 90 Ramdiskfs uint64 91 } 92 93 // RTMemoryStats contains the memory usage values that need to be directly 94 // exposed through a shared memory file for real-time access. These are 95 // categories not backed by platform memory. For details about how this works, 96 // see the memory accounting docs. 97 // 98 // N.B. Please keep the struct in sync with the API. Notably, changes to this 99 // struct requires a version bump and addition of compatibility logic in the 100 // control server. As a special-case, adding fields without re-ordering existing 101 // ones do not require a version bump because the mapped page we use is 102 // initially zeroed. Any added field will be ignored by an older API and will be 103 // zero if read by a newer API. 104 type RTMemoryStats struct { 105 RTMapped uint64 106 } 107 108 // MemoryLocked is Memory with access methods. 109 type MemoryLocked struct { 110 mu sync.RWMutex 111 // MemoryStats records the memory stats. 112 MemoryStats 113 // RTMemoryStats records the memory stats that need to be exposed through 114 // shared page. 115 *RTMemoryStats 116 // File is the backing file storing the memory stats. 117 File *os.File 118 } 119 120 // Init initializes global 'MemoryAccounting'. 121 func Init() error { 122 const name = "memory-usage" 123 fd, err := memutil.CreateMemFD(name, 0) 124 if err != nil { 125 return fmt.Errorf("error creating usage file: %v", err) 126 } 127 file := os.NewFile(uintptr(fd), name) 128 if err := file.Truncate(int64(RTMemoryStatsSize)); err != nil { 129 return fmt.Errorf("error truncating usage file: %v", err) 130 } 131 // Note: We rely on the returned page being initially zeroed. This will 132 // always be the case for a newly mapped page from /dev/shm. If we obtain 133 // the shared memory through some other means in the future, we may have to 134 // explicitly zero the page. 135 mmap, err := memutil.MapFile(0, RTMemoryStatsSize, unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED, file.Fd(), 0) 136 if err != nil { 137 return fmt.Errorf("error mapping usage file: %v", err) 138 } 139 140 MemoryAccounting = &MemoryLocked{ 141 File: file, 142 RTMemoryStats: RTMemoryStatsPointer(mmap), 143 } 144 return nil 145 } 146 147 // MemoryAccounting is the global memory stats. 148 // 149 // There is no need to save or restore the global memory accounting object, 150 // because individual frame kinds are saved and charged only when they become 151 // resident. 152 var MemoryAccounting *MemoryLocked 153 154 func (m *MemoryLocked) incLocked(val uint64, kind MemoryKind) { 155 switch kind { 156 case System: 157 atomic.AddUint64(&m.System, val) 158 case Anonymous: 159 atomic.AddUint64(&m.Anonymous, val) 160 case PageCache: 161 atomic.AddUint64(&m.PageCache, val) 162 case Mapped: 163 atomic.AddUint64(&m.RTMapped, val) 164 case Tmpfs: 165 atomic.AddUint64(&m.Tmpfs, val) 166 case Ramdiskfs: 167 atomic.AddUint64(&m.Ramdiskfs, val) 168 default: 169 panic(fmt.Sprintf("invalid memory kind: %v", kind)) 170 } 171 } 172 173 // Inc adds an additional usage of 'val' bytes to memory category 'kind'. 174 // 175 // This method is thread-safe. 176 func (m *MemoryLocked) Inc(val uint64, kind MemoryKind) { 177 m.mu.RLock() 178 m.incLocked(val, kind) 179 m.mu.RUnlock() 180 } 181 182 func (m *MemoryLocked) decLocked(val uint64, kind MemoryKind) { 183 switch kind { 184 case System: 185 atomic.AddUint64(&m.System, ^(val - 1)) 186 case Anonymous: 187 atomic.AddUint64(&m.Anonymous, ^(val - 1)) 188 case PageCache: 189 atomic.AddUint64(&m.PageCache, ^(val - 1)) 190 case Mapped: 191 atomic.AddUint64(&m.RTMapped, ^(val - 1)) 192 case Tmpfs: 193 atomic.AddUint64(&m.Tmpfs, ^(val - 1)) 194 case Ramdiskfs: 195 atomic.AddUint64(&m.Ramdiskfs, ^(val - 1)) 196 default: 197 panic(fmt.Sprintf("invalid memory kind: %v", kind)) 198 } 199 } 200 201 // Dec remove a usage of 'val' bytes from memory category 'kind'. 202 // 203 // This method is thread-safe. 204 func (m *MemoryLocked) Dec(val uint64, kind MemoryKind) { 205 m.mu.RLock() 206 m.decLocked(val, kind) 207 m.mu.RUnlock() 208 } 209 210 // Move moves a usage of 'val' bytes from 'from' to 'to'. 211 // 212 // This method is thread-safe. 213 func (m *MemoryLocked) Move(val uint64, to MemoryKind, from MemoryKind) { 214 m.mu.RLock() 215 // Just call decLocked and incLocked directly. We held the RLock to 216 // protect against concurrent callers to Total(). 217 m.decLocked(val, from) 218 m.incLocked(val, to) 219 m.mu.RUnlock() 220 } 221 222 // totalLocked returns a total usage. 223 // 224 // Precondition: must be called when locked. 225 func (m *MemoryLocked) totalLocked() (total uint64) { 226 total += atomic.LoadUint64(&m.System) 227 total += atomic.LoadUint64(&m.Anonymous) 228 total += atomic.LoadUint64(&m.PageCache) 229 total += atomic.LoadUint64(&m.RTMapped) 230 total += atomic.LoadUint64(&m.Tmpfs) 231 total += atomic.LoadUint64(&m.Ramdiskfs) 232 return 233 } 234 235 // Total returns a total memory usage. 236 // 237 // This method is thread-safe. 238 func (m *MemoryLocked) Total() uint64 { 239 m.mu.Lock() 240 defer m.mu.Unlock() 241 return m.totalLocked() 242 } 243 244 // Copy returns a copy of the structure with a total. 245 // 246 // This method is thread-safe. 247 func (m *MemoryLocked) Copy() (MemoryStats, uint64) { 248 m.mu.Lock() 249 defer m.mu.Unlock() 250 ms := m.MemoryStats 251 ms.Mapped = m.RTMapped 252 return ms, m.totalLocked() 253 } 254 255 // These options control how much total memory the is reported to the application. 256 // They may only be set before the application starts executing, and must not 257 // be modified. 258 var ( 259 // MinimumTotalMemoryBytes is the minimum reported total system memory. 260 MinimumTotalMemoryBytes uint64 = 2 << 30 // 2 GB 261 262 // MaximumTotalMemoryBytes is the maximum reported total system memory. 263 // The 0 value indicates no maximum. 264 MaximumTotalMemoryBytes uint64 265 ) 266 267 // TotalMemory returns the "total usable memory" available. 268 // 269 // This number doesn't really have a true value so it's based on the following 270 // inputs and further bounded to be above the MinumumTotalMemoryBytes and below 271 // MaximumTotalMemoryBytes. 272 // 273 // memSize should be the platform.Memory size reported by platform.Memory.TotalSize() 274 // used is the total memory reported by MemoryLocked.Total() 275 func TotalMemory(memSize, used uint64) uint64 { 276 if memSize < MinimumTotalMemoryBytes { 277 memSize = MinimumTotalMemoryBytes 278 } 279 if memSize < used { 280 memSize = used 281 // Bump memSize to the next largest power of 2, if one exists, so 282 // that MemFree isn't 0. 283 if msb := bits.MostSignificantOne64(memSize); msb < 63 { 284 memSize = uint64(1) << (uint(msb) + 1) 285 } 286 } 287 if MaximumTotalMemoryBytes > 0 && memSize > MaximumTotalMemoryBytes { 288 memSize = MaximumTotalMemoryBytes 289 } 290 return memSize 291 }