File: memory.go

package info (click to toggle)
golang-gvisor-gvisor 0.0~20240729.0-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 21,276 kB
  • sloc: asm: 3,361; ansic: 1,197; cpp: 348; makefile: 92; python: 89; sh: 83
file content (414 lines) | stat: -rw-r--r-- 12,627 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
// Copyright 2018 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 usage

import (
	"fmt"
	"os"

	"golang.org/x/sys/unix"
	"gvisor.dev/gvisor/pkg/atomicbitops"
	"gvisor.dev/gvisor/pkg/bits"
	"gvisor.dev/gvisor/pkg/memutil"
	"gvisor.dev/gvisor/pkg/sync"
)

// MemoryKind represents a type of memory used by the application.
//
// For efficiency reasons, it is assumed that the Memory implementation is
// responsible for specific stats (documented below), and those may be reported
// in aggregate independently. See the platform.Memory interface as well as the
// control.Usage.Collect method for more information.
type MemoryKind int

const (
	// System represents miscellaneous system memory. This may include
	// memory that is in the process of being reclaimed, system caches,
	// page tables, swap, etc.
	//
	// This memory kind is backed by platform memory.
	System MemoryKind = iota

	// Anonymous represents anonymous application memory.
	//
	// This memory kind is backed by platform memory.
	Anonymous

	// PageCache represents memory allocated to back sandbox-visible files that
	// do not have a local fd. The contents of these files are buffered in
	// memory to support application mmaps.
	//
	// This memory kind is backed by platform memory.
	PageCache

	// Tmpfs represents memory used by the sandbox-visible tmpfs.
	//
	// This memory kind is backed by platform memory.
	Tmpfs

	// Ramdiskfs represents memory used by the ramdiskfs.
	//
	// This memory kind is backed by platform memory.
	Ramdiskfs

	// Mapped represents memory related to files which have a local fd on the
	// host, and thus can be directly mapped. Typically these are files backed
	// by gofers with donated-fd support. Note that this value may not track the
	// exact amount of memory used by mapping on the host, because we don't have
	// any visibility into the host kernel memory management. In particular,
	// once we map some part of a host file, the host kernel is free to
	// arbitrarily populate/decommit the pages, which it may do for various
	// reasons (ex. host memory reclaim, NUMA balancing).
	//
	// This memory kind is backed by the host pagecache, via host mmaps.
	Mapped
)

// memoryStats tracks application memory usage in bytes. All fields correspond to the
// memory category with the same name. This object is thread-safe if accessed
// through the provided methods. The public fields may be safely accessed
// directly on a copy of the object obtained from Memory.Copy().
type memoryStats struct {
	System    atomicbitops.Uint64
	Anonymous atomicbitops.Uint64
	PageCache atomicbitops.Uint64
	Tmpfs     atomicbitops.Uint64
	Mapped    atomicbitops.Uint64
	Ramdiskfs atomicbitops.Uint64
}

// incLocked adds a usage of 'val' bytes from memory category 'kind'.
//
// Precondition: must be called when locked.
func (ms *memoryStats) incLocked(val uint64, kind MemoryKind) {
	switch kind {
	case System:
		ms.System.Add(val)
	case Anonymous:
		ms.Anonymous.Add(val)
	case PageCache:
		ms.PageCache.Add(val)
	case Mapped:
		ms.Mapped.Add(val)
	case Tmpfs:
		ms.Tmpfs.Add(val)
	case Ramdiskfs:
		ms.Ramdiskfs.Add(val)
	default:
		panic(fmt.Sprintf("invalid memory kind: %v", kind))
	}
}

// decLocked removes a usage of 'val' bytes from memory category 'kind'.
//
// Precondition: must be called when locked.
func (ms *memoryStats) decLocked(val uint64, kind MemoryKind) {
	switch kind {
	case System:
		ms.System.Add(^(val - 1))
	case Anonymous:
		ms.Anonymous.Add(^(val - 1))
	case PageCache:
		ms.PageCache.Add(^(val - 1))
	case Mapped:
		ms.Mapped.Add(^(val - 1))
	case Tmpfs:
		ms.Tmpfs.Add(^(val - 1))
	case Ramdiskfs:
		ms.Ramdiskfs.Add(^(val - 1))
	default:
		panic(fmt.Sprintf("invalid memory kind: %v", kind))
	}
}

// totalLocked returns a total usage.
//
// Precondition: must be called when locked.
func (ms *memoryStats) totalLocked() (total uint64) {
	total += ms.System.RacyLoad()
	total += ms.Anonymous.RacyLoad()
	total += ms.PageCache.RacyLoad()
	total += ms.Mapped.RacyLoad()
	total += ms.Tmpfs.RacyLoad()
	total += ms.Ramdiskfs.RacyLoad()
	return
}

