Metrics and listing¶
A sandbox's metrics history (CPU, memory, disk), the guest's facts (the CPU
and memory it sees) and a resumable listing with ordering and filters.
None of this needs new IAM: the history travels over the same gRPC as the
rest and the listing is list-microvms.
Snapshot and history¶
get_metrics() is a procfs snapshot (two reads of /proc/stat 100 ms
apart). rayd also samples every 5 s while the sandbox runs and keeps an
8 h ring (5,760 samples); get_metrics_history(start=, end=,
max_points=) returns it in ascending order.
from datetime import datetime, timedelta, timezone
from rayito import Sandbox
with Sandbox.create() as sbx:
now = sbx.get_metrics()
print(now.cpu_used_pct, now.mem_used_bytes, now.mem_cache_bytes, now.disk_used_bytes)
since = datetime.now(timezone.utc) - timedelta(minutes=10)
for sample in sbx.get_metrics_history(start=since, max_points=60):
print(sample.timestamp, sample.cpu_used_pct, sample.mem_used_bytes)
# From another process: the class form needs the access token
series = Sandbox.get_metrics_history(sbx.sandbox_id, access_token=sbx.access_token)
import asyncio
from rayito import AsyncSandbox
async def main() -> None:
async with await AsyncSandbox.create() as sbx:
print((await sbx.get_metrics()).cpu_used_pct)
series = await sbx.get_metrics_history(max_points=30)
print(len(series))
again = await AsyncSandbox.get_metrics_history(
sbx.sandbox_id, access_token=sbx.access_token
)
print(len(again))
asyncio.run(main())
import { Sandbox } from "rayito";
await using sbx = await Sandbox.create();
const now = await sbx.getMetrics();
console.log(now.cpuUsedPct, now.memUsedBytes, now.memCacheBytes);
const history = await sbx.getMetricsHistory({
start: new Date(Date.now() - 600_000),
maxPoints: 60,
});
for (const sample of history) {
console.log(sample.timestamp.toISOString(), sample.cpuUsedPct);
}
const series = await Sandbox.getMetricsHistory(sbx.sandboxId, {
accessToken: sbx.accessToken,
});
console.log(series.length);
SandboxMetrics:cpu_used_pct,cpu_count,mem_used_bytes,mem_total_bytes,mem_cache_bytes,disk_used_bytes,disk_total_bytesandtimestamp. In the history,cpu_used_pctis the average since the previous sample (≈ 5 s), not the snapshot's 100 ms window.startandendare inclusive (a naivedatetimeis local time);max_pointsreduces the series to that number of buckets (the last sample of each bucket with the CPU averaged). A full response weighs ≈ 350 KB.- Gap while suspended: it is not sampled, so the series jumps from the last sample before the pause to the first after the resume. Sampling does not touch the network and does not count as activity for the idle policy.
- The class form needs the access token (
access_token=orRAYITO_ACCESS_TOKEN) and never wakes a suspended sandbox. - On an image earlier than 0.3.0,
get_metrics_history()(TSgetMetricsHistory()), on an instance and in the class form, raisesUnimplementedError(it is not aSandboxException) asking you to publish a new image, like transfers and the server-side deadline; the genericUnimplementedErrorstays in__cause__(TScause), and gRPC'sUNIMPLEMENTEDone level further down, in__cause__.__cause__(TScause.cause).
Guest facts¶
get_health() carries cpu_count and memory_total_bytes, and
get_info() fills in agent_version, cpu_count and memory_mb: they
are the guest's view, which may not match the image size (the guest
reported 8016 MiB and 4 CPUs with a 2048 MiB image). In TypeScript:
agentVersion, cpuCount, memoryMb and memoryTotalBytes.
Resumable listing¶
Sandbox.list() walks list-microvms (pages of 50) lazily.
Sandbox.paginate() gives a resumable paginator: next_items(),
has_next and next_token, an opaque cursor (it carries AWS's
nextToken and the filter) that you can store and pass to another process.
from datetime import datetime, timedelta, timezone
from rayito import Sandbox
since = datetime.now(timezone.utc) - timedelta(hours=1)
pages = Sandbox.paginate(limit=20, order="desc", started_after=since, states=["RUNNING"])
first = pages.next_items()
for item in first:
print(item.sandbox_id, item.state, item.started_at, item.template_version)
if pages.has_next:
cursor = pages.next_token # store it wherever you like
more = Sandbox.paginate(limit=20, order="desc", started_after=since,
states=["RUNNING"], next_token=cursor).next_items()
for item in Sandbox.list(template="rayito-base", order="asc"):
print(item.sandbox_id)
import { Sandbox } from "rayito";
const pages = Sandbox.paginate({ limit: 20, order: "desc", states: ["RUNNING"] });
const first = await pages.nextItems();
console.log(first.map((item) => item.sandboxId));
if (pages.hasNext) {
const cursor = pages.nextToken;
const more = await Sandbox.paginate({
limit: 20,
order: "desc",
states: ["RUNNING"],
nextToken: cursor,
}).nextItems();
console.log(more.length);
}
for await (const item of Sandbox.list({ template: "rayito-base" })) {
console.log(item.sandboxId, item.state);
}
| Filter (Python / TS) | Where it is applied | Note |
|---|---|---|
template, template_version / templateVersion |
server (list-microvms) |
image name or ARN |
states |
client | by default everything except TERMINATING/TERMINATED |
started_after / startedAfter |
client | inclusive |
order="asc" or "desc" |
client, by startedAt |
AWS does not sort: the first item arrives after walking all the pages (O(pages)); a resumed token skips by identity the items already delivered |
metadata |
client, O(n) | one Health probe per RUNNING sandbox (≈ 0.5-1 s each, counts as traffic for its idle); it never probes a suspended one. With index= (optional, below): no probes and over any non-terminal state |
index (optional) |
your DynamoDB table | DynamoDbIndex(...): one BatchGetItem per page; see Metadata index |
limit (only paginate) |
client | items per next_items(); all if missing |
A next_token is only valid with the same filters it was issued with; a
tampered one or one from another filter is InvalidArgumentException.
Listing by metadata with an index (optional)¶
Without an index, list(metadata=...) only sees RUNNING sandboxes: the
metadata lives in the agent and probing a suspended one would wake it. The
metadata index (optional, off by default, with a DynamoDB cost) keeps a
copy of each sandbox's metadata in a table in your account, so you can
also filter the SUSPENDED ones without probing or waking any.
Full guide, union rules, cost and IAM: Metadata index.
From the CLI¶
rayito sandbox list --template rayito-base
rayito sandbox list --metadata user=42 --state suspended --index-table rayito-sandboxes
rayito sandbox metrics microvm-<id> --token-file ~/.rayito/<id>.token
rayito --json sandbox metrics microvm-<id> --follow --interval 5 --token-file ~/.rayito/<id>.token
metrics prints the get_metrics() snapshot (CLI); connecting
wakes a suspended sandbox.
SDK OpenTelemetry traces (optional)¶
SDK-side spans (rayito.sandbox.create, rayito.commands.run,
rayito.code.run, rayito.files.*…) over the calls you already make,
exported wherever you configure your TracerProvider. Off by default:
without tracer_provider= / tracerProvider OpenTelemetry is not
imported. It is not sandbox telemetry (for that there is the metrics
history).
Full guide, span names, attributes and what is never recorded: OpenTelemetry.
Sending the history metrics (CPU, memory and disk) from the sandbox to
your OTLP collector is another optional feature, also off by default:
create(telemetry=TelemetryExport(...)), and
sbx.get_telemetry_status() (TypeScript: getTelemetryStatus()) counts
what was exported and what was discarded. See
OTLP export.
In the E2B shim¶
sbx.get_metrics(start, end) returns the history as
list[SandboxMetrics] in E2B's format (with mem_cache);
Sandbox.get_metrics(sandbox_id, access_token=...) is the class form, and
Sandbox.list(query=SandboxQuery(state=..., started_after=..., template=...,
metadata=...), limit=, next_token=, order=) returns a SandboxPaginator
(E2B compatibility). With the index= extension (or
E2B(index=...)), query.metadata also filters state=[PAUSED]
(E2B compatibility).