| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514 |
- // +build linux
- package mem
- import (
- "bufio"
- "context"
- "encoding/json"
- "fmt"
- "io"
- "math"
- "os"
- "strconv"
- "strings"
- "github.com/shirou/gopsutil/internal/common"
- "golang.org/x/sys/unix"
- )
- type VirtualMemoryExStat struct {
- ActiveFile uint64 `json:"activefile"`
- InactiveFile uint64 `json:"inactivefile"`
- ActiveAnon uint64 `json:"activeanon"`
- InactiveAnon uint64 `json:"inactiveanon"`
- Unevictable uint64 `json:"unevictable"`
- }
- func (v VirtualMemoryExStat) String() string {
- s, _ := json.Marshal(v)
- return string(s)
- }
- func VirtualMemory() (*VirtualMemoryStat, error) {
- return VirtualMemoryWithContext(context.Background())
- }
- func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
- vm, _, err := fillFromMeminfoWithContext(ctx)
- if err != nil {
- return nil, err
- }
- return vm, nil
- }
- func VirtualMemoryEx() (*VirtualMemoryExStat, error) {
- return VirtualMemoryExWithContext(context.Background())
- }
- func VirtualMemoryExWithContext(ctx context.Context) (*VirtualMemoryExStat, error) {
- _, vmEx, err := fillFromMeminfoWithContext(ctx)
- if err != nil {
- return nil, err
- }
- return vmEx, nil
- }
- func fillFromMeminfoWithContext(ctx context.Context) (*VirtualMemoryStat, *VirtualMemoryExStat, error) {
- filename := common.HostProc("meminfo")
- lines, _ := common.ReadLines(filename)
- // flag if MemAvailable is in /proc/meminfo (kernel 3.14+)
- memavail := false
- activeFile := false // "Active(file)" not available: 2.6.28 / Dec 2008
- inactiveFile := false // "Inactive(file)" not available: 2.6.28 / Dec 2008
- sReclaimable := false // "SReclaimable:" not available: 2.6.19 / Nov 2006
- ret := &VirtualMemoryStat{}
- retEx := &VirtualMemoryExStat{}
- for _, line := range lines {
- fields := strings.Split(line, ":")
- if len(fields) != 2 {
- continue
- }
- key := strings.TrimSpace(fields[0])
- value := strings.TrimSpace(fields[1])
- value = strings.Replace(value, " kB", "", -1)
- switch key {
- case "MemTotal":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.Total = t * 1024
- case "MemFree":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.Free = t * 1024
- case "MemAvailable":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- memavail = true
- ret.Available = t * 1024
- case "Buffers":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.Buffers = t * 1024
- case "Cached":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.Cached = t * 1024
- case "Active":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.Active = t * 1024
- case "Inactive":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.Inactive = t * 1024
- case "Active(anon)":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- retEx.ActiveAnon = t * 1024
- case "Inactive(anon)":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- retEx.InactiveAnon = t * 1024
- case "Active(file)":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- activeFile = true
- retEx.ActiveFile = t * 1024
- case "Inactive(file)":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- inactiveFile = true
- retEx.InactiveFile = t * 1024
- case "Unevictable":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- retEx.Unevictable = t * 1024
- case "Writeback":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.Writeback = t * 1024
- case "WritebackTmp":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.WritebackTmp = t * 1024
- case "Dirty":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.Dirty = t * 1024
- case "Shmem":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.Shared = t * 1024
- case "Slab":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.Slab = t * 1024
- case "SReclaimable":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- sReclaimable = true
- ret.SReclaimable = t * 1024
- case "SUnreclaim":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.SUnreclaim = t * 1024
- case "PageTables":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.PageTables = t * 1024
- case "SwapCached":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.SwapCached = t * 1024
- case "CommitLimit":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.CommitLimit = t * 1024
- case "Committed_AS":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.CommittedAS = t * 1024
- case "HighTotal":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.HighTotal = t * 1024
- case "HighFree":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.HighFree = t * 1024
- case "LowTotal":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.LowTotal = t * 1024
- case "LowFree":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.LowFree = t * 1024
- case "SwapTotal":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.SwapTotal = t * 1024
- case "SwapFree":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.SwapFree = t * 1024
- case "Mapped":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.Mapped = t * 1024
- case "VmallocTotal":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.VMallocTotal = t * 1024
- case "VmallocUsed":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.VMallocUsed = t * 1024
- case "VmallocChunk":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.VMallocChunk = t * 1024
- case "HugePages_Total":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.HugePagesTotal = t
- case "HugePages_Free":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.HugePagesFree = t
- case "Hugepagesize":
- t, err := strconv.ParseUint(value, 10, 64)
- if err != nil {
- return ret, retEx, err
- }
- ret.HugePageSize = t * 1024
- }
- }
- ret.Cached += ret.SReclaimable
- if !memavail {
- if activeFile && inactiveFile && sReclaimable {
- ret.Available = calcuateAvailVmem(ret, retEx)
- } else {
- ret.Available = ret.Cached + ret.Free
- }
- }
- ret.Used = ret.Total - ret.Free - ret.Buffers - ret.Cached
- ret.UsedPercent = float64(ret.Used) / float64(ret.Total) * 100.0
- return ret, retEx, nil
- }
- func SwapMemory() (*SwapMemoryStat, error) {
- return SwapMemoryWithContext(context.Background())
- }
- func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
- sysinfo := &unix.Sysinfo_t{}
- if err := unix.Sysinfo(sysinfo); err != nil {
- return nil, err
- }
- ret := &SwapMemoryStat{
- Total: uint64(sysinfo.Totalswap) * uint64(sysinfo.Unit),
- Free: uint64(sysinfo.Freeswap) * uint64(sysinfo.Unit),
- }
- ret.Used = ret.Total - ret.Free
- //check Infinity
- if ret.Total != 0 {
- ret.UsedPercent = float64(ret.Total-ret.Free) / float64(ret.Total) * 100.0
- } else {
- ret.UsedPercent = 0
- }
- filename := common.HostProc("vmstat")
- lines, _ := common.ReadLines(filename)
- for _, l := range lines {
- fields := strings.Fields(l)
- if len(fields) < 2 {
- continue
- }
- switch fields[0] {
- case "pswpin":
- value, err := strconv.ParseUint(fields[1], 10, 64)
- if err != nil {
- continue
- }
- ret.Sin = value * 4 * 1024
- case "pswpout":
- value, err := strconv.ParseUint(fields[1], 10, 64)
- if err != nil {
- continue
- }
- ret.Sout = value * 4 * 1024
- case "pgpgin":
- value, err := strconv.ParseUint(fields[1], 10, 64)
- if err != nil {
- continue
- }
- ret.PgIn = value * 4 * 1024
- case "pgpgout":
- value, err := strconv.ParseUint(fields[1], 10, 64)
- if err != nil {
- continue
- }
- ret.PgOut = value * 4 * 1024
- case "pgfault":
- value, err := strconv.ParseUint(fields[1], 10, 64)
- if err != nil {
- continue
- }
- ret.PgFault = value * 4 * 1024
- case "pgmajfault":
- value, err := strconv.ParseUint(fields[1], 10, 64)
- if err != nil {
- continue
- }
- ret.PgMajFault = value * 4 * 1024
- }
- }
- return ret, nil
- }
- // calcuateAvailVmem is a fallback under kernel 3.14 where /proc/meminfo does not provide
- // "MemAvailable:" column. It reimplements an algorithm from the link below
- // https://github.com/giampaolo/psutil/pull/890
- func calcuateAvailVmem(ret *VirtualMemoryStat, retEx *VirtualMemoryExStat) uint64 {
- var watermarkLow uint64
- fn := common.HostProc("zoneinfo")
- lines, err := common.ReadLines(fn)
- if err != nil {
- return ret.Free + ret.Cached // fallback under kernel 2.6.13
- }
- pagesize := uint64(os.Getpagesize())
- watermarkLow = 0
- for _, line := range lines {
- fields := strings.Fields(line)
- if strings.HasPrefix(fields[0], "low") {
- lowValue, err := strconv.ParseUint(fields[1], 10, 64)
- if err != nil {
- lowValue = 0
- }
- watermarkLow += lowValue
- }
- }
- watermarkLow *= pagesize
- availMemory := ret.Free - watermarkLow
- pageCache := retEx.ActiveFile + retEx.InactiveFile
- pageCache -= uint64(math.Min(float64(pageCache/2), float64(watermarkLow)))
- availMemory += pageCache
- availMemory += ret.SReclaimable - uint64(math.Min(float64(ret.SReclaimable/2.0), float64(watermarkLow)))
- if availMemory < 0 {
- availMemory = 0
- }
- return availMemory
- }
- const swapsFilename = "swaps"
- // swaps file column indexes
- const (
- nameCol = 0
- // typeCol = 1
- totalCol = 2
- usedCol = 3
- // priorityCol = 4
- )
- func SwapDevices() ([]*SwapDevice, error) {
- return SwapDevicesWithContext(context.Background())
- }
- func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) {
- swapsFilePath := common.HostProc(swapsFilename)
- f, err := os.Open(swapsFilePath)
- if err != nil {
- return nil, err
- }
- defer f.Close()
- return parseSwapsFile(f)
- }
- func parseSwapsFile(r io.Reader) ([]*SwapDevice, error) {
- swapsFilePath := common.HostProc(swapsFilename)
- scanner := bufio.NewScanner(r)
- if !scanner.Scan() {
- if err := scanner.Err(); err != nil {
- return nil, fmt.Errorf("couldn't read file %q: %w", swapsFilePath, err)
- }
- return nil, fmt.Errorf("unexpected end-of-file in %q", swapsFilePath)
- }
- // Check header headerFields are as expected
- headerFields := strings.Fields(scanner.Text())
- if len(headerFields) < usedCol {
- return nil, fmt.Errorf("couldn't parse %q: too few fields in header", swapsFilePath)
- }
- if headerFields[nameCol] != "Filename" {
- return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapsFilePath, headerFields[nameCol], "Filename")
- }
- if headerFields[totalCol] != "Size" {
- return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapsFilePath, headerFields[totalCol], "Size")
- }
- if headerFields[usedCol] != "Used" {
- return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapsFilePath, headerFields[usedCol], "Used")
- }
- var swapDevices []*SwapDevice
- for scanner.Scan() {
- fields := strings.Fields(scanner.Text())
- if len(fields) < usedCol {
- return nil, fmt.Errorf("couldn't parse %q: too few fields", swapsFilePath)
- }
- totalKiB, err := strconv.ParseUint(fields[totalCol], 10, 64)
- if err != nil {
- return nil, fmt.Errorf("couldn't parse 'Size' column in %q: %w", swapsFilePath, err)
- }
- usedKiB, err := strconv.ParseUint(fields[usedCol], 10, 64)
- if err != nil {
- return nil, fmt.Errorf("couldn't parse 'Used' column in %q: %w", swapsFilePath, err)
- }
- swapDevices = append(swapDevices, &SwapDevice{
- Name: fields[nameCol],
- UsedBytes: usedKiB * 1024,
- FreeBytes: (totalKiB - usedKiB) * 1024,
- })
- }
- if err := scanner.Err(); err != nil {
- return nil, fmt.Errorf("couldn't read file %q: %w", swapsFilePath, err)
- }
- return swapDevices, nil
- }
|