// copyLocked returns a copy of the structure.
//
// Precondition: must be called when locked.
func (ms *memoryStats) copyLocked() MemoryStats {
	return MemoryStats{
		System:    ms.System.RacyLoad(),
		Anonymous: ms.Anonymous.RacyLoad(),
		PageCache: ms.PageCache.RacyLoad(),
		Tmpfs:     ms.Tmpfs.RacyLoad(),
		Mapped:    ms.Mapped.RacyLoad(),
		Ramdiskfs: ms.Ramdiskfs.RacyLoad(),
	}
}

// MemoryStats tracks application memory usage in bytes. All fields correspond
// to the memory category with the same name.
type MemoryStats struct {
	System    uint64
	Anonymous uint64
	PageCache uint64
	Tmpfs     uint64
	Mapped    uint64
	Ramdiskfs uint64
}

// RTMemoryStats contains the memory usage values that need to be directly
// exposed through a shared memory file for real-time access. These are
// categories not backed by platform memory. For details about how this works,
// see the memory accounting docs.
//
// N.B. Please keep the struct in sync with the API. Notably, changes to this
// struct requires a version bump and addition of compatibility logic in the
// control server. As a special-case, adding fields without re-ordering existing
// ones do not require a version bump because the mapped page we use is
// initially zeroed. Any added field will be ignored by an older API and will be
// zero if read by a newer API.
type RTMemoryStats struct {
	RTMapped atomicbitops.Uint64
}

// MemoryLocked is Memory with access methods.
type MemoryLocked struct {
	mu memoryMutex
	// memoryStats records the memory stats.
	memoryStats
	// RTMemoryStats records the memory stats that need to be exposed through
	// shared page.
	*RTMemoryStats
	// File is the backing file storing the memory stats.
	File *os.File
	// MemCgIDToMemStats is the map of cgroup ids to memory stats.
	MemCgIDToMemStats map[uint32]*memoryStats
}

var (
	initOnce sync.Once
	initErr  error
)

// Init initializes global 'MemoryAccounting'.
func Init() error {
	initOnce.Do(func() {
		initErr = func() error {
			const name = "memory-usage"
			fd, err := memutil.CreateMemFD(name, 0)
			if err != nil {
				return fmt.Errorf("error creating usage file: %v", err)
			}
			file := os.NewFile(uintptr(fd), name)
			if err := file.Truncate(int64(RTMemoryStatsSize)); err != nil {
				return fmt.Errorf("error truncating usage file: %v", err)
			}
			// Note: We rely on the returned page being initially zeroed. This will
			// always be the case for a newly mapped page from /dev/shm. If we obtain
			// the shared memory through some other means in the future, we may have to
			// explicitly zero the page.
			mmap, err := memutil.MapFile(0, RTMemoryStatsSize, unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED, file.Fd(), 0)
			if err != nil {
				return fmt.Errorf("error mapping usage file: %v", err)
			}

			MemoryAccounting = &MemoryLocked{
				File:              file,
				RTMemoryStats:     RTMemoryStatsPointer(mmap),
				MemCgIDToMemStats: make(map[uint32]*memoryStats),
			}
			return nil
		}()
	})
	return initErr
}

// MemoryAccounting is the global memory stats.
//
// There is no need to save or restore the global memory accounting object,
// because individual frame kinds are saved and charged only when they become
// resident.
var MemoryAccounting *MemoryLocked

func (m *MemoryLocked) incLockedPerCg(val uint64, kind MemoryKind, memCgID uint32) {
	if _, ok := m.MemCgIDToMemStats[memCgID]; !ok {
		m.MemCgIDToMemStats[memCgID] = &memoryStats{}
	}

	ms := m.MemCgIDToMemStats[memCgID]
	ms.incLocked(val, kind)
}

// Inc adds an additional usage of 'val' bytes to memory category 'kind' for a
// cgroup with id 'memCgID'. If 'memCgID' is zero, the memory is accounted only
// for the total memory usage.
//
// This method is thread-safe.
func (m *MemoryLocked) Inc(val uint64, kind MemoryKind, memCgID uint32) {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.incLocked(val, kind)
	if memCgID != 0 {
		m.incLockedPerCg(val, kind, memCgID)
	}

	// If the memory category is 'Mapped', update RTMapped.
	if kind == Mapped {
		m.RTMapped.Add(val)
	}
}

