Show reproducible job diagnostics
This commit is contained in:
@@ -67,6 +67,8 @@ without changing the original training scripts. Starting any web job or
|
|||||||
dataset YOLO shortcut automatically opens its live log; the SSE stream resumes
|
dataset YOLO shortcut automatically opens its live log; the SSE stream resumes
|
||||||
from the current log size after the initial tail so existing lines are not
|
from the current log size after the initial tail so existing lines are not
|
||||||
duplicated in the panel.
|
duplicated in the panel.
|
||||||
|
The selected-job panel also shows reproducibility diagnostics: command, cwd,
|
||||||
|
PID, exit code, log size, error text, and the exact task params submitted.
|
||||||
The task builder also reads `GET /api/system/gpus` and lets an operator choose
|
The task builder also reads `GET /api/system/gpus` and lets an operator choose
|
||||||
CPU or one or more GPUs before launch. Selected GPUs are passed to the backend
|
CPU or one or more GPUs before launch. Selected GPUs are passed to the backend
|
||||||
as `gpus`, exported as `CUDA_VISIBLE_DEVICES`, and reflected into YOLO/visual
|
as `gpus`, exported as `CUDA_VISIBLE_DEVICES`, and reflected into YOLO/visual
|
||||||
|
|||||||
@@ -61,6 +61,11 @@ def evaluate_project() -> dict:
|
|||||||
and "setResults(resultsNext)" in frontend_text
|
and "setResults(resultsNext)" in frontend_text
|
||||||
and "slice(0, 240)" not in frontend_text,
|
and "slice(0, 240)" not in frontend_text,
|
||||||
"job_progress_ui": "JobProgressBar" in frontend_text and "progressTrack" in frontend_text,
|
"job_progress_ui": "JobProgressBar" in frontend_text and "progressTrack" in frontend_text,
|
||||||
|
"job_diagnostics_ui": "JobDiagnostics" in frontend_text
|
||||||
|
and "jobDiagnostics" in frontend_text
|
||||||
|
and "command?.join" in frontend_text
|
||||||
|
and "job.cwd" in frontend_text
|
||||||
|
and "JSON.stringify(job.params" in frontend_text,
|
||||||
"gpu_task_selection_ui": "selectedGpuIds" in frontend_text
|
"gpu_task_selection_ui": "selectedGpuIds" in frontend_text
|
||||||
and "gpuSelector" in frontend_text
|
and "gpuSelector" in frontend_text
|
||||||
and "jobPayload" in frontend_text
|
and "jobPayload" in frontend_text
|
||||||
|
|||||||
@@ -41,9 +41,15 @@ type Job = {
|
|||||||
type: string;
|
type: string;
|
||||||
status: string;
|
status: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
command?: string[];
|
||||||
|
cwd?: string;
|
||||||
|
pid?: number | null;
|
||||||
|
exit_code?: number | null;
|
||||||
|
error?: string | null;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
started_at?: string;
|
started_at?: string;
|
||||||
finished_at?: string;
|
finished_at?: string;
|
||||||
|
log_path?: string;
|
||||||
log_tail?: string;
|
log_tail?: string;
|
||||||
log_size?: number;
|
log_size?: number;
|
||||||
params: Record<string, unknown>;
|
params: Record<string, unknown>;
|
||||||
@@ -1298,6 +1304,7 @@ function App() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{selectedJob && <JobProgressBar progress={selectedJob.progress} />}
|
{selectedJob && <JobProgressBar progress={selectedJob.progress} />}
|
||||||
|
{selectedJob && <JobDiagnostics job={selectedJob} />}
|
||||||
<pre>{log || "No log selected."}</pre>
|
<pre>{log || "No log selected."}</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1326,6 +1333,37 @@ function App() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function JobDiagnostics({ job }: { job: Job }) {
|
||||||
|
const command = job.command?.join(" ") ?? "";
|
||||||
|
return (
|
||||||
|
<div className="jobDiagnostics">
|
||||||
|
<div className="jobDetailGrid">
|
||||||
|
<div><span>ID</span><strong>{job.id.slice(0, 12)}</strong></div>
|
||||||
|
<div><span>PID</span><strong>{job.pid ?? "-"}</strong></div>
|
||||||
|
<div><span>Exit</span><strong>{job.exit_code ?? "-"}</strong></div>
|
||||||
|
<div><span>Log</span><strong>{formatBytes(job.log_size)}</strong></div>
|
||||||
|
</div>
|
||||||
|
{job.cwd && (
|
||||||
|
<div className="jobPath">
|
||||||
|
<span>CWD</span>
|
||||||
|
<code>{job.cwd}</code>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!!job.error && <div className="jobError">{job.error}</div>}
|
||||||
|
{!!command && (
|
||||||
|
<details className="jobDetailBlock" open>
|
||||||
|
<summary>Command</summary>
|
||||||
|
<pre>{command}</pre>
|
||||||
|
</details>
|
||||||
|
)}
|
||||||
|
<details className="jobDetailBlock">
|
||||||
|
<summary>Params</summary>
|
||||||
|
<pre>{JSON.stringify(job.params ?? {}, null, 2)}</pre>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function AgentCheckList({ checks, limit }: { checks: AgentCheck[]; limit: number }) {
|
function AgentCheckList({ checks, limit }: { checks: AgentCheck[]; limit: number }) {
|
||||||
if (!checks.length) {
|
if (!checks.length) {
|
||||||
return <p className="muted">尚未运行验证。</p>;
|
return <p className="muted">尚未运行验证。</p>;
|
||||||
|
|||||||
@@ -1346,6 +1346,90 @@ meter {
|
|||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.jobDiagnostics {
|
||||||
|
display: grid;
|
||||||
|
gap: 9px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jobDetailGrid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jobDetailGrid div {
|
||||||
|
min-width: 0;
|
||||||
|
display: grid;
|
||||||
|
gap: 3px;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #080a08;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jobDetailGrid span,
|
||||||
|
.jobPath span {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jobDetailGrid strong {
|
||||||
|
min-width: 0;
|
||||||
|
color: var(--ink);
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jobPath {
|
||||||
|
min-width: 0;
|
||||||
|
display: grid;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jobPath code {
|
||||||
|
display: block;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #080a08;
|
||||||
|
color: var(--muted);
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jobError {
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid rgba(240, 113, 103, 0.45);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--red);
|
||||||
|
background: rgba(240, 113, 103, 0.08);
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jobDetailBlock {
|
||||||
|
min-width: 0;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #080a08;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jobDetailBlock summary {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px;
|
||||||
|
color: var(--green);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jobDetailBlock pre {
|
||||||
|
min-height: 0;
|
||||||
|
max-height: 180px;
|
||||||
|
margin: 0;
|
||||||
|
border-width: 1px 0 0;
|
||||||
|
border-radius: 0;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
.curvePanel {
|
.curvePanel {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
@@ -1479,6 +1563,7 @@ meter {
|
|||||||
.gpuPicker,
|
.gpuPicker,
|
||||||
.taskCheckList,
|
.taskCheckList,
|
||||||
.agentCheckList,
|
.agentCheckList,
|
||||||
|
.jobDetailGrid,
|
||||||
.qualityStats,
|
.qualityStats,
|
||||||
.qualityChecks {
|
.qualityChecks {
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
|||||||
Reference in New Issue
Block a user