c885b5acf4
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.
37 lines
2.1 KiB
Python
37 lines
2.1 KiB
Python
#!/usr/bin/env python3
|
|
"""catalog_check.py — §7 C: measured memory against each template's mem_request and mem_limit.
|
|
|
|
Writes ../evidence/catalog-check.json and prints two lists:
|
|
RISK the app's peak > 80 % of its mem_limit, OR any one container's bench peak > 80 % of that
|
|
container's own compose limit (the limit Docker enforces is per container)
|
|
OVER the app's typical use > its mem_request (the catalog under-states what it needs)
|
|
Read only: nothing in the catalog is changed.
|
|
"""
|
|
import json, os
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
EV = os.path.join(os.path.dirname(HERE), "evidence")
|
|
t = json.load(open(os.path.join(EV, "apps-table.json")))
|
|
risk, over = [], []
|
|
for a in sorted(t, key=lambda a: a["slug"]):
|
|
if a["source"] == "unmeasured":
|
|
continue
|
|
lim, req = a["catalog_limit_mb"], a["catalog_request_mb"]
|
|
why = []
|
|
if lim and a["mem_peak_mb"] and a["mem_peak_mb"] > 0.8 * lim:
|
|
why.append(f"app peak {a['mem_peak_mb']} MB = {100 * a['mem_peak_mb'] / lim:.0f} % of mem_limit {lim} MB")
|
|
for cname, c in ((a.get("bench") or {}).get("containers") or {}).items():
|
|
if c.get("limit_mb") and c["peak_used_mb"] > 0.8 * c["limit_mb"]:
|
|
why.append(f"container {cname} peak {c['peak_used_mb']} MB = {100 * c['peak_used_mb'] / c['limit_mb']:.0f} % of its {c['limit_mb']} MB limit")
|
|
if why:
|
|
risk.append(dict(slug=a["slug"], why=why, source=a["source"]))
|
|
if req and a["mem_typical_mb"] and a["mem_typical_mb"] > req:
|
|
over.append(dict(slug=a["slug"], typical_mb=a["mem_typical_mb"], mem_request_mb=req,
|
|
ratio=round(a["mem_typical_mb"] / req, 1), source=a["source"]))
|
|
json.dump(dict(risk=risk, over_request=over), open(os.path.join(EV, "catalog-check.json"), "w"), indent=1)
|
|
print("RISK (peak > 80 % of a limit):", len(risk))
|
|
for r in risk:
|
|
print(f" {r['slug']:16} {r['source']:12} " + "; ".join(r["why"]))
|
|
print("OVER (typical > mem_request):", len(over))
|
|
for o in sorted(over, key=lambda o: -o["ratio"]):
|
|
print(f" {o['slug']:16} typical {o['typical_mb']:>5} > request {o['mem_request_mb']:>5} x{o['ratio']} ({o['source']})")
|