fix(xfusion): parse disk_info Capacity in TB/binary units for size_gb
parseDiskInfo read capacity with Sscanf(cap, "%f GB"), so every iBMC "Capacity : 6.986 TB" NVMe line failed to match and size_gb stayed 0 while the BEE-SP live-CD export of the same drive had the real size. The iBMC number is also binary (GiB/TiB) despite the GB/TB label. parseDiskCapacityGB parses number + unit (TB/GB/MB), treats it as binary, and emits decimal GB, matching the drive's marketed capacity and the live-CD inventory. Found by diffing a G5500 V7 BMC dump against its live-CD bundle. See ADL-063. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011LffAvostt3uMkiUbVUiyM
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
8e7f22077d
commit
ab8636da04
+30
-5
@@ -2,6 +2,7 @@ package xfusion
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -1086,11 +1087,7 @@ func parseDiskInfo(content []byte) *models.Storage {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
sizeGB := 0
|
sizeGB := parseDiskCapacityGB(fields["Capacity"])
|
||||||
var capFloat float64
|
|
||||||
if _, err := fmt.Sscanf(fields["Capacity"], "%f GB", &capFloat); err == nil {
|
|
||||||
sizeGB = int(capFloat)
|
|
||||||
}
|
|
||||||
|
|
||||||
var wearPct *int
|
var wearPct *int
|
||||||
if wearStr := fields["Remnant Media Wearout"]; wearStr != "" {
|
if wearStr := fields["Remnant Media Wearout"]; wearStr != "" {
|
||||||
@@ -1120,6 +1117,34 @@ func parseDiskInfo(content []byte) *models.Storage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseDiskCapacityGB converts an iBMC disk_info "Capacity" value to decimal GB.
|
||||||
|
// iBMC labels the number "GB"/"TB" but the value is binary (GiB/TiB): a
|
||||||
|
// "6.986 TB" NVMe drive is the vendor's 7.68 TB / 7680 GB part, which is also
|
||||||
|
// what the BEE-SP live-CD inventory reports. Normalize to decimal GB so a
|
||||||
|
// BMC-dump and a live-CD export of the same drive carry the same size_gb.
|
||||||
|
func parseDiskCapacityGB(s string) int {
|
||||||
|
f := strings.Fields(strings.TrimSpace(s))
|
||||||
|
if len(f) < 2 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
val, err := strconv.ParseFloat(f[0], 64)
|
||||||
|
if err != nil || val <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var binaryBytes float64
|
||||||
|
switch strings.ToUpper(f[1]) {
|
||||||
|
case "TB", "TIB":
|
||||||
|
binaryBytes = val * (1 << 40)
|
||||||
|
case "GB", "GIB":
|
||||||
|
binaryBytes = val * (1 << 30)
|
||||||
|
case "MB", "MIB":
|
||||||
|
binaryBytes = val * (1 << 20)
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return int(math.Round(binaryBytes / 1e9))
|
||||||
|
}
|
||||||
|
|
||||||
// parseKeyValueBlock parses "Key (spaces) : Value" lines from a text block.
|
// parseKeyValueBlock parses "Key (spaces) : Value" lines from a text block.
|
||||||
func parseKeyValueBlock(content []byte) map[string]string {
|
func parseKeyValueBlock(content []byte) map[string]string {
|
||||||
result := make(map[string]string)
|
result := make(map[string]string)
|
||||||
|
|||||||
+52
@@ -369,3 +369,55 @@ func TestParseMemInfo_EmbeddedNewlineInBOM(t *testing.T) {
|
|||||||
t.Errorf("m170 fields not recovered: %+v", m170)
|
t.Errorf("m170 fields not recovered: %+v", m170)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestParseDiskInfo_CapacityUnits guards the disk_info "Capacity" parse: iBMC
|
||||||
|
// labels the number "GB"/"TB" but the value is binary, and the old code only
|
||||||
|
// matched a literal " GB" so every "X TB" NVMe drive got size_gb 0 while the
|
||||||
|
// BEE-SP live-CD export of the same drive reported the real decimal size.
|
||||||
|
func TestParseDiskInfo_CapacityUnits(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
content string
|
||||||
|
wantGB int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "kioxia nvme reported as TB",
|
||||||
|
content: "Serial Number : 9F30A0440V43\n" +
|
||||||
|
"Model : KIOXIA KCD8XPUG7T68\n" +
|
||||||
|
"Media Type : SSD\n" +
|
||||||
|
"Interface Type : PCIe\n" +
|
||||||
|
"Capacity : 6.986 TB\n",
|
||||||
|
wantGB: 7680,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "intel sata reported as TB",
|
||||||
|
content: "Serial Number : PHYF202100WK3P8EGN\n" +
|
||||||
|
"Model : INTEL SSDSC2KB038T8\n" +
|
||||||
|
"Capacity : 3.492 TB\n",
|
||||||
|
wantGB: 3840,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "boot ssd reported as GB",
|
||||||
|
content: "Serial Number : X1\nModel : M\nCapacity : 446.625 GB\n",
|
||||||
|
wantGB: 480,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing capacity",
|
||||||
|
content: "Serial Number : X2\nModel : M\n",
|
||||||
|
wantGB: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
d := parseDiskInfo([]byte(tc.content))
|
||||||
|
if d == nil {
|
||||||
|
t.Fatal("parseDiskInfo returned nil")
|
||||||
|
}
|
||||||
|
// iBMC reports binary units; a few GB of rounding slack vs the
|
||||||
|
// live-CD's exact byte count is acceptable (size_gb is display-only).
|
||||||
|
if diff := d.SizeGB - tc.wantGB; diff < -2 || diff > 2 {
|
||||||
|
t.Errorf("SizeGB = %d, want ~%d", d.SizeGB, tc.wantGB)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user