test/native_stress.py
Ref: Size: 6.7 KiB History
#!/usr/bin/env python3
"""Raw cat /dev/random with same-daemon and separate-daemon GUI neighbours.
Run through `make native-stress`: its build graph supplies both release binaries.
Linux /proc provides independent producer identity and daemon read-progress
oracles. Producer write accounting misses GNU cat's splice path.
"""
import hashlib
import json
import os
from pathlib import Path
import shlex
import signal
import subprocess
import sys
import time
from native_lifecycle import LifecycleRig, start_workspace
from native_resize import by_id
from native_tiling import eventually, output_flood, require
class StressRig(LifecycleRig):
# Sample completed painted state more closely than the general integration
# harness's 40 ms polling. These remain observed upper bounds, not GPU timings.
def artifact(self, command, suffix):
self.serial += 1
path = self.root / (str(self.serial) + suffix)
self.send(command + ':' + str(path))
deadline = time.monotonic() + 5
while not path.exists() and self.gui.poll() is None and time.monotonic() < deadline:
time.sleep(.005)
require(path.exists(), 'GUI did not produce ' + command)
return path
def painted(self, pane, marker):
deadline = time.monotonic() + 2
while time.monotonic() < deadline:
state = self.state()
if marker in by_id(state)[pane]['painted_text']:
return state
time.sleep(.005)
raise AssertionError('neighbour did not paint ' + marker)
def main():
require(sys.platform == 'linux', 'native-stress currently requires Linux /proc')
r = StressRig(*sys.argv[1:3])
producer = None
report = {'artifacts': str(r.root), 'samples': []}
report_path = r.root / 'stress-result.json'
def cat_alive():
try:
return Path(f'/proc/{producer}/cmdline').read_bytes().startswith(b'cat\0/dev/random\0')
except FileNotFoundError:
return False
def read_count(pid):
fields = dict(line.split(': ', 1) for line in Path(f'/proc/{pid}/io').read_text().splitlines())
return int(fields['rchar'])
def probe(pane, sock, session, iteration):
r.focus(pane)
marker = f'RAW-NEIGHBOUR-{iteration}-{pane}'
before = time.monotonic()
# The contiguous marker is absent from shell input, so echo alone cannot
# satisfy the check before the shell executes our command.
r.shell("printf '\\033[32mRAW-NEIGHBOUR-%s\\033[0m\\n' " + shlex.quote(f'{iteration}-{pane}'))
state = r.painted(pane, marker)
delay_ms = (time.monotonic() - before) * 1000
report['samples'].append({'pane': pane, 'session': session,
'observed_input_to_painted_ms': round(delay_ms, 1)})
require(delay_ms < 250, f'neighbour exceeded 250 ms: {delay_ms:.1f} ms')
require(all(p['phase'] == 'attached' for p in state['panes']), 'pane lost attachment')
# Independently check the authoritative grid outside the latency interval.
r.wait_marker(sock, session, marker)
try:
version = subprocess.check_output([r.muxg, '--version'], text=True)
require('ReleaseSafe' in version or 'ReleaseFast' in version, 'stress requires release binaries')
report['binaries'] = {path: hashlib.sha256(Path(path).read_bytes()).hexdigest()
for path in (r.mux, r.muxg)}
refs = start_workspace(r)
output_flood(r, refs)
ids = list(refs)
flooded, separate, shared = ids
report['panes'] = {'flooded': flooded, 'separate_daemon': separate, 'shared_daemon': shared}
daemon_pid = next(proc.pid for sock, proc in r.daemons if sock == refs[flooded][0])
r.focus(flooded)
pid_path = r.root / 'random.pid'
r.shell('sh -c ' + shlex.quote('printf "%s\\n" $$ > ' + shlex.quote(str(pid_path)) +
'; exec cat /dev/random'))
eventually(lambda: pid_path.exists() and pid_path.stat().st_size, 'cat did not start')
producer = int(pid_path.read_text())
eventually(cat_alive, 'owned producer did not exec cat /dev/random')
started = time.monotonic()
baseline = read_count(daemon_pid)
last_count = baseline
round_no = 0
restarted = False
while time.monotonic() - started < 30:
require(cat_alive(), 'raw producer exited early')
for pane in (separate, shared):
probe(pane, *refs[pane], round_no)
if not restarted and time.monotonic() - started >= 10:
r.chord('p')
r.wait_state(lambda s: s['recovery'] is not None)
r.key('escape')
r.wait_state(lambda s: s['recovery'] is None)
r.drag('beside', dx=-r.state()['cell_w'] * 3)
r.kernel_sizes()
report['before_reopen_frames'], report['before_reopen_p99_us'] = r.frames()
r.quit()
r.launch_gui([], 'gui-raw-restored')
r.kernel_sizes()
report['reopened_during_flood'] = True
restarted = True
time.sleep(.15)
count = read_count(daemon_pid)
require(count - last_count > 16 * 1024, 'raw flood made insufficient progress this round')
last_count = count
round_no += 1
report['active_seconds'] = round(time.monotonic() - started, 1)
report['daemon_bytes_read'] = last_count - baseline
report['final_frames'], report['final_p99_us'] = r.frames()
require(restarted and cat_alive(), 'reopen did not run during active raw output')
require(report['daemon_bytes_read'] > 256 * 1024, 'raw flood made insufficient total progress')
require(report['before_reopen_frames'] > 0 and report['final_frames'] > 0, 'no frames during flood')
require(max(report['before_reopen_p99_us'], report['final_p99_us']) < 20000,
'raw flood exceeded 20 ms frame p99 budget')
os.kill(producer, signal.SIGTERM)
producer = None
for pane in (separate, shared):
probe(pane, *refs[pane], 'after-stop')
r.quit()
r.assert_cli_untouched()
report['passed'] = True
print(json.dumps(report, indent=2), flush=True)
print('native stress OK', flush=True)
except BaseException:
r.failure_artifacts()
raise
finally:
try:
if producer is not None and cat_alive():
os.kill(producer, signal.SIGTERM)
except ProcessLookupError:
pass
try:
report_path.write_text(json.dumps(report, indent=2) + '\n')
finally:
r.close()
if __name__ == '__main__':
main()