Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 16 additions & 85 deletions cmd/report/dimm.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,85 +13,16 @@ import (
"strings"
)

const (
BankLocatorIdx = iota
LocatorIdx
ManufacturerIdx
PartIdx
SerialIdx
SizeIdx
TypeIdx
DetailIdx
SpeedIdx
RankIdx
ConfiguredSpeedIdx
DerivedSocketIdx
DerivedChannelIdx
DerivedSlotIdx
)

func dimmInfoFromDmiDecode(dmiDecodeOutput string) [][]string {
return common.ValsArrayFromDmiDecodeRegexSubmatch(
dmiDecodeOutput,
"17",
`^Bank Locator:\s*(.+?)$`,
`^Locator:\s*(.+?)$`,
`^Manufacturer:\s*(.+?)$`,
`^Part Number:\s*(.+?)\s*$`,
`^Serial Number:\s*(.+?)\s*$`,
`^Size:\s*(.+?)$`,
`^Type:\s*(.+?)$`,
`^Type Detail:\s*(.+?)$`,
`^Speed:\s*(.+?)$`,
`^Rank:\s*(.+?)$`,
`^Configured.*Speed:\s*(.+?)$`,
)
}

func installedMemoryFromOutput(outputs map[string]script.ScriptOutput) string {
dimmInfo := dimmInfoFromDmiDecode(outputs[script.DmidecodeScriptName].Stdout)
dimmTypeCount := make(map[string]int)
for _, dimm := range dimmInfo {
dimmKey := dimm[TypeIdx] + ":" + dimm[SizeIdx] + ":" + dimm[SpeedIdx] + ":" + dimm[ConfiguredSpeedIdx]
if count, ok := dimmTypeCount[dimmKey]; ok {
dimmTypeCount[dimmKey] = count + 1
} else {
dimmTypeCount[dimmKey] = 1
}
}
var summaries []string
re := regexp.MustCompile(`(\d+)\s*(\w*)`)
for dimmKey, count := range dimmTypeCount {
fields := strings.Split(dimmKey, ":")
match := re.FindStringSubmatch(fields[1]) // size field
if match != nil {
size, err := strconv.Atoi(match[1])
if err != nil {
slog.Warn("Don't recognize DIMM size format.", slog.String("field", fields[1]))
return ""
}
sum := count * size
unit := match[2]
dimmType := fields[0]
speed := strings.ReplaceAll(fields[2], " ", "")
configuredSpeed := strings.ReplaceAll(fields[3], " ", "")
summary := fmt.Sprintf("%d%s (%dx%d%s %s %s [%s])", sum, unit, count, size, unit, dimmType, speed, configuredSpeed)
summaries = append(summaries, summary)
}
}
return strings.Join(summaries, "; ")
}

