feat(monitor): 每个阶段都按实时数据算剩余时间,用时精确到秒——打包按已打包分片数、预训练用脚本自报 eta_h、APG 用 it/min、挖错用本轮 APG + 已完成轮次平均时长、头门禁用已完成那次评估、一致性门禁读日志里的 k/300 行(只读日志尾部,不占 CPU);修 mine 的当前轮判断(原来 len(cur)>k 在第二轮起永远判不到在跑)与 APG 漏加的 eta;总用时/本步用时/步骤条用时改为带秒,预估仍只到分钟

This commit is contained in:
cbb
2026-10-18 17:59:46 +08:00
parent faf6dd2748
commit 4a59ca84f6
2 changed files with 57 additions and 21 deletions
+6 -6
View File
@@ -224,7 +224,7 @@ const $ = id => document.getElementById(id);
const isNum = v => typeof v === 'number' && isFinite(v);
const fmt = (v, d = 3) => !isNum(v) ? '—' : (Math.abs(v) >= 1000 ? Math.round(v).toLocaleString() : Math.abs(v) >= 100 ? v.toFixed(1) : Math.abs(v) >= 1 ? v.toFixed(Math.min(d, 3)) : v === 0 ? '0' : v.toPrecision(3));
const pct = (v, d = 1) => !isNum(v) ? '—' : (100 * v).toFixed(d) + '%';
const hms = s => { if (!isNum(s)) return '—'; s = Math.max(0, s); const h = Math.floor(s / 3600), m = Math.floor(s % 3600 / 60); return h ? `${h} 小时 ${m} 分` : m ? `${m} 分 ${Math.floor(s % 60)} 秒` : `${Math.floor(s)} 秒`; };
const hms = s => { if (!isNum(s)) return '—'; s = Math.max(0, s); const h = Math.floor(s / 3600), m = Math.floor(s % 3600 / 60); return h ? `${h} 小时 ${m} 分 ${Math.floor(s % 60)} 秒` : m ? `${m} 分 ${Math.floor(s % 60)} 秒` : `${Math.floor(s)} 秒`; }; // 用时精确到秒(页面 1 秒轮询,秒位是有意义的)
const hmsShort = s => !isNum(s) ? '—' : s >= 3600 ? `${(s / 3600).toFixed(1)} h` : s >= 60 ? `${Math.round(s / 60)} 分` : `${Math.round(s)} 秒`;
const ago = t => { const s = Date.now() / 1000 - t; return s < 60 ? `${Math.max(0, Math.floor(s))} 秒前` : s < 3600 ? `${Math.floor(s / 60)} 分钟前` : `${(s / 3600).toFixed(1)} 小时前`; };
const ICON_WAIT = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>';
@@ -420,7 +420,7 @@ function renderProgress() {
<div class="facts">
${fact('步数', `${step.toLocaleString()}${total ? ' / ' + total.toLocaleString() : ''}`)}
${fact('已用时间', hms(elapsed))}
${fact('剩余时间', finished ? '—' : hms(eta))}
${fact('剩余时间', finished ? '—' : hmsShort(eta))} // 预估只到分钟(用时才精确到秒)
${fact('预计完成', fin)}
${kind === 'apg' ? fact('本批展开', r.plans ? `${r.plans} 次推理` : '—') : fact('学习率', isNum(r.lr) ? r.lr.toExponential(2) : '—')}
${fact('日志更新', lastMtime ? ago(lastMtime) : '—', running ? '' : 'warn')}
@@ -508,7 +508,7 @@ function stages(s) {
const r = P.stages[k], nm = SM[k].n;
if (!r) return [nm, 'pending', '—'];
if (P.current === k) return [nm, 'active', P.progress && P.progress.frac != null ? pct(P.progress.frac, 0) : '进行中'];
if (r.rc === 0) return [nm, 'done', '用时 ' + hmsShort(el[k] != null ? el[k] : r.end - r.start)];
if (r.rc === 0) return [nm, 'done', '用时 ' + hms(el[k] != null ? el[k] : r.end - r.start)];
if (r.rc === -15 || r.rc === 143) return [nm, 'fail', '已停止'];
if (r.rc != null) return [nm, 'fail', `失败(返回码 ${r.rc})`];
return [nm, 'fail', '中断'];
@@ -631,7 +631,7 @@ function renderPipe(s) {
const doneN = plan.filter(k => P.stages[k] && P.stages[k].rc === 0 && P.current !== k).length;
const failed = plan.find(k => P.stages[k] && P.stages[k].rc != null && P.stages[k].rc !== 0);
const allDone = !cur && doneN === plan.length;
let remain = 0; for (const k of plan) { const r = P.stages[k]; if (!r || r.rc == null) remain += (SM[k]?.w || 0.1) * (k === cur ? 1 - (pr.frac || 0) : 1); }
let remain = 0, calib = false; for (const k of plan) { const r = P.stages[k]; if (!r || r.rc == null) { if (k === cur && pr.eta_s != null) { remain += pr.eta_s / 3600; calib = true; } else remain += (SM[k]?.w || 0.1) * (k === cur ? 1 - (pr.frac || 0) : 1); } }
const head = cur ? `<div class="now">正在运行<b>${SM[cur]?.n || cur}</b>${pr.label || ''}</div>`
: allDone ? `<div class="now">状态<b class="ok">全部完成</b>共 ${plan.length} 步</div>`
: failed ? `<div class="now">状态<b class="bad">停在「${SM[failed]?.n || failed}」</b>${(P.stages[failed].info || {}).timed_out || P.stages[failed].rc === 124 ? '超过时间上限,判定卡死' : P.stages[failed].rc === -15 || P.stages[failed].rc === 143 ? '已停止' : '失败,看终端输出'}</div>`
@@ -644,8 +644,8 @@ function renderPipe(s) {
<div class="facts">
<div class="fact"><div class="k">总用时</div><div class="v">${hms(plan.reduce((a, k) => a + ((P.elapsed || {})[k] || 0), 0))}</div></div>
<div class="fact"><div class="k">本步用时</div><div class="v">${cur ? hms((P.elapsed || {})[cur] || 0) : '—'}</div></div>
<div class="fact"><div class="k">本步粗估 / 上限</div><div class="v">${cur ? hmsShort((SM[cur]?.w || 0) * 3600) + ' / ' + hmsShort((cr && cr.timeout_h || SM[cur]?.to || 0) * 3600) : '—'}</div></div>
<div class="fact"><div class="k">全流程剩余(粗估)</div><div class="v">${allDone ? '—' : '约 ' + hmsShort(remain * 3600)}</div></div>
<div class="fact"><div class="k">${cur && pr.eta_s != null ? '本步预计剩余(实测速率)' : '本步粗估 / 上限'}</div><div class="v">${cur ? (pr.eta_s != null ? hmsShort(pr.eta_s) + ' / 上限 ' + hmsShort((cr && cr.timeout_h || SM[cur]?.to || 0) * 3600) : hmsShort((SM[cur]?.w || 0) * 3600) + ' / ' + hmsShort((cr && cr.timeout_h || SM[cur]?.to || 0) * 3600)) : '—'}</div></div>
<div class="fact"><div class="k">全流程剩余(${calib ? '实测外推' : '粗估'})</div><div class="v">${allDone ? '—' : '约 ' + hmsShort(remain * 3600)}</div></div>
</div>${btns}`;
}
function renderJob() {