File size: 7,172 Bytes
5c049df | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | """pod_ledger.py — RUNNER-2 campaign clock + utilization ledger
(COPY of runner 1's pod/pod_ledger.py per the charter — never import or edit
the original; its WINDOW constants are runner 1's live campaign clock.)
WINDOW: PLACEHOLDER 2026-07-16 -> 2026-07-20 UTC — Phil sets the real rental
window at kickoff; override via env R2_WINDOW_START_UTC / R2_WINDOW_END_UTC
(ISO, e.g. 2026-07-17T00:00) or edit the constants below THEN, not mid-run.
Usage (pod-side, wraps every run):
from pod_ledger import ledger_run, burn_down
with ledger_run("dexp001 sd15 relay all16", budget_h=1.5):
... # the run; row written on exit, verdict settable
burn_down() # print remaining wall/allocated/reserve
Ledger file: JSONL at LEDGER_PATH (pod: /workspace/ledger2/pod2_ledger.jsonl;
local fallback under GEOLIP_DATA). Rows: {kind: run|note|day_report, name,
start_utc, end_utc, hours, budget_h, verdict, note}. The daily report for
Phil aggregates by day + experiment.
Colab/pod-safe: stdlib only.
"""
from __future__ import annotations
import contextlib
import datetime as dt
import json
import os
# r2 window (PLACEHOLDER until kickoff; env-overridable, ISO format)
def _win(env, default):
v = os.environ.get(env)
if v:
return dt.datetime.fromisoformat(v).replace(tzinfo=dt.timezone.utc)
return default
WINDOW_START_UTC = _win("R2_WINDOW_START_UTC",
dt.datetime(2026, 7, 16, 0, 0, tzinfo=dt.timezone.utc))
WINDOW_END_UTC = _win("R2_WINDOW_END_UTC",
dt.datetime(2026, 7, 20, 0, 0, tzinfo=dt.timezone.utc))
TOTAL_H = (WINDOW_END_UTC - WINDOW_START_UTC).total_seconds() / 3600.0
RESERVE_H = 12.0
_DEFAULT_DIRS = ("/workspace/ledger2",
os.path.join(os.environ.get("GEOLIP_DATA", "./data"),
"pod_ledger"))
def _ledger_path():
for d in _DEFAULT_DIRS:
try:
os.makedirs(d, exist_ok=True)
return os.path.join(d, "pod2_ledger.jsonl")
except OSError:
continue
return "pod2_ledger.jsonl"
LEDGER_PATH = _ledger_path()
def _now():
return dt.datetime.now(dt.timezone.utc)
def _write(row: dict):
row["ts"] = _now().isoformat()
with open(LEDGER_PATH, "a", encoding="utf-8") as f:
f.write(json.dumps(row) + "\n")
def _rows():
if not os.path.exists(LEDGER_PATH):
return []
return [json.loads(l) for l in open(LEDGER_PATH, encoding="utf-8")]
def note(text: str):
_write({"kind": "note", "note": text})
print(f"[ledger] {text}", flush=True)
def spent_hours() -> float:
return sum(r.get("hours", 0.0) for r in _rows() if r.get("kind") == "run")
def wall_remaining_h() -> float:
return max(0.0, (WINDOW_END_UTC - _now()).total_seconds() / 3600.0)
def burn_down(print_it=True) -> dict:
spent = spent_hours()
wall = wall_remaining_h()
elapsed = TOTAL_H - wall
state = {
"wall_elapsed_h": round(elapsed, 2),
"wall_remaining_h": round(wall, 2),
"gpu_hours_ledgered": round(spent, 2),
"allocatable_remaining_h": round(max(0.0, wall - RESERVE_H), 2),
"reserve_h": RESERVE_H,
"utilization_pct_of_elapsed": round(100 * spent / max(elapsed, 0.01),
1),
}
if print_it:
print(f"[burn-down] wall {state['wall_elapsed_h']:.1f}h elapsed / "
f"{state['wall_remaining_h']:.1f}h left | runs "
f"{state['gpu_hours_ledgered']:.1f}h ledgered "
f"({state['utilization_pct_of_elapsed']:.0f}% of elapsed) | "
f"allocatable {state['allocatable_remaining_h']:.1f}h "
f"(reserve {RESERVE_H:.0f}h)", flush=True)
return state
@contextlib.contextmanager
def ledger_run(name: str, budget_h: float = None):
"""Time a run; write the row on exit (even on failure, with verdict)."""
start = _now()
print(f"[ledger] START {name}"
+ (f" (budget {budget_h}h)" if budget_h else ""), flush=True)
holder = {"verdict": None}
try:
yield holder
verdict = holder["verdict"] or "completed"
except BaseException as e:
verdict = f"FAILED: {type(e).__name__}: {e}"
raise
finally:
end = _now()
hours = (end - start).total_seconds() / 3600.0
over = (budget_h is not None and hours > budget_h)
_write({"kind": "run", "name": name,
"start_utc": start.isoformat(), "end_utc": end.isoformat(),
"hours": round(hours, 3), "budget_h": budget_h,
"over_budget": over, "verdict": verdict})
print(f"[ledger] END {name}: {hours:.2f}h"
+ (" ** OVER BUDGET **" if over else ""), flush=True)
burn_down()
def day_report() -> str:
"""Aggregate for Phil's daily check-in."""
rows = [r for r in _rows() if r.get("kind") == "run"]
by_day = {}
for r in rows:
day = r["start_utc"][:10]
by_day.setdefault(day, []).append(r)
lines = ["POD UTILIZATION REPORT", "=" * 40]
for day in sorted(by_day):
runs = by_day[day]
total = sum(r["hours"] for r in runs)
lines.append(f"\n{day}: {total:.1f}h across {len(runs)} runs")
for r in runs:
flag = " **OVER**" if r.get("over_budget") else ""
lines.append(f" {r['hours']:5.2f}h {r['name']}"
f" [{str(r['verdict'])[:60]}]{flag}")
s = burn_down(print_it=False)
lines.append(f"\nWall: {s['wall_elapsed_h']:.1f}h elapsed, "
f"{s['wall_remaining_h']:.1f}h remaining "
f"({s['allocatable_remaining_h']:.1f}h allocatable + "
f"{s['reserve_h']:.0f}h reserve)")
report = "\n".join(lines)
_write({"kind": "day_report", "note": report})
return report
def smoke():
import tempfile, time
global LEDGER_PATH
old = LEDGER_PATH
LEDGER_PATH = os.path.join(tempfile.gettempdir(), "pod_ledger_smoke.jsonl")
if os.path.exists(LEDGER_PATH):
os.remove(LEDGER_PATH)
note("smoke start")
with ledger_run("smoke-run", budget_h=0.00001) as h: # 0.036s budget
time.sleep(0.05)
h["verdict"] = "smoke ok"
try:
with ledger_run("smoke-fail", budget_h=1.0):
raise ValueError("boom")
except ValueError:
pass
rows = _rows()
kinds = [r["kind"] for r in rows]
assert kinds.count("run") == 2
runs = [r for r in rows if r["kind"] == "run"]
assert runs[0]["verdict"] == "smoke ok" and runs[0]["over_budget"]
assert runs[1]["verdict"].startswith("FAILED: ValueError")
rep = day_report()
assert "smoke-run" in rep and "OVER" in rep
s = burn_down(print_it=False)
assert 0 <= s["wall_remaining_h"] <= TOTAL_H
LEDGER_PATH = old
print("pod_ledger smoke passed (run rows, failure verdicts, "
"over-budget flags, report, burn-down)")
if __name__ == "__main__":
smoke()
|