From 13e00c76e424d7425ca08a649d7bec967b1dc2a8 Mon Sep 17 00:00:00 2001 From: Johan Walles Date: Fri, 19 Feb 2016 21:28:50 +0100 Subject: [PATCH] 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 ba4dbd8..df997d7 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 8db0e2d..cf10ea8 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)