diff --git a/collector/exec_bsd.go b/collector/exec_bsd.go index 07beb518..910d2330 100644 --- a/collector/exec_bsd.go +++ b/collector/exec_bsd.go @@ -39,6 +39,9 @@ func NewExecCollector() (Collector, error) { // vm.stats.sys.v_intr: Device interrupts // vm.stats.sys.v_soft: Software interrupts // vm.stats.vm.v_forks: Number of fork() calls + // + // From sys/kern/kern_tc.c: + // kern.boottime is an S,timeval return &execCollector{ sysctls: []bsdSysctl{ @@ -72,6 +75,12 @@ func NewExecCollector() (Collector, error) { description: "Number of fork() calls since system boot. Resets at architeture unsigned integer.", mib: "vm.stats.vm.v_forks", }, + { + name: "boot_timestamp_seconds", + description: "Unix time of last boot, including microseconds.", + mib: "kern.boottime", + dataType: bsdSysctlTypeStructTimeval, + }, }, }, nil } diff --git a/collector/meminfo_bsd.go b/collector/meminfo_bsd.go index 1f3941aa..0c722c24 100644 --- a/collector/meminfo_bsd.go +++ b/collector/meminfo_bsd.go @@ -29,8 +29,8 @@ func (c *meminfoCollector) getMemInfo() (map[string]float64, error) { if err != nil { return nil, fmt.Errorf("sysctl(vm.stats.vm.v_page_size) failed: %s", err) } - size := uint64(tmp32) - fromPage := func(v uint64) uint64 { + size := float64(tmp32) + fromPage := func(v float64) float64 { return v * size } diff --git a/collector/sysctl_bsd.go b/collector/sysctl_bsd.go index 2fb94f74..1cc5ec6f 100644 --- a/collector/sysctl_bsd.go +++ b/collector/sysctl_bsd.go @@ -17,19 +17,24 @@ package collector import ( + "fmt" "github.com/prometheus/client_golang/prometheus" "golang.org/x/sys/unix" + "unsafe" ) +// #include +import "C" + type bsdSysctlType uint8 // BSD-specific sysctl value types. There is an impedience mismatch between // native C types, e.g. int vs long, and the golang unix.Sysctl variables - const ( // Default to uint32. bsdSysctlTypeUint32 bsdSysctlType = iota bsdSysctlTypeUint64 + bsdSysctlTypeStructTimeval ) // Contains all the info needed to map a single bsd-sysctl to a prometheus value. @@ -50,28 +55,68 @@ type bsdSysctl struct { dataType bsdSysctlType // Post-retrieval conversion hooks - conversion func(uint64) uint64 + conversion func(float64) float64 } func (b bsdSysctl) Value() (float64, error) { var tmp32 uint32 var tmp64 uint64 + var tmpf64 float64 var err error switch b.dataType { case bsdSysctlTypeUint32: tmp32, err = unix.SysctlUint32(b.mib) - tmp64 = uint64(tmp32) + tmpf64 = float64(tmp32) case bsdSysctlTypeUint64: tmp64, err = unix.SysctlUint64(b.mib) + tmpf64 = float64(tmp64) + case bsdSysctlTypeStructTimeval: + raw, err := unix.SysctlRaw(b.mib) + if err != nil { + return 0, err + } + + /* + * From 10.3-RELEASE sources: + * + * /usr/include/sys/_timeval.h:47 + * time_t tv_sec + * suseconds_t tv_usec + * + * /usr/include/sys/_types.h:60 + * long __suseconds_t + * + * ... architecture dependent, via #ifdef: + * typedef __int64_t __time_t; + * typedef __int32_t __time_t; + */ + if len(raw) != (C.sizeof_time_t + C.sizeof_suseconds_t) { + // Shouldn't get here, unless the ABI changes... + return 0, fmt.Errorf( + "Length of bytes recieved from sysctl (%d) does not match expected bytes (%d).", + len(raw), + C.sizeof_time_t+C.sizeof_suseconds_t, + ) + } + + secondsUp := unsafe.Pointer(&raw[0]) + susecondsUp := uintptr(secondsUp) + C.sizeof_time_t + unix := float64(*(*C.time_t)(secondsUp)) + usec := float64(*(*C.suseconds_t)(unsafe.Pointer(susecondsUp))) + + // This conversion maintains the usec precision. Using + // the time package did not. + tmpf64 = unix + (usec / float64(1000*1000)) } + if err != nil { return 0, err } if b.conversion != nil { - return float64(b.conversion(tmp64)), nil + return b.conversion(tmpf64), nil } - return float64(tmp64), nil + return tmpf64, nil }