Skip to content

Commit 7a9aad0

Browse files
authored
Merge pull request #310 from stuartnelson3/dfly-cpu
export DragonFlyBSD CPU time
2 parents bb2b984 + 450fe0f commit 7a9aad0

File tree

2 files changed

+193
-0
lines changed

2 files changed

+193
-0
lines changed

collector/cpu_dragonfly.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// Copyright 2016 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
// +build !nocpu
15+
16+
package collector
17+
18+
import (
19+
"errors"
20+
"fmt"
21+
"unsafe"
22+
23+
"github.com/prometheus/client_golang/prometheus"
24+
)
25+
26+
/*
27+
#cgo LDFLAGS:
28+
#include <sys/sysctl.h>
29+
#include <kinfo.h>
30+
#include <stdlib.h>
31+
#include <stdio.h>
32+
33+
int
34+
getCPUTimes(uint64_t **cputime, size_t *cpu_times_len, long *freq) {
35+
size_t len;
36+
37+
// Get number of cpu cores.
38+
int mib[2];
39+
int ncpu;
40+
mib[0] = CTL_HW;
41+
mib[1] = HW_NCPU;
42+
len = sizeof(ncpu);
43+
if (sysctl(mib, 2, &ncpu, &len, NULL, 0)) {
44+
return -1;
45+
}
46+
47+
// The bump on each statclock is
48+
// ((cur_systimer - prev_systimer) * systimer_freq) >> 32
49+
// where
50+
// systimer_freq = sysctl kern.cputimer.freq
51+
len = sizeof(*freq);
52+
if (sysctlbyname("kern.cputimer.freq", freq, &len, NULL, 0)) {
53+
return -1;
54+
}
55+
56+
// Get the cpu times.
57+
struct kinfo_cputime cp_t[ncpu];
58+
bzero(cp_t, sizeof(struct kinfo_cputime)*ncpu);
59+
len = sizeof(cp_t[0])*ncpu;
60+
if (sysctlbyname("kern.cputime", &cp_t, &len, NULL, 0)) {
61+
return -1;
62+
}
63+
64+
*cpu_times_len = ncpu*CPUSTATES;
65+
66+
uint64_t user, nice, sys, intr, idle;
67+
user = nice = sys = intr = idle = 0;
68+
*cputime = (uint64_t *) malloc(sizeof(uint64_t)*(*cpu_times_len));
69+
for (int i = 0; i < ncpu; ++i) {
70+
int offset = CPUSTATES * i;
71+
(*cputime)[offset] = cp_t[i].cp_user;
72+
(*cputime)[offset+1] = cp_t[i].cp_nice;
73+
(*cputime)[offset+2] = cp_t[i].cp_sys;
74+
(*cputime)[offset+3] = cp_t[i].cp_intr;
75+
(*cputime)[offset+4] = cp_t[i].cp_idle;
76+
}
77+
78+
return 0;
79+
80+
}
81+
*/
82+
import "C"
83+
84+
const maxCPUTimesLen = C.MAXCPU * C.CPUSTATES
85+
86+
type statCollector struct {
87+
cpu *prometheus.Desc
88+
}
89+
90+
func init() {
91+
Factories["cpu"] = NewStatCollector
92+
}
93+
94+
// Takes a prometheus registry and returns a new Collector exposing
95+
// CPU stats.
96+
func NewStatCollector() (Collector, error) {
97+
return &statCollector{
98+
cpu: prometheus.NewDesc(
99+
prometheus.BuildFQName(Namespace, "", "cpu"),
100+
"Seconds the cpus spent in each mode.",
101+
[]string{"cpu", "mode"}, nil,
102+
),
103+
}, nil
104+
}
105+
106+
func getDragonFlyCPUTimes() ([]float64, error) {
107+
// We want time spent per-cpu per CPUSTATE.
108+
// CPUSTATES (number of CPUSTATES) is defined as 5U.
109+
// States: CP_USER | CP_NICE | CP_SYS | CP_IDLE | CP_INTR
110+
//
111+
// Each value is a counter incremented at frequency
112+
// kern.cputimer.freq
113+
//
114+
// Look into sys/kern/kern_clock.c for details.
115+
116+
var (
117+
cpuTimesC *C.uint64_t
118+
cpuTimerFreq C.long
119+
cpuTimesLength C.size_t
120+
)
121+
122+
if C.getCPUTimes(&cpuTimesC, &cpuTimesLength, &cpuTimerFreq) == -1 {
123+
return nil, errors.New("could not retrieve CPU times")
124+
}
125+
defer C.free(unsafe.Pointer(cpuTimesC))
126+
127+
cput := (*[maxCPUTimesLen]C.uint64_t)(unsafe.Pointer(cpuTimesC))[:cpuTimesLength:cpuTimesLength]
128+
129+
cpuTimes := make([]float64, cpuTimesLength)
130+
for i, value := range cput {
131+
cpuTimes[i] = float64(value) / float64(cpuTimerFreq)
132+
}
133+
return cpuTimes, nil
134+
}
135+
136+
// Expose CPU stats using sysctl.
137+
func (c *statCollector) Update(ch chan<- prometheus.Metric) error {
138+
var fieldsCount = 5
139+
cpuTimes, err := getDragonFlyCPUTimes()
140+
if err != nil {
141+
return err
142+
}
143+
144+
// Export order: user nice sys intr idle
145+
cpuFields := []string{"user", "nice", "sys", "interrupt", "idle"}
146+
for i, value := range cpuTimes {
147+
cpux := fmt.Sprintf("cpu%d", i/fieldsCount)
148+
ch <- prometheus.MustNewConstMetric(c.cpu, prometheus.CounterValue, value, cpux, cpuFields[i%fieldsCount])
149+
}
150+
151+
return nil
152+
}

collector/cpu_dragonfly_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2016 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
// +build !nocpu
15+
16+
package collector
17+
18+
import (
19+
"runtime"
20+
"testing"
21+
)
22+
23+
func TestCPU(t *testing.T) {
24+
var (
25+
fieldsCount = 5
26+
times, err = getDragonFlyCPUTimes()
27+
)
28+
29+
if err != nil {
30+
t.Fatalf("expected no error, got %v", err)
31+
}
32+
33+
if len(times) == 0 {
34+
t.Fatalf("no cputimes found")
35+
}
36+
37+
want := runtime.NumCPU() * fieldsCount
38+
if len(times) != want {
39+
t.Fatalf("should have %d cpuTimes: got %d", want, len(times))
40+
}
41+
}

0 commit comments

Comments
 (0)