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.
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
#!/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")
|
||||
Reference in New Issue
Block a user