diff --git a/.gitignore b/.gitignore index f0e3b842..71768043 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ uv.lock .coverage pytest-coverage.xml imgtests.egg-info + +# External +vdbench.tar.gz diff --git a/.gitmodules b/.gitmodules index 4b527be9..71e675ae 100644 --- a/.gitmodules +++ b/.gitmodules @@ -33,3 +33,7 @@ path = layers/meta-cloud-services url = https://github.com/ni/meta-cloud-services branch = nilrt/master/next +[submodule "layers/meta-openjdk-temurin"] + path = layers/meta-openjdk-temurin + url = https://github.com/lucimber/meta-openjdk-temurin.git + branch = scarthgap diff --git a/conf/added_packages.conf b/conf/added_packages.conf index 6cf4f6b5..91509419 100644 --- a/conf/added_packages.conf +++ b/conf/added_packages.conf @@ -4,6 +4,7 @@ IMAGE_INSTALL:append = " \ fwts \ iperf3 \ lvm2 \ + openjdk-25-jre \ perf \ phoronix-test-suite \ ptest-runner \ diff --git a/conf/packages.conf b/conf/packages.conf index 2a72d4e6..81914ef9 100644 --- a/conf/packages.conf +++ b/conf/packages.conf @@ -168,4 +168,5 @@ IMAGE_INSTALL:append = " \ zip \ zlib \ zstd \ + vdbench \ " diff --git a/layers/meta-image-tests/recipes-vdbench/vdbench/README.md b/layers/meta-image-tests/recipes-vdbench/vdbench/README.md new file mode 100644 index 00000000..6069a142 --- /dev/null +++ b/layers/meta-image-tests/recipes-vdbench/vdbench/README.md @@ -0,0 +1,7 @@ +To make the recipe work, you will need to attach an archive (with name vdbench.tar.gz) containing the following files: + +- binary tool files; + +- linux x64 file and config script; + +- recipe license. diff --git a/layers/meta-image-tests/recipes-vdbench/vdbench/vdbench.bb b/layers/meta-image-tests/recipes-vdbench/vdbench/vdbench.bb new file mode 100644 index 00000000..7a13da06 --- /dev/null +++ b/layers/meta-image-tests/recipes-vdbench/vdbench/vdbench.bb @@ -0,0 +1,19 @@ +SUMMARY = "Vdbench" +# License must be withing the archive and md5sum should be updated. +LICENSE = "GPL-2.0-only" +LIC_FILES_CHKSUM = "file://LICENSE;md5=8a1984b6adf89397460816c68c05c616" + +FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:" +SRC_URI += "file://vdbench.tar.gz" + +S = "${WORKDIR}/vdbench" + +RDEPENDS:${PN} += "bash" + +do_install() { + install -d ${D}${sbindir}/vdbench + + cp -r ${S}/* ${D}${sbindir}/vdbench/ +} + +FILES:${PN} = "${sbindir}/vdbench" diff --git a/layers/meta-image-tests/recipes-vdbench/vdbench/vdbench/.gitkeep b/layers/meta-image-tests/recipes-vdbench/vdbench/vdbench/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/layers/meta-openjdk-temurin b/layers/meta-openjdk-temurin new file mode 160000 index 00000000..5804d9a8 --- /dev/null +++ b/layers/meta-openjdk-temurin @@ -0,0 +1 @@ +Subproject commit 5804d9a8b33c40b7a6c6ecae9fc8799b8df61108 diff --git a/scripts/add-layers.sh b/scripts/add-layers.sh index cdb42b79..525e8231 100755 --- a/scripts/add-layers.sh +++ b/scripts/add-layers.sh @@ -26,6 +26,7 @@ LAYERS=( "meta-security" "meta-virtualization" "meta-cloud-services" + "meta-openjdk-temurin" ) for layer in "${LAYERS[@]}"; do @@ -39,3 +40,5 @@ for layer in "${LAYERS[@]}"; do echo "Warning: Layer $layer not found!" fi done + +rm -rf /home/user/poky/meta-openjdk-temurin/recipes-java/helloworld-java diff --git a/src/imgtests/exec/loaders/__init__.py b/src/imgtests/exec/loaders/__init__.py index 56472af8..1a17da9e 100644 --- a/src/imgtests/exec/loaders/__init__.py +++ b/src/imgtests/exec/loaders/__init__.py @@ -11,3 +11,4 @@ from .pts import PhoronixTestSuite as PhoronixTestSuite from .stress_ng import StressNg as StressNg from .stress_ng import StressNgParamVerValidationError as StressNgParamVerValidationError +from .vdbench import Vdbench as Vdbench diff --git a/src/imgtests/exec/loaders/vdbench.py b/src/imgtests/exec/loaders/vdbench.py new file mode 100644 index 00000000..3c838616 --- /dev/null +++ b/src/imgtests/exec/loaders/vdbench.py @@ -0,0 +1,105 @@ +import logging +from pathlib import Path +from typing import Final + +from imgtests.constant import LIB_NAME +from imgtests.exec.base_util import GenericUtil +from imgtests.exec.exec import ExecResult, SSHClient, common_run_command +from imgtests.exec.utils import create_opt + +logger = logging.getLogger(__name__) + +VDBENCH_NAME: Final = "vdbench" +VDBENCH_DIR: Final = Path("/usr/sbin") / VDBENCH_NAME + + +class Vdbench(GenericUtil): + def __init__(self, ssh_client: SSHClient | None = None) -> None: + super().__init__(VDBENCH_NAME, ssh_client) + self.path = VDBENCH_DIR / VDBENCH_NAME + + def validate_setup(self) -> ExecResult: + result = common_run_command(["ls", str(self.path)], self.ssh_client) + if result.returncode: + return ExecResult( + cmd=result.cmd, + stderr=f"Failed to locate '{self.name}'.", + returncode=1, + ) + return result + + def configure_params( + self, + config_file: Path, + timeout_sec: int = 60, + block_size: int = 4096, + read_percentage: int = 70, + iorate: int = 1000, + ) -> ExecResult: + configuration = ( + f"sd=sd1,lun=storage.img,size=1024m\n" + f"wd=wd1,sd=sd1,xfersize={block_size},readpct={read_percentage},seekpct=100\n" + f"rd=run1,wd=wd1,iorate={iorate},elapsed={timeout_sec},interval=1" + ) + result = self.__provide_folder(config_file.parent) + if result.returncode: + return result + return common_run_command( + [ + "echo", + "-e", + f"'{configuration}'", + ">", + str(config_file), + ], + self.ssh_client, + ) + + def run( + self, + timeout_sec: int, + block_size: int = 4096, + read_percentage: int = 70, + iorate: int = 1000, + ) -> tuple[ExecResult, Path | None]: + """Runs the vdbench. + + Args: + timeout_sec (int): Execution time of the vdbench in seconds. + block_size (int): Block size in bytes for IO operations. + read_percentage (int): Percentage of read operations. + iorate (int): IO operations per second. + """ + result = self.validate_setup() + if result.returncode: + return result, None + result = common_run_command(["echo", "$HOME"], self.ssh_client) + home_dir = result.stdout.strip() + if result.returncode or not home_dir: + return result, None + + config_file: Final = Path(home_dir) / LIB_NAME / VDBENCH_NAME / f"{VDBENCH_NAME}-config" + self.configure_params( + config_file=config_file, + timeout_sec=timeout_sec, + block_size=block_size, + read_percentage=read_percentage, + iorate=iorate, + ) + + output_dir = Path(home_dir) / LIB_NAME / VDBENCH_NAME / f"{VDBENCH_NAME}-output" + result = self.__provide_folder(output_dir) + if result.returncode: + return result, None + opts = [ + *create_opt("f", config_file, use_one_dash=True), + *create_opt("o", output_dir, use_one_dash=True), + ] + result = self(opts) + return result, output_dir + + def __provide_folder(self, folder: Path) -> ExecResult: + result = common_run_command(["test", "-d", str(folder)], self.ssh_client) + if result.returncode: + return common_run_command(["mkdir", "--parents", str(folder)], self.ssh_client) + return result diff --git a/src/imgtests/suites/drive/vdbench.py b/src/imgtests/suites/drive/vdbench.py new file mode 100644 index 00000000..55741833 --- /dev/null +++ b/src/imgtests/suites/drive/vdbench.py @@ -0,0 +1,46 @@ +from datetime import UTC, datetime +from typing import TYPE_CHECKING + +from imgtests.exec.loaders import Vdbench +from imgtests.planning.base import AbstractRunnableTimeLimitedTest +from imgtests.types import Subsystem, TestResult, TestStatus + +if TYPE_CHECKING: + from collections.abc import Iterable + from concurrent.futures import ThreadPoolExecutor + + from imgtests.exec.exec import SSHClient + + +class VdbenchTest(AbstractRunnableTimeLimitedTest): + """Test that runs vdbench on a disk.""" + + def __init__(self, timeout: int) -> None: + super().__init__( + "Load drives with vdbench.", + frozenset({Subsystem.FILE}), + timeout=timeout, + ) + + def _run( + self, + executor: ThreadPoolExecutor, + client: SSHClient | None, + timeout: int, + ) -> Iterable[TestResult]: + vdbench = Vdbench(client) + started_at = datetime.now(UTC) + future = executor.submit( + vdbench.run, + timeout_sec=timeout, + block_size=4096, + read_percentage=70, + iorate=1000, + ) + result, output_dir = future.result() + yield TestResult( + metrics={"output_dir": output_dir}, + command=" ".join(result.cmd), + started_at=started_at, + status=TestStatus.PASSED if not result.returncode else TestStatus.FAILED, + )