From cda2f493ee46f6626995236b58af5ac59774f7e0 Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Sat, 10 Sep 2016 18:55:21 +0200 Subject: [PATCH 01/17] wip --- collector/cpu_dragonfly.go | 128 +++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 collector/cpu_dragonfly.go diff --git a/collector/cpu_dragonfly.go b/collector/cpu_dragonfly.go new file mode 100644 index 0000000000..fd36418f51 --- /dev/null +++ b/collector/cpu_dragonfly.go @@ -0,0 +1,128 @@ +// 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 !nocpu + +package collector + +import ( + "errors" + "strconv" + "unsafe" + + "github.com/prometheus/client_golang/prometheus" +) + +/* +#cgo LDFLAGS: +#include +#include +#include + +int +getCPUTimes(int *ncpu, double **cpu_times, size_t *cp_times_length) +{ + int cpu, mib[2]; + struct kinfo_cputime cp_t[ncpu]; + uint64_t user, nice, sys, intr, idle; + + size_t len; + + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + len = sizeof(*ncpu); + if (-1 == sysctl(mib, 2, &ncpu, &len, NULL, 0)) + return -1; + + bzero(cp_t, sizeof(struct kinfo_cputime)*(*ncpu)); + + len = sizeof(cp_t[0])*(*ncpu); + if (sysctlbyname("kern.cputime", &cp_t, &len, NULL, 0)) + return -1; + + // Retrieve clockrate + struct clockinfo clockrate; + int mib_kern_clockrate[] = {CTL_KERN, KERN_CLOCKRATE}; + size_t mib_kern_clockrate_len = 2; + size_t clockrate_size = sizeof(clockrate); + + if (sysctl(mib_kern_clockrate, mib_kern_clockrate_len, &clockrate, &clockrate_size, NULL, 0) == -1) + return -1; + + 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; +} + +void freeCPUTimes(double *cpu_times) { + free(cpu_times); +} +*/ + +import "C" + +type statCollector struct { + cpu *prometheus.CounterVec +} + +func init() { + Factories["cpu"] = NewStatCollector +} + +// Takes a prometheus registry and returns a new Collector exposing +// CPU stats. +func NewStatCollector() (Collector, error) { + return &statCollector{ + cpu: prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: Namespace, + Name: "cpu_seconds_total", + Help: "Seconds the CPU spent in each mode.", + }, + []string{"cpu", "mode"}, + ), + }, nil +} + +// Expose CPU stats using sysctl. +func (c *statCollector) Update(ch chan<- prometheus.Metric) (err error) { + // Adapted from + // https://www.dragonflybsd.org/mailarchive/users/2010-04/msg00056.html + + 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") + } + defer C.freeCPUTimes(cpuTimesC) + + // 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 +} From 03da1ea516d91918f8d481787afc11fb81c01533 Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Sat, 10 Sep 2016 21:13:06 +0200 Subject: [PATCH 02/17] successfully exporting one of 2 cpus --- collector/cpu_dragonfly.go | 154 +++++++++++++++++++++++++++---------- 1 file changed, 113 insertions(+), 41 deletions(-) diff --git a/collector/cpu_dragonfly.go b/collector/cpu_dragonfly.go index fd36418f51..0d414617a0 100644 --- a/collector/cpu_dragonfly.go +++ b/collector/cpu_dragonfly.go @@ -17,64 +17,113 @@ package collector import ( "errors" - "strconv" - "unsafe" + "fmt" "github.com/prometheus/client_golang/prometheus" ) /* #cgo LDFLAGS: -#include -#include +#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.cputime", mib_kern_cp_times, &mib_kern_cp_times_len); + if (ret == 0) mibs_set_up = 1; + return ret; +} + +int getCPUTimes(int *ncpu, struct kinfo_cputime *cputime, uint64_t *cpu_user) { + // // Assert that mibs are set up through setupSysctlMIBs + // if (!mibs_set_up) { + // return -1; + // } -int -getCPUTimes(int *ncpu, double **cpu_times, size_t *cp_times_length) -{ - int cpu, mib[2]; - struct kinfo_cputime cp_t[ncpu]; - uint64_t user, nice, sys, intr, idle; + // // 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; + // } size_t len; + // Get number of cpu cores. + int mib[2]; mib[0] = CTL_HW; mib[1] = HW_NCPU; len = sizeof(*ncpu); - if (-1 == sysctl(mib, 2, &ncpu, &len, NULL, 0)) - return -1; - - bzero(cp_t, sizeof(struct kinfo_cputime)*(*ncpu)); - - len = sizeof(cp_t[0])*(*ncpu); - if (sysctlbyname("kern.cputime", &cp_t, &len, NULL, 0)) + if (sysctl(mib, 2, ncpu, &len, NULL, 0)) { return -1; + } // Retrieve clockrate struct clockinfo clockrate; - int mib_kern_clockrate[] = {CTL_KERN, KERN_CLOCKRATE}; - size_t mib_kern_clockrate_len = 2; size_t clockrate_size = sizeof(clockrate); - - if (sysctl(mib_kern_clockrate, mib_kern_clockrate_len, &clockrate, &clockrate_size, NULL, 0) == -1) + if (sysctl(mib_kern_clockrate, mib_kern_clockrate_len, &clockrate, &clockrate_size, NULL, 0) == -1 || + sizeof(clockrate) != clockrate_size) { return -1; + } - 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; + // // Retrieve cp_times values + // *cp_times_length = (*ncpu) * CPUSTATES; + // + // long cp_times[*cp_times_length]; + // size_t cp_times_size = sizeof(cp_times); + + // Get the cpu times. + struct kinfo_cputime cp_t[*ncpu]; + bzero(cp_t, sizeof(struct kinfo_cputime)*(*ncpu)); + len = sizeof(cp_t[0])*(*ncpu); + if (sysctlbyname("kern.cputime", &cp_t, &len, NULL, 0)) { + return -1; } + *cpu_user = cp_t[0].cp_user; + *cputime = cp_t[0]; + // This results in outputting: + // {1362514572 273421 845667973 12986861 17529717536 0 0 0 0 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]} + // So, we have the first 5 numbers from the first cpu. + // Need to figure out how to get the second cpu, i.e. cputime needs to be created with [2] + + // Compute absolute time for different CPU states + // long cpufreq = clockrate.stathz > 0 ? clockrate.stathz : clockrate.hz; + // *cpu_times = (double *) malloc(sizeof(double)*(len)); + // for (int i = 0; i < (len); i++) { + // (*cpu_times)[i] = ((double) cp_t[i]) / cpufreq; + // } + return 0; + } void freeCPUTimes(double *cpu_times) { free(cpu_times); } -*/ +*/ import "C" +const maxCPUTimesLen = C.MAXCPU * C.CPUSTATES + type statCollector struct { cpu *prometheus.CounterVec } @@ -86,6 +135,9 @@ 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{ @@ -98,30 +150,50 @@ func NewStatCollector() (Collector, error) { }, nil } +type kinfoCPUTime struct { + cp_user, cp_nice, cp_sys, cp_intr, cp_idle uint64 +} + // Expose CPU stats using sysctl. func (c *statCollector) Update(ch chan<- prometheus.Metric) (err error) { - // Adapted from - // https://www.dragonflybsd.org/mailarchive/users/2010-04/msg00056.html + + // 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 + // sysctl kern.cp_times provides hw.ncpu * CPUSTATES long integers: + // hw.ncpu * (space-separated list of the above variables) + // + // Each value is a counter incremented at frequency + // kern.clockrate.(stathz | hz) + // + // Look into sys/kern/kern_clock.c for details. var ncpu C.int - var cpuTimesC *C.double - var cpuTimesLength C.size_t + var cpuTimesC C.struct_kinfo_cputime + var cpuTimesLength C.uint64_t + if C.getCPUTimes(&ncpu, &cpuTimesC, &cpuTimesLength) == -1 { return errors.New("could not retrieve CPU times") } - defer C.freeCPUTimes(cpuTimesC) + // defer C.freeCPUTimes(cpuTimesC) + fmt.Println(cpuTimesC) + fmt.Println(uint64(cpuTimesLength)) + return errors.New("early kill") + if cpuTimesLength > maxCPUTimesLen { + return errors.New("more CPU's than MAXCPU?") + } // 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])) - } + // 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 From 1b7a18c27190863443e3938f9412726b5ba4ad3f Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Sat, 17 Sep 2016 14:05:36 +0200 Subject: [PATCH 03/17] Creating slice from C-array Might not be lined up correctly? Weird output data in the second CPU. --- collector/cpu_dragonfly.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/collector/cpu_dragonfly.go b/collector/cpu_dragonfly.go index 0d414617a0..4dde7b2289 100644 --- a/collector/cpu_dragonfly.go +++ b/collector/cpu_dragonfly.go @@ -18,6 +18,7 @@ package collector import ( "errors" "fmt" + "unsafe" "github.com/prometheus/client_golang/prometheus" ) @@ -175,9 +176,11 @@ func (c *statCollector) Update(ch chan<- prometheus.Metric) (err error) { if C.getCPUTimes(&ncpu, &cpuTimesC, &cpuTimesLength) == -1 { return errors.New("could not retrieve CPU times") } + // TODO: Remember to free variables // defer C.freeCPUTimes(cpuTimesC) - fmt.Println(cpuTimesC) - fmt.Println(uint64(cpuTimesLength)) + + cpuTimes := (*[1 << 30]C.struct_kinfo_cputime)(unsafe.Pointer(&cpuTimesC))[:ncpu:ncpu] + fmt.Println(cpuTimes) return errors.New("early kill") if cpuTimesLength > maxCPUTimesLen { return errors.New("more CPU's than MAXCPU?") From 1d75b376aed909dbc1e0b972f24b252cb3a4aed4 Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Sat, 17 Sep 2016 14:43:29 +0200 Subject: [PATCH 04/17] checkpoint --- collector/cpu_dragonfly.go | 39 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/collector/cpu_dragonfly.go b/collector/cpu_dragonfly.go index 4dde7b2289..70a8117ab8 100644 --- a/collector/cpu_dragonfly.go +++ b/collector/cpu_dragonfly.go @@ -18,6 +18,7 @@ package collector import ( "errors" "fmt" + "strconv" "unsafe" "github.com/prometheus/client_golang/prometheus" @@ -52,7 +53,7 @@ int setupSysctlMIBs() { return ret; } -int getCPUTimes(int *ncpu, struct kinfo_cputime *cputime, uint64_t *cpu_user) { +int getCPUTimes(int *ncpu, struct kinfo_cputime *cputime, uint64_t *cpufreq) { // // Assert that mibs are set up through setupSysctlMIBs // if (!mibs_set_up) { // return -1; @@ -83,6 +84,7 @@ int getCPUTimes(int *ncpu, struct kinfo_cputime *cputime, uint64_t *cpu_user) { sizeof(clockrate) != clockrate_size) { return -1; } + *cpufreq = clockrate.stathz > 0 ? clockrate.stathz : clockrate.hz; // // Retrieve cp_times values // *cp_times_length = (*ncpu) * CPUSTATES; @@ -98,19 +100,7 @@ int getCPUTimes(int *ncpu, struct kinfo_cputime *cputime, uint64_t *cpu_user) { return -1; } - *cpu_user = cp_t[0].cp_user; *cputime = cp_t[0]; - // This results in outputting: - // {1362514572 273421 845667973 12986861 17529717536 0 0 0 0 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]} - // So, we have the first 5 numbers from the first cpu. - // Need to figure out how to get the second cpu, i.e. cputime needs to be created with [2] - - // Compute absolute time for different CPU states - // long cpufreq = clockrate.stathz > 0 ? clockrate.stathz : clockrate.hz; - // *cpu_times = (double *) malloc(sizeof(double)*(len)); - // for (int i = 0; i < (len); i++) { - // (*cpu_times)[i] = ((double) cp_t[i]) / cpufreq; - // } return 0; @@ -171,24 +161,29 @@ func (c *statCollector) Update(ch chan<- prometheus.Metric) (err error) { var ncpu C.int var cpuTimesC C.struct_kinfo_cputime - var cpuTimesLength C.uint64_t + var cpuFreq C.uint64_t - if C.getCPUTimes(&ncpu, &cpuTimesC, &cpuTimesLength) == -1 { + if C.getCPUTimes(&ncpu, &cpuTimesC, &cpuFreq) == -1 { return errors.New("could not retrieve CPU times") } // TODO: Remember to free variables // defer C.freeCPUTimes(cpuTimesC) cpuTimes := (*[1 << 30]C.struct_kinfo_cputime)(unsafe.Pointer(&cpuTimesC))[:ncpu:ncpu] + + // Sample output: + // cpu0: {590123223 35166845 334263626 8693757 9845460604 0 0 0 0 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]} + // cpu1: {0 590123223 35166845 334263626 8693757 9845460604 0 0 0 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]} + // Figure out why I'm getting the same values here instead of the second CPU fmt.Println(cpuTimes) - return errors.New("early kill") - if cpuTimesLength > maxCPUTimesLen { - return errors.New("more CPU's than MAXCPU?") - } - // 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 i, cpu := range cpuTimes { + c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(i), "mode": "user"}).Set(float64(cpu.cp_user / cpuFreq)) + c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(i), "mode": "nice"}).Set(float64(cpu.cp_nice / cpuFreq)) + c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(i), "mode": "system"}).Set(float64(cpu.cp_sys / cpuFreq)) + c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(i), "mode": "interrupt"}).Set(float64(cpu.cp_intr / cpuFreq)) + c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(i), "mode": "idle"}).Set(float64(cpu.cp_idle / cpuFreq)) + } // 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])) From a9d27ea72282e472ce1bf028700791293ed0130b Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Sat, 17 Sep 2016 18:50:08 +0200 Subject: [PATCH 05/17] Appears to be working? Still need to cleanup after self --- collector/cpu_dragonfly.go | 75 +++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 42 deletions(-) diff --git a/collector/cpu_dragonfly.go b/collector/cpu_dragonfly.go index 70a8117ab8..615d635551 100644 --- a/collector/cpu_dragonfly.go +++ b/collector/cpu_dragonfly.go @@ -53,19 +53,15 @@ int setupSysctlMIBs() { return ret; } -int getCPUTimes(int *ncpu, struct kinfo_cputime *cputime, uint64_t *cpufreq) { - // // 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; - // } - +struct exported_cputime { + uint64_t cp_user; + uint64_t cp_nice; + uint64_t cp_sys; + uint64_t cp_intr; + uint64_t cp_idle; +}; + +int getCPUTimes(int *ncpu, struct exported_cputime **cputime) { size_t len; // Get number of cpu cores. @@ -84,13 +80,10 @@ int getCPUTimes(int *ncpu, struct kinfo_cputime *cputime, uint64_t *cpufreq) { sizeof(clockrate) != clockrate_size) { return -1; } - *cpufreq = clockrate.stathz > 0 ? clockrate.stathz : clockrate.hz; - // // Retrieve cp_times values - // *cp_times_length = (*ncpu) * CPUSTATES; - // - // long cp_times[*cp_times_length]; - // size_t cp_times_size = sizeof(cp_times); + // What are the consequences of casting this immediately to uint64_t + // instead of long? + uint64_t cpufreq = clockrate.stathz > 0 ? clockrate.stathz : clockrate.hz; // Get the cpu times. struct kinfo_cputime cp_t[*ncpu]; @@ -100,7 +93,18 @@ int getCPUTimes(int *ncpu, struct kinfo_cputime *cputime, uint64_t *cpufreq) { return -1; } - *cputime = cp_t[0]; + struct exported_cputime xp_t[*ncpu]; + for (int i = 0; i < *ncpu; ++i) { + xp_t[i].cp_user = cp_t[i].cp_user/cpufreq; + xp_t[i].cp_nice = cp_t[i].cp_nice/cpufreq; + xp_t[i].cp_sys = cp_t[i].cp_sys/cpufreq; + xp_t[i].cp_intr = cp_t[i].cp_intr/cpufreq; + xp_t[i].cp_idle = cp_t[i].cp_idle/cpufreq; + } + + *cputime = &xp_t[0]; + + // free(&cp_t); return 0; @@ -141,7 +145,7 @@ func NewStatCollector() (Collector, error) { }, nil } -type kinfoCPUTime struct { +type exportedCPUTime struct { cp_user, cp_nice, cp_sys, cp_intr, cp_idle uint64 } @@ -160,38 +164,25 @@ 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.struct_kinfo_cputime - var cpuFreq C.uint64_t + var cpuTimesC *C.struct_exported_cputime - if C.getCPUTimes(&ncpu, &cpuTimesC, &cpuFreq) == -1 { + if C.getCPUTimes(&ncpu, &cpuTimesC) == -1 { return errors.New("could not retrieve CPU times") } // TODO: Remember to free variables // defer C.freeCPUTimes(cpuTimesC) - cpuTimes := (*[1 << 30]C.struct_kinfo_cputime)(unsafe.Pointer(&cpuTimesC))[:ncpu:ncpu] + cpuTimes := (*[1 << 30]C.struct_exported_cputime)(unsafe.Pointer(cpuTimesC))[:ncpu:ncpu] - // Sample output: - // cpu0: {590123223 35166845 334263626 8693757 9845460604 0 0 0 0 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]} - // cpu1: {0 590123223 35166845 334263626 8693757 9845460604 0 0 0 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]} - // Figure out why I'm getting the same values here instead of the second CPU fmt.Println(cpuTimes) for i, cpu := range cpuTimes { - c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(i), "mode": "user"}).Set(float64(cpu.cp_user / cpuFreq)) - c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(i), "mode": "nice"}).Set(float64(cpu.cp_nice / cpuFreq)) - c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(i), "mode": "system"}).Set(float64(cpu.cp_sys / cpuFreq)) - c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(i), "mode": "interrupt"}).Set(float64(cpu.cp_intr / cpuFreq)) - c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(i), "mode": "idle"}).Set(float64(cpu.cp_idle / cpuFreq)) + c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(i), "mode": "user"}).Set(float64(cpu.cp_user)) + c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(i), "mode": "nice"}).Set(float64(cpu.cp_nice)) + c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(i), "mode": "system"}).Set(float64(cpu.cp_sys)) + c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(i), "mode": "interrupt"}).Set(float64(cpu.cp_intr)) + c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(i), "mode": "idle"}).Set(float64(cpu.cp_idle)) } - // 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 From cc5142a007c531351f9faf53e79edb6d0b968212 Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Sat, 17 Sep 2016 19:12:48 +0200 Subject: [PATCH 06/17] Remove some unnecessary includes --- collector/cpu_dragonfly.go | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/collector/cpu_dragonfly.go b/collector/cpu_dragonfly.go index 615d635551..511468a059 100644 --- a/collector/cpu_dragonfly.go +++ b/collector/cpu_dragonfly.go @@ -26,22 +26,15 @@ import ( /* #cgo LDFLAGS: -#include -#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; @@ -145,18 +138,12 @@ func NewStatCollector() (Collector, error) { }, nil } -type exportedCPUTime struct { - cp_user, cp_nice, cp_sys, cp_intr, cp_idle uint64 -} - // 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 - // sysctl kern.cp_times provides hw.ncpu * CPUSTATES long integers: - // hw.ncpu * (space-separated list of the above variables) + // States: CP_USER | CP_NICE | CP_SYS | CP_IDLE | CP_INTR // // Each value is a counter incremented at frequency // kern.clockrate.(stathz | hz) From 4b4385bd443a3b3c3769f2cbfe7b7e0414adaf34 Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Sat, 17 Sep 2016 19:14:31 +0200 Subject: [PATCH 07/17] Remove free Don't need it since we aren't malloc'ing --- collector/cpu_dragonfly.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/collector/cpu_dragonfly.go b/collector/cpu_dragonfly.go index 511468a059..2bfa76ff5b 100644 --- a/collector/cpu_dragonfly.go +++ b/collector/cpu_dragonfly.go @@ -97,16 +97,9 @@ int getCPUTimes(int *ncpu, struct exported_cputime **cputime) { *cputime = &xp_t[0]; - // free(&cp_t); - return 0; } - -void freeCPUTimes(double *cpu_times) { - free(cpu_times); -} - */ import "C" @@ -156,8 +149,6 @@ func (c *statCollector) Update(ch chan<- prometheus.Metric) (err error) { if C.getCPUTimes(&ncpu, &cpuTimesC) == -1 { return errors.New("could not retrieve CPU times") } - // TODO: Remember to free variables - // defer C.freeCPUTimes(cpuTimesC) cpuTimes := (*[1 << 30]C.struct_exported_cputime)(unsafe.Pointer(cpuTimesC))[:ncpu:ncpu] From 3e4a15465620a4d34ff9e319191c4f571b5c67f2 Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Sun, 18 Sep 2016 14:16:26 +0200 Subject: [PATCH 08/17] Correctly exporting values Moved to exporting via a string, which is then split and parsed. The string is sometimes duplicated, however. --- collector/cpu_dragonfly.go | 82 +++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/collector/cpu_dragonfly.go b/collector/cpu_dragonfly.go index 2bfa76ff5b..f50e0dc748 100644 --- a/collector/cpu_dragonfly.go +++ b/collector/cpu_dragonfly.go @@ -19,6 +19,7 @@ import ( "errors" "fmt" "strconv" + "strings" "unsafe" "github.com/prometheus/client_golang/prometheus" @@ -29,6 +30,7 @@ import ( #include #include #include +#include static int mibs_set_up = 0; @@ -40,21 +42,15 @@ 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 +setupSysctlMIBs() { int ret = sysctlnametomib("kern.cputime", mib_kern_cp_times, &mib_kern_cp_times_len); if (ret == 0) mibs_set_up = 1; return ret; } -struct exported_cputime { - uint64_t cp_user; - uint64_t cp_nice; - uint64_t cp_sys; - uint64_t cp_intr; - uint64_t cp_idle; -}; - -int getCPUTimes(int *ncpu, struct exported_cputime **cputime) { +int +getCPUTimes(int *ncpu, char **cputime) { size_t len; // Get number of cpu cores. @@ -74,9 +70,7 @@ int getCPUTimes(int *ncpu, struct exported_cputime **cputime) { return -1; } - // What are the consequences of casting this immediately to uint64_t - // instead of long? - uint64_t cpufreq = clockrate.stathz > 0 ? clockrate.stathz : clockrate.hz; + long freq = clockrate.stathz > 0 ? clockrate.stathz : clockrate.hz; // Get the cpu times. struct kinfo_cputime cp_t[*ncpu]; @@ -86,17 +80,21 @@ int getCPUTimes(int *ncpu, struct exported_cputime **cputime) { return -1; } - struct exported_cputime xp_t[*ncpu]; + // string needs to hold (5*ncpu)(uint64_t + char) + // The char is the space between values. + *cputime = (char *) malloc((sizeof(uint64_t)+sizeof(char))*(5*(*ncpu))); + + uint64_t user, nice, sys, intr, idle; + user = nice = sys = intr = idle = 0; for (int i = 0; i < *ncpu; ++i) { - xp_t[i].cp_user = cp_t[i].cp_user/cpufreq; - xp_t[i].cp_nice = cp_t[i].cp_nice/cpufreq; - xp_t[i].cp_sys = cp_t[i].cp_sys/cpufreq; - xp_t[i].cp_intr = cp_t[i].cp_intr/cpufreq; - xp_t[i].cp_idle = cp_t[i].cp_idle/cpufreq; + user = ((double) cp_t[i].cp_user) / freq; + nice = ((double) cp_t[i].cp_nice) / freq; + sys = ((double) cp_t[i].cp_sys) / freq; + intr = ((double) cp_t[i].cp_intr) / freq; + idle = ((double) cp_t[i].cp_idle) / freq; + sprintf(*cputime + strlen(*cputime), "%llu %llu %llu %llu %llu ", user, nice, sys, intr, idle ); } - *cputime = &xp_t[0]; - return 0; } @@ -106,7 +104,7 @@ import "C" const maxCPUTimesLen = C.MAXCPU * C.CPUSTATES type statCollector struct { - cpu *prometheus.CounterVec + cpu *prometheus.Desc } func init() { @@ -120,19 +118,16 @@ func NewStatCollector() (Collector, error) { return nil, errors.New("could not initialize sysctl MIBs") } return &statCollector{ - cpu: prometheus.NewCounterVec( - prometheus.CounterOpts{ - Namespace: Namespace, - Name: "cpu_seconds_total", - Help: "Seconds the CPU spent in each mode.", - }, - []string{"cpu", "mode"}, + cpu: prometheus.NewDesc( + prometheus.BuildFQName(Namespace, "", "cpu"), + "Seconds the cpus spent in each mode.", + []string{"cpu", "mode"}, nil, ), }, nil } // Expose CPU stats using sysctl. -func (c *statCollector) Update(ch chan<- prometheus.Metric) (err error) { +func (c *statCollector) Update(ch chan<- prometheus.Metric) error { // We want time spent per-cpu per CPUSTATE. // CPUSTATES (number of CPUSTATES) is defined as 5U. @@ -144,24 +139,29 @@ 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.struct_exported_cputime + var cpuTimesC *C.char + var fieldsCount = 5 if C.getCPUTimes(&ncpu, &cpuTimesC) == -1 { return errors.New("could not retrieve CPU times") } - cpuTimes := (*[1 << 30]C.struct_exported_cputime)(unsafe.Pointer(cpuTimesC))[:ncpu:ncpu] - + // TODO: Find better way to remove trailing white space. + cpuTimes := strings.Split(strings.TrimSpace(C.GoString(cpuTimesC)), " ") + C.free(unsafe.Pointer(cpuTimesC)) + // TODO: Figure out why the string is always growing fmt.Println(cpuTimes) - for i, cpu := range cpuTimes { - c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(i), "mode": "user"}).Set(float64(cpu.cp_user)) - c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(i), "mode": "nice"}).Set(float64(cpu.cp_nice)) - c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(i), "mode": "system"}).Set(float64(cpu.cp_sys)) - c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(i), "mode": "interrupt"}).Set(float64(cpu.cp_intr)) - c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(i), "mode": "idle"}).Set(float64(cpu.cp_idle)) + // Export order: user nice sys intr idle + cpuFields := []string{"user", "nice", "sys", "interrupt", "idle"} + for i, v := range cpuTimes { + cpux := fmt.Sprintf("cpu%d", i/fieldsCount) + value, err := strconv.ParseFloat(v, 64) + if err != nil { + return err + } + ch <- prometheus.MustNewConstMetric(c.cpu, prometheus.CounterValue, value, cpux, cpuFields[i%fieldsCount]) } - c.cpu.Collect(ch) - return err + return nil } From c02dcdeb35718dedc5b1d5beda6dd19e549b2ea7 Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Sun, 18 Sep 2016 14:21:54 +0200 Subject: [PATCH 09/17] Remove unused comment. --- collector/cpu_dragonfly.go | 1 - 1 file changed, 1 deletion(-) diff --git a/collector/cpu_dragonfly.go b/collector/cpu_dragonfly.go index f50e0dc748..d51cfe5dbb 100644 --- a/collector/cpu_dragonfly.go +++ b/collector/cpu_dragonfly.go @@ -146,7 +146,6 @@ func (c *statCollector) Update(ch chan<- prometheus.Metric) error { return errors.New("could not retrieve CPU times") } - // TODO: Find better way to remove trailing white space. cpuTimes := strings.Split(strings.TrimSpace(C.GoString(cpuTimesC)), " ") C.free(unsafe.Pointer(cpuTimesC)) // TODO: Figure out why the string is always growing From 9f7822ccdc208dcec0dd141fa529b8e170c10372 Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Sun, 18 Sep 2016 16:17:49 +0200 Subject: [PATCH 10/17] Remember to bzero string Duplication was caused by malloc returning a region of memory that already had data in it. --- collector/cpu_dragonfly.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/collector/cpu_dragonfly.go b/collector/cpu_dragonfly.go index d51cfe5dbb..fe9e0cf93c 100644 --- a/collector/cpu_dragonfly.go +++ b/collector/cpu_dragonfly.go @@ -82,7 +82,9 @@ getCPUTimes(int *ncpu, char **cputime) { // string needs to hold (5*ncpu)(uint64_t + char) // The char is the space between values. - *cputime = (char *) malloc((sizeof(uint64_t)+sizeof(char))*(5*(*ncpu))); + int cputime_size = (sizeof(uint64_t)+sizeof(char))*(5*(*ncpu)); + *cputime = (char *) malloc(cputime_size); + bzero(*cputime, cputime_size); uint64_t user, nice, sys, intr, idle; user = nice = sys = intr = idle = 0; From 8cc06aab04e27ba7b1bcbd2ea416516d2e550649 Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Sun, 18 Sep 2016 17:36:39 +0200 Subject: [PATCH 11/17] Remove unneeded ncpu variable --- collector/cpu_dragonfly.go | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/collector/cpu_dragonfly.go b/collector/cpu_dragonfly.go index fe9e0cf93c..2440f2a2e7 100644 --- a/collector/cpu_dragonfly.go +++ b/collector/cpu_dragonfly.go @@ -50,15 +50,16 @@ setupSysctlMIBs() { } int -getCPUTimes(int *ncpu, char **cputime) { +getCPUTimes(char **cputime) { size_t len; // Get number of cpu cores. int mib[2]; + int ncpu; mib[0] = CTL_HW; mib[1] = HW_NCPU; - len = sizeof(*ncpu); - if (sysctl(mib, 2, ncpu, &len, NULL, 0)) { + len = sizeof(ncpu); + if (sysctl(mib, 2, &ncpu, &len, NULL, 0)) { return -1; } @@ -73,22 +74,22 @@ getCPUTimes(int *ncpu, char **cputime) { long freq = clockrate.stathz > 0 ? clockrate.stathz : clockrate.hz; // Get the cpu times. - struct kinfo_cputime cp_t[*ncpu]; - bzero(cp_t, sizeof(struct kinfo_cputime)*(*ncpu)); - len = sizeof(cp_t[0])*(*ncpu); + struct kinfo_cputime cp_t[ncpu]; + bzero(cp_t, sizeof(struct kinfo_cputime)*ncpu); + len = sizeof(cp_t[0])*ncpu; if (sysctlbyname("kern.cputime", &cp_t, &len, NULL, 0)) { return -1; } // string needs to hold (5*ncpu)(uint64_t + char) // The char is the space between values. - int cputime_size = (sizeof(uint64_t)+sizeof(char))*(5*(*ncpu)); + int cputime_size = (sizeof(uint64_t)+sizeof(char))*(5*ncpu); *cputime = (char *) malloc(cputime_size); bzero(*cputime, cputime_size); uint64_t user, nice, sys, intr, idle; user = nice = sys = intr = idle = 0; - for (int i = 0; i < *ncpu; ++i) { + for (int i = 0; i < ncpu; ++i) { user = ((double) cp_t[i].cp_user) / freq; nice = ((double) cp_t[i].cp_nice) / freq; sys = ((double) cp_t[i].cp_sys) / freq; @@ -140,18 +141,15 @@ func (c *statCollector) Update(ch chan<- prometheus.Metric) error { // // Look into sys/kern/kern_clock.c for details. - var ncpu C.int var cpuTimesC *C.char var fieldsCount = 5 - if C.getCPUTimes(&ncpu, &cpuTimesC) == -1 { + if C.getCPUTimes(&cpuTimesC) == -1 { return errors.New("could not retrieve CPU times") } cpuTimes := strings.Split(strings.TrimSpace(C.GoString(cpuTimesC)), " ") C.free(unsafe.Pointer(cpuTimesC)) - // TODO: Figure out why the string is always growing - fmt.Println(cpuTimes) // Export order: user nice sys intr idle cpuFields := []string{"user", "nice", "sys", "interrupt", "idle"} From 45ac033d9ef416cf17a6b57244a47f4fab92ab17 Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Mon, 19 Sep 2016 09:35:41 +0200 Subject: [PATCH 12/17] Use correct frequency for calculating cpu time The correct frequency is the systimer frequency, not the stathz. From one of the DragonFly developers: The bump upon each statclock is: ((cur_systimer - prev_systimer) * systimer_freq) >> 32 systimer_freq can be extracted from following sysctl in userspace: sysctl kern.cputimer.freq --- collector/cpu_dragonfly.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/collector/cpu_dragonfly.go b/collector/cpu_dragonfly.go index 2440f2a2e7..b1a9785f5e 100644 --- a/collector/cpu_dragonfly.go +++ b/collector/cpu_dragonfly.go @@ -63,16 +63,16 @@ getCPUTimes(char **cputime) { 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) { + // The bump on each statclock is + // ((cur_systimer - prev_systimer) * systimer_freq) >> 32 + // where + // systimer_freq = sysctl kern.cputimer.freq + long freq; + len = sizeof(freq); + if (sysctlbyname("kern.cputimer.freq", &freq, &len, NULL, 0)) { return -1; } - long freq = clockrate.stathz > 0 ? clockrate.stathz : clockrate.hz; - // Get the cpu times. struct kinfo_cputime cp_t[ncpu]; bzero(cp_t, sizeof(struct kinfo_cputime)*ncpu); From 78c84b1a4737bc5b43096d9fd7f4078ba37e0e77 Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Mon, 19 Sep 2016 09:48:34 +0200 Subject: [PATCH 13/17] Remove old freq finding code This is the code that was lifted from the freebsd implementation, but was not correct. --- collector/cpu_dragonfly.go | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/collector/cpu_dragonfly.go b/collector/cpu_dragonfly.go index b1a9785f5e..173f4fa4a1 100644 --- a/collector/cpu_dragonfly.go +++ b/collector/cpu_dragonfly.go @@ -32,23 +32,6 @@ import ( #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_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.cputime", mib_kern_cp_times, &mib_kern_cp_times_len); - if (ret == 0) mibs_set_up = 1; - return ret; -} - int getCPUTimes(char **cputime) { size_t len; @@ -117,9 +100,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.NewDesc( prometheus.BuildFQName(Namespace, "", "cpu"), From 57f88ac4f6f6e36b33420ce29b7a9ec171705ef2 Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Mon, 19 Sep 2016 09:48:53 +0200 Subject: [PATCH 14/17] Update comment --- collector/cpu_dragonfly.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/collector/cpu_dragonfly.go b/collector/cpu_dragonfly.go index 173f4fa4a1..35836f925b 100644 --- a/collector/cpu_dragonfly.go +++ b/collector/cpu_dragonfly.go @@ -117,7 +117,7 @@ func (c *statCollector) Update(ch chan<- prometheus.Metric) error { // States: CP_USER | CP_NICE | CP_SYS | CP_IDLE | CP_INTR // // Each value is a counter incremented at frequency - // kern.clockrate.(stathz | hz) + // kern.cputimer.freq // // Look into sys/kern/kern_clock.c for details. From e942d7e234e1395c55c4539e417ff66637d86e57 Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Tue, 20 Sep 2016 09:10:53 +0200 Subject: [PATCH 15/17] Maintain granularity in cpu data Export cpu mode times as original uint64_t data, and update frequency, and do the conversion to float64 and subsequent division in go. --- collector/cpu_dragonfly.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/collector/cpu_dragonfly.go b/collector/cpu_dragonfly.go index 35836f925b..16d38117d9 100644 --- a/collector/cpu_dragonfly.go +++ b/collector/cpu_dragonfly.go @@ -33,7 +33,7 @@ import ( #include int -getCPUTimes(char **cputime) { +getCPUTimes(char **cputime, long *freq) { size_t len; // Get number of cpu cores. @@ -50,9 +50,8 @@ getCPUTimes(char **cputime) { // ((cur_systimer - prev_systimer) * systimer_freq) >> 32 // where // systimer_freq = sysctl kern.cputimer.freq - long freq; - len = sizeof(freq); - if (sysctlbyname("kern.cputimer.freq", &freq, &len, NULL, 0)) { + len = sizeof(*freq); + if (sysctlbyname("kern.cputimer.freq", freq, &len, NULL, 0)) { return -1; } @@ -73,11 +72,11 @@ getCPUTimes(char **cputime) { uint64_t user, nice, sys, intr, idle; user = nice = sys = intr = idle = 0; for (int i = 0; i < ncpu; ++i) { - user = ((double) cp_t[i].cp_user) / freq; - nice = ((double) cp_t[i].cp_nice) / freq; - sys = ((double) cp_t[i].cp_sys) / freq; - intr = ((double) cp_t[i].cp_intr) / freq; - idle = ((double) cp_t[i].cp_idle) / freq; + user = cp_t[i].cp_user; + nice = cp_t[i].cp_nice; + sys = cp_t[i].cp_sys; + intr = cp_t[i].cp_intr; + idle = cp_t[i].cp_idle; sprintf(*cputime + strlen(*cputime), "%llu %llu %llu %llu %llu ", user, nice, sys, intr, idle ); } @@ -122,9 +121,10 @@ func (c *statCollector) Update(ch chan<- prometheus.Metric) error { // Look into sys/kern/kern_clock.c for details. var cpuTimesC *C.char + var cpuTimerFreq C.long var fieldsCount = 5 - if C.getCPUTimes(&cpuTimesC) == -1 { + if C.getCPUTimes(&cpuTimesC, &cpuTimerFreq) == -1 { return errors.New("could not retrieve CPU times") } @@ -139,7 +139,7 @@ func (c *statCollector) Update(ch chan<- prometheus.Metric) error { if err != nil { return err } - ch <- prometheus.MustNewConstMetric(c.cpu, prometheus.CounterValue, value, cpux, cpuFields[i%fieldsCount]) + ch <- prometheus.MustNewConstMetric(c.cpu, prometheus.CounterValue, value/float64(cpuTimerFreq), cpux, cpuFields[i%fieldsCount]) } return nil From ee37a27d9175a25441cbd915bd97929cba77c675 Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Tue, 20 Sep 2016 23:27:56 +0200 Subject: [PATCH 16/17] Export values as uint64_t --- collector/cpu_dragonfly.go | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/collector/cpu_dragonfly.go b/collector/cpu_dragonfly.go index 16d38117d9..ccea799f43 100644 --- a/collector/cpu_dragonfly.go +++ b/collector/cpu_dragonfly.go @@ -18,8 +18,6 @@ package collector import ( "errors" "fmt" - "strconv" - "strings" "unsafe" "github.com/prometheus/client_golang/prometheus" @@ -33,7 +31,7 @@ import ( #include int -getCPUTimes(char **cputime, long *freq) { +getCPUTimes(uint64_t **cputime, size_t *cpu_times_len, long *freq) { size_t len; // Get number of cpu cores. @@ -63,21 +61,18 @@ getCPUTimes(char **cputime, long *freq) { return -1; } - // string needs to hold (5*ncpu)(uint64_t + char) - // The char is the space between values. - int cputime_size = (sizeof(uint64_t)+sizeof(char))*(5*ncpu); - *cputime = (char *) malloc(cputime_size); - bzero(*cputime, cputime_size); + *cpu_times_len = ncpu*CPUSTATES; uint64_t user, nice, sys, intr, idle; user = nice = sys = intr = idle = 0; + *cputime = (uint64_t *) malloc(sizeof(uint64_t)*(*cpu_times_len)); for (int i = 0; i < ncpu; ++i) { - user = cp_t[i].cp_user; - nice = cp_t[i].cp_nice; - sys = cp_t[i].cp_sys; - intr = cp_t[i].cp_intr; - idle = cp_t[i].cp_idle; - sprintf(*cputime + strlen(*cputime), "%llu %llu %llu %llu %llu ", user, nice, sys, intr, idle ); + int offset = CPUSTATES * i; + (*cputime)[offset] = cp_t[i].cp_user; + (*cputime)[offset+1] = cp_t[i].cp_nice; + (*cputime)[offset+2] = cp_t[i].cp_sys; + (*cputime)[offset+3] = cp_t[i].cp_intr; + (*cputime)[offset+4] = cp_t[i].cp_idle; } return 0; @@ -120,26 +115,23 @@ func (c *statCollector) Update(ch chan<- prometheus.Metric) error { // // Look into sys/kern/kern_clock.c for details. - var cpuTimesC *C.char + var cpuTimesC *C.uint64_t var cpuTimerFreq C.long + var cpuTimesLength C.size_t var fieldsCount = 5 - if C.getCPUTimes(&cpuTimesC, &cpuTimerFreq) == -1 { + if C.getCPUTimes(&cpuTimesC, &cpuTimesLength, &cpuTimerFreq) == -1 { return errors.New("could not retrieve CPU times") } - cpuTimes := strings.Split(strings.TrimSpace(C.GoString(cpuTimesC)), " ") + cpuTimes := (*[maxCPUTimesLen]C.uint64_t)(unsafe.Pointer(cpuTimesC))[:cpuTimesLength:cpuTimesLength] C.free(unsafe.Pointer(cpuTimesC)) // Export order: user nice sys intr idle cpuFields := []string{"user", "nice", "sys", "interrupt", "idle"} - for i, v := range cpuTimes { + for i, value := range cpuTimes { cpux := fmt.Sprintf("cpu%d", i/fieldsCount) - value, err := strconv.ParseFloat(v, 64) - if err != nil { - return err - } - ch <- prometheus.MustNewConstMetric(c.cpu, prometheus.CounterValue, value/float64(cpuTimerFreq), cpux, cpuFields[i%fieldsCount]) + ch <- prometheus.MustNewConstMetric(c.cpu, prometheus.CounterValue, float64(value)/float64(cpuTimerFreq), cpux, cpuFields[i%fieldsCount]) } return nil From 450fe0f3ba3f5f6e0641554c6dda2ad527c9ebd7 Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Wed, 28 Sep 2016 09:10:05 +0200 Subject: [PATCH 17/17] Add test --- collector/cpu_dragonfly.go | 38 ++++++++++++++++++++---------- collector/cpu_dragonfly_test.go | 41 +++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 12 deletions(-) create mode 100644 collector/cpu_dragonfly_test.go diff --git a/collector/cpu_dragonfly.go b/collector/cpu_dragonfly.go index ccea799f43..db690b0a51 100644 --- a/collector/cpu_dragonfly.go +++ b/collector/cpu_dragonfly.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Prometheus Authors +// Copyright 2016 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 @@ -103,9 +103,7 @@ func NewStatCollector() (Collector, error) { }, nil } -// Expose CPU stats using sysctl. -func (c *statCollector) Update(ch chan<- prometheus.Metric) error { - +func getDragonFlyCPUTimes() ([]float64, error) { // We want time spent per-cpu per CPUSTATE. // CPUSTATES (number of CPUSTATES) is defined as 5U. // States: CP_USER | CP_NICE | CP_SYS | CP_IDLE | CP_INTR @@ -115,23 +113,39 @@ func (c *statCollector) Update(ch chan<- prometheus.Metric) error { // // Look into sys/kern/kern_clock.c for details. - var cpuTimesC *C.uint64_t - var cpuTimerFreq C.long - var cpuTimesLength C.size_t - var fieldsCount = 5 + var ( + cpuTimesC *C.uint64_t + cpuTimerFreq C.long + cpuTimesLength C.size_t + ) if C.getCPUTimes(&cpuTimesC, &cpuTimesLength, &cpuTimerFreq) == -1 { - return errors.New("could not retrieve CPU times") + return nil, errors.New("could not retrieve CPU times") + } + defer C.free(unsafe.Pointer(cpuTimesC)) + + cput := (*[maxCPUTimesLen]C.uint64_t)(unsafe.Pointer(cpuTimesC))[:cpuTimesLength:cpuTimesLength] + + cpuTimes := make([]float64, cpuTimesLength) + for i, value := range cput { + cpuTimes[i] = float64(value) / float64(cpuTimerFreq) } + return cpuTimes, nil +} - cpuTimes := (*[maxCPUTimesLen]C.uint64_t)(unsafe.Pointer(cpuTimesC))[:cpuTimesLength:cpuTimesLength] - C.free(unsafe.Pointer(cpuTimesC)) +// Expose CPU stats using sysctl. +func (c *statCollector) Update(ch chan<- prometheus.Metric) error { + var fieldsCount = 5 + cpuTimes, err := getDragonFlyCPUTimes() + if err != nil { + return err + } // Export order: user nice sys intr idle cpuFields := []string{"user", "nice", "sys", "interrupt", "idle"} for i, value := range cpuTimes { cpux := fmt.Sprintf("cpu%d", i/fieldsCount) - ch <- prometheus.MustNewConstMetric(c.cpu, prometheus.CounterValue, float64(value)/float64(cpuTimerFreq), cpux, cpuFields[i%fieldsCount]) + ch <- prometheus.MustNewConstMetric(c.cpu, prometheus.CounterValue, value, cpux, cpuFields[i%fieldsCount]) } return nil diff --git a/collector/cpu_dragonfly_test.go b/collector/cpu_dragonfly_test.go new file mode 100644 index 0000000000..4be0d5bbbd --- /dev/null +++ b/collector/cpu_dragonfly_test.go @@ -0,0 +1,41 @@ +// Copyright 2016 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 !nocpu + +package collector + +import ( + "runtime" + "testing" +) + +func TestCPU(t *testing.T) { + var ( + fieldsCount = 5 + times, err = getDragonFlyCPUTimes() + ) + + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + if len(times) == 0 { + t.Fatalf("no cputimes found") + } + + want := runtime.NumCPU() * fieldsCount + if len(times) != want { + t.Fatalf("should have %d cpuTimes: got %d", want, len(times)) + } +}