github.com/isyscore/isc-gobase@v1.5.3-0.20231218061332-cbc7451899e9/system/mem/mem_linux.go (about)

     1  //go:build linux
     2  
     3  package mem
     4  
     5  import (
     6  	"bufio"
     7  	"context"
     8  	"encoding/json"
     9  	"fmt"
    10  	"github.com/isyscore/isc-gobase/system/common"
    11  	"golang.org/x/sys/unix"
    12  	"io"
    13  	"math"
    14  	"os"
    15  	"strconv"
    16  	"strings"
    17  )
    18  
    19  type VirtualMemoryExStat struct {
    20  	ActiveFile   uint64 `json:"activefile"`
    21  	InactiveFile uint64 `json:"inactivefile"`
    22  	ActiveAnon   uint64 `json:"activeanon"`
    23  	InactiveAnon uint64 `json:"inactiveanon"`
    24  	Unevictable  uint64 `json:"unevictable"`
    25  }
    26  
    27  func (v VirtualMemoryExStat) String() string {
    28  	s, _ := json.Marshal(v)
    29  	return string(s)
    30  }
    31  
    32  func VirtualMemory() (*VirtualMemoryStat, error) {
    33  	return VirtualMemoryWithContext(context.Background())
    34  }
    35  
    36  func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
    37  	vm, _, err := fillFromMeminfoWithContext(ctx)
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  	return vm, nil
    42  }
    43  
    44  func VirtualMemoryEx() (*VirtualMemoryExStat, error) {
    45  	return VirtualMemoryExWithContext(context.Background())
    46  }
    47  
    48  func VirtualMemoryExWithContext(ctx context.Context) (*VirtualMemoryExStat, error) {
    49  	_, vmEx, err := fillFromMeminfoWithContext(ctx)
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  	return vmEx, nil
    54  }
    55  
    56  func fillFromMeminfoWithContext(ctx context.Context) (*VirtualMemoryStat, *VirtualMemoryExStat, error) {
    57  	filename := common.HostProc("meminfo")
    58  	lines, _ := common.ReadLines(filename)
    59  
    60  	// flag if MemAvailable is in /proc/meminfo (kernel 3.14+)
    61  	memavail := false
    62  	activeFile := false   // "Active(file)" not available: 2.6.28 / Dec 2008
    63  	inactiveFile := false // "Inactive(file)" not available: 2.6.28 / Dec 2008
    64  	sReclaimable := false // "SReclaimable:" not available: 2.6.19 / Nov 2006
    65  
    66  	ret := &VirtualMemoryStat{}
    67  	retEx := &VirtualMemoryExStat{}
    68  
    69  	for _, line := range lines {
    70  		fields := strings.Split(line, ":")
    71  		if len(fields) != 2 {
    72  			continue
    73  		}
    74  		key := strings.TrimSpace(fields[0])
    75  		value := strings.TrimSpace(fields[1])
    76  		value = strings.Replace(value, " kB", "", -1)
    77  
    78  		switch key {
    79  		case "MemTotal":
    80  			t, err := strconv.ParseUint(value, 10, 64)
    81  			if err != nil {
    82  				return ret, retEx, err
    83  			}
    84  			ret.Total = t * 1024
    85  		case "MemFree":
    86  			t, err := strconv.ParseUint(value, 10, 64)
    87  			if err != nil {
    88  				return ret, retEx, err
    89  			}
    90  			ret.Free = t * 1024
    91  		case "MemAvailable":
    92  			t, err := strconv.ParseUint(value, 10, 64)
    93  			if err != nil {
    94  				return ret, retEx, err
    95  			}
    96  			memavail = true
    97  			ret.Available = t * 1024
    98  		case "Buffers":
    99  			t, err := strconv.ParseUint(value, 10, 64)
   100  			if err != nil {
   101  				return ret, retEx, err
   102  			}
   103  			ret.Buffers = t * 1024
   104  		case "Cached":
   105  			t, err := strconv.ParseUint(value, 10, 64)
   106  			if err != nil {
   107  				return ret, retEx, err
   108  			}
   109  			ret.Cached = t * 1024
   110  		case "Active":
   111  			t, err := strconv.ParseUint(value, 10, 64)
   112  			if err != nil {
   113  				return ret, retEx, err
   114  			}
   115  			ret.Active = t * 1024
   116  		case "Inactive":
   117  			t, err := strconv.ParseUint(value, 10, 64)
   118  			if err != nil {
   119  				return ret, retEx, err
   120  			}
   121  			ret.Inactive = t * 1024
   122  		case "Active(anon)":
   123  			t, err := strconv.ParseUint(value, 10, 64)
   124  			if err != nil {
   125  				return ret, retEx, err
   126  			}
   127  			retEx.ActiveAnon = t * 1024
   128  		case "Inactive(anon)":
   129  			t, err := strconv.ParseUint(value, 10, 64)
   130  			if err != nil {
   131  				return ret, retEx, err
   132  			}
   133  			retEx.InactiveAnon = t * 1024
   134  		case "Active(file)":
   135  			t, err := strconv.ParseUint(value, 10, 64)
   136  			if err != nil {
   137  				return ret, retEx, err
   138  			}
   139  			activeFile = true
   140  			retEx.ActiveFile = t * 1024
   141  		case "Inactive(file)":
   142  			t, err := strconv.ParseUint(value, 10, 64)
   143  			if err != nil {
   144  				return ret, retEx, err
   145  			}
   146  			inactiveFile = true
   147  			retEx.InactiveFile = t * 1024
   148  		case "Unevictable":
   149  			t, err := strconv.ParseUint(value, 10, 64)
   150  			if err != nil {
   151  				return ret, retEx, err
   152  			}
   153  			retEx.Unevictable = t * 1024
   154  		case "Writeback":
   155  			t, err := strconv.ParseUint(value, 10, 64)
   156  			if err != nil {
   157  				return ret, retEx, err
   158  			}
   159  			ret.Writeback = t * 1024
   160  		case "WritebackTmp":
   161  			t, err := strconv.ParseUint(value, 10, 64)
   162  			if err != nil {
   163  				return ret, retEx, err
   164  			}
   165  			ret.WritebackTmp = t * 1024
   166  		case "Dirty":
   167  			t, err := strconv.ParseUint(value, 10, 64)
   168  			if err != nil {
   169  				return ret, retEx, err
   170  			}
   171  			ret.Dirty = t * 1024
   172  		case "Shmem":
   173  			t, err := strconv.ParseUint(value, 10, 64)
   174  			if err != nil {
   175  				return ret, retEx, err
   176  			}
   177  			ret.Shared = t * 1024
   178  		case "Slab":
   179  			t, err := strconv.ParseUint(value, 10, 64)
   180  			if err != nil {
   181  				return ret, retEx, err
   182  			}
   183  			ret.Slab = t * 1024
   184  		case "SReclaimable":
   185  			t, err := strconv.ParseUint(value, 10, 64)
   186  			if err != nil {
   187  				return ret, retEx, err
   188  			}
   189  			sReclaimable = true
   190  			ret.SReclaimable = t * 1024
   191  		case "SUnreclaim":
   192  			t, err := strconv.ParseUint(value, 10, 64)
   193  			if err != nil {
   194  				return ret, retEx, err
   195  			}
   196  			ret.SUnreclaim = t * 1024
   197  		case "PageTables":
   198  			t, err := strconv.ParseUint(value, 10, 64)
   199  			if err != nil {
   200  				return ret, retEx, err
   201  			}
   202  			ret.PageTables = t * 1024
   203  		case "SwapCached":
   204  			t, err := strconv.ParseUint(value, 10, 64)
   205  			if err != nil {
   206  				return ret, retEx, err
   207  			}
   208  			ret.SwapCached = t * 1024
   209  		case "CommitLimit":
   210  			t, err := strconv.ParseUint(value, 10, 64)
   211  			if err != nil {
   212  				return ret, retEx, err
   213  			}
   214  			ret.CommitLimit = t * 1024
   215  		case "Committed_AS":
   216  			t, err := strconv.ParseUint(value, 10, 64)
   217  			if err != nil {
   218  				return ret, retEx, err
   219  			}
   220  			ret.CommittedAS = t * 1024
   221  		case "HighTotal":
   222  			t, err := strconv.ParseUint(value, 10, 64)
   223  			if err != nil {
   224  				return ret, retEx, err
   225  			}
   226  			ret.HighTotal = t * 1024
   227  		case "HighFree":
   228  			t, err := strconv.ParseUint(value, 10, 64)
   229  			if err != nil {
   230  				return ret, retEx, err
   231  			}
   232  			ret.HighFree = t * 1024
   233  		case "LowTotal":
   234  			t, err := strconv.ParseUint(value, 10, 64)
   235  			if err != nil {
   236  				return ret, retEx, err
   237  			}
   238  			ret.LowTotal = t * 1024
   239  		case "LowFree":
   240  			t, err := strconv.ParseUint(value, 10, 64)
   241  			if err != nil {
   242  				return ret, retEx, err
   243  			}
   244  			ret.LowFree = t * 1024
   245  		case "SwapTotal":
   246  			t, err := strconv.ParseUint(value, 10, 64)
   247  			if err != nil {
   248  				return ret, retEx, err
   249  			}
   250  			ret.SwapTotal = t * 1024
   251  		case "SwapFree":
   252  			t, err := strconv.ParseUint(value, 10, 64)
   253  			if err != nil {
   254  				return ret, retEx, err
   255  			}
   256  			ret.SwapFree = t * 1024
   257  		case "Mapped":
   258  			t, err := strconv.ParseUint(value, 10, 64)
   259  			if err != nil {
   260  				return ret, retEx, err
   261  			}
   262  			ret.Mapped = t * 1024
   263  		case "VmallocTotal":
   264  			t, err := strconv.ParseUint(value, 10, 64)
   265  			if err != nil {
   266  				return ret, retEx, err
   267  			}
   268  			ret.VMallocTotal = t * 1024
   269  		case "VmallocUsed":
   270  			t, err := strconv.ParseUint(value, 10, 64)
   271  			if err != nil {
   272  				return ret, retEx, err
   273  			}
   274  			ret.VMallocUsed = t * 1024
   275  		case "VmallocChunk":
   276  			t, err := strconv.ParseUint(value, 10, 64)
   277  			if err != nil {
   278  				return ret, retEx, err
   279  			}
   280  			ret.VMallocChunk = t * 1024
   281  		case "HugePages_Total":
   282  			t, err := strconv.ParseUint(value, 10, 64)
   283  			if err != nil {
   284  				return ret, retEx, err
   285  			}
   286  			ret.HugePagesTotal = t
   287  		case "HugePages_Free":
   288  			t, err := strconv.ParseUint(value, 10, 64)
   289  			if err != nil {
   290  				return ret, retEx, err
   291  			}
   292  			ret.HugePagesFree = t
   293  		case "Hugepagesize":
   294  			t, err := strconv.ParseUint(value, 10, 64)
   295  			if err != nil {
   296  				return ret, retEx, err
   297  			}
   298  			ret.HugePageSize = t * 1024
   299  		}
   300  	}
   301  
   302  	ret.Cached += ret.SReclaimable
   303  
   304  	if !memavail {
   305  		if activeFile && inactiveFile && sReclaimable {
   306  			ret.Available = calcuateAvailVmem(ret, retEx)
   307  		} else {
   308  			ret.Available = ret.Cached + ret.Free
   309  		}
   310  	}
   311  
   312  	ret.Used = ret.Total - ret.Free - ret.Buffers - ret.Cached
   313  	ret.UsedPercent = float64(ret.Used) / float64(ret.Total) * 100.0
   314  
   315  	return ret, retEx, nil
   316  }
   317  
   318  func SwapMemory() (*SwapMemoryStat, error) {
   319  	return SwapMemoryWithContext(context.Background())
   320  }
   321  
   322  func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
   323  	sysinfo := &unix.Sysinfo_t{}
   324  
   325  	if err := unix.Sysinfo(sysinfo); err != nil {
   326  		return nil, err
   327  	}
   328  	ret := &SwapMemoryStat{
   329  		Total: uint64(sysinfo.Totalswap) * uint64(sysinfo.Unit),
   330  		Free:  uint64(sysinfo.Freeswap) * uint64(sysinfo.Unit),
   331  	}
   332  	ret.Used = ret.Total - ret.Free
   333  	//check Infinity
   334  	if ret.Total != 0 {
   335  		ret.UsedPercent = float64(ret.Total-ret.Free) / float64(ret.Total) * 100.0
   336  	} else {
   337  		ret.UsedPercent = 0
   338  	}
   339  	filename := common.HostProc("vmstat")
   340  	lines, _ := common.ReadLines(filename)
   341  	for _, l := range lines {
   342  		fields := strings.Fields(l)
   343  		if len(fields) < 2 {
   344  			continue
   345  		}
   346  		switch fields[0] {
   347  		case "pswpin":
   348  			value, err := strconv.ParseUint(fields[1], 10, 64)
   349  			if err != nil {
   350  				continue
   351  			}
   352  			ret.Sin = value * 4 * 1024
   353  		case "pswpout":
   354  			value, err := strconv.ParseUint(fields[1], 10, 64)
   355  			if err != nil {
   356  				continue
   357  			}
   358  			ret.Sout = value * 4 * 1024
   359  		case "pgpgin":
   360  			value, err := strconv.ParseUint(fields[1], 10, 64)
   361  			if err != nil {
   362  				continue
   363  			}
   364  			ret.PgIn = value * 4 * 1024
   365  		case "pgpgout":
   366  			value, err := strconv.ParseUint(fields[1], 10, 64)
   367  			if err != nil {
   368  				continue
   369  			}
   370  			ret.PgOut = value * 4 * 1024
   371  		case "pgfault":
   372  			value, err := strconv.ParseUint(fields[1], 10, 64)
   373  			if err != nil {
   374  				continue
   375  			}
   376  			ret.PgFault = value * 4 * 1024
   377  		case "pgmajfault":
   378  			value, err := strconv.ParseUint(fields[1], 10, 64)
   379  			if err != nil {
   380  				continue
   381  			}
   382  			ret.PgMajFault = value * 4 * 1024
   383  		}
   384  	}
   385  	return ret, nil
   386  }
   387  
   388  // calcuateAvailVmem is a fallback under kernel 3.14 where /proc/meminfo does not provide
   389  // "MemAvailable:" column. It reimplements an algorithm from the link below
   390  // https://github.com/giampaolo/psutil/pull/890
   391  func calcuateAvailVmem(ret *VirtualMemoryStat, retEx *VirtualMemoryExStat) uint64 {
   392  	var watermarkLow uint64
   393  
   394  	fn := common.HostProc("zoneinfo")
   395  	lines, err := common.ReadLines(fn)
   396  
   397  	if err != nil {
   398  		return ret.Free + ret.Cached // fallback under kernel 2.6.13
   399  	}
   400  
   401  	pagesize := uint64(os.Getpagesize())
   402  	watermarkLow = 0
   403  
   404  	for _, line := range lines {
   405  		fields := strings.Fields(line)
   406  
   407  		if strings.HasPrefix(fields[0], "low") {
   408  			lowValue, err := strconv.ParseUint(fields[1], 10, 64)
   409  
   410  			if err != nil {
   411  				lowValue = 0
   412  			}
   413  			watermarkLow += lowValue
   414  		}
   415  	}
   416  
   417  	watermarkLow *= pagesize
   418  
   419  	availMemory := ret.Free - watermarkLow
   420  	pageCache := retEx.ActiveFile + retEx.InactiveFile
   421  	pageCache -= uint64(math.Min(float64(pageCache/2), float64(watermarkLow)))
   422  	availMemory += pageCache
   423  	availMemory += ret.SReclaimable - uint64(math.Min(float64(ret.SReclaimable/2.0), float64(watermarkLow)))
   424  
   425  	if availMemory < 0 {
   426  		availMemory = 0
   427  	}
   428  
   429  	return availMemory
   430  }
   431  
   432  const swapsFilename = "swaps"
   433  
   434  // swaps file column indexes
   435  const (
   436  	nameCol = 0
   437  	// typeCol     = 1
   438  	totalCol = 2
   439  	usedCol  = 3
   440  	// priorityCol = 4
   441  )
   442  
   443  func SwapDevices() ([]*SwapDevice, error) {
   444  	return SwapDevicesWithContext(context.Background())
   445  }
   446  
   447  func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) {
   448  	swapsFilePath := common.HostProc(swapsFilename)
   449  	f, err := os.Open(swapsFilePath)
   450  	if err != nil {
   451  		return nil, err
   452  	}
   453  	defer f.Close()
   454  
   455  	return parseSwapsFile(f)
   456  }
   457  
   458  func parseSwapsFile(r io.Reader) ([]*SwapDevice, error) {
   459  	swapsFilePath := common.HostProc(swapsFilename)
   460  	scanner := bufio.NewScanner(r)
   461  	if !scanner.Scan() {
   462  		if err := scanner.Err(); err != nil {
   463  			return nil, fmt.Errorf("couldn't read file %q: %w", swapsFilePath, err)
   464  		}
   465  		return nil, fmt.Errorf("unexpected end-of-file in %q", swapsFilePath)
   466  
   467  	}
   468  
   469  	// Check header headerFields are as expected
   470  	headerFields := strings.Fields(scanner.Text())
   471  	if len(headerFields) < usedCol {
   472  		return nil, fmt.Errorf("couldn't parse %q: too few fields in header", swapsFilePath)
   473  	}
   474  	if headerFields[nameCol] != "Filename" {
   475  		return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapsFilePath, headerFields[nameCol], "Filename")
   476  	}
   477  	if headerFields[totalCol] != "Size" {
   478  		return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapsFilePath, headerFields[totalCol], "Size")
   479  	}
   480  	if headerFields[usedCol] != "Used" {
   481  		return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapsFilePath, headerFields[usedCol], "Used")
   482  	}
   483  
   484  	var swapDevices []*SwapDevice
   485  	for scanner.Scan() {
   486  		fields := strings.Fields(scanner.Text())
   487  		if len(fields) < usedCol {
   488  			return nil, fmt.Errorf("couldn't parse %q: too few fields", swapsFilePath)
   489  		}
   490  
   491  		totalKiB, err := strconv.ParseUint(fields[totalCol], 10, 64)
   492  		if err != nil {
   493  			return nil, fmt.Errorf("couldn't parse 'Size' column in %q: %w", swapsFilePath, err)
   494  		}
   495  
   496  		usedKiB, err := strconv.ParseUint(fields[usedCol], 10, 64)
   497  		if err != nil {
   498  			return nil, fmt.Errorf("couldn't parse 'Used' column in %q: %w", swapsFilePath, err)
   499  		}
   500  
   501  		swapDevices = append(swapDevices, &SwapDevice{
   502  			Name:      fields[nameCol],
   503  			UsedBytes: usedKiB * 1024,
   504  			FreeBytes: (totalKiB - usedKiB) * 1024,
   505  		})
   506  	}
   507  
   508  	if err := scanner.Err(); err != nil {
   509  		return nil, fmt.Errorf("couldn't read file %q: %w", swapsFilePath, err)
   510  	}
   511  
   512  	return swapDevices, nil
   513  }