197 lines
4.5 KiB
Go
197 lines
4.5 KiB
Go
package collector
|
|
|
|
import (
|
|
"bee/audit/internal/schema"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
cpuSysBaseDir = "/sys/devices/system/cpu"
|
|
socketIndexRe = regexp.MustCompile(`(?i)(?:package id|socket|cpu)\s*([0-9]+)`)
|
|
)
|
|
|
|
func enrichCPUsWithTelemetry(cpus []schema.HardwareCPU, doc sensorsDoc) []schema.HardwareCPU {
|
|
if len(cpus) == 0 {
|
|
return cpus
|
|
}
|
|
|
|
tempBySocket := cpuTempsFromSensors(doc, len(cpus))
|
|
powerBySocket := cpuPowerFromSensors(doc, len(cpus))
|
|
throttleBySocket := cpuThrottleBySocket()
|
|
|
|
for i := range cpus {
|
|
socket := 0
|
|
if cpus[i].Socket != nil {
|
|
socket = *cpus[i].Socket
|
|
}
|
|
if value, ok := tempBySocket[socket]; ok {
|
|
cpus[i].TemperatureC = &value
|
|
}
|
|
if value, ok := powerBySocket[socket]; ok {
|
|
cpus[i].PowerW = &value
|
|
}
|
|
if value, ok := throttleBySocket[socket]; ok {
|
|
cpus[i].Throttled = &value
|
|
}
|
|
}
|
|
|
|
return cpus
|
|
}
|
|
|
|
func cpuTempsFromSensors(doc sensorsDoc, cpuCount int) map[int]float64 {
|
|
out := map[int]float64{}
|
|
if len(doc) == 0 {
|
|
return out
|
|
}
|
|
var fallback []float64
|
|
for chip, features := range doc {
|
|
for featureName, raw := range features {
|
|
feature, ok := raw.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if classifySensorFeature(feature) != "temp" {
|
|
continue
|
|
}
|
|
temp, ok := firstFeatureFloat(feature, "_input")
|
|
if !ok {
|
|
continue
|
|
}
|
|
if socket, ok := detectCPUSocket(chip, featureName); ok {
|
|
if _, exists := out[socket]; !exists {
|
|
out[socket] = temp
|
|
}
|
|
continue
|
|
}
|
|
if isLikelyCPUTemp(chip, featureName) {
|
|
fallback = append(fallback, temp)
|
|
}
|
|
}
|
|
}
|
|
if len(out) == 0 && cpuCount == 1 && len(fallback) > 0 {
|
|
out[0] = fallback[0]
|
|
}
|
|
return out
|
|
}
|
|
|
|
func cpuPowerFromSensors(doc sensorsDoc, cpuCount int) map[int]float64 {
|
|
out := map[int]float64{}
|
|
if len(doc) == 0 {
|
|
return out
|
|
}
|
|
var fallback []float64
|
|
for chip, features := range doc {
|
|
for featureName, raw := range features {
|
|
feature, ok := raw.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if classifySensorFeature(feature) != "power" {
|
|
continue
|
|
}
|
|
power, ok := firstFeatureFloatWithContains(feature, []string{"power"})
|
|
if !ok {
|
|
continue
|
|
}
|
|
if socket, ok := detectCPUSocket(chip, featureName); ok {
|
|
if _, exists := out[socket]; !exists {
|
|
out[socket] = power
|
|
}
|
|
continue
|
|
}
|
|
if isLikelyCPUPower(chip, featureName) {
|
|
fallback = append(fallback, power)
|
|
}
|
|
}
|
|
}
|
|
if len(out) == 0 && cpuCount == 1 && len(fallback) > 0 {
|
|
out[0] = fallback[0]
|
|
}
|
|
return out
|
|
}
|
|
|
|
func detectCPUSocket(parts ...string) (int, bool) {
|
|
for _, part := range parts {
|
|
matches := socketIndexRe.FindStringSubmatch(strings.ToLower(part))
|
|
if len(matches) == 2 {
|
|
value, err := strconv.Atoi(matches[1])
|
|
if err == nil {
|
|
return value, true
|
|
}
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
func isLikelyCPUTemp(chip, feature string) bool {
|
|
value := strings.ToLower(chip + " " + feature)
|
|
return strings.Contains(value, "coretemp") ||
|
|
strings.Contains(value, "k10temp") ||
|
|
strings.Contains(value, "package id") ||
|
|
strings.Contains(value, "tdie") ||
|
|
strings.Contains(value, "tctl") ||
|
|
strings.Contains(value, "cpu temp")
|
|
}
|
|
|
|
func isLikelyCPUPower(chip, feature string) bool {
|
|
value := strings.ToLower(chip + " " + feature)
|
|
return strings.Contains(value, "intel-rapl") ||
|
|
strings.Contains(value, "package id") ||
|
|
strings.Contains(value, "package-") ||
|
|
strings.Contains(value, "cpu power")
|
|
}
|
|
|
|
func cpuThrottleBySocket() map[int]bool {
|
|
out := map[int]bool{}
|
|
cpuDirs, err := filepath.Glob(filepath.Join(cpuSysBaseDir, "cpu[0-9]*"))
|
|
if err != nil {
|
|
return out
|
|
}
|
|
sort.Strings(cpuDirs)
|
|
for _, cpuDir := range cpuDirs {
|
|
socket, ok := readSocketIndex(cpuDir)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if cpuPackageThrottled(cpuDir) {
|
|
out[socket] = true
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func readSocketIndex(cpuDir string) (int, bool) {
|
|
raw, err := os.ReadFile(filepath.Join(cpuDir, "topology", "physical_package_id"))
|
|
if err != nil {
|
|
return 0, false
|
|
}
|
|
value, err := strconv.Atoi(strings.TrimSpace(string(raw)))
|
|
if err != nil || value < 0 {
|
|
return 0, false
|
|
}
|
|
return value, true
|
|
}
|
|
|
|
func cpuPackageThrottled(cpuDir string) bool {
|
|
paths := []string{
|
|
filepath.Join(cpuDir, "thermal_throttle", "package_throttle_count"),
|
|
filepath.Join(cpuDir, "thermal_throttle", "core_throttle_count"),
|
|
}
|
|
for _, path := range paths {
|
|
raw, err := os.ReadFile(path)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
value, err := strconv.ParseInt(strings.TrimSpace(string(raw)), 10, 64)
|
|
if err == nil && value > 0 {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|