Skip to content

Commit f0adcd1

Browse files
committed
Implement CPU collector on FreeBSD without cgo
1 parent d2a43f7 commit f0adcd1

File tree

1 file changed

+51
-95
lines changed

1 file changed

+51
-95
lines changed

collector/cpu_freebsd.go

Lines changed: 51 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -16,95 +16,67 @@
1616
package collector
1717

1818
import (
19-
"errors"
2019
"strconv"
2120
"unsafe"
2221

2322
"github.com/prometheus/client_golang/prometheus"
23+
"golang.org/x/sys/unix"
2424
)
2525

26-
/*
27-
#cgo LDFLAGS:
28-
#include <fcntl.h>
29-
#include <stdlib.h>
30-
#include <sys/param.h>
31-
#include <sys/pcpu.h>
32-
#include <sys/resource.h>
33-
#include <sys/sysctl.h>
34-
#include <sys/time.h>
35-
36-
static int mibs_set_up = 0;
37-
38-
static int mib_kern_cp_times[2];
39-
static size_t mib_kern_cp_times_len = 2;
40-
41-
static const int mib_hw_ncpu[] = {CTL_HW, HW_NCPU};
42-
static const size_t mib_hw_ncpu_len = 2;
43-
44-
static const int mib_kern_clockrate[] = {CTL_KERN, KERN_CLOCKRATE};
45-
static size_t mib_kern_clockrate_len = 2;
26+
type clockinfo struct {
27+
hz int32 // clock frequency
28+
tick int32 // micro-seconds per hz tick
29+
spare int32
30+
stathz int32 // statistics clock frequency
31+
profhz int32 // profiling clock frequency
32+
}
4633

47-
// Setup method for MIBs not available as constants.
48-
// Calls to this method must be synchronized externally.
49-
int setupSysctlMIBs() {
50-
int ret = sysctlnametomib("kern.cp_times", mib_kern_cp_times, &mib_kern_cp_times_len);
51-
if (ret == 0) mibs_set_up = 1;
52-
return ret;
34+
type cputime struct {
35+
user float64
36+
nice float64
37+
sys float64
38+
intr float64
39+
idle float64
5340
}
5441

55-
int getCPUTimes(int *ncpu, double **cpu_times, size_t *cp_times_length) {
42+
func getCPUTimes() ([]cputime, error) {
43+
const states = 5
5644

57-
// Assert that mibs are set up through setupSysctlMIBs
58-
if (!mibs_set_up) {
59-
return -1;
45+
clockb, err := unix.SysctlRaw("kern.clockrate")
46+
if err != nil {
47+
return nil, err
6048
}
61-
62-
// Retrieve number of cpu cores
63-
size_t ncpu_size = sizeof(*ncpu);
64-
if (sysctl(mib_hw_ncpu, mib_hw_ncpu_len, ncpu, &ncpu_size, NULL, 0) == -1 ||
65-
sizeof(*ncpu) != ncpu_size) {
66-
return -1;
49+
clock := *(*clockinfo)(unsafe.Pointer(&clockb[0]))
50+
cpb, err := unix.SysctlRaw("kern.cp_times")
51+
if err != nil {
52+
return nil, err
6753
}
6854

69-
// Retrieve clockrate
70-
struct clockinfo clockrate;
71-
size_t clockrate_size = sizeof(clockrate);
72-
if (sysctl(mib_kern_clockrate, mib_kern_clockrate_len, &clockrate, &clockrate_size, NULL, 0) == -1 ||
73-
sizeof(clockrate) != clockrate_size) {
74-
return -1;
55+
var cpufreq float64
56+
if clock.stathz > 0 {
57+
cpufreq = float64(clock.stathz)
58+
} else {
59+
cpufreq = float64(clock.hz)
7560
}
76-
77-
// Retrieve cp_times values
78-
*cp_times_length = (*ncpu) * CPUSTATES;
79-
80-
long cp_times[*cp_times_length];
81-
size_t cp_times_size = sizeof(cp_times);
82-
83-
if (sysctl(mib_kern_cp_times, mib_kern_cp_times_len, &cp_times, &cp_times_size, NULL, 0) == -1 ||
84-
sizeof(cp_times) != cp_times_size) {
85-
return -1;
61+
var times []float64
62+
for len(cpb) >= int(unsafe.Sizeof(int(0))) {
63+
t := *(*int)(unsafe.Pointer(&cpb[0]))
64+
times = append(times, float64(t)/cpufreq)
65+
cpb = cpb[unsafe.Sizeof(int(0)):]
8666
}
8767

88-
// Compute absolute time for different CPU states
89-
long cpufreq = clockrate.stathz > 0 ? clockrate.stathz : clockrate.hz;
90-
*cpu_times = (double *) malloc(sizeof(double)*(*cp_times_length));
91-
for (int i = 0; i < (*cp_times_length); i++) {
92-
(*cpu_times)[i] = ((double) cp_times[i]) / cpufreq;
68+
cpus := make([]cputime, len(times)/states)
69+
for i := 0; i < len(times); i += states {
70+
cpu := &cpus[i/states]
71+
cpu.user = times[i]
72+
cpu.nice = times[i+1]
73+
cpu.sys = times[i+2]
74+
cpu.intr = times[i+3]
75+
cpu.idle = times[i+4]
9376
}
94-
95-
return 0;
96-
77+
return cpus, nil
9778
}
9879

99-
void freeCPUTimes(double *cpu_times) {
100-
free(cpu_times);
101-
}
102-
103-
*/
104-
import "C"
105-
106-
const maxCPUTimesLen = C.MAXCPU * C.CPUSTATES
107-
10880
type statCollector struct {
10981
cpu *prometheus.CounterVec
11082
}
@@ -116,9 +88,6 @@ func init() {
11688
// Takes a prometheus registry and returns a new Collector exposing
11789
// CPU stats.
11890
func NewStatCollector() (Collector, error) {
119-
if C.setupSysctlMIBs() == -1 {
120-
return nil, errors.New("could not initialize sysctl MIBs")
121-
}
12291
return &statCollector{
12392
cpu: prometheus.NewCounterVec(
12493
prometheus.CounterOpts{
@@ -133,7 +102,6 @@ func NewStatCollector() (Collector, error) {
133102

134103
// Expose CPU stats using sysctl.
135104
func (c *statCollector) Update(ch chan<- prometheus.Metric) (err error) {
136-
137105
// We want time spent per-cpu per CPUSTATE.
138106
// CPUSTATES (number of CPUSTATES) is defined as 5U.
139107
// 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) {
145113
//
146114
// Look into sys/kern/kern_clock.c for details.
147115

148-
var ncpu C.int
149-
var cpuTimesC *C.double
150-
var cpuTimesLength C.size_t
151-
if C.getCPUTimes(&ncpu, &cpuTimesC, &cpuTimesLength) == -1 {
152-
return errors.New("could not retrieve CPU times")
116+
cpuTimes, err := getCPUTimes()
117+
if err != nil {
118+
return err
153119
}
154-
defer C.freeCPUTimes(cpuTimesC)
155-
if cpuTimesLength > maxCPUTimesLen {
156-
return errors.New("more CPU's than MAXCPU?")
120+
for cpu, t := range cpuTimes {
121+
c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(cpu), "mode": "user"}).Set(t.user)
122+
c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(cpu), "mode": "nice"}).Set(t.nice)
123+
c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(cpu), "mode": "system"}).Set(t.sys)
124+
c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(cpu), "mode": "interrupt"}).Set(t.intr)
125+
c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(cpu), "mode": "idle"}).Set(t.idle)
157126
}
158-
159-
// Convert C.double array to Go array (https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices).
160-
cpuTimes := (*[maxCPUTimesLen]C.double)(unsafe.Pointer(cpuTimesC))[:cpuTimesLength:cpuTimesLength]
161-
162-
for cpu := 0; cpu < int(ncpu); cpu++ {
163-
base_idx := C.CPUSTATES * cpu
164-
c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(cpu), "mode": "user"}).Set(float64(cpuTimes[base_idx+C.CP_USER]))
165-
c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(cpu), "mode": "nice"}).Set(float64(cpuTimes[base_idx+C.CP_NICE]))
166-
c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(cpu), "mode": "system"}).Set(float64(cpuTimes[base_idx+C.CP_SYS]))
167-
c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(cpu), "mode": "interrupt"}).Set(float64(cpuTimes[base_idx+C.CP_INTR]))
168-
c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(cpu), "mode": "idle"}).Set(float64(cpuTimes[base_idx+C.CP_IDLE]))
169-
}
170-
171127
c.cpu.Collect(ch)
172128
return err
173129
}

0 commit comments

Comments
 (0)