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