mirror of
				https://github.com/prometheus/node_exporter.git
				synced 2025-08-20 18:33:52 -07:00 
			
		
		
		
	Implement CPU collector on FreeBSD without cgo
This commit is contained in:
		
							parent
							
								
									d2a43f7d05
								
							
						
					
					
						commit
						f0adcd163d
					
				| 
						 | 
					@ -16,95 +16,67 @@
 | 
				
			||||||
package collector
 | 
					package collector
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"errors"
 | 
					 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"unsafe"
 | 
						"unsafe"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/prometheus/client_golang/prometheus"
 | 
						"github.com/prometheus/client_golang/prometheus"
 | 
				
			||||||
 | 
						"golang.org/x/sys/unix"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					type clockinfo struct {
 | 
				
			||||||
#cgo LDFLAGS:
 | 
						hz     int32 // clock frequency
 | 
				
			||||||
#include <fcntl.h>
 | 
						tick   int32 // micro-seconds per hz tick
 | 
				
			||||||
#include <stdlib.h>
 | 
						spare  int32
 | 
				
			||||||
#include <sys/param.h>
 | 
						stathz int32 // statistics clock frequency
 | 
				
			||||||
#include <sys/pcpu.h>
 | 
						profhz int32 // profiling clock frequency
 | 
				
			||||||
#include <sys/resource.h>
 | 
					 | 
				
			||||||
#include <sys/sysctl.h>
 | 
					 | 
				
			||||||
#include <sys/time.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int getCPUTimes(int *ncpu, double **cpu_times, size_t *cp_times_length) {
 | 
					type cputime struct {
 | 
				
			||||||
 | 
						user float64
 | 
				
			||||||
	// Assert that mibs are set up through setupSysctlMIBs
 | 
						nice float64
 | 
				
			||||||
	if (!mibs_set_up) {
 | 
						sys  float64
 | 
				
			||||||
		return -1;
 | 
						intr float64
 | 
				
			||||||
 | 
						idle float64
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Retrieve number of cpu cores
 | 
					func getCPUTimes() ([]cputime, error) {
 | 
				
			||||||
	size_t ncpu_size = sizeof(*ncpu);
 | 
						const states = 5
 | 
				
			||||||
	if (sysctl(mib_hw_ncpu, mib_hw_ncpu_len, ncpu, &ncpu_size, NULL, 0) == -1 ||
 | 
					
 | 
				
			||||||
	    sizeof(*ncpu) != ncpu_size) {
 | 
						clockb, err := unix.SysctlRaw("kern.clockrate")
 | 
				
			||||||
		return -1;
 | 
						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
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Retrieve clockrate
 | 
						var cpufreq float64
 | 
				
			||||||
	struct clockinfo clockrate;
 | 
						if clock.stathz > 0 {
 | 
				
			||||||
	size_t clockrate_size = sizeof(clockrate);
 | 
							cpufreq = float64(clock.stathz)
 | 
				
			||||||
	if (sysctl(mib_kern_clockrate, mib_kern_clockrate_len, &clockrate, &clockrate_size, NULL, 0) == -1 ||
 | 
						} else {
 | 
				
			||||||
	    sizeof(clockrate) != clockrate_size) {
 | 
							cpufreq = float64(clock.hz)
 | 
				
			||||||
		return -1;
 | 
						}
 | 
				
			||||||
 | 
						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)):]
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Retrieve cp_times values
 | 
						cpus := make([]cputime, len(times)/states)
 | 
				
			||||||
	*cp_times_length = (*ncpu) * CPUSTATES;
 | 
						for i := 0; i < len(times); i += states {
 | 
				
			||||||
 | 
							cpu := &cpus[i/states]
 | 
				
			||||||
	long cp_times[*cp_times_length];
 | 
							cpu.user = times[i]
 | 
				
			||||||
	size_t cp_times_size = sizeof(cp_times);
 | 
							cpu.nice = times[i+1]
 | 
				
			||||||
 | 
							cpu.sys = times[i+2]
 | 
				
			||||||
	if (sysctl(mib_kern_cp_times, mib_kern_cp_times_len, &cp_times, &cp_times_size, NULL, 0) == -1 ||
 | 
							cpu.intr = times[i+3]
 | 
				
			||||||
	    sizeof(cp_times) != cp_times_size) {
 | 
							cpu.idle = times[i+4]
 | 
				
			||||||
		return -1;
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						return cpus, nil
 | 
				
			||||||
	// 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;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void freeCPUTimes(double *cpu_times) {
 | 
					 | 
				
			||||||
	free(cpu_times);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
import "C"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const maxCPUTimesLen = C.MAXCPU * C.CPUSTATES
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type statCollector struct {
 | 
					type statCollector struct {
 | 
				
			||||||
	cpu *prometheus.CounterVec
 | 
						cpu *prometheus.CounterVec
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -116,9 +88,6 @@ func init() {
 | 
				
			||||||
// Takes a prometheus registry and returns a new Collector exposing
 | 
					// Takes a prometheus registry and returns a new Collector exposing
 | 
				
			||||||
// CPU stats.
 | 
					// CPU stats.
 | 
				
			||||||
func NewStatCollector() (Collector, error) {
 | 
					func NewStatCollector() (Collector, error) {
 | 
				
			||||||
	if C.setupSysctlMIBs() == -1 {
 | 
					 | 
				
			||||||
		return nil, errors.New("could not initialize sysctl MIBs")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return &statCollector{
 | 
						return &statCollector{
 | 
				
			||||||
		cpu: prometheus.NewCounterVec(
 | 
							cpu: prometheus.NewCounterVec(
 | 
				
			||||||
			prometheus.CounterOpts{
 | 
								prometheus.CounterOpts{
 | 
				
			||||||
| 
						 | 
					@ -133,7 +102,6 @@ func NewStatCollector() (Collector, error) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Expose CPU stats using sysctl.
 | 
					// Expose CPU stats using sysctl.
 | 
				
			||||||
func (c *statCollector) Update(ch chan<- prometheus.Metric) (err error) {
 | 
					func (c *statCollector) Update(ch chan<- prometheus.Metric) (err error) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
	// We want time spent per-cpu per CPUSTATE.
 | 
						// We want time spent per-cpu per CPUSTATE.
 | 
				
			||||||
	// CPUSTATES (number of CPUSTATES) is defined as 5U.
 | 
						// CPUSTATES (number of CPUSTATES) is defined as 5U.
 | 
				
			||||||
	// Order: CP_USER | CP_NICE | CP_SYS | CP_IDLE | CP_INTR
 | 
						// 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.
 | 
						// Look into sys/kern/kern_clock.c for details.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var ncpu C.int
 | 
						cpuTimes, err := getCPUTimes()
 | 
				
			||||||
	var cpuTimesC *C.double
 | 
						if err != nil {
 | 
				
			||||||
	var cpuTimesLength C.size_t
 | 
							return err
 | 
				
			||||||
	if C.getCPUTimes(&ncpu, &cpuTimesC, &cpuTimesLength) == -1 {
 | 
					 | 
				
			||||||
		return errors.New("could not retrieve CPU times")
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	defer C.freeCPUTimes(cpuTimesC)
 | 
						for cpu, t := range cpuTimes {
 | 
				
			||||||
	if cpuTimesLength > maxCPUTimesLen {
 | 
							c.cpu.With(prometheus.Labels{"cpu": strconv.Itoa(cpu), "mode": "user"}).Set(t.user)
 | 
				
			||||||
		return errors.New("more CPU's than MAXCPU?")
 | 
							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)
 | 
						c.cpu.Collect(ch)
 | 
				
			||||||
	return err
 | 
						return err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue