> ## Documentation Index
> Fetch the complete documentation index at: https://docs.altnautica.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Performance and budgets

> Resource budgets, cgroup throttling, memory pressure, and how to profile.

A plugin runs on a small SBC alongside the rest of the agent. The
supervisor budgets resources through systemd and the shared
`ados-plugins.slice` cgroup. This page covers the budgets, how to spot
when you blow them, and how to profile.

## Default budgets

When a plugin's manifest does not declare resources, it gets:

| Resource             | Default cap            |
| -------------------- | ---------------------- |
| RAM                  | 96 MB                  |
| CPU                  | 25% of one core        |
| PIDs                 | 12                     |
| Inbound topic outbox | 256 messages per topic |

Declare different caps in the manifest:

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
agent:
  resources:
    max_ram_mb: 256       # 8 to 4096
    max_cpu_percent: 30   # 1 to 100
    max_pids: 8           # 1 to 256
```

Honest declarations matter. The operator sees the requested budget in the
install dialog. Asking for far more than you use is a bad signal; asking
for too little ends with the kernel killing your process.

The caps map straight to the per-plugin systemd unit: `max_ram_mb` to
`MemoryMax`, `max_cpu_percent` to `CPUQuota`, `max_pids` to `TasksMax`.

## Spotting cgroup throttling

`max_cpu_percent` is enforced by the cgroup CPU controller. A plugin that
breaches it does not crash; it gets throttled. The symptom is
"everything got slower for no reason."

Read the throttle stats from the unit's cgroup (the unit name is
`ados-plugin-<id>` with the dots in your plugin id replaced by hyphens):

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
cat /sys/fs/cgroup/ados-plugins.slice/ados-plugin-com-example-battery.service/cpu.stat
```

The interesting field is `nr_throttled`. If it climbs every second, your
plugin is over budget. Optimize the code or declare a higher
`max_cpu_percent`.

## Memory pressure

`max_ram_mb` is enforced by `MemoryMax`. A breach OOM-kills the process.
The unit restarts on failure, and repeated kills that exceed the start
limit move the plugin to `failed`.

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
cat /sys/fs/cgroup/ados-plugins.slice/ados-plugin-com-example-battery.service/memory.current
cat /sys/fs/cgroup/ados-plugins.slice/ados-plugin-com-example-battery.service/memory.events
```

`memory.events` shows `low`, `high`, `max`, `oom`, `oom_kill` counters.
Any non-zero `high` or `max` means the kernel started applying pressure.

To stay under the cap:

* Process events on a bounded queue, not an unbounded list.
* For ML models, prefer quantized weights (int8 over fp32).
* Stream files; do not load whole logs into RAM.
* Use `array.array` or `numpy` over a Python list of floats.

## Inspecting a running plugin

`ados plugin info <id>` shows the plugin's recorded state, granted
permissions, and recent events. Pair it with the cgroup stat files above
and the durable log query for a full picture:

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
ados plugin info com.example.battery
ados plugin logs com.example.battery
ados logs query --unit ados-plugin-com-example-battery
```

A plugin running consistently near any cap is one bad input away from a
throttle stall or an OOM kill.

## Profiling Python plugins

The SDK does not bundle a profiler; use `cProfile` or `pyinstrument`
inside your own start path:

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
import cProfile, pstats

def on_start_profiled(self, ctx):
    pr = cProfile.Profile()
    pr.enable()
    try:
        return self._real_on_start(ctx)
    finally:
        pr.disable()
        pstats.Stats(pr).sort_stats("cumulative").print_stats(30)
```

Plugin stdout and stderr append to `/var/log/ados/plugins/<id>.log`, so
profiler output lands there (or read it with
`journalctl -u ados-plugin-<id>.service`).

## Profiling TypeScript plugins

Open the GCS in developer mode, pick the plugin's iframe in browser
devtools (Sources tab), and use the Performance recorder. The iframe is a
real browsing context with the standard devtools surface.

Common GCS hot paths:

* React re-renders triggered by every telemetry event. Coalesce with
  `requestAnimationFrame` and a local ref.
* Heavy SVG redraws. Switch to canvas past a few hundred paths.
* Synchronous JSON parse in the message handler. Move it off the main
  thread.

## Event-loop discipline

The biggest cause of agent-plugin stalls is a blocked event loop. A
plugin that blocks the loop stops responding and the supervisor restarts
it.

Don't:

* `time.sleep(...)` in async code.
* Heavy CPU work directly in `on_start`.
* Synchronous I/O on a slow disk.

Do:

* `await asyncio.sleep(...)`.
* `await asyncio.to_thread(heavy_work)`.
* `aiofiles` or chunked reads for slow I/O.

## Disk and network discipline

Disk I/O is a common cause of throttling. A plugin writing a megabyte of
samples per second to an SD card is already at the sustained-write
ceiling for the cheap end of the market. Batch writes (one `fsync` per
second is plenty), compress logs, and rotate them.

Plugins with `network.outbound` share the drone's link with telemetry and
video. Pulling large payloads in flight is a UX bug; pull during ground
time and cache.

## Thermal behavior

Sustained high CPU on a small SoC raises its temperature and the kernel
eventually throttles the whole package, which shows up as cgroup throttle
events on every plugin, not just yours. The agent reduces its own
non-essential work as the SoC heats up. A well-behaved plugin watches the
system telemetry and backs off before the kernel forces the issue.

## See also

* [Agent plugin deep dive](/developers/agent-plugin-deep-dive)
* [State and storage](/developers/state-and-storage)
* [Vision plugins](/developers/vision-plugins)
