Merge pull request #161 from walles/walles/darwin-vmperf

Use OS calls rather than exec() to get memory stats on Darwin
pull/163/head
shirou 9 years ago
commit e04a02d2d7

@ -3,113 +3,12 @@
package mem
import (
"os/exec"
"strconv"
"strings"
"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
}
// 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{}
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
ret.Used = ret.Total - ret.Free
ret.UsedPercent = float64(ret.Total-ret.Available) / float64(ret.Total) * 100.0
return ret, nil
}
// SwapMemory returns swapinfo.
func SwapMemory() (*SwapMemoryStat, error) {
var ret *SwapMemoryStat

@ -0,0 +1,52 @@
// +build darwin
// +build cgo
package mem
/*
#include <mach/mach_host.h>
*/
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
}

@ -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
}

@ -4,64 +4,38 @@ package mem
import (
"testing"
"github.com/stretchr/testify/assert"
)
var vm_stat_out = `
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 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)
func TestParseVmStat(t *testing.T) {
ret := &VirtualMemoryStat{}
err := parseVmStat(vm_stat_out, 4096, ret)
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)
if err != nil {
t.Errorf("Expected no error, got %s\n", err.Error())
}
assert.True(t, v.Used > 0)
assert.True(t, v.Used < v.Total)
if ret.Free != uint64(105885*4096) {
t.Errorf("Free pages, actual: %d, expected: %d", ret.Free,
105885*4096)
}
assert.True(t, v.UsedPercent > 0)
assert.True(t, v.UsedPercent < 100)
if ret.Inactive != uint64(449242*4096) {
t.Errorf("Inactive pages, actual: %d, expected: %d", ret.Inactive,
449242*4096)
}
assert.True(t, v.Free > 0)
assert.True(t, v.Free < v.Available)
if ret.Active != uint64(725641*4096) {
t.Errorf("Active pages, actual: %d, expected: %d", ret.Active,
725641*4096)
}
assert.True(t, v.Active > 0)
assert.True(t, v.Active < v.Total)
if ret.Wired != uint64(560835*4096) {
t.Errorf("Wired pages, actual: %d, expected: %d", ret.Wired,
560835*4096)
}
assert.True(t, v.Inactive > 0)
assert.True(t, v.Inactive < v.Total)
if ret.Cached != uint64(128967*4096+449242.*4096) {
t.Errorf("Cached pages, actual: %d, expected: %f", ret.Cached,
128967*4096+449242.*4096)
}
assert.True(t, v.Wired > 0)
assert.True(t, v.Wired < v.Total)
}

Loading…
Cancel
Save