Docs / Contact / devices/live-metrics
Live Metrics
A compact rail sits at the right of the workspace tab bar and shows what the connected board is actually using: RAM and storage, as used / total, with a bar under each.
It answers one question: how close am I to running out?
Reading the rail
| What it means | |
|---|---|
RAM 396KB / 3.91MB |
Heap in use, and the total heap. Total is gc.mem_alloc() + gc.mem_free(). |
STORAGE 420KB / 8.00MB |
Filesystem in use, and its size, from os.statvfs. |
| The bar | Current fill. Tints amber past 75%, red past 90%. |
| The hairline on the bar | Peak: the worst level seen since you connected. |
| Dimmed | No sample has arrived in over 10 seconds. Hover for last update Ns ago. |
The peak marker is the one worth watching. You run out at the peak, not the average: a loop that averages 40% but spikes to 95% is the one that fails, and the spike is usually gone by the next sample.
During a well-behaved loop the value barely moves. That's correct, not a frozen readout; most loops collect garbage each pass and return to the same state. Drift across many samples is what indicates a leak. If you're unsure whether the feed is alive, hover the rail: it says updating or last update Ns ago.
How often it updates
| Situation | RAM | Storage |
|---|---|---|
| Connected, idle | every 3 s | every 3 s |
A Tendrl client is running (serial_metrics enabled) |
every 10 s | every 15 s |
| Tuning in the Vision playground | ~3 / s | held |
| Your own script is running | only if it reports (see below) |
A running client emits the rail's TDLMETRIC: lines only when serial_metrics is set in its config. It's off by default: the line is printed from the client's timer (every 10 s by default, tunable via serial_metrics_interval), which keeps firing even while a tool holds the REPL for a transfer. The provisioning tools pause it around their own transfers, but a deployed device should still leave it off. Turn it on for a dev board when you want a live rail while your app runs; a deployed device should leave it off (its RAM/storage still reach the dashboard via the heartbeat).
Keeping it live from your own script
The board has one REPL, and a running script owns it. Rather than compete for the port, which would corrupt both its samples and your script's output, the rail stands down and waits to be told.
So while your script runs, your script is what feeds the rail. One call does it:
from tendrl import emit_metrics
while True:
# ...your work...
emit_metrics()
The Terminal recognizes those lines, hides them from the scrollback, and routes them to the rail, exactly like preview.frame() does for camera frames.
In a tight loop, skip the filesystem check, since heap is what moves:
emit_metrics(refresh_disk=False) # reuses the cached storage figures
If you also want the numbers in your terminal output, sample once and use it for both, so the two can never disagree:
from tendrl import sample_metrics
while True:
m = sample_metrics(refresh_disk=False)
print('TDLMETRIC:' + json.dumps(m)) # -> the rail (hidden from scrollback)
print('used %d / %d' % (m['a'], m['h'] + m['a'])) # -> your terminal
time.sleep(1)
sample_metrics() returns {'h': free, 'a': allocated, 'df': disk_free, 'dt': disk_total, 'p': psram_free}. It handles the root path for you: /flash on OpenMV, / elsewhere.
Watching memory while you develop
The rail is most useful when you're about to allocate something large and want to know whether it fits.
Loading a model. Start a detector in the Vision playground and watch RAM step up as the tensor arena allocates. Measured on an OpenMV AE3:
| Detector | Resident while running |
|---|---|
| Person | ~396 KB |
| Face | ~425 KB |
| Hand (palm) | ~950 KB |
| Hand skeleton (2 models) | ~2.0 MB, peaking at ~3.3 MB on the first frame |
That first-frame spike is exactly what the peak marker is for: the steady figure alone understates what the detector demands.
Stopping a run frees it. Models live in device globals, so stopping the playground releases them and RAM returns near its baseline. If you load a detector by hand over the REPL, it stays resident until you drop the reference and collect.
Finding a leak. Run your loop with emit_metrics() and watch across a few minutes. Flat is healthy. A steady climb that survives collection is a real leak, and the peak marker preserves the high-water mark even if the value settles back.
The SDK's own footprint depends on how it's installed. The precompiled .mpy install imports in ~79 ms versus ~524 ms from source on an OpenMV AE3, and an ESP32 ROMFS install cuts resident heap from 73.9 KB to 25.6 KB (and peak-during-import from 82.7 KB to 32.9 KB). See Install paths by board.
Tendrl