+
+
+
+ drag = zoom to range · wheel = zoom · ctrl+drag = pan
+
+
+
+
+
+"""
+
+class H(BaseHTTPRequestHandler):
+ def log_message(self, *a): pass
+ def _send(self, body, ct):
+ self.send_response(200); self.send_header('Content-Type', ct)
+ self.send_header('Content-Length', str(len(body))); self.end_headers()
+ self.wfile.write(body)
+ def do_GET(self):
+ if self.path.startswith('/runs'):
+ body = json.dumps(list_runs(ARCHIVE) if ARCHIVE else []).encode()
+ return self._send(body, 'application/json')
+ if self.path.startswith('/data'):
+ run = None
+ mo = re.search(r'[?&]run=([^&]+)', self.path)
+ if mo:
+ run = urllib.parse.unquote(mo.group(1))
+ if run and run != 'live' and ARCHIVE:
+ # path-traversal guard: only serve a direct child of the archive
+ rd = os.path.join(ARCHIVE, os.path.basename(run))
+ snap = load_run(rd) if os.path.isdir(rd) else {
+ "t": [], "series": {k: [] for k in PANEL_KEYS}, "panels": panels_meta(),
+ "status": "run not found", "verdict": {"text": "—", "level": "idle"}, "target": run}
+ elif COLLECTOR is not None:
+ snap = COLLECTOR.snapshot()
+ if ARCHIVE and not snap.get("github_url"):
+ snap.update({k: v for k, v in latest_run_meta(ARCHIVE).items()
+ if k in ("github_url", "github_run_id", "github_repo", "run_id")})
+ else:
+ snap = {"t": [], "series": {k: [] for k in PANEL_KEYS}, "panels": panels_meta(),
+ "status": "no live node — pick a recorded run", "verdict": {"text": "—", "level": "idle"},
+ "target": "archive"}
+ return self._send(json.dumps(snap).encode(), 'application/json')
+ return self._send(PAGE.encode(), 'text/html; charset=utf-8')
+
+def do_classify(path, label, ckpt_limit, dl_limit, verdict_out):
+ samples = []
+ meta = {}
+ if os.path.isdir(path):
+ run_dir = path
+ mp = os.path.join(path, "meta.json")
+ if os.path.exists(mp):
+ meta = json.load(open(mp))
+ path = os.path.join(path, "samples.jsonl")
+ if os.path.exists(path):
+ for ln in open(path):
+ ln = ln.strip()
+ if ln:
+ try:
+ samples.append(json.loads(ln))
+ except ValueError:
+ pass
+ ckpt_limit = ckpt_limit or meta.get("ckpt_limit")
+ dl_limit = dl_limit or meta.get("dl_limit")
+ label = label or meta.get("label")
+ v = classify(samples, ckpt_limit, dl_limit)
+ out = {"label": label, **v, "samples": len(samples),
+ "ckpt_limit": ckpt_limit, "dl_limit": dl_limit}
+ if verdict_out:
+ with open(verdict_out, "w") as f:
+ json.dump(out, f, indent=2)
+ print(render_markdown(v, label))
+ return v
+
+def main():
+ global COLLECTOR, ARCHIVE
+ ap = argparse.ArgumentParser()
+ ap.add_argument('--target', default=None, help='node metrics host:port (default: auto-detect)')
+ ap.add_argument('--port', type=int, default=8090)
+ ap.add_argument('--host', default='0.0.0.0', help='dashboard bind host (default 0.0.0.0 = reachable by IP)')
+ ap.add_argument('--interval', type=float, default=2.0)
+ ap.add_argument('--window', type=int, default=4000)
+ ap.add_argument('--smooth', type=float, default=20.0,
+ help='throughput smoothing window in seconds (default 20; '
+ 'averages over batched checkpoint commits)')
+ ap.add_argument('--record', default=None, metavar='DIR', help='persist samples to DIR/samples.jsonl')
+ ap.add_argument('--no-serve', action='store_true', help='record only, no web server (CI sidecar)')
+ ap.add_argument('--archive', default=None, metavar='DIR', help='serve recorded runs under DIR (+live)')
+ ap.add_argument('--label', default=None, help='run label (recorded into meta / used by classify)')
+ ap.add_argument('--github-url', default=None, help='GitHub Actions run URL for this recorded run')
+ ap.add_argument('--github-run-id', default=None, help='GitHub Actions run id for this recorded run')
+ ap.add_argument('--github-repo', default=None, help='GitHub repository owner/name for this recorded run')
+ ap.add_argument('--ckpt-limit', type=float, default=None, help='checkpoint_verify_concurrency_limit')
+ ap.add_argument('--dl-limit', type=float, default=None, help='download_concurrency_limit')
+ ap.add_argument('--classify', default=None, metavar='PATH', help='classify a recorded run dir or samples.jsonl and exit')
+ ap.add_argument('--verdict-out', default=None, help='write the classifier verdict JSON here')
+ a = ap.parse_args()
+
+ if a.classify:
+ do_classify(a.classify, a.label, a.ckpt_limit, a.dl_limit, a.verdict_out)
+ return
+
+ ARCHIVE = a.archive
+ meta = {"label": a.label, "ckpt_limit": a.ckpt_limit, "dl_limit": a.dl_limit,
+ "github_url": a.github_url, "github_run_id": a.github_run_id,
+ "github_repo": a.github_repo,
+ "run_id": os.path.basename(a.record) if a.record else None}
+
+ target = a.target
+ if (a.record or a.no_serve or not a.archive) and not target:
+ target = autodetect_target()
+ if (a.record or a.no_serve) and not target:
+ raise SystemExit("no running zebrad metrics endpoint found; pass --target host:port")
+ if target or (a.archive and not a.no_serve):
+ COLLECTOR = Collector(target, a.interval, a.window, record_dir=a.record, meta=meta,
+ smooth_secs=a.smooth)
+ threading.Thread(target=COLLECTOR.loop, daemon=True).start()
+ src = f"http://{target}/metrics" if target else "auto-detecting live node"
+ print(f"scraping {src} every {a.interval}s"
+ + (f"; recording to {a.record}" if a.record else ""))
+
+ if a.no_serve:
+ # headless recorder: keep the scrape loop alive in the foreground
+ while True:
+ time.sleep(3600)
+ print(f"dashboard bound on {a.host}:{a.port}"
+ + (f"; archive {a.archive}" if a.archive else ""))
+ ThreadingHTTPServer((a.host, a.port), H).serve_forever()
+
+if __name__ == '__main__':
+ main()
diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml
index b151f020ab5..cd6f72e3022 100644
--- a/supply-chain/audits.toml
+++ b/supply-chain/audits.toml
@@ -101,18 +101,6 @@ who = "Alfredo Garcia