#!/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}]")