From b1ce1927022d374198441afafeb27199fc8a052a Mon Sep 17 00:00:00 2001 From: eunhhu Date: Thu, 24 Sep 2026 17:14:54 +0100 Subject: Establish native micro runtime checkpoint --- .../2026-09-24-ext4-linux-aarch64.json | 221 +++++++++++++++++++++ .../experiments/006-native-save-latency/measure.py | 139 +++++++++++++ 2 files changed, 360 insertions(+) create mode 100644 research/experiments/006-native-save-latency/2026-09-24-ext4-linux-aarch64.json create mode 100644 research/experiments/006-native-save-latency/measure.py (limited to 'research/experiments/006-native-save-latency') diff --git a/research/experiments/006-native-save-latency/2026-09-24-ext4-linux-aarch64.json b/research/experiments/006-native-save-latency/2026-09-24-ext4-linux-aarch64.json new file mode 100644 index 0000000..0f2d215 --- /dev/null +++ b/research/experiments/006-native-save-latency/2026-09-24-ext4-linux-aarch64.json @@ -0,0 +1,221 @@ +{ + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "filesystem": "/dev/mmcblk0p2[/home/eunhhu/work/pure] ext4", + "workload": "one-byte final-character replacement, Ctrl+S to complete visible frame; includes full draft comparison, runtime, durable store and redraw; initial creation and final independent CLI read excluded", + "rounds": 3, + "edits_per_session": 8, + "binaries": { + "flat": { + "cli_sha256": "27d9ad1c83265cef24f5e756537a68692852145bfc7c1e31255e18c08fac6295", + "tui_sha256": "e8b224e3ce5f5d83d597418d4027ec36dd44bfe84d17f6e9224135dc457316c1" + }, + "paged": { + "cli_sha256": "e829bd6bad3b56bc319cdab03aba7f45c44afab53876e462ae57c35b82823e9a", + "tui_sha256": "09303ec33d07c12b07834b07fb5eeb836005c23fcb3a6b6cc2683631a9d4b13c" + } + }, + "runs": { + "flat": { + "256": [ + { + "samples_ns": [ + 5958081, + 7778966, + 6548524, + 19995479, + 62301450, + 9400518, + 5842210, + 8589502 + ], + "median_ns": 8184234, + "final_revision": 9, + "body_sha256": "e075f2f51cad23d0537186cfcd50f911ea954f9c2e32a437f45327f1b7899bbb" + }, + { + "samples_ns": [ + 9415093, + 16809059, + 8447428, + 8676279, + 6705838, + 9542795, + 10029758, + 6036581 + ], + "median_ns": 9045686, + "final_revision": 9, + "body_sha256": "e075f2f51cad23d0537186cfcd50f911ea954f9c2e32a437f45327f1b7899bbb" + }, + { + "samples_ns": [ + 7148893, + 10145461, + 7778114, + 6517728, + 62411765, + 50067253, + 7390504, + 8495113 + ], + "median_ns": 8136613, + "final_revision": 9, + "body_sha256": "e075f2f51cad23d0537186cfcd50f911ea954f9c2e32a437f45327f1b7899bbb" + } + ], + "1048576": [ + { + "samples_ns": [ + 145172249, + 96882586, + 40365568, + 349602121, + 39360237, + 89495933, + 183181933, + 87081031 + ], + "median_ns": 93189259, + "final_revision": 9, + "body_sha256": "4e29ad18ab9f42d7c233500771a39d7c852b200baf328fd00fbbe3fecea1eb56" + }, + { + "samples_ns": [ + 29222924, + 88900009, + 92498076, + 104674699, + 38498924, + 95164515, + 104447385, + 98216731 + ], + "median_ns": 93831295, + "final_revision": 9, + "body_sha256": "4e29ad18ab9f42d7c233500771a39d7c852b200baf328fd00fbbe3fecea1eb56" + }, + { + "samples_ns": [ + 150963793, + 89303082, + 89762414, + 128623245, + 37319148, + 93848499, + 101872780, + 90491636 + ], + "median_ns": 92170067, + "final_revision": 9, + "body_sha256": "4e29ad18ab9f42d7c233500771a39d7c852b200baf328fd00fbbe3fecea1eb56" + } + ] + }, + "paged": { + "256": [ + { + "samples_ns": [ + 8496650, + 19415609, + 6801246, + 9666703, + 10048962, + 6038507, + 8610260, + 7698300 + ], + "median_ns": 8553455, + "final_revision": 9, + "body_sha256": "e075f2f51cad23d0537186cfcd50f911ea954f9c2e32a437f45327f1b7899bbb" + }, + { + "samples_ns": [ + 9062278, + 62254117, + 6872783, + 9583444, + 50943122, + 6743320, + 9396814, + 9949258 + ], + "median_ns": 9490129, + "final_revision": 9, + "body_sha256": "e075f2f51cad23d0537186cfcd50f911ea954f9c2e32a437f45327f1b7899bbb" + }, + { + "samples_ns": [ + 9526426, + 8758853, + 6706653, + 97037325, + 5886063, + 20592941, + 9999906, + 6661357 + ], + "median_ns": 9142639, + "final_revision": 9, + "body_sha256": "e075f2f51cad23d0537186cfcd50f911ea954f9c2e32a437f45327f1b7899bbb" + } + ], + "1048576": [ + { + "samples_ns": [ + 20936773, + 33867655, + 20611237, + 21735235, + 20170997, + 34975172, + 20189182, + 22094974 + ], + "median_ns": 21336004, + "final_revision": 9, + "body_sha256": "4e29ad18ab9f42d7c233500771a39d7c852b200baf328fd00fbbe3fecea1eb56" + }, + { + "samples_ns": [ + 97702566, + 25086172, + 20087812, + 22047179, + 53326394, + 22037808, + 20212422, + 21726179 + ], + "median_ns": 22042493, + "final_revision": 9, + "body_sha256": "4e29ad18ab9f42d7c233500771a39d7c852b200baf328fd00fbbe3fecea1eb56" + }, + { + "samples_ns": [ + 19868442, + 22179308, + 21243699, + 30931198, + 21662716, + 68731271, + 23544638, + 26258818 + ], + "median_ns": 22861973, + "final_revision": 9, + "body_sha256": "4e29ad18ab9f42d7c233500771a39d7c852b200baf328fd00fbbe3fecea1eb56" + } + ] + } + }, + "median_ns": { + "flat": { + "256": 8542307, + "1048576": 93173287 + }, + "paged": { + "256": 9229546, + "1048576": 22042493 + } + } +} diff --git a/research/experiments/006-native-save-latency/measure.py b/research/experiments/006-native-save-latency/measure.py new file mode 100644 index 0000000..f1e2a00 --- /dev/null +++ b/research/experiments/006-native-save-latency/measure.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 +"""Time durable one-byte saves in the actual native terminal document app.""" +import argparse +import fcntl +import hashlib +import json +import os +from pathlib import Path +import platform +import pty +import select +import statistics +import struct +import subprocess +import tempfile +import termios +import time + + +def run(*args): + return subprocess.run([str(arg) for arg in args], check=True, + capture_output=True, timeout=30) + + +def frame(master, process): + output = bytearray() + deadline = time.monotonic() + 15 + while time.monotonic() < deadline: + if select.select([master], [], [], 0.5)[0]: + output.extend(os.read(master, 65536)) + if b'\x1b[?25h' in output: + return bytes(output) + if process.poll() is not None: + break + raise AssertionError((process.poll(), output[-1000:])) + + +def one_session(cli, tui, root, size, iterations): + root.mkdir(parents=True) + store = root / 'store' + body = b'A' * size + insert = root / 'initial.bin' + insert.write_bytes(body) + assert json.loads(run(cli, '--store', store, 'edit', 'document', + 0, 0, 0, insert).stdout)['revision'] == '1' + master, slave = pty.openpty() + fcntl.ioctl(slave, termios.TIOCSWINSZ, struct.pack('HHHH', 24, 80, 0, 0)) + original = termios.tcgetattr(slave) + process = subprocess.Popen([str(tui), '--store', str(store), 'document'], + stdin=slave, stdout=slave, stderr=slave, + env={**os.environ, 'LANG': 'C.UTF-8', 'LC_ALL': 'C.UTF-8'}) + samples = [] + try: + assert b'Pure | document' in frame(master, process) + for index in range(iterations): + replacement = b'B' if index % 2 == 0 else b'A' + os.write(master, b'\x7f') + frame(master, process) + os.write(master, replacement) + frame(master, process) + start = time.perf_counter_ns() + os.write(master, b'\x13') + shown = frame(master, process) + elapsed = time.perf_counter_ns() - start + expected = f'Saved 1-byte edit at byte {size - 1}'.encode() + revision = f'revision {index + 2}'.encode() + assert expected in shown and revision in shown, shown[-1500:] + samples.append(elapsed) + os.write(master, b'\x11') + process.wait(timeout=8) + assert process.returncode == 0 + assert termios.tcgetattr(slave) == original + finally: + if process.poll() is None: + process.kill() + process.wait(timeout=5) + os.close(master) + os.close(slave) + final = run(cli, '--store', store, 'show', 'document') + expected_body = body[:-1] + (b'B' if iterations % 2 else b'A') + assert final.stdout == expected_body + assert json.loads(final.stderr)['revision'] == str(iterations + 1) + return {'samples_ns': samples, 'median_ns': int(statistics.median(samples)), + 'final_revision': iterations + 1, + 'body_sha256': hashlib.sha256(final.stdout).hexdigest()} + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--flat-cli', type=Path, required=True) + parser.add_argument('--flat-tui', type=Path, required=True) + parser.add_argument('--paged-cli', type=Path, required=True) + parser.add_argument('--paged-tui', type=Path, required=True) + parser.add_argument('--scratch-parent', type=Path, required=True) + parser.add_argument('--rounds', type=int, default=3) + parser.add_argument('--edits', type=int, default=8) + parser.add_argument('--output', type=Path) + args = parser.parse_args() + if args.rounds < 1 or args.edits < 1 or (args.output and args.output.exists()): + parser.error('rounds and edits must be positive; output must not exist') + binaries = {name: (cli.resolve(), tui.resolve()) for name, cli, tui in + (('flat', args.flat_cli, args.flat_tui), + ('paged', args.paged_cli, args.paged_tui))} + parent = args.scratch_parent.resolve() + parent.mkdir(parents=True, exist_ok=True) + filesystem = run('findmnt', '-T', parent, '-no', 'SOURCE,FSTYPE').stdout.decode().strip() + raw = {name: {'256': [], '1048576': []} for name in binaries} + with tempfile.TemporaryDirectory(prefix='pure-native-save-', dir=parent) as directory: + root = Path(directory) + for size in (256, 1_048_576): + for round_number in range(args.rounds): + order = ('flat', 'paged') if round_number % 2 == 0 else ('paged', 'flat') + for name in order: + cli, tui = binaries[name] + raw[name][str(size)].append(one_session( + cli, tui, root / f'{size}-{round_number}-{name}', size, args.edits)) + result = {'platform': platform.platform(), 'filesystem': filesystem, + 'workload': 'one-byte final-character replacement, Ctrl+S to complete visible frame; ' + 'includes full draft comparison, runtime, durable store and redraw; ' + 'initial creation and final independent CLI read excluded', + 'rounds': args.rounds, 'edits_per_session': args.edits, + 'binaries': {name: {'cli_sha256': hashlib.sha256(cli.read_bytes()).hexdigest(), + 'tui_sha256': hashlib.sha256(tui.read_bytes()).hexdigest()} + for name, (cli, tui) in binaries.items()}, + 'runs': raw, + 'median_ns': {name: {size: int(statistics.median( + sample for session in sessions for sample in session['samples_ns'])) + for size, sessions in sizes.items()} for name, sizes in raw.items()}} + encoded = json.dumps(result, indent=2) + '\n' + if args.output: + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(encoded) + print(json.dumps({'platform': result['platform'], 'filesystem': filesystem, + 'median_ns': result['median_ns'], 'rounds': args.rounds, + 'edits_per_session': args.edits})) + + +if __name__ == '__main__': + main() -- cgit v1.2.3