1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
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()
|