fix: add axes to sentinel memory chart

This commit is contained in:
Codex
2026-07-01 02:44:58 +00:00
parent c61ba69ce7
commit 5f00fe4cf3
4 changed files with 130 additions and 15 deletions
@@ -977,13 +977,12 @@ select {
.memory-chart {
display: block;
width: 100%;
height: 154px;
height: 192px;
border: 1px solid var(--line);
border-radius: 8px;
background: #ffffff;
}
.memory-axis,
.memory-legend {
display: flex;
min-width: 0;
@@ -993,6 +992,44 @@ select {
font-size: 12px;
}
.memory-grid-line {
stroke: #e5ecea;
stroke-width: 1;
}
.memory-axis-line {
stroke: #71817c;
stroke-width: 1.2;
}
.memory-tick-line {
stroke: #71817c;
stroke-width: 1;
}
.memory-axis-label {
fill: var(--muted);
font-size: 11px;
}
.memory-axis-label-y {
text-anchor: end;
}
.memory-axis-label-x {
text-anchor: middle;
}
.memory-axis-title {
fill: #4f625d;
font-size: 11px;
font-weight: 700;
}
.memory-axis-title-x {
text-anchor: middle;
}
.detail-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -4,6 +4,15 @@ import { createApp, computed, onMounted, ref, watch } from "./vendor/vue.esm-bro
const bootstrap = readBootstrap();
const internalTextPattern = /水合|投影|Trace|trace|Shell|API|DOM|Console|console|Runner|runner|JSONL|steer|facts|分页|HTTP|http|requestfailed|pageerror|Final Response|Code Agent|web-probe|observe|analyzer|终态/u;
const memoryChartFrame = Object.freeze({
left: 64,
right: 696,
top: 20,
bottom: 132,
yLabelX: 56,
xLabelY: 154,
xTitleY: 172,
});
createApp({
setup() {
@@ -113,6 +122,18 @@ createApp({
});
const detailMemoryMax = computed(() => Math.max(1, ...detailMemorySeries.value.flatMap((series) => series.points.map((point) => number(point.memoryMb)))));
const detailMemoryDurationMax = computed(() => Math.max(1, ...detailMemorySeries.value.flatMap((series) => series.points.map((point) => number(point.elapsedMinutes)))));
const detailMemoryYAxisTicks = computed(() => memoryTickValues(detailMemoryMax.value).map((value) => ({
key: `memory-y-${value}`,
value,
y: memoryYValue(value),
label: formatMb(value),
})));
const detailMemoryXAxisTicks = computed(() => memoryTickValues(detailMemoryDurationMax.value).map((value) => ({
key: `memory-x-${value}`,
value,
x: memoryXValue(value),
label: formatMinuteTick(value),
})));
const scopedCheckFindings = computed(() => {
if (checkScope.value === "history") return historicalCheckFindings.value;
return runDetailCheckFindings.value;
@@ -318,12 +339,22 @@ createApp({
function memoryX(point) {
const elapsed = number(point?.elapsedMinutes);
return Math.round(24 + (elapsed / detailMemoryDurationMax.value) * 672);
return memoryXValue(elapsed);
}
function memoryY(point) {
const memory = number(point?.memoryMb);
return Math.round(126 - (memory / detailMemoryMax.value) * 102);
return memoryYValue(memory);
}
function memoryXValue(elapsedMinutes) {
const ratio = clamp(number(elapsedMinutes) / detailMemoryDurationMax.value, 0, 1);
return Math.round(memoryChartFrame.left + ratio * (memoryChartFrame.right - memoryChartFrame.left));
}
function memoryYValue(memoryMb) {
const ratio = clamp(number(memoryMb) / detailMemoryMax.value, 0, 1);
return Math.round(memoryChartFrame.bottom - ratio * (memoryChartFrame.bottom - memoryChartFrame.top));
}
function memoryLineClass(index) {
@@ -402,6 +433,9 @@ createApp({
detailMemorySeries,
detailMemoryMax,
detailMemoryDurationMax,
detailMemoryYAxisTicks,
detailMemoryXAxisTicks,
memoryFrame: memoryChartFrame,
scopedCheckFindings,
scopedCheckSummary,
visibleCheckFindings,
@@ -724,10 +758,23 @@ createApp({
<span class="tag">每个页面一条线 · 峰值 {{ formatMb(detailMemory.maxMemoryMb || detailMemoryMax) }}</span>
</div>
<div v-if="detailMemorySeries.length > 0" class="memory-chart-wrap">
<svg class="memory-chart" viewBox="0 0 720 142" role="img" aria-label="运行详情页面内存曲线">
<line class="trend-grid-line" x1="24" x2="696" y1="24" y2="24"></line>
<line class="trend-grid-line" x1="24" x2="696" y1="75" y2="75"></line>
<line class="trend-grid-line" x1="24" x2="696" y1="126" y2="126"></line>
<svg class="memory-chart" viewBox="0 0 720 178" role="img" aria-label="运行详情页面内存曲线,纵轴为 MB,横轴为运行分钟">
<g data-memory-axis-y="true">
<g v-for="tick in detailMemoryYAxisTicks" :key="tick.key" data-memory-axis-y-tick="true">
<line class="memory-grid-line" :x1="memoryFrame.left" :x2="memoryFrame.right" :y1="tick.y" :y2="tick.y"></line>
<text class="memory-axis-label memory-axis-label-y" :x="memoryFrame.yLabelX" :y="tick.y + 4">{{ tick.label }}</text>
</g>
<line class="memory-axis-line" :x1="memoryFrame.left" :x2="memoryFrame.left" :y1="memoryFrame.top" :y2="memoryFrame.bottom"></line>
<text class="memory-axis-title memory-axis-title-y" x="18" y="78" transform="rotate(-90 18 78)">内存 MB</text>
</g>
<g data-memory-axis-x="true">
<line class="memory-axis-line" :x1="memoryFrame.left" :x2="memoryFrame.right" :y1="memoryFrame.bottom" :y2="memoryFrame.bottom"></line>
<g v-for="tick in detailMemoryXAxisTicks" :key="tick.key" data-memory-axis-x-tick="true">
<line class="memory-tick-line" :x1="tick.x" :x2="tick.x" :y1="memoryFrame.bottom" :y2="memoryFrame.bottom + 6"></line>
<text class="memory-axis-label memory-axis-label-x" :x="tick.x" :y="memoryFrame.xLabelY">{{ tick.label }}</text>
</g>
<text class="memory-axis-title memory-axis-title-x" :x="(memoryFrame.left + memoryFrame.right) / 2" :y="memoryFrame.xTitleY">运行分钟</text>
</g>
<polyline
v-for="(series, index) in detailMemorySeries"
:key="series.key || series.label || index"
@@ -736,11 +783,6 @@ createApp({
:points="memoryPolyline(series)"
></polyline>
</svg>
<div class="memory-axis">
<span>0m</span>
<span>{{ formatMinutes(detailMemoryDurationMax) }}</span>
<span>峰值 {{ formatMb(detailMemory.maxMemoryMb || detailMemoryMax) }}</span>
</div>
<div class="memory-legend">
<span
v-for="(series, index) in detailMemorySeries"
@@ -1065,6 +1107,14 @@ function formatMinutes(value) {
return `${Number.isInteger(rounded) ? String(rounded) : String(rounded).replace(/0+$/u, "").replace(/\.$/u, "")} 分钟`;
}
function formatMinuteTick(value) {
const numberValue = optionalNumber(value);
if (numberValue === null) return "-";
const rounded = Math.round(numberValue * 100) / 100;
const text = Number.isInteger(rounded) ? String(rounded) : String(rounded).replace(/0+$/u, "").replace(/\.$/u, "");
return `${text}m`;
}
function formatMb(value) {
const numberValue = optionalNumber(value);
if (numberValue === null) return "-";
@@ -1073,6 +1123,11 @@ function formatMb(value) {
return `${text} MB`;
}
function memoryTickValues(maxValue) {
const max = Math.max(1, number(maxValue));
return [0, max / 2, max];
}
function runDurationText(run) {
const timing = run?.timing || {};
return formatMinutes(optionalNumber(run?.runDurationMinutes, run?.durationMinutes, timing.runDurationMinutes, timing.durationMinutes));
+18 -1
View File
@@ -687,6 +687,7 @@ const dom = await page.evaluate(async ({ expectedRoutePrefix, expectedSentinelId
const targetDetailPayload = objectOrNull(targetDetailResult.body) || {};
const targetMemory = objectOrNull(targetDetailPayload.memory) || {};
const targetPageSeries = Array.isArray(targetMemory.pageSeries) ? targetMemory.pageSeries : [];
const memoryAxisText = String(memoryChart?.textContent || "").replace(/\s+/g, " ").trim();
const memorySummary = {
present: Boolean(memoryChart),
runId: memoryChart?.getAttribute("data-memory-run-id") || null,
@@ -703,6 +704,12 @@ const dom = await page.evaluate(async ({ expectedRoutePrefix, expectedSentinelId
apiSampleCount: numberValue(targetMemory.sampleCount),
apiSource: targetMemory.source || null,
perPageLines: false,
axisX: Boolean(memoryChart?.querySelector("[data-memory-axis-x='true']")),
axisY: Boolean(memoryChart?.querySelector("[data-memory-axis-y='true']")),
axisXTickCount: document.querySelectorAll(".memory-chart [data-memory-axis-x-tick='true']").length,
axisYTickCount: document.querySelectorAll(".memory-chart [data-memory-axis-y-tick='true']").length,
axisHasUnits: memoryAxisText.includes("MB") && memoryAxisText.includes("运行分钟"),
axesOk: false,
};
memorySummary.perPageLines = memorySummary.present === true
&& memorySummary.matchesTargetRun === true
@@ -711,6 +718,11 @@ const dom = await page.evaluate(async ({ expectedRoutePrefix, expectedSentinelId
&& memorySummary.legendCount === memorySummary.pageCount
&& memorySummary.apiOk === true
&& memorySummary.apiPageCount === memorySummary.pageCount;
memorySummary.axesOk = memorySummary.axisX === true
&& memorySummary.axisY === true
&& memorySummary.axisXTickCount >= 3
&& memorySummary.axisYTickCount >= 3
&& memorySummary.axisHasUnits === true;
const datasetSentinelId = String(dataset.sentinelId || "");
const finalPath = new URL(window.location.href).pathname.replace(/\/+$/u, "") || "/";
const expectedPath = expectedRoutePrefix.replace(/\/+$/u, "") || "/";
@@ -770,6 +782,7 @@ const dom = await page.evaluate(async ({ expectedRoutePrefix, expectedSentinelId
latestFindingTypeCount: latestRunCounts.typeCount,
trendCurves: chartTiming.ok,
memoryPerPageLines: memorySummary.perPageLines,
memoryAxes: memorySummary.axesOk,
},
sentinelBoundary,
title: document.title,
@@ -822,6 +835,7 @@ const ok = !navigationError
&& dom.requestedRunSelection?.ok === true
&& dom.chartTiming?.ok === true
&& dom.memorySummary?.perPageLines === true
&& dom.memorySummary?.axesOk === true
&& dom.layout?.horizontalOverflow !== true
&& pageErrors.length === 0;
@@ -1326,7 +1340,7 @@ function renderDashboardResult(result: Record<string, unknown>): string {
chartTiming.hasWarningCurve ?? "-",
]]),
"",
table(["MEMORY_CHART", "MEMORY_RUN", "MEMORY_MATCH", "MEMORY_PAGES", "MEMORY_CURVES", "MEMORY_SAMPLES", "API_PAGES", "MEMORY_PAGE_LINES", "MEMORY_SOURCE"], [[
table(["MEMORY_CHART", "MEMORY_RUN", "MEMORY_MATCH", "MEMORY_PAGES", "MEMORY_CURVES", "MEMORY_SAMPLES", "API_PAGES", "MEMORY_PAGE_LINES", "MEMORY_AXES", "X_TICKS", "Y_TICKS", "MEMORY_SOURCE"], [[
memorySummary.present ?? "-",
memorySummary.runId ?? "-",
memorySummary.matchesTargetRun ?? "-",
@@ -1335,6 +1349,9 @@ function renderDashboardResult(result: Record<string, unknown>): string {
memorySummary.sampleCount ?? "-",
memorySummary.apiPageCount ?? "-",
memorySummary.perPageLines ?? "-",
memorySummary.axesOk ?? "-",
memorySummary.axisXTickCount ?? "-",
memorySummary.axisYTickCount ?? "-",
memorySummary.source ?? memorySummary.apiSource ?? "-",
]]),
"",
@@ -12,13 +12,19 @@ const checks: Array<{ readonly path: string; readonly contains: readonly string[
"data-monitor-independent-scroll",
"data-check-row",
"data-check-dialog",
"data-memory-axis-x",
"data-memory-axis-y",
"rootCause",
],
},
{
path: "scripts/assets/web-probe-sentinel-monitor-web/monitor-web.css",
minBytes: 8_000,
contains: [],
contains: [
".memory-axis-line",
".memory-axis-label",
".memory-axis-title",
],
},
{
path: "scripts/assets/web-probe-sentinel-monitor-web/vendor/vue.esm-browser.prod.js",