Files
virtualjaguar-libretro/test/tools/netlink_sim_clockpatch.py
Joseph MattielloandClaude Opus 5.5 4841d260f7 perf(netlink): wait for unprompted link bytes, add on-device hop diagnostic (#860)
A Doom link tic is 12 one-way single-byte hops (6 pairs, ping-pong).  Two
parts of the old behaviour made the tic rate depend on the vsync phase
between the two devices, collapsing to 5 tics/s at bad phases even on a
fast link: JLinkAwaitReply only engaged after we transmitted, so a side that
only ANSWERS spun on ASISTAT with no wait, finished its frame in ~2 ms and
sat in the frontend while the partner's byte arrived; every such hop then
cost a whole video frame.

uart.c now counts consecutive ASISTAT reads with nothing to send or receive
and, past 24 in a frame, calls JLinkAwaitIdle(), which blocks like the reply
wait (same frame budget and timeout) so the byte is seen the moment it
arrives.  A silent partner backs off exponentially (1..32 frames without idle
waits); any received byte resets it.  Real transports on the JagLink device
only (not loopback, not the voice modem).

Simulator, Doom 2P, tics/s averaged over 4 vsync phases, before -> after
(emu 2 ms, 5 us poll_receive):
  2 ms one-way 27.5 -> 27.5   2.5 ms 19.8 -> 26.0   3 ms 16.3 -> 20.8
  4 ms 7.5 -> 19.2   Wi-Fi A (2 ms+3 jitter+2% spikes) 8.9 -> 16.0
  Wi-Fi B (3 ms+8 jitter+3% spikes) 5.4 -> 9.0   3 ms with emu 6 ms 10 -> 15

Also: a once-per-5-s INFO log line while a link carries traffic (hops/s, mean
hop time, wait per hop, slipped-hop share, timeouts, budget cuts, emulation
cost, idle-wait hits) so the numbers can be read from the RetroArch log on
iOS; netlink_sim gains --btrace/--log/--loss-pct/--rto-ms/--udp-rto-ms/
--corrupt and the clock patch gains a byte-event hook; netlink_btrace_report.py
breaks hop time down by cause and analyses the Doom tic structure.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 23:01:35 -04:00

65 lines
3.2 KiB
Python

#!/usr/bin/env python3
"""Measurement-only patch for test/tools/netlink_sim.c: lets a host process
drive jlink.c's clock and sleep (adds JLinkSimSetClock) and observe the
byte-level link events (adds JLinkSimSetTrace). Apply to a THROWAWAY
worktree only -- it must never be committed to src/.
Usage: netlink_sim_clockpatch.py <worktree> (idempotent)
JLinkSimSetTrace(fn): fn(kind, value) is called at
1 = byte handed to the transport (JLinkWireSendByte) value = byte
2 = byte arrived in the RX ring (JLinkRingPush) value = byte
3 = game wrote ASIDATA (UARTWriteWord) value = byte
4 = game read ASIDATA with RBF set (UARTReadWord) value = byte
5 = JLinkAwaitReply entered a wait value = 0
6 = JLinkAwaitReply returned value = 1 reply, 0 not
netlink_sim --btrace FILE logs "inst virtual_us kind value" per event."""
import sys
root = sys.argv[1]
def patch(path, edits):
s = open(path).read()
for old, new in edits:
if new in s:
continue # already applied
if old not in s:
sys.exit('clockpatch: anchor not found in %s: %r' % (path, old[:50]))
s = s.replace(old, new, 1)
open(path, 'w').write(s)
hook = '''#include <time.h>
#include <sys/time.h>
#define JLINK_HAVE_WAIT 1
static long long (*vjSimNow)(void) = 0;
static void (*vjSimSleep)(int) = 0;
void JLinkSimSetClock(long long (*n)(void), void (*sl)(int)) { vjSimNow = n; vjSimSleep = sl; }
void (*vjSimTrace)(int, int) = 0;
void JLinkSimSetTrace(void (*t)(int, int)) { vjSimTrace = t; }
'''
patch(root + '/src/jerry/jlink.c', [
('#include <time.h>\n#include <sys/time.h>\n#define JLINK_HAVE_WAIT 1\n',
hook),
('static long long JLinkNowUsec(void)\n{\n /* CLOCK_MONOTONIC',
'static long long JLinkNowUsec(void)\n{\n if (vjSimNow) return vjSimNow();\n /* CLOCK_MONOTONIC'),
('static void JLinkSleepUsec(int usec)\n{\n /* nanosleep',
'static void JLinkSleepUsec(int usec)\n{\n if (vjSimSleep) { vjSimSleep(usec); return; }\n /* nanosleep'),
(' jlinkTxTotal++;\n if (jlinkMode == JLINK_MODE_LOOPBACK)',
' jlinkTxTotal++;\n if (vjSimTrace) vjSimTrace(1, b);\n if (jlinkMode == JLINK_MODE_LOOPBACK)'),
(' jlinkRing[tail] = b;\n jlinkCount++;\n',
' jlinkRing[tail] = b;\n jlinkCount++;\n if (vjSimTrace) vjSimTrace(2, b);\n'),
(' start = JLinkNowUsec();\n',
' start = JLinkNowUsec();\n if (vjSimTrace) vjSimTrace(5, 0);\n'),
(' now = JLinkNowUsec();\n jlinkWaitBudgetUsec -= (int)(now - start);',
' now = JLinkNowUsec();\n if (vjSimTrace) vjSimTrace(6, JLinkDeliverable() > 0);\n jlinkWaitBudgetUsec -= (int)(now - start);'),
])
patch(root + '/src/jerry/uart.c', [
('static uint16_t asiCtrl;',
'extern void (*vjSimTrace)(int, int);\nstatic uint16_t asiCtrl;'),
(' v = uartRxData;\n',
' if (vjSimTrace && uartRxFull) vjSimTrace(4, uartRxData);\n v = uartRxData;\n'),
(' if (!uartTxBusy)\n {\n uartTxShift = ',
' if (vjSimTrace) vjSimTrace(3, data & 0xFF);\n if (!uartTxBusy)\n {\n uartTxShift = '),
])