the-custodian/docs/changes/netkingdom-target-2026-10-16/native-preflight-r1/analyze_resources.py
codex cf59a5f9ab
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Reject generated-only commits / generated-commit-guard (push) Successful in 7s
Retain native preflight and independent signer rollout reviews
2026-10-16 21:28:42 +02:00

51 lines
3.9 KiB
Python

"""Offline parse public resource streams; never concatenate JSON as one object."""
import json,re
from decimal import Decimal
def objects(value):
if value is None:return []
if type(value) is dict:return [value]
if type(value) is not str:raise ValueError('resource stream type')
out=[];decoder=json.JSONDecoder();offset=0
while offset<len(value):
while offset<len(value) and value[offset].isspace():offset+=1
if offset==len(value):break
item,length=decoder.raw_decode(value,offset)
if type(item) is not dict:raise ValueError('resource object required')
out.append(item);offset=length
return out
def quantity(value,kind):
if value is None:return Decimal(0)
match=re.fullmatch(r'([0-9]+(?:\.[0-9]+)?)([A-Za-z]*)',str(value))
if not match:raise ValueError('unsupported quantity')
number,suffix=match.groups();number=Decimal(number)
factors={'':1,'n':Decimal('0.000000001'),'u':Decimal('0.000001'),'m':Decimal('0.001'),'k':1000,'M':1000000,'G':1000000000,'Ki':1024,'Mi':1024**2,'Gi':1024**3,'Ti':1024**4}
if suffix not in factors:raise ValueError('unsupported quantity suffix')
return number*factors[suffix]*(1000 if kind=='cpu' else 1)
def analyze(receipt,supplement=None):
rows=(supplement['pods']['rows'] if supplement else receipt['observations']['resource_requests']['rows'])
nodes=(supplement['nodes']['rows'] if supplement else receipt['observations']['nodes']['rows'])
node=next(n for n in nodes if any(a.get('address')=='92.205.62.239' for a in n['status.addresses']))
totals={r:Decimal(0) for r in ['cpu','memory']};count=0;multi=0;init_count=0;pod_level_count=0;sidecars=0
for row in rows:
if row['status.phase'] in ['Succeeded','Failed'] or row['spec.nodeName']!=node['metadata.name']:continue
count+=1;containers=objects(row['spec.containers[*].resources']);init=objects(row['spec.initContainers[*].resources']);multi+=len(containers)>1;init_count+=bool(init)
if row.get('spec.resources'):pod_level_count+=1
restartable=bool(row.get('spec.initContainers[*].restartPolicy'));sidecars+=restartable
for resource in totals:
app=sum((quantity(x.get('requests',{}).get(resource),resource) for x in containers),Decimal(0))
init_requests=[quantity(x.get('requests',{}).get(resource),resource) for x in init]
# Sum of app+all init is a conservative bound for restartable init.
effective=app+sum(init_requests,Decimal(0)) if restartable else max([app]+init_requests)
if row.get('spec.resources'):effective=max(effective,quantity(row['spec.resources'].get('requests',{}).get(resource),resource))
effective+=quantity((row.get('spec.overhead') or {}).get(resource),resource)
totals[resource]+=effective
capacity={r:quantity(node['status.allocatable'][r],r) for r in totals}
return {'node':node['metadata.name'],'nonterminal_bound_pods':count,'multicontainer_pods':multi,'pods_with_init_resources':init_count,'pods_with_pod_level_resources':pod_level_count,'pods_with_restartable_init_fields':sidecars,'supplement_used':bool(supplement),'requested_cpu_m':str(totals['cpu']),'allocatable_cpu_m':str(capacity['cpu']),'unrequested_cpu_m':str(capacity['cpu']-totals['cpu']),'requested_memory_bytes':str(totals['memory']),'allocatable_memory_bytes':str(capacity['memory']),'unrequested_memory_bytes':str(capacity['memory']-totals['memory']),'assumptions':[] if supplement else ['Init restartPolicy and pod-level resources not collected; scheduler totals provisional.'],'method':'exclude terminal/unbound/other-node Pods; sum regular requests; per-resource maximum init request; overhead; conservative sum if restartable init present; max with pod-level requests'}
if __name__=='__main__':
import sys
from pathlib import Path
p=Path(sys.argv[1]);s=Path(sys.argv[2]) if len(sys.argv)>2 else None
print(json.dumps(analyze(json.loads(p.read_text()),json.loads(s.read_text()) if s else None),indent=2))