From d968f63e51fb9ea514a933e21d571a40fe358637 Mon Sep 17 00:00:00 2001 From: Steven Hartland Date: Sun, 11 Mar 2018 22:10:31 +0000 Subject: [PATCH] Eliminate call to swapinfo on FreeBSD Improve performance by eliminating the fork out to swapinfo on FreeBSD which also helps prevent crashes / hangs due to the outstanding fork crash bug: golang/go#15658 This also fixes the value reported by SwapMemory and SwapMemoryWithContext on FreeBSD which previously only included the first swap device and also reported the values in terms of 1K blocks instead of bytes. --- mem/mem_freebsd.go | 105 +++++++++++++++++++++++++-------------------- 1 file changed, 58 insertions(+), 47 deletions(-) diff --git a/mem/mem_freebsd.go b/mem/mem_freebsd.go index e691227..1fa880b 100644 --- a/mem/mem_freebsd.go +++ b/mem/mem_freebsd.go @@ -5,9 +5,7 @@ package mem import ( "context" "errors" - "os/exec" - "strconv" - "strings" + "unsafe" "golang.org/x/sys/unix" ) @@ -69,53 +67,66 @@ func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) { } // Return swapinfo -// FreeBSD can have multiple swap devices. but use only first device func SwapMemory() (*SwapMemoryStat, error) { return SwapMemoryWithContext(context.Background()) } -func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) { - swapinfo, err := exec.LookPath("swapinfo") - if err != nil { - return nil, err - } +// Constants from vm/vm_param.h +// nolint: golint +const ( + XSWDEV_VERSION = 1 +) - out, err := invoke.Command(swapinfo) - if err != nil { - return nil, err - } - for _, line := range strings.Split(string(out), "\n") { - values := strings.Fields(line) - // skip title line - if len(values) == 0 || values[0] == "Device" { - continue - } - - u := strings.Replace(values[4], "%", "", 1) - total_v, err := strconv.ParseUint(values[1], 10, 64) - if err != nil { - return nil, err - } - used_v, err := strconv.ParseUint(values[2], 10, 64) - if err != nil { - return nil, err - } - free_v, err := strconv.ParseUint(values[3], 10, 64) - if err != nil { - return nil, err - } - up_v, err := strconv.ParseFloat(u, 64) - if err != nil { - return nil, err - } - - return &SwapMemoryStat{ - Total: total_v, - Used: used_v, - Free: free_v, - UsedPercent: up_v, - }, nil - } - - return nil, errors.New("no swap devices found") +// Types from vm/vm_param.h +type xswdev struct { + Version uint32 // Version is the version + Dev uint32 // Dev is the device identifier + Flags int32 // Flags is the swap flags applied to the device + NBlks int32 // NBlks is the total number of blocks + Used int32 // Used is the number of blocks used +} + +func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) { + // FreeBSD can have multiple swap devices so we total them up + i, err := unix.SysctlUint32("vm.nswapdev") + if err != nil { + return nil, err + } + + if i == 0 { + return nil, errors.New("no swap devices found") + } + + c := int(i) + + i, err = unix.SysctlUint32("vm.stats.vm.v_page_size") + if err != nil { + return nil, err + } + pageSize := uint64(i) + + var buf []byte + s := &SwapMemoryStat{} + for n := 0; n < c; n++ { + buf, err = unix.SysctlRaw("vm.swap_info", n) + if err != nil { + return nil, err + } + + xsw := (*xswdev)(unsafe.Pointer(&buf[0])) + if xsw.Version != XSWDEV_VERSION { + return nil, errors.New("xswdev version mismatch") + } + s.Total += uint64(xsw.NBlks) + s.Used += uint64(xsw.Used) + } + + if s.Total != 0 { + s.UsedPercent = float64(s.Used) / float64(s.Total) * 100 + } + s.Total *= pageSize + s.Used *= pageSize + s.Free = s.Total - s.Used + + return s, nil }