整合求解器活动监控与步长回归证据
同步远端 PNL0003 诊断和大采样网格能力,语义合并活动感知的 60 秒真停滞判定与旧后端 15 分钟兼容兜底。 纳管热路径优化、15 单元运行证据、浏览器与 API 报告,并补充北京时间更新日志和遗留问题。
This commit is contained in:
1 parent
c19cf77aee
commit
e18399c022
46 files changed
+181589
-170
No files matched your search
+223
-22
@@ -109,8 +109,12 @@ import {
|
||||
lmechn1RightPortCount,
|
||||
} from "./componentSymbols/mechanical";
|
||||
import {
|
||||
SIMULATION_ACTIVITY_STALL_TIMEOUT_MS,
|
||||
SIMULATION_ACTIVITY_STALL_TIMEOUT_SECONDS,
|
||||
SIMULATION_SOLVER_STALL_TIMEOUT_MINUTES,
|
||||
SIMULATION_SOLVER_STALL_TIMEOUT_MS,
|
||||
solverActivityStallTimeoutMessage,
|
||||
solverStallTimeoutMessage,
|
||||
solverStallTimeoutReached,
|
||||
} from "./simulationTimeout";
|
||||
import {
|
||||
CONTACT_AWARE_EDGE_TYPE,
|
||||
@@ -1053,6 +1057,37 @@ type SimulationStreamIssue = {
|
||||
message?: string;
|
||||
};
|
||||
|
||||
type SimulationActivityTelemetry = {
|
||||
activitySequence?: number;
|
||||
activityKind?: string;
|
||||
currentTrialTime?: number | null;
|
||||
rhsCallCount?: number;
|
||||
acceptedStepSequence?: number;
|
||||
acceptedTime?: number | null;
|
||||
};
|
||||
|
||||
export type SimulationActivityWatchdogStatus =
|
||||
| "progressing"
|
||||
| "slow-active"
|
||||
| "slow-telemetry-unavailable"
|
||||
| "stalled";
|
||||
|
||||
export type SimulationActivityWatchdogState = {
|
||||
lastAcceptedProgressAt: number;
|
||||
lastActivityChangeAt: number | null;
|
||||
lastActivitySequence: number | null;
|
||||
lastAcceptedStepSequence: number | null;
|
||||
lastAcceptedTime: number | null;
|
||||
integratingPhaseActive: boolean;
|
||||
};
|
||||
|
||||
export type SimulationActivityWatchdogObservation = {
|
||||
status: SimulationActivityWatchdogStatus;
|
||||
acceptedProgressIdleMs: number;
|
||||
activityIdleMs: number | null;
|
||||
activityTelemetryAvailable: boolean;
|
||||
};
|
||||
|
||||
type SimulationCancelReason = "user" | "stalled";
|
||||
|
||||
type SimulationTaskResponse = {
|
||||
@@ -1064,7 +1099,7 @@ type SimulationTaskResponse = {
|
||||
};
|
||||
|
||||
type SimulationStreamEvent =
|
||||
| {
|
||||
| ({
|
||||
event: "progress";
|
||||
progress: number;
|
||||
phase: string;
|
||||
@@ -1072,7 +1107,7 @@ type SimulationStreamEvent =
|
||||
heartbeat?: boolean;
|
||||
simulatedTime?: number;
|
||||
totalTime?: number;
|
||||
}
|
||||
} & SimulationActivityTelemetry)
|
||||
| {
|
||||
event: "result";
|
||||
progress: number;
|
||||
@@ -1092,6 +1127,137 @@ type SimulationStreamEvent =
|
||||
simulatedTime?: number;
|
||||
totalTime?: number;
|
||||
};
|
||||
|
||||
export function createSimulationActivityWatchdog(
|
||||
now = Date.now(),
|
||||
): SimulationActivityWatchdogState {
|
||||
return {
|
||||
lastAcceptedProgressAt: now,
|
||||
lastActivityChangeAt: null,
|
||||
lastActivitySequence: null,
|
||||
lastAcceptedStepSequence: null,
|
||||
lastAcceptedTime: null,
|
||||
integratingPhaseActive: false,
|
||||
};
|
||||
}
|
||||
|
||||
export function isSimulationSolverActivityPhase(phase: string): boolean {
|
||||
return phase === "integrating";
|
||||
}
|
||||
|
||||
export function observeSimulationActivityWatchdog(
|
||||
current: SimulationActivityWatchdogState,
|
||||
event: SimulationActivityTelemetry & { heartbeat?: boolean; phase?: string },
|
||||
now = Date.now(),
|
||||
stallTimeoutMs = SIMULATION_ACTIVITY_STALL_TIMEOUT_MS,
|
||||
legacyStallTimeoutMs = SIMULATION_SOLVER_STALL_TIMEOUT_MS,
|
||||
): {
|
||||
state: SimulationActivityWatchdogState;
|
||||
observation: SimulationActivityWatchdogObservation;
|
||||
} {
|
||||
const reportsPhase = typeof event.phase === "string";
|
||||
if (reportsPhase && !isSimulationSolverActivityPhase(event.phase!)) {
|
||||
return {
|
||||
state: createSimulationActivityWatchdog(now),
|
||||
observation: {
|
||||
status: "progressing",
|
||||
acceptedProgressIdleMs: 0,
|
||||
activityIdleMs: null,
|
||||
activityTelemetryAvailable: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const baseline =
|
||||
reportsPhase && !current.integratingPhaseActive
|
||||
? {
|
||||
...createSimulationActivityWatchdog(now),
|
||||
integratingPhaseActive: true,
|
||||
}
|
||||
: current;
|
||||
let acceptedProgress = event.heartbeat !== true;
|
||||
let lastAcceptedStepSequence = baseline.lastAcceptedStepSequence;
|
||||
let lastAcceptedTime = baseline.lastAcceptedTime;
|
||||
|
||||
if (
|
||||
typeof event.acceptedStepSequence === "number" &&
|
||||
Number.isFinite(event.acceptedStepSequence)
|
||||
) {
|
||||
if (
|
||||
lastAcceptedStepSequence !== null &&
|
||||
event.acceptedStepSequence !== lastAcceptedStepSequence
|
||||
) {
|
||||
acceptedProgress = true;
|
||||
}
|
||||
lastAcceptedStepSequence = event.acceptedStepSequence;
|
||||
}
|
||||
if (typeof event.acceptedTime === "number" && Number.isFinite(event.acceptedTime)) {
|
||||
if (lastAcceptedTime !== null && event.acceptedTime !== lastAcceptedTime) {
|
||||
acceptedProgress = true;
|
||||
}
|
||||
lastAcceptedTime = event.acceptedTime;
|
||||
}
|
||||
|
||||
let lastActivityChangeAt = baseline.lastActivityChangeAt;
|
||||
let lastActivitySequence = baseline.lastActivitySequence;
|
||||
const reportsActivitySequence =
|
||||
typeof event.activitySequence === "number" &&
|
||||
Number.isFinite(event.activitySequence);
|
||||
if (reportsActivitySequence) {
|
||||
if (
|
||||
lastActivitySequence === null ||
|
||||
event.activitySequence !== lastActivitySequence
|
||||
) {
|
||||
lastActivityChangeAt = now;
|
||||
}
|
||||
lastActivitySequence = event.activitySequence!;
|
||||
}
|
||||
|
||||
const lastAcceptedProgressAt = acceptedProgress
|
||||
? now
|
||||
: baseline.lastAcceptedProgressAt;
|
||||
const acceptedProgressIdleMs = Math.max(0, now - lastAcceptedProgressAt);
|
||||
const activityIdleMs =
|
||||
lastActivityChangeAt === null
|
||||
? null
|
||||
: Math.max(0, now - lastActivityChangeAt);
|
||||
const effectiveStallTimeoutMs = Math.max(0, stallTimeoutMs);
|
||||
const effectiveLegacyStallTimeoutMs = Math.max(
|
||||
effectiveStallTimeoutMs,
|
||||
legacyStallTimeoutMs,
|
||||
);
|
||||
let status: SimulationActivityWatchdogStatus = "progressing";
|
||||
if (acceptedProgressIdleMs >= effectiveStallTimeoutMs) {
|
||||
if (!reportsActivitySequence || activityIdleMs === null) {
|
||||
status =
|
||||
acceptedProgressIdleMs >= effectiveLegacyStallTimeoutMs
|
||||
? "stalled"
|
||||
: "slow-telemetry-unavailable";
|
||||
} else if (activityIdleMs >= effectiveStallTimeoutMs) {
|
||||
status = "stalled";
|
||||
} else {
|
||||
status = "slow-active";
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
state: {
|
||||
lastAcceptedProgressAt,
|
||||
lastActivityChangeAt,
|
||||
lastActivitySequence,
|
||||
lastAcceptedStepSequence,
|
||||
lastAcceptedTime,
|
||||
integratingPhaseActive:
|
||||
reportsPhase || baseline.integratingPhaseActive,
|
||||
},
|
||||
observation: {
|
||||
status,
|
||||
acceptedProgressIdleMs,
|
||||
activityIdleMs,
|
||||
activityTelemetryAvailable: reportsActivitySequence,
|
||||
},
|
||||
};
|
||||
}
|
||||
const SUPPORTED_SOLVER_METHODS = ["BDF", "Radau", "LSODA", "RK45", "RK23", "DOP853"];
|
||||
const simulationConfigLabels: Record<SimulationNumericKey, string> = {
|
||||
t_start: "起始时间",
|
||||
@@ -7355,10 +7521,11 @@ function FlowWorkbench() {
|
||||
setSimulationStopPending(false);
|
||||
let lastLoggedPhase = "";
|
||||
let lastLoggedBucket = -1;
|
||||
let lastWatchdogStatus: SimulationActivityWatchdogStatus = "progressing";
|
||||
try {
|
||||
const result = await streamSystemSimulation(
|
||||
xml,
|
||||
(event) => {
|
||||
(event, watchdog) => {
|
||||
if (event.event !== "progress") {
|
||||
return;
|
||||
}
|
||||
@@ -7373,14 +7540,38 @@ function FlowWorkbench() {
|
||||
"initialization",
|
||||
"initializing",
|
||||
].includes(event.phase);
|
||||
const progressMessage =
|
||||
watchdog?.status === "slow-active"
|
||||
? `正在处理耗时积分步,内部计算仍活跃${
|
||||
event.activityKind ? `(${event.activityKind})` : ""
|
||||
},可手动停止`
|
||||
: watchdog?.status === "slow-telemetry-unavailable"
|
||||
? "积分步耗时较长,后端未提供活动遥测;任务保持运行,可手动停止"
|
||||
: event.message;
|
||||
setSimulationProgress((current) => ({
|
||||
...current,
|
||||
state: "running",
|
||||
percent: normalizedProgress.percent,
|
||||
message: event.message,
|
||||
message: progressMessage,
|
||||
simulatedTime: normalizedProgress.simulatedTime,
|
||||
totalTime: normalizedProgress.totalTime,
|
||||
}));
|
||||
if (watchdog && watchdog.status !== lastWatchdogStatus) {
|
||||
if (watchdog.status === "slow-active") {
|
||||
appendConsoleEntry(
|
||||
"warning",
|
||||
`积分步超过 ${SIMULATION_ACTIVITY_STALL_TIMEOUT_SECONDS} 秒尚未接受,但后端内部活动仍在推进;继续等待,可随时手动停止`,
|
||||
);
|
||||
} else if (watchdog.status === "slow-telemetry-unavailable") {
|
||||
appendConsoleEntry(
|
||||
"warning",
|
||||
`积分步超过 ${SIMULATION_ACTIVITY_STALL_TIMEOUT_SECONDS} 秒尚未接受,且后端未提供活动遥测;将按 ${SIMULATION_SOLVER_STALL_TIMEOUT_MINUTES} 分钟保守上限继续等待,可手动停止`,
|
||||
);
|
||||
} else if (lastWatchdogStatus !== "progressing") {
|
||||
appendConsoleEntry("info", "求解器已接受新的积分步,仿真继续推进");
|
||||
}
|
||||
lastWatchdogStatus = watchdog.status;
|
||||
}
|
||||
const progressBucket = Math.floor(normalizedProgress.percent / 10);
|
||||
if (event.phase !== lastLoggedPhase || progressBucket !== lastLoggedBucket) {
|
||||
const progressDetail = reportsSimulationTime
|
||||
@@ -7404,7 +7595,9 @@ function FlowWorkbench() {
|
||||
appendConsoleEntry(
|
||||
"error",
|
||||
error.code === "SOLVER_STALLED"
|
||||
? solverStallTimeoutMessage("recovering")
|
||||
? error.message.includes("内部活动")
|
||||
? solverActivityStallTimeoutMessage("recovering")
|
||||
: solverStallTimeoutMessage("recovering")
|
||||
: "超过 30 秒未收到后端数据,正在终止任务并恢复部分结果",
|
||||
);
|
||||
try {
|
||||
@@ -10882,7 +11075,10 @@ async function readSimulationStreamChunk(
|
||||
|
||||
async function streamSystemSimulation(
|
||||
xml: string,
|
||||
onEvent: (event: SimulationStreamEvent) => void,
|
||||
onEvent: (
|
||||
event: SimulationStreamEvent,
|
||||
watchdog?: SimulationActivityWatchdogObservation,
|
||||
) => void,
|
||||
simulationId: string,
|
||||
): Promise<SimulationResult> {
|
||||
const abortController = new AbortController();
|
||||
@@ -10920,7 +11116,7 @@ async function streamSystemSimulation(
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = "";
|
||||
let result: SimulationResult | null = null;
|
||||
let lastSolverProgressAt = Date.now();
|
||||
let activityWatchdog = createSimulationActivityWatchdog();
|
||||
|
||||
const consumeLine = (line: string) => {
|
||||
const trimmed = line.trim();
|
||||
@@ -10936,23 +11132,28 @@ async function streamSystemSimulation(
|
||||
throw new SimulationStreamError("仿真服务返回了无法识别的进度消息");
|
||||
}
|
||||
const event = parsed as SimulationStreamEvent;
|
||||
let watchdogObservation: SimulationActivityWatchdogObservation | undefined;
|
||||
if (event.event === "progress") {
|
||||
if (
|
||||
event.heartbeat === true &&
|
||||
solverStallTimeoutReached(lastSolverProgressAt)
|
||||
) {
|
||||
throw new SimulationStreamError(
|
||||
solverStallTimeoutMessage("detected"),
|
||||
[],
|
||||
undefined,
|
||||
"SOLVER_STALLED",
|
||||
);
|
||||
}
|
||||
if (event.heartbeat !== true) {
|
||||
lastSolverProgressAt = Date.now();
|
||||
const watched = observeSimulationActivityWatchdog(
|
||||
activityWatchdog,
|
||||
event,
|
||||
);
|
||||
activityWatchdog = watched.state;
|
||||
if (isSimulationSolverActivityPhase(event.phase)) {
|
||||
watchdogObservation = watched.observation;
|
||||
if (watchdogObservation.status === "stalled") {
|
||||
throw new SimulationStreamError(
|
||||
watchdogObservation.activityTelemetryAvailable
|
||||
? solverActivityStallTimeoutMessage("detected")
|
||||
: solverStallTimeoutMessage("detected"),
|
||||
[],
|
||||
undefined,
|
||||
"SOLVER_STALLED",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
onEvent(event);
|
||||
onEvent(event, watchdogObservation);
|
||||
if (event.event === "result") {
|
||||
result = event.result;
|
||||
return;
|
||||
|
||||
Reference in new issue
Block a user