summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/plugins/rmharness_plugin.py
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-02 10:58:21 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-02 10:58:21 -0400
commit002e3d7d13c414ea874abbee8d82803dd3a72fe5 (patch)
tree6627c12fa07f6a1cf73e5aaeb7390e698dd3a896 /debug/risc-v-gsp-rm-vm/plugins/rmharness_plugin.py
parent2f249817df327f5dcccf0464ad517fa60d6071d3 (diff)
downloadopen-iov-002e3d7d13c414ea874abbee8d82803dd3a72fe5.tar.gz
open-iov-002e3d7d13c414ea874abbee8d82803dd3a72fe5.zip
debug: green-eye wave 3 - RM harness, local simulator, device map, MCP plugin, value-validation demos, CI pipeline, toolchain docs; add host RM tools rmtool/rmtop
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/plugins/rmharness_plugin.py')
-rw-r--r--debug/risc-v-gsp-rm-vm/plugins/rmharness_plugin.py120
1 files changed, 120 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/plugins/rmharness_plugin.py b/debug/risc-v-gsp-rm-vm/plugins/rmharness_plugin.py
new file mode 100644
index 0000000..085751c
--- /dev/null
+++ b/debug/risc-v-gsp-rm-vm/plugins/rmharness_plugin.py
@@ -0,0 +1,120 @@
+"""green-eye plugin: RM host-oracle harness commands.
+
+Registers REPL commands that run the read-only Open-IOV RM tools through the
+``rmharness`` wrapper scripts and record each run into the active session:
+
+ greeneye> rmtool gpu list
+ greeneye> rmtool mem stats --format=json
+ greeneye> rmtop --once
+ greeneye> rmq
+
+Every run is recorded as a ``rm_harness_run`` event with a captured
+stdout+stderr blob (via ``ge.recorder.blob``); write/ownership operations are
+refused and recorded as ``rm_harness_skip``. The plugin is stdlib-only and
+never touches kernel modules, VFIO, mdevs or the live GPU state.
+"""
+from __future__ import annotations
+
+import hashlib
+import os
+import subprocess
+import sys
+import time
+
+_TOOL_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+_HARNESS = os.path.join(_TOOL_ROOT, "rmharness")
+_WRAPPERS = {
+ "rmtool": os.path.join(_HARNESS, "rmtool.sh"),
+ "rmtop": os.path.join(_HARNESS, "rmtop.sh"),
+ "rmq": os.path.join(_HARNESS, "rmq.sh"),
+}
+#: arguments that would name a write/ownership operation on any tool.
+_BLOCKED = frozenset({
+ "alloc", "allocate", "map", "unmap", "pin", "free", "create",
+ "destroy", "submit", "bind", "unbind", "reset", "start", "stop",
+ "load", "unload", "reboot", "write", "poke", "set",
+})
+
+
+def _blocked_arg(args):
+ for arg in args:
+ token = arg.split("=", 1)[0].lstrip("-").lower()
+ if token in _BLOCKED:
+ return arg
+ return None
+
+
+def _record(ge, tool, args, proc, duration):
+ stdout = proc.stdout or ""
+ stderr = proc.stderr or ""
+ payload = (stdout + stderr).encode("utf-8", "replace")
+ blob = None
+ try:
+ blob = ge.recorder.blob(
+ f"rmharness/{tool}-{time.time_ns()}.txt", payload)
+ except Exception:
+ blob = None
+ fields = dict(
+ plugin="rmharness",
+ tool=tool,
+ argv=list(args),
+ exit=proc.returncode,
+ duration_s=round(duration, 6),
+ stdout_sha256=hashlib.sha256(
+ stdout.encode("utf-8", "replace")).hexdigest(),
+ stderr_sha256=hashlib.sha256(
+ stderr.encode("utf-8", "replace")).hexdigest(),
+ stdout_bytes=len(stdout.encode("utf-8", "replace")),
+ stderr_bytes=len(stderr.encode("utf-8", "replace")),
+ blob=blob,
+ readonly=True,
+ )
+ ge.recorder.event("rm_harness_run", **fields)
+
+
+def _run(ge, tool, args):
+ wrapper = _WRAPPERS[tool]
+ if not os.path.isfile(wrapper):
+ print(f" rmharness: wrapper missing: {wrapper}", file=sys.stderr)
+ ge.recorder.event("rm_harness_error", plugin="rmharness",
+ tool=tool, error="wrapper missing")
+ return 2
+ bad = _blocked_arg(args)
+ if bad is not None:
+ print(f" rmharness: refused '{bad}': read-only policy "
+ f"(see docs/17-RM-HARNESS.md)", file=sys.stderr)
+ ge.recorder.event("rm_harness_skip", plugin="rmharness", tool=tool,
+ argv=list(args), reason=f"blocked argument {bad!r}")
+ return 2
+ start = time.monotonic()
+ proc = subprocess.run([wrapper, *args], capture_output=True, text=True)
+ duration = time.monotonic() - start
+ if proc.stdout:
+ print(proc.stdout, end="")
+ if proc.stderr:
+ print(proc.stderr, end="", file=sys.stderr)
+ _record(ge, tool, args, proc, duration)
+ directory = getattr(ge.recorder, "dir", None)
+ if directory:
+ print(f" rmharness: recorded {tool} run -> {directory}")
+ return proc.returncode
+
+
+def register(ge):
+ ge.log("rmharness plugin loaded; tools=" + ",".join(sorted(_WRAPPERS)))
+
+ def cmd_rmtool(session, args):
+ return _run(ge, "rmtool", args)
+
+ def cmd_rmtop(session, args):
+ return _run(ge, "rmtop", args)
+
+ def cmd_rmq(session, args):
+ return _run(ge, "rmq", args)
+
+ ge.add_command("rmtool", cmd_rmtool,
+ help="rmtool <args>: read-only RM oracle (gpu/engine/mem)")
+ ge.add_command("rmtop", cmd_rmtop,
+ help="rmtop <args>: dashboard; use --once for capture")
+ ge.add_command("rmq", cmd_rmq,
+ help="rmq: existing tools/rmq host oracle (read-only)")