From 54c74923eeb15d695a3cd5a47e3c5bb8c3eb336c Mon Sep 17 00:00:00 2001 From: Dominik Honnef Date: Mon, 26 Dec 2016 22:50:20 +0100 Subject: [PATCH 1/4] Implement loadavg on FreeBSD without cgo The code may also work for other BSDs, but I don't have access to those for testing. --- collector/loadavg_freebsd.go | 27 +++++++++++++++++++++++++++ collector/loadavg_unix.go | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 collector/loadavg_freebsd.go diff --git a/collector/loadavg_freebsd.go b/collector/loadavg_freebsd.go new file mode 100644 index 00000000..b490513f --- /dev/null +++ b/collector/loadavg_freebsd.go @@ -0,0 +1,27 @@ +// +build !noloadavg + +package collector + +import ( + "unsafe" + + "golang.org/x/sys/unix" +) + +func getLoad() ([]float64, error) { + 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) + return []float64{ + float64(load.load[0]) / scale, + float64(load.load[1]) / scale, + float64(load.load[2]) / scale, + }, nil +} diff --git a/collector/loadavg_unix.go b/collector/loadavg_unix.go index f121a657..7641ceaf 100644 --- a/collector/loadavg_unix.go +++ b/collector/loadavg_unix.go @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build darwin dragonfly freebsd netbsd openbsd solaris +// +build darwin dragonfly netbsd openbsd solaris // +build !noloadavg package collector From 0f6191987ef73067a8d66750cc95cccb1b99f760 Mon Sep 17 00:00:00 2001 From: Dominik Honnef Date: Mon, 26 Dec 2016 22:50:52 +0100 Subject: [PATCH 2/4] Implement file systems on FreeBSD without cgo The code may also work for other BSDs, but I don't have access to those for testing. --- collector/filesystem_bsd.go | 2 +- collector/filesystem_freebsd.go | 88 +++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 collector/filesystem_freebsd.go diff --git a/collector/filesystem_bsd.go b/collector/filesystem_bsd.go index 3a2037a3..7a98161f 100644 --- a/collector/filesystem_bsd.go +++ b/collector/filesystem_bsd.go @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build freebsd openbsd darwin,amd64 dragonfly +// +build openbsd darwin,amd64 dragonfly // +build !nofilesystem package collector diff --git a/collector/filesystem_freebsd.go b/collector/filesystem_freebsd.go new file mode 100644 index 00000000..8bd697be --- /dev/null +++ b/collector/filesystem_freebsd.go @@ -0,0 +1,88 @@ +// Copyright 2015 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build !nofilesystem + +package collector + +import ( + "bytes" + "unsafe" + + "github.com/prometheus/common/log" + "golang.org/x/sys/unix" +) + +const ( + defIgnoredMountPoints = "^/(dev)($|/)" + defIgnoredFSTypes = "^devfs$" + MNT_RDONLY = 0x1 + MNT_NOWAIT = 0x2 +) + +func gostring(b []int8) string { + bb := *(*[]byte)(unsafe.Pointer(&b)) + idx := bytes.IndexByte(bb, 0) + if idx < 0 { + return "" + } + return string(bb[:idx]) +} + +// Expose filesystem fullness. +func (c *filesystemCollector) GetStats() (stats []filesystemStats, err error) { + buf := make([]unix.Statfs_t, 16) + for { + n, err := unix.Getfsstat(buf, MNT_NOWAIT) + if err != nil { + return nil, err + } + if n < len(buf) { + buf = buf[:n] + break + } + buf = make([]unix.Statfs_t, len(buf)*2) + } + stats = []filesystemStats{} + for _, fs := range buf { + mountpoint := gostring(fs.Mntonname[:]) + if c.ignoredMountPointsPattern.MatchString(mountpoint) { + log.Debugf("Ignoring mount point: %s", mountpoint) + continue + } + + device := gostring(fs.Mntfromname[:]) + fstype := gostring(fs.Fstypename[:]) + if c.ignoredFSTypesPattern.MatchString(fstype) { + log.Debugf("Ignoring fs type: %s", fstype) + continue + } + + var ro float64 + if (fs.Flags & MNT_RDONLY) != 0 { + ro = 1 + } + + labelValues := []string{device, mountpoint, fstype} + stats = append(stats, filesystemStats{ + labelValues: labelValues, + size: float64(fs.Blocks) * float64(fs.Bsize), + free: float64(fs.Bfree) * float64(fs.Bsize), + avail: float64(fs.Bavail) * float64(fs.Bsize), + files: float64(fs.Files), + filesFree: float64(fs.Ffree), + ro: ro, + }) + } + return stats, nil +} From d2a43f7d0556d35ba857899e29f04dfbf5af13a2 Mon Sep 17 00:00:00 2001 From: Dominik Honnef Date: Thu, 29 Dec 2016 02:15:35 +0100 Subject: [PATCH 3/4] Implement meminfo on BSD without cgo This removes some error handling, which should be fine. If the calls fail, we will get the zeroes, which is a safe enough fallback. Additionally, if the first sysctl (page_size) succeeded it is unlikely that other ones will fail. --- collector/meminfo_bsd.go | 60 ++++++++++------------------------------ 1 file changed, 14 insertions(+), 46 deletions(-) diff --git a/collector/meminfo_bsd.go b/collector/meminfo_bsd.go index 9e70fd1a..bcdc3879 100644 --- a/collector/meminfo_bsd.go +++ b/collector/meminfo_bsd.go @@ -17,30 +17,12 @@ package collector import ( - "errors" + "fmt" "github.com/prometheus/client_golang/prometheus" + "golang.org/x/sys/unix" ) -/* -#include -#include - -int _sysctl(const char* name) { - int val; - size_t size = sizeof(val); - int res = sysctlbyname(name, &val, &size, NULL, 0); - if (res == -1) { - return -1; - } - if (size != sizeof(val)) { - return -2; - } - return val; -} -*/ -import "C" - const ( memInfoSubsystem = "memory" ) @@ -58,34 +40,20 @@ func NewMeminfoCollector() (Collector, error) { } func (c *meminfoCollector) Update(ch chan<- prometheus.Metric) (err error) { - var pages map[string]C.int - pages = make(map[string]C.int) + pages := make(map[string]uint32) - size := C._sysctl(C.CString("vm.stats.vm.v_page_size")) - if size == -1 { - return errors.New("sysctl(vm.stats.vm.v_page_size) failed") - } - if size == -2 { - return errors.New("sysctl(vm.stats.vm.v_page_size) failed, wrong buffer size") - } - - pages["active"] = C._sysctl(C.CString("vm.stats.vm.v_active_count")) - pages["inactive"] = C._sysctl(C.CString("vm.stats.vm.v_inactive_count")) - pages["wire"] = C._sysctl(C.CString("vm.stats.vm.v_wire_count")) - pages["cache"] = C._sysctl(C.CString("vm.stats.vm.v_cache_count")) - pages["free"] = C._sysctl(C.CString("vm.stats.vm.v_free_count")) - pages["swappgsin"] = C._sysctl(C.CString("vm.stats.vm.v_swappgsin")) - pages["swappgsout"] = C._sysctl(C.CString("vm.stats.vm.v_swappgsout")) - pages["total"] = C._sysctl(C.CString("vm.stats.vm.v_page_count")) - - for key := range pages { - if pages[key] == -1 { - return errors.New("sysctl() failed for " + key) - } - if pages[key] == -2 { - return errors.New("sysctl() failed for " + key + ", wrong buffer size") - } + size, err := unix.SysctlUint32("vm.stats.vm.v_page_size") + if err != nil { + return fmt.Errorf("sysctl(vm.stats.vm.v_page_size) failed: %s", err) } + pages["active"], _ = unix.SysctlUint32("vm.stats.vm.v_active_count") + pages["inactive"], _ = unix.SysctlUint32("vm.stats.vm.v_inactive_count") + pages["wire"], _ = unix.SysctlUint32("vm.stats.vm.v_wire_count") + pages["cache"], _ = unix.SysctlUint32("vm.stats.vm.v_cache_count") + pages["free"], _ = unix.SysctlUint32("vm.stats.vm.v_free_count") + pages["swappgsin"], _ = unix.SysctlUint32("vm.stats.vm.v_swappgsin") + pages["swappgsout"], _ = unix.SysctlUint32("vm.stats.vm.v_swappgsout") + pages["total"], _ = unix.SysctlUint32("vm.stats.vm.v_page_count") for k, v := range pages { ch <- prometheus.MustNewConstMetric( From f0adcd163d7b0cd26775bbe2ab950c38db4f5f7f Mon Sep 17 00:00:00 2001 From: Dominik Honnef Date: Thu, 29 Dec 2016 04:26:41 +0100 Subject: [PATCH 4/4] Implement CPU collector on FreeBSD without cgo --- collector/cpu_freebsd.go | 160 ++++++++++++++------------------------- 1 file changed, 58 insertions(+), 102 deletions(-) diff --git a/collector/cpu_freebsd.go b/collector/cpu_freebsd.go index 0ede1a69..a5bc33fa 100644 --- a/collector/cpu_freebsd.go +++ b/collector/cpu_freebsd.go @@ -16,95 +16,67 @@ package collector import ( - "errors" "strconv" "unsafe" "github.com/prometheus/client_golang/prometheus" + "golang.org/x/sys/unix" ) -/* -#cgo LDFLAGS: -#include -#include -#include -#include -#include -#include -#include - -static int mibs_set_up = 0; - -static int mib_kern_cp_times[2]; -static size_t mib_kern_cp_times_len = 2; - -static const int mib_hw_ncpu[] = {CTL_HW, HW_NCPU}; -static const size_t mib_hw_ncpu_len = 2; - -static const int mib_kern_clockrate[] = {CTL_KERN, KERN_CLOCKRATE}; -static size_t mib_kern_clockrate_len = 2; - -// Setup method for MIBs not available as constants. -// Calls to this method must be synchronized externally. -int setupSysctlMIBs() { - int ret = sysctlnametomib("kern.cp_times", mib_kern_cp_times, &mib_kern_cp_times_len); - if (ret == 0) mibs_set_up = 1; - return ret; +type clockinfo struct { + hz int32 // clock frequency + tick int32 // micro-seconds per hz tick + spare int32 + stathz int32 // statistics clock frequency + profhz int32 // profiling clock frequency } -int getCPUTimes(int *ncpu, double **cpu_times, size_t *cp_times_length) { - - // Assert that mibs are set up through setupSysctlMIBs - if (!mibs_set_up) { - return -1; - } - - // Retrieve number of cpu cores - size_t ncpu_size = sizeof(*ncpu); - if (sysctl(mib_hw_ncpu, mib_hw_ncpu_len, ncpu, &ncpu_size, NULL, 0) == -1 || - sizeof(*ncpu) != ncpu_size) { - return -1; - } - - // Retrieve clockrate - struct clockinfo clockrate; - size_t clockrate_size = sizeof(clockrate); - if (sysctl(mib_kern_clockrate, mib_kern_clockrate_len, &clockrate, &clockrate_size, NULL, 0) == -1 || - sizeof(clockrate) != clockrate_size) { - return -1; - } - - // Retrieve cp_times values - *cp_times_length = (*ncpu) * CPUSTATES; - - long cp_times[*cp_times_length]; - size_t cp_times_size = sizeof(cp_times); - - if (sysctl(mib_kern_cp_times, mib_kern_cp_times_len, &cp_times, &cp_times_size, NULL, 0) == -1 || - sizeof(cp_times) != cp_times_size) { - return -1; - } - - // Compute absolute time for different CPU states - long cpufreq = clockrate.stathz > 0 ? clockrate.stathz : clockrate.hz; - *cpu_times = (double *) malloc(sizeof(double)*(*cp_times_length)); - for (int i = 0; i < (*cp_times_length); i++) { - (*cpu_times)[i] = ((double) cp_times[i]) / cpufreq; - } - - return 0; - +type cputime struct { + user float64 + nice float64 + sys float64 + intr float64 + idle float64 } -void freeCPUTimes(double *cpu_times) { - free(cpu_times); +func getCPUTimes() ([]cputime, error) { + const states = 5 + + clockb, err := unix.SysctlRaw("kern.clockrate") + if err != nil { + return nil, err + } + clock := *(*clockinfo)(unsafe.Pointer(&clockb[0])) + cpb, err := unix.SysctlRaw("kern.cp_times") + if err != nil { + return nil, err + } + + var cpufreq float64 + if clock.stathz > 0 { + cpufreq = float64(clock.stathz) + } else { + cpufreq = float64(clock.hz) + } + var times []float64 + for len(cpb) >= int(unsafe.Sizeof(int(0))) { + t := *(*int)(unsafe.Pointer(&cpb[0])) + times = append(times, float64(t)/cpufreq) + cpb = cpb[unsafe.Sizeof(int(0)):] + } + + cpus := make([]cputime, len(times)/states) + for i := 0; i < len(times); i += states { + cpu := &cpus[i/states] + cpu.user = times[i] + cpu.nice = times[i+1] + cpu.sys = times[i+2] + cpu.intr = times[i+3] + cpu.idle = times[i+4] + } + return cpus, nil } -*/ -import "C" - -const maxCPUTimesLen = C.MAXCPU * C.CPUSTATES - type statCollector struct { cpu *prometheus.CounterVec } @@ -116,9 +88,6 @@ func init() { // Takes a prometheus registry and returns a new Collector exposing // CPU stats. func NewStatCollector() (Collector, error) { - if C.setupSysctlMIBs() == -1 { - return nil, errors.New("could not initialize sysctl MIBs") - } return &statCollector{ cpu: prometheus.NewCounterVec( prometheus.CounterOpts{ @@ -133,7 +102,6 @@ func NewStatCollector() (Collector, error) { // Expose CPU stats using sysctl. func (c *statCollector) Update(ch chan<- prometheus.Metric) (err error) { - // We want time spent per-cpu per CPUSTATE. // CPUSTATES (number of CPUSTATES) is defined as 5U. // Order: CP_USER | CP_NICE | CP_SYS | CP_IDLE | CP_INTR @@ -145,29 +113,17 @@ func (c *statCollector) Update(ch chan<- prometheus.Metric) (err error) { // // Look into sys/kern/kern_clock.c for details. - var ncpu C.int - var cpuTimesC *C.double - var cpuTimesLength C.size_t - if C.getCPUTimes(&ncpu, &cpuTimesC, &cpuTimesLength) == -1 { - return errors.New("could not retrieve CPU times") + cpuTimes, err := getCPUTimes() + if err != nil { + return err } - defer C.freeCPUTimes(cpuTimesC) - if cpuTimesLength > maxCPUTimesLen { - return errors.New("more CPU's than MAXCPU?") + for cpu, t := range cpuTimes { + c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(cpu), "mode": "user"}).Set(t.user) + c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(cpu), "mode": "nice"}).Set(t.nice) + c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(cpu), "mode": "system"}).Set(t.sys) + c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(cpu), "mode": "interrupt"}).Set(t.intr) + c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(cpu), "mode": "idle"}).Set(t.idle) } - - // Convert C.double array to Go array (https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices). - cpuTimes := (*[maxCPUTimesLen]C.double)(unsafe.Pointer(cpuTimesC))[:cpuTimesLength:cpuTimesLength] - - for cpu := 0; cpu < int(ncpu); cpu++ { - base_idx := C.CPUSTATES * cpu - c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(cpu), "mode": "user"}).Set(float64(cpuTimes[base_idx+C.CP_USER])) - c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(cpu), "mode": "nice"}).Set(float64(cpuTimes[base_idx+C.CP_NICE])) - c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(cpu), "mode": "system"}).Set(float64(cpuTimes[base_idx+C.CP_SYS])) - c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(cpu), "mode": "interrupt"}).Set(float64(cpuTimes[base_idx+C.CP_INTR])) - c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(cpu), "mode": "idle"}).Set(float64(cpuTimes[base_idx+C.CP_IDLE])) - } - c.cpu.Collect(ch) return err }