Add compute_rollup() to capacity-scan.py

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sjat 2026-06-01 10:21:22 +02:00
parent 07ecbb2789
commit b240fa8bfe
2 changed files with 67 additions and 0 deletions

View file

@ -43,3 +43,38 @@ def parse_table(markdown, required_cols):
rows.append(dict(zip(headers, cells)))
return rows
return []
def compute_rollup(node_rows, workload_rows):
"""Per node: physical totals, summed allocations, RAM headroom %, and an
oversubscribed flag. Workloads on unknown nodes are ignored."""
nodes = {}
for r in node_rows:
nodes[r["node"]] = {
"cores": int(r["cores"]),
"ram_gb": float(r["ram_gb"]),
"disk_gb": float(r["disk_gb"]),
"alloc_cores": 0,
"alloc_ram_mb": 0,
"alloc_disk_gb": 0.0,
}
for w in workload_rows:
node = nodes.get(w["node"])
if node is None:
continue
node["alloc_cores"] += int(w["cores"])
node["alloc_ram_mb"] += int(w["ram_mb"])
node["alloc_disk_gb"] += float(w["disk_gb"])
for node in nodes.values():
node["alloc_ram_gb"] = round(node.pop("alloc_ram_mb") / 1024, 1)
node["ram_headroom_pct"] = (
round(100 * (node["ram_gb"] - node["alloc_ram_gb"]) / node["ram_gb"])
if node["ram_gb"]
else 0
)
node["oversubscribed"] = (
node["alloc_cores"] > node["cores"]
or node["alloc_ram_gb"] > node["ram_gb"]
or node["alloc_disk_gb"] > node["disk_gb"]
)
return nodes

View file

@ -26,3 +26,35 @@ trailing text
def test_parse_table_returns_empty_when_header_absent():
assert cs.parse_table("no tables here", ["node", "cores"]) == []
def test_compute_rollup_sums_allocations_and_flags_headroom():
node_rows = [{"node": "pve0", "cores": "20", "ram_gb": "64", "disk_gb": "4000"}]
workload_rows = [
{"workload": "dns1", "node": "pve0", "cores": "1", "ram_mb": "512", "disk_gb": "10"},
{"workload": "forgejo", "node": "pve0", "cores": "4", "ram_mb": "8192", "disk_gb": "100"},
]
nodes = cs.compute_rollup(node_rows, workload_rows)
pve0 = nodes["pve0"]
assert pve0["alloc_cores"] == 5
assert pve0["alloc_ram_gb"] == 8.5 # (512 + 8192) / 1024
assert pve0["alloc_disk_gb"] == 110.0
assert pve0["ram_headroom_pct"] == 87 # round(100 * (64 - 8.5) / 64)
assert pve0["oversubscribed"] is False
def test_compute_rollup_flags_oversubscription():
node_rows = [{"node": "tiny", "cores": "2", "ram_gb": "4", "disk_gb": "50"}]
workload_rows = [
{"workload": "hog", "node": "tiny", "cores": "4", "ram_mb": "1024", "disk_gb": "10"},
]
nodes = cs.compute_rollup(node_rows, workload_rows)
assert nodes["tiny"]["oversubscribed"] is True # 4 cores > 2
def test_compute_rollup_ignores_workloads_on_unknown_nodes():
nodes = cs.compute_rollup(
[{"node": "pve0", "cores": "20", "ram_gb": "64", "disk_gb": "4000"}],
[{"workload": "ghost", "node": "nope", "cores": "1", "ram_mb": "512", "disk_gb": "10"}],
)
assert nodes["pve0"]["alloc_cores"] == 0