from __future__ import annotations import hashlib, json, os, time, datetime as _dt, subprocess from pathlib import Path from typing import Any ROOT = Path(__file__).resolve().parents[2] def utc_now() -> str: return _dt.datetime.now(_dt.timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%fZ") def sha256_bytes(b: bytes) -> str: return hashlib.sha256(b).hexdigest() def sha256_text(s: str) -> str: return sha256_bytes(s.encode("utf-8")) def sha256_file(p: str | Path, chunk: int = 1 << 20) -> str: h = hashlib.sha256() with open(p, "rb") as f: while True: b = f.read(chunk) if not b: break h.update(b) return h.hexdigest() def sha256_json(obj: Any) -> str: return sha256_text(json.dumps(obj, sort_keys=True, separators=(",", ":"), default=str)) def git_commit(root: Path = ROOT) -> str | None: try: return subprocess.check_output(["git", "-C", str(root), "rev-parse", "HEAD"], text=True, stderr=subprocess.DEVNULL).strip() except Exception: return None def append_jsonl(path: str | Path, rec: dict) -> None: path = Path(path) path.parent.mkdir(parents=True, exist_ok=True) line = json.dumps(rec, ensure_ascii=False, default=str) + "\n" with open(path, "a", encoding="utf-8") as f: f.write(line) f.flush() os.fsync(f.fileno()) def read_jsonl(path: str | Path) -> list[dict]: out = [] p = Path(path) if not p.exists(): return out with open(p, encoding="utf-8") as f: for line in f: line = line.strip() if line: out.append(json.loads(line)) return out def write_json(path: str | Path, obj: Any) -> None: path = Path(path) path.parent.mkdir(parents=True, exist_ok=True) with open(path, "w", encoding="utf-8") as f: json.dump(obj, f, indent=2, ensure_ascii=False, default=str) def read_json(path: str | Path) -> Any: with open(path, encoding="utf-8") as f: return json.load(f) class Timer: """Wall-clock span timer (perf_counter). Call sync() hooks around spans for accelerators.""" def __init__(self): self.spans: dict[str, float] = {} self._t0: dict[str, float] = {} def start(self, name: str): self._t0[name] = time.perf_counter() def stop(self, name: str) -> float: dt = (time.perf_counter() - self._t0.pop(name)) * 1000.0 self.spans[name] = self.spans.get(name, 0.0) + dt return dt def ms(self) -> dict[str, float]: return dict(self.spans)