summaryrefslogtreecommitdiffstats
path: root/tools/guest-diag/uvmt_poll.py
blob: 668435af0bc83d37b91d153a26f23cc62963e02a (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
#!/usr/bin/env python3
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2026 Open-IOV contributors
# uvmt_poll.py - poll the live state of the tracking semaphores the CUDA
# client spins on, from the guest trace kptrs, via QMP (no guest agent
# besides the trace read). Prints the payload word the client actually
# reads (pool page + index*4) on every change, so the last stall before a
# failure is visible.
#
# Usage: tools/guest-diag/uvmt_poll.py [interval-seconds]
import os
import re
import subprocess
import sys
import time

HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
from semfix_watch import Qmp, QMP_SOCK, is_kern, is_gpu  # noqa: E402

VMEXEC = os.path.normpath(os.path.join(HERE, "..", "..", "scripts", "vgpu_vm.py"))


def log(msg):
    print("[%s] %s" % (time.strftime("%H:%M:%S"), msg), flush=True)


def trace_sems():
    inner = ("tail -c 262144 /sys/kernel/tracing/trace "
             "| grep -E \"uvmt\" | tail -40")
    try:
        r = subprocess.run([VMEXEC, "exec", "sudo sh -c '%s'" % inner],
                           capture_output=True, text=True, timeout=60)
        out = r.stdout
    except Exception:
        return set()
    return {int(m, 16) for m in re.findall(r"sem=0x([0-9a-fA-F]+)", out)}


def state(q, sem):
    vals = q.read64(sem, 12)
    if len(vals) < 2 or not is_kern(vals[0]):
        return None
    page = vals[0]
    pv = q.read64(page)
    if not pv or not is_kern(pv[0]):
        return None
    mem = pv[0]
    vas = q.read64(mem + 0x10, 16)
    cpu = next((v for v in vas if is_kern(v)), None)
    gpu = [v for v in vas if is_gpu(v)]
    idx = vals[1] & 0xffff
    payload = None
    if cpu:
        w = q.read64(cpu + idx * 4)
        payload = w[0] if w else None
    words = " ".join("+%x=0x%x" % (4 * i, vals[i])
                     for i in range(2, min(len(vals), 12)))
    return (page, mem, idx, gpu, payload, words)


def main():
    interval = float(sys.argv[1]) if len(sys.argv) > 1 else 2.0
    q = Qmp(QMP_SOCK)
    last = {}
    sems_seen = set()
    log("polling uvmt sems (interval %.1fs)" % interval)
    while True:
        sems = trace_sems()
        if not sems and not sems_seen:
            time.sleep(interval)
            continue
        for sem in sems:
            st = None
            try:
                st = state(q, sem)
            except Exception:
                st = None
            key = st[:3] + (st[4],) if st else None
            if key != last.get(sem):
                last[sem] = key
                if st:
                    print("uvmt sem=0x%x page=0x%x mem=0x%x idx=%d gpu=%s "
                          "payload=%s %s"
                          % (sem, st[0], st[1], st[2],
                             ["0x%x" % v for v in st[3]],
                             "0x%x" % st[4] if st[4] is not None else "?",
                             st[5]), flush=True)
                else:
                    print("uvmt sem=0x%x state unreadable" % sem, flush=True)
        sems_seen |= sems
        time.sleep(interval)


if __name__ == "__main__":
    main()