github.com/boomhut/fiber/v2@v2.0.0-20230603160335-b65c856e57d3/internal/gopsutil/mem/mem_linux.go (about)

     1  //go:build linux
     2  // +build linux
     3  
     4  package mem
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"math"
    10  	"os"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"github.com/boomhut/fiber/v2/internal/gopsutil/common"
    15  	"golang.org/x/sys/unix"
    16  )
    17  
    18  type VirtualMemoryExStat struct {
    19  	ActiveFile   uint64 `json:"activefile"`
    20  	InactiveFile uint64 `json:"inactivefile"`
    21  	ActiveAnon   uint64 `json:"activeanon"`
    22  	InactiveAnon uint64 `json:"inactiveanon"`
    23  	Unevictable  uint64 `json:"unevictable"`
    24  }
    25  
    26  func (v VirtualMemoryExStat) String() string {
    27  	s, _ := json.Marshal(v)
    28  	return string(s)
    29  }
    30  
    31  func VirtualMemory() (*VirtualMemoryStat, error) {
    32  	return VirtualMemoryWithContext(context.Background())
    33  }
    34  
    35  func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
    36  	vm, _, err := fillFromMeminfoWithContext(ctx)
    37  	if err != nil {
    38  		return nil, err
    39  	}
    40  	return vm, nil
    41  }
    42  
    43  func VirtualMemoryEx() (*VirtualMemoryExStat, error) {
    44  	return VirtualMemoryExWithContext(context.Background())
    45  }
    46  
    47  func VirtualMemoryExWithContext(ctx context.Context) (*VirtualMemoryExStat, error) {
    48  	_, vmEx, err := fillFromMeminfoWithContext(ctx)
    49  	if err != nil {
    50  		return nil, err
    51  	}
    52  	return vmEx, nil
    53  }
    54  
    55  func fillFromMeminfoWithContext(ctx context.Context) (*VirtualMemoryStat, *VirtualMemoryExStat, error) {
    56  	filename := common.HostProc("meminfo")
    57  	lines, _ := common.ReadLines(filename)
    58  
    59  	// flag if MemAvailable is in /proc/meminfo (kernel 3.14+)
    60  	memavail := false
    61  	activeFile := false   // "Active(file)" not available: 2.6.28 / Dec 2008
    62  	inactiveFile := false // "Inactive(file)" not available: 2.6.28 / Dec 2008
    63  	sReclaimable := false // "SReclaimable:" not available: 2.6.19 / Nov 2006
    64  
    65  	ret := &VirtualMemoryStat{}
    66  	retEx := &VirtualMemoryExStat{}
    67  
    68  	for _, line := range lines {
    69  		fields := strings.Split(line, ":")
    70  		if len(fields) != 2 {
    71  			continue
    72  		}
    73  		key := strings.TrimSpace(fields[0])
    74  		value := strings.TrimSpace(fields[1])
    75  		value = strings.Replace(value, " kB", "", -1)
    76  
    77  		t, err := strconv.ParseUint(value, 10, 64)
    78  		if err != nil {
    79  			return ret, retEx, err
    80  		}
    81  		switch key {
    82  		case "MemTotal":
    83  			ret.Total = t * 1024
    84  		case "MemFree":
    85  			ret.Free = t * 1024
    86  		case "MemAvailable":
    87  			memavail = true
    88  			ret.Available = t * 1024
    89  		case "Buffers":
    90  			ret.Buffers = t * 1024
    91  		case "Cached":
    92  			ret.Cached = t * 1024
    93  		case "Active":
    94  			ret.Active = t * 1024
    95  		case "Inactive":
    96  			ret.Inactive = t * 1024
    97  		case "Active(anon)":
    98  			retEx.ActiveAnon = t * 1024
    99  		case "Inactive(anon)":
   100  			retEx.InactiveAnon = t * 1024
   101  		case "Active(file)":
   102  			activeFile = true
   103  			retEx.ActiveFile = t * 1024
   104  		case "Inactive(file)":
   105  			inactiveFile = true
   106  			retEx.InactiveFile = t * 1024
   107  		case "Unevictable":
   108  			retEx.Unevictable = t * 1024
   109  		case "Writeback":
   110  			ret.Writeback = t * 1024
   111  		case "WritebackTmp":
   112  			ret.WritebackTmp = t * 1024
   113  		case "Dirty":
   114  			ret.Dirty = t * 1024
   115  		case "Shmem":
   116  			ret.Shared = t * 1024
   117  		case "Slab":
   118  			ret.Slab = t * 1024
   119  		case "SReclaimable":
   120  			sReclaimable = true
   121  			ret.SReclaimable = t * 1024
   122  		case "SUnreclaim":
   123  			ret.SUnreclaim = t * 1024
   124  		case "PageTables":
   125  			ret.PageTables = t * 1024
   126  		case "SwapCached":
   127  			ret.SwapCached = t * 1024
   128  		case "CommitLimit":
   129  			ret.CommitLimit = t * 1024
   130  		case "Committed_AS":
   131  			ret.CommittedAS = t * 1024
   132  		case "HighTotal":
   133  			ret.HighTotal = t * 1024
   134  		case "HighFree":
   135  			ret.HighFree = t * 1024
   136  		case "LowTotal":
   137  			ret.LowTotal = t * 1024
   138  		case "LowFree":
   139  			ret.LowFree = t * 1024
   140  		case "SwapTotal":
   141  			ret.SwapTotal = t * 1024
   142  		case "SwapFree":
   143  			ret.SwapFree = t * 1024
   144  		case "Mapped":
   145  			ret.Mapped = t * 1024
   146  		case "VmallocTotal":
   147  			ret.VMallocTotal = t * 1024
   148  		case "VmallocUsed":
   149  			ret.VMallocUsed = t * 1024
   150  		case "VmallocChunk":
   151  			ret.VMallocChunk = t * 1024
   152  		case "HugePages_Total":
   153  			ret.HugePagesTotal = t
   154  		case "HugePages_Free":
   155  			ret.HugePagesFree = t
   156  		case "Hugepagesize":
   157  			ret.HugePageSize = t * 1024
   158  		}
   159  	}
   160  
   161  	ret.Cached += ret.SReclaimable
   162  
   163  	if !memavail {
   164  		if activeFile && inactiveFile && sReclaimable {
   165  			ret.Available = calcuateAvailVmem(ret, retEx)
   166  		} else {
   167  			ret.Available = ret.Cached + ret.Free
   168  		}
   169  	}
   170  
   171  	ret.Used = ret.Total - ret.Free - ret.Buffers - ret.Cached
   172  	ret.UsedPercent = float64(ret.Used) / float64(ret.Total) * 100.0
   173  
   174  	return ret, retEx, nil
   175  }
   176  
   177  func SwapMemory() (*SwapMemoryStat, error) {
   178  	return SwapMemoryWithContext(context.Background())
   179  }
   180  
   181  func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
   182  	sysinfo := &unix.Sysinfo_t{}
   183  
   184  	if err := unix.Sysinfo(sysinfo); err != nil {
   185  		return nil, err
   186  	}
   187  	ret := &SwapMemoryStat{
   188  		Total: uint64(sysinfo.Totalswap) * uint64(sysinfo.Unit),
   189  		Free:  uint64(sysinfo.Freeswap) * uint64(sysinfo.Unit),
   190  	}
   191  	ret.Used = ret.Total - ret.Free
   192  	//check Infinity
   193  	if ret.Total != 0 {
   194  		ret.UsedPercent = float64(ret.Total-ret.Free) / float64(ret.Total) * 100.0
   195  	} else {
   196  		ret.UsedPercent = 0
   197  	}
   198  	filename := common.HostProc("vmstat")
   199  	lines, _ := common.ReadLines(filename)
   200  	for _, l := range lines {
   201  		fields := strings.Fields(l)
   202  		if len(fields) < 2 {
   203  			continue
   204  		}
   205  		switch fields[0] {
   206  		case "pswpin":
   207  			value, err := strconv.ParseUint(fields[1], 10, 64)
   208  			if err != nil {
   209  				continue
   210  			}
   211  			ret.Sin = value * 4 * 1024
   212  		case "pswpout":
   213  			value, err := strconv.ParseUint(fields[1], 10, 64)
   214  			if err != nil {
   215  				continue
   216  			}
   217  			ret.Sout = value * 4 * 1024
   218  		case "pgpgin":
   219  			value, err := strconv.ParseUint(fields[1], 10, 64)
   220  			if err != nil {
   221  				continue
   222  			}
   223  			ret.PgIn = value * 4 * 1024
   224  		case "pgpgout":
   225  			value, err := strconv.ParseUint(fields[1], 10, 64)
   226  			if err != nil {
   227  				continue
   228  			}
   229  			ret.PgOut = value * 4 * 1024
   230  		case "pgfault":
   231  			value, err := strconv.ParseUint(fields[1], 10, 64)
   232  			if err != nil {
   233  				continue
   234  			}
   235  			ret.PgFault = value * 4 * 1024
   236  		case "pgmajfault":
   237  			value, err := strconv.ParseUint(fields[1], 10, 64)
   238  			if err != nil {
   239  				continue
   240  			}
   241  			ret.PgMajFault = value * 4 * 1024
   242  		}
   243  	}
   244  	return ret, nil
   245  }
   246  
   247  // calcuateAvailVmem is a fallback under kernel 3.14 where /proc/meminfo does not provide
   248  // "MemAvailable:" column. It reimplements an algorithm from the link below
   249  // https://github.com/giampaolo/psutil/pull/890
   250  func calcuateAvailVmem(ret *VirtualMemoryStat, retEx *VirtualMemoryExStat) uint64 {
   251  	var watermarkLow uint64
   252  
   253  	fn := common.HostProc("zoneinfo")
   254  	lines, err := common.ReadLines(fn)
   255  
   256  	if err != nil {
   257  		return ret.Free + ret.Cached // fallback under kernel 2.6.13
   258  	}
   259  
   260  	pagesize := uint64(os.Getpagesize())
   261  	watermarkLow = 0
   262  
   263  	for _, line := range lines {
   264  		fields := strings.Fields(line)
   265  
   266  		if strings.HasPrefix(fields[0], "low") {
   267  			lowValue, err := strconv.ParseUint(fields[1], 10, 64)
   268  
   269  			if err != nil {
   270  				lowValue = 0
   271  			}
   272  			watermarkLow += lowValue
   273  		}
   274  	}
   275  
   276  	watermarkLow *= pagesize
   277  
   278  	availMemory := ret.Free - watermarkLow
   279  	pageCache := retEx.ActiveFile + retEx.InactiveFile
   280  	pageCache -= uint64(math.Min(float64(pageCache/2), float64(watermarkLow)))
   281  	availMemory += pageCache
   282  	availMemory += ret.SReclaimable - uint64(math.Min(float64(ret.SReclaimable/2.0), float64(watermarkLow)))
   283  
   284  	return availMemory
   285  }