Skip to content
Open
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
76 changes: 75 additions & 1 deletion bindings/python/nrm/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand All @@ -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")
Expand All @@ -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))
Expand Down
9 changes: 8 additions & 1 deletion bindings/python/nrm/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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={}):
Expand Down
37 changes: 37 additions & 0 deletions bindings/python/tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
12 changes: 12 additions & 0 deletions bindings/python/tests/test_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()