diff --git a/src/confd/yang/confd.inc b/src/confd/yang/confd.inc index d39401b7..f15ac271 100644 --- a/src/confd/yang/confd.inc +++ b/src/confd/yang/confd.inc @@ -32,7 +32,7 @@ MODULES=( "infix-firewall-services@2025-04-26.yang" "infix-firewall-icmp-types@2025-04-26.yang" "infix-meta@2024-10-18.yang" - "infix-system@2025-04-29.yang" + "infix-system@2025-10-18.yang" "infix-services@2024-12-03.yang" "ieee802-ethernet-interface@2019-06-21.yang" "infix-ethernet-interface@2024-02-27.yang" diff --git a/src/confd/yang/confd/infix-system.yang b/src/confd/yang/confd/infix-system.yang index 240bef7f..805495df 100644 --- a/src/confd/yang/confd/infix-system.yang +++ b/src/confd/yang/confd/infix-system.yang @@ -28,11 +28,12 @@ module infix-system { contact "kernelkit@googlegroups.com"; description "Infix augments and deviations to ietf-system."; - revision 2025-04-29 { - description "Add services status."; + revision 2025-10-18 { + description "New system-state status: + - Add system resource usage: memory, loadavg, filesystem usage + - Add services status"; reference "internal"; } - revision 2025-01-25 { description "Add DNS resolver status."; reference "internal"; @@ -509,6 +510,86 @@ module infix-system { } } } + + container resource-usage { + description "Runtime system resource usage information"; + config false; + + container memory { + description "System memory usage"; + + leaf total { + description "Total system memory in kibibytes"; + type uint64; + units "KiB"; + } + + leaf free { + description "Free memory in kibibytes"; + type uint64; + units "KiB"; + } + + leaf available { + description "Available memory in kibibytes (includes cache that can be reclaimed)"; + type uint64; + units "KiB"; + } + } + + container load-average { + description "System load average"; + + leaf load-1min { + description "1-minute load average"; + type decimal64 { + fraction-digits 2; + } + } + + leaf load-5min { + description "5-minute load average"; + type decimal64 { + fraction-digits 2; + } + } + + leaf load-15min { + description "15-minute load average"; + type decimal64 { + fraction-digits 2; + } + } + } + + list filesystem { + key "mount-point"; + description "Filesystem usage for mounted filesystems"; + + leaf mount-point { + description "Filesystem mount point (e.g., /, /var, /cfg)"; + type string; + } + + leaf size { + description "Total filesystem size in kibibytes"; + type uint64; + units "KiB"; + } + + leaf used { + description "Used space in kibibytes"; + type uint64; + units "KiB"; + } + + leaf available { + description "Available space in kibibytes"; + type uint64; + units "KiB"; + } + } + } } deviation "/sys:system/sys:hostname" { diff --git a/src/confd/yang/confd/infix-system@2025-04-29.yang b/src/confd/yang/confd/infix-system@2025-10-18.yang similarity index 100% rename from src/confd/yang/confd/infix-system@2025-04-29.yang rename to src/confd/yang/confd/infix-system@2025-10-18.yang diff --git a/src/show/show.py b/src/show/show.py index 73d4f10f..1ce61e86 100755 --- a/src/show/show.py +++ b/src/show/show.py @@ -222,58 +222,61 @@ def system(args: List[str]) -> None: if thermal_zones: runtime["thermal"] = thermal_zones - # Get disk usage for /, /var, /cfg - disk_usage = [] - for mount in ["/", "/var", "/cfg"]: - try: - result = subprocess.run(["df", "-h", mount], - capture_output=True, text=True, check=True) - lines = result.stdout.strip().split("\n") - if len(lines) > 1: - parts = lines[1].split() - if len(parts) >= 5: - disk_usage.append({ - "mount": mount, - "size": parts[1], - "used": parts[2], - "available": parts[3], - "percent": parts[4] - }) - except subprocess.CalledProcessError: - pass + # Extract resource usage from system-state + system_state = data.get("ietf-system:system-state", {}) + resource_usage = system_state.get("infix-system:resource-usage", {}) - if disk_usage: + # Memory info - convert KiB to MiB for display + memory_kib = resource_usage.get("memory", {}) + if memory_kib: + memory = {} + if "total" in memory_kib: + memory["MemTotal"] = int(memory_kib["total"]) // 1024 + if "free" in memory_kib: + memory["MemFree"] = int(memory_kib["free"]) // 1024 + if "available" in memory_kib: + memory["MemAvailable"] = int(memory_kib["available"]) // 1024 + runtime["memory"] = memory + + # Load average + load_avg = resource_usage.get("load-average", {}) + if load_avg: + runtime["load"] = { + "1min": str(load_avg.get("load-1min", "0.00")), + "5min": str(load_avg.get("load-5min", "0.00")), + "15min": str(load_avg.get("load-15min", "0.00")) + } + + # Filesystem usage - convert KiB to human-readable format + filesystems = resource_usage.get("filesystem", []) + if filesystems: + disk_usage = [] + for fs in filesystems: + mount = fs.get("mount-point", "") + size_kib = int(fs.get("size", 0)) + used_kib = int(fs.get("used", 0)) + avail_kib = int(fs.get("available", 0)) + + # Convert KiB to human-readable format (similar to df -h) + def human_readable(kib_val): + for unit in ['K', 'M', 'G', 'T']: + if kib_val < 1024.0: + return f"{kib_val:.1f}{unit}" + kib_val /= 1024.0 + return f"{kib_val:.1f}P" + + # Calculate percentage + percent = f"{int((used_kib / size_kib * 100) if size_kib > 0 else 0)}%" + + disk_usage.append({ + "mount": mount, + "size": human_readable(size_kib), + "used": human_readable(used_kib), + "available": human_readable(avail_kib), + "percent": percent + }) runtime["disk"] = disk_usage - # Get memory info - try: - with open("/proc/meminfo") as f: - mem_info = {} - for line in f: - parts = line.split(":") - if len(parts) == 2: - key = parts[0].strip() - value = parts[1].strip() - if key in ["MemTotal", "MemFree", "MemAvailable"]: - # Convert from kB to MB - mem_info[key] = int(value.split()[0]) // 1024 - runtime["memory"] = mem_info - except FileNotFoundError: - pass - - # Get load average - try: - with open("/proc/loadavg") as f: - load_parts = f.read().strip().split() - if len(load_parts) >= 3: - runtime["load"] = { - "1min": load_parts[0], - "5min": load_parts[1], - "15min": load_parts[2] - } - except FileNotFoundError: - pass - # Add runtime data to main data structure data["runtime"] = runtime diff --git a/src/statd/python/yanger/ietf_system.py b/src/statd/python/yanger/ietf_system.py index bc032a13..23e08d0f 100644 --- a/src/statd/python/yanger/ietf_system.py +++ b/src/statd/python/yanger/ietf_system.py @@ -292,6 +292,74 @@ def add_clock(out): clock["current-datetime"] = str(clock_now) insert(out, "clock", clock) +def add_resource_usage(out): + """Add system resource usage (memory, load average, filesystem) to system-state""" + resource = {} + + # Memory usage + try: + meminfo = HOST.read("/proc/meminfo") + if not meminfo: + return + mem_info = {} + for line in meminfo.splitlines(): + parts = line.split(":") + if len(parts) == 2: + key = parts[0].strip() + value = parts[1].strip() + if key in ["MemTotal", "MemFree", "MemAvailable"]: + # Store in KiB (as provided by /proc/meminfo, mislabeled as kB) + mem_info[key] = int(value.split()[0]) + + if mem_info: + memory = {} + if "MemTotal" in mem_info: + memory["total"] = str(mem_info["MemTotal"]) + if "MemFree" in mem_info: + memory["free"] = str(mem_info["MemFree"]) + if "MemAvailable" in mem_info: + memory["available"] = str(mem_info["MemAvailable"]) + resource["memory"] = memory + except (FileNotFoundError, ValueError): + pass + + # Load average + try: + loadavg = HOST.read("/proc/loadavg") + load_parts = loadavg.strip().split() + if len(load_parts) >= 3: + load = { + "load-1min": load_parts[0], + "load-5min": load_parts[1], + "load-15min": load_parts[2] + } + resource["load-average"] = load + except (FileNotFoundError, ValueError): + pass + + # Filesystem usage + filesystems = [] + for mount in ["/", "/var", "/cfg"]: + try: + result = HOST.run_multiline(["df", "-k", mount], []) + if len(result) > 1: + parts = result[1].split() + if len(parts) >= 4: + filesystems.append({ + "mount-point": mount, + "size": str(parts[1]), + "used": str(parts[2]), + "available": str(parts[3]) + }) + except (subprocess.CalledProcessError, ValueError, IndexError): + pass + + if filesystems: + resource["filesystem"] = filesystems + + if resource: + insert(out, "infix-system:resource-usage", resource) + def operational(): out = { "ietf-system:system": { @@ -310,5 +378,6 @@ def operational(): add_clock(out_state) add_platform(out_state) add_services(out_state) + add_resource_usage(out_state) return out