diff --git a/cmd/report/dimm.go b/cmd/report/dimm.go index 0d814750..3371997c 100644 --- a/cmd/report/dimm.go +++ b/cmd/report/dimm.go @@ -625,7 +625,11 @@ func deriveDIMMInfoOther(dimms [][]string, channelsPerSocket int) ([]derivedFiel err := fmt.Errorf("no DIMMs") return nil, err } - dimmType, reBankLoc, reLoc := getDIMMParseInfo((dimms)[0][BankLocatorIdx], (dimms)[0][LocatorIdx]) + if len(dimms[0]) <= max(BankLocatorIdx, LocatorIdx) { + err := fmt.Errorf("DIMM data has insufficient fields") + return nil, err + } + dimmType, reBankLoc, reLoc := getDIMMParseInfo(dimms[0][BankLocatorIdx], dimms[0][LocatorIdx]) if dimmType == DIMMTypeUNKNOWN { err := fmt.Errorf("unknown DIMM identification format") return nil, err diff --git a/cmd/report/report.go b/cmd/report/report.go index ff8bd84a..28100255 100644 --- a/cmd/report/report.go +++ b/cmd/report/report.go @@ -383,7 +383,7 @@ func benchmarkSummaryFromTableValues(allTableValues []table.TableValues, outputs memLatTableValues := getTableValues(allTableValues, MemoryBenchmarkTableName) var bandwidthValues []string if len(memLatTableValues.Fields) > 1 { - bandwidthValues = getTableValues(allTableValues, MemoryBenchmarkTableName).Fields[1].Values + bandwidthValues = memLatTableValues.Fields[1].Values } maxBandwidth := 0.0 for _, bandwidthValue := range bandwidthValues { @@ -517,27 +517,28 @@ func summaryHTMLTableRenderer(tv table.TableValues, targetName string) string { panic(err) } // if we have reference data that matches the microarchitecture and sockets, use it - if refData, ok := referenceData[ReferenceDataKey{tv.Fields[uarchFieldIdx].Values[0], tv.Fields[socketsFieldIdx].Values[0]}]; ok { - // remove microarchitecture and sockets fields - fields := tv.Fields[:len(tv.Fields)-2] - refTableValues := table.TableValues{ - Fields: []table.Field{ - {Name: "CPU Speed", Values: []string{fmt.Sprintf("%.0f Ops/s", refData.CPUSpeed)}}, - {Name: "Single-core Maximum frequency", Values: []string{fmt.Sprintf("%.0f MHz", refData.SingleCoreFreq)}}, - {Name: "All-core Maximum frequency", Values: []string{fmt.Sprintf("%.0f MHz", refData.AllCoreFreq)}}, - {Name: "Maximum Power", Values: []string{fmt.Sprintf("%.0f W", refData.MaxPower)}}, - {Name: "Maximum Temperature", Values: []string{fmt.Sprintf("%.0f C", refData.MaxTemp)}}, - {Name: "Minimum Power", Values: []string{fmt.Sprintf("%.0f W", refData.MinPower)}}, - {Name: "Memory Peak Bandwidth", Values: []string{fmt.Sprintf("%.0f GB/s", refData.MemPeakBandwidth)}}, - {Name: "Memory Minimum Latency", Values: []string{fmt.Sprintf("%.0f ns", refData.MemMinLatency)}}, - }, + if len(tv.Fields[uarchFieldIdx].Values) > 0 && len(tv.Fields[socketsFieldIdx].Values) > 0 { + if refData, ok := referenceData[ReferenceDataKey{tv.Fields[uarchFieldIdx].Values[0], tv.Fields[socketsFieldIdx].Values[0]}]; ok { + // remove microarchitecture and sockets fields + fields := tv.Fields[:len(tv.Fields)-2] + refTableValues := table.TableValues{ + Fields: []table.Field{ + {Name: "CPU Speed", Values: []string{fmt.Sprintf("%.0f Ops/s", refData.CPUSpeed)}}, + {Name: "Single-core Maximum frequency", Values: []string{fmt.Sprintf("%.0f MHz", refData.SingleCoreFreq)}}, + {Name: "All-core Maximum frequency", Values: []string{fmt.Sprintf("%.0f MHz", refData.AllCoreFreq)}}, + {Name: "Maximum Power", Values: []string{fmt.Sprintf("%.0f W", refData.MaxPower)}}, + {Name: "Maximum Temperature", Values: []string{fmt.Sprintf("%.0f C", refData.MaxTemp)}}, + {Name: "Minimum Power", Values: []string{fmt.Sprintf("%.0f W", refData.MinPower)}}, + {Name: "Memory Peak Bandwidth", Values: []string{fmt.Sprintf("%.0f GB/s", refData.MemPeakBandwidth)}}, + {Name: "Memory Minimum Latency", Values: []string{fmt.Sprintf("%.0f ns", refData.MemMinLatency)}}, + }, + } + return report.RenderMultiTargetTableValuesAsHTML([]table.TableValues{{TableDefinition: tv.TableDefinition, Fields: fields}, refTableValues}, []string{targetName, refData.Description}) } - return report.RenderMultiTargetTableValuesAsHTML([]table.TableValues{{TableDefinition: tv.TableDefinition, Fields: fields}, refTableValues}, []string{targetName, refData.Description}) - } else { - // remove microarchitecture and sockets fields - fields := tv.Fields[:len(tv.Fields)-2] - return report.DefaultHTMLTableRendererFunc(table.TableValues{TableDefinition: tv.TableDefinition, Fields: fields}) } + // remove microarchitecture and sockets fields + fields := tv.Fields[:len(tv.Fields)-2] + return report.DefaultHTMLTableRendererFunc(table.TableValues{TableDefinition: tv.TableDefinition, Fields: fields}) } func summaryXlsxTableRenderer(tv table.TableValues, f *excelize.File, targetName string, row *int) { diff --git a/cmd/report/report_tables.go b/cmd/report/report_tables.go index 603a923d..4081b86d 100644 --- a/cmd/report/report_tables.go +++ b/cmd/report/report_tables.go @@ -1219,19 +1219,23 @@ func dimmTableInsights(outputs map[string]script.ScriptOutput, tableValues table for i, speed := range tableValues.Fields[SpeedIndex].Values { configuredSpeed := tableValues.Fields[ConfiguredSpeedIndex].Values[i] if speed != "" && configuredSpeed != "" && speed != "Unknown" && configuredSpeed != "Unknown" { - speedVal, err := strconv.Atoi(strings.Split(speed, " ")[0]) - if err != nil { - slog.Warn(err.Error()) - } else { - configuredSpeedVal, err := strconv.Atoi(strings.Split(configuredSpeed, " ")[0]) + speedParts := strings.Split(speed, " ") + configuredSpeedParts := strings.Split(configuredSpeed, " ") + if len(speedParts) > 0 && len(configuredSpeedParts) > 0 { + speedVal, err := strconv.Atoi(speedParts[0]) if err != nil { slog.Warn(err.Error()) } else { - if speedVal < configuredSpeedVal { - insights = append(insights, table.Insight{ - Recommendation: "Consider configuring DIMMs for their maximum speed.", - Justification: fmt.Sprintf("DIMMs configured for %s when their maximum speed is %s.", configuredSpeed, speed), - }) + configuredSpeedVal, err := strconv.Atoi(configuredSpeedParts[0]) + if err != nil { + slog.Warn(err.Error()) + } else { + if speedVal < configuredSpeedVal { + insights = append(insights, table.Insight{ + Recommendation: "Consider configuring DIMMs for their maximum speed.", + Justification: fmt.Sprintf("DIMMs configured for %s when their maximum speed is %s.", configuredSpeed, speed), + }) + } } } } @@ -2033,7 +2037,13 @@ func dimmDetails(dimm []string) (details string) { } func dimmTableHTMLRenderer(tableValues table.TableValues, targetName string) string { - if tableValues.Fields[DerivedSocketIdx].Values[0] == "" || tableValues.Fields[DerivedChannelIdx].Values[0] == "" || tableValues.Fields[DerivedSlotIdx].Values[0] == "" { + 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] == "" { return report.DefaultHTMLTableRendererFunc(tableValues) } htmlColors := []string{"lightgreen", "orange", "aqua", "lime", "yellow", "beige", "magenta", "violet", "salmon", "pink"} diff --git a/cmd/report/system.go b/cmd/report/system.go index a00d2beb..814a6683 100644 --- a/cmd/report/system.go +++ b/cmd/report/system.go @@ -102,7 +102,7 @@ func filesystemFieldValuesFromOutput(outputs map[string]script.ScriptOutput) []t } fields := strings.Fields(line) // "Mounted On" gets split into two fields, rejoin - if i == 0 && fields[len(fields)-2] == "Mounted" && fields[len(fields)-1] == "on" { + if i == 0 && len(fields) >= 2 && fields[len(fields)-2] == "Mounted" && fields[len(fields)-1] == "on" { fields[len(fields)-2] = "Mounted on" fields = fields[:len(fields)-1] for _, field := range fields { @@ -126,7 +126,7 @@ func filesystemFieldValuesFromOutput(outputs map[string]script.ScriptOutput) []t continue } match := reFindmnt.FindStringSubmatch(line) - if match != nil { + if match != nil && len(fields) > 5 { target := match[1] source := match[2] if fields[0] == source && fields[5] == target { diff --git a/internal/common/nic.go b/internal/common/nic.go index 765f3bcc..58ef009d 100644 --- a/internal/common/nic.go +++ b/internal/common/nic.go @@ -114,7 +114,10 @@ func ParseNicInfo(scriptOutput string) []nicInfo { } } // special case for model as it sometimes has additional information in parentheses - nic.Model = strings.TrimSpace(strings.Split(nic.Model, "(")[0]) + modelParts := strings.Split(nic.Model, "(") + if len(modelParts) > 0 { + nic.Model = strings.TrimSpace(modelParts[0]) + } nics = append(nics, nic) } // Assign card and port information @@ -176,7 +179,7 @@ func assignCardAndPort(nics []nicInfo) { } // Further split the last part to separate device from function deviceFunc := strings.Split(parts[2], ".") - if len(deviceFunc) != 2 { + if len(deviceFunc) < 1 { continue } // Card identifier is domain:bus:device diff --git a/internal/common/power.go b/internal/common/power.go index 06f1c73b..cb08093c 100644 --- a/internal/common/power.go +++ b/internal/common/power.go @@ -37,7 +37,11 @@ func EPPFromOutput(outputs map[string]script.ScriptOutput) string { if line == "" { continue } - currentEpbValid := strings.TrimSpace(strings.Split(line, ":")[1]) + lineParts := strings.Split(line, ":") + if len(lineParts) < 2 { + continue + } + currentEpbValid := strings.TrimSpace(lineParts[1]) if i == 0 { eppValid = currentEpbValid continue @@ -53,7 +57,11 @@ func EPPFromOutput(outputs map[string]script.ScriptOutput) string { if line == "" { continue } - currentEppPkgCtrl := strings.TrimSpace(strings.Split(line, ":")[1]) + lineParts := strings.Split(line, ":") + if len(lineParts) < 2 { + continue + } + currentEppPkgCtrl := strings.TrimSpace(lineParts[1]) if i == 0 { eppPkgCtrl = currentEppPkgCtrl continue @@ -77,7 +85,11 @@ func EPPFromOutput(outputs map[string]script.ScriptOutput) string { if line == "" { continue } - currentEpp := strings.TrimSpace(strings.Split(line, ":")[1]) + lineParts := strings.Split(line, ":") + if len(lineParts) < 2 { + continue + } + currentEpp := strings.TrimSpace(lineParts[1]) if i == 0 { epp = currentEpp continue @@ -112,13 +124,7 @@ func EPBFromOutput(outputs map[string]script.ScriptOutput) string { func ELCSummaryFromOutput(outputs map[string]script.ScriptOutput) string { fieldValues := ELCFieldValuesFromOutput(outputs) - if len(fieldValues) == 0 { - return "" - } - if len(fieldValues) < 10 { - return "" - } - if len(fieldValues[9].Values) == 0 { + if len(fieldValues) < 10 || len(fieldValues[9].Values) == 0 { return "" } summary := fieldValues[9].Values[0] @@ -203,7 +209,7 @@ func ELCFieldValuesFromOutput(outputs map[string]script.ScriptOutput) (fieldValu // value rows for _, row := range rows[1:] { var mode string - if row[2] == "IO" { + if len(row) > 7 && row[2] == "IO" { if row[5] == "0" && row[6] == "0" && row[7] == "0" { mode = "Latency Optimized" } else if row[5] == "800" && row[6] == "10" && row[7] == "94" { @@ -211,7 +217,7 @@ func ELCFieldValuesFromOutput(outputs map[string]script.ScriptOutput) (fieldValu } else { mode = "Custom" } - } else { // COMPUTE + } else if len(row) > 5 { // COMPUTE switch row[5] { case "0": mode = "Latency Optimized" diff --git a/internal/report/render_excel.go b/internal/report/render_excel.go index 1d72665f..98bac2a0 100644 --- a/internal/report/render_excel.go +++ b/internal/report/render_excel.go @@ -69,6 +69,9 @@ func renderXlsxTable(tableValues table.TableValues, f *excelize.File, sheetName } func renderXlsxTableMultiTarget(targetTableValues []table.TableValues, targetNames []string, f *excelize.File, sheetName string, row *int) { + if len(targetTableValues) == 0 { + return + } col := 1 // print the table name tableNameStyle, _ := f.NewStyle(&excelize.Style{ @@ -202,7 +205,7 @@ func DefaultXlsxTableRendererFunc(tableValues table.TableValues, f *excelize.Fil col := 1 for _, field := range tableValues.Fields { var fieldValue string - if len(tableValues.Fields[0].Values) > 0 { + if len(field.Values) > 0 { fieldValue = field.Values[0] } _ = f.SetCellValue(sheetName, cellName(col, *row), field.Name) diff --git a/internal/report/render_html.go b/internal/report/render_html.go index 27bd5971..044ed814 100644 --- a/internal/report/render_html.go +++ b/internal/report/render_html.go @@ -303,6 +303,9 @@ func createHtmlReport(allTableValues []table.TableValues, targetName string) (ou } func createHtmlReportMultiTarget(allTargetsTableValues [][]table.TableValues, targetNames []string, allTableNames []string) (out []byte, err error) { + if len(allTargetsTableValues) == 0 { + return nil, fmt.Errorf("no target table values provided") + } var sb strings.Builder sb.WriteString(getHtmlReportBegin()) @@ -593,7 +596,11 @@ func DefaultHTMLTableRendererFunc(tableValues table.TableValues) string { for _, field := range tableValues.Fields { rowValues := []string{} rowValues = append(rowValues, CreateFieldNameWithDescription(field.Name, field.Description)) - rowValues = append(rowValues, htmltemplate.HTMLEscapeString(field.Values[0])) + if len(field.Values) > 0 { + rowValues = append(rowValues, htmltemplate.HTMLEscapeString(field.Values[0])) + } else { + rowValues = append(rowValues, "") + } values = append(values, rowValues) tableValueStyles = append(tableValueStyles, []string{"font-weight:bold"}) } @@ -604,6 +611,9 @@ func DefaultHTMLTableRendererFunc(tableValues table.TableValues) string { // RenderMultiTargetTableValuesAsHTML renders a table for multiple targets // tableValues is a slice of table.TableValues, each of which represents the same table from a single target func RenderMultiTargetTableValuesAsHTML(tableValues []table.TableValues, targetNames []string) string { + if len(tableValues) == 0 { + return "" + } values := [][]string{} var tableValueStyles [][]string for fieldIndex, field := range tableValues[0].Fields {