560 lines
13 KiB
Go
560 lines
13 KiB
Go
package inspur
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"git.mchus.pro/mchus/logpile/internal/models"
|
|
)
|
|
|
|
var (
|
|
reRedisGPUKey = regexp.MustCompile(`GPUInfo:REDIS_GPUINFO_T([0-9]+):([A-Za-z0-9_]+)`)
|
|
reRedisNICKey = regexp.MustCompile(`RedisNicInfo:redis_nic_info_t:stNicDeviceInfo([0-9]+):([A-Za-z0-9_]+)`)
|
|
reRedisRAIDSerial = regexp.MustCompile(`RAIDMSCCInfo:redis_pcie_mscc_raid_info_t([0-9]+):RAIDInfo:SerialNum`)
|
|
reRedisPCIESNPN = regexp.MustCompile(`AssetInfoPCIE:SNPN([0-9]+):(SN|PN)`)
|
|
)
|
|
|
|
type redisGPUSnapshot struct {
|
|
ByIndex map[int]map[string]string
|
|
}
|
|
|
|
type redisNICSnapshot struct {
|
|
ByIndex map[int]map[string]string
|
|
}
|
|
|
|
type redisPCIESerialSnapshot struct {
|
|
ByPart map[string]string
|
|
}
|
|
|
|
func enrichFromRedisDump(content []byte, hw *models.HardwareConfig) {
|
|
if hw == nil || len(content) == 0 {
|
|
return
|
|
}
|
|
|
|
gpuSnap := parseRedisGPUSnapshot(content)
|
|
nicSnap := parseRedisNICSnapshot(content)
|
|
raidSerials := parseRedisRAIDSerials(content)
|
|
pcieSnap := parseRedisPCIESerialSnapshot(content)
|
|
|
|
applyRedisGPUEnrichment(hw, gpuSnap)
|
|
applyRedisNICEnrichment(hw, nicSnap)
|
|
applyRedisPCIESNPNEnrichment(hw, pcieSnap)
|
|
applyRedisPCIeEnrichment(hw, raidSerials)
|
|
}
|
|
|
|
func parseRedisRAIDSerials(content []byte) []string {
|
|
matches := reRedisRAIDSerial.FindAllSubmatchIndex(content, -1)
|
|
if len(matches) == 0 {
|
|
return nil
|
|
}
|
|
|
|
seen := make(map[string]bool, len(matches))
|
|
serials := make([]string, 0, len(matches))
|
|
for _, m := range matches {
|
|
if len(m) < 4 {
|
|
continue
|
|
}
|
|
value := normalizeRedisValue(extractRedisCandidateValue(content, m[1]))
|
|
if value == "" || seen[value] {
|
|
continue
|
|
}
|
|
seen[value] = true
|
|
serials = append(serials, value)
|
|
}
|
|
return serials
|
|
}
|
|
|
|
func parseRedisPCIESerialSnapshot(content []byte) redisPCIESerialSnapshot {
|
|
type rec struct {
|
|
PN string
|
|
SN string
|
|
}
|
|
tmp := make(map[int]rec)
|
|
|
|
matches := reRedisPCIESNPN.FindAllSubmatchIndex(content, -1)
|
|
for _, m := range matches {
|
|
if len(m) < 6 {
|
|
continue
|
|
}
|
|
idxStr := string(content[m[2]:m[3]])
|
|
field := string(content[m[4]:m[5]])
|
|
idx, err := strconv.Atoi(idxStr)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
value := normalizeRedisValue(extractRedisCandidateValue(content, m[1]))
|
|
if value == "" {
|
|
continue
|
|
}
|
|
|
|
r := tmp[idx]
|
|
if field == "PN" {
|
|
r.PN = value
|
|
} else if field == "SN" {
|
|
r.SN = value
|
|
}
|
|
tmp[idx] = r
|
|
}
|
|
|
|
out := redisPCIESerialSnapshot{ByPart: make(map[string]string)}
|
|
for _, r := range tmp {
|
|
pn := normalizeRedisValue(r.PN)
|
|
sn := normalizeRedisValue(r.SN)
|
|
if pn == "" || sn == "" {
|
|
continue
|
|
}
|
|
out.ByPart[strings.ToLower(strings.TrimSpace(pn))] = sn
|
|
}
|
|
return out
|
|
}
|
|
|
|
func parseRedisGPUSnapshot(content []byte) redisGPUSnapshot {
|
|
snap := redisGPUSnapshot{ByIndex: make(map[int]map[string]string)}
|
|
matches := reRedisGPUKey.FindAllSubmatchIndex(content, -1)
|
|
for _, m := range matches {
|
|
if len(m) < 6 {
|
|
continue
|
|
}
|
|
|
|
idxStr := string(content[m[2]:m[3]])
|
|
field := string(content[m[4]:m[5]])
|
|
idx, err := strconv.Atoi(idxStr)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
value := extractRedisInlineValue(content, m[1])
|
|
if value == "" {
|
|
continue
|
|
}
|
|
|
|
byField, ok := snap.ByIndex[idx]
|
|
if !ok {
|
|
byField = make(map[string]string)
|
|
snap.ByIndex[idx] = byField
|
|
}
|
|
byField[field] = value
|
|
}
|
|
return snap
|
|
}
|
|
|
|
func parseRedisNICSnapshot(content []byte) redisNICSnapshot {
|
|
snap := redisNICSnapshot{ByIndex: make(map[int]map[string]string)}
|
|
matches := reRedisNICKey.FindAllSubmatchIndex(content, -1)
|
|
for _, m := range matches {
|
|
if len(m) < 6 {
|
|
continue
|
|
}
|
|
|
|
idxStr := string(content[m[2]:m[3]])
|
|
field := string(content[m[4]:m[5]])
|
|
idx, err := strconv.Atoi(idxStr)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
value := extractRedisInlineValue(content, m[1])
|
|
if value == "" {
|
|
continue
|
|
}
|
|
|
|
byField, ok := snap.ByIndex[idx]
|
|
if !ok {
|
|
byField = make(map[string]string)
|
|
snap.ByIndex[idx] = byField
|
|
}
|
|
byField[field] = value
|
|
}
|
|
return snap
|
|
}
|
|
|
|
func extractRedisInlineValue(content []byte, start int) string {
|
|
if start < 0 || start >= len(content) {
|
|
return ""
|
|
}
|
|
|
|
i := start
|
|
for i < len(content) && content[i] <= 0x20 {
|
|
i++
|
|
}
|
|
if i >= len(content) {
|
|
return ""
|
|
}
|
|
|
|
j := i
|
|
for j < len(content) {
|
|
c := content[j]
|
|
if c == 0 || c < 0x20 || c > 0x7e {
|
|
break
|
|
}
|
|
j++
|
|
}
|
|
|
|
if j <= i {
|
|
return ""
|
|
}
|
|
|
|
raw := strings.TrimSpace(string(content[i:j]))
|
|
if raw == "" {
|
|
return ""
|
|
}
|
|
|
|
decoded := maybeDecodeHexString(raw)
|
|
if decoded != "" {
|
|
return decoded
|
|
}
|
|
return raw
|
|
}
|
|
|
|
func extractRedisCandidateValue(content []byte, start int) string {
|
|
// Fast-path for simple inline string values.
|
|
if v := extractRedisInlineValue(content, start); normalizeRedisValue(v) != "" {
|
|
return v
|
|
}
|
|
|
|
if start < 0 || start >= len(content) {
|
|
return ""
|
|
}
|
|
|
|
end := start + 256
|
|
if end > len(content) {
|
|
end = len(content)
|
|
}
|
|
window := content[start:end]
|
|
|
|
for _, token := range splitAlphaNumTokens(window) {
|
|
if len(token) < 6 {
|
|
continue
|
|
}
|
|
lower := strings.ToLower(token)
|
|
if strings.Contains(lower, "redis") || strings.Contains(lower, "sensor") || strings.Contains(lower, "fullsdr") {
|
|
continue
|
|
}
|
|
if decoded := maybeDecodeHexString(token); normalizeRedisValue(decoded) != "" {
|
|
return decoded
|
|
}
|
|
if normalizeRedisValue(token) != "" {
|
|
return token
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func splitAlphaNumTokens(b []byte) []string {
|
|
var out []string
|
|
start := -1
|
|
for i := 0; i < len(b); i++ {
|
|
c := rune(b[i])
|
|
if unicode.IsLetter(c) || unicode.IsDigit(c) {
|
|
if start == -1 {
|
|
start = i
|
|
}
|
|
continue
|
|
}
|
|
if start != -1 {
|
|
out = append(out, string(b[start:i]))
|
|
start = -1
|
|
}
|
|
}
|
|
if start != -1 {
|
|
out = append(out, string(b[start:]))
|
|
}
|
|
return out
|
|
}
|
|
|
|
func maybeDecodeHexString(s string) string {
|
|
if len(s) < 8 || len(s)%2 != 0 {
|
|
return ""
|
|
}
|
|
|
|
for _, c := range s {
|
|
if (c < '0' || c > '9') && (c < 'a' || c > 'f') && (c < 'A' || c > 'F') {
|
|
return ""
|
|
}
|
|
}
|
|
|
|
b, err := hex.DecodeString(s)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
decoded := strings.TrimSpace(strings.TrimRight(string(b), "\x00"))
|
|
if decoded == "" {
|
|
return ""
|
|
}
|
|
for _, c := range decoded {
|
|
if c < 0x20 || c > 0x7e {
|
|
return ""
|
|
}
|
|
}
|
|
return decoded
|
|
}
|
|
|
|
func applyRedisGPUEnrichment(hw *models.HardwareConfig, snap redisGPUSnapshot) {
|
|
if len(hw.GPUs) == 0 || len(snap.ByIndex) == 0 {
|
|
return
|
|
}
|
|
|
|
type redisGPU struct {
|
|
Index int
|
|
Data map[string]string
|
|
}
|
|
redisGPUs := make([]redisGPU, 0, len(snap.ByIndex))
|
|
for idx, data := range snap.ByIndex {
|
|
if data == nil {
|
|
continue
|
|
}
|
|
if data["NV_GPU_SerialNumber"] == "" && data["NV_GPU_FWVersion"] == "" && data["NV_GPU_UUID"] == "" {
|
|
continue
|
|
}
|
|
redisGPUs = append(redisGPUs, redisGPU{Index: idx, Data: data})
|
|
}
|
|
if len(redisGPUs) == 0 {
|
|
return
|
|
}
|
|
sort.Slice(redisGPUs, func(i, j int) bool { return redisGPUs[i].Index < redisGPUs[j].Index })
|
|
|
|
target := make([]*models.GPU, 0, len(hw.GPUs))
|
|
for i := range hw.GPUs {
|
|
gpu := &hw.GPUs[i]
|
|
if isNVIDIAGPU(gpu) {
|
|
target = append(target, gpu)
|
|
}
|
|
}
|
|
if len(target) == 0 || len(target) != len(redisGPUs) {
|
|
return
|
|
}
|
|
sort.Slice(target, func(i, j int) bool {
|
|
left := strings.TrimSpace(target[i].BDF)
|
|
right := strings.TrimSpace(target[j].BDF)
|
|
if left != "" && right != "" {
|
|
return left < right
|
|
}
|
|
return strings.TrimSpace(target[i].Slot) < strings.TrimSpace(target[j].Slot)
|
|
})
|
|
|
|
for i := range target {
|
|
applyRedisGPUFields(target[i], redisGPUs[i].Data)
|
|
}
|
|
}
|
|
|
|
func isNVIDIAGPU(gpu *models.GPU) bool {
|
|
if gpu == nil {
|
|
return false
|
|
}
|
|
if gpu.VendorID == 0x10de {
|
|
return true
|
|
}
|
|
man := strings.ToLower(strings.TrimSpace(gpu.Manufacturer))
|
|
return strings.Contains(man, "nvidia")
|
|
}
|
|
|
|
func applyRedisGPUFields(gpu *models.GPU, fields map[string]string) {
|
|
if gpu == nil || fields == nil {
|
|
return
|
|
}
|
|
|
|
if serial := normalizeRedisValue(fields["NV_GPU_SerialNumber"]); serial != "" && isMissingGPUField(gpu.SerialNumber) {
|
|
gpu.SerialNumber = serial
|
|
}
|
|
if fw := normalizeRedisValue(fields["NV_GPU_FWVersion"]); fw != "" && isMissingGPUField(gpu.Firmware) {
|
|
gpu.Firmware = fw
|
|
}
|
|
if uuid := normalizeRedisValue(fields["NV_GPU_UUID"]); uuid != "" && isMissingGPUField(gpu.UUID) {
|
|
gpu.UUID = uuid
|
|
}
|
|
|
|
if part := normalizeRedisValue(fields["NVGPUPartNumber"]); part != "" && isMissingGPUField(gpu.PartNumber) {
|
|
gpu.PartNumber = part
|
|
}
|
|
if model := normalizeRedisValue(fields["NVGPUMarketingName"]); model != "" && isGenericGPUModel(gpu.Model) {
|
|
gpu.Model = model
|
|
}
|
|
|
|
if gpu.ClockSpeed == 0 {
|
|
if mhz, ok := parseIntField(fields["OperatingSpeedMHz"]); ok {
|
|
gpu.ClockSpeed = mhz
|
|
}
|
|
}
|
|
if gpu.Power == 0 {
|
|
if pwr, ok := parseIntField(fields["GPUTotalPower"]); ok {
|
|
gpu.Power = pwr
|
|
}
|
|
}
|
|
if gpu.Temperature == 0 {
|
|
if temp, ok := parseIntField(fields["Temp"]); ok {
|
|
gpu.Temperature = temp
|
|
}
|
|
}
|
|
if gpu.MemTemperature == 0 {
|
|
if temp, ok := parseIntField(fields["MemTemp"]); ok {
|
|
gpu.MemTemperature = temp
|
|
}
|
|
}
|
|
}
|
|
|
|
func parseIntField(v string) (int, bool) {
|
|
v = normalizeRedisValue(v)
|
|
if v == "" {
|
|
return 0, false
|
|
}
|
|
n, err := strconv.Atoi(v)
|
|
if err != nil {
|
|
return 0, false
|
|
}
|
|
return n, true
|
|
}
|
|
|
|
func normalizeRedisValue(v string) string {
|
|
v = strings.TrimSpace(v)
|
|
if v == "" {
|
|
return ""
|
|
}
|
|
l := strings.ToLower(v)
|
|
if l == "n/a" || l == "na" || l == "null" || l == "unknown" {
|
|
return ""
|
|
}
|
|
return v
|
|
}
|
|
|
|
func isMissingGPUField(v string) bool {
|
|
return normalizeRedisValue(v) == ""
|
|
}
|
|
|
|
func isGenericGPUModel(model string) bool {
|
|
m := strings.ToLower(strings.TrimSpace(model))
|
|
switch m {
|
|
case "", "unknown", "display", "display controller", "3d controller", "vga", "gpu":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func applyRedisNICEnrichment(hw *models.HardwareConfig, snap redisNICSnapshot) {
|
|
if len(hw.NetworkAdapters) == 0 || len(snap.ByIndex) == 0 {
|
|
return
|
|
}
|
|
|
|
type redisNIC struct {
|
|
Index int
|
|
Data map[string]string
|
|
}
|
|
redisNICs := make([]redisNIC, 0, len(snap.ByIndex))
|
|
for idx, data := range snap.ByIndex {
|
|
if data == nil {
|
|
continue
|
|
}
|
|
if normalizeRedisValue(data["FWVersion"]) == "" {
|
|
continue
|
|
}
|
|
redisNICs = append(redisNICs, redisNIC{Index: idx, Data: data})
|
|
}
|
|
if len(redisNICs) == 0 {
|
|
return
|
|
}
|
|
sort.Slice(redisNICs, func(i, j int) bool { return redisNICs[i].Index < redisNICs[j].Index })
|
|
|
|
target := make([]*models.NetworkAdapter, 0, len(hw.NetworkAdapters))
|
|
for i := range hw.NetworkAdapters {
|
|
nic := &hw.NetworkAdapters[i]
|
|
if nic.Present {
|
|
target = append(target, nic)
|
|
}
|
|
}
|
|
if len(target) == 0 {
|
|
return
|
|
}
|
|
sort.Slice(target, func(i, j int) bool {
|
|
left := strings.TrimSpace(target[i].Location)
|
|
right := strings.TrimSpace(target[j].Location)
|
|
if left != "" && right != "" {
|
|
return left < right
|
|
}
|
|
return strings.TrimSpace(target[i].Slot) < strings.TrimSpace(target[j].Slot)
|
|
})
|
|
|
|
limit := len(target)
|
|
if len(redisNICs) < limit {
|
|
limit = len(redisNICs)
|
|
}
|
|
for i := 0; i < limit; i++ {
|
|
nic := target[i]
|
|
data := redisNICs[i].Data
|
|
|
|
if fw := normalizeRedisValue(data["FWVersion"]); fw != "" && normalizeRedisValue(nic.Firmware) == "" {
|
|
nic.Firmware = fw
|
|
}
|
|
if serial := normalizeRedisValue(data["SerialNum"]); serial != "" && normalizeRedisValue(nic.SerialNumber) == "" {
|
|
nic.SerialNumber = serial
|
|
}
|
|
if part := normalizeRedisValue(data["PartNum"]); part != "" && normalizeRedisValue(nic.PartNumber) == "" {
|
|
nic.PartNumber = part
|
|
}
|
|
}
|
|
}
|
|
|
|
func applyRedisPCIeEnrichment(hw *models.HardwareConfig, raidSerials []string) {
|
|
if hw == nil || len(hw.PCIeDevices) == 0 || len(raidSerials) == 0 {
|
|
return
|
|
}
|
|
|
|
target := make([]*models.PCIeDevice, 0, len(hw.PCIeDevices))
|
|
for i := range hw.PCIeDevices {
|
|
dev := &hw.PCIeDevices[i]
|
|
if normalizeRedisValue(dev.SerialNumber) != "" {
|
|
continue
|
|
}
|
|
class := strings.ToLower(strings.TrimSpace(dev.DeviceClass))
|
|
part := strings.ToLower(strings.TrimSpace(dev.PartNumber))
|
|
if strings.Contains(class, "raid") || strings.Contains(class, "sas") || strings.Contains(class, "storage") ||
|
|
strings.Contains(part, "raid") || strings.Contains(part, "sas") || strings.Contains(part, "hba") {
|
|
target = append(target, dev)
|
|
}
|
|
}
|
|
if len(target) == 0 {
|
|
return
|
|
}
|
|
|
|
sort.Slice(target, func(i, j int) bool {
|
|
left := strings.TrimSpace(target[i].BDF)
|
|
right := strings.TrimSpace(target[j].BDF)
|
|
if left != "" && right != "" {
|
|
return left < right
|
|
}
|
|
return strings.TrimSpace(target[i].Slot) < strings.TrimSpace(target[j].Slot)
|
|
})
|
|
|
|
limit := len(target)
|
|
if len(raidSerials) < limit {
|
|
limit = len(raidSerials)
|
|
}
|
|
for i := 0; i < limit; i++ {
|
|
target[i].SerialNumber = raidSerials[i]
|
|
}
|
|
}
|
|
|
|
func applyRedisPCIESNPNEnrichment(hw *models.HardwareConfig, snap redisPCIESerialSnapshot) {
|
|
if hw == nil || len(hw.PCIeDevices) == 0 || len(snap.ByPart) == 0 {
|
|
return
|
|
}
|
|
|
|
for i := range hw.PCIeDevices {
|
|
dev := &hw.PCIeDevices[i]
|
|
if normalizeRedisValue(dev.SerialNumber) != "" {
|
|
continue
|
|
}
|
|
part := strings.ToLower(strings.TrimSpace(dev.PartNumber))
|
|
if part == "" {
|
|
continue
|
|
}
|
|
if serial := normalizeRedisValue(snap.ByPart[part]); serial != "" {
|
|
dev.SerialNumber = serial
|
|
}
|
|
}
|
|
}
|