From 5771ea946534232d3ef3984b522567d011eabc9d Mon Sep 17 00:00:00 2001 From: Johan Walles Date: Fri, 19 Feb 2016 20:53:29 +0100 Subject: [PATCH 1/5] Add Darwin specific mem.VirtualMemory() tests --- mem/mem_darwin_test.go | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/mem/mem_darwin_test.go b/mem/mem_darwin_test.go index 730dc52f3..8db0e2db0 100644 --- a/mem/mem_darwin_test.go +++ b/mem/mem_darwin_test.go @@ -4,9 +4,11 @@ package mem import ( "testing" + + "github.com/stretchr/testify/assert" ) -var vm_stat_out = ` +var vmStatOut = ` Mach Virtual Memory Statistics: (page size of 4096 bytes) Pages free: 105885. Pages active: 725641. @@ -34,7 +36,7 @@ Swapouts: 3283599. func TestParseVmStat(t *testing.T) { ret := &VirtualMemoryStat{} - err := parseVmStat(vm_stat_out, 4096, ret) + err := parseVmStat(vmStatOut, 4096, ret) if err != nil { t.Errorf("Expected no error, got %s\n", err.Error()) @@ -65,3 +67,32 @@ func TestParseVmStat(t *testing.T) { 128967*4096+449242.*4096) } } + +func TestVirtualMemoryDarwin(t *testing.T) { + v, err := VirtualMemory() + assert.Nil(t, err) + + assert.True(t, v.Total > 0) + + 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.True(t, v.Used > 0) + assert.True(t, v.Used < v.Total) + + assert.True(t, v.UsedPercent > 0) + assert.True(t, v.UsedPercent < 100) + + assert.True(t, v.Free > 0) + assert.True(t, v.Free < v.Available) + + assert.True(t, v.Active > 0) + assert.True(t, v.Active < v.Total) + + assert.True(t, v.Inactive > 0) + assert.True(t, v.Inactive < v.Total) + + assert.True(t, v.Wired > 0) + assert.True(t, v.Wired < v.Total) +} From 13e00c76e424d7425ca08a649d7bec967b1dc2a8 Mon Sep 17 00:00:00 2001 From: Johan Walles Date: Fri, 19 Feb 2016 21:28:50 +0100 Subject: [PATCH 2/5] Use OS calls rather than exec() to get memory statistics Before this change we used to exec() various binaries to find out memory information. While this worked, it was awfully slow. And if somebody would want to compute how many percent of available memory all PIDs on the system uses, that would take almost ten seconds on my laptop with the previous implementation. This implementation fares a lot better, and is smaller. --- mem/mem_darwin.go | 118 +++++++++++++---------------------------- mem/mem_darwin_test.go | 60 --------------------- 2 files changed, 38 insertions(+), 140 deletions(-) diff --git a/mem/mem_darwin.go b/mem/mem_darwin.go index ba4dbd826..df997d70c 100644 --- a/mem/mem_darwin.go +++ b/mem/mem_darwin.go @@ -2,10 +2,18 @@ package mem +/* +#include +#include +*/ +import "C" + import ( + "fmt" "os/exec" "strconv" "strings" + "unsafe" "github.com/shirou/gopsutil/internal/common" ) @@ -23,91 +31,41 @@ func getPageSize() (uint64, error) { return p, nil } -// Runs vm_stat and returns Free and inactive pages -func getVmStat(pagesize uint64, vms *VirtualMemoryStat) error { - out, err := exec.Command("vm_stat").Output() - if err != nil { - return err - } - return parseVmStat(string(out), pagesize, vms) -} - -func parseVmStat(out string, pagesize uint64, vms *VirtualMemoryStat) error { - var err error - - lines := strings.Split(out, "\n") - for _, line := range lines { - fields := strings.Split(line, ":") - if len(fields) < 2 { - continue - } - key := strings.TrimSpace(fields[0]) - value := strings.Trim(fields[1], " .") - switch key { - case "Pages free": - free, e := strconv.ParseUint(value, 10, 64) - if e != nil { - err = e - } - vms.Free = free * pagesize - case "Pages inactive": - inactive, e := strconv.ParseUint(value, 10, 64) - if e != nil { - err = e - } - vms.Cached += inactive * pagesize - vms.Inactive = inactive * pagesize - case "Pages active": - active, e := strconv.ParseUint(value, 10, 64) - if e != nil { - err = e - } - vms.Active = active * pagesize - case "Pages wired down": - wired, e := strconv.ParseUint(value, 10, 64) - if e != nil { - err = e - } - vms.Wired = wired * pagesize - case "Pages purgeable": - purgeable, e := strconv.ParseUint(value, 10, 64) - if e != nil { - err = e - } - vms.Cached += purgeable * pagesize - } - } - return err -} - // VirtualMemory returns VirtualmemoryStat. func VirtualMemory() (*VirtualMemoryStat, error) { - ret := &VirtualMemoryStat{} + count := C.mach_msg_type_number_t(C.HOST_VM_INFO_COUNT) + var vmstat C.vm_statistics_data_t - p, err := getPageSize() - if err != nil { - return nil, err - } - t, err := common.DoSysctrl("hw.memsize") - if err != nil { - return nil, err - } - total, err := strconv.ParseUint(t[0], 10, 64) - if err != nil { - return nil, err - } - err = getVmStat(p, ret) - if err != nil { - return nil, err - } - - ret.Available = ret.Free + ret.Cached - ret.Total = total + status := C.host_statistics(C.host_t(C.mach_host_self()), + C.HOST_VM_INFO, + C.host_info_t(unsafe.Pointer(&vmstat)), + &count) - ret.Used = ret.Total - ret.Free - ret.UsedPercent = float64(ret.Total-ret.Available) / float64(ret.Total) * 100.0 + if status != C.KERN_SUCCESS { + return nil, fmt.Errorf("host_statistics error=%d", status) + } - return ret, nil + totalCount := vmstat.wire_count + + vmstat.active_count + + vmstat.inactive_count + + vmstat.free_count + + availableCount := vmstat.inactive_count + vmstat.free_count + usedPercent := 100 * float64(totalCount-availableCount) / float64(totalCount) + + usedCount := totalCount - vmstat.free_count + + pageSize := uint64(C.getpagesize()) + return &VirtualMemoryStat{ + Total: pageSize * uint64(totalCount), + Available: pageSize * uint64(availableCount), + Used: pageSize * uint64(usedCount), + UsedPercent: usedPercent, + Free: pageSize * uint64(vmstat.free_count), + Active: pageSize * uint64(vmstat.active_count), + Inactive: pageSize * uint64(vmstat.inactive_count), + Wired: pageSize * uint64(vmstat.wire_count), + }, nil } // SwapMemory returns swapinfo. diff --git a/mem/mem_darwin_test.go b/mem/mem_darwin_test.go index 8db0e2db0..cf10ea82a 100644 --- a/mem/mem_darwin_test.go +++ b/mem/mem_darwin_test.go @@ -8,66 +8,6 @@ import ( "github.com/stretchr/testify/assert" ) -var vmStatOut = ` -Mach Virtual Memory Statistics: (page size of 4096 bytes) -Pages free: 105885. -Pages active: 725641. -Pages inactive: 449242. -Pages speculative: 6155. -Pages throttled: 0. -Pages wired down: 560835. -Pages purgeable: 128967. -"Translation faults": 622528839. -Pages copy-on-write: 17697839. -Pages zero filled: 311034413. -Pages reactivated: 4705104. -Pages purged: 5605610. -File-backed pages: 349192. -Anonymous pages: 831846. -Pages stored in compressor: 876507. -Pages occupied by compressor: 249167. -Decompressions: 4555025. -Compressions: 7524729. -Pageins: 40532443. -Pageouts: 126496. -Swapins: 2988073. -Swapouts: 3283599. -` - -func TestParseVmStat(t *testing.T) { - ret := &VirtualMemoryStat{} - err := parseVmStat(vmStatOut, 4096, ret) - - if err != nil { - t.Errorf("Expected no error, got %s\n", err.Error()) - } - - if ret.Free != uint64(105885*4096) { - t.Errorf("Free pages, actual: %d, expected: %d", ret.Free, - 105885*4096) - } - - if ret.Inactive != uint64(449242*4096) { - t.Errorf("Inactive pages, actual: %d, expected: %d", ret.Inactive, - 449242*4096) - } - - if ret.Active != uint64(725641*4096) { - t.Errorf("Active pages, actual: %d, expected: %d", ret.Active, - 725641*4096) - } - - if ret.Wired != uint64(560835*4096) { - t.Errorf("Wired pages, actual: %d, expected: %d", ret.Wired, - 560835*4096) - } - - if ret.Cached != uint64(128967*4096+449242.*4096) { - t.Errorf("Cached pages, actual: %d, expected: %f", ret.Cached, - 128967*4096+449242.*4096) - } -} - func TestVirtualMemoryDarwin(t *testing.T) { v, err := VirtualMemory() assert.Nil(t, err) From a6cd6f803b35bd1e01cb9c85a471b0bd0514a11b Mon Sep 17 00:00:00 2001 From: Johan Walles Date: Fri, 19 Feb 2016 23:37:43 +0100 Subject: [PATCH 3/5] Use Go's built in syscall.Getpagesize() --- mem/mem_darwin.go | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/mem/mem_darwin.go b/mem/mem_darwin.go index df997d70c..5f744b896 100644 --- a/mem/mem_darwin.go +++ b/mem/mem_darwin.go @@ -10,27 +10,14 @@ import "C" import ( "fmt" - "os/exec" "strconv" "strings" + "syscall" "unsafe" "github.com/shirou/gopsutil/internal/common" ) -func getPageSize() (uint64, error) { - out, err := exec.Command("pagesize").Output() - if err != nil { - return 0, err - } - o := strings.TrimSpace(string(out)) - p, err := strconv.ParseUint(o, 10, 64) - if err != nil { - return 0, err - } - return p, nil -} - // VirtualMemory returns VirtualmemoryStat. func VirtualMemory() (*VirtualMemoryStat, error) { count := C.mach_msg_type_number_t(C.HOST_VM_INFO_COUNT) @@ -55,7 +42,7 @@ func VirtualMemory() (*VirtualMemoryStat, error) { usedCount := totalCount - vmstat.free_count - pageSize := uint64(C.getpagesize()) + pageSize := uint64(syscall.Getpagesize()) return &VirtualMemoryStat{ Total: pageSize * uint64(totalCount), Available: pageSize * uint64(availableCount), From 00504a37e31cecb9388189cd40ca1bcc8eb78c32 Mon Sep 17 00:00:00 2001 From: Johan Walles Date: Fri, 19 Feb 2016 23:39:00 +0100 Subject: [PATCH 4/5] Remove unused import --- mem/mem_darwin.go | 1 - 1 file changed, 1 deletion(-) diff --git a/mem/mem_darwin.go b/mem/mem_darwin.go index 5f744b896..566559583 100644 --- a/mem/mem_darwin.go +++ b/mem/mem_darwin.go @@ -3,7 +3,6 @@ package mem /* -#include #include */ import "C" From 44f817eb8d238214a03af1c3bd392a648e1d916d Mon Sep 17 00:00:00 2001 From: Johan Walles Date: Sat, 20 Feb 2016 20:37:17 +0100 Subject: [PATCH 5/5] Split mem_darwin.go into CGO and non CGO variants --- mem/mem_darwin.go | 45 --------------------- mem/mem_darwin_cgo.go | 52 ++++++++++++++++++++++++ mem/mem_darwin_nocgo.go | 90 +++++++++++++++++++++++++++++++++++++++++ mem/mem_darwin_test.go | 3 ++ 4 files changed, 145 insertions(+), 45 deletions(-) create mode 100644 mem/mem_darwin_cgo.go create mode 100644 mem/mem_darwin_nocgo.go diff --git a/mem/mem_darwin.go b/mem/mem_darwin.go index 566559583..33147bba7 100644 --- a/mem/mem_darwin.go +++ b/mem/mem_darwin.go @@ -2,58 +2,13 @@ package mem -/* -#include -*/ -import "C" - import ( - "fmt" "strconv" "strings" - "syscall" - "unsafe" "github.com/shirou/gopsutil/internal/common" ) -// VirtualMemory returns VirtualmemoryStat. -func VirtualMemory() (*VirtualMemoryStat, error) { - count := C.mach_msg_type_number_t(C.HOST_VM_INFO_COUNT) - var vmstat C.vm_statistics_data_t - - status := C.host_statistics(C.host_t(C.mach_host_self()), - C.HOST_VM_INFO, - C.host_info_t(unsafe.Pointer(&vmstat)), - &count) - - if status != C.KERN_SUCCESS { - return nil, fmt.Errorf("host_statistics error=%d", status) - } - - totalCount := vmstat.wire_count + - vmstat.active_count + - vmstat.inactive_count + - vmstat.free_count - - availableCount := vmstat.inactive_count + vmstat.free_count - usedPercent := 100 * float64(totalCount-availableCount) / float64(totalCount) - - usedCount := totalCount - vmstat.free_count - - pageSize := uint64(syscall.Getpagesize()) - return &VirtualMemoryStat{ - Total: pageSize * uint64(totalCount), - Available: pageSize * uint64(availableCount), - Used: pageSize * uint64(usedCount), - UsedPercent: usedPercent, - Free: pageSize * uint64(vmstat.free_count), - Active: pageSize * uint64(vmstat.active_count), - Inactive: pageSize * uint64(vmstat.inactive_count), - Wired: pageSize * uint64(vmstat.wire_count), - }, 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 new file mode 100644 index 000000000..1d0852818 --- /dev/null +++ b/mem/mem_darwin_cgo.go @@ -0,0 +1,52 @@ +// +build darwin +// +build cgo + +package mem + +/* +#include +*/ +import "C" + +import ( + "fmt" + "syscall" + "unsafe" +) + +// VirtualMemory returns VirtualmemoryStat. +func VirtualMemory() (*VirtualMemoryStat, error) { + count := C.mach_msg_type_number_t(C.HOST_VM_INFO_COUNT) + var vmstat C.vm_statistics_data_t + + status := C.host_statistics(C.host_t(C.mach_host_self()), + C.HOST_VM_INFO, + C.host_info_t(unsafe.Pointer(&vmstat)), + &count) + + if status != C.KERN_SUCCESS { + return nil, fmt.Errorf("host_statistics error=%d", status) + } + + totalCount := vmstat.wire_count + + vmstat.active_count + + vmstat.inactive_count + + vmstat.free_count + + availableCount := vmstat.inactive_count + vmstat.free_count + usedPercent := 100 * float64(totalCount-availableCount) / float64(totalCount) + + usedCount := totalCount - vmstat.free_count + + pageSize := uint64(syscall.Getpagesize()) + return &VirtualMemoryStat{ + Total: pageSize * uint64(totalCount), + Available: pageSize * uint64(availableCount), + Used: pageSize * uint64(usedCount), + UsedPercent: usedPercent, + Free: pageSize * uint64(vmstat.free_count), + Active: pageSize * uint64(vmstat.active_count), + Inactive: pageSize * uint64(vmstat.inactive_count), + Wired: pageSize * uint64(vmstat.wire_count), + }, nil +} diff --git a/mem/mem_darwin_nocgo.go b/mem/mem_darwin_nocgo.go new file mode 100644 index 000000000..d1bd4f657 --- /dev/null +++ b/mem/mem_darwin_nocgo.go @@ -0,0 +1,90 @@ +// +build darwin +// +build !cgo + +package mem + +import ( + "os/exec" + "strconv" + "strings" + "syscall" + + "github.com/shirou/gopsutil/internal/common" +) + +// Runs vm_stat and returns Free and inactive pages +func getVMStat(vms *VirtualMemoryStat) error { + out, err := exec.Command("vm_stat").Output() + if err != nil { + return err + } + return parseVMStat(string(out), vms) +} + +func parseVMStat(out string, vms *VirtualMemoryStat) error { + var err error + + lines := strings.Split(out, "\n") + pagesize := uint64(syscall.Getpagesize()) + for _, line := range lines { + fields := strings.Split(line, ":") + if len(fields) < 2 { + continue + } + key := strings.TrimSpace(fields[0]) + value := strings.Trim(fields[1], " .") + switch key { + case "Pages free": + free, e := strconv.ParseUint(value, 10, 64) + if e != nil { + err = e + } + vms.Free = free * pagesize + case "Pages inactive": + inactive, e := strconv.ParseUint(value, 10, 64) + if e != nil { + err = e + } + vms.Inactive = inactive * pagesize + case "Pages active": + active, e := strconv.ParseUint(value, 10, 64) + if e != nil { + err = e + } + vms.Active = active * pagesize + case "Pages wired down": + wired, e := strconv.ParseUint(value, 10, 64) + if e != nil { + err = e + } + vms.Wired = wired * pagesize + } + } + return err +} + +// VirtualMemory returns VirtualmemoryStat. +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) + if err != nil { + return nil, err + } + err = getVMStat(ret) + if err != nil { + return nil, err + } + + 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 + + return ret, nil +} diff --git a/mem/mem_darwin_test.go b/mem/mem_darwin_test.go index cf10ea82a..0f2ea3a93 100644 --- a/mem/mem_darwin_test.go +++ b/mem/mem_darwin_test.go @@ -14,9 +14,12 @@ func TestVirtualMemoryDarwin(t *testing.T) { assert.True(t, v.Total > 0) + assert.Equal(t, v.Total, v.Active+v.Inactive+v.Free+v.Wired) + 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) assert.True(t, v.Used < v.Total)