Files
logpile/internal/parser/vendors/inspur/redis_dump.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
}
}
}