diff --git a/mem/mem.go b/mem/mem.go index 67f8741e7c94ac25cff4837fd03b3a0c52ddbc08..f77c6f01f762cfe67617bb2074506df10bf38703 100644 --- a/mem/mem.go +++ b/mem/mem.go @@ -4,18 +4,44 @@ import ( "encoding/json" ) +// Memory usage statistics. Total, Available and Used contain numbers of bytes +// for human consumption. +// +// The other fields in this struct contain kernel specific values. type VirtualMemoryStat struct { - Total uint64 `json:"total"` - Available uint64 `json:"available"` - Used uint64 `json:"used"` + // Total amount of RAM on this system + Total uint64 `json:"total"` + + // RAM available for programs to allocate + // + // This value is computed from the kernel specific values. + Available uint64 `json:"available"` + + // RAM used by programs + // + // This value is computed from the kernel specific values. + Used uint64 `json:"used"` + + // Percentage of RAM used by programs + // + // This value is computed from the kernel specific values. UsedPercent float64 `json:"used_percent"` - Free uint64 `json:"free"` - Active uint64 `json:"active"` - Inactive uint64 `json:"inactive"` - Buffers uint64 `json:"buffers"` - Cached uint64 `json:"cached"` - Wired uint64 `json:"wired"` - Shared uint64 `json:"shared"` + + // This is the kernel's notion of free memory; RAM chips whose bits nobody + // cares about the value of right now. For a human consumable number, + // Available is what you really want. + Free uint64 `json:"free"` + + // OS X / BSD specific numbers: + // http://www.macyourself.com/2010/02/17/what-is-free-wired-active-and-inactive-system-memory-ram/ + Active uint64 `json:"active"` + Inactive uint64 `json:"inactive"` + Wired uint64 `json:"wired"` + + // Linux specific numbers + // https://www.centos.org/docs/5/html/5.1/Deployment_Guide/s2-proc-meminfo.html + Buffers uint64 `json:"buffers"` + Cached uint64 `json:"cached"` } type SwapMemoryStat struct { diff --git a/mem/mem_darwin.go b/mem/mem_darwin.go index 33147bba795ddcd90cb6800f8afc3a5d1758cc6c..922b05cb4f60da498feb9ca7c1d8c33276c6974c 100644 --- a/mem/mem_darwin.go +++ b/mem/mem_darwin.go @@ -3,12 +3,29 @@ package mem import ( + "encoding/binary" "strconv" "strings" + "syscall" "github.com/shirou/gopsutil/internal/common" ) +func getHwMemsize() (uint64, error) { + totalString, err := syscall.Sysctl("hw.memsize") + if err != nil { + return 0, err + } + + // syscall.sysctl() helpfully assumes the result is a null-terminated string and + // removes the last byte of the result if it's 0 :/ + totalString += "\x00" + + total := uint64(binary.LittleEndian.Uint64([]byte(totalString))) + + return total, nil +} + // SwapMemory returns swapinfo. func SwapMemory() (*SwapMemoryStat, error) { var ret *SwapMemoryStat diff --git a/mem/mem_darwin_cgo.go b/mem/mem_darwin_cgo.go index 1d08528188e9c513dad79f5b33f81b6b4420e100..461631976e8b0e9eaf80b7a715d5a28dec8686fd 100644 --- a/mem/mem_darwin_cgo.go +++ b/mem/mem_darwin_cgo.go @@ -28,19 +28,20 @@ func VirtualMemory() (*VirtualMemoryStat, error) { return nil, fmt.Errorf("host_statistics error=%d", status) } - totalCount := vmstat.wire_count + - vmstat.active_count + - vmstat.inactive_count + - vmstat.free_count + pageSize := uint64(syscall.Getpagesize()) + total, err := getHwMemsize() + if err != nil { + return nil, err + } + totalCount := C.natural_t(total / pageSize) availableCount := vmstat.inactive_count + vmstat.free_count usedPercent := 100 * float64(totalCount-availableCount) / float64(totalCount) - usedCount := totalCount - vmstat.free_count + usedCount := totalCount - availableCount - pageSize := uint64(syscall.Getpagesize()) return &VirtualMemoryStat{ - Total: pageSize * uint64(totalCount), + Total: total, Available: pageSize * uint64(availableCount), Used: pageSize * uint64(usedCount), UsedPercent: usedPercent, diff --git a/mem/mem_darwin_nocgo.go b/mem/mem_darwin_nocgo.go index d1bd4f657dc56a99d554c53bfb7abd2ab4679aeb..f3b085e8d194776829fc3ba1e8efeb929d9b4791 100644 --- a/mem/mem_darwin_nocgo.go +++ b/mem/mem_darwin_nocgo.go @@ -8,8 +8,6 @@ import ( "strconv" "strings" "syscall" - - "github.com/shirou/gopsutil/internal/common" ) // Runs vm_stat and returns Free and inactive pages @@ -67,11 +65,7 @@ func parseVMStat(out string, vms *VirtualMemoryStat) error { func VirtualMemory() (*VirtualMemoryStat, error) { ret := &VirtualMemoryStat{} - t, err := common.DoSysctrl("hw.memsize") - if err != nil { - return nil, err - } - total, err := strconv.ParseUint(t[0], 10, 64) + total, err := getHwMemsize() if err != nil { return nil, err } @@ -83,8 +77,8 @@ func VirtualMemory() (*VirtualMemoryStat, error) { ret.Available = ret.Free + ret.Inactive ret.Total = total - ret.Used = ret.Total - ret.Free - ret.UsedPercent = float64(ret.Total-ret.Available) / float64(ret.Total) * 100.0 + ret.Used = ret.Total - ret.Available + ret.UsedPercent = 100 * float64(ret.Used) / float64(ret.Total) return ret, nil } diff --git a/mem/mem_darwin_test.go b/mem/mem_darwin_test.go index 0f2ea3a93c626d58ab1386f6720697222e2a6269..6d9e6b89c490428d136c0c1819149cabefc5cf43 100644 --- a/mem/mem_darwin_test.go +++ b/mem/mem_darwin_test.go @@ -3,6 +3,9 @@ package mem import ( + "os/exec" + "strconv" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -12,13 +15,16 @@ func TestVirtualMemoryDarwin(t *testing.T) { v, err := VirtualMemory() assert.Nil(t, err) - assert.True(t, v.Total > 0) - - assert.Equal(t, v.Total, v.Active+v.Inactive+v.Free+v.Wired) + outBytes, err := exec.Command("/usr/sbin/sysctl", "hw.memsize").Output() + assert.Nil(t, err) + outString := string(outBytes) + outString = strings.TrimSpace(outString) + outParts := strings.Split(outString, " ") + actualTotal, err := strconv.ParseInt(outParts[1], 10, 64) + assert.Nil(t, err) + assert.Equal(t, uint64(actualTotal), v.Total) assert.True(t, v.Available > 0) - assert.True(t, v.Available < v.Total) - assert.Equal(t, v.Available, v.Total-v.Wired-v.Active, "%v", v) assert.Equal(t, v.Available, v.Free+v.Inactive, "%v", v) assert.True(t, v.Used > 0) diff --git a/mem/mem_freebsd.go b/mem/mem_freebsd.go index 7c64f105ac47a4d784d5607fc3ac86afd98b1eb9..808538d81511e8f3a113c951806227dce644fb91 100644 --- a/mem/mem_freebsd.go +++ b/mem/mem_freebsd.go @@ -79,8 +79,8 @@ func VirtualMemory() (*VirtualMemoryStat, error) { } ret.Available = ret.Inactive + ret.Cached + ret.Free - ret.Used = ret.Active + ret.Wired + ret.Cached - ret.UsedPercent = float64(ret.Total-ret.Available) / float64(ret.Total) * 100.0 + ret.Used = ret.Total - ret.Available + ret.UsedPercent = float64(ret.Used) / float64(ret.Total) * 100.0 return ret, nil } diff --git a/mem/mem_linux.go b/mem/mem_linux.go index fc9226219d7fb4398d53797b4e5bc7ad63296b12..899da83dffb4c2fa7e17ebad997846300f1d020b 100644 --- a/mem/mem_linux.go +++ b/mem/mem_linux.go @@ -51,7 +51,7 @@ func VirtualMemory() (*VirtualMemoryStat, error) { if !memavail { ret.Available = ret.Free + ret.Buffers + ret.Cached } - ret.Used = ret.Total - ret.Free + ret.Used = ret.Total - ret.Available ret.UsedPercent = float64(ret.Total-ret.Available) / float64(ret.Total) * 100.0 return ret, nil diff --git a/mem/mem_test.go b/mem/mem_test.go index 28693574a5aada2f78a0baace472951698601316..770b6f315f37cdc360ff4f79ef66964460ba9231 100644 --- a/mem/mem_test.go +++ b/mem/mem_test.go @@ -3,6 +3,8 @@ package mem import ( "fmt" "testing" + + "github.com/stretchr/testify/assert" ) func TestVirtual_memory(t *testing.T) { @@ -14,6 +16,21 @@ func TestVirtual_memory(t *testing.T) { if v == empty { t.Errorf("error %v", v) } + + assert.True(t, v.Total > 0) + assert.True(t, v.Available > 0) + assert.True(t, v.Used > 0) + + assert.Equal(t, v.Total, v.Available+v.Used, + "Total should be computable from available + used: %v", v) + + assert.True(t, v.Free > 0) + assert.True(t, v.Available > v.Free, + "Free should be a subset of Available: %v", v) + + assert.InDelta(t, v.UsedPercent, + 100*float64(v.Used)/float64(v.Total), 0.1, + "UsedPercent should be how many percent of Total is Used: %v", v) } func TestSwap_memory(t *testing.T) { @@ -35,7 +52,7 @@ func TestVirtualMemoryStat_String(t *testing.T) { UsedPercent: 30.1, Free: 40, } - e := `{"total":10,"available":20,"used":30,"used_percent":30.1,"free":40,"active":0,"inactive":0,"buffers":0,"cached":0,"wired":0,"shared":0}` + e := `{"total":10,"available":20,"used":30,"used_percent":30.1,"free":40,"active":0,"inactive":0,"wired":0,"buffers":0,"cached":0}` if e != fmt.Sprintf("%v", v) { t.Errorf("VirtualMemoryStat string is invalid: %v", v) }