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  }