Files
felhom.eu/documentation/audits/sizing-2026-10-13/tools/render_table.py
T
admin c885b5acf4 Audit: what a Felhom box needs — floor, 64 apps' memory and CPU, power (N100 bench); R-993..R-995; R-231 due check read green
Measured on demo-felhom (N100, 16 GB): 61 apps deployed and removed through the
product, sampled every 5 s; heavy apps with one real action (Immich 200 photos,
Jellyfin transcode, Paperless OCR, Nextcloud 500 MB, ...). sizing.json for the
estimator (tools/build_sizing.py), SIZING.md with the proposed minimum machine,
METHOD.md. Power is RAPL CPU-package only (no meter). R-993 asks the operator to
approve the minimum machine; R-994 lists catalog limits/requests that are too low;
R-995 records leftover unnamed volumes after removal. R-231's dated check measured:
dooplex-backup.service success, journal green.
2026-10-13 13:23:51 +02:00

26 lines
1.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""render_table.py — put the per-app table from evidence/apps-table.json into SIZING.md (__APPS_TABLE__ or between markers)."""
import json, os, re
A = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
t = json.load(open(os.path.join(A, "evidence", "apps-table.json")))
rows = ["<!-- apps-table: generated by tools/render_table.py -->",
"| App | Typical MB | Peak MB | Catalog request / limit | CPU class | CPU rest % | Busiest minute % | Source |",
"|---|---|---|---|---|---|---|---|"]
for a in sorted(t, key=lambda a: -(a["mem_peak_mb"] or 0)):
f = lambda v: "–" if v is None else (f"{v:,}" if isinstance(v, int) else str(v))
src = a["source"] + (" !" if a["sources_disagree"] else "")
if a.get("lifecycle"):
src += f" — {a['lifecycle']}"
rows.append(f"| {a['display_name']} (`{a['slug']}`) | {f(a['mem_typical_mb'])} | {f(a['mem_peak_mb'])} | "
f"{f(a['catalog_request_mb'])} / {f(a['catalog_limit_mb'])} | {a['cpu_class'] or '—'} | "
f"{f(a['cpu_rest_pct'])} | {f(a['cpu_peak_1m_pct'])} | {src} |")
rows.append("<!-- /apps-table -->")
p = os.path.join(A, "SIZING.md"); s = open(p, encoding="utf-8").read()
block = "\n".join(rows)
if "__APPS_TABLE__" in s:
s = s.replace("__APPS_TABLE__", block)
else:
s = re.sub(r"<!-- apps-table:.*?<!-- /apps-table -->", block, s, flags=re.S)
open(p, "w", encoding="utf-8").write(s)
print(len(t), "rows")