Align hardware export with ingest contract
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user