Compare commits

...

4 Commits

Author SHA1 Message Date
Sung-Hyun Kang
1a1aca5bd5 Added snapshot 2026-02-18 12:26:48 -06:00
Sung-Hyun Kang
e03de89bf3 Remove duplicate span for allowed maximum throughput 2026-02-18 11:07:26 -06:00
sunghyunkang1111
cc26e2800e Fix health metrics race condition and improve expected-failure handling (#2387)
* Fix health monitoring

* fix compile error
2026-02-17 12:54:04 -06:00
Laurent Nguyen
39ac7cf3f2 Add SC1010 to codes redacted in syntax error messages (#2388) 2026-02-16 18:34:54 +01:00
6 changed files with 53 additions and 36 deletions

View File

@@ -6,7 +6,7 @@ import { MinimalQueryIterator, nextPage } from "../IteratorUtilities";
// Redact sensitive information from BadRequest errors with specific codes
export const redactSyntaxErrorMessage = (error: unknown): unknown => {
const codesToRedact = ["SC1001", "SC2001"];
const codesToRedact = ["SC1001", "SC2001", "SC1010"];
try {
// Handle error objects with a message property

View File

@@ -543,9 +543,6 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
<span style={{ float: "left", transform: "translateX(-50%)" }}>
{this.props.instantMaximumThroughput.toLocaleString(ThroughputInputAutoPilotV3Component.LOCALE_EN_US)}
</span>
<span style={{ float: "right" }}>
{this.props.softAllowedMaximumThroughput.toLocaleString(ThroughputInputAutoPilotV3Component.LOCALE_EN_US)}
</span>
<span style={{ float: "right" }} data-test="soft-allowed-maximum-throughput">
{this.props.softAllowedMaximumThroughput.toLocaleString(ThroughputInputAutoPilotV3Component.LOCALE_EN_US)}
</span>

View File

@@ -426,15 +426,6 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
>
5,000
</span>
<span
style={
{
"float": "right",
}
}
>
1,000,000
</span>
<span
data-test="soft-allowed-maximum-throughput"
style={
@@ -1034,15 +1025,6 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
>
5,000
</span>
<span
style={
{
"float": "right",
}
}
>
1,000,000
</span>
<span
data-test="soft-allowed-maximum-throughput"
style={
@@ -1620,15 +1602,6 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
>
5,000
</span>
<span
style={
{
"float": "right",
}
}
>
1,000,000
</span>
<span
data-test="soft-allowed-maximum-throughput"
style={

View File

@@ -120,7 +120,7 @@ const App = (): JSX.Element => {
}, [explorer]);
// Track interactive phase for both ContainerCopyPanel and DivExplorer paths
useInteractive(MetricScenario.ApplicationLoad);
useInteractive(MetricScenario.ApplicationLoad, !!config);
if (!explorer) {
return <LoadingExplorer />;

View File

@@ -56,6 +56,13 @@ class ScenarioMonitor {
});
}
private devLog(msg: string) {
if (process.env.NODE_ENV === "development") {
// eslint-disable-next-line no-console
console.log(`[Metrics] ${msg}`);
}
}
start(scenario: MetricScenario) {
if (this.contexts.has(scenario)) {
return;
@@ -86,6 +93,10 @@ class ScenarioMonitor {
ctx.phases.set(phase, { startMarkName: phaseStartMarkName });
});
this.devLog(
`scenario_start: ${scenario} | phases=${config.requiredPhases.join(", ")} | timeout=${config.timeoutMs}ms`,
);
traceMark(Action.MetricsScenario, {
event: "scenario_start",
scenario,
@@ -136,6 +147,12 @@ class ScenarioMonitor {
const endTimeISO = endEntry ? new Date(navigationStart + endEntry.startTime).toISOString() : undefined;
const durationMs = startEntry && endEntry ? endEntry.startTime - startEntry.startTime : undefined;
this.devLog(
`phase_complete: ${scenario}.${phase} | ${
durationMs !== null && durationMs !== undefined ? `${Math.round(durationMs)}ms` : "?"
} | ${ctx.completed.size}/${ctx.config.requiredPhases.length} phases`,
);
traceSuccess(Action.MetricsScenario, {
event: "phase_complete",
scenario,
@@ -155,6 +172,13 @@ class ScenarioMonitor {
return;
}
// If an expected failure was flagged (auth, firewall, etc.), treat as success.
if (ctx.hasExpectedFailure) {
this.devLog(`phase_fail: ${scenario}.${phase} — expected failure, completing as healthy`);
this.completePhase(scenario, phase);
return;
}
// Mark the explicitly failed phase
performance.mark(`scenario_${scenario}_${phase}_failed`);
ctx.failed.add(phase);
@@ -169,6 +193,12 @@ class ScenarioMonitor {
// Build a snapshot with failure info
const failureSnapshot = this.buildSnapshot(ctx, { final: false, timedOut: false });
this.devLog(
`phase_fail: ${scenario}.${phase} | failed=[${Array.from(ctx.failed).join(", ")}] | completed=[${Array.from(
ctx.completed,
).join(", ")}]`,
);
traceFailure(Action.MetricsScenario, {
event: "phase_fail",
scenario,
@@ -177,7 +207,7 @@ class ScenarioMonitor {
completedPhases: Array.from(ctx.completed).join(","),
});
// Emit unhealthy immediately
// Emit unhealthy immediately for unexpected failures
this.emit(ctx, false, false, failureSnapshot);
}
@@ -270,6 +300,19 @@ class ScenarioMonitor {
ttfb: finalSnapshot.vitals?.ttfb,
});
this.devLog(
`scenario_end: ${ctx.scenario} | ${healthy ? "healthy" : "unhealthy"} | ${
timedOut ? "timed out" : `${Math.round(finalSnapshot.durationMs)}ms`
} | ${JSON.stringify({
completedPhases: finalSnapshot.completed.join(", "),
failedPhases: finalSnapshot.failedPhases?.join(", ") || "none",
platform,
api,
phaseTimings: finalSnapshot.phaseTimings,
vitals: finalSnapshot.vitals,
})}`,
);
// Call portal backend health metrics endpoint
// If healthy is true (either completed successfully or timeout with expected failure), report healthy
if (healthy) {

View File

@@ -7,14 +7,18 @@ import { ApplicationMetricPhase, CommonMetricPhase } from "./ScenarioConfig";
* Hook to automatically complete the Interactive phase when the component becomes interactive.
* Uses requestAnimationFrame to complete after the browser has painted.
*/
export function useInteractive(scenario: MetricScenario) {
export function useInteractive(scenario: MetricScenario, enabled = true) {
const { completePhase } = useMetricScenario();
React.useEffect(() => {
requestAnimationFrame(() => {
if (!enabled) {
return undefined;
}
const id = requestAnimationFrame(() => {
completePhase(scenario, CommonMetricPhase.Interactive);
});
}, [scenario, completePhase]);
return () => cancelAnimationFrame(id);
}, [scenario, completePhase, enabled]);
}
/**