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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
/internal/script/resources/aarch64
/test
/__debug_bin*.log
/internal/script/resources/README.md
37 changes: 22 additions & 15 deletions internal/report/table_defs.go
Original file line number Diff line number Diff line change
Expand Up @@ -1607,14 +1607,15 @@ func nicTableValues(outputs map[string]script.ScriptOutput) []Field {
{Name: "Name"},
{Name: "Vendor (ID)"},
{Name: "Model (ID)"},
{Name: "MAC Address"},
{Name: "Speed"},
{Name: "Link"},
{Name: "Bus"},
{Name: "Card / Port"},
{Name: "NUMA Node"},
{Name: "Driver"},
{Name: "Driver Version"},
{Name: "Firmware Version"},
{Name: "MAC Address"},
{Name: "NUMA Node"},
{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 All @@ -1631,19 +1632,25 @@ func nicTableValues(outputs map[string]script.ScriptOutput) []Field {
if nicInfo.ModelID != "" {
fields[2].Values[len(fields[2].Values)-1] += fmt.Sprintf(" (%s)", nicInfo.ModelID)
}
fields[3].Values = append(fields[3].Values, nicInfo.Speed)
fields[4].Values = append(fields[4].Values, nicInfo.Link)
fields[5].Values = append(fields[5].Values, nicInfo.Bus)
fields[6].Values = append(fields[6].Values, nicInfo.Driver)
fields[7].Values = append(fields[7].Values, nicInfo.DriverVersion)
fields[8].Values = append(fields[8].Values, nicInfo.FirmwareVersion)
fields[9].Values = append(fields[9].Values, nicInfo.MACAddress)
fields[10].Values = append(fields[10].Values, nicInfo.NUMANode)
fields[11].Values = append(fields[11].Values, nicInfo.IRQBalance)
fields[12].Values = append(fields[12].Values, nicInfo.AdaptiveRX)
fields[13].Values = append(fields[13].Values, nicInfo.AdaptiveTX)
fields[14].Values = append(fields[14].Values, nicInfo.RxUsecs)
fields[15].Values = append(fields[15].Values, nicInfo.TxUsecs)
fields[3].Values = append(fields[3].Values, nicInfo.MACAddress)
fields[4].Values = append(fields[4].Values, nicInfo.Speed)
fields[5].Values = append(fields[5].Values, nicInfo.Link)
fields[6].Values = append(fields[6].Values, nicInfo.Bus)
// Add Card / Port column
cardPort := ""
if nicInfo.Card != "" && nicInfo.Port != "" {
cardPort = nicInfo.Card + " / " + nicInfo.Port
}
fields[7].Values = append(fields[7].Values, cardPort)
fields[8].Values = append(fields[8].Values, nicInfo.NUMANode)
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)
}
return fields
}
Expand Down
75 changes: 75 additions & 0 deletions internal/report/table_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -1233,6 +1233,8 @@ type nicInfo struct {
AdaptiveTX string
RxUsecs string
TxUsecs string
Card string
Port string
}

func parseNicInfo(scriptOutput string) []nicInfo {
Expand Down Expand Up @@ -1284,9 +1286,82 @@ func parseNicInfo(scriptOutput string) []nicInfo {
nic.Model = strings.TrimSpace(strings.Split(nic.Model, "(")[0])
nics = append(nics, nic)
}
// Assign card and port information
assignCardAndPort(nics)
return nics
}

// assignCardAndPort assigns card and port numbers to NICs based on their PCI addresses
func assignCardAndPort(nics []nicInfo) {
if len(nics) == 0 {
return
}

// Map to store card identifiers (domain:bus:device) to card numbers
cardMap := make(map[string]int)
// Map to track ports within each card
portMap := make(map[string][]int) // card identifier -> list of indices in nics slice
cardCounter := 1

// First pass: identify cards and group NICs by card
for i := range nics {
if nics[i].Bus == "" {
continue
}
// PCI address format: domain:bus:device.function (e.g., 0000:32:00.0)
// Extract domain:bus:device as the card identifier
parts := strings.Split(nics[i].Bus, ":")
if len(parts) != 3 {
continue
}
// Further split the last part to separate device from function
deviceFunc := strings.Split(parts[2], ".")
if len(deviceFunc) != 2 {
continue
}
// Card identifier is domain:bus:device
cardID := parts[0] + ":" + parts[1] + ":" + deviceFunc[0]

// Assign card number if not already assigned
if _, exists := cardMap[cardID]; !exists {
cardMap[cardID] = cardCounter
cardCounter++
}
// Add this NIC index to the card's port list
portMap[cardID] = append(portMap[cardID], i)
}

// Second pass: assign card and port numbers
for cardID, nicIndices := range portMap {
cardNum := cardMap[cardID]
// Sort NICs within a card by their function number
sort.Slice(nicIndices, func(i, j int) bool {
// Extract function numbers
funcI := extractFunction(nics[nicIndices[i]].Bus)
funcJ := extractFunction(nics[nicIndices[j]].Bus)
return funcI < funcJ
})
// Assign port numbers
for portNum, nicIdx := range nicIndices {
nics[nicIdx].Card = fmt.Sprintf("%d", cardNum)
nics[nicIdx].Port = fmt.Sprintf("%d", portNum+1)
}
}
}

// extractFunction extracts the function number from a PCI address
func extractFunction(busAddr string) int {
parts := strings.Split(busAddr, ".")
if len(parts) != 2 {
return 0
}
funcNum, err := strconv.Atoi(parts[1])
if err != nil {
return 0
}
return funcNum
}

