summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/plugins/rmharness_plugin.py
blob: 937c9faf072ec26085412b49107d30d53fb6209b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
"""risc-v-gsp-rm-debugger 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:

    risc-v-gsp-rm-debugger> rmtool gpu list
    risc-v-gsp-rm-debugger> rmtool mem stats --format=json
    risc-v-gsp-rm-debugger> rmtop --once
    risc-v-gsp-rm-debugger> 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)")