hetchyy's picture
deploy: inspector prod @ c16da46
50ff870 verified
Raw
History Blame Contribute Delete
6.36 kB
"""Bench the storage backend on real inspector hot paths.
Measures cold + warm timings for the read/write operations the
inspector actually performs against a real reciter in the dev bucket.
Run:
INSPECTOR_BUCKET_REPO=hetchyy/quranic-inspector-bucket-dev \\
python3 scripts/diagnostics/bench_storage.py [--mount /path]
A scratch dir ``reciters/__bench__/`` is created for writes and cleaned at exit.
"""
from __future__ import annotations
import argparse
import gc
import os
import statistics
import sys
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "inspector"))
sys.path.insert(0, str(ROOT))
from inspector.services.hf_bucket import BucketBackend # noqa: E402
BUCKET = os.environ.get("INSPECTOR_BUCKET_REPO", "hetchyy/quranic-inspector-bucket-dev")
SLUG = "mishary_rashid_al_afasy_mp3quran"
SCRATCH = "reciters/__bench__"
def time_call(fn, *args, **kwargs) -> tuple[float, object]:
gc.collect()
t0 = time.perf_counter()
result = fn(*args, **kwargs)
return (time.perf_counter() - t0) * 1000.0, result
def invalidate_hffs():
try:
from huggingface_hub import hffs
hffs.invalidate_cache()
except Exception:
pass
def stats(label: str, samples: list[float], extra: str = "") -> None:
if not samples:
print(f" {label:<38} no samples")
return
s = sorted(samples)
p50 = statistics.median(s)
p95 = s[min(len(s) - 1, int(0.95 * len(s)))]
print(
f" {label:<38} n={len(s):<2} min={min(s):7.1f} "
f"p50={p50:7.1f} p95={p95:7.1f} max={max(s):7.1f} ms {extra}"
)
def bench_reads(b: BucketBackend, n_warm: int = 5) -> None:
print("\n== READS (cold = fresh hffs cache, warm = repeat) ==")
targets = [
("state/reciter_state.json", "read_json", "store"),
("access/inspector_roles.json", "read_json", "store"),
("catalog/reciter_catalog.json", "read_json", "store-large"),
(f"reciters/{SLUG}/segments.json", "read_json", "reciter-md"),
(f"reciters/{SLUG}/detailed.json", "read_bytes", "reciter-big"),
(f"reciters/{SLUG}/edit_history.jsonl", "iter_jsonl", "history"),
(f"reciters/{SLUG}/timestamps/1.json", "read_bytes", "ts-chapter"),
]
for path, op, tag in targets:
cold_samples: list[float] = []
warm_samples: list[float] = []
size = None
def call(op: str = op, path: str = path) -> object:
if op == "read_bytes":
return b.read_bytes(path)
if op == "read_json":
return b.read_json(path)
if op == "iter_jsonl":
return list(b.iter_jsonl(path))
raise ValueError(op)
# Cold: invalidate fsspec cache each iteration
for _ in range(3):
invalidate_hffs()
dt, res = time_call(call)
cold_samples.append(dt)
if size is None:
if isinstance(res, (bytes, bytearray)):
size = len(res)
elif isinstance(res, list):
size = sum(len(repr(x)) for x in res)
else:
import orjson
size = len(orjson.dumps(res))
# Warm: no invalidation, repeat
for _ in range(n_warm):
dt, _ = time_call(call)
warm_samples.append(dt)
sz_kb = (size or 0) / 1024.0
stats(f"{tag:<11} {op:<11} {path[-40:]}", cold_samples, extra=f"[cold, {sz_kb:6.1f} KB]")
stats(f"{'':<11} {'':<11} {'(warm)':<40}", warm_samples, extra="[warm]")
# list_dir + exists
invalidate_hffs()
samples = []
for _ in range(3):
invalidate_hffs()
dt, _ = time_call(b.list_dir, "reciters")
samples.append(dt)
stats("list_dir list_dir reciters/", samples, extra="[cold]")
samples = []
for _ in range(3):
dt, _ = time_call(b.exists, f"reciters/{SLUG}/segments.json")
samples.append(dt)
stats("exists exists segments.json", samples, extra="[mixed]")
def bench_writes(b: BucketBackend, n: int = 3) -> None:
print("\n== WRITES ==")
small_path = f"{SCRATCH}/small.json"
large_path = f"{SCRATCH}/large.json"
hist_path = f"{SCRATCH}/edit_history.jsonl"
small = {"x": list(range(100))}
large = {"rows": [{"i": i, "vals": list(range(20))} for i in range(20_000)]}
import orjson
print(f" small payload: {len(orjson.dumps(small)) / 1024:.1f} KB")
print(f" large payload: {len(orjson.dumps(large)) / 1024:.1f} KB")
s = []
for _ in range(n):
dt, _ = time_call(b.write_json_atomic, small_path, small)
s.append(dt)
stats("write_json small (~1 KB)", s)
s = []
for _ in range(n):
dt, _ = time_call(b.write_json_atomic, large_path, large)
s.append(dt)
stats("write_json large (1+ MB)", s)
# append_jsonl — without mount, this is read-modify-write of whole file.
# Pre-seed so the read leg is non-trivial.
for i in range(50):
b.append_jsonl(hist_path, {"i": i, "data": "x" * 200})
s = []
for i in range(n):
dt, _ = time_call(b.append_jsonl, hist_path, {"i": 1000 + i, "data": "y" * 200})
s.append(dt)
stats("append_jsonl (50-row preseeded)", s, extra="[rmw on bucket, in-place on mount]")
def cleanup(b: BucketBackend) -> None:
try:
for name in b.list_dir(SCRATCH):
try:
b.delete(f"{SCRATCH}/{name}")
except Exception as e:
print(f" cleanup err {name}: {e}")
except Exception:
pass
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument(
"--mount", default=None, help="path to local hf-mount of the dev bucket (optional)"
)
ap.add_argument("--skip-writes", action="store_true")
ap.add_argument("--warm", type=int, default=5)
args = ap.parse_args()
print(f"bucket={BUCKET}")
print(f"mode={'MOUNTED (' + args.mount + ')' if args.mount else 'NO MOUNT (hffs.cat_file)'}")
print(f"slug={SLUG}")
b = BucketBackend(BUCKET, mount=args.mount)
bench_reads(b, n_warm=args.warm)
if not args.skip_writes:
try:
bench_writes(b)
finally:
cleanup(b)
if __name__ == "__main__":
main()