Skip to content

Commit a6b72a8

Browse files
authored
Merge pull request #51 from MiniMax-AI/codex/session-runtime-metrics-pg
Add Session runtime metrics tab
2 parents e3d6b95 + b28bd11 commit a6b72a8

7 files changed

Lines changed: 281 additions & 132 deletions

File tree

‎apps/web/src/App.tsx‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -952,6 +952,7 @@ export function App() {
952952
void (async () => {
953953
const generation = coreGeneration;
954954
void refreshAgents();
955+
void refreshRuntimeSnapshot();
955956
const refreshed = await refreshSessions();
956957
if (!refreshed || generation !== connectionGenerationRef.current) return;
957958
const filter = sessionAgentFilterRef.current;
@@ -960,7 +961,7 @@ export function App() {
960961
const sessionId = selectedIdRef.current;
961962
if (sessionId) await refreshSelectedSession(sessionId);
962963
})();
963-
}, [coreGeneration, refreshAgents, refreshFilteredSessions, refreshSelectedSession, refreshSessions]);
964+
}, [coreGeneration, refreshAgents, refreshFilteredSessions, refreshRuntimeSnapshot, refreshSelectedSession, refreshSessions]);
964965

965966
const refreshDashboard = useCallback(() => {
966967
if (dashboardRefreshInFlightRef.current) return;
@@ -1050,7 +1051,7 @@ export function App() {
10501051
}, [refreshAgents, refreshEnvironmentTemplates, refreshSessions, refreshStartupConfiguration, refreshVaults]);
10511052

10521053
useEffect(() => {
1053-
if (view !== "dashboard") return;
1054+
if (view !== "dashboard" && view !== "sessions") return;
10541055
let timer: number | null = null;
10551056
void (async () => {
10561057
if (
@@ -2338,6 +2339,10 @@ export function App() {
23382339
turnError={turnError}
23392340
turnState={turnState}
23402341
environmentObservation={environmentObservation}
2342+
runtimeSnapshot={runtimeSnapshot}
2343+
runtimeError={runtimeCollectionError}
2344+
runtimeStale={runtimeCollectionState === "failed"}
2345+
loadRuntimeHistory={loadDashboardRuntimeHistory}
23412346
sendError={sendError}
23422347
streamError={streamError}
23432348
streamState={streamState}

‎apps/web/src/features/dashboard/RuntimeObservabilityContent.tsx‎

Lines changed: 4 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
Search,
1111
Server,
1212
} from "lucide-react";
13-
import { useEffect, useMemo, useState, type ReactNode } from "react";
13+
import { useMemo, useState, type ReactNode } from "react";
1414
import {
1515
flexRender,
1616
getCoreRowModel,
@@ -35,30 +35,11 @@ import {
3535
type RuntimeDashboardRow,
3636
} from "./dashboard-model";
3737
import type { RuntimeDashboardSnapshot } from "./runtime-snapshot";
38-
import { RUNTIME_SNAPSHOT_REFRESH_MS } from "./runtime-snapshot";
39-
import { RuntimeTrendCharts } from "./RuntimeTrendCharts";
40-
import type { RuntimeTrendSource } from "./RuntimeTrendCharts";
41-
import {
42-
RUNTIME_DURABLE_RANGES,
43-
type RuntimeDurableRange,
44-
type RuntimeDurableSnapshot,
45-
} from "./runtime-history";
46-
import {
47-
appendRuntimeTrendSample,
48-
RUNTIME_TREND_RANGES,
49-
RUNTIME_TREND_WINDOW_MS,
50-
runtimeTrendRange,
51-
type RuntimeTrendRange,
52-
type RuntimeTrendSample,
53-
} from "./runtime-trends";
38+
import { RuntimeTrendPanel, type RuntimeHistoryLoader } from "./RuntimeTrendPanel";
5439

5540
const PAGE_SIZE = 10;
5641

57-
export type RuntimeHistoryLoader = (
58-
snapshot: RuntimeDashboardSnapshot,
59-
range: RuntimeDurableRange,
60-
signal: AbortSignal,
61-
) => Promise<RuntimeDurableSnapshot | null>;
42+
export type { RuntimeHistoryLoader } from "./RuntimeTrendPanel";
6243

6344
function RuntimeMetric({
6445
icon,
@@ -308,68 +289,6 @@ export function RuntimeObservabilityContent({
308289
}) {
309290
const model = useMemo(() => buildRuntimeDashboardModel(snapshot.sessions, snapshot.observations), [snapshot]);
310291
const summary = model.summary;
311-
const [trendSamples, setTrendSamples] = useState<RuntimeTrendSample[]>(() => appendRuntimeTrendSample([], snapshot));
312-
const [selectedTrendRange, setSelectedTrendRange] = useState<RuntimeTrendRange>(RUNTIME_TREND_WINDOW_MS);
313-
const [selectedDurableRange, setSelectedDurableRange] = useState<RuntimeDurableRange>(RUNTIME_DURABLE_RANGES[0].milliseconds);
314-
const [durableSnapshot, setDurableSnapshot] = useState<RuntimeDurableSnapshot | null>(null);
315-
const [durableState, setDurableState] = useState<"connecting" | "ready" | "unavailable" | "failed">("connecting");
316-
const [durableError, setDurableError] = useState<string | null>(null);
317-
const visibleTrendSamples = useMemo(
318-
() => runtimeTrendRange(trendSamples, selectedTrendRange),
319-
[selectedTrendRange, trendSamples],
320-
);
321-
const source: RuntimeTrendSource = durableState === "unavailable" && durableSnapshot === null
322-
? "live"
323-
: "durable";
324-
const selectedSamples = source === "durable"
325-
? durableSnapshot?.samples ?? []
326-
: visibleTrendSamples;
327-
const latestTrendSample = selectedSamples.at(-1);
328-
const selectedRange = source === "durable" ? selectedDurableRange : selectedTrendRange;
329-
const rangeEnd = source === "durable" && durableSnapshot !== null
330-
? durableSnapshot.rangeEnd
331-
: latestTrendSample?.sampledAt ?? snapshot.loadedAt;
332-
const rangeStart = source === "durable" && durableSnapshot !== null
333-
? durableSnapshot.rangeStart
334-
: rangeEnd - selectedTrendRange;
335-
336-
useEffect(() => {
337-
setTrendSamples((current) => appendRuntimeTrendSample(current, snapshot));
338-
}, [snapshot]);
339-
340-
useEffect(() => {
341-
const controller = new AbortController();
342-
setDurableState("connecting");
343-
setDurableError(null);
344-
void loadRuntimeHistory(snapshot, selectedDurableRange, controller.signal).then((result) => {
345-
if (controller.signal.aborted) return;
346-
if (result === null) {
347-
setDurableSnapshot(null);
348-
setDurableState("unavailable");
349-
return;
350-
}
351-
setDurableSnapshot(result);
352-
setDurableState("ready");
353-
}).catch((error: unknown) => {
354-
if (controller.signal.aborted) return;
355-
setDurableState("failed");
356-
setDurableError(error instanceof Error ? error.message : "Durable Runtime history request failed.");
357-
});
358-
return () => controller.abort();
359-
}, [loadRuntimeHistory, selectedDurableRange, snapshot]);
360-
361-
const rangeOptions = source === "durable" ? RUNTIME_DURABLE_RANGES : RUNTIME_TREND_RANGES;
362-
const sourceStatus = source === "durable"
363-
? durableState === "failed"
364-
? "History stale"
365-
: durableState === "connecting"
366-
? "History · loading"
367-
: `Durable · ${durableSnapshot?.resolutionSeconds ?? 0}s`
368-
: stale
369-
? "Stale · retrying"
370-
: `Live · ${RUNTIME_SNAPSHOT_REFRESH_MS / 1_000}s`;
371-
const sourceStatusStale = source === "durable" ? durableState === "failed" : stale;
372-
373292
return (
374293
<>
375294
<div className="dashboard-runtime-summary" aria-label="Runtime resource snapshot">
@@ -379,49 +298,7 @@ export function RuntimeObservabilityContent({
379298
<RuntimeMetric icon={<Gauge size={17} />} label="Reported tokens" value={formatDashboardTokens(summary.totalTokens)} detail={`${summary.tokenCoverageCount}/${summary.sessionCount} Sessions report usage`} />
380299
</div>
381300

382-
<section className="dashboard-runtime-live" aria-labelledby="dashboard-runtime-live-heading">
383-
<header className="dashboard-runtime-live-toolbar">
384-
<div>
385-
<h3 id="dashboard-runtime-live-heading">Resource trends</h3>
386-
<p>{source === "durable" ? "Retained samples · durable history" : "Browser-local samples · reset on reload"}</p>
387-
</div>
388-
<div className="dashboard-runtime-live-controls">
389-
<span
390-
className={`${sourceStatusStale ? "dashboard-runtime-live-status dashboard-runtime-live-status-stale" : "dashboard-runtime-live-status"}${source === "durable" ? " dashboard-runtime-live-status-durable" : ""}`}
391-
aria-label={source === "durable"
392-
? `${sourceStatus}; ${durableSnapshot?.targetCount ?? 0} Runtime targets`
393-
: stale
394-
? "Runtime sampling refresh failed; showing retained samples"
395-
: `Live Runtime sampling every ${RUNTIME_SNAPSHOT_REFRESH_MS / 1_000} seconds`}
396-
>
397-
<i aria-hidden="true" />{sourceStatus}
398-
</span>
399-
<span className="dashboard-runtime-sample-count">
400-
{selectedSamples.length} {source === "durable" ? selectedSamples.length === 1 ? "bucket" : "buckets" : selectedSamples.length === 1 ? "sample" : "samples"}
401-
{source === "durable" && durableSnapshot ? <> · {durableSnapshot.sampleCount}/{durableSnapshot.expectedSampleCount} observations</> : null}
402-
{latestTrendSample ? <> · <time dateTime={new Date(latestTrendSample.sampledAt).toISOString()}>{new Date(latestTrendSample.sampledAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" })}</time></> : null}
403-
</span>
404-
<div className="dashboard-runtime-range" role="group" aria-label={source === "durable" ? "Runtime durable range" : "Runtime live range"}>
405-
{rangeOptions.map((range) => (
406-
<button
407-
key={range.label}
408-
type="button"
409-
aria-pressed={selectedRange === range.milliseconds}
410-
onClick={() => {
411-
if (source === "durable") setSelectedDurableRange(range.milliseconds as RuntimeDurableRange);
412-
else setSelectedTrendRange(range.milliseconds as RuntimeTrendRange);
413-
}}
414-
>
415-
{range.label}
416-
</button>
417-
))}
418-
</div>
419-
</div>
420-
</header>
421-
{durableState === "failed" && durableError ? <p className="dashboard-runtime-history-error" role="status">Durable history refresh failed: {durableError}</p> : null}
422-
{durableState === "unavailable" ? <p className="dashboard-runtime-history-note">Durable history is not configured; Live samples remain available.</p> : null}
423-
<RuntimeTrendCharts samples={selectedSamples} source={source} rangeStart={rangeStart} rangeEnd={rangeEnd} />
424-
</section>
301+
<RuntimeTrendPanel snapshot={snapshot} stale={stale} loadRuntimeHistory={loadRuntimeHistory} />
425302

426303
<details className="dashboard-runtime-explorer">
427304
<summary>

‎apps/web/src/features/dashboard/RuntimeTrendCharts.test.tsx‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,17 @@ describe("Runtime live-window chart accessibility", () => {
8888
expect(html).not.toContain('aria-label="CPU usage: 2 live samples"');
8989
});
9090

91+
it("can retain an honest uptime card when a consumer requires four metric panels", () => {
92+
const html = renderToStaticMarkup(
93+
<RuntimeTrendCharts samples={[sample(60_000, .25)]} source="durable" showDurableUptimePlaceholder />,
94+
);
95+
96+
expect(html.match(/data-chart-engine="uplot"/g)).toHaveLength(4);
97+
expect(html).toContain("Compute uptime");
98+
expect(html).toContain("Live-only metric");
99+
expect(html).toContain("Select Live to inspect current Runtime uptime");
100+
});
101+
91102
it("announces an isolated durable value as sparse rather than empty", () => {
92103
const html = renderToStaticMarkup(
93104
<RuntimeTrendCharts samples={[sample(60_000, .25)]} source="durable" />,

‎apps/web/src/features/dashboard/RuntimeTrendCharts.tsx‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -507,11 +507,13 @@ export function RuntimeTrendCharts({
507507
source = "live",
508508
rangeStart,
509509
rangeEnd,
510+
showDurableUptimePlaceholder = false,
510511
}: {
511512
samples: readonly RuntimeTrendSample[];
512513
source?: RuntimeTrendSource;
513514
rangeStart?: number;
514515
rangeEnd?: number;
516+
showDurableUptimePlaceholder?: boolean;
515517
}) {
516518
const charts = useMemo(() => {
517519
const cpuIds = targetIds(samples, "cpuRatio");
@@ -556,7 +558,7 @@ export function RuntimeTrendCharts({
556558
<div className="dashboard-runtime-trend-grid" aria-label={durable ? "Runtime durable-history charts" : "Runtime live-window charts"}>
557559
<TrendChart title="CPU usage" subtitle={durable ? "bucketed cumulative-delta utilization · durable history" : "reported or cumulative-delta utilization · live window"} samples={samples} series={charts.cpu} maximum={cpuMaximum} formatValue={(value) => `${Math.round(value)}%`} rangeStart={oldest} rangeEnd={newest} source={source} bands={[{ from: 0, to: 30, tone: "safe" }, { from: 30, to: 70, tone: "warning" }, { from: 70, to: 100, tone: "danger" }]} ticks={[1, .7, .3, 0]} emptyMessage={durable ? "No retained CPU samples" : undefined} />
558560
<TrendChart title="Memory usage" subtitle={durable ? "complete target aggregate / configured limit · durable history" : "working set / configured limit · live window"} samples={samples} series={charts.memory} maximum={memoryMaximum} formatValue={(value) => formatDashboardBytes(Math.round(value))} rangeStart={oldest} rangeEnd={newest} source={source} emptyMessage={durable ? "No complete retained memory samples" : undefined} />
559-
{!durable ? <TrendChart title="Compute uptime" subtitle="provider started_at → observed_at · allocation series" samples={samples} series={charts.uptime} maximum={uptimeMaximum} formatValue={(value) => formatDashboardDuration(value)} rangeStart={oldest} rangeEnd={newest} source={source} /> : null}
561+
{!durable || showDurableUptimePlaceholder ? <TrendChart title="Compute uptime" subtitle={durable ? "current Runtime measurement · not retained in durable history" : "provider started_at → observed_at · allocation series"} samples={samples} series={durable ? [] : charts.uptime} maximum={uptimeMaximum} formatValue={(value) => formatDashboardDuration(value)} rangeStart={oldest} rangeEnd={newest} source={source} emptyMessage={durable ? "Live-only metric" : undefined} emptyDetail={durable ? "Select Live to inspect current Runtime uptime" : undefined} /> : null}
560562
<TrendChart title="Token throughput" subtitle={durable ? "canonical Session Usage deltas · durable history" : "Session Usage deltas · missing usage excluded"} samples={samples} series={charts.tokens} maximum={tokenMaximum} formatValue={(value) => `${formatDashboardTokens(Math.round(value))}/min`} rangeStart={oldest} rangeEnd={newest} source={source} emptyMessage={durable ? "No retained token samples" : undefined} />
561563
</div>
562564
);

0 commit comments

Comments
 (0)