package_courier: fix sampler (tps_ewma field, run-lifetime sampling, w-mode) + report (x-domain clipping, t0 anchor, aggregate-tps/max-ctx tiles) — bug-2223
Browse files
bench/package_courier/agent_app.py
CHANGED
|
@@ -160,6 +160,7 @@ class Runtime:
|
|
| 160 |
self.target = cfg.workers
|
| 161 |
self.live = 0 # threads actually running; idx is monotonic, never a count
|
| 162 |
self.no_more_packages = threading.Event()
|
|
|
|
| 163 |
self.events: list[dict] = []
|
| 164 |
self.workers: list[Worker] = []
|
| 165 |
|
|
@@ -383,18 +384,26 @@ def engine_props(url: str) -> dict:
|
|
| 383 |
|
| 384 |
|
| 385 |
def sampler(rt: Runtime, out_path: Path):
|
|
|
|
|
|
|
|
|
|
| 386 |
http = httpx.Client(base_url=rt.cfg.engine, timeout=10)
|
| 387 |
-
with out_path.open("
|
| 388 |
-
while not rt.
|
| 389 |
row = {"t": round(time.time(), 3)}
|
| 390 |
try:
|
| 391 |
r = http.get("/polykv/tps", params={"once": "1"})
|
| 392 |
if r.status_code == 200:
|
| 393 |
d = r.json()
|
| 394 |
row["mean_active_tps"] = d.get("mean_active_tps")
|
|
|
|
|
|
|
| 395 |
row["sessions"] = [
|
| 396 |
-
{
|
| 397 |
-
|
|
|
|
|
|
|
|
|
|
| 398 |
for s in d.get("sessions", [])]
|
| 399 |
except Exception:
|
| 400 |
row["err"] = True
|
|
@@ -490,6 +499,7 @@ def main():
|
|
| 490 |
rt.spawn_worker(schemas)
|
| 491 |
for w in rt.workers:
|
| 492 |
w.join()
|
|
|
|
| 493 |
|
| 494 |
work = WorkClient(cfg.work)
|
| 495 |
work.signal_done()
|
|
|
|
| 160 |
self.target = cfg.workers
|
| 161 |
self.live = 0 # threads actually running; idx is monotonic, never a count
|
| 162 |
self.no_more_packages = threading.Event()
|
| 163 |
+
self.run_done = threading.Event() # set after the last worker joins
|
| 164 |
self.events: list[dict] = []
|
| 165 |
self.workers: list[Worker] = []
|
| 166 |
|
|
|
|
| 384 |
|
| 385 |
|
| 386 |
def sampler(rt: Runtime, out_path: Path):
|
| 387 |
+
# "w" not "a": a relaunch into the same --out dir must not keep stale
|
| 388 |
+
# samples from a crashed attempt. Runs until the last worker exits
|
| 389 |
+
# (run_done) — the queue empties while episodes are still decoding.
|
| 390 |
http = httpx.Client(base_url=rt.cfg.engine, timeout=10)
|
| 391 |
+
with out_path.open("w") as f:
|
| 392 |
+
while not rt.run_done.is_set():
|
| 393 |
row = {"t": round(time.time(), 3)}
|
| 394 |
try:
|
| 395 |
r = http.get("/polykv/tps", params={"once": "1"})
|
| 396 |
if r.status_code == 200:
|
| 397 |
d = r.json()
|
| 398 |
row["mean_active_tps"] = d.get("mean_active_tps")
|
| 399 |
+
# engine field is tps_ewma (recorded here as "tps"); keep
|
| 400 |
+
# "active" so idle sessions' stale EWMA isn't aggregated
|
| 401 |
row["sessions"] = [
|
| 402 |
+
{"session_id": s.get("session_id"),
|
| 403 |
+
"tps": s.get("tps_ewma"),
|
| 404 |
+
"active": s.get("active"),
|
| 405 |
+
"ctx_used": s.get("ctx_used"),
|
| 406 |
+
"ctx_headroom_tokens": s.get("ctx_headroom_tokens")}
|
| 407 |
for s in d.get("sessions", [])]
|
| 408 |
except Exception:
|
| 409 |
row["err"] = True
|
|
|
|
| 499 |
rt.spawn_worker(schemas)
|
| 500 |
for w in rt.workers:
|
| 501 |
w.join()
|
| 502 |
+
rt.run_done.set()
|
| 503 |
|
| 504 |
work = WorkClient(cfg.work)
|
| 505 |
work.signal_done()
|
bench/package_courier/report_html.py
CHANGED
|
@@ -24,6 +24,11 @@ def svg_chart(series: dict[str, list[tuple[float, float]]], title: str,
|
|
| 24 |
step_series: set[str] | None = None) -> str:
|
| 25 |
pad_l, pad_r, pad_t, pad_b = 56, 16, 30, 40
|
| 26 |
step_series = step_series or set()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
pts = [p for s in series.values() for p in s]
|
| 28 |
if not pts:
|
| 29 |
return f"<p>{title}: no data</p>"
|
|
@@ -132,7 +137,8 @@ def build(run_dir: Path) -> Path:
|
|
| 132 |
x_domain=xdom, step_series={"delivered"}))
|
| 133 |
|
| 134 |
charts.append(svg_chart(
|
| 135 |
-
{"delivery time": [(
|
|
|
|
| 136 |
"Delivery time per package (does the run slow down?)",
|
| 137 |
"completion run time (s)", "episode duration (s)", x_domain=xdom))
|
| 138 |
|
|
@@ -157,6 +163,31 @@ def build(run_dir: Path) -> Path:
|
|
| 157 |
x_domain=xdom, step_series={"worker target", "live workers"}))
|
| 158 |
|
| 159 |
dt = rep.get("delivery_time_s") or {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
tiles = [
|
| 161 |
("Score", f"{rep.get('score_pct')}%"),
|
| 162 |
("Paid", f"${rep.get('paid_usd')}"),
|
|
@@ -168,6 +199,9 @@ def build(run_dir: Path) -> Path:
|
|
| 168 |
("Delivery mean / p90",
|
| 169 |
" / ".join(f"{dt.get(k):.1f}s" if dt.get(k) is not None else "—"
|
| 170 |
for k in ("mean", "p90"))),
|
|
|
|
|
|
|
|
|
|
| 171 |
("Tool errors", stats.get("tool_errors")),
|
| 172 |
("Workers", f"{stats.get('workers_initial', '?')} → {stats.get('workers_final')}"),
|
| 173 |
("tps floor", stats.get("tps_floor")),
|
|
|
|
| 24 |
step_series: set[str] | None = None) -> str:
|
| 25 |
pad_l, pad_r, pad_t, pad_b = 56, 16, 30, 40
|
| 26 |
step_series = step_series or set()
|
| 27 |
+
if x_domain:
|
| 28 |
+
# clip to the shared clock — SVG polylines happily render outside the
|
| 29 |
+
# plot area, so stray points (e.g. stale samples) would smear the chart
|
| 30 |
+
series = {n: [p for p in s if x_domain[0] <= p[0] <= x_domain[1]]
|
| 31 |
+
for n, s in series.items()}
|
| 32 |
pts = [p for s in series.values() for p in s]
|
| 33 |
if not pts:
|
| 34 |
return f"<p>{title}: no data</p>"
|
|
|
|
| 137 |
x_domain=xdom, step_series={"delivered"}))
|
| 138 |
|
| 139 |
charts.append(svg_chart(
|
| 140 |
+
{"delivery time": [(0.0, 0.0)]
|
| 141 |
+
+ [(t, d) for t, d in episodes if d is not None]},
|
| 142 |
"Delivery time per package (does the run slow down?)",
|
| 143 |
"completion run time (s)", "episode duration (s)", x_domain=xdom))
|
| 144 |
|
|
|
|
| 163 |
x_domain=xdom, step_series={"worker target", "live workers"}))
|
| 164 |
|
| 165 |
dt = rep.get("delivery_time_s") or {}
|
| 166 |
+
# aggregate engine throughput per sample = sum of ACTIVE sessions' gen tps;
|
| 167 |
+
# window to the run clock so stale samples from a reused --out dir don't count
|
| 168 |
+
win_rows = [r for r in tps_rows if t0 <= r["t"] <= t_end]
|
| 169 |
+
agg = [sum(s.get("tps") or 0.0 for s in (r.get("sessions") or [])
|
| 170 |
+
if s.get("active", True))
|
| 171 |
+
for r in win_rows]
|
| 172 |
+
agg = [a for a in agg if a > 0]
|
| 173 |
+
if not agg:
|
| 174 |
+
# older runs sampled the wrong field (tps always null) — reconstruct
|
| 175 |
+
# from turn events: each turn generated out_toks at gen_tps, so it
|
| 176 |
+
# occupied [t - out/gen, t]; aggregate at time g = Σ gen_tps overlapping
|
| 177 |
+
turns = [(e["t"] - t0 - e["out_toks"] / e["gen_tps"], e["t"] - t0, e["gen_tps"])
|
| 178 |
+
for e in events if e.get("ev") == "turn"
|
| 179 |
+
and (e.get("gen_tps") or 0) > 0 and (e.get("out_toks") or 0) > 0]
|
| 180 |
+
grid = [g * 5.0 for g in range(int(T / 5.0) + 1)]
|
| 181 |
+
agg = [a for g in grid
|
| 182 |
+
if (a := sum(tps for b, e, tps in turns if b <= g <= e)) > 0]
|
| 183 |
+
agg.sort()
|
| 184 |
+
agg_avg = sum(agg) / len(agg) if agg else None
|
| 185 |
+
agg_p90 = agg[int(0.9 * (len(agg) - 1))] if agg else None
|
| 186 |
+
max_ctx = max((s.get("ctx_used") or 0 for r in win_rows
|
| 187 |
+
for s in (r.get("sessions") or [])), default=0)
|
| 188 |
+
if not max_ctx:
|
| 189 |
+
max_ctx = max((e.get("in_toks") or 0 for e in events
|
| 190 |
+
if e.get("ev") == "turn"), default=0)
|
| 191 |
tiles = [
|
| 192 |
("Score", f"{rep.get('score_pct')}%"),
|
| 193 |
("Paid", f"${rep.get('paid_usd')}"),
|
|
|
|
| 199 |
("Delivery mean / p90",
|
| 200 |
" / ".join(f"{dt.get(k):.1f}s" if dt.get(k) is not None else "—"
|
| 201 |
for k in ("mean", "p90"))),
|
| 202 |
+
("Aggregate gen tps avg / p90",
|
| 203 |
+
f"{agg_avg:.1f} / {agg_p90:.1f}" if agg else "—"),
|
| 204 |
+
("Max ctx / session", f"{max_ctx:,} tok" if max_ctx else "—"),
|
| 205 |
("Tool errors", stats.get("tool_errors")),
|
| 206 |
("Workers", f"{stats.get('workers_initial', '?')} → {stats.get('workers_final')}"),
|
| 207 |
("tps floor", stats.get("tps_floor")),
|