Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions apps/web/e2e/data/routes.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,11 @@ export function buildDemo(now = Math.floor(Date.now() / 1000), publicUrl = "http
});
const observations = sessions.map((session) => {
const base = { id: session.id, object: "agent.runtime_observation", session_id: session.id, resolved_at: now };
if (session.environment.type === "none") return { ...base, environment_id: null, mode: "none", provider_type: null, instance: { kind: "none", allocation_id: null, device_id: null, connection_generation: null }, lifecycle_state: null, status: "unsupported", reason: "runtime_mode_not_observable", allocation_created_at: null, observed_at: null, started_at: null, cpu: null, memory: null };
if (session.environment.type === "self_hosted") return { ...base, environment_id: session.environment.id, mode: "self_hosted", provider_type: null, instance: { kind: "self_hosted_connection", allocation_id: null, device_id: null, connection_generation: null }, lifecycle_state: null, status: "unsupported", reason: "runtime_mode_not_observable", allocation_created_at: null, observed_at: null, started_at: null, cpu: null, memory: null };
if (session.environment.type === "none") return { ...base, environment_id: null, mode: "none", provider_type: null, instance: { kind: "none", allocation_id: null, device_id: null, connection_generation: null }, lifecycle_state: null, status: "unsupported", reason: "runtime_mode_not_observable", allocation_created_at: null, observed_at: null, started_at: null, memory: null };
if (session.environment.type === "self_hosted") return { ...base, environment_id: session.environment.id, mode: "self_hosted", provider_type: null, instance: { kind: "self_hosted_connection", allocation_id: null, device_id: null, connection_generation: null }, lifecycle_state: null, status: "unsupported", reason: "runtime_mode_not_observable", allocation_created_at: null, observed_at: null, started_at: null, memory: null };
const allocation = allocations.find((entry) => entry.session_id === session.id);
const sleeping = allocation.compute_phase === "suspended";
return { ...base, environment_id: session.environment.id, mode: "openai_hosted", provider_type: "docker", instance: { kind: "managed_allocation", allocation_id: allocation.id, device_id: null, connection_generation: null }, lifecycle_state: sleeping ? "sleeping" : "active", status: "observed", reason: null, allocation_created_at: session.created_at, observed_at: now - 2, started_at: session.created_at, cpu: sleeping ? null : { usage_seconds_total: 600 + Math.floor(rand() * 4000), capacity_cores: 2, utilization_ratio: null }, memory: sleeping ? null : { usage_bytes: Math.floor((0.4 + rand() * 1.4) * 2 ** 30), limit_bytes: 2 * 2 ** 30 } };
return { ...base, environment_id: session.environment.id, mode: "openai_hosted", provider_type: "docker", instance: { kind: "managed_allocation", allocation_id: allocation.id, device_id: null, connection_generation: null }, lifecycle_state: sleeping ? "sleeping" : "active", status: "observed", reason: null, allocation_created_at: session.created_at, observed_at: now - 2, started_at: session.created_at, memory: sleeping ? null : { usage_bytes: Math.floor((0.4 + rand() * 1.4) * 2 ** 30), limit_bytes: 2 * 2 ** 30 } };
});
// Added after everything else is generated, so the seeded data above does not change.
agents.push(...providerAgentDefinitions.map((definition, index) => ({
Expand Down
15 changes: 2 additions & 13 deletions apps/web/src/features/metrics/SandboxMetricsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { TableSkeleton } from "../../components/Skeleton";
import { useFailureToast } from "../../components/Toast";
import { ListToolbar, listSummary, NameCell, SearchField } from "../../components/list-ui";
import { useConsoleNavigation } from "../../lib/console-navigation";
import { formatBytes, formatClock, formatCompact, formatDuration, formatInteger, formatPercent, MISSING } from "../../lib/format";
import { formatBytes, formatClock, formatCompact, formatDuration, formatInteger, MISSING } from "../../lib/format";
import { projectClient, ProjectName, useProjects } from "../../lib/projects";
import { coreError } from "../../lib/core-error";
import { loadRuntimeDurableSnapshot, RUNTIME_DURABLE_RANGES, RuntimeDurableHistoryIncompleteError, type RuntimeDurableRange } from "../dashboard/runtime-history";
Expand Down Expand Up @@ -277,7 +277,6 @@ function RuntimeTable({ rows, showProject, showNode, onOpen }: { rows: HostedRun
<th scope="col"><span className="column-help">{t("sandbox.status")}<HelpTip>{t("sandbox.statusHelp")}</HelpTip></span></th>
{showProject ? <th scope="col">{tCommon("project.column")}</th> : null}
{showNode ? <th scope="col">{t("sandbox.node")}</th> : null}
<th scope="col">{t("sandbox.cpu")}</th>
<th scope="col">{t("sandbox.memory")}</th>
<th scope="col" className="numeric">{t("sandbox.uptime")}</th>
<th scope="col" className="numeric">{t("sandbox.tokens")}</th>
Expand All @@ -288,7 +287,6 @@ function RuntimeTable({ rows, showProject, showNode, onOpen }: { rows: HostedRun
const { observation, session } = row;
const observed = observation.status === "observed";
const sampleGap = runtimeSampleGap(observation);
const cpu = observed ? observation.cpu : null;
const memory = observed ? observation.memory : null;
const open = () => onOpen(observation.session_id);
return (
Expand All @@ -299,16 +297,7 @@ function RuntimeTable({ rows, showProject, showNode, onOpen }: { rows: HostedRun
<td><StatusDot tone={lifecycleTone(row)} label={lifecycleLabel(row, t)} /></td>
{showProject ? <td><ProjectName project={byId.get(observation.project_id)} /></td> : null}
{showNode ? <td><NodeLink node={row.node} /></td> : null}
{sampleGap ? <td colSpan={2} className="table-muted">{t(`sandbox.reason.${sampleGap}`)}</td> : <>
<td>
{cpu?.utilization_ratio != null ? (
// Providers that report only a utilization ratio: show it as a share of the sandbox's CPU.
<span className="table-meter">
<Meter value={cpu.utilization_ratio} limit={1} label={t("sandbox.cpu")} />
<span>{formatPercent(cpu.utilization_ratio, locale)}</span>
</span>
) : <span className="table-muted">{MISSING}</span>}
</td>
{sampleGap ? <td className="table-muted">{t(`sandbox.reason.${sampleGap}`)}</td> : <>
<td>
{memory?.usage_bytes != null ? (
<span className="table-meter">
Expand Down
12 changes: 6 additions & 6 deletions apps/web/src/features/metrics/sandbox-runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { SandboxAllocation } from "@oac/agents-client";
import { hostedObservation, node, session } from "../overview/test-fixtures";
import { hostedRuntimeRows, hostedRuntimeUsage, loadHostedRuntimes, matchesRuntime, runtimeSampleGap, runtimeSnapshot } from "./sandbox-runtime";

const none = { ...hostedObservation("plain", "p1"), mode: "none", instance: { kind: "none", allocation_id: null, device_id: null, connection_generation: null }, lifecycle_state: null, status: "unsupported", reason: "runtime_mode_not_observable", cpu: null, memory: null } as unknown as ReturnType<typeof hostedObservation>;
const none = { ...hostedObservation("plain", "p1"), mode: "none", instance: { kind: "none", allocation_id: null, device_id: null, connection_generation: null }, lifecycle_state: null, status: "unsupported", reason: "runtime_mode_not_observable", memory: null } as unknown as ReturnType<typeof hostedObservation>;

describe("loadHostedRuntimes", () => {
it("keeps hosted observations, reads their Sessions through their project and bounds the reads", async () => {
Expand Down Expand Up @@ -37,8 +37,8 @@ describe("hosted Runtime projections", () => {
const load = {
observations: [
hostedObservation("a", "p1"),
hostedObservation("b", "p2", { lifecycle_state: "sleeping", status: "observed", cpu: null, memory: null }),
hostedObservation("c", "p2", { lifecycle_state: "pending", status: "unavailable", reason: "allocation_pending", cpu: null, memory: null, observed_at: null }),
hostedObservation("b", "p2", { lifecycle_state: "sleeping", status: "observed", memory: null }),
hostedObservation("c", "p2", { lifecycle_state: "pending", status: "unavailable", reason: "allocation_pending", memory: null, observed_at: null }),
],
sessions: new Map([["a", session("a", { metadata: { title: "Nightly build" } })], ["b", session("b")]]),
unread: 0,
Expand All @@ -56,10 +56,10 @@ describe("hosted Runtime projections", () => {
expect(one.sessions.map((entry) => entry.id)).toEqual(["b"]);
});

it("sums only reported CPU and memory and counts lifecycle states", () => {
it("sums only reported memory and counts lifecycle states", () => {
expect(hostedRuntimeUsage(load.observations)).toEqual({
hosted: 3, active: 1, sleeping: 1, pending: 1, observed: 2,
cpuCapacityCores: 2, memoryUsageBytes: 100, memoryLimitBytes: 400,
memoryUsageBytes: 100, memoryLimitBytes: 400,
});
});

Expand All @@ -77,7 +77,7 @@ describe("hosted Runtime projections", () => {
});

it("names a missing sample only for a sandbox Core records as active", () => {
const missing = { status: "unavailable", observed_at: null, started_at: null, cpu: null, memory: null } as const;
const missing = { status: "unavailable", observed_at: null, started_at: null, memory: null } as const;
expect(runtimeSampleGap(hostedObservation("a", "p", { ...missing, reason: "sample_timeout" }))).toBe("sample_timeout");
expect(runtimeSampleGap(hostedObservation("b", "p", { ...missing, lifecycle_state: "sleeping", reason: "runtime_not_running" }))).toBeNull();
expect(runtimeSampleGap(hostedObservation("c", "p"))).toBeNull();
Expand Down
6 changes: 2 additions & 4 deletions apps/web/src/features/metrics/sandbox-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { type OwnedRuntimeObservation } from "../../lib/admin-view";
* Hosted Runtimes across every project: the observations come from the Web
* API's deployment-wide list (each labelled with its project); the Sessions
* they belong to are read by ID through their project, bounded. Only
* Core-managed hosted sandboxes report CPU and memory.
* Core-managed hosted sandboxes report memory.
*/

/** Most hosted Sessions read by ID on one load; the rest are listed by ID only. */
Expand Down Expand Up @@ -97,9 +97,8 @@ export interface HostedRuntimeUsage {
sleeping: number;
/** Transitioning or waiting for an allocation. */
pending: number;
/** Runtimes with a current CPU and memory sample. */
/** Runtimes with a current memory sample. */
observed: number;
cpuCapacityCores: number | null;
memoryUsageBytes: number | null;
memoryLimitBytes: number | null;
}
Expand All @@ -114,7 +113,6 @@ export function hostedRuntimeUsage(observations: readonly OwnedRuntimeObservatio
sleeping: hosted.filter((observation) => observation.lifecycle_state === "sleeping").length,
pending: hosted.filter((observation) => observation.lifecycle_state === "pending" || observation.lifecycle_state === "transitioning").length,
observed: observed.length,
cpuCapacityCores: sumKnown(observed.map((observation) => observation.cpu?.capacity_cores)),
memoryUsageBytes: sumKnown(observed.map((observation) => observation.memory?.usage_bytes)),
memoryLimitBytes: sumKnown(observed.map((observation) => observation.memory?.limit_bytes)),
};
Expand Down
1 change: 0 additions & 1 deletion apps/web/src/features/overview/test-fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ export function hostedObservation(sessionId: string, projectId: string, override
environment_id: `env_${sessionId}`, mode: "openai_hosted", provider_type: "docker",
instance: { kind: "managed_allocation", allocation_id: `alloc_${sessionId}`, device_id: null, connection_generation: null },
lifecycle_state: "active", status: "observed", reason: null, allocation_created_at: 100, observed_at: 1_000, started_at: 400,
cpu: { usage_seconds_total: 10, capacity_cores: 2, utilization_ratio: null },
memory: { usage_bytes: 100, limit_bytes: 400 },
project_id: projectId,
...overrides,
Expand Down
5 changes: 1 addition & 4 deletions apps/web/src/features/sessions/SessionRuntimeSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next";
import { failedLast, useFailureToast } from "../../components/Toast";
import { DashboardSkeleton } from "../../components/Skeleton";
import { Kpi, KpiStrip, Section, SegmentedControl } from "../../components/console-ui";
import { formatBytes, formatDateTime, formatPercent, formatRelative, MISSING } from "../../lib/format";
import { formatBytes, formatDateTime, formatRelative, MISSING } from "../../lib/format";
import { coreError } from "../../lib/core-error";
import { RuntimeCharts } from "../metrics/RuntimeCharts";
import { runtimeSampleGap } from "../metrics/sandbox-runtime";
Expand Down Expand Up @@ -83,9 +83,7 @@ export function SessionRuntimeSection({
// The same state and sample reason as Sandbox metrics' Hosted Runtimes.
const stateLabel = observation?.lifecycle_state ? tMetrics(`sandbox.lifecycle.${observation.lifecycle_state}`) : MISSING;
const sampleGap = observation ? runtimeSampleGap(observation) : null;
const cpu = observation?.status === "observed" ? observation.cpu : null;
const memory = observation?.status === "observed" ? observation.memory : null;
const cpuValue = cpu?.utilization_ratio != null ? formatPercent(cpu.utilization_ratio, locale) : MISSING;
const memoryValue = memory?.usage_bytes != null
? memory.limit_bytes != null ? `${formatBytes(memory.usage_bytes)} / ${formatBytes(memory.limit_bytes)}` : formatBytes(memory.usage_bytes)
: MISSING;
Expand All @@ -107,7 +105,6 @@ export function SessionRuntimeSection({
>
<KpiStrip label={t("runtime.title")}>
<Kpi label={t("runtime.state")} value={stateLabel} />
<Kpi label={t("runtime.cpu")} value={cpuValue} />
<Kpi label={t("runtime.memory")} value={memoryValue} />
<Kpi label={t("runtime.observed")} value={sampleGap ? tMetrics(`sandbox.reason.${sampleGap}`) : observedAt === null ? MISSING : <span title={formatDateTime(observedAt, locale)}>{formatRelative(observedAt, Math.floor(Date.now() / 1000), locale)}</span>} tone={sampleGap ? "warning" : undefined} />
</KpiStrip>
Expand Down
1 change: 0 additions & 1 deletion apps/web/src/i18n/locales/en/metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,6 @@ export const metrics = {
runtimeUnavailable: "Runtime observations are unavailable. {{reason}}",
runtimeStale: "Refresh failed; showing the previous observations. {{reason}}",
runtimePartial: "Session details were not read for {{unread}} Runtimes (over the read limit) and could not be read for {{failed}}. Those rows show the Session ID only.",
cpu: "CPU",
cores: "{{value}} cores",
memory: "Memory",
disk: "Disk",
Expand Down
3 changes: 1 addition & 2 deletions apps/web/src/i18n/locales/en/sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,8 @@ export const sessions = {
},
runtime: {
title: "Runtime",
help: "CPU and memory of this Session's hosted sandbox as observed by Core, and the samples Core retained for the chosen range.",
help: "Current memory of this Session's hosted sandbox as observed by Core, and the CPU and memory samples Core retained for the chosen range.",
state: "State",
cpu: "CPU",
memory: "Memory",
observed: "Observed",
range: "Time range",
Expand Down
1 change: 0 additions & 1 deletion apps/web/src/i18n/locales/zh-CN/metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,6 @@ export const metrics = {
runtimeUnavailable: "Runtime 观测不可用。{{reason}}",
runtimeStale: "刷新失败,显示的是上次的观测。{{reason}}",
runtimePartial: "有 {{unread}} 个 Runtime 超过读取上限、{{failed}} 个读取失败,未读取 Session 详情,这些行只显示 Session ID。",
cpu: "CPU",
cores: "{{value}} 核",
memory: "内存",
disk: "磁盘",
Expand Down
3 changes: 1 addition & 2 deletions apps/web/src/i18n/locales/zh-CN/sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,8 @@ export const sessions = {
},
runtime: {
title: "Runtime",
help: "Core 观测到的此 Session 托管沙箱的 CPU 和内存,以及所选时间范围内 Core 保留的采样。",
help: "Core 观测到的此 Session 托管沙箱当前内存,以及所选时间范围内 Core 保留的 CPU 和内存采样。",
state: "状态",
cpu: "CPU",
memory: "内存",
observed: "观测时间",
range: "时间范围",
Expand Down
29 changes: 0 additions & 29 deletions contracts/agents-api/core.openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,6 @@ definitions:
minimum: 0
type: integer
x-nullable: true
cpu:
allOf:
- $ref: '#/definitions/v1.RuntimeCPUObservation'
x-nullable: true
disk:
allOf:
- $ref: '#/definitions/api.RuntimeDiskObservation'
Expand Down Expand Up @@ -143,7 +139,6 @@ definitions:
type: string
required:
- allocation_created_at
- cpu
- disk
- environment_id
- id
Expand Down Expand Up @@ -2694,25 +2689,6 @@ definitions:
required:
- type
type: object
v1.RuntimeCPUObservation:
properties:
capacity_cores:
minimum: 5e-324
type: number
x-nullable: true
usage_seconds_total:
minimum: 0
type: number
x-nullable: true
utilization_ratio:
minimum: 0
type: number
x-nullable: true
required:
- capacity_cores
- usage_seconds_total
- utilization_ratio
type: object
v1.RuntimeHistory:
properties:
coverage:
Expand Down Expand Up @@ -3056,10 +3032,6 @@ definitions:
minimum: 0
type: integer
x-nullable: true
cpu:
allOf:
- $ref: '#/definitions/v1.RuntimeCPUObservation'
x-nullable: true
environment_id:
format: uuid
type: string
Expand Down Expand Up @@ -3126,7 +3098,6 @@ definitions:
type: string
required:
- allocation_created_at
- cpu
- environment_id
- id
- instance
Expand Down
16 changes: 0 additions & 16 deletions contracts/agents-api/runtime-observability-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,6 @@ The list has one row for every Session of every Project that is not deleted, inc
"resolved_at": 1789953021,
"observed_at": 1789953020,
"started_at": 1789951220,
"cpu": {
"usage_seconds_total": 482.75,
"capacity_cores": 2.0,
"utilization_ratio": null
},
"memory": {
"usage_bytes": 805306368,
"limit_bytes": 2147483648
Expand Down Expand Up @@ -95,7 +90,6 @@ This returns one `RuntimeObservation`, without `disk`. It accepts no query param
| `resolved_at` | integer | Unix seconds when Core resolved this row. |
| `observed_at` | integer or null | Unix seconds of the provider sample; null without a sample. |
| `started_at` | integer or null | Unix seconds when the current compute incarnation started. |
| `cpu` | object or null | Null when no CPU value was observed. |
| `memory` | object or null | Null when no memory value was observed. |

`lifecycle_state` comes from Core's allocation records, never from the sample:
Expand All @@ -117,16 +111,6 @@ This returns one `RuntimeObservation`, without `disk`. It accepts no query param
| `device_id` | The Runtime device bound to the managed allocation, when there is one; null otherwise. |
| `connection_generation` | Always null: Core does not observe self-hosted connections. |

### `cpu`

All fields are finite nonnegative numbers or null. Zero is an observed zero; null is unavailable.

| Field | Meaning |
| --- | --- |
| `usage_seconds_total` | Cumulative CPU seconds of the current compute incarnation (Docker, microsandbox). |
| `capacity_cores` | Configured CPU capacity, greater than zero. |
| `utilization_ratio` | Provider-reported share of `capacity_cores` (E2B), not clamped; null for providers that report cumulative CPU time. |

### `memory`

`usage_bytes` and `limit_bytes` are safe JSON integers or null. Zero usage is observed zero; `limit_bytes` is at least 1, and an unknown or unlimited limit is null.
Expand Down
Loading
Loading