feat(system): instrument worker pipeline stages
This commit is contained in:
@@ -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/);
|
||||
|
||||
Reference in New Issue
Block a user