170 ap = argparse.ArgumentParser(description=__doc__,
171 formatter_class=argparse.RawDescriptionHelpFormatter)
172 ap.add_argument(
"--design", type=Path, default=
None,
173 help=
"default: the design named in machines.json")
174 ap.add_argument(
"--registry", type=Path, default=HERE /
"machines.json")
175 ap.add_argument(
"--results", type=Path, default=HERE /
"results")
176 ap.add_argument(
"--at-dose", type=float, default=
None,
177 help=
"also report how many rows reached this dose rung")
178 ap.add_argument(
"--sync", action=
"store_true",
179 help=
"first send THIS machine's results and pull everyone "
180 "else's, then tally (git is the transport)")
181 ap.add_argument(
"--no-push", action=
"store_true",
182 help=
"with --sync: pull and commit, but do not push")
183 a = ap.parse_args(argv)
186 import campaign_ops
as ops
189 ops.sync_results(push=
not a.no_push)
190 except Exception
as e:
194 print(f
" *** sync failed ({e}); tallying LOCAL results only")
196 reg = json.loads(a.registry.read_text())
197 design = a.design
or (HERE / reg[
"design"])
199 weights = [float(x)
for x
in str(reg[
"weights"]).split(
",")]
200 names = {int(m[
"index"]): m[
"name"]
for m
in reg[
"machines"]}
213 here_k = RE.detect_machine(reg)[
"index"]
216 starts = {k: v
for k, v
in starts.items()
if k == here_k}
218 print(f
"\ndesign {design.name} {len(all_ids)} rows")
219 print(f
"registry {a.registry.name} of={of} weights={reg['weights']} "
220 f
"timeout_s={reg.get('timeout_s')}")
223 hdr = (f
"{'#':>2} {'machine':<18} {'owns':>5} {'done':>5} {'%':>6} "
224 f
"{'cut':>4} {'rc!=0':>6} {'med_s':>7} {'rows/h':>7} {'ETA':>7} status")
226 print(
"-" * len(hdr))
228 tot_done = tot_owned = 0
231 n_own = len(own.get(k, []))
233 recs = per.get(k, {})
237 if not files.get(k)
and not recs:
238 print(f
"{k:>2} {names.get(k,'?'):<18} {n_own:>5} {'-':>5} {'-':>6} "
239 f
"{'-':>4} {'-':>6} {'-':>7} {'-':>7} {'-':>7} NO FILE - not pushed / not started")
242 walls = sorted(r.get(
"wall_s", 0.0)
for r
in recs.values())
243 med = walls[len(walls) // 2]
if walls
else 0.0
244 cut = sum(1
for r
in recs.values()
if r.get(
"starved"))
245 bad = sum(1
for r
in recs.values()
if r.get(
"solver_rc"))
246 nw = max((int(r.get(
"workers")
or 1))
for r
in recs.values())
if recs
else 1
259 exact = st
is not None and now
is not None
261 elapsed_h = max(now - float(st[
"unix"]), 1.0) / 3600.0
262 base = int(st.get(
"rows_already_done")
or 0)
263 n_this = max(n_done - base, 0)
264 rate = (n_this / elapsed_h)
if elapsed_h > 0
else None
266 burned_h = (sum(walls) / nw) / 3600.0
if walls
else 0.0
267 rate = (n_done / burned_h)
if burned_h > 0
else None
268 eta = ((n_own - n_done) / rate)
if rate
else None
269 if eta
is not None and exact:
270 eta_hours.append(eta)
272 pct = 100.0 * n_done / n_own
if n_own
else 0.0
280 if exact
and med > 0
and elapsed_h * 3600.0 < 2.0 * med:
281 note += (f
" TRANSIENT: {elapsed_h*3600/med:.1f} row-times in, "
282 f
"rate not yet meaningful")
283 if not exact
and rate:
284 note +=
" ~rate: no start marker, CEILING only"
286 note += f
" {bad} FAIL"
289 mark =
"" if exact
else "~"
290 print(f
"{k:>2} {names.get(k,'?'):<18} {n_own:>5} {n_done:>5} {pct:>5.1f}% "
291 f
"{cut:>4} {bad:>6} {med:>7.0f} "
292 f
"{(f'{mark}{rate:.1f}' if rate else '-'):>7} {fmt_eta(eta)} {note}")
294 print(
"-" * len(hdr))
295 print(f
"{'':>2} {'TOTAL':<18} {tot_owned:>5} {tot_done:>5} "
296 f
"{100.0*tot_done/tot_owned if tot_owned else 0:>5.1f}%")
304 for k, recs
in per.items():
307 dup.append((rid, seen[rid], k))
309 missing = len(all_ids - set(seen))
310 print(f
"\ncoverage {len(seen)}/{len(all_ids)} distinct rows "
311 f
"{missing} not yet reported"
312 + (f
" *** {len(dup)} row(s) computed by TWO machines" if dup
else ""))
314 for rid, k1, k2
in dup[:5]:
315 print(f
" row {rid}: machine {k1} and machine {k2}")
318 for recs
in per.values():
321 print(
"\nprovenance")
322 check_provenance(pool)
323 print(
" (no output above = all reported rows are poolable)")
325 rungs = defaultdict(int)
326 for r
in pool.values():
327 for d
in (r.get(
"at_dose")
or {}):
330 print(
"\ndose ladder (rows reaching each rung, of "
331 f
"{len(pool)} reported)")
332 line =
" " +
" ".join(f
"{d:g}:{rungs[d]}"
333 for d
in sorted(rungs))
335 full = max(rungs)
if rungs
else 0
336 common = [d
for d
in sorted(rungs)
if rungs[d] == len(pool)]
337 print(f
" highest rung ALL reported rows reached: "
338 f
"{max(common):g}" if common
else
339 " no rung reached by every reported row")
341 if a.at_dose
is not None:
342 key = f
"{a.at_dose:g}"
343 n = sum(1
for r
in pool.values()
if key
in (r.get(
"at_dose")
or {}))
344 print(f
"\n at dose {key}: {n}/{len(pool)} reported rows have a value")
347 print(f
"\nETA to finish the design: {max(eta_hours):.1f} h "
348 f
"(slowest machine; optimistic - excludes rows in flight)")