#!/usr/bin/env python3 # usage: analyse_lighthouse.py import glob, json, os, sys import numpy as np rng = np.random.default_rng(11) d = sys.argv[1] M = {"score": lambda j: j["categories"]["performance"]["score"] * 100, "server": lambda j: j["audits"]["server-response-time"]["numericValue"], "FCP": lambda j: j["audits"]["first-contentful-paint"]["numericValue"], "LCP": lambda j: j["audits"]["largest-contentful-paint"]["numericValue"], "TBT": lambda j: j["audits"]["total-blocking-time"]["numericValue"], "CLS": lambda j: j["audits"]["cumulative-layout-shift"]["numericValue"]} data = {} bad = 0 for p in glob.glob(os.path.join(d, "*.json")): host, page, form, run = os.path.basename(p)[:-5].split("-") try: j = json.load(open(p)) data.setdefault((page, form, host), []).append({k: f(j) for k, f in M.items()}) except Exception: bad += 1 print("unreadable runs:", bad) def bootdiff(a, b, n=10000): a, b = np.array(a), np.array(b) s = [np.median(rng.choice(a, len(a))) - np.median(rng.choice(b, len(b))) for _ in range(n)] return np.median(a) - np.median(b), np.percentile(s, 2.5), np.percentile(s, 97.5) for page in ("home", "post"): for form in ("mobile", "desktop"): print(f"\n=== {page} / {form} ===") v, c = data.get((page, form, "vercel"), []), data.get((page, form, "cloudflare"), []) print(f"runs: vercel {len(v)}, cloudflare {len(c)}") for k in M: va, ca = [r[k] for r in v], [r[k] for r in c] if not va or not ca: continue m, lo, hi = bootdiff(va, ca) print(f"{k:7s} vercel med {np.median(va):8.2f} (min {min(va):8.2f} max {max(va):8.2f}) | cloudflare med {np.median(ca):8.2f} (min {min(ca):8.2f} max {max(ca):8.2f}) | median(V-C) {m:8.2f} [{lo:8.2f}, {hi:8.2f}]")