# How to read a sandbox's CPU and memory metrics Call `sbx.metrics({ range: "1h" })`: `latest` is the newest reading and `points` the series, measured by the sandbox's host. **On Runtime the CPU metric is the same reading the bill is made from.** The host measures each running sandbox's CPU time once a minute, and Runtime charges $0.025 per active vCPU-hour of that measured CPU, with a floor of 50 millicores ([pricing](/docs/pricing)). So the chart that shows a sandbox idling also shows why it costs little: a 2 vCPU, 4 GiB sandbox that waits costs $0.03125 an hour, and $0.08 with both CPUs busy. Metrics, events and export cost nothing extra ([metrics and webhooks](/docs/observability)). ## Read them ```ts check import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create(); await sbx.exec("python3 -c 'sum(i * i for i in range(10**8))'", { timeoutMs: 120_000 }); const m = await sbx.metrics({ range: "15m" }); console.log(m.latest?.cpuPercent, m.latest?.memoryBytes); for (const p of m.points) console.log(p.at, p.cpuCores, p.cpuPeakPercent, p.memoryPeakBytes); ``` ```python check from withruntime import Sandbox with Sandbox.create() as sbx: sbx.exec("python3 -c 'sum(i * i for i in range(10**8))'", timeout_ms=120_000) m = sbx.metrics(range="15m") latest = m["latest"] if latest: print(latest["cpuPercent"], latest["memoryBytes"]) for point in m["points"]: print(point["at"], point["cpuCores"], point["memoryPeakBytes"]) ``` ```bash check npx withruntime sandbox metrics --range 6h ``` The same readings are charts on the sandbox's page in your account, under [Sandboxes](https://withruntime.com/account/sandboxes), and an agent reads them with the `runtime_sandbox_metrics` MCP tool. ## What each field means | Field | Meaning | | ----------------- | --------------------------------------------------------------------- | | `cpuPercent` | CPU in use as a percent of all the sandbox's vCPUs, 0 to 100 | | `cpuCores` | The same, as a number of cores | | `cpuPeakPercent` | The busiest interval between two readings inside the bucket | | `memoryBytes` | Memory the sandbox's machine holds, as its host measures it | | `memoryPeakBytes` | The most it held inside the bucket | | `latest` | The newest reading, or null when there is none in the last 15 minutes | `range` chooses the window, and each point sums one bucket: | `range` | Bucket | Kept for | | ----------- | ---------- | -------- | | `15m` | 10 seconds | 24 hours | | `1h` | 20 seconds | 24 hours | | `6h` | 2 minutes | 24 hours | | `24h` | 8 minutes | 24 hours | | `7d`, `30d` | 1 hour | 30 days | ## Size a sandbox from its readings A sandbox's size is `vcpu` and `memoryMiB`, and you choose them. The readings say whether you chose well: - **Memory.** Memory is billed on what you reserve, $0.0075 per GiB-hour. If the largest `memoryPeakBytes` over a week of runs is 1.2 GiB, a 2 GiB sandbox does the job that a 4 GiB one did, for half the memory charge. The response also carries `memoryLimitBytes` to compare against. - **CPU.** CPU is billed on use, so extra vCPUs cost nothing while idle. A `cpuPeakPercent` near 100 during builds or tests means more `vcpu` would finish sooner; the bill follows the work done. ```ts check import { Sandbox } from "withruntime"; const sbx = await Sandbox.connect(""); const week = await sbx.metrics({ range: "7d" }); const peak = Math.max(0, ...week.points.map((p) => p.memoryPeakBytes)); console.log( `peak ${(peak / 2 ** 30).toFixed(2)} GiB of ${(week.memoryLimitBytes / 2 ** 30).toFixed(0)} GiB`, ); ``` For what you were actually charged, per resource, `runtime usage` and `GET /v1/usage` give the settled figures. ## Send them to your own dashboards An OpenTelemetry export pushes every sandbox's readings as OTLP gauges, about every 30 seconds, to Grafana, Honeycomb, Datadog, New Relic or any endpoint that takes OTLP over HTTP: ```ts check import { Runtime } from "withruntime"; const runtime = new Runtime(); await runtime.otel.create({ endpoint: "https://api.honeycomb.io", headers: { "x-honeycomb-team": process.env.HONEYCOMB_KEY ?? "" }, signals: ["metrics"], }); ``` The gauges are `runtime.sandbox.cpu.utilization`, `runtime.sandbox.cpu.usage`, `runtime.sandbox.memory.usage` and `runtime.sandbox.memory.limit`, with one resource per sandbox carrying its id, name, vCPUs and region. An account can have three exports ([OpenTelemetry export](/docs/observability#opentelemetry-export)). ## Mistakes and how Runtime handles them - **Reading `cpuPercent` as per core.** It is a share of all the sandbox's vCPUs. On a 2 vCPU sandbox, 50 means one core busy; `cpuCores` says 1. - **Expecting readings from a paused sandbox.** A paused or stopped sandbox has no new readings, and its earlier ones stay. `latest` is null once nothing has been read for 15 minutes. - **Looking for last month's detail.** Each minute's reading is kept 24 hours, and hourly averages and peaks 30 days. Export them to keep more. - **Code written for E2B.** Its `getMetrics()` works through `withruntime/e2b` and reads these same values; `diskUsed` is null. ## Related - [Receive webhooks](/how-to/receive-webhooks) to hear when a sandbox stops. - [Pause and resume a sandbox](/how-to/pause-and-resume-a-sandbox) when the readings show it waiting. - [The sandbox cost calculator](/calculators/sandbox-cost-calculator). ## Start ```bash no-run npx withruntime sandbox run --trial --keep -- python3 -c 'print(6 * 7)' ``` New accounts get 50 free sandbox hours, no card. Then run `runtime sandbox metrics ` on the sandbox it kept. Facts on this page were checked on 25 September 2026.