diff options
| author | eunhhu <[email protected]> | 2026-09-24 17:14:54 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-09-24 17:14:54 +0100 |
| commit | b1ce1927022d374198441afafeb27199fc8a052a (patch) | |
| tree | 6021f175f4c67ed03b44240eaa2f90d846f12f6f /research/experiments/005-storage-edit-latency/measure.py | |
| parent | d34f9426d5317a1948f12f229439946728bc342a (diff) | |
| download | pure-b1ce1927022d374198441afafeb27199fc8a052a.tar.gz pure-b1ce1927022d374198441afafeb27199fc8a052a.zip | |
Establish native micro runtime checkpoint
Diffstat (limited to 'research/experiments/005-storage-edit-latency/measure.py')
| -rw-r--r-- | research/experiments/005-storage-edit-latency/measure.py | 99 |
1 files changed, 99 insertions, 0 deletions
diff --git a/research/experiments/005-storage-edit-latency/measure.py b/research/experiments/005-storage-edit-latency/measure.py new file mode 100644 index 0000000..b390c92 --- /dev/null +++ b/research/experiments/005-storage-edit-latency/measure.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +"""Time the same durable one-MiB document creation and one-byte revision edit.""" +import argparse +import hashlib +import json +from pathlib import Path +import platform +import statistics +import subprocess +import tempfile +import time + +HERE = Path(__file__).resolve().parents[3] +PROGRAM = HERE / 'prototypes/connections/examples/editor.pure' +INITIAL = b'A' * 1_048_576 +EXPECTED = INITIAL[:-1] + b'Z' + + +def invoke(command): + started = time.monotonic_ns() + result = subprocess.run([str(arg) for arg in command], capture_output=True, timeout=30) + elapsed_ms = (time.monotonic_ns() - started) / 1_000_000 + if result.returncode: + raise AssertionError((command, result.returncode, result.stderr[-1000:])) + return result, elapsed_ms + + +def one(binary, root, initial, replacement): + base = [binary, '--program', PROGRAM, '--store', root] + created, create_ms = invoke([*base, 'edit', 'document', 0, 0, 0, initial]) + assert json.loads(created.stdout)['revision'] == '1' + edited, edit_ms = invoke([*base, 'edit', 'document', 1, 1_048_575, 1, replacement]) + assert json.loads(edited.stdout)['revision'] == '2' + shown, _ = invoke([*base, 'show', 'document']) + assert shown.stdout == EXPECTED + assert json.loads(shown.stderr)['revision'] == '2' + files = [path for path in root.iterdir() if path.is_file()] + return {'create_ms': round(create_ms, 3), 'edit_ms': round(edit_ms, 3), + 'store_files': len(files), + 'store_file_bytes': sum(path.stat().st_size for path in files), + 'store_allocated_bytes': sum(path.stat().st_blocks * 512 for path in files)} + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--flat', type=Path, required=True) + parser.add_argument('--copy', type=Path, required=True) + parser.add_argument('--paged', type=Path, required=True) + parser.add_argument('--scratch-parent', type=Path, required=True) + parser.add_argument('--trials', type=int, default=18) + parser.add_argument('--output', type=Path, required=True) + args = parser.parse_args() + if args.trials < 3 or args.trials % 3 or args.output.exists(): + parser.error('trials must be a positive multiple of three and output must be new') + variants = {'flat': args.flat.resolve(), 'copy': args.copy.resolve(), + 'paged': args.paged.resolve()} + for binary in variants.values(): + if not binary.is_file(): + parser.error(f'missing binary: {binary}') + args.scratch_parent.mkdir(parents=True, exist_ok=True) + with tempfile.TemporaryDirectory(prefix='pure-storage-timing-', + dir=args.scratch_parent.resolve()) as name: + root = Path(name) + (root / 'initial').write_bytes(INITIAL) + (root / 'replacement').write_bytes(b'Z') + filesystem = subprocess.run(['findmnt', '-T', str(root), '-no', 'FSTYPE,SOURCE'], + capture_output=True, text=True, check=True).stdout.strip() + samples = {name: [] for name in variants} + names = list(variants) + for trial in range(args.trials): + for name in names[trial % 3:] + names[:trial % 3]: + sample = one(variants[name], root / f'{name}-{trial}', + root / 'initial', root / 'replacement') + samples[name].append({'trial': trial, **sample}) + result = {'profile': 'native-cli-durable-one-mib-document-last-byte-edit', + 'platform': platform.platform(), 'filesystem': filesystem, + 'trials_per_variant': args.trials, + 'final_body_sha256': hashlib.sha256(EXPECTED).hexdigest(), + 'scope': ('Each sample starts a new CLI process; create and edit each ' + 'include program parsing, store initialization, file and ' + 'directory synchronization. Show verifies the final revision ' + 'but is outside the timed operations. The host page cache, ' + 'filesystem and device remain uncontrolled.'), + 'variants': {name: {'binary_sha256': hashlib.sha256(binary.read_bytes()).hexdigest(), + 'median_create_ms': round(statistics.median( + row['create_ms'] for row in samples[name]), 3), + 'median_edit_ms': round(statistics.median( + row['edit_ms'] for row in samples[name]), 3), + 'samples': samples[name]} + for name, binary in variants.items()}} + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(result, indent=2) + '\n') + print(json.dumps({name: {key: value for key, value in entry.items() + if key.startswith('median_')} + for name, entry in result['variants'].items()}, indent=2)) + + +if __name__ == '__main__': + main() |
