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.
pull/432/head
Steven Hartland 8 years ago
parent 1ba77cdb3d
commit 6450c60b61

@ -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++ {

@ -0,0 +1,9 @@
package cpu
type cpuTimes struct {
User uint32
Nice uint32
Sys uint32
Intr uint32
Idle uint32
}

@ -0,0 +1,9 @@
package cpu
type cpuTimes struct {
User uint64
Nice uint64
Sys uint64
Intr uint64
Idle uint64
}

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

@ -1,4 +1,4 @@
// +build freebsd
// +build freebsd openbsd
package load
@ -32,7 +32,7 @@ func Avg() (*AvgStat, error) {
return ret, nil
}
// Misc returnes miscellaneous host-wide statistics.
// 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) {

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

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

Loading…
Cancel
Save