func (m *MemoryLocked) decLockedPerCg(val uint64, kind MemoryKind, memCgID uint32) {
	if _, ok := m.MemCgIDToMemStats[memCgID]; !ok {
		panic(fmt.Sprintf("invalid memory cgroup id: %v", memCgID))
	}

	ms := m.MemCgIDToMemStats[memCgID]
	ms.decLocked(val, kind)
}

// Dec removes a usage of 'val' bytes from memory category 'kind' for a cgroup
// with id 'memCgID'. If 'memCgID' is zero, the memory is removed only from the
// total usage.
//
// This method is thread-safe.
func (m *MemoryLocked) Dec(val uint64, kind MemoryKind, memCgID uint32) {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.decLocked(val, kind)
	if memCgID != 0 {
		m.decLockedPerCg(val, kind, memCgID)
	}

	// If the memory category is 'Mapped', update RTMapped.
	if kind == Mapped {
		m.RTMapped.Add(^(val - 1))
	}
}

// Move moves a usage of 'val' bytes from 'from' to 'to' for a cgroup with
// id 'memCgID'.
//
// This method is thread-safe.
func (m *MemoryLocked) Move(val uint64, to MemoryKind, from MemoryKind, memCgID uint32) {
	m.mu.Lock()
	defer m.mu.Unlock()
	// Just call decLocked and incLocked directly. We held the Lock to
	// protect against concurrent callers to Total().
	m.decLocked(val, from)
	m.incLocked(val, to)

	if memCgID != 0 {
		m.decLockedPerCg(val, from, memCgID)
		m.incLockedPerCg(val, to, memCgID)
	}
}

// Total returns a total memory usage.
//
// This method is thread-safe.
func (m *MemoryLocked) Total() uint64 {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.totalLocked()
}

// TotalPerCg returns a total memory usage for a cgroup.
//
// This method is thread-safe.
func (m *MemoryLocked) TotalPerCg(memCgID uint32) uint64 {
	m.mu.Lock()
	defer m.mu.Unlock()

	// Total memory usage including the sentry memory.
	if memCgID == 0 {
		return m.totalLocked()
	}
	// Memory usage for all cgroups except sentry memory.
	ms, ok := m.MemCgIDToMemStats[memCgID]
	if !ok {
		return 0
	}
	return ms.totalLocked()
}

// Copy returns a copy of the structure with a total.
//
// This method is thread-safe.
func (m *MemoryLocked) Copy() (MemoryStats, uint64) {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.copyLocked(), m.totalLocked()
}

// CopyPerCg returns a copy of the structure with a total for a cgroup.
//
// This method is thread-safe.
func (m *MemoryLocked) CopyPerCg(memCgID uint32) (MemoryStats, uint64) {
	m.mu.Lock()
	defer m.mu.Unlock()

	// Total memory usage including the sentry memory.
	if memCgID == 0 {
		return m.copyLocked(), m.totalLocked()
	}
	// Memory usage for all cgroups except sentry memory.
	ms, ok := m.MemCgIDToMemStats[memCgID]
	if !ok {
		return MemoryStats{}, 0
	}
	return ms.copyLocked(), ms.totalLocked()
}

// These options control how much total memory the is reported to the
// application. They may only be set before the application starts executing,
// and must not be modified.
var (
	// MinimumTotalMemoryBytes is the minimum reported total system memory.
	MinimumTotalMemoryBytes uint64 = 2 << 30 // 2 GB

	// MaximumTotalMemoryBytes is the maximum reported total system memory.
	// The 0 value indicates no maximum.
	MaximumTotalMemoryBytes uint64
)

// TotalMemory returns the "total usable memory" available.
//
// This number doesn't really have a true value so it's based on the following
// inputs and further bounded to be above the MinumumTotalMemoryBytes and below
// MaximumTotalMemoryBytes.
//
// memSize should be the platform.Memory size reported by platform.Memory.TotalSize()
// used is the total memory reported by MemoryLocked.Total()
func TotalMemory(memSize, used uint64) uint64 {
	if memSize < MinimumTotalMemoryBytes {
		memSize = MinimumTotalMemoryBytes
	}
	if memSize < used {
		memSize = used
		// Bump memSize to the next largest power of 2, if one exists, so
		// that MemFree isn't 0.
		if msb := bits.MostSignificantOne64(memSize); msb < 63 {
			memSize = uint64(1) << (uint(msb) + 1)
		}
	}
	if MaximumTotalMemoryBytes > 0 && memSize > MaximumTotalMemoryBytes {
		memSize = MaximumTotalMemoryBytes
	}
	return memSize
}