Skip to content
Open
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
45 changes: 42 additions & 3 deletions lisa/tools/iperf3.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
import re
import time
from decimal import Decimal
from typing import TYPE_CHECKING, Any, Dict, List, Pattern, Type, cast
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Pattern, Type, cast

from retry import retry
from semver import VersionInfo

from lisa.executable import Tool
from lisa.messages import (
Expand Down Expand Up @@ -70,6 +71,14 @@
class Iperf3(Tool):
_repo = "https://github.com/esnet/iperf"
_branch = "3.10.1"
# iperf3 versions >= 3.14.0 have been observed to segfault in UDP mode
# with high parallelism (-P 64+), so versions in that range are replaced
# with a source build from _branch. When the upstream fix is confirmed,
# update or remove this workaround.
_buggy_version_min = VersionInfo(3, 14, 0)
_version_pattern = re.compile(
r"iperf\s+(?P<major>\d+)\.(?P<minor>\d+)\.?(?P<patch>\d*)"
)
_sender_pattern = re.compile(
r"(([\w\W]*?)[SUM].* (?P<bandwidth>[0-9]+.[0-9]+)"
r" Gbits/sec.*sender([\w\W]*?))",
Expand Down Expand Up @@ -100,6 +109,17 @@ def dependencies(self) -> List[Type[Tool]]:
def help(self) -> ExecutableResult:
return self.run("-h", force_run=True)

def get_version(self) -> Optional[VersionInfo]:
result = self.run("-v", force_run=True)
matched = self._version_pattern.search(result.stdout)
if matched:
major = int(matched.group("major"))
minor = int(matched.group("minor"))
patch_str = matched.group("patch")
patch = int(patch_str) if patch_str.isdigit() else 0
return VersionInfo(major, minor, patch)
return None

def install(self) -> bool:
posix_os: Posix = cast(Posix, self.node.os)
try:
Expand All @@ -110,6 +130,21 @@ def install(self) -> bool:
if self._check_exists():
if "--logfile" not in self.help().stdout:
install_from_src = True
else:
# iperf3 >= 3.14 segfaults in UDP mode with high parallelism.
# Force a source build from a known-good version.
version = self.get_version()
if version and version >= self._buggy_version_min:
self._log.info(
f"iperf3 {version} has known UDP segfault, "
f"rebuilding from source ({self._branch})"
)
install_from_src = True
Comment on lines +134 to +142
else:
self._log.debug(
f"iperf3 {version} is below buggy threshold "
f"{self._buggy_version_min}, keeping distro version"
)
else:
install_from_src = True
if install_from_src:
Expand Down Expand Up @@ -577,7 +612,7 @@ def _initialize(self, *args: Any, **kwargs: Any) -> None:
def _install_from_src(self) -> None:
tool_path = self.get_tool_path()
git = self.node.tools[Git]
git.clone(self._repo, tool_path)
git.clone(self._repo, tool_path, ref=self._branch)
code_path = tool_path.joinpath("iperf")
make = self.node.tools[Make]
self.node.execute("./configure", cwd=code_path).assert_exit_code()
Expand All @@ -597,5 +632,9 @@ def _get_bandwidth(self, result: str, pattern: Pattern[str]) -> Decimal:
def _pre_handle(self, result: str) -> str:
result = result.replace("-nan", "0")
result_matched = get_matched_str(result, self._json_pattern)
assert result_matched, "fail to find json format results"
if not result_matched:
raise LisaException(
f"Failed to parse iperf3 JSON output. "
f"Raw output (first 500 chars): {result[:500]}"
Comment on lines +637 to +638
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This exception message explains what happened, but it doesn’t give a next step to investigate/resolve. Consider mentioning common causes (e.g., iperf3 crash/segfault or non-JSON output because -J wasn’t used) and what to check (exit code, iperf3 version, full stdout/stderr/logfile).

Suggested change
f"Failed to parse iperf3 JSON output. "
f"Raw output (first 500 chars): {result[:500]}"
"Failed to parse iperf3 JSON output. "
"This usually means iperf3 produced non-JSON output, such as "
"running without JSON mode enabled, a crash/segfault, or truncated "
f"output. Verify the iperf3 command used JSON output and inspect the "
f"full stdout/stderr or logfile for errors. Raw output (first 500 "
f"chars): {result[:500]}"

Copilot uses AI. Check for mistakes.
)
return result_matched
Loading