diff --git a/internal/exporter/reanimator_converter.go b/internal/exporter/reanimator_converter.go index 2a31b15..b279ed7 100644 --- a/internal/exporter/reanimator_converter.go +++ b/internal/exporter/reanimator_converter.go @@ -998,6 +998,9 @@ func convertPCIeFromDevices(devices []models.HardwareDevice, collectedAt string) float64(intFromDetailMap(d.Details, "power")), ) status := normalizeStatus(d.Status, false) + if status != "Critical" && isPCIeLinkWidthDegraded(d.LinkWidth, d.MaxLinkWidth) { + status = "Warning" + } meta := buildStatusMeta(status, d.StatusCheckedAt, d.StatusChangedAt, d.StatusHistory, d.ErrorDescription, collectedAt) slot := firstNonEmptyString(d.Slot, d.BDF) result = append(result, ReanimatorPCIe{ @@ -2649,6 +2652,15 @@ func normalizeStatus(status string, allowEmpty bool) string { } } +// isPCIeLinkWidthDegraded reports whether a device negotiated a narrower PCIe +// link than the slot/device supports (e.g. x8 in an x16 slot) — a signal of a +// bad seat, bent connector, or misconfigured riser. This runs once here, on +// the shared LinkWidth/MaxLinkWidth fields every vendor parser populates, so +// it applies uniformly regardless of which parser produced the reading. +func isPCIeLinkWidthDegraded(linkWidth, maxLinkWidth int) bool { + return linkWidth > 0 && maxLinkWidth > 0 && linkWidth < maxLinkWidth +} + var ( ipv4Regex = regexp.MustCompile(`(?:^|[^0-9])((?:\d{1,3}\.){3}\d{1,3})(?:[^0-9]|$)`) )