fix(web-probe): report recent update resets separately
This commit is contained in:
@@ -1260,10 +1260,12 @@ function buildSampleMetrics(samples, control) {
|
||||
const turnTiming = buildTurnTimingTable(samples, timeline);
|
||||
const turnCells = turnTiming.rows.flatMap((row) => Object.values(row.cells || {}));
|
||||
const turnTimingNonMonotonic = Array.isArray(turnTiming.nonMonotonic) ? turnTiming.nonMonotonic : [];
|
||||
const turnTimingRecentUpdateResets = Array.isArray(turnTiming.recentUpdateResets) ? turnTiming.recentUpdateResets : [];
|
||||
const withTotal = timeline.filter((item) => item.totalElapsedSeconds !== null).length;
|
||||
const withRecent = timeline.filter((item) => item.recentUpdateSeconds !== null).length;
|
||||
const diagnostics = timeline.filter((item) => item.diagnosticSeen).length;
|
||||
const rounds = buildRoundMetricSummaries(timeline, promptCommands);
|
||||
const recentUpdateJumpCount = turnTimingNonMonotonic.filter((item) => item.metric === "recentUpdateSeconds" && item.anomaly === "jump").length;
|
||||
return {
|
||||
summary: {
|
||||
sampleCount: timeline.length,
|
||||
@@ -1278,12 +1280,15 @@ function buildSampleMetrics(samples, control) {
|
||||
turnCellsWithRecentUpdate: turnCells.filter((item) => item.recentUpdateSeconds !== null).length,
|
||||
turnTimingNonMonotonicCount: turnTimingNonMonotonic.length,
|
||||
turnTimingTotalElapsedDecreaseCount: turnTimingNonMonotonic.filter((item) => item.metric === "totalElapsedSeconds").length,
|
||||
turnTimingRecentUpdateDecreaseCount: turnTimingNonMonotonic.filter((item) => item.metric === "recentUpdateSeconds").length
|
||||
turnTimingRecentUpdateJumpCount: recentUpdateJumpCount,
|
||||
turnTimingRecentUpdateResetCount: turnTimingRecentUpdateResets.length,
|
||||
turnTimingRecentUpdateDecreaseCount: turnTimingRecentUpdateResets.length
|
||||
},
|
||||
rounds,
|
||||
turnColumns: turnTiming.columns,
|
||||
turnTimingTable: turnTiming.rows,
|
||||
turnTimingNonMonotonic,
|
||||
turnTimingRecentUpdateResets,
|
||||
timeline
|
||||
};
|
||||
}
|
||||
@@ -1343,11 +1348,18 @@ function buildTurnTimingTable(samples, timeline) {
|
||||
cells
|
||||
});
|
||||
}
|
||||
return { columns, rows, nonMonotonic: detectTurnTimingNonMonotonic(columns, rows) };
|
||||
const timingEvents = detectTurnTimingNonMonotonic(columns, rows);
|
||||
return {
|
||||
columns,
|
||||
rows,
|
||||
nonMonotonic: timingEvents.anomalies,
|
||||
recentUpdateResets: timingEvents.recentUpdateResets
|
||||
};
|
||||
}
|
||||
|
||||
function detectTurnTimingNonMonotonic(columns, rows) {
|
||||
const events = [];
|
||||
const anomalies = [];
|
||||
const recentUpdateResets = [];
|
||||
for (const column of columns) {
|
||||
const previousByMetric = new Map();
|
||||
for (const row of rows) {
|
||||
@@ -1358,11 +1370,12 @@ function detectTurnTimingNonMonotonic(columns, rows) {
|
||||
if (value === null || value === undefined || !Number.isFinite(Number(value))) continue;
|
||||
const current = Number(value);
|
||||
const previous = previousByMetric.get(metric);
|
||||
if (previous && current < previous.value) {
|
||||
events.push({
|
||||
if (previous && metric === "totalElapsedSeconds" && current < previous.value) {
|
||||
anomalies.push({
|
||||
columnId: column.id,
|
||||
columnLabel: column.label,
|
||||
metric,
|
||||
anomaly: "decrease",
|
||||
fromSeq: previous.seq,
|
||||
fromTs: previous.ts,
|
||||
fromValue: previous.value,
|
||||
@@ -1380,10 +1393,31 @@ function detectTurnTimingNonMonotonic(columns, rows) {
|
||||
if (previous && metric === "recentUpdateSeconds") {
|
||||
const elapsedMs = Date.parse(String(row.ts ?? "")) - Date.parse(String(previous.ts ?? ""));
|
||||
const elapsedSeconds = Number.isFinite(elapsedMs) && elapsedMs >= 0 ? elapsedMs / 1000 : null;
|
||||
const allowedIncrease = elapsedSeconds === null ? 10 : Math.max(10, elapsedSeconds + 5);
|
||||
const increase = current - previous.value;
|
||||
if (increase < 0) {
|
||||
recentUpdateResets.push({
|
||||
columnId: column.id,
|
||||
columnLabel: column.label,
|
||||
metric: "recentUpdateSeconds",
|
||||
event: "reset",
|
||||
fromSeq: previous.seq,
|
||||
fromTs: previous.ts,
|
||||
fromValue: previous.value,
|
||||
toSeq: row.seq ?? null,
|
||||
toTs: row.ts ?? null,
|
||||
toValue: current,
|
||||
delta: increase,
|
||||
sampleDeltaSeconds: elapsedSeconds,
|
||||
traceId: cell.traceId ?? column.traceId ?? null,
|
||||
messageId: cell.messageId ?? column.messageId ?? null,
|
||||
promptIndex: cell.promptIndex ?? row.promptIndex ?? null,
|
||||
source: cell.source ?? column.source ?? null,
|
||||
valuesRedacted: true
|
||||
});
|
||||
}
|
||||
const allowedIncrease = elapsedSeconds === null ? 10 : Math.max(10, elapsedSeconds + 5);
|
||||
if (increase > allowedIncrease) {
|
||||
events.push({
|
||||
anomalies.push({
|
||||
columnId: column.id,
|
||||
columnLabel: column.label,
|
||||
metric: "recentUpdateSeconds",
|
||||
@@ -1409,7 +1443,7 @@ function detectTurnTimingNonMonotonic(columns, rows) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return events;
|
||||
return { anomalies, recentUpdateResets };
|
||||
}
|
||||
|
||||
function turnMetricItems(sample, timelineItem) {
|
||||
@@ -1686,8 +1720,12 @@ function renderTurnTimingTable(sampleMetrics) {
|
||||
const nonMonotonic = Array.isArray(sampleMetrics?.turnTimingNonMonotonic) ? sampleMetrics.turnTimingNonMonotonic : [];
|
||||
const nonMonotonicLines = nonMonotonic.length > 0
|
||||
? nonMonotonic.slice(0, 80).map((item) => "- " + (item.columnLabel || item.columnId || "-") + " " + item.metric + (item.anomaly ? " " + item.anomaly : "") + " " + (item.fromValue ?? "-") + " -> " + (item.toValue ?? "-") + " delta=" + (item.delta ?? "-") + " sampleDelta=" + (item.sampleDeltaSeconds ?? "-") + " allowed=" + (item.allowedIncreaseSeconds ?? "-") + " seq " + (item.fromSeq ?? "-") + " -> " + (item.toSeq ?? "-") + " ts " + (item.fromTs || "-") + " -> " + (item.toTs || "-") + " traceId=" + (item.traceId || "-")).join("\n")
|
||||
: "- 未观察到数值下降事件。";
|
||||
return lines.join("\n") + "\n\n列说明:\n" + columnLines + "\n\n非单调/跳增事件(仅报表暴露,不做下游 repair):\n" + nonMonotonicLines;
|
||||
: "- 未观察到总耗时下降或最近更新异常跳增。";
|
||||
const recentUpdateResets = Array.isArray(sampleMetrics?.turnTimingRecentUpdateResets) ? sampleMetrics.turnTimingRecentUpdateResets : [];
|
||||
const resetLines = recentUpdateResets.length > 0
|
||||
? recentUpdateResets.slice(0, 80).map((item) => "- " + (item.columnLabel || item.columnId || "-") + " reset " + (item.fromValue ?? "-") + " -> " + (item.toValue ?? "-") + " delta=" + (item.delta ?? "-") + " sampleDelta=" + (item.sampleDeltaSeconds ?? "-") + " seq " + (item.fromSeq ?? "-") + " -> " + (item.toSeq ?? "-") + " ts " + (item.fromTs || "-") + " -> " + (item.toTs || "-") + " traceId=" + (item.traceId || "-")).join("\n")
|
||||
: "- 未观察到最近更新归零/回落。";
|
||||
return lines.join("\n") + "\n\n列说明:\n" + columnLines + "\n\n异常事件(仅报表暴露,不做下游 repair):\n" + nonMonotonicLines + "\n\n最近更新 reset 事件(预期三角波归零,不计为异常):\n" + resetLines;
|
||||
}
|
||||
|
||||
function formatMetricCell(value) {
|
||||
@@ -1745,6 +1783,12 @@ function renderMarkdown(report) {
|
||||
+ "- withRecentUpdate: " + (metricSummary.withRecentUpdate ?? 0) + "\n"
|
||||
+ "- diagnostics: " + (metricSummary.diagnostics ?? 0) + "\n"
|
||||
+ "- promptSegments: " + (metricSummary.promptSegments ?? 0) + "\n\n"
|
||||
+ "- turnColumns: " + (metricSummary.turnColumns ?? 0) + "\n"
|
||||
+ "- turnTimingRows: " + (metricSummary.turnTimingRows ?? 0) + "\n"
|
||||
+ "- turnTimingNonMonotonicCount: " + (metricSummary.turnTimingNonMonotonicCount ?? 0) + "\n"
|
||||
+ "- turnTimingTotalElapsedDecreaseCount: " + (metricSummary.turnTimingTotalElapsedDecreaseCount ?? 0) + "\n"
|
||||
+ "- turnTimingRecentUpdateJumpCount: " + (metricSummary.turnTimingRecentUpdateJumpCount ?? 0) + "\n"
|
||||
+ "- turnTimingRecentUpdateResetCount: " + (metricSummary.turnTimingRecentUpdateResetCount ?? 0) + "\n\n"
|
||||
+ "### Rounds\n\n" + roundLines + "\n\n"
|
||||
+ "### Prompt network\n\n" + promptNetworkLines + "\n\n"
|
||||
+ "### Runtime alerts\n\n"
|
||||
|
||||
Reference in New Issue
Block a user