Add compute_rollup() to capacity-scan.py
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
07ecbb2789
commit
b240fa8bfe
2 changed files with 67 additions and 0 deletions
|
|
@ -43,3 +43,38 @@ def parse_table(markdown, required_cols):
|
||||||
rows.append(dict(zip(headers, cells)))
|
rows.append(dict(zip(headers, cells)))
|
||||||
return rows
|
return rows
|
||||||
return []
|
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
|
||||||
|
|
|
||||||
|
|
@ -26,3 +26,35 @@ trailing text
|
||||||
|
|
||||||
def test_parse_table_returns_empty_when_header_absent():
|
def test_parse_table_returns_empty_when_header_absent():
|
||||||
assert cs.parse_table("no tables here", ["node", "cores"]) == []
|
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
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue