package collector import ( "bee/audit/internal/schema" "log/slog" "os" "path/filepath" "regexp" "strconv" "strings" ) var ( ethtoolModuleQuery = func(iface string) (string, error) { out, err := raidToolQuery("ethtool", "-m", iface) if err != nil { return "", err } return string(out), nil } readNetStatFile = func(iface, key string) (int64, error) { path := filepath.Join("/sys/class/net", iface, "statistics", key) raw, err := os.ReadFile(path) if err != nil { return 0, err } v, err := strconv.ParseInt(strings.TrimSpace(string(raw)), 10, 64) if err != nil { return 0, err } return v, nil } ) func enrichPCIeWithNICTelemetry(devs []schema.HardwarePCIeDevice) []schema.HardwarePCIeDevice { enriched := 0 for i := range devs { if !isNICDevice(devs[i]) || devs[i].BDF == nil { continue } bdf := normalizePCIeBDF(*devs[i].BDF) if bdf == "" { continue } ifaces := netIfacesByBDF(bdf) if len(ifaces) == 0 { continue } iface := ifaces[0] if devs[i].Firmware == nil { if out, err := ethtoolInfoQuery(iface); err == nil { if fw := parseEthtoolFirmwareInfo(out); fw != "" { devs[i].Firmware = &fw } } } if devs[i].Telemetry == nil { devs[i].Telemetry = map[string]any{} } injectNICPacketStats(devs[i].Telemetry, iface) if out, err := ethtoolModuleQuery(iface); err == nil { injectSFPDOMTelemetry(devs[i].Telemetry, out) } if len(devs[i].Telemetry) == 0 { devs[i].Telemetry = nil } else { enriched++ } } slog.Info("nic: telemetry enriched", "count", enriched) return devs } func isNICDevice(dev schema.HardwarePCIeDevice) bool { if dev.DeviceClass == nil { return false } c := strings.ToLower(strings.TrimSpace(*dev.DeviceClass)) return strings.Contains(c, "ethernet controller") || strings.Contains(c, "network controller") || strings.Contains(c, "infiniband controller") } func injectNICPacketStats(dst map[string]any, iface string) { for _, key := range []string{"rx_packets", "tx_packets", "rx_errors", "tx_errors"} { if v, err := readNetStatFile(iface, key); err == nil { dst[key] = v } } } func injectSFPDOMTelemetry(dst map[string]any, raw string) { parsed := parseSFPDOM(raw) for k, v := range parsed { dst[k] = v } } var floatRe = regexp.MustCompile(`[-+]?[0-9]*\.?[0-9]+`) func parseSFPDOM(raw string) map[string]any { out := map[string]any{} for _, line := range strings.Split(raw, "\n") { trimmed := strings.TrimSpace(line) if trimmed == "" { continue } idx := strings.Index(trimmed, ":") if idx < 0 { continue } key := strings.ToLower(strings.TrimSpace(trimmed[:idx])) val := strings.TrimSpace(trimmed[idx+1:]) switch { case strings.Contains(key, "module temperature"): if f, ok := firstFloat(val); ok { out["sfp_temperature_c"] = f } case strings.Contains(key, "laser output power"): if f, ok := dbmValue(val); ok { out["sfp_tx_power_dbm"] = f } case strings.Contains(key, "receiver signal"): if f, ok := dbmValue(val); ok { out["sfp_rx_power_dbm"] = f } case strings.Contains(key, "module voltage"): if f, ok := firstFloat(val); ok { out["sfp_voltage_v"] = f } case strings.Contains(key, "laser bias current"): if f, ok := firstFloat(val); ok { out["sfp_bias_ma"] = f } } } return out } func firstFloat(raw string) (float64, bool) { m := floatRe.FindString(raw) if m == "" { return 0, false } v, err := strconv.ParseFloat(m, 64) if err != nil { return 0, false } return v, true } func dbmValue(raw string) (float64, bool) { parts := strings.Split(strings.ToLower(raw), "dbm") if len(parts) == 0 { return 0, false } for i := len(parts) - 1; i >= 0; i-- { candidate := parts[i] matches := floatRe.FindAllString(candidate, -1) if len(matches) == 0 { continue } v, err := strconv.ParseFloat(matches[len(matches)-1], 64) if err == nil { return v, true } } return 0, false }