github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/sys/mem_linux.go (about)

     1  // Package sys provides methods to read system information
     2  /*
     3   * Copyright (c) 2018-2022, NVIDIA CORPORATION. All rights reserved.
     4   */
     5  package sys
     6  
     7  import (
     8  	"math"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/NVIDIA/aistore/cmn/cos"
    13  	"github.com/NVIDIA/aistore/cmn/debug"
    14  )
    15  
    16  var mem0 = MemStat{ActualFree: math.MaxUint64}
    17  
    18  func (mem *MemStat) setValue(name, valStr string) error {
    19  	val, err := strconv.ParseUint(valStr, 10, 64)
    20  	if err != nil {
    21  		debug.AssertNoErr(err)
    22  		return err
    23  	}
    24  	val *= cos.KiB
    25  	switch name {
    26  	case "MemTotal":
    27  		mem.Total = val
    28  	case "MemFree":
    29  		mem.Free = val
    30  	case "MemAvailable":
    31  		mem.ActualFree = val
    32  	case "Cached", "Buffers":
    33  		mem.BuffCache += val
    34  	case "SwapTotal":
    35  		mem.SwapTotal = val
    36  	case "SwapFree":
    37  		mem.SwapFree = val
    38  	}
    39  	return nil
    40  }
    41  
    42  // parse `/proc/meminfo` lines
    43  func (mem *MemStat) parse(line string) error {
    44  	fields := strings.Split(line, ":")
    45  	if len(fields) != 2 {
    46  		return nil
    47  	}
    48  	name := fields[0]
    49  	valStr := strings.Fields(strings.TrimLeft(fields[1], " "))[0]
    50  	return mem.setValue(name, valStr)
    51  }
    52  
    53  func (mem *MemStat) host() (err error) {
    54  	*mem = mem0 // ActualFree = MaxUint64, zeros all the rest
    55  	if err = cos.ReadLines(hostMemPath, mem.parse); err != nil {
    56  		return
    57  	}
    58  
    59  	if mem.ActualFree == math.MaxUint64 { // remains unassigned (see above)
    60  		mem.ActualFree = mem.Free + mem.BuffCache
    61  	}
    62  	mem.Used = mem.Total - mem.Free
    63  	mem.ActualUsed = mem.Total - mem.ActualFree
    64  	mem.SwapUsed = mem.SwapTotal - mem.SwapFree
    65  	return
    66  }
    67  
    68  // parse `/sys/fs/cgroup/memory/memory.stat` lines
    69  func (mem *MemStat) cgroupParse(line string) error {
    70  	fields := strings.Fields(line)
    71  	if len(fields) < 2 {
    72  		return nil
    73  	}
    74  	if fields[0] != "total_cache" {
    75  		return nil
    76  	}
    77  	val, err := strconv.ParseUint(fields[1], 10, 64)
    78  	if err != nil {
    79  		return err
    80  	}
    81  	mem.BuffCache += val
    82  	return nil
    83  }
    84  
    85  // Returns host stats if memory is not limited via cgroups "memory.limit_in_bytes".
    86  func (mem *MemStat) container() error {
    87  	if err := mem.host(); err != nil {
    88  		return err
    89  	}
    90  	memLimit, err := cos.ReadOneUint64(contMemLimitPath)
    91  	if err != nil {
    92  		return nil
    93  	}
    94  	// It is safe to assume that the value greater than MaxInt64/2 indicates "no limit"
    95  	// https://unix.stackexchange.com/questions/420906/what-is-the-value-for-the-cgroups-limit-in-bytes-if-the-memory-is-not-restricte
    96  	if memLimit > math.MaxInt64/2 {
    97  		return nil
    98  	}
    99  
   100  	// this one is an approximate value that includes buff/cache
   101  	// (ie., kernel buffers and page caches that can be reclaimed)
   102  	memUsed, err := cos.ReadOneUint64(contMemUsedPath)
   103  	if err != nil {
   104  		return nil
   105  	}
   106  	mem.Total = memLimit
   107  	mem.Used = memUsed
   108  	mem.Free = mem.Total - mem.Used
   109  
   110  	// calculate memory used for buffcache
   111  	err = cos.ReadLines(contMemStatPath, mem.cgroupParse)
   112  	if err != nil {
   113  		debug.AssertNoErr(err)
   114  		// NOTE: returning host memory
   115  		return nil
   116  	}
   117  
   118  	mem.ActualUsed = memUsed - mem.BuffCache
   119  	mem.ActualFree = mem.Total - mem.ActualUsed
   120  	return nil
   121  }