func populatedChannelsFromOutput(outputs map[string]script.ScriptOutput) string {
channelsMap := make(map[string]bool)
dimmInfo := dimmInfoFromDmiDecode(outputs[script.DmidecodeScriptName].Stdout)
dimmInfo := common.DimmInfoFromDmiDecode(outputs[script.DmidecodeScriptName].Stdout)
derivedDimmFields := derivedDimmsFieldFromOutput(outputs)
if len(derivedDimmFields) != len(dimmInfo) {
slog.Warn("derivedDimmFields and dimmInfo have different lengths", slog.Int("derivedDimmFields", len(derivedDimmFields)), slog.Int("dimmInfo", len(dimmInfo)))
return ""
}
for i, dimm := range dimmInfo {
if !strings.Contains(dimm[SizeIdx], "No") {
if !strings.Contains(dimm[common.SizeIdx], "No") {
channelsMap[derivedDimmFields[i].socket+","+derivedDimmFields[i].channel] = true
}
}
Expand All @@ -109,7 +40,7 @@ type derivedFields struct {

// derivedDimmsFieldFromOutput returns a slice of derived fields from the output of a script.
func derivedDimmsFieldFromOutput(outputs map[string]script.ScriptOutput) []derivedFields {
dimmInfo := dimmInfoFromDmiDecode(outputs[script.DmidecodeScriptName].Stdout)
dimmInfo := common.DimmInfoFromDmiDecode(outputs[script.DmidecodeScriptName].Stdout)
var derivedFields []derivedFields
var err error
channels := channelsFromOutput(outputs)
Expand Down Expand Up @@ -159,11 +90,11 @@ func deriveDIMMInfoDell(dimms [][]string, channelsPerSocket int) ([]derivedField
derivedFields := make([]derivedFields, len(dimms))
re := regexp.MustCompile(`([ABCD])([1-9]\d*)`)
for i, dimm := range dimms {
if !strings.Contains(dimm[BankLocatorIdx], "Not Specified") {
if !strings.Contains(dimm[common.BankLocatorIdx], "Not Specified") {
err := fmt.Errorf("doesn't conform to expected Dell Bank Locator format")
return nil, err
}
match := re.FindStringSubmatch(dimm[LocatorIdx])
match := re.FindStringSubmatch(dimm[common.LocatorIdx])
if match == nil {
err := fmt.Errorf("doesn't conform to expected Dell Locator format")
return nil, err
Expand Down Expand Up @@ -223,8 +154,8 @@ func deriveDIMMInfoEC2(dimms [][]string, channelsPerSocket int) ([]derivedFields
c6ilocRe := regexp.MustCompile(`CPU(\d+)\s+Channel(\d+)\s+DIMM(\d+)`)
for i, dimm := range dimms {
// try c5.metal format
bankLocMatch := c5bankLocRe.FindStringSubmatch(dimm[BankLocatorIdx])
locMatch := c5locRe.FindStringSubmatch(dimm[LocatorIdx])
bankLocMatch := c5bankLocRe.FindStringSubmatch(dimm[common.BankLocatorIdx])
locMatch := c5locRe.FindStringSubmatch(dimm[common.LocatorIdx])
if locMatch != nil && bankLocMatch != nil {
var socket, channel, slot int
socket, _ = strconv.Atoi(bankLocMatch[1])
Expand All @@ -244,8 +175,8 @@ func deriveDIMMInfoEC2(dimms [][]string, channelsPerSocket int) ([]derivedFields
continue
}
// try c6i.metal format
bankLocMatch = c6ibankLocRe.FindStringSubmatch(dimm[BankLocatorIdx])
locMatch = c6ilocRe.FindStringSubmatch(dimm[LocatorIdx])
bankLocMatch = c6ibankLocRe.FindStringSubmatch(dimm[common.BankLocatorIdx])
locMatch = c6ilocRe.FindStringSubmatch(dimm[common.LocatorIdx])
if locMatch != nil && bankLocMatch != nil {
var socket, channel, slot int
socket, _ = strconv.Atoi(locMatch[1])
Expand All @@ -271,13 +202,13 @@ func deriveDIMMInfoHPE(dimms [][]string, numSockets int, channelsPerSocket int)
slotsPerChannel := len(dimms) / (numSockets * channelsPerSocket)
re := regexp.MustCompile(`PROC ([1-9]\d*) DIMM ([1-9]\d*)`)
for i, dimm := range dimms {
if !strings.Contains(dimm[BankLocatorIdx], "Not Specified") {
err := fmt.Errorf("doesn't conform to expected HPE Bank Locator format: %s", dimm[BankLocatorIdx])
if !strings.Contains(dimm[common.BankLocatorIdx], "Not Specified") {
err := fmt.Errorf("doesn't conform to expected HPE Bank Locator format: %s", dimm[common.BankLocatorIdx])
return nil, err
}
match := re.FindStringSubmatch(dimm[LocatorIdx])
match := re.FindStringSubmatch(dimm[common.LocatorIdx])
if match == nil {
err := fmt.Errorf("doesn't conform to expected HPE Locator format: %s", dimm[LocatorIdx])
err := fmt.Errorf("doesn't conform to expected HPE Locator format: %s", dimm[common.LocatorIdx])
return nil, err
}
socket, err := strconv.Atoi(match[1])
Expand Down Expand Up @@ -625,18 +556,18 @@ func deriveDIMMInfoOther(dimms [][]string, channelsPerSocket int) ([]derivedFiel
err := fmt.Errorf("no DIMMs")
return nil, err
}
if len(dimms[0]) <= max(BankLocatorIdx, LocatorIdx) {
if len(dimms[0]) <= max(common.BankLocatorIdx, common.LocatorIdx) {
err := fmt.Errorf("DIMM data has insufficient fields")
return nil, err
}
dimmType, reBankLoc, reLoc := getDIMMParseInfo(dimms[0][BankLocatorIdx], dimms[0][LocatorIdx])
dimmType, reBankLoc, reLoc := getDIMMParseInfo(dimms[0][common.BankLocatorIdx], dimms[0][common.LocatorIdx])
if dimmType == DIMMTypeUNKNOWN {
err := fmt.Errorf("unknown DIMM identification format")
return nil, err
}
for i, dimm := range dimms {
var socket, slot int
socket, slot, err := getDIMMSocketSlot(dimmType, reBankLoc, reLoc, dimm[BankLocatorIdx], dimm[LocatorIdx])
socket, slot, err := getDIMMSocketSlot(dimmType, reBankLoc, reLoc, dimm[common.BankLocatorIdx], dimm[common.LocatorIdx])
if err != nil {
slog.Info("Couldn't extract socket and slot from DIMM info", slog.String("error", err.Error()))
return nil, nil
Expand Down
56 changes: 28 additions & 28 deletions cmd/report/report_tables.go
Original file line number Diff line number Diff line change
Expand Up @@ -994,7 +994,7 @@ func sstTFLPTableValues(outputs map[string]script.ScriptOutput) []table.Field {

func memoryTableValues(outputs map[string]script.ScriptOutput) []table.Field {
return []table.Field{
{Name: "Installed Memory", Values: []string{installedMemoryFromOutput(outputs)}},
{Name: "Installed Memory", Values: []string{common.InstalledMemoryFromOutput(outputs)}},
{Name: "MemTotal", Values: []string{common.ValFromRegexSubmatch(outputs[script.MeminfoScriptName].Stdout, `^MemTotal:\s*(.+?)$`)}},
{Name: "MemFree", Values: []string{common.ValFromRegexSubmatch(outputs[script.MeminfoScriptName].Stdout, `^MemFree:\s*(.+?)$`)}},
{Name: "MemAvailable", Values: []string{common.ValFromRegexSubmatch(outputs[script.MeminfoScriptName].Stdout, `^MemAvailable:\s*(.+?)$`)}},
Expand Down Expand Up @@ -1670,7 +1670,7 @@ func systemSummaryTableValues(outputs map[string]script.ScriptOutput) []table.Fi
{Name: "Prefetchers", Values: []string{common.PrefetchersSummaryFromOutput(outputs)}},
{Name: "PPINs", Values: []string{ppinsFromOutput(outputs)}},
{Name: "Accelerators Available [used]", Values: []string{acceleratorSummaryFromOutput(outputs)}},
{Name: "Installed Memory", Values: []string{installedMemoryFromOutput(outputs)}},
{Name: "Installed Memory", Values: []string{common.InstalledMemoryFromOutput(outputs)}},
{Name: "Hugepagesize", Values: []string{common.ValFromRegexSubmatch(outputs[script.MeminfoScriptName].Stdout, `^Hugepagesize:\s*(.+?)$`)}},
{Name: "Transparent Huge Pages", Values: []string{common.ValFromRegexSubmatch(outputs[script.TransparentHugePagesScriptName].Stdout, `.*\[(.*)\].*`)}},
{Name: "Automatic NUMA Balancing", Values: []string{numaBalancingFromOutput(outputs)}},
Expand All @@ -1691,57 +1691,57 @@ func systemSummaryTableValues(outputs map[string]script.ScriptOutput) []table.Fi
}
}
func dimmDetails(dimm []string) (details string) {
if strings.Contains(dimm[SizeIdx], "No") {
if strings.Contains(dimm[common.SizeIdx], "No") {
details = "No Module Installed"
} else {
// Intel PMEM modules may have serial number appended to end of part number...
// strip that off so it doesn't mess with color selection later
partNumber := dimm[PartIdx]
if strings.Contains(dimm[DetailIdx], "Synchronous Non-Volatile") &&
dimm[ManufacturerIdx] == "Intel" &&
strings.HasSuffix(dimm[PartIdx], dimm[SerialIdx]) {
partNumber = dimm[PartIdx][:len(dimm[PartIdx])-len(dimm[SerialIdx])]
partNumber := dimm[common.PartIdx]
if strings.Contains(dimm[common.DetailIdx], "Synchronous Non-Volatile") &&
dimm[common.ManufacturerIdx] == "Intel" &&
strings.HasSuffix(dimm[common.PartIdx], dimm[common.SerialIdx]) {
partNumber = dimm[common.PartIdx][:len(dimm[common.PartIdx])-len(dimm[common.SerialIdx])]
}
// example: "64GB DDR5 R2 Synchronous Registered (Buffered) Micron Technology MTC78ASF4G72PZ-2G6E1 6400 MT/s [6000 MT/s]"
details = fmt.Sprintf("%s %s %s R%s %s %s %s [%s]",
strings.ReplaceAll(dimm[SizeIdx], " ", ""),
dimm[TypeIdx],
dimm[DetailIdx],
dimm[RankIdx],
dimm[ManufacturerIdx],
strings.ReplaceAll(dimm[common.SizeIdx], " ", ""),
dimm[common.TypeIdx],
dimm[common.DetailIdx],
dimm[common.RankIdx],
dimm[common.ManufacturerIdx],
partNumber,
strings.ReplaceAll(dimm[SpeedIdx], " ", ""),
strings.ReplaceAll(dimm[ConfiguredSpeedIdx], " ", ""))
strings.ReplaceAll(dimm[common.SpeedIdx], " ", ""),
strings.ReplaceAll(dimm[common.ConfiguredSpeedIdx], " ", ""))
}
return
}

func dimmTableHTMLRenderer(tableValues table.TableValues, targetName string) string {
if len(tableValues.Fields) <= max(DerivedSocketIdx, DerivedChannelIdx, DerivedSlotIdx) ||
len(tableValues.Fields[DerivedSocketIdx].Values) == 0 ||
len(tableValues.Fields[DerivedChannelIdx].Values) == 0 ||
len(tableValues.Fields[DerivedSlotIdx].Values) == 0 ||
tableValues.Fields[DerivedSocketIdx].Values[0] == "" ||
tableValues.Fields[DerivedChannelIdx].Values[0] == "" ||
tableValues.Fields[DerivedSlotIdx].Values[0] == "" {
if len(tableValues.Fields) <= max(common.DerivedSocketIdx, common.DerivedChannelIdx, common.DerivedSlotIdx) ||
len(tableValues.Fields[common.DerivedSocketIdx].Values) == 0 ||
len(tableValues.Fields[common.DerivedChannelIdx].Values) == 0 ||
len(tableValues.Fields[common.DerivedSlotIdx].Values) == 0 ||
tableValues.Fields[common.DerivedSocketIdx].Values[0] == "" ||
tableValues.Fields[common.DerivedChannelIdx].Values[0] == "" ||
tableValues.Fields[common.DerivedSlotIdx].Values[0] == "" {
return report.DefaultHTMLTableRendererFunc(tableValues)
}
htmlColors := []string{"lightgreen", "orange", "aqua", "lime", "yellow", "beige", "magenta", "violet", "salmon", "pink"}
var slotColorIndices = make(map[string]int)
// socket -> channel -> slot -> dimm details
var dimms = map[string]map[string]map[string]string{}
for dimmIdx := range tableValues.Fields[DerivedSocketIdx].Values {
if _, ok := dimms[tableValues.Fields[DerivedSocketIdx].Values[dimmIdx]]; !ok {
dimms[tableValues.Fields[DerivedSocketIdx].Values[dimmIdx]] = make(map[string]map[string]string)
for dimmIdx := range tableValues.Fields[common.DerivedSocketIdx].Values {
if _, ok := dimms[tableValues.Fields[common.DerivedSocketIdx].Values[dimmIdx]]; !ok {
dimms[tableValues.Fields[common.DerivedSocketIdx].Values[dimmIdx]] = make(map[string]map[string]string)
}
if _, ok := dimms[tableValues.Fields[DerivedSocketIdx].Values[dimmIdx]][tableValues.Fields[DerivedChannelIdx].Values[dimmIdx]]; !ok {
dimms[tableValues.Fields[DerivedSocketIdx].Values[dimmIdx]][tableValues.Fields[DerivedChannelIdx].Values[dimmIdx]] = make(map[string]string)
if _, ok := dimms[tableValues.Fields[common.DerivedSocketIdx].Values[dimmIdx]][tableValues.Fields[common.DerivedChannelIdx].Values[dimmIdx]]; !ok {
dimms[tableValues.Fields[common.DerivedSocketIdx].Values[dimmIdx]][tableValues.Fields[common.DerivedChannelIdx].Values[dimmIdx]] = make(map[string]string)
}
dimmValues := []string{}
for _, field := range tableValues.Fields {
dimmValues = append(dimmValues, field.Values[dimmIdx])
}
dimms[tableValues.Fields[DerivedSocketIdx].Values[dimmIdx]][tableValues.Fields[DerivedChannelIdx].Values[dimmIdx]][tableValues.Fields[DerivedSlotIdx].Values[dimmIdx]] = dimmDetails(dimmValues)
dimms[tableValues.Fields[common.DerivedSocketIdx].Values[dimmIdx]][tableValues.Fields[common.DerivedChannelIdx].Values[dimmIdx]][tableValues.Fields[common.DerivedSlotIdx].Values[dimmIdx]] = dimmDetails(dimmValues)
}

var socketTableHeaders = []string{"Socket", ""}
Expand Down
2 changes: 1 addition & 1 deletion cmd/report/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func systemSummaryFromOutput(outputs map[string]script.ScriptOutput) string {
turboOnOff = "?"
}
// memory
installedMem = installedMemoryFromOutput(outputs)
installedMem = common.InstalledMemoryFromOutput(outputs)
// BIOS
biosVersion = common.ValFromRegexSubmatch(outputs[script.DmidecodeScriptName].Stdout, `^Version:\s*(.+?)$`)
// microcode
Expand Down
7 changes: 6 additions & 1 deletion internal/common/table_defs.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,16 @@ var TableDefinitions = map[string]table.TableDefinition{
script.ArmImplementerScriptName,
script.ArmPartScriptName,
script.ArmDmidecodePartScriptName,
script.DmidecodeScriptName,
},
FieldsFunc: briefSummaryTableValues},
}

func briefSummaryTableValues(outputs map[string]script.ScriptOutput) []table.Field {
memory := InstalledMemoryFromOutput(outputs) // Dmidecode, try this first
if memory == "" {
memory = ValFromRegexSubmatch(outputs[script.MeminfoScriptName].Stdout, `^MemTotal:\s*(.+?)$`) // Meminfo as fallback
}
return []table.Field{
{Name: "Host Name", Values: []string{strings.TrimSpace(outputs[script.HostnameScriptName].Stdout)}}, // Hostname
{Name: "Time", Values: []string{strings.TrimSpace(outputs[script.DateScriptName].Stdout)}}, // Date
Expand All @@ -61,7 +66,7 @@ func briefSummaryTableValues(outputs map[string]script.ScriptOutput) []table.Fie
{Name: "All-core Maximum Frequency", Values: []string{AllCoreMaxFrequencyFromOutput(outputs)}, Description: "The highest speed all cores can reach simultaneously with Turbo Boost."}, // Lscpu, LspciBits, LspciDevices, SpecCoreFrequencies
{Name: "Energy Performance Bias", Values: []string{EPBFromOutput(outputs)}}, // EpbSource, EpbBIOS, EpbOS
{Name: "Efficiency Latency Control", Values: []string{ELCSummaryFromOutput(outputs)}}, // Elc
{Name: "MemTotal", Values: []string{ValFromRegexSubmatch(outputs[script.MeminfoScriptName].Stdout, `^MemTotal:\s*(.+?)$`)}}, // Meminfo
{Name: "Memory", Values: []string{memory}}, // Dmidecode,Meminfo
{Name: "NIC", Values: []string{NICSummaryFromOutput(outputs)}}, // Lshw, NicInfo
{Name: "Disk", Values: []string{DiskSummaryFromOutput(outputs)}}, // DiskInfo, Hdparm
{Name: "OS", Values: []string{OperatingSystemFromOutput(outputs)}}, // EtcRelease
Expand Down
69 changes: 69 additions & 0 deletions internal/common/table_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,3 +289,72 @@ func TDPFromOutput(outputs map[string]script.ScriptOutput) string {
}
return fmt.Sprint(msr/8) + "W"
}

const (
BankLocatorIdx = iota
LocatorIdx
ManufacturerIdx
PartIdx
SerialIdx
SizeIdx
TypeIdx
DetailIdx
SpeedIdx
RankIdx
ConfiguredSpeedIdx
DerivedSocketIdx
DerivedChannelIdx
DerivedSlotIdx
)

func DimmInfoFromDmiDecode(dmiDecodeOutput string) [][]string {
return ValsArrayFromDmiDecodeRegexSubmatch(
dmiDecodeOutput,
"17",
`^Bank Locator:\s*(.+?)$`,
`^Locator:\s*(.+?)$`,
`^Manufacturer:\s*(.+?)$`,
`^Part Number:\s*(.+?)\s*$`,
`^Serial Number:\s*(.+?)\s*$`,
`^Size:\s*(.+?)$`,
`^Type:\s*(.+?)$`,
`^Type Detail:\s*(.+?)$`,
`^Speed:\s*(.+?)$`,
`^Rank:\s*(.+?)$`,
`^Configured.*Speed:\s*(.+?)$`,
)
}

func InstalledMemoryFromOutput(outputs map[string]script.ScriptOutput) string {
dimmInfo := DimmInfoFromDmiDecode(outputs[script.DmidecodeScriptName].Stdout)
dimmTypeCount := make(map[string]int)
for _, dimm := range dimmInfo {
dimmKey := dimm[TypeIdx] + ":" + dimm[SizeIdx] + ":" + dimm[SpeedIdx] + ":" + dimm[ConfiguredSpeedIdx]
if count, ok := dimmTypeCount[dimmKey]; ok {
dimmTypeCount[dimmKey] = count + 1
} else {
dimmTypeCount[dimmKey] = 1
}
}
var summaries []string
re := regexp.MustCompile(`(\d+)\s*(\w*)`)
for dimmKey, count := range dimmTypeCount {
fields := strings.Split(dimmKey, ":")
match := re.FindStringSubmatch(fields[1]) // size field
if match != nil {
size, err := strconv.Atoi(match[1])
if err != nil {
slog.Warn("Don't recognize DIMM size format.", slog.String("field", fields[1]))
return ""
}
sum := count * size
unit := match[2]
dimmType := fields[0]
speed := strings.ReplaceAll(fields[2], " ", "")
configuredSpeed := strings.ReplaceAll(fields[3], " ", "")
summary := fmt.Sprintf("%d%s (%dx%d%s %s %s [%s])", sum, unit, count, size, unit, dimmType, speed, configuredSpeed)
summaries = append(summaries, summary)
}
}
return strings.Join(summaries, "; ")
}