# Method — sizing bench, 2026-10-13 **Bench:** `demo-felhom` — Intel N100 (4 cores), 16 GB RAM, 512 GB SATA SSD + 1 TB HDD, Proxmox VE; guest 9201 (3 cores, 12 GiB) running controller 0.309.0. Tier 0 (`runbooks/target-selection.md`). Baselines: felhom.eu `c2a6cf7a58`, app-catalog `8ee687e544`, felhom-controller `1b2f74c52f`. ## Sources, in the spec's order | # | Source | What it is | Where | |---|---|---|---| | 1 | telemetry | hub App Telemetry, last 30 days, one row per 15-min report; app = sum of its containers. Used when one box has ≥ 7 days | `evidence/telemetry-30d.json` (from a hub DB copy with `-wal`, freshness proven: newest host report 2 min old) | | 2 | onboarding | measured peaks from earlier onboarding/bench sessions; per run, the app's containers summed, anon basis only | `evidence/onboarding-peaks.json` (collected by a read-only helper; every figure cites its file:line) | | 3 | measured | this run, on the N100 | `evidence//` | | 4 | unmeasured | catalog `mem_request` / `mem_limit` | `.felhom.yml` | Telemetry under 7 days is used only when nothing better exists (`short-telemetry`). When peaks from two sources differ by more than 50 %, both are kept (`evidence/apps-table.json`) and the higher goes into `sizing.json`. **Telemetry is mostly demo and test boxes at rest** (demo-hp, tester-1, demo-felhom): it shows idle use, not household use. Its `peak` is the highest of 1-minute `docker stats` samples, not the true peak. Its CPU is docker's per-core percent; divided by 4 here to approximate the N100 scale (other CPUs, approximate). ## The bench measurement - **Deploy** through the product: the endpoints the dashboard calls (`GET /api/stacks//deploy-fields`, `POST /api/stacks//deploy`), the box's own login + CSRF. The data location: the box has no registered drive, and the deploy page then says „Nincs regisztrált adattároló — add meg kézzel az útvonalat”; we typed `/mnt/sizing-bench` (the guest system disk — the controller container sees only `/mnt`). **Removed** through the product (`stop`, `remove` with data and backups); the product keeps data it cannot resolve (R-442), so the folder was then deleted by name. Every removal checked: no stack, no container, no volume (`evidence/*/run.log`). - **Sampling every 5 s** from each container's cgroup v2 files in the guest (`tools/guest_sampler.sh`), and on the host (`tools/host_sampler.sh`): memory, RAPL package energy, CPU temperature. - **Memory, two numbers:** `used` = `memory.current − inactive_file` (what `docker stats` and the fleet telemetry show) and `anon` (the app's own heap). `sizing.json` uses `used`. The kernel's own high-water mark (`memory.peak`, includes page cache) is recorded too — it is the figure closest to what a container limit is measured against. - **Windows:** *work* = from the app's deploy to the end of its rest; *rest* = the last 15 minutes, nothing deploying. Typical = average of rest; peak = highest 5-s sample of work. - **Batches:** heavy apps alone (deploy → one real action → 15 min rest). Light apps 6–7 per batch, deployed one after another, then 15 min rest together. Per-container numbers are exact either way; host power in a batch is shared. - **CPU:** `usage_usec` deltas, summed per app, as **% of the whole N100 (4 cores = 100 %)**. `cpu_peak_1m` = the busiest 60 seconds. Classes: **light** — rest < 1 % and busiest minute < 15 %; **heavy-in-bursts** — busiest minute ≥ 50 % (two cores fully busy for a minute); **medium** — between. - **The floor:** 15 min with only the platform running. One app (opengist, ~23 MB) was already deployed there and was left alone; it is excluded from the platform figures. ## Actions (content: generated or CC0; no personal data — `tools/make_content.py`) | App | Action | |---|---| | Immich | first user; upload 200 photos (140 generated 12 MP, 60 CC0 portrait paintings from The Met — faces without living people); wait until every job queue is empty | | Jellyfin | startup wizard; library of three generated 1080p H.264 films; scan; one forced transcode (H.264 3 Mbit/s, CPU — no GPU passed through) | | Paperless-ngx | 20 image-only A4 scans at 300 dpi through the API; wait until all are OCR'd | | Nextcloud | first start; 50 × 10 MiB over WebDAV | | Home Assistant | first start; onboarding (first user) | | Calibre-Web Automated | 20 generated EPUBs into the ingest folder | | RomM | 15 dummy files copied into the library; **the scan was not triggered** (it needs a websocket client) | | Mealie, Docmost, AdventureLog | first start (inside their batch) | ## Power RAPL package energy on the host throughout (`evidence/*/host.tsv`): **CPU package only — not wall power.** The wall reading is the operator's STOP (§ power in `SIZING.md`). ## Limits - One box, one run per app, 15 min at rest — a household's long tail (a nightly backup, a library of 50,000 photos, several users) is not in these numbers. The Immich import is 200 photos, not a family archive. - The batch neighbours share CPU during first start; a burst can be slower (and lower) than alone. - Apps that need an account or key to do work (Usenet, the *arr set, Plex sign-in) were measured at rest only. - The bench path is on the guest system disk; on a real box the data sits on a separate drive — no effect on memory. ## Mixing bases — a known softness Onboarding peaks are **anon** (the app's own heap); telemetry and this bench are **used** (anon + active file cache). `used` is usually the larger. Where onboarding is the chosen source, its peak can read low — the >50 % rule then lifts it to the bench figure (e.g. Immich, Grimmory, Grafana); within 50 % it does not. ## Teardown — three layers - **Machine (guest 9201):** all 61 bench deploys removed through the product, each checked (no stack, container or labelled volume). After the queue the guest still held **8 unnamed Docker volumes** made by the bench (4–72 KB: Immich 1, RomM 2, PrivateBin 3, OnlyOffice 2) and **3 empty import folders** the deploys created (`import/paperless`, `import/calibre`, `import/grimmory` on the system drive). Recorded (`evidence/leftover-volumes-after-bench.txt`), then removed by name — no prune. `/root/sizing` and the 1.8 GB of content deleted. End state = start state: 5 containers (platform + opengist), 3 volumes, 12 images, opengist's own backup folder only. - **Host (felhom-pve):** samplers stopped, `/root/sizing` deleted. Nothing else was touched. - **Hub:** read only (a DB copy). The box reported its own telemetry for the bench apps, as any box does; no hub write.