"""Generate comparison chart: AGFS localfs vs PostgreSQL direct."""
import json
import sys
from pathlib import Path
LOCALFS = {
"write_node": {"p50": 9.5, "p95": 12.5, "p99": 13.3, "ops": 102},
"write_merge": {"p50": 8.9, "p95": 11.3, "p99": 12.3, "ops": 117},
"read_node": {"p50": 3.6, "p95": 5.3, "p99": 6.3, "ops": 268},
"exists": {"p50": 0.8, "p95": 1.4, "p99": 1.8, "ops": 1181},
"exists_miss": {"p50": 0.8, "p95": 1.3, "p99": 1.7, "ops": 1136},
"list_children[10]": {"p50": 94.7, "p95": 105.1, "p99": 112.7, "ops": 11},
"list_children[50]": {"p50": 127.3, "p95": 135.8, "p99": 140.9, "ops": 8},
"list_children[100]": {"p50": 170.6, "p95": 189.1, "p99": 203.0, "ops": 6},
"delete_node": {"p50": 0.9, "p95": 1.5, "p99": 1.8, "ops": 1006},
"move_node": {"p50": 1.9, "p95": 3.1, "p99": 3.7, "ops": 481},
"outbox_roundtrip": {"p50": 127.8, "p95": 144.2, "p99": 174.4, "ops": 8},
"outbox_register": {"p50": 14.2, "p95": 17.9, "p99": 19.8, "ops": 70},
"relation_upsert_5": {"p50": 1.8, "p95": 2.8, "p99": 3.4, "ops": 546},
"relation_get": {"p50": 0.8, "p95": 1.3, "p99": 1.7, "ops": 1188},
"write_read_cycle": {"p50": 14.5, "p95": 17.0, "p99": 17.9, "ops": 70},
"write_with_relations": {"p50": 9.9, "p95": 13.3, "p99": 14.2, "ops": 99},
}
SQL_DIRECT = {
"write_node": {"p50": 1.2, "p95": 2.2, "p99": 2.8, "ops": 775},
"write_merge": {"p50": 1.2, "p95": 2.1, "p99": 2.2, "ops": 791},
"read_node": {"p50": 0.1, "p95": 0.4, "p99": 0.5, "ops": 6385},
"exists": {"p50": 0.2, "p95": 0.3, "p99": 1.4, "ops": 5197},
"exists_miss": {"p50": 0.3, "p95": 0.5, "p99": 0.9, "ops": 3006},
"list_children[10]": {"p50": 0.2, "p95": 0.3, "p99": 0.4, "ops": 5093},
"list_children[50]": {"p50": 0.1, "p95": 0.3, "p99": 0.3, "ops": 5477},
"list_children[100]": {"p50": 0.2, "p95": 0.3, "p99": 0.4, "ops": 4258},
"delete_node": {"p50": 1.1, "p95": 1.8, "p99": 2.3, "ops": 978},
"move_node": {"p50": 1.5, "p95": 3.1, "p99": 3.3, "ops": 543},
"outbox_roundtrip": {"p50": 2.3, "p95": 4.7, "p99": 5.4, "ops": 381},
"outbox_register": {"p50": 3.0, "p95": 4.9, "p99": 5.5, "ops": 339},
"relation_upsert_5": {"p50": 1.7, "p95": 3.0, "p99": 3.5, "ops": 556},
"relation_get": {"p50": 0.2, "p95": 0.3, "p99": 0.3, "ops": 5477},
"write_read_cycle": {"p50": 1.5, "p95": 2.9, "p99": 3.2, "ops": 573},
"write_with_relations": {"p50": 1.5, "p95": 2.4, "p99": 3.2, "ops": 682},
}
def bar(value_a, value_b, max_val, width=30):
"""Return two ASCII bars for comparison."""
wa = int(value_a / max_val * width) if max_val > 0 else 0
wb = int(value_b / max_val * width) if max_val > 0 else 0
return "█" * wa + "░" * (width - wa), "█" * wb + "░" * (width - wb)
def main():
names = list(LOCALFS.keys())
print("\n" + "=" * 85)
print(" P50 LATENCY COMPARISON (ms) — lower is better")
print("=" * 85)
max_p50 = max(max(v["p50"] for v in LOCALFS.values()),
max(v["p50"] for v in SQL_DIRECT.values()))
print(f" {'Operation':<24} {'AGFS/localfs':>12} {'PG direct':>12} {'ratio':>7} Bar")
print(f" {'-'*24} {'-'*12} {'-'*12} {'-'*7} {'-'*35}")
for name in names:
la = LOCALFS[name]["p50"]
lb = SQL_DIRECT[name]["p50"]
ratio = la / lb if lb > 0 else float("inf")
winner = "PG" if ratio > 1 else "agfs" if ratio < 1 else "="
bar_a, bar_b = bar(la, lb, max_p50, width=15)
print(f" {name:<24} {la:>10.1f}ms {lb:>10.1f}ms {ratio:>6.1f}x {bar_a} vs {bar_b}")
print("\n" + "=" * 85)
print(" THROUGHPUT COMPARISON (ops/s) — higher is better")
print("=" * 85)
max_ops = max(max(v["ops"] for v in LOCALFS.values()),
max(v["ops"] for v in SQL_DIRECT.values()))
print(f" {'Operation':<24} {'AGFS/localfs':>12} {'PG direct':>12} {'ratio':>7}")
print(f" {'-'*24} {'-'*12} {'-'*12} {'-'*7}")
for name in names:
oa = LOCALFS[name]["ops"]
ob = SQL_DIRECT[name]["ops"]
ratio = ob / oa if oa > 0 else 0
print(f" {name:<24} {oa:>12.0f} {ob:>12.0f} {ratio:>6.1f}x")
print("\n" + "=" * 85)
print(" KEY FINDINGS")
print("=" * 85)
ratios = []
for name in names:
la = LOCALFS[name]["p50"]
lb = SQL_DIRECT[name]["p50"]
ratios.append((name, la / lb if lb > 0 else float("inf"), la, lb))
ratios.sort(key=lambda x: -x[1])
print("\n PG direct biggest wins:")
for name, ratio, la, lb in ratios[:5]:
print(f" {name:<24} {la:.1f}ms → {lb:.1f}ms ({ratio:.1f}x faster)")
print("\n Closest (smallest gap):")
for name, ratio, la, lb in ratios[-3:]:
print(f" {name:<24} {la:.1f}ms → {lb:.1f}ms ({ratio:.1f}x)")
print("\n list_children scaling (P50 ms):")
print(f" {'children':>10} {'AGFS/localfs':>14} {'PG direct':>14} {'speedup':>10}")
for c in [10, 50, 100]:
key = f"list_children[{c}]"
la = LOCALFS[key]["p50"]
lb = SQL_DIRECT[key]["p50"]
print(f" {c:>10} {la:>12.1f}ms {lb:>12.1f}ms {la/lb:>9.1f}x")
print("\n AGFS list_children grows linearly (file listing per child × N).")
print(" PG list_children is O(1) — constant ~0.2ms regardless of child count.")
print("=" * 85)
if __name__ == "__main__":
main()