feat(system): instrument worker pipeline stages

This commit is contained in:
DCCONSTRUCTIONS
2026-07-27 21:41:40 +03:00
parent 67e12a47a4
commit 667db00b52
10 changed files with 446 additions and 152 deletions
@@ -0,0 +1,72 @@
import type { CSSProperties } from "react";
import type { WorkerPipelineStage } from "../../core/system/workerTelemetry";
interface WorkerPipelineStagesProps {
stages: WorkerPipelineStage[];
}
function stateLabel(state: WorkerPipelineStage["state"]): string {
if (state === "active") return "выполняется";
if (state === "waiting") return "ожидает";
if (state === "ready") return "готов";
return "нет live-состояния";
}
function stageDuration(value: number | null): string {
if (typeof value !== "number" || !Number.isFinite(value)) return "—";
if (value < 1) return `${Math.round(value * 1000)} мс`;
return `${new Intl.NumberFormat("ru-RU", {
maximumFractionDigits: value < 10 ? 2 : 1,
}).format(value)} с`;
}
function stageDetail(stage: WorkerPipelineStage): string {
if (
typeof stage.activations !== "number"
|| typeof stage.share_percent !== "number"
|| !Number.isFinite(stage.share_percent)
) {
return "Измерений ещё нет";
}
const share = new Intl.NumberFormat("ru-RU", {
maximumFractionDigits: 1,
}).format(stage.share_percent);
return `${stage.activations} проходов · ${share}% измеренного времени`;
}
export function WorkerPipelineStages({ stages }: WorkerPipelineStagesProps) {
return (
<ol className="worker-stage-list" aria-label="Стадии текущей задачи">
{stages.map((stage, index) => {
const share = typeof stage.share_percent === "number"
? Math.max(0, Math.min(100, stage.share_percent))
: 0;
const style = {
"--worker-stage-share": `${share}%`,
} as CSSProperties;
return (
<li
key={stage.id}
data-state={stage.state}
style={style}
aria-label={`${stage.label}: ${stageDetail(stage)}`}
>
<span className="worker-stage-list__fill" aria-hidden="true" />
<span className="worker-stage-list__index">
{String(index + 1).padStart(2, "0")}
</span>
<div>
<strong>{stage.label}</strong>
<small>{stageDetail(stage)}</small>
</div>
<div className="worker-stage-list__value">
<strong>{stageDuration(stage.elapsed_seconds)}</strong>
<small>{stateLabel(stage.state)}</small>
</div>
</li>
);
})}
</ol>
);
}
@@ -51,6 +51,9 @@ export interface WorkerPipelineStage {
id: string;
label: string;
state: "active" | "waiting" | "ready" | "unavailable";
elapsed_seconds: number | null;
share_percent: number | null;
activations: number | null;
}
export interface WorkerTelemetryHistoryRow {
@@ -299,7 +299,7 @@
.worker-stage-list {
display: grid;
min-width: 0;
grid-template-columns: repeat(3, minmax(0, 1fr));
grid-template-columns: minmax(0, 1fr);
margin: 0;
padding: 0;
gap: 0.48rem;
@@ -307,12 +307,15 @@
}
.worker-stage-list li {
position: relative;
display: grid;
overflow: hidden;
min-width: 0;
grid-template-columns: auto minmax(0, 1fr) auto;
grid-template-columns: 2rem minmax(0, 1fr) minmax(7.5rem, auto);
align-items: center;
gap: 0.55rem;
padding: 0.66rem;
gap: 0.75rem;
padding: 0.72rem 0.8rem;
isolation: isolate;
border: 1px solid var(--station-hairline);
border-radius: var(--nodedc-radius-control);
background: var(--station-panel-soft);
@@ -323,21 +326,53 @@
background: var(--nodedc-focus-surface);
}
.worker-stage-list li > span,
.worker-stage-list li > small {
.worker-stage-list__fill {
position: absolute;
z-index: -1;
inset: 0 auto 0 0;
width: var(--worker-stage-share, 0%);
background: color-mix(
in srgb,
var(--nodedc-text-primary) 9%,
transparent
);
pointer-events: none;
}
.worker-stage-list li[data-state="active"] .worker-stage-list__fill {
background: color-mix(
in srgb,
var(--nodedc-text-primary) 15%,
transparent
);
}
.worker-stage-list__index,
.worker-stage-list small {
color: var(--nodedc-text-muted);
font-size: 0.53rem;
}
.worker-stage-list li > strong {
.worker-stage-list li > div {
display: grid;
min-width: 0;
gap: 0.2rem;
}
.worker-stage-list li strong {
min-width: 0;
overflow: hidden;
color: var(--nodedc-text-primary);
font-size: 0.62rem;
font-size: 0.65rem;
text-overflow: ellipsis;
white-space: nowrap;
}
.worker-stage-list__value {
justify-items: end;
text-align: right;
}
.network-stat-card {
display: grid;
align-content: center;
@@ -483,7 +518,6 @@
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.worker-stage-list,
.network-interface-list {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
@@ -522,12 +556,21 @@
.network-overview-grid,
.worker-hardware__facts,
.worker-runtime-grid,
.worker-stage-list,
.network-interface-list,
.network-profile__security {
grid-template-columns: 1fr;
}
.worker-stage-list li {
grid-template-columns: 1.5rem minmax(0, 1fr);
}
.worker-stage-list__value {
grid-column: 2;
justify-items: start;
text-align: left;
}
.worker-runtime-card dl,
.worker-pipeline__summary {
grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -6,6 +6,7 @@ import {
} from "@nodedc/ui-react";
import { TelemetrySeries } from "../../components/system/TelemetrySeries";
import { WorkerPipelineStages } from "../../components/system/WorkerPipelineStages";
import { WorkerRuntimeCard } from "../../components/system/WorkerRuntimeCard";
import {
formatBytes,
@@ -189,20 +190,7 @@ export function ComputeModulesWorkspace() {
<div><span>Inference success</span><strong>{node?.triton.requests_succeeded ?? "—"}</strong></div>
<div><span>Inference failed</span><strong>{node?.triton.requests_failed ?? "—"}</strong></div>
</div>
<ol className="worker-stage-list">
{(telemetry?.pipeline.stages ?? []).map((stage, index) => (
<li key={stage.id} data-state={stage.state}>
<span>{String(index + 1).padStart(2, "0")}</span>
<strong>{stage.label}</strong>
<small>{
stage.state === "active" ? "выполняется"
: stage.state === "waiting" ? "в очереди"
: stage.state === "ready" ? "готов"
: "нет данных"
}</small>
</li>
))}
</ol>
<WorkerPipelineStages stages={telemetry?.pipeline.stages ?? []} />
</GlassSurface>
<section className="system-runtime-section system-runtime-section--external">
@@ -14,6 +14,7 @@ test("Worker 006 telemetry remains a bounded system feature slice", async () =>
workspaceHub,
core,
computeWorkspace,
pipelineStages,
networkWorkspace,
styles,
] = await Promise.all([
@@ -21,6 +22,7 @@ test("Worker 006 telemetry remains a bounded system feature slice", async () =>
read("workspaces/Workspaces.tsx"),
read("core/system/workerTelemetry.ts"),
read("workspaces/system/ComputeModulesWorkspace.tsx"),
read("components/system/WorkerPipelineStages.tsx"),
read("workspaces/system/NetworkWorkspace.tsx"),
read("styles.css"),
]);
@@ -32,8 +34,12 @@ test("Worker 006 telemetry remains a bounded system feature slice", async () =>
assert.doesNotMatch(workspaceHub, /worker-telemetry|worker-profile|DESKTOP-OPJ8J04/);
assert.match(core, /\/api\/v1\/system\/worker-telemetry/);
assert.match(core, /\/api\/v1\/system\/worker-profile/);
assert.match(core, /share_percent/);
assert.doesNotMatch(core, /@nodedc\/ui-react/);
assert.match(computeWorkspace, /useWorkerTelemetry/);
assert.match(computeWorkspace, /WorkerPipelineStages/);
assert.match(pipelineStages, /измеренного времени/);
assert.doesNotMatch(pipelineStages, /CPU|GPU|hardware/);
assert.match(networkWorkspace, /127\.0\.0\.1:8000/);
assert.doesNotMatch(networkWorkspace, /8765/);
assert.match(styles, /system-telemetry\.css/);