127 lines
3.3 KiB
Go
127 lines
3.3 KiB
Go
|
// +build darwin
|
||
|
|
||
|
package memory
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"os/exec"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"time"
|
||
|
"unsafe"
|
||
|
|
||
|
"golang.org/x/sys/unix"
|
||
|
)
|
||
|
|
||
|
// Get memory statistics
|
||
|
func Get() (*Stats, error) {
|
||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||
|
defer cancel()
|
||
|
|
||
|
// Reference: man 1 vm_stat
|
||
|
cmd := exec.CommandContext(ctx, "vm_stat")
|
||
|
out, err := cmd.StdoutPipe()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if err := cmd.Start(); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
memory, err := collectMemoryStats(out)
|
||
|
if err != nil {
|
||
|
go cmd.Wait()
|
||
|
return nil, err
|
||
|
}
|
||
|
if err := cmd.Wait(); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// Reference: sys/sysctl.h, man 3 sysctl, sysctl vm.swapusage
|
||
|
ret, err := unix.SysctlRaw("vm.swapusage")
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("failed in sysctl vm.swapusage: %s", err)
|
||
|
}
|
||
|
swap, err := collectSwapStats(ret)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
memory.SwapTotal = swap.Total
|
||
|
memory.SwapUsed = swap.Used
|
||
|
memory.SwapFree = swap.Avail
|
||
|
|
||
|
return memory, nil
|
||
|
}
|
||
|
|
||
|
// Stats represents memory statistics for darwin
|
||
|
type Stats struct {
|
||
|
Total, Used, Cached, Free, Active, Inactive, SwapTotal, SwapUsed, SwapFree uint64
|
||
|
}
|
||
|
|
||
|
// References:
|
||
|
// - https://support.apple.com/en-us/HT201464#memory
|
||
|
// - https://developer.apple.com/library/content/documentation/Performance/Conceptual/ManagingMemoryStats/Articles/AboutMemoryStats.html
|
||
|
// - https://opensource.apple.com/source/system_cmds/system_cmds-790/vm_stat.tproj/
|
||
|
func collectMemoryStats(out io.Reader) (*Stats, error) {
|
||
|
scanner := bufio.NewScanner(out)
|
||
|
if !scanner.Scan() {
|
||
|
return nil, fmt.Errorf("failed to scan output of vm_stat")
|
||
|
}
|
||
|
line := scanner.Text()
|
||
|
if !strings.HasPrefix(line, "Mach Virtual Memory Statistics:") {
|
||
|
return nil, fmt.Errorf("unexpected output of vm_stat: %s", line)
|
||
|
}
|
||
|
|
||
|
var memory Stats
|
||
|
var speculative, wired, purgeable, fileBacked, compressed uint64
|
||
|
memStats := map[string]*uint64{
|
||
|
"Pages free": &memory.Free,
|
||
|
"Pages active": &memory.Active,
|
||
|
"Pages inactive": &memory.Inactive,
|
||
|
"Pages speculative": &speculative,
|
||
|
"Pages wired down": &wired,
|
||
|
"Pages purgeable": &purgeable,
|
||
|
"File-backed pages": &fileBacked,
|
||
|
"Pages occupied by compressor": &compressed,
|
||
|
}
|
||
|
for scanner.Scan() {
|
||
|
line := scanner.Text()
|
||
|
i := strings.IndexRune(line, ':')
|
||
|
if i < 0 {
|
||
|
continue
|
||
|
}
|
||
|
if ptr := memStats[line[:i]]; ptr != nil {
|
||
|
val := strings.TrimRight(strings.TrimSpace(line[i+1:]), ".")
|
||
|
if v, err := strconv.ParseUint(val, 10, 64); err == nil {
|
||
|
*ptr = v * 4096
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if err := scanner.Err(); err != nil {
|
||
|
return nil, fmt.Errorf("scan error for vm_stat: %s", err)
|
||
|
}
|
||
|
|
||
|
memory.Cached = purgeable + fileBacked
|
||
|
memory.Used = wired + compressed + memory.Active + memory.Inactive + speculative - memory.Cached
|
||
|
memory.Total = memory.Used + memory.Cached + memory.Free
|
||
|
return &memory, nil
|
||
|
}
|
||
|
|
||
|
// xsw_usage in sys/sysctl.h
|
||
|
type swapUsage struct {
|
||
|
Total uint64
|
||
|
Avail uint64
|
||
|
Used uint64
|
||
|
Pagesize int32
|
||
|
Encrypted bool
|
||
|
}
|
||
|
|
||
|
func collectSwapStats(out []byte) (*swapUsage, error) {
|
||
|
if len(out) != 32 {
|
||
|
return nil, fmt.Errorf("unexpected output of sysctl vm.swapusage: %v (len: %d)", out, len(out))
|
||
|
}
|
||
|
return (*swapUsage)(unsafe.Pointer(&out[0])), nil
|
||
|
}
|