feat: account for VM memory ballooning in host memory reporting (#1750)

* feat: account for VM memory ballooning in host memory reporting

Extend hostMemory.ts with a readBalloonedMemory() function that parses the
Balloon: field from /proc/meminfo, following the same fail-open pattern as
the ZFS ARC integration. When a nonzero balloon is detected, effective
memory fields (effectiveUsed, effectiveFree, effectiveUsagePercent) are
computed and exposed through /api/system/stats and /api/fleet/overview.

All consumers that derive meaning from host memory now prefer effective
values when present: the dashboard gauge, Fleet card RAM bar, mobile
views, health verdict, health status bar stat tile, and host RAM alerts.

Backward compatible: missing /proc/meminfo or absent Balloon: line
preserves exact current behavior. Old remote nodes without the new fields
continue rendering normally.

* refactor: extract shared helpers for balloon memory wiring

Extract readCandidateFile() and logSelectedPath() in hostMemory.ts to
deduplicate ARC and balloon file-read logic. Add memoryToWire() to
centralize the optional-field spread used by /api/system/stats and
/api/fleet/overview. Add getNodeMemUsed()/getNodeMemTotal() helpers
in nodeUtils.ts for frontend byte-text consumers.

* fix: make desktop fleet masthead aggregate balloon-aware

The desktop fleet overview's memory aggregate in useFleetOverview.ts still
summed raw memory.used, while the mobile fleet aggregate and per-node cards
already used effective values. Update to use getNodeMemUsed/getNodeMemTotal
helpers.

* fix: revert balloon adjustment from alerting and health decisions

Ballooned memory is host-reclaimed (unlike ZFS ARC, which the guest can
reclaim on demand). The guest cannot get ballooned pages back until the
hypervisor deflates them, so treating ballooned memory as available for
alerting or health can mask real memory pressure.

Keep balloon parsing, wire fields, and the dashboard context line as
informational-only. The memory gauge, health verdict, and host RAM alerts
now use the standard ARC-adjusted working-set percentage regardless of
balloon. Updated configuration.mdx and dashboard.mdx to document that
balloon data is informational and does not influence alerting.
This commit is contained in:
Anso
2026-08-02 20:48:38 -04:00
committed by GitHub
parent a74905ff1e
commit c613010199
20 changed files with 540 additions and 99 deletions
+2
View File
@@ -49,6 +49,8 @@ While the dashboard is loading the CPU tile reads `--` and the caption shows `co
<Note>
**ZFS hosts:** the memory tile and host RAM alerts are ZFS ARC-aware. Reclaimable ARC cache is treated as available memory rather than used, so a large ARC does not inflate the gauge or trigger false low-memory alerts. See [ZFS ARC-aware host memory](/getting-started/configuration#zfs-arc-aware-host-memory) for how to expose ARC stats to a Docker install.
**Virtual machines:** the memory tile shows hypervisor-ballooned memory (TrueNAS/KVM, Proxmox) as informational context. Unlike ARC, ballooned pages are host-reclaimed and the guest cannot get them back on demand, so the gauge, health verdict, and alerts continue to use the standard working-set percentage. See [VM memory ballooning](/getting-started/configuration#vm-memory-ballooning) for details.
</Note>
## Stack health
+16
View File
@@ -53,6 +53,7 @@ These tune optional subsystems. Most deployments never set them; the defaults ar
| `SENCHO_COMPOSE_COMMAND_TIMEOUT_MS` | `1800000` | Hard timeout for a single Compose command (pull, up, down) during deploy and update, in milliseconds (30 minutes). Sencho kills the command and reports failure if it runs longer than this, regardless of whether it is still producing output. Raise it only for very large images or slow storage. |
| `SENCHO_COMPOSE_STALL_TIMEOUT_MS` | `600000` | Idle-output backstop for deploy and update Compose steps (pull and recreate), separate from the hard timeout above. If a step produces no output for this long while still running, Sencho stops it so a hung image pull surfaces a clear failure and the in-app recovery actions instead of spinning. Raise it on slow links or for heavy local image builds. |
| `SENCHO_ZFS_ARCSTATS_PATH` | *(auto)* | Path **inside the container** to the OpenZFS ARC kstat file, for [ZFS ARC-aware host memory](#zfs-arc-aware-host-memory). Sencho checks this path first, then `/host/proc/spl/kstat/zfs/arcstats`, then `/proc/spl/kstat/zfs/arcstats`. Set it only when your ARC stats live at a non-standard path. |
| `SENCHO_PROC_MEMINFO_PATH` | *(auto)* | Path **inside the container** to `/proc/meminfo`, for [VM memory ballooning](#vm-memory-ballooning). Sencho checks this path first, then `/host/proc/meminfo`, then `/proc/meminfo`. Set it only when you need a custom meminfo path. |
Running a remote host as a pilot agent uses four more variables (`SENCHO_MODE`, `SENCHO_PRIMARY_URL`, `SENCHO_ENROLL_TOKEN`, and `SENCHO_PILOT_CA_FILE`), set only on the remote agent container. Sencho bakes them into the enrollment Compose file it generates, so you rarely write them by hand. See [Pilot Agent](/features/pilot-agent) for the full enrollment walkthrough.
@@ -71,6 +72,21 @@ volumes:
Sencho checks `SENCHO_ZFS_ARCSTATS_PATH`, then `/host/proc/spl/kstat/zfs/arcstats`, then `/proc/spl/kstat/zfs/arcstats`. Set `SENCHO_ZFS_ARCSTATS_PATH` only if your ARC stats live somewhere else inside the container.
## VM memory ballooning
On Linux virtual machines with memory ballooning enabled (TrueNAS/KVM, Proxmox, VMware), the hypervisor can reclaim guest memory through a balloon driver. The reclaimed amount is tracked in `/proc/meminfo` on the `Balloon:` line but standard memory counters do not account for it, so a ballooned VM can appear memory-critical when the guest workload is actually healthy.
Sencho reads the `Balloon:` field from `/proc/meminfo` when it is available and shows the ballooned amount on the dashboard memory tile alongside an effective-usage percentage. The memory gauge, health verdict, and host RAM alerts continue to use the standard working-set percentage: unlike ZFS ARC, ballooned memory is reclaimed by the hypervisor and the guest cannot get it back on demand, so balloon data is informational context rather than a factor in alerting or health decisions. When `/proc/meminfo` is unreadable or the `Balloon:` field is absent, the behavior is unchanged.
`/proc/meminfo` is usually visible inside the container at `/proc/meminfo` with no extra configuration. If your runtime does not expose it, mount it read-only:
```yaml
volumes:
- /proc/meminfo:/host/proc/meminfo:ro
```
Sencho checks `SENCHO_PROC_MEMINFO_PATH`, then `/host/proc/meminfo`, then `/proc/meminfo`. Set `SENCHO_PROC_MEMINFO_PATH` only if your meminfo lives somewhere else inside the container.
## Listen port
Sencho always listens on `1852` inside the container. The port is fixed and is not read from an environment variable. To expose Sencho on a different host port, remap with Docker's `-p` flag (or the `ports:` key in your compose file):