type diskInfo struct {
Name string
Model string
Expand Down
204 changes: 204 additions & 0 deletions internal/report/table_helpers_nic_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
package report

// Copyright (C) 2021-2025 Intel Corporation
// SPDX-License-Identifier: BSD-3-Clause

import (
"testing"

"perfspect/internal/script"
)

func TestParseNicInfoWithCardPort(t *testing.T) {
// Sample output simulating the scenario from the issue
sampleOutput := `Interface: eth2
Vendor ID: 8086
Model ID: 1593
Vendor: Intel Corporation
Model: Ethernet Controller 10G X550T
Speed: 1000Mb/s
Link detected: yes
bus-info: 0000:32:00.0
driver: ixgbe
version: 5.1.0-k
firmware-version: 0x800009e0
MAC Address: aa:bb:cc:dd:ee:00
NUMA Node: 0
CPU Affinity:
IRQ Balance: Enabled
rx-usecs: 1
tx-usecs: 1
Adaptive RX: off TX: off
----------------------------------------
Interface: eth3
Vendor ID: 8086
Model ID: 1593
Vendor: Intel Corporation
Model: Ethernet Controller 10G X550T
Speed: Unknown!
Link detected: no
bus-info: 0000:32:00.1
driver: ixgbe
version: 5.1.0-k
firmware-version: 0x800009e0
MAC Address: aa:bb:cc:dd:ee:01
NUMA Node: 0
CPU Affinity:
IRQ Balance: Enabled
rx-usecs: 1
tx-usecs: 1
Adaptive RX: off TX: off
----------------------------------------
Interface: eth0
Vendor ID: 8086
Model ID: 37d2
Vendor: Intel Corporation
Model: Ethernet Controller E810-C for QSFP
Speed: 100000Mb/s
Link detected: yes
bus-info: 0000:c0:00.0
driver: ice
version: K_5.19.0-41-generic_5.1.9
firmware-version: 4.40 0x8001c967 1.3534.0
MAC Address: aa:bb:cc:dd:ee:82
NUMA Node: 1
CPU Affinity:
IRQ Balance: Enabled
rx-usecs: 1
tx-usecs: 1
Adaptive RX: off TX: off
----------------------------------------
Interface: eth1
Vendor ID: 8086
Model ID: 37d2
Vendor: Intel Corporation
Model: Ethernet Controller E810-C for QSFP
Speed: 100000Mb/s
Link detected: yes
bus-info: 0000:c0:00.1
driver: ice
version: K_5.19.0-41-generic_5.1.9
firmware-version: 4.40 0x8001c967 1.3534.0
MAC Address: aa:bb:cc:dd:ee:83
NUMA Node: 1
CPU Affinity:
IRQ Balance: Enabled
rx-usecs: 1
tx-usecs: 1
Adaptive RX: off TX: off
----------------------------------------`

nics := parseNicInfo(sampleOutput)

if len(nics) != 4 {
t.Fatalf("Expected 4 NICs, got %d", len(nics))
}

// Expected card/port assignments based on the issue example
expectedCardPort := map[string]struct {
card string
port string
}{
"eth2": {"1", "1"}, // 0000:32:00.0
"eth3": {"1", "2"}, // 0000:32:00.1
"eth0": {"2", "1"}, // 0000:c0:00.0
"eth1": {"2", "2"}, // 0000:c0:00.1
}

for _, nic := range nics {
expected, exists := expectedCardPort[nic.Name]
if !exists {
t.Errorf("Unexpected NIC name: %s", nic.Name)
continue
}
if nic.Card != expected.card {
t.Errorf("NIC %s: expected card %s, got %s", nic.Name, expected.card, nic.Card)
}
if nic.Port != expected.port {
t.Errorf("NIC %s: expected port %s, got %s", nic.Name, expected.port, nic.Port)
}
}
}

func TestNicTableValuesWithCardPort(t *testing.T) {
// Sample output simulating the scenario from the issue
sampleOutput := `Interface: eth2
bus-info: 0000:32:00.0
Vendor: Intel Corporation
Model: Ethernet Controller 10G X550T
Speed: 1000Mb/s
Link detected: yes
----------------------------------------
Interface: eth3
bus-info: 0000:32:00.1
Vendor: Intel Corporation
Model: Ethernet Controller 10G X550T
Speed: Unknown!
Link detected: no
----------------------------------------
Interface: eth0
bus-info: 0000:c0:00.0
Vendor: Intel Corporation
Model: Ethernet Controller E810-C for QSFP
Speed: 100000Mb/s
Link detected: yes
----------------------------------------
Interface: eth1
bus-info: 0000:c0:00.1
Vendor: Intel Corporation
Model: Ethernet Controller E810-C for QSFP
Speed: 100000Mb/s
Link detected: yes
----------------------------------------`

outputs := map[string]script.ScriptOutput{
script.NicInfoScriptName: {Stdout: sampleOutput},
}

fields := nicTableValues(outputs)

// Find the "Card / Port" field
var cardPortField Field
found := false
for _, field := range fields {
if field.Name == "Card / Port" {
cardPortField = field
found = true
break
}
}

if !found {
t.Fatal("Card / Port field not found in NIC table")
}

// Verify we have 4 entries
if len(cardPortField.Values) != 4 {
t.Fatalf("Expected 4 Card / Port values, got %d", len(cardPortField.Values))
}

// Find the Name field to match values
var nameField Field
for _, field := range fields {
if field.Name == "Name" {
nameField = field
break
}
}

// Verify card/port assignments
expectedCardPort := map[string]string{
"eth2": "1 / 1",
"eth3": "1 / 2",
"eth0": "2 / 1",
"eth1": "2 / 2",
}

for i, name := range nameField.Values {
expected := expectedCardPort[name]
actual := cardPortField.Values[i]
if actual != expected {
t.Errorf("NIC %s: expected Card / Port %q, got %q", name, expected, actual)
}
}
}
Loading