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 ( import (
"fmt" "fmt"
"os/exec" "os/exec"
"reflect"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"unsafe"
"github.com/shirou/gopsutil/internal/common" "github.com/shirou/gopsutil/internal/common"
) "golang.org/x/sys/unix"
// sys/resource.h
const (
CPUser = 0
CPNice = 1
CPSys = 2
CPIntr = 3
CPIdle = 4
CPUStates = 5
) )
var ClocksPerSec = float64(128) var ClocksPerSec = float64(128)
@ -27,6 +20,8 @@ var featuresMatch = regexp.MustCompile(`Features=.+<(.+)>`)
var featuresMatch2 = regexp.MustCompile(`Features2=[a-f\dx]+<(.+)>`) var featuresMatch2 = regexp.MustCompile(`Features2=[a-f\dx]+<(.+)>`)
var cpuEnd = regexp.MustCompile(`^Trying to mount root`) var cpuEnd = regexp.MustCompile(`^Trying to mount root`)
var cpuCores = regexp.MustCompile(`FreeBSD/SMP: (\d*) package\(s\) x (\d*) core\(s\)`) var cpuCores = regexp.MustCompile(`FreeBSD/SMP: (\d*) package\(s\) x (\d*) core\(s\)`)
var cpuTimesSize int
var emptyTimes cpuTimes
func init() { func init() {
getconf, err := exec.LookPath("/usr/bin/getconf") getconf, err := exec.LookPath("/usr/bin/getconf")
@ -43,64 +38,49 @@ func init() {
} }
} }
func Times(percpu bool) ([]TimesStat, error) { func timeStat(name string, t *cpuTimes) *TimesStat {
var ret []TimesStat return &TimesStat{
User: float64(t.User) / ClocksPerSec,
var sysctlCall string Nice: float64(t.Nice) / ClocksPerSec,
var ncpu int System: float64(t.Sys) / ClocksPerSec,
if percpu { Idle: float64(t.Idle) / ClocksPerSec,
sysctlCall = "kern.cp_times" Irq: float64(t.Intr) / ClocksPerSec,
ncpu, _ = Counts(true) CPU: name,
} else {
sysctlCall = "kern.cp_time"
ncpu = 1
}
cpuTimes, err := common.DoSysctrl(sysctlCall)
if err != nil {
return ret, err
} }
}
for i := 0; i < ncpu; i++ { func Times(percpu bool) ([]TimesStat, error) {
offset := CPUStates * i if percpu {
user, err := strconv.ParseFloat(cpuTimes[CPUser+offset], 64) buf, err := unix.SysctlRaw("kern.cp_times")
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)
if err != nil { if err != nil {
return ret, err return nil, err
} }
c := TimesStat{ // We can't do this in init due to the conflict with cpu.init()
User: float64(user / ClocksPerSec), if cpuTimesSize == 0 {
Nice: float64(nice / ClocksPerSec), cpuTimesSize = int(reflect.TypeOf(cpuTimes{}).Size())
System: float64(sys / ClocksPerSec),
Idle: float64(idle / ClocksPerSec),
Irq: float64(intr / ClocksPerSec),
} }
if !percpu {
c.CPU = "cpu-total" ncpus := len(buf) / cpuTimesSize
} else { ret := make([]TimesStat, 0, ncpus)
c.CPU = fmt.Sprintf("cpu%d", i) 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 // Returns only one InfoStat on FreeBSD. The information regarding core
@ -113,27 +93,21 @@ func Info() ([]InfoStat, error) {
if err != nil { if err != nil {
return nil, err 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 return nil, err
} }
if c.Mhz, err = strconv.ParseFloat(vals[0], 64); err != nil { c.Mhz = float64(u32)
return nil, fmt.Errorf("unable to parse FreeBSD CPU clock rate: %v", err)
}
if vals, err = common.DoSysctrl("hw.ncpu"); err != nil { if u32, err = unix.SysctlUint32("hw.ncpu"); err != nil {
return nil, err return nil, err
} }
var i64 int64 c.Cores = int32(u32)
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)
if vals, err = common.DoSysctrl("hw.model"); err != nil { if c.ModelName, err = unix.Sysctl("hw.model"); err != nil {
return nil, err return nil, err
} }
c.ModelName = strings.Join(vals, " ")
ret := make([]InfoStat, num) ret := make([]InfoStat, num)
for i := 0; i < num; i++ { 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"
"os/exec" "os/exec"
"runtime" "runtime"
"strconv"
"strings" "strings"
"sync/atomic" "sync/atomic"
"syscall"
"time" "time"
"unsafe" "unsafe"
"github.com/shirou/gopsutil/internal/common" "github.com/shirou/gopsutil/internal/common"
"github.com/shirou/gopsutil/process" "github.com/shirou/gopsutil/process"
"golang.org/x/sys/unix"
) )
const ( const (
@ -61,9 +62,9 @@ func Info() (*InfoStat, error) {
ret.Procs = uint64(len(procs)) ret.Procs = uint64(len(procs))
} }
values, err := common.DoSysctrl("kern.hostuuid") hostid, err := unix.Sysctl("kern.hostuuid")
if err == nil && len(values) == 1 && values[0] != "" { if err == nil && hostid != "" {
ret.HostID = strings.ToLower(values[0]) ret.HostID = strings.ToLower(hostid)
} }
return ret, nil return ret, nil
@ -77,19 +78,13 @@ func BootTime() (uint64, error) {
if t != 0 { if t != 0 {
return t, nil return t, nil
} }
values, err := common.DoSysctrl("kern.boottime") buf, err := unix.SysctlRaw("kern.boottime")
if err != nil { if err != nil {
return 0, err 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) tv := *(*syscall.Timeval)(unsafe.Pointer((&buf[0])))
if err != nil { atomic.StoreUint64(&cachedBootTime, uint64(tv.Sec))
return 0, err
}
t = uint64(boottime)
atomic.StoreUint64(&cachedBootTime, t)
return t, nil return t, nil
} }

@ -1,4 +1,4 @@
// +build freebsd // +build freebsd openbsd
package load package load
@ -32,7 +32,7 @@ func Avg() (*AvgStat, error) {
return ret, nil 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. // darwin use ps command to get process running/blocked count.
// Almost same as Darwin implementation, but state is different. // Almost same as Darwin implementation, but state is different.
func Misc() (*MiscStat, error) { 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" "strconv"
"strings" "strings"
"github.com/shirou/gopsutil/internal/common" "golang.org/x/sys/unix"
) )
func VirtualMemory() (*VirtualMemoryStat, error) { 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 { if err != nil {
return nil, err return nil, err
} }
p, err := strconv.ParseUint(pageSize[0], 10, 64) pageCount, err := unix.SysctlUint32("vm.stats.vm.v_page_count")
if err != nil { if err != nil {
return nil, err return nil, err
} }
free, err := unix.SysctlUint32("vm.stats.vm.v_free_count")
pageCount, err := common.DoSysctrl("vm.stats.vm.v_page_count")
if err != nil { if err != nil {
return nil, err 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 { if err != nil {
return nil, err 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 { if err != nil {
return nil, err 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 { if err != nil {
return nil, err return nil, err
} }
cache, err := common.DoSysctrl("vm.stats.vm.v_cache_count") buffers, err := unix.SysctlUint32("vfs.bufspace")
if err != nil { if err != nil {
return nil, err return nil, err
} }
buffer, err := common.DoSysctrl("vfs.bufspace") wired, err := unix.SysctlUint32("vm.stats.vm.v_wire_count")
if err != nil { if err != nil {
return nil, err 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{ ret := &VirtualMemoryStat{
Total: parsed[0] * p, Total: uint64(pageCount) * p,
Free: parsed[1] * p, Free: uint64(free) * p,
Active: parsed[2] * p, Active: uint64(active) * p,
Inactive: parsed[3] * p, Inactive: uint64(inactive) * p,
Cached: parsed[4] * p, Cached: uint64(cached) * p,
Buffers: parsed[5], Buffers: uint64(buffers),
Wired: parsed[6] * p, Wired: uint64(wired) * p,
} }
ret.Available = ret.Inactive + ret.Cached + ret.Free ret.Available = ret.Inactive + ret.Cached + ret.Free

Loading…
Cancel
Save