From 6450c60b61c965d0257f0c3f92a568e53584a0a6 Mon Sep 17 00:00:00 2001 From: Steven Hartland Date: Thu, 5 Oct 2017 08:36:05 +0100 Subject: [PATCH] Eliminate use of sysctl command on FreeBSD In order to improve performance and help prevent crashes due to the outstanding fork crash bug: https://github.com/golang/go/issues/15658 Replace string parsed values from the sysctl command with native reads of sysctl values using unix.SysctlRaw and unix.SysctlUint32. This also merges OpenBSD and FreeBSD load implementations which are identical. --- cpu/cpu_freebsd.go | 116 ++++++++++++++++++----------------------------- cpu/cpu_freebsd_386.go | 9 ++++ cpu/cpu_freebsd_amd64.go | 9 ++++ host/host_freebsd.go | 21 ++++----- load/load_bsd.go | 59 ++++++++++++++++++++++++ load/load_freebsd.go | 59 ------------------------ load/load_openbsd.go | 65 -------------------------- mem/mem_freebsd.go | 56 +++++++---------------- 8 files changed, 147 insertions(+), 247 deletions(-) create mode 100644 cpu/cpu_freebsd_386.go create mode 100644 cpu/cpu_freebsd_amd64.go create mode 100644 load/load_bsd.go delete mode 100644 load/load_freebsd.go delete mode 100644 load/load_openbsd.go diff --git a/cpu/cpu_freebsd.go b/cpu/cpu_freebsd.go index 7d0321f..a6d7847 100644 --- a/cpu/cpu_freebsd.go +++ b/cpu/cpu_freebsd.go @@ -3,21 +3,14 @@ package cpu import ( "fmt" "os/exec" + "reflect" "regexp" "strconv" "strings" + "unsafe" "github.com/shirou/gopsutil/internal/common" -) - -// sys/resource.h -const ( - CPUser = 0 - CPNice = 1 - CPSys = 2 - CPIntr = 3 - CPIdle = 4 - CPUStates = 5 + "golang.org/x/sys/unix" ) var ClocksPerSec = float64(128) @@ -27,6 +20,8 @@ var featuresMatch = regexp.MustCompile(`Features=.+<(.+)>`) var featuresMatch2 = regexp.MustCompile(`Features2=[a-f\dx]+<(.+)>`) var cpuEnd = regexp.MustCompile(`^Trying to mount root`) var cpuCores = regexp.MustCompile(`FreeBSD/SMP: (\d*) package\(s\) x (\d*) core\(s\)`) +var cpuTimesSize int +var emptyTimes cpuTimes func init() { getconf, err := exec.LookPath("/usr/bin/getconf") @@ -43,64 +38,49 @@ func init() { } } -func Times(percpu bool) ([]TimesStat, error) { - var ret []TimesStat - - var sysctlCall string - var ncpu int - if percpu { - sysctlCall = "kern.cp_times" - ncpu, _ = Counts(true) - } else { - sysctlCall = "kern.cp_time" - ncpu = 1 - } - - cpuTimes, err := common.DoSysctrl(sysctlCall) - if err != nil { - return ret, err +func timeStat(name string, t *cpuTimes) *TimesStat { + return &TimesStat{ + User: float64(t.User) / ClocksPerSec, + Nice: float64(t.Nice) / ClocksPerSec, + System: float64(t.Sys) / ClocksPerSec, + Idle: float64(t.Idle) / ClocksPerSec, + Irq: float64(t.Intr) / ClocksPerSec, + CPU: name, } +} - for i := 0; i < ncpu; i++ { - offset := CPUStates * i - user, err := strconv.ParseFloat(cpuTimes[CPUser+offset], 64) - if err != nil { - return ret, err - } - nice, err := strconv.ParseFloat(cpuTimes[CPNice+offset], 64) - if err != nil { - return ret, err - } - sys, err := strconv.ParseFloat(cpuTimes[CPSys+offset], 64) - if err != nil { - return ret, err - } - idle, err := strconv.ParseFloat(cpuTimes[CPIdle+offset], 64) - if err != nil { - return ret, err - } - intr, err := strconv.ParseFloat(cpuTimes[CPIntr+offset], 64) +func Times(percpu bool) ([]TimesStat, error) { + if percpu { + buf, err := unix.SysctlRaw("kern.cp_times") if err != nil { - return ret, err + return nil, err } - c := TimesStat{ - User: float64(user / ClocksPerSec), - Nice: float64(nice / ClocksPerSec), - System: float64(sys / ClocksPerSec), - Idle: float64(idle / ClocksPerSec), - Irq: float64(intr / ClocksPerSec), + // We can't do this in init due to the conflict with cpu.init() + if cpuTimesSize == 0 { + cpuTimesSize = int(reflect.TypeOf(cpuTimes{}).Size()) } - if !percpu { - c.CPU = "cpu-total" - } else { - c.CPU = fmt.Sprintf("cpu%d", i) + + ncpus := len(buf) / cpuTimesSize + ret := make([]TimesStat, 0, ncpus) + for i := 0; i < ncpus; i++ { + times := (*cpuTimes)(unsafe.Pointer(&buf[i*cpuTimesSize])) + if *times == emptyTimes { + // CPU not present + continue + } + ret = append(ret, *timeStat(fmt.Sprintf("cpu%d", len(ret)), times)) } + return ret, nil + } - ret = append(ret, c) + buf, err := unix.SysctlRaw("kern.cp_time") + if err != nil { + return nil, err } - return ret, nil + times := (*cpuTimes)(unsafe.Pointer(&buf[0])) + return []TimesStat{*timeStat("cpu-total", times)}, nil } // Returns only one InfoStat on FreeBSD. The information regarding core @@ -113,27 +93,21 @@ func Info() ([]InfoStat, error) { if err != nil { return nil, err } - var vals []string - if vals, err = common.DoSysctrl("hw.clockrate"); err != nil { + + var u32 uint32 + if u32, err = unix.SysctlUint32("hw.clockrate"); err != nil { return nil, err } - if c.Mhz, err = strconv.ParseFloat(vals[0], 64); err != nil { - return nil, fmt.Errorf("unable to parse FreeBSD CPU clock rate: %v", err) - } + c.Mhz = float64(u32) - if vals, err = common.DoSysctrl("hw.ncpu"); err != nil { + if u32, err = unix.SysctlUint32("hw.ncpu"); err != nil { return nil, err } - var i64 int64 - if i64, err = strconv.ParseInt(vals[0], 10, 32); err != nil { - return nil, fmt.Errorf("unable to parse FreeBSD cores: %v", err) - } - c.Cores = int32(i64) + c.Cores = int32(u32) - if vals, err = common.DoSysctrl("hw.model"); err != nil { + if c.ModelName, err = unix.Sysctl("hw.model"); err != nil { return nil, err } - c.ModelName = strings.Join(vals, " ") ret := make([]InfoStat, num) for i := 0; i < num; i++ { diff --git a/cpu/cpu_freebsd_386.go b/cpu/cpu_freebsd_386.go new file mode 100644 index 0000000..8b7f4c3 --- /dev/null +++ b/cpu/cpu_freebsd_386.go @@ -0,0 +1,9 @@ +package cpu + +type cpuTimes struct { + User uint32 + Nice uint32 + Sys uint32 + Intr uint32 + Idle uint32 +} diff --git a/cpu/cpu_freebsd_amd64.go b/cpu/cpu_freebsd_amd64.go new file mode 100644 index 0000000..57e1452 --- /dev/null +++ b/cpu/cpu_freebsd_amd64.go @@ -0,0 +1,9 @@ +package cpu + +type cpuTimes struct { + User uint64 + Nice uint64 + Sys uint64 + Intr uint64 + Idle uint64 +} diff --git a/host/host_freebsd.go b/host/host_freebsd.go index 6b2962c..d6bcb58 100644 --- a/host/host_freebsd.go +++ b/host/host_freebsd.go @@ -9,14 +9,15 @@ import ( "os" "os/exec" "runtime" - "strconv" "strings" "sync/atomic" + "syscall" "time" "unsafe" "github.com/shirou/gopsutil/internal/common" "github.com/shirou/gopsutil/process" + "golang.org/x/sys/unix" ) const ( @@ -61,9 +62,9 @@ func Info() (*InfoStat, error) { ret.Procs = uint64(len(procs)) } - values, err := common.DoSysctrl("kern.hostuuid") - if err == nil && len(values) == 1 && values[0] != "" { - ret.HostID = strings.ToLower(values[0]) + hostid, err := unix.Sysctl("kern.hostuuid") + if err == nil && hostid != "" { + ret.HostID = strings.ToLower(hostid) } return ret, nil @@ -77,19 +78,13 @@ func BootTime() (uint64, error) { if t != 0 { return t, nil } - values, err := common.DoSysctrl("kern.boottime") + buf, err := unix.SysctlRaw("kern.boottime") if err != nil { return 0, err } - // ex: { sec = 1392261637, usec = 627534 } Thu Feb 13 12:20:37 2014 - v := strings.Replace(values[2], ",", "", 1) - boottime, err := strconv.ParseUint(v, 10, 64) - if err != nil { - return 0, err - } - t = uint64(boottime) - atomic.StoreUint64(&cachedBootTime, t) + tv := *(*syscall.Timeval)(unsafe.Pointer((&buf[0]))) + atomic.StoreUint64(&cachedBootTime, uint64(tv.Sec)) return t, nil } diff --git a/load/load_bsd.go b/load/load_bsd.go new file mode 100644 index 0000000..4fda3c0 --- /dev/null +++ b/load/load_bsd.go @@ -0,0 +1,59 @@ +// +build freebsd openbsd + +package load + +import ( + "os/exec" + "strings" + "unsafe" + + "golang.org/x/sys/unix" +) + +func Avg() (*AvgStat, error) { + // This SysctlRaw method borrowed from + // https://github.com/prometheus/node_exporter/blob/master/collector/loadavg_freebsd.go + type loadavg struct { + load [3]uint32 + scale int + } + b, err := unix.SysctlRaw("vm.loadavg") + if err != nil { + return nil, err + } + load := *(*loadavg)(unsafe.Pointer((&b[0]))) + scale := float64(load.scale) + ret := &AvgStat{ + Load1: float64(load.load[0]) / scale, + Load5: float64(load.load[1]) / scale, + Load15: float64(load.load[2]) / scale, + } + + return ret, nil +} + +// Misc returns miscellaneous host-wide statistics. +// darwin use ps command to get process running/blocked count. +// Almost same as Darwin implementation, but state is different. +func Misc() (*MiscStat, error) { + bin, err := exec.LookPath("ps") + if err != nil { + return nil, err + } + out, err := invoke.Command(bin, "axo", "state") + if err != nil { + return nil, err + } + lines := strings.Split(string(out), "\n") + + ret := MiscStat{} + for _, l := range lines { + if strings.Contains(l, "R") { + ret.ProcsRunning++ + } else if strings.Contains(l, "D") { + ret.ProcsBlocked++ + } + } + + return &ret, nil +} diff --git a/load/load_freebsd.go b/load/load_freebsd.go deleted file mode 100644 index 2625f2c..0000000 --- a/load/load_freebsd.go +++ /dev/null @@ -1,59 +0,0 @@ -// +build freebsd - -package load - -import ( - "os/exec" - "strings" - "unsafe" - - "golang.org/x/sys/unix" -) - -func Avg() (*AvgStat, error) { - // This SysctlRaw method borrowed from - // https://github.com/prometheus/node_exporter/blob/master/collector/loadavg_freebsd.go - type loadavg struct { - load [3]uint32 - scale int - } - b, err := unix.SysctlRaw("vm.loadavg") - if err != nil { - return nil, err - } - load := *(*loadavg)(unsafe.Pointer((&b[0]))) - scale := float64(load.scale) - ret := &AvgStat{ - Load1: float64(load.load[0]) / scale, - Load5: float64(load.load[1]) / scale, - Load15: float64(load.load[2]) / scale, - } - - return ret, nil -} - -// Misc returnes miscellaneous host-wide statistics. -// darwin use ps command to get process running/blocked count. -// Almost same as Darwin implementation, but state is different. -func Misc() (*MiscStat, error) { - bin, err := exec.LookPath("ps") - if err != nil { - return nil, err - } - out, err := invoke.Command(bin, "axo", "state") - if err != nil { - return nil, err - } - lines := strings.Split(string(out), "\n") - - ret := MiscStat{} - for _, l := range lines { - if strings.Contains(l, "R") { - ret.ProcsRunning++ - } else if strings.Contains(l, "D") { - ret.ProcsBlocked++ - } - } - - return &ret, nil -} diff --git a/load/load_openbsd.go b/load/load_openbsd.go deleted file mode 100644 index 8acfab1..0000000 --- a/load/load_openbsd.go +++ /dev/null @@ -1,65 +0,0 @@ -// +build openbsd - -package load - -import ( - "os/exec" - "strconv" - "strings" - - "github.com/shirou/gopsutil/internal/common" -) - -func Avg() (*AvgStat, error) { - values, err := common.DoSysctrl("vm.loadavg") - if err != nil { - return nil, err - } - - load1, err := strconv.ParseFloat(values[0], 64) - if err != nil { - return nil, err - } - load5, err := strconv.ParseFloat(values[1], 64) - if err != nil { - return nil, err - } - load15, err := strconv.ParseFloat(values[2], 64) - if err != nil { - return nil, err - } - - ret := &AvgStat{ - Load1: float64(load1), - Load5: float64(load5), - Load15: float64(load15), - } - - return ret, nil -} - -// Misc returnes miscellaneous host-wide statistics. -// darwin use ps command to get process running/blocked count. -// Almost same as Darwin implementation, but state is different. -func Misc() (*MiscStat, error) { - bin, err := exec.LookPath("ps") - if err != nil { - return nil, err - } - out, err := invoke.Command(bin, "axo", "state") - if err != nil { - return nil, err - } - lines := strings.Split(string(out), "\n") - - ret := MiscStat{} - for _, l := range lines { - if strings.Contains(l, "R") { - ret.ProcsRunning++ - } else if strings.Contains(l, "D") { - ret.ProcsBlocked++ - } - } - - return &ret, nil -} diff --git a/mem/mem_freebsd.go b/mem/mem_freebsd.go index 8cca44b..55e78d6 100644 --- a/mem/mem_freebsd.go +++ b/mem/mem_freebsd.go @@ -8,74 +8,52 @@ import ( "strconv" "strings" - "github.com/shirou/gopsutil/internal/common" + "golang.org/x/sys/unix" ) func VirtualMemory() (*VirtualMemoryStat, error) { - pageSize, err := common.DoSysctrl("vm.stats.vm.v_page_size") + pageSize, err := unix.SysctlUint32("vm.stats.vm.v_page_size") if err != nil { return nil, err } - p, err := strconv.ParseUint(pageSize[0], 10, 64) + pageCount, err := unix.SysctlUint32("vm.stats.vm.v_page_count") if err != nil { return nil, err } - - pageCount, err := common.DoSysctrl("vm.stats.vm.v_page_count") + free, err := unix.SysctlUint32("vm.stats.vm.v_free_count") if err != nil { return nil, err } - free, err := common.DoSysctrl("vm.stats.vm.v_free_count") + active, err := unix.SysctlUint32("vm.stats.vm.v_active_count") if err != nil { return nil, err } - active, err := common.DoSysctrl("vm.stats.vm.v_active_count") + inactive, err := unix.SysctlUint32("vm.stats.vm.v_inactive_count") if err != nil { return nil, err } - inactive, err := common.DoSysctrl("vm.stats.vm.v_inactive_count") + cached, err := unix.SysctlUint32("vm.stats.vm.v_cache_count") if err != nil { return nil, err } - cache, err := common.DoSysctrl("vm.stats.vm.v_cache_count") + buffers, err := unix.SysctlUint32("vfs.bufspace") if err != nil { return nil, err } - buffer, err := common.DoSysctrl("vfs.bufspace") + wired, err := unix.SysctlUint32("vm.stats.vm.v_wire_count") if err != nil { return nil, err } - wired, err := common.DoSysctrl("vm.stats.vm.v_wire_count") - if err != nil { - return nil, err - } - - parsed := make([]uint64, 0, 7) - vv := []string{ - pageCount[0], - free[0], - active[0], - inactive[0], - cache[0], - buffer[0], - wired[0], - } - for _, target := range vv { - t, err := strconv.ParseUint(target, 10, 64) - if err != nil { - return nil, err - } - parsed = append(parsed, t) - } + p := uint64(pageSize) ret := &VirtualMemoryStat{ - Total: parsed[0] * p, - Free: parsed[1] * p, - Active: parsed[2] * p, - Inactive: parsed[3] * p, - Cached: parsed[4] * p, - Buffers: parsed[5], - Wired: parsed[6] * p, + Total: uint64(pageCount) * p, + Free: uint64(free) * p, + Active: uint64(active) * p, + Inactive: uint64(inactive) * p, + Cached: uint64(cached) * p, + Buffers: uint64(buffers), + Wired: uint64(wired) * p, } ret.Available = ret.Inactive + ret.Cached + ret.Free