...

Source file src/github.com/shirou/gopsutil/mem/mem_linux.go

Documentation: github.com/shirou/gopsutil/mem

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

View as plain text