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
2 changes: 1 addition & 1 deletion cmd/report/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ var categories = []common.Category{
{FlagName: flagElcName, FlagVar: &flagElc, Help: "Efficiency Latency Control Settings", TableNames: []string{report.ElcTableName}},
{FlagName: flagMemoryName, FlagVar: &flagMemory, Help: "Memory Configuration", TableNames: []string{report.MemoryTableName}},
{FlagName: flagDimmName, FlagVar: &flagDimm, Help: "DIMM Population", TableNames: []string{report.DIMMTableName}},
{FlagName: flagNicName, FlagVar: &flagNic, Help: "Network Cards", TableNames: []string{report.NICTableName}},
{FlagName: flagNicName, FlagVar: &flagNic, Help: "Network Cards", TableNames: []string{report.NICTableName, report.NICPacketSteeringTableName}},
{FlagName: flagNetConfigName, FlagVar: &flagNetConfig, Help: "Network Configuration", TableNames: []string{report.NetworkConfigTableName}},
{FlagName: flagNetIrqName, FlagVar: &flagNetIrq, Help: "Network IRQ to CPU Mapping", TableNames: []string{report.NetworkIRQMappingTableName}},
{FlagName: flagDiskName, FlagVar: &flagDisk, Help: "Storage Devices", TableNames: []string{report.DiskTableName}},
Expand Down
87 changes: 82 additions & 5 deletions internal/report/table_defs.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"log/slog"
"regexp"
"slices"
"sort"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -94,6 +95,7 @@ const (
NICTableName = "NIC"
NetworkIRQMappingTableName = "Network IRQ Mapping"
NetworkConfigTableName = "Network Configuration"
NICPacketSteeringTableName = "NIC Packet Steering"
DiskTableName = "Disk"
FilesystemTableName = "Filesystem"
GPUTableName = "GPU"
Expand Down Expand Up @@ -380,6 +382,13 @@ var tableDefinitions = map[string]TableDefinition{
script.SysctlScriptName,
},
FieldsFunc: networkConfigTableValues},
NICPacketSteeringTableName: {
Name: NICPacketSteeringTableName,
HasRows: true,
ScriptNames: []string{
script.NicInfoScriptName,
},
FieldsFunc: nicPacketSteeringTableValues},
NetworkIRQMappingTableName: {
Name: NetworkIRQMappingTableName,
HasRows: true,
Expand Down Expand Up @@ -1625,6 +1634,9 @@ func nicTableValues(outputs map[string]script.ScriptOutput) []Field {
{Name: "Driver"},
{Name: "Driver Version"},
{Name: "Firmware Version"},
{Name: "MTU", Description: "Maximum Transmission Unit. The largest size packet or frame, specified in octets (eight-bit bytes), that can be sent in a packet- or frame-based network such as the Internet."},
{Name: "TX Queues"},
{Name: "RX Queues"},
{Name: "IRQBalance", Description: "System level setting. Dynamically monitors system activity and spreads IRQs across available cores, aiming to balance CPU load, improve throughput, and reduce latency for interrupt-heavy workloads."},
{Name: "Adaptive RX", Description: "Enables dynamic adjustment of receive interrupt coalescing based on traffic patterns."},
{Name: "Adaptive TX", Description: "Enables dynamic adjustment of transmit interrupt coalescing based on traffic patterns."},
Expand Down Expand Up @@ -1660,15 +1672,80 @@ func nicTableValues(outputs map[string]script.ScriptOutput) []Field {
fields[9].Values = append(fields[9].Values, nicInfo.Driver)
fields[10].Values = append(fields[10].Values, nicInfo.DriverVersion)
fields[11].Values = append(fields[11].Values, nicInfo.FirmwareVersion)
fields[12].Values = append(fields[12].Values, nicInfo.IRQBalance)
fields[13].Values = append(fields[13].Values, nicInfo.AdaptiveRX)
fields[14].Values = append(fields[14].Values, nicInfo.AdaptiveTX)
fields[15].Values = append(fields[15].Values, nicInfo.RxUsecs)
fields[16].Values = append(fields[16].Values, nicInfo.TxUsecs)
fields[12].Values = append(fields[12].Values, nicInfo.MTU)
fields[13].Values = append(fields[13].Values, nicInfo.TXQueues)
fields[14].Values = append(fields[14].Values, nicInfo.RXQueues)
fields[15].Values = append(fields[15].Values, nicInfo.IRQBalance)
fields[16].Values = append(fields[16].Values, nicInfo.AdaptiveRX)
fields[17].Values = append(fields[17].Values, nicInfo.AdaptiveTX)
fields[18].Values = append(fields[18].Values, nicInfo.RxUsecs)
fields[19].Values = append(fields[19].Values, nicInfo.TxUsecs)
}
return fields
}

func nicPacketSteeringTableValues(outputs map[string]script.ScriptOutput) []Field {
allNicsInfo := parseNicInfo(outputs[script.NicInfoScriptName].Stdout)
if len(allNicsInfo) == 0 {
return []Field{}
}

fields := []Field{
{Name: "Interface"},
{Name: "Type", Description: "XPS (Transmit Packet Steering) and RPS (Receive Packet Steering) are software-based mechanisms that allow the selection of a specific logical CPU core to handle the transmission or processing of network packets for a given queue."},
{Name: "Queue:CPU(s) | Queue|CPU(s) | ..."},
}

for _, nicInfo := range allNicsInfo {
// XPS row
if nicInfo.TXQueues != "0" {
fields[0].Values = append(fields[0].Values, nicInfo.Name)
fields[1].Values = append(fields[1].Values, "xps_cpus")
fields[2].Values = append(fields[2].Values, formatQueueCPUMappings(nicInfo.XPSCPUs, "tx-"))
}

// RPS row
if nicInfo.RXQueues != "0" {
fields[0].Values = append(fields[0].Values, nicInfo.Name)
fields[1].Values = append(fields[1].Values, "rps_cpus")
fields[2].Values = append(fields[2].Values, formatQueueCPUMappings(nicInfo.RPSCPUs, "rx-"))
}
}

if len(fields[0].Values) == 0 {
return []Field{}
}
return fields
}

func formatQueueCPUMappings(mappings map[string]string, prefix string) string {
var queueMappings []string

// Extract and sort queue numbers to ensure consistent output
var queues []int
for queueStr := range mappings {
queueNum, err := strconv.Atoi(strings.TrimPrefix(queueStr, prefix))
if err == nil {
queues = append(queues, queueNum)
}
}
sort.Ints(queues)

for _, queueNum := range queues {
queueStr := fmt.Sprintf("%s%d", prefix, queueNum)
cpus := mappings[queueStr]
// a nil value can be returned from the map if the key does not exist, so check for that
if cpus != "" {
queueMappings = append(queueMappings, fmt.Sprintf("%d:%s", queueNum, cpus))
}
}

if len(queueMappings) == 0 {
return "N/A"
}
return strings.Join(queueMappings, " | ")
}

func networkIRQMappingTableValues(outputs map[string]script.ScriptOutput) []Field {
nicIRQMappings := nicIRQMappingsFromOutput(outputs)
if len(nicIRQMappings) == 0 {
Expand Down
57 changes: 57 additions & 0 deletions internal/report/table_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"encoding/csv"
"fmt"
"log/slog"
"math/big"
"regexp"
"sort"
"strconv"
Expand Down Expand Up @@ -1260,7 +1261,12 @@ type nicInfo struct {
TxUsecs string
Card string
Port string
MTU string
IsVirtual bool
TXQueues string
RXQueues string
XPSCPUs map[string]string
RPSCPUs map[string]string
}

func parseNicInfo(scriptOutput string) []nicInfo {
Expand All @@ -1270,6 +1276,8 @@ func parseNicInfo(scriptOutput string) []nicInfo {
continue
}
var nic nicInfo
nic.XPSCPUs = make(map[string]string)
nic.RPSCPUs = make(map[string]string)
// Map of prefixes to field pointers
fieldMap := map[string]*string{
"Interface: ": &nic.Name,
Expand All @@ -1289,6 +1297,9 @@ func parseNicInfo(scriptOutput string) []nicInfo {
"IRQ Balance: ": &nic.IRQBalance,
"rx-usecs: ": &nic.RxUsecs,
"tx-usecs: ": &nic.TxUsecs,
"MTU: ": &nic.MTU,
"TX Queues: ": &nic.TXQueues,
"RX Queues: ": &nic.RXQueues,
}
for line := range strings.SplitSeq(nicOutput, "\n") {
line = strings.TrimSpace(line)
Expand All @@ -1306,6 +1317,23 @@ func parseNicInfo(scriptOutput string) []nicInfo {
nic.IsVirtual = (strings.TrimSpace(value) == "yes")
continue
}
// Special parsing for xps_cpus and rps_cpus
if strings.HasPrefix(line, "xps_cpus tx-") {
parts := strings.SplitN(line, ": ", 2)
if len(parts) == 2 {
queue := strings.TrimPrefix(parts[0], "xps_cpus ")
nic.XPSCPUs[queue] = hexBitmapToCPUList(parts[1])
}
continue
}
if strings.HasPrefix(line, "rps_cpus rx-") {
parts := strings.SplitN(line, ": ", 2)
if len(parts) == 2 {
queue := strings.TrimPrefix(parts[0], "rps_cpus ")
nic.RPSCPUs[queue] = hexBitmapToCPUList(parts[1])
}
continue
}
for prefix, fieldPtr := range fieldMap {
if after, ok := strings.CutPrefix(line, prefix); ok {
*fieldPtr = after
Expand All @@ -1322,6 +1350,35 @@ func parseNicInfo(scriptOutput string) []nicInfo {
return nics
}

func hexBitmapToCPUList(hexBitmap string) string {
if hexBitmap == "" {
return ""
}

// Remove commas to form a single continuous hex string.
// This assumes the comma-separated parts are in big-endian order.
fullHexBitmap := strings.ReplaceAll(hexBitmap, ",", "")

i := new(big.Int)
// The string is a hex string, so the base is 16.
if _, success := i.SetString(fullHexBitmap, 16); !success {
// If parsing fails, it might not be a hex string. Return as is.
return hexBitmap
}

var cpus []string
// Iterate through the bits of the big integer.
for bit := 0; bit < i.BitLen(); bit++ {
if i.Bit(bit) == 1 {
cpus = append(cpus, fmt.Sprintf("%d", bit))
}
}
if len(cpus) == 0 {
return ""
}
return strings.Join(cpus, ",")
}

// assignCardAndPort assigns card and port numbers to NICs based on their PCI addresses
func assignCardAndPort(nics []nicInfo) {
if len(nics) == 0 {
Expand Down
13 changes: 13 additions & 0 deletions internal/script/script_defs.go
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,7 @@ rdmsr 0x2FFE
echo "Model ID: $(echo "$udevadm_out" | grep ID_MODEL_ID= | cut -d'=' -f2)"
echo "Vendor: $(echo "$udevadm_out" | grep ID_VENDOR_FROM_DATABASE= | cut -d'=' -f2)"
echo "Model: $(echo "$udevadm_out" | grep ID_MODEL_FROM_DATABASE= | cut -d'=' -f2)"
echo "MTU: $(cat /sys/class/net/"$ifc"/mtu 2>/dev/null)"
echo "$ethtool_out"
echo "$ethtool_i_out"
if ethtool_c_out=$(ethtool -c "$ifc" 2>/dev/null); then
Expand All @@ -779,6 +780,18 @@ rdmsr 0x2FFE
done
printf "\n"
echo "IRQ Balance: $(pgrep irqbalance >/dev/null 2>&1 && echo "Enabled" || echo "Disabled")"
echo "TX Queues: $(ls -d /sys/class/net/"$ifc"/queues/tx-* | wc -l)"
echo "RX Queues: $(ls -d /sys/class/net/"$ifc"/queues/rx-* | wc -l)"
for q in /sys/class/net/"$ifc"/queues/tx-*; do
if [ -f "$q/xps_cpus" ]; then
echo "xps_cpus $(basename "$q"): $(cat "$q/xps_cpus")"
fi
done
for q in /sys/class/net/"$ifc"/queues/rx-*; do
if [ -f "$q/rps_cpus" ]; then
echo "rps_cpus $(basename "$q"): $(cat "$q/rps_cpus")"
fi
done
echo "----------------------------------------"
done
`,
Expand Down