<div dir="ltr"><div dir="ltr"><br></div><br><div class="gmail_quote gmail_quote_container"><div dir="ltr" class="gmail_attr">On Wed, Jun 24, 2026 at 5:33 PM Koushik Bhargav Nimoji <<a href="mailto:knimoji@iol.unh.edu">knimoji@iol.unh.edu</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">This patch gathers NIC info during a DTS run and writes it to an output<br>
json file. This allows the json file to be used when reporting results<br>
on the DTS results dashboard.<br>
<br>
Signed-off-by: Koushik Bhargav Nimoji <<a href="mailto:knimoji@iol.unh.edu" target="_blank">knimoji@iol.unh.edu</a>><br>
---<br>
v2:<br>
*Resolved merge conflicts<br>
v3:<br>
*Fixed an issue with retrieving<br>
the NIC's hardware version <br>
v4:<br>
*Moved nic info gathering step before the nics get<br>
binded to their respective drivers <br>
*Condensed some areas of code in order to make them<br>
more readable<br>
*Removed redundant None checks and added some where<br>
required<br>
*Fixed LshwOutput class to better reflect the lshw<br>
command output<br>
---<br>
dts/framework/test_run.py | 8 +++<br>
dts/framework/testbed_model/linux_session.py | 68 ++++++++++++++++++++<br>
dts/framework/testbed_model/os_session.py | 11 ++++<br>
3 files changed, 87 insertions(+)<br>
<br>
diff --git a/dts/framework/test_run.py b/dts/framework/test_run.py<br>
index 94dc6023a7..c92fe90f2e 100644<br>
--- a/dts/framework/test_run.py<br>
+++ b/dts/framework/test_run.py<br>
@@ -98,6 +98,7 @@<br>
"InternalError" -> "exit":ew<br>
"""<br>
<br>
+import json<br>
import random<br>
from collections import deque<br>
from collections.abc import Iterable<br>
@@ -347,6 +348,12 @@ def next(self) -> State | None:<br>
test_run.ctx.dpdk.setup()<br>
test_run.ctx.topology.setup()<br>
<br>
+ used_nic_info: list[dict[str, str]] = self.test_run.ctx.sut_node.main_session.get_nic_info()<br></blockquote><div><br></div><div>drop "used" for nic_info or change to testrun_nic_info?</div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
+ with open(f"{SETTINGS.output_dir}/dut_info.json", "w") as file:<br>
+ json.dump(used_nic_info, file, indent=3)<br>
+<br>
+ <a href="http://self.logger.info" rel="noreferrer" target="_blank">self.logger.info</a>(f"DUT NIC info written to: {SETTINGS.output_dir}/dut_info.json")<br>
+<br>
if test_run.config.use_virtual_functions:<br>
test_run.ctx.topology.instantiate_vf_ports()<br>
if test_run.ctx.sut_node.cryptodevs and test_run.config.crypto:<br>
@@ -370,6 +377,7 @@ def next(self) -> State | None:<br>
test_run.supported_capabilities = get_supported_capabilities(<br>
test_run.ctx.sut_node, test_run.ctx.topology, test_run.required_capabilities<br>
)<br>
+<br>
return TestRunExecution(test_run, self.result)<br>
<br>
def on_error(self, ex: BaseException) -> State | None:<br>
diff --git a/dts/framework/testbed_model/linux_session.py b/dts/framework/testbed_model/linux_session.py<br>
index 3a6e97974b..9e9146c372 100644<br>
--- a/dts/framework/testbed_model/linux_session.py<br>
+++ b/dts/framework/testbed_model/linux_session.py<br>
@@ -38,6 +38,8 @@ class LshwConfigurationOutput(TypedDict):<br>
driver: str<br>
#:<br>
link: str<br>
+ #:<br>
+ firmware: str<br>
<br>
<br>
class LshwOutput(TypedDict):<br>
@@ -61,6 +63,12 @@ class LshwOutput(TypedDict):<br>
...<br>
"""<br>
<br>
+ #:<br>
+ vendor: NotRequired[str]<br>
+ #:<br>
+ product: NotRequired[str]<br>
+ #:<br>
+ version: NotRequired[str]<br>
#:<br>
businfo: str<br>
#:<br>
@@ -197,6 +205,66 @@ def unbind_ports(self, ports: list[Port]):<br>
if self._lshw_net_info:<br>
del self._lshw_net_info<br>
<br>
+ def get_nic_info(self) -> list[dict[str, str]]:<br>
+ """Overrides :meth`~.os_session.OSSession.get_nic_info`.<br>
+<br>
+ Raises:<br>
+ ConfigurationError: If the NIC info could not be found.<br>
+ """<br>
+ port_data = {<br>
+ port.get("businfo"): port for port in self._lshw_net_info if port.get("businfo")<br>
+ }<br>
+<br>
+ all_nic_info: list[dict[str, str]] = []<br>
+ for port in self._config.ports:<br>
+ pci_addr = port.pci<br>
+<br>
+ command_result = self.send_command(<br></blockquote><div><br></div><div>rename to lshw_result please.</div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
+ f"sudo lshw -c network -businfo | grep '{pci_addr}' | cut -d'@' -f1"<br>
+ )<br>
+ if command_result.return_code != 0 and command_result.stdout == "":<br>
+ raise ConfigurationError(f"Unable to get bus type for port {pci_addr}.")<br>
+ bus_type = command_result.stdout<br>
+<br>
+ bus_info = f"{bus_type}@{pci_addr}"<br>
+ nic_port: LshwOutput | None = port_data[bus_info]<br>
+ if nic_port is None:<br>
+ raise ConfigurationError(f"Port {pci_addr} could not be found on the node.")<br>
+<br>
+ config: LshwConfigurationOutput | None = nic_port["configuration"]<br>
+ if config is None:<br>
+ raise ConfigurationError(<br>
+ f"Configuration info for port {pci_addr} could not be found on the node."<br>
+ )<br>
+<br>
+ if "logicalname" not in nic_port:<br>
+ raise ConfigurationError(<br>
+ f"Logical name for port {pci_addr} could not be found on the node."<br>
+ )<br>
+<br>
+ command_result = self.send_command(<br></blockquote><div><br></div><div>ethtool_result</div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
+ f"ethtool {nic_port['logicalname']} | grep 'Speed:' | awk '{{print $2}}'"<br>
+ )<span style="background-color:transparent"> </span></blockquote><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
+ if command_result.return_code == 0 and command_result.stdout:<br>
+ nic_speed = command_result.stdout<br>
+ else:<br>
+ self._logger.error(f"Unable to get speed for NIC: {pci_addr}")<br>
+ nic_speed = None<br>
+<br>
+ dut_json = {<br>
+ "make": nic_port["vendor"] if "vendor" in nic_port else "Unknown",<br>
+ "model": nic_port["product"] if "product" in nic_port else "Unknown",<br>
+ "hardware version": nic_port["version"] if "version" in nic_port else "Unknown",<br>
+ "firmware version": config["firmware"] if "firmware" in config else "Unknown",<br>
+ "deviceBusType": bus_type,<br>
+ "deviceId": nic_port["serial"] if "serial" in nic_port else "Unknown",<br>
+ "pmd": config["driver"] if "driver" in config else "Unknown",<br>
+ "speed": nic_speed or "Unknown",<br>
+ }<br>
+ all_nic_info.append(dut_json)<br>
+<br>
+ return all_nic_info<br>
+<br></blockquote><div><br></div><div>What is the intended behavior for cryptodev tests? I realize the ports list will be empty and we will not enter the initial loop, but is this intended? Do we want to gether cryptodev info too?</div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
def bind_ports_to_driver(self, ports: list[Port], driver_name: str) -> None:<br><br></blockquote><div><br></div><div>Reviewed-by: Patrick Robb <<a href="mailto:patrickrobb1997@gmail.com">patrickrobb1997@gmail.com</a>> </div></div></div>