diff --git a/bindings/python/nrm/client.py b/bindings/python/nrm/client.py index 32e645b..a187a42 100644 --- a/bindings/python/nrm/client.py +++ b/bindings/python/nrm/client.py @@ -6,8 +6,15 @@ # # SPDX-License-Identifier: BSD-3-Clause -from ctypes import byref, POINTER, c_void_p, c_double +import os +import shutil +import logging +import subprocess +from pathlib import Path +from typing import List, Union from dataclasses import dataclass +from ctypes import byref, POINTER, c_void_p, c_double + from .base import ( Error, _nrm_get_function, @@ -128,6 +135,31 @@ "nrm_actuator_list_choices", [nrm_actuator, POINTER(nrm_vector)] ) +_logger = logging.getLogger("nrm") + + +@dataclass +class ClientExec: + cmd: list + stdout: Path + stderr: Path + process: subprocess.Popen = None + errcode: int = -1 + preloads: str = "" + + def _setup_preloads(self, preloads): + preloads = ":".join([str(Path(path).absolute()) for path in preloads]) + if len(preloads): + os.environ["LD_PRELOAD"] = preloads + self.preloads = preloads + + def wait(self, timeout=None): + return self.process.wait(timeout=timeout) + + def poll(self): + self.errcode = self.process.poll() + return self.errcode + class Client: """Client class for interacting with NRM C interface. @@ -151,6 +183,7 @@ def __init__( self.pub_port = pub_port if pub_port else upstream_pub_port self.rpc_port = rpc_port if rpc_port else upstream_rpc_port self.client = nrm_client(0) + self.runs = [] if isinstance(self.uri, str): self.uri = bytes(self.uri, "utf-8") @@ -159,6 +192,47 @@ def __init__( byref(self.client), self.uri, self.pub_port, self.rpc_port ) + def run( + self, cmd: Union[str, List[str]], preloads: List[Union[str, Path]] = [] + ): + cmd = [cmd] if isinstance(cmd, str) else cmd + execcmd = ClientExec( + cmd=cmd, + stdout=cmd[0] + "-stdout.log", + stderr=cmd[0] + "-stderr.log", + ) + execcmd._setup_preloads(preloads) + try: + with open(execcmd.stdout, "w") as stdout, open( + execcmd.stderr, "w" + ) as stderr: + _logger.debug("Launching " + str(cmd)) + execcmd.process = subprocess.Popen( + cmd, stdout=stdout, stderr=stderr + ) + self.runs.append(execcmd) + return execcmd + except Exception as e: + _logger.error("Error on launch: ", e.__class__, e.args) + raise e + + def papi_run( + self, + cmd: Union[str, List[str]], + events: List[str] = ["PAPI_TOT_INS"], + freq: float = 1.0, + preloads: List[Union[str, Path]] = [], + ): + papiwrapper = shutil.which("nrm-papiwrapper") + if not papiwrapper: + raise FileNotFoundError("Unable to find nrm-papiwrapper") + cmd = [cmd] if isinstance(cmd, str) else cmd + eevents = ["-e " + event for event in events] + cmd = ( + [papiwrapper] + ["-f", str(freq)] + eevents + cmd + ) # should resemble /usr/bin/nrm-papiwrapper -f 1 -e PAPI_EVENT ./app + return self.run(cmd, preloads) + def list_sensors(self) -> list: vector = nrm_vector(0) nrm_client_list_sensors(self.client, byref(vector)) diff --git a/bindings/python/nrm/setup.py b/bindings/python/nrm/setup.py index 0ecc7fa..89e2977 100644 --- a/bindings/python/nrm/setup.py +++ b/bindings/python/nrm/setup.py @@ -78,15 +78,22 @@ def nrmd_waitready(options): @waitretry -def dummy_connect(options): +def dummy_connect(*args): actuators = Client().list_actuators() return "nrm-dummy-extra-actuator" in [act.get_uuid() for act in actuators] +@waitretry +def geopm_connect(*args): + actuators = Client().list_actuators() + return "nrm.geopm.cpu.power" in [act.get_uuid() for act in actuators] + + class Setup: binaries = { "nrmd": NRMBinary("nrmd", False, nrmd_waitready), "nrm-dummy-extra": NRMBinary("nrm-dummy-extra", False, dummy_connect), + "nrm-geopm": NRMBinary("nrm-geopm", False, geopm_connect), } def __init__(self, name, args=[], options={}): diff --git a/bindings/python/tests/test_client.py b/bindings/python/tests/test_client.py index 0cc7edc..a1d9f4b 100644 --- a/bindings/python/tests/test_client.py +++ b/bindings/python/tests/test_client.py @@ -69,6 +69,43 @@ def test_actuator_values_from_extra(self): assert dummy_act.list_choices() == [0.0, 1.0] assert len(dummy_act.get_clientid()) + def test_client_run(self): + with Setup("nrmd", options=options): + client = Client() + cmd = client.run("ls") + cmd.wait(timeout=1) + assert all( + f in os.listdir(".") + for f in ["ls-stdout.log", "ls-stderr.log"] + ) + with open("ls-stdout.log", "r") as f: + assert len(f.readlines()) + + def test_client_run_poll(self): + with Setup("nrmd", options=options): + client = Client() + cmd = client.run(["sleep", "2"]) + assert cmd.poll() + cmd.wait() + + def test_client_run_preload(self): + with Setup("nrmd", options=options): + client = Client() + cmd = client.run("who", preloads=[os.environ.get("LIBNRM_SO_")]) + cmd.wait(timeout=1) + + def test_client_papi_run(self): + with Setup("nrmd", options=options): + client = Client() + cmd = client.papi_run("ls", events=["PAPI_TOT_INS"]) + cmd.wait(timeout=1) + assert all( + f in os.listdir(".") + for f in ["ls-stdout.log", "ls-stderr.log"] + ) + with open("ls-stdout.log", "r") as f: + assert len(f.readlines()) + if __name__ == "__main__": unittest.main() diff --git a/bindings/python/tests/test_setup.py b/bindings/python/tests/test_setup.py index c3c9305..4125359 100644 --- a/bindings/python/tests/test_setup.py +++ b/bindings/python/tests/test_setup.py @@ -18,6 +18,18 @@ def test_setup_init(self): with Setup("nrmd", options=options): pass + def test_dummy_extra_init(self): + with Setup("nrmd", options=options), Setup( + "nrm-dummy-extra", options=options + ): + pass # client tested in test_client.py - just check no crash + + def test_geopm_init(self): + with Setup("nrmd", options=options), Setup( + "nrm-geopm", options=options + ): + pass + if __name__ == "__main__": unittest.main()