Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ Windows support is now removed, the [wmi_exporter](https://github.com/martinlind
* [CHANGE] Switch to kingpin flags #639
* [CHANGE] Replace --collectors.enabled with per-collector flags #640
* [FEATURE] Add ARP collector for Linux #540
* [FEATURE] Add XFS colector for Linux #568, #575
* [FEATURE] Add XFS collector for Linux #568, #575
* [FEATURE] Add qdisc collector for Linux #580
* [FEATURE] Add cpufreq stats for Linux #548
* [FEATURE] Add diskstats for Darwin #593
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ zoneinfo | Exposes NUMA memory zone metrics. | Linux

### Deprecated

These colectors are deprecated and will be removed in the next major release.
These collectors are deprecated and will be removed in the next major release.

Name | Description | OS
---------|-------------|----
Expand Down
277 changes: 277 additions & 0 deletions collector/cpu_netbsd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
// Copyright 2023 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.

//go:build !nocpu
// +build !nocpu

package collector

import (
"errors"
"math"
"regexp"
"sort"
"strconv"
"strings"
"unsafe"

"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/prometheus/client_golang/prometheus"
"golang.org/x/sys/unix"

"howett.net/plist"
)

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
}

type cputime struct {
user float64
nice float64
sys float64
intr float64
idle float64
}

type plistref struct {
pref_plist unsafe.Pointer
pref_len uint64
}

type sysmonValues struct {
CurValue int `plist:"cur-value"`
Description string `plist:"description"`
State string `plist:"state"`
Type string `plist:"type"`
}

type sysmonProperty []sysmonValues

type sysmonProperties map[string]sysmonProperty

func readBytes(ptr unsafe.Pointer, length uint64) []byte {
buf := make([]byte, length-1)
var i uint64
for ; i < length-1; i++ {
buf[i] = *(*byte)(unsafe.Pointer(uintptr(ptr) + uintptr(i)))
}
return buf
}

func ioctl(fd int, nr int64, typ byte, size uintptr, retptr unsafe.Pointer) error {
_, _, errno := unix.Syscall(
unix.SYS_IOCTL,
uintptr(fd),
// Some magicks derived from sys/ioccom.h.
uintptr((0x40000000|0x80000000)|
((int64(size)&(1<<13-1))<<16)|
(int64(typ)<<8)|
nr,
),
uintptr(retptr),
)
if errno != 0 {
return errno
}
return nil
}

func readSysmonProperties() (sysmonProperties, error) {
fd, err := unix.Open(rootfsFilePath("/dev/sysmon"), unix.O_RDONLY, 0777)
if err != nil {
return nil, err
}
defer unix.Close(fd)

var retptr plistref

if err = ioctl(fd, 0, 'E', unsafe.Sizeof(retptr), unsafe.Pointer(&retptr)); err != nil {
return nil, err
}

bytes := readBytes(retptr.pref_plist, retptr.pref_len)

var props sysmonProperties
if _, err = plist.Unmarshal(bytes, &props); err != nil {
return nil, err
}
return props, nil
}

func sortFilterSysmonProperties(props sysmonProperties, prefix string) []string {
var keys []string
for key := range props {
if !strings.HasPrefix(key, prefix) {
continue
}
keys = append(keys, key)
}
sort.Strings(keys)
return keys
}

func convertTemperatures(prop sysmonProperty, res map[int]float64) error {

for _, val := range prop {
if val.State == "invalid" || val.State == "unknown" || val.State == "" {
continue
}

re := regexp.MustCompile("^cpu([0-9]+) temperature$")
core := re.FindStringSubmatch(val.Description)[1]
ncore, _ := strconv.Atoi(core)
temperature := ((float64(uint64(val.CurValue))) / 1000000) - 273.15
res[ncore] = temperature
}
return nil
}

func getCPUTemperatures() (map[int]float64, error) {

res := make(map[int]float64)

// Read all properties
props, err := readSysmonProperties()
if err != nil {
return res, err
}

keys := sortFilterSysmonProperties(props, "coretemp")
for idx, _ := range keys {
convertTemperatures(props[keys[idx]], res)
}

return res, nil
}

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]))

var cpufreq float64
if clock.stathz > 0 {
cpufreq = float64(clock.stathz)
} else {
cpufreq = float64(clock.hz)
}

ncpusb, err := unix.SysctlRaw("hw.ncpu")
if err != nil {
return nil, err
}
ncpus := *(*int)(unsafe.Pointer(&ncpusb[0]))

