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.

A flat number is a good sign

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)
`serial_metrics` is off by default

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:

python

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:

python

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:

python

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.

Choosing an install

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.