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.
pull/161/head
Johan Walles 9 years ago
parent 5771ea9465
commit 13e00c76e4

@ -2,10 +2,18 @@
package mem
/*
#include <unistd.h>
#include <mach/mach_host.h>
*/
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.

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

Loading…
Cancel
Save