Skip to content

Commit 31ebe19

Browse files
committed
feat: Add NIC queue counts and packet steering info
1 parent ae153a9 commit 31ebe19

File tree

3 files changed

+140
-5
lines changed

3 files changed

+140
-5
lines changed

internal/report/table_defs.go

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ const (
9494
NICTableName = "NIC"
9595
NetworkIRQMappingTableName = "Network IRQ Mapping"
9696
NetworkConfigTableName = "Network Configuration"
97+
NICPacketSteeringTableName = "NIC Packet Steering"
9798
DiskTableName = "Disk"
9899
FilesystemTableName = "Filesystem"
99100
GPUTableName = "GPU"
@@ -380,6 +381,13 @@ var tableDefinitions = map[string]TableDefinition{
380381
script.SysctlScriptName,
381382
},
382383
FieldsFunc: networkConfigTableValues},
384+
NICPacketSteeringTableName: {
385+
Name: NICPacketSteeringTableName,
386+
HasRows: true,
387+
ScriptNames: []string{
388+
script.NicInfoScriptName,
389+
},
390+
FieldsFunc: nicPacketSteeringTableValues},
383391
NetworkIRQMappingTableName: {
384392
Name: NetworkIRQMappingTableName,
385393
HasRows: true,
@@ -1626,6 +1634,8 @@ func nicTableValues(outputs map[string]script.ScriptOutput) []Field {
16261634
{Name: "Driver Version"},
16271635
{Name: "Firmware Version"},
16281636
{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."},
1637+
{Name: "TX Queues"},
1638+
{Name: "RX Queues"},
16291639
{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."},
16301640
{Name: "Adaptive RX", Description: "Enables dynamic adjustment of receive interrupt coalescing based on traffic patterns."},
16311641
{Name: "Adaptive TX", Description: "Enables dynamic adjustment of transmit interrupt coalescing based on traffic patterns."},
@@ -1662,11 +1672,80 @@ func nicTableValues(outputs map[string]script.ScriptOutput) []Field {
16621672
fields[10].Values = append(fields[10].Values, nicInfo.DriverVersion)
16631673
fields[11].Values = append(fields[11].Values, nicInfo.FirmwareVersion)
16641674
fields[12].Values = append(fields[12].Values, nicInfo.MTU)
1665-
fields[13].Values = append(fields[13].Values, nicInfo.IRQBalance)
1666-
fields[14].Values = append(fields[14].Values, nicInfo.AdaptiveRX)
1667-
fields[15].Values = append(fields[15].Values, nicInfo.AdaptiveTX)
1668-
fields[16].Values = append(fields[16].Values, nicInfo.RxUsecs)
1669-
fields[17].Values = append(fields[17].Values, nicInfo.TxUsecs)
1675+
fields[13].Values = append(fields[13].Values, nicInfo.TXQueues)
1676+
fields[14].Values = append(fields[14].Values, nicInfo.RXQueues)
1677+
fields[15].Values = append(fields[15].Values, nicInfo.IRQBalance)
1678+
fields[16].Values = append(fields[16].Values, nicInfo.AdaptiveRX)
1679+
fields[17].Values = append(fields[17].Values, nicInfo.AdaptiveTX)
1680+
fields[18].Values = append(fields[18].Values, nicInfo.RxUsecs)
1681+
fields[19].Values = append(fields[19].Values, nicInfo.TxUsecs)
1682+
}
1683+
return fields
1684+
}
1685+
1686+
func nicPacketSteeringTableValues(outputs map[string]script.ScriptOutput) []Field {
1687+
allNicsInfo := parseNicInfo(outputs[script.NicInfoScriptName].Stdout)
1688+
if len(allNicsInfo) == 0 {
1689+
return []Field{}
1690+
}
1691+
1692+
// Find the maximum number of queues across all NICs for both TX and RX
1693+
maxNumQueues := 0
1694+
for _, nicInfo := range allNicsInfo {
1695+
txq, err := strconv.Atoi(nicInfo.TXQueues)
1696+
if err == nil && txq > maxNumQueues {
1697+
maxNumQueues = txq
1698+
}
1699+
rxq, err := strconv.Atoi(nicInfo.RXQueues)
1700+
if err == nil && rxq > maxNumQueues {
1701+
maxNumQueues = rxq
1702+
}
1703+
}
1704+
1705+
fields := []Field{
1706+
{Name: "Interface"},
1707+
{Name: "Type", Description: "XPS (Transmit Packet Steering) and RPS (Receive Packet Steering) are software-based mechanisms that allow the selection of a specific CPU core to handle the transmission or processing of network packets for a given queue."},
1708+
}
1709+
for i := 0; i < maxNumQueues; i++ {
1710+
fields = append(fields, Field{Name: strconv.Itoa(i)})
1711+
}
1712+
1713+
for _, nicInfo := range allNicsInfo {
1714+
// XPS row
1715+
if nicInfo.TXQueues != "0" && len(nicInfo.XPSCPUs) > 0 {
1716+
xpsValues := make([]string, maxNumQueues)
1717+
for queue, val := range nicInfo.XPSCPUs {
1718+
queueNum, err := strconv.Atoi(strings.TrimPrefix(queue, "tx-"))
1719+
if err == nil && queueNum < maxNumQueues {
1720+
xpsValues[queueNum] = hexBitmapToCPUList(val)
1721+
}
1722+
}
1723+
fields[0].Values = append(fields[0].Values, nicInfo.Name)
1724+
fields[1].Values = append(fields[1].Values, "xps_cpus")
1725+
for i := 0; i < maxNumQueues; i++ {
1726+
fields[i+2].Values = append(fields[i+2].Values, xpsValues[i])
1727+
}
1728+
}
1729+
1730+
// RPS row
1731+
if nicInfo.RXQueues != "0" && len(nicInfo.RPSCPUs) > 0 {
1732+
rpsValues := make([]string, maxNumQueues)
1733+
for queue, val := range nicInfo.RPSCPUs {
1734+
queueNum, err := strconv.Atoi(strings.TrimPrefix(queue, "rx-"))
1735+
if err == nil && queueNum < maxNumQueues {
1736+
rpsValues[queueNum] = hexBitmapToCPUList(val)
1737+
}
1738+
}
1739+
fields[0].Values = append(fields[0].Values, nicInfo.Name)
1740+
fields[1].Values = append(fields[1].Values, "rps_cpus")
1741+
for i := 0; i < maxNumQueues; i++ {
1742+
fields[i+2].Values = append(fields[i+2].Values, rpsValues[i])
1743+
}
1744+
}
1745+
}
1746+
1747+
if len(fields[0].Values) == 0 {
1748+
return []Field{}
16701749
}
16711750
return fields
16721751
}

internal/report/table_helpers.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"encoding/csv"
1010
"fmt"
1111
"log/slog"
12+
"math/big"
1213
"regexp"
1314
"sort"
1415
"strconv"
@@ -1262,6 +1263,10 @@ type nicInfo struct {
12621263
Port string
12631264
MTU string
12641265
IsVirtual bool
1266+
TXQueues string
1267+
RXQueues string
1268+
XPSCPUs map[string]string
1269+
RPSCPUs map[string]string
12651270
}
12661271

12671272
func parseNicInfo(scriptOutput string) []nicInfo {
@@ -1271,6 +1276,8 @@ func parseNicInfo(scriptOutput string) []nicInfo {
12711276
continue
12721277
}
12731278
var nic nicInfo
1279+
nic.XPSCPUs = make(map[string]string)
1280+
nic.RPSCPUs = make(map[string]string)
12741281
// Map of prefixes to field pointers
12751282
fieldMap := map[string]*string{
12761283
"Interface: ": &nic.Name,
@@ -1291,6 +1298,8 @@ func parseNicInfo(scriptOutput string) []nicInfo {
12911298
"rx-usecs: ": &nic.RxUsecs,
12921299
"tx-usecs: ": &nic.TxUsecs,
12931300
"MTU: ": &nic.MTU,
1301+
"TX Queues: ": &nic.TXQueues,
1302+
"RX Queues: ": &nic.RXQueues,
12941303
}
12951304
for line := range strings.SplitSeq(nicOutput, "\n") {
12961305
line = strings.TrimSpace(line)
@@ -1308,6 +1317,23 @@ func parseNicInfo(scriptOutput string) []nicInfo {
13081317
nic.IsVirtual = (strings.TrimSpace(value) == "yes")
13091318
continue
13101319
}
1320+
// Special parsing for xps_cpus and rps_cpus
1321+
if strings.HasPrefix(line, "xps_cpus tx-") {
1322+
parts := strings.SplitN(line, ": ", 2)
1323+
if len(parts) == 2 {
1324+
queue := strings.TrimPrefix(parts[0], "xps_cpus ")
1325+
nic.XPSCPUs[queue] = parts[1]
1326+
}
1327+
continue
1328+
}
1329+
if strings.HasPrefix(line, "rps_cpus rx-") {
1330+
parts := strings.SplitN(line, ": ", 2)
1331+
if len(parts) == 2 {
1332+
queue := strings.TrimPrefix(parts[0], "rps_cpus ")
1333+
nic.RPSCPUs[queue] = parts[1]
1334+
}
1335+
continue
1336+
}
13111337
for prefix, fieldPtr := range fieldMap {
13121338
if after, ok := strings.CutPrefix(line, prefix); ok {
13131339
*fieldPtr = after
@@ -1324,6 +1350,24 @@ func parseNicInfo(scriptOutput string) []nicInfo {
13241350
return nics
13251351
}
13261352

1353+
func hexBitmapToCPUList(hexBitmap string) string {
1354+
if hexBitmap == "" {
1355+
return ""
1356+
}
1357+
i := new(big.Int)
1358+
if _, success := i.SetString(hexBitmap, 16); !success {
1359+
return hexBitmap // Return original string if not a valid hex
1360+
}
1361+
1362+
var cpus []string
1363+
for bit := 0; bit < i.BitLen(); bit++ {
1364+
if i.Bit(bit) == 1 {
1365+
cpus = append(cpus, fmt.Sprintf("%d", bit))
1366+
}
1367+
}
1368+
return strings.Join(cpus, ", ")
1369+
}
1370+
13271371
// assignCardAndPort assigns card and port numbers to NICs based on their PCI addresses
13281372
func assignCardAndPort(nics []nicInfo) {
13291373
if len(nics) == 0 {

internal/script/script_defs.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,18 @@ rdmsr 0x2FFE
780780
done
781781
printf "\n"
782782
echo "IRQ Balance: $(pgrep irqbalance >/dev/null 2>&1 && echo "Enabled" || echo "Disabled")"
783+
echo "TX Queues: $(ls -d /sys/class/net/"$ifc"/queues/tx-* | wc -l)"
784+
echo "RX Queues: $(ls -d /sys/class/net/"$ifc"/queues/rx-* | wc -l)"
785+
for q in /sys/class/net/"$ifc"/queues/tx-*; do
786+
if [ -f "$q/xps_cpus" ]; then
787+
echo "xps_cpus $(basename "$q"): $(cat "$q/xps_cpus")"
788+
fi
789+
done
790+
for q in /sys/class/net/"$ifc"/queues/rx-*; do
791+
if [ -f "$q/rps_cpus" ]; then
792+
echo "rps_cpus $(basename "$q"): $(cat "$q/rps_cpus")"
793+
fi
794+
done
783795
echo "----------------------------------------"
784796
done
785797
`,

0 commit comments

Comments
 (0)