if ncpus < 1 {
return nil, errors.New("Invalid cpu number")
}

var times []float64
for ncpu := 0; ncpu < ncpus; ncpu++ {
cpb, err := unix.SysctlRaw("kern.cp_time", ncpu)
if err != nil {
return nil, err
}
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
}

type statCollector struct {
cpu typedDesc
temp typedDesc
logger log.Logger
}

func init() {
registerCollector("cpu", defaultEnabled, NewStatCollector)
}

// NewStatCollector returns a new Collector exposing CPU stats.
func NewStatCollector(logger log.Logger) (Collector, error) {
return &statCollector{
cpu: typedDesc{nodeCPUSecondsDesc, prometheus.CounterValue},
temp: typedDesc{prometheus.NewDesc(
prometheus.BuildFQName(namespace, cpuCollectorSubsystem, "temperature_celsius"),
"CPU temperature",
[]string{"cpu"}, nil,
), prometheus.GaugeValue},
logger: logger,
}, nil
}

// Expose CPU stats using sysctl.
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.
// Order: CP_USER | CP_NICE | CP_SYS | CP_IDLE | CP_INTR
// sysctl kern.cp_time.x provides CPUSTATES long integers:
// (space-separated list of the above variables, where
// x stands for the number of the CPU core)
//
// Each value is a counter incremented at frequency
// kern.clockrate.(stathz | hz)
//
// Look into sys/kern/kern_clock.c for details.

cpuTimes, err := getCPUTimes()
if err != nil {
return err
}

cpuTemperatures, err := getCPUTemperatures()
if err != nil {
return err
}

for cpu, t := range cpuTimes {
lcpu := strconv.Itoa(cpu)
ch <- c.cpu.mustNewConstMetric(float64(t.user), lcpu, "user")
ch <- c.cpu.mustNewConstMetric(float64(t.nice), lcpu, "nice")
ch <- c.cpu.mustNewConstMetric(float64(t.sys), lcpu, "system")
ch <- c.cpu.mustNewConstMetric(float64(t.intr), lcpu, "interrupt")
ch <- c.cpu.mustNewConstMetric(float64(t.idle), lcpu, "idle")

if temp, ok := cpuTemperatures[cpu]; ok {
ch <- c.temp.mustNewConstMetric(temp, lcpu)
} else {
level.Debug(c.logger).Log("msg", "no temperature information for CPU", "cpu", cpu)
ch <- c.temp.mustNewConstMetric(math.NaN(), lcpu)
}
}
return err
}
44 changes: 44 additions & 0 deletions collector/cpu_netbsd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2023 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.

//go:build !nocpu
// +build !nocpu

package collector

import (
"runtime"
"testing"
)

func TestCPUTimes(t *testing.T) {
times, err := getCPUTimes()
if err != nil {
t.Fatalf("getCPUTimes returned error: %v", err)
}

if len(times) == 0 {
t.Fatalf("no CPU times found")
}

if got, want := len(times), runtime.NumCPU(); got != want {
t.Fatalf("unexpected # of CPU times; got %d want %d", got, want)
}
}

func TestCPUTemperatures(t *testing.T) {
_, err := getCPUTemperatures()
if err != nil {
t.Fatalf("getCPUTemperatures returned error: %v", err)
}
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ require (
github.com/soundcloud/go-runit v0.0.0-20150630195641-06ad41a06c4a
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb
golang.org/x/sys v0.6.0
howett.net/plist v1.0.0
)

require (
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ github.com/hodgesds/perf-utils v0.7.0 h1:7KlHGMuig4FRH5fNw68PV6xLmgTe7jKs9hgAcEA
github.com/hodgesds/perf-utils v0.7.0/go.mod h1:LAklqfDadNKpkxoAJNHpD5tkY0rkZEVdnCEWN5k4QJY=
github.com/illumos/go-kstat v0.0.0-20210513183136-173c9b0a9973 h1:hk4LPqXIY/c9XzRbe7dA6qQxaT6Axcbny0L/G5a4owQ=
github.com/illumos/go-kstat v0.0.0-20210513183136-173c9b0a9973/go.mod h1:PoK3ejP3LJkGTzKqRlpvCIFas3ncU02v8zzWDW+g0FY=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
Expand Down Expand Up @@ -165,7 +166,10 @@ google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=