summaryrefslogtreecommitdiffstats
path: root/tools/guest-diag/pin_gr_stream.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/guest-diag/pin_gr_stream.py')
-rw-r--r--tools/guest-diag/pin_gr_stream.py98
1 files changed, 91 insertions, 7 deletions
diff --git a/tools/guest-diag/pin_gr_stream.py b/tools/guest-diag/pin_gr_stream.py
index b864ab5..e1f5195 100644
--- a/tools/guest-diag/pin_gr_stream.py
+++ b/tools/guest-diag/pin_gr_stream.py
@@ -16,6 +16,7 @@ import re
import socket
import subprocess
import sys
+import threading
import time
HERE = os.path.dirname(os.path.abspath(__file__))
@@ -62,12 +63,19 @@ def _start_cmd():
capture_output=True, text=True).stdout
prefixes = os.environ.get("CLIENT_PREFIXES", "cdt,va,victory")
vma_every = os.environ.get("STREAM_VMA_EVERY", "1")
- return ("echo '%s' | base64 -d > /tmp/guest_pin_stream.py; "
- "if [ -f /tmp/guest_pin_stream.pid ]; then "
- "p=$(cat /tmp/guest_pin_stream.pid 2>/dev/null); "
- "[ -n \"$p\" ] && grep -q guest_pin_stream "
- "/proc/$p/cmdline 2>/dev/null && sudo kill $p 2>/dev/null; "
- "fi; "
+ # Kill EVERY existing scanner, not just the pid-file one: a retry after
+ # a partially-failed first attempt (pid file not written yet) used to
+ # leave the first scanner alive, and multiple scanners each re-assert
+ # the whole CE pair set - r148 ran three and drove the guest to load 19
+ # (clients wedged in userspace spins). The pattern is anchored
+ # (^python3) and assembled at runtime via printf: an unanchored pattern
+ # or a literal one would also match this command's own shell (its
+ # cmdline contains the script path), and pkill would kill the shell
+ # before it could start the new scanner - every start attempt then
+ # failed silently (r149).
+ return ("P=$(printf '^python3 /tmp/guest_pin_%%s.py' stream); "
+ "sudo pkill -f \"$P\" 2>/dev/null; sleep 1; "
+ "echo '%s' | base64 -d > /tmp/guest_pin_stream.py; "
"sudo rm -f /tmp/guest_pin_stream.pid; "
"sudo setsid nohup env CLIENT_PREFIXES='%s' "
"STREAM_VMA_EVERY='%s' "
@@ -101,9 +109,70 @@ def inject(va, pa, ln):
return False
+def scrub_register(fin_pa, hsem_pa):
+ """Register the PMA scrubber buffer words (guest-resolved PAs) with the
+ service so it can author the scrub progress (third-client scrub freeze)."""
+ cmd = ("printf '0x%x,0x%x' > %s/sema_scrubbuf_add"
+ % (fin_pa, hsem_pa, PARAM))
+ for _ in range(60):
+ r = subprocess.run(["sudo", "-n", "sh", "-c", cmd],
+ capture_output=True, text=True)
+ if r.returncode == 0:
+ log("scrub registered fin=0x%x hsem=0x%x" % (fin_pa, hsem_pa))
+ return True
+ time.sleep(0.05)
+ log("scrub register FAILED fin=0x%x hsem=0x%x" % (fin_pa, hsem_pa))
+ return False
+
+
+def _demand_loop():
+ """Push the host's unreadable CE push VAs to the guest scanner.
+
+ The service logs each distinct stalled VA once per boot ('stalled VA
+ #N: 0x...'). Each sighting is tracked and re-sent every 20 s for five
+ minutes: the guest resolves the VA by content against the
+ nv_vm_map_pages areas (event-independent, no object chain), so a page
+ that was not yet written when first asked is picked up on a retry.
+ This closes the stall class where the one-shot #sem kprobe event was
+ lost and the chain resolver never learns the client.
+ """
+ track = {} # va -> [first_seen, last_sent]
+ while True:
+ time.sleep(3)
+ try:
+ r = subprocess.run(["sudo", "-n", "dmesg"],
+ capture_output=True, text=True, timeout=20)
+ except Exception:
+ continue
+ now = time.time()
+ for m in re.finditer(r"stalled VA #\d+: (0x[0-9a-f]+)",
+ (r.stdout or "")[-200000:]):
+ va = int(m.group(1), 16)
+ track.setdefault(va, [now, 0.0])
+ for va in list(track):
+ first, last = track[va]
+ if now - first > 300.0:
+ del track[va]
+ todo = [va for va, (f, l) in track.items() if now - l > 20.0]
+ if not todo:
+ continue
+ lines = "".join("0x%x\n" % va for va in todo)
+ try:
+ vexec("printf '%%s' '%s' >> /tmp/guest_want_vas.txt" % lines, 30)
+ except Exception:
+ continue
+ for va in todo:
+ track[va][1] = now
+ log("demand: sent %d stalled VA(s) to guest" % len(todo))
+
+
def main():
if not install_and_start():
log("WARN: could not start guest scanner (will retry via vsock wait)")
+ try:
+ threading.Thread(target=_demand_loop, daemon=True).start()
+ except Exception as e:
+ log("WARN: demand thread failed to start: %s" % e)
srv = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind((socket.VMADDR_CID_ANY, PORT))
@@ -158,6 +227,13 @@ def main():
log(text)
_mirror(text)
continue
+ if text.startswith("#scrub"):
+ m = re.search(r"0x([0-9a-fA-F]+),0x([0-9a-fA-F]+)", text)
+ _mirror(text)
+ if m:
+ scrub_register(int(m.group(1), 16),
+ int(m.group(2), 16))
+ continue
m = re.match(r"^(0x[0-9a-f]+),(0x[0-9a-f]+)(?:,(\d+))?$", text)
if m:
runs.append((int(m.group(1), 16), int(m.group(2), 16),
@@ -182,7 +258,15 @@ def _flush(runs, seen):
"""
todo = []
for va, pa, n in runs:
- if seen.get(va) == pa and n == 1:
+ # The CE window (0x120000000..0x121000000) is re-asserted by the
+ # guest every couple of seconds on purpose: the host's session flush
+ # wipes the whole pin table when the previous client's last channel
+ # closes, and a pin batch that raced in for the new client must be
+ # allowed to land again even when the pair is unchanged. Skipping
+ # repeats there stranded the second client's CE pushes (the r144/
+ # r145 stall: sema-probe "NO ENTRY" for the live push pages).
+ ce_win = 0x120000000 <= va < 0x121000000
+ if not ce_win and seen.get(va) == pa and n == 1:
continue
todo.append((va, pa, n))
if not todo: