55import sys
66import os
77import glob
8+ import json
89from collections import defaultdict
910
1011try :
4142 'Jupyter Web Application' : {
4243 'keywords' : ['jupyter-web-app' , 'jupyter' ]
4344 },
44- 'Tensorboards Web Application' : {
45- 'keywords' : ['tensorboards-web-app' ]
46- },
45+ # 'Tensorboards Web Application': {
46+ # 'keywords': ['tensorboards-web-app']
47+ # },
4748 'Volumes Web Application' : {
4849 'keywords' : ['volumes-web-app' ]
4950 },
9899 'Profiles + KFAM' ,
99100 'PodDefaults Webhook' ,
100101 'Jupyter Web Application' ,
101- 'Tensorboards Web Application' ,
102+ # 'Tensorboards Web Application',
102103 'Volumes Web Application' ,
103104 'Katib' ,
104105 'KServe' ,
@@ -134,6 +135,42 @@ def run_kubectl_top():
134135 print ("Error: kubectl command not found. Please install kubectl" )
135136 sys .exit (1 )
136137
138+ def get_live_pvcs ():
139+ """Get live PVCs from the cluster and return storage information by component"""
140+ try :
141+ result = subprocess .run (['kubectl' , 'get' , 'pvc' , '--all-namespaces' , '-o' , 'json' ],
142+ capture_output = True , text = True , check = True )
143+ pvc_data = json .loads (result .stdout )
144+
145+ component_storage = defaultdict (int )
146+
147+ for pvc in pvc_data .get ('items' , []):
148+ metadata = pvc .get ('metadata' , {})
149+ name = metadata .get ('name' , 'unknown' )
150+ namespace = metadata .get ('namespace' , 'default' )
151+
152+ component = categorize_resource (namespace , name )
153+ if not component :
154+ continue
155+
156+ storage_str = None
157+ if 'status' in pvc and 'capacity' in pvc ['status' ]:
158+ storage_str = pvc ['status' ]['capacity' ].get ('storage' , '0' )
159+ else :
160+ storage_str = pvc .get ('spec' , {}).get ('resources' , {}).get ('requests' , {}).get ('storage' , '0' )
161+
162+ storage_gb = parse_resource_value (storage_str , 'storage' )
163+ component_storage [component ] += storage_gb
164+
165+ return dict (component_storage )
166+
167+ except subprocess .CalledProcessError as e :
168+ print (f"Warning: Error getting live PVCs: { e } " )
169+ return {}
170+ except (json .JSONDecodeError , FileNotFoundError ) as e :
171+ print (f"Warning: Error parsing PVC data: { e } " )
172+ return {}
173+
137174def parse_resource_value (value_str , resource_type ):
138175 """Parse CPU (to millicores) or memory (to MiB) resource values"""
139176 if not value_str or value_str == '0' :
@@ -205,7 +242,7 @@ def categorize_resource(namespace, name, filepath=""):
205242
206243def parse_kubectl_output (output ):
207244 """Parse kubectl top output and categorize by component"""
208- lines = output .strip ().split ('\n ' )[1 :] # Skip header
245+ lines = output .strip ().split ('\n ' )[1 :]
209246 component_resources = defaultdict (lambda : {'cpu' : 0 , 'memory' : 0 })
210247
211248 for line in lines :
@@ -318,34 +355,46 @@ def calculate_max_resources(actual_usage, manifest_requests):
318355
319356 return max_resources
320357
321- def generate_table (component_resources , storage_map , actual_usage , manifest_requests ):
358+ def calculate_max_storage (manifest_storage , live_storage ):
359+ """Calculate maximum of manifest storage and live PVC storage"""
360+ all_components = set (manifest_storage .keys ()) | set (live_storage .keys ())
361+ max_storage = {}
362+
363+ for component in all_components :
364+ manifest_val = manifest_storage .get (component , 0 )
365+ live_val = live_storage .get (component , 0 )
366+ max_storage [component ] = max (manifest_val , live_val )
367+
368+ return max_storage
369+
370+ def generate_table (component_resources , actual_usage , manifest_requests , live_storage = None ):
322371 """Generate markdown table from component resources"""
323372 print ("## Resource Usage by Components" )
324373 print ()
325374 print ("The following table shows the resource requirements for each Kubeflow components:" )
326375 print ()
327- print ("| Component | CPU (cores) | Memory (Mi) | Storage (GB) |" )
328- print ("|-----------|-------------|-------------|--------------|" )
376+ print ("| Component | CPU (cores) | Memory (Mi) | PVC Storage (GB) |" )
377+ print ("|-----------|-------------|-------------|------------------ |" )
329378
330- totals = {'cpu' : 0 , 'memory' : 0 , 'storage ' : 0 }
379+ totals = {'cpu' : 0 , 'memory' : 0 , 'live_storage ' : 0 }
331380
332381 for component in COMPONENT_ORDER :
333382 if component in component_resources :
334383 resources = component_resources [component ]
335- storage = storage_map .get (component , 0 )
384+ live_stor = live_storage .get (component , 0 ) if live_storage else 0
336385
337386 totals ['cpu' ] += resources ['cpu' ]
338387 totals ['memory' ] += resources ['memory' ]
339- totals ['storage ' ] += storage
388+ totals ['live_storage ' ] += live_stor
340389
341- print (f"| { component } | { resources ['cpu' ]} m | { resources ['memory' ]} Mi | { storage } GB |" )
390+ print (f"| { component } | { resources ['cpu' ]} m | { resources ['memory' ]} Mi | { live_stor } GB |" )
342391
343- print (f"| **Total** | **{ totals ['cpu' ]} m** | **{ totals ['memory' ]} Mi** | **{ totals ['storage ' ]} GB** |" )
392+ print (f"| **Total** | **{ totals ['cpu' ]} m** | **{ totals ['memory' ]} Mi** | **{ totals ['live_storage ' ]} GB** |" )
344393 print ()
345394
346395 print ("### Notes" )
347396 print ("- CPU/Memory values are maximum of actual usage and configured requests" )
348- print ("- Storage values are total PVC allocations from manifest files " )
397+ print ("- PVC Storage: Actual storage allocated to PVCs in the cluster " )
349398 print ("- Components not matching the official list are excluded from the table" )
350399 print ()
351400
@@ -357,18 +406,22 @@ def main():
357406 kubectl_output = run_kubectl_top ()
358407 actual_usage = parse_kubectl_output (kubectl_output )
359408
409+ live_storage = get_live_pvcs ()
410+
360411 if YAML_AVAILABLE :
361412 print ("Parsing manifest files..." )
362413 manifest_files = find_manifest_files ()
363- manifest_requests , storage_map = parse_manifest_resources (manifest_files )
414+ manifest_requests , manifest_storage = parse_manifest_resources (manifest_files )
364415 max_resources = calculate_max_resources (actual_usage , manifest_requests )
416+ max_storage = calculate_max_storage (manifest_storage , live_storage )
365417 else :
366418 print ("Using actual usage only (manifest parsing skipped)..." )
367419 manifest_requests = defaultdict (lambda : {'cpu' : 0 , 'memory' : 0 })
420+ manifest_storage = {}
368421 max_resources = actual_usage
369- storage_map = STORAGE_FALLBACK
422+ max_storage = live_storage if live_storage else STORAGE_FALLBACK
370423
371- generate_table (max_resources , storage_map , actual_usage , manifest_requests )
424+ generate_table (max_resources , actual_usage , manifest_requests , live_storage )
372425
373426if __name__ == "__main__" :
374427 main ()
0 commit comments