diff --git a/src/confd/yang/confd.inc b/src/confd/yang/confd.inc index 3e102c987..4bd4c2677 100644 --- a/src/confd/yang/confd.inc +++ b/src/confd/yang/confd.inc @@ -37,7 +37,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-10-18.yang" + "infix-system@2025-12-02.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 805495dfb..f9ecaa906 100644 --- a/src/confd/yang/confd/infix-system.yang +++ b/src/confd/yang/confd/infix-system.yang @@ -28,6 +28,11 @@ module infix-system { contact "kernelkit@googlegroups.com"; description "Infix augments and deviations to ietf-system."; + revision 2025-12-02 { + description "Extend services with runtime statistics: + - Add statistics container with memory-usage, uptime, restart-count"; + reference "internal"; + } revision 2025-10-18 { description "New system-state status: - Add system resource usage: memory, loadavg, filesystem usage @@ -508,6 +513,28 @@ module infix-system { description "Detailed current status of the process."; } + + container statistics { + description "Service resource usage and runtime statistics"; + config false; + + leaf memory-usage { + type uint64; + units "bytes"; + description "Current memory usage in bytes"; + } + + leaf uptime { + type uint64; + units "seconds"; + description "Time service has been running"; + } + + leaf restart-count { + type uint32; + description "Number of service restarts"; + } + } } } diff --git a/src/confd/yang/confd/infix-system@2025-10-18.yang b/src/confd/yang/confd/infix-system@2025-12-02.yang similarity index 100% rename from src/confd/yang/confd/infix-system@2025-10-18.yang rename to src/confd/yang/confd/infix-system@2025-12-02.yang diff --git a/src/statd/python/cli_pretty/cli_pretty.py b/src/statd/python/cli_pretty/cli_pretty.py index 0c6d3e694..2c04916bf 100755 --- a/src/statd/python/cli_pretty/cli_pretty.py +++ b/src/statd/python/cli_pretty/cli_pretty.py @@ -189,13 +189,6 @@ class PadNtpSource: poll = 14 -class PadService: - name = 16 - status = 8 - pid = 8 - description = 40 - - class PadWifiScan: ssid = 40 encryption = 30 @@ -217,6 +210,34 @@ class PadDiskUsage: avail = 12 percent = 6 + +def format_memory_bytes(bytes_val): + """Convert bytes to human-readable format""" + if bytes_val == 0: + return " " + elif bytes_val < 1024: + return f"{bytes_val}B" + elif bytes_val < 1024 * 1024: + return f"{bytes_val // 1024}K" + elif bytes_val < 1024 * 1024 * 1024: + return f"{bytes_val // (1024 * 1024):.1f}M" + else: + return f"{bytes_val // (1024 * 1024 * 1024):.1f}G" + + +def format_uptime_seconds(seconds): + """Convert seconds to compact time format""" + if seconds == 0: + return " " + elif seconds < 60: + return f"{seconds}s" + elif seconds < 3600: + return f"{seconds // 60}m" + elif seconds < 86400: + return f"{seconds // 3600}h" + else: + return f"{seconds // 86400}d" + @classmethod def table_width(cls): """Total width of disk usage table""" @@ -259,6 +280,69 @@ def table_width(cls): return cls.zone_locked + cls.zone_name + cls.zone_type + cls.zone_data \ + cls.zone_services +class Column: + """Column definition for SimpleTable""" + def __init__(self, name, length, align='left', formatter=None): + self.name = name # Header text + self.width = length # Max visible data length (excluding ANSI codes) + self.align = align # 'left' or 'right' (defaults to 'left') + self.formatter = formatter # Optional function to format values + +class SimpleTable: + """Simple table formatter that handles ANSI colors correctly""" + + def __init__(self, columns): + self.columns = columns + + @staticmethod + def visible_width(text): + """Return visible character count, excluding ANSI escape sequences""" + ansi_pattern = r'\x1b\[[0-9;]*m' + clean_text = re.sub(ansi_pattern, '', str(text)) + return len(clean_text) + + def _format_column(self, value, column): + """Format a single column value with proper alignment + + The column length specifies max expected data length. + Framework automatically adds 1 space for column separation. + """ + if column.formatter: + value = column.formatter(value) + + value_str = str(value) + visible_len = self.visible_width(value_str) + + if column.align == 'right': + padding = column.width - visible_len + return ' ' * max(0, padding) + value_str + ' ' + else: # left alignment (default) + padding = column.width - visible_len + return value_str + ' ' * max(0, padding) + ' ' + + def header(self, styled=True): + """Generate formatted header row""" + header_parts = [] + for column in self.columns: + if column.align == 'right': + header_parts.append(f"{column.name:>{column.width}} ") + else: # left alignment (default) + header_parts.append(f"{column.name:{column.width}} ") + + header_str = ''.join(header_parts) + return Decore.invert(header_str) if styled else header_str + + def row(self, *values): + """Generate formatted data row""" + if len(values) != len(self.columns): + raise ValueError(f"Expected {len(self.columns)} values, got {len(values)}") + + row_parts = [] + for value, column in zip(values, self.columns): + row_parts.append(self._format_column(value, column)) + + return ''.join(row_parts).rstrip() + class Decore(): @staticmethod @@ -1661,17 +1745,27 @@ def show_services(json): services_data = get_json_data({}, json, 'ietf-system:system-state', 'infix-system:services') services = services_data.get("service", []) - hdr = (f"{'NAME':<{PadService.name}}" - f"{'STATUS':<{PadService.status}}" - f"{'PID':>{PadService.pid -1}}" - f" {'DESCRIPTION'}") - print(Decore.invert(hdr)) + # This is the first usage of simple table. I assume this will be + # copied so I left a lot of comments. If you copy it feel free + # to be less verbose.. + service_table = SimpleTable([ + Column('NAME', 15, 'left'), # Max service name length + Column('STATUS', 10, 'left'), # Max status text length (e.g., "running") + Column('PID', 7, 'right'), # Max PID digits + Column('MEM', 6, 'right'), # Max memory string (e.g., "123.4M") + Column('UP', 4, 'right'), # Max uptime string (e.g., "3d") + Column('RST', 3, 'right'), # Max restart count digits + Column('DESCRIPTION', 30) # Last column needs no padding + ]) + + print(service_table.header()) for svc in services: name = svc.get('name', '') status = svc.get('status', '') pid = svc.get('pid', 0) description = svc.get('description', '') + stats = svc.get('statistics', {}) if status in ('running', 'active', 'done'): status_str = Decore.green(status) @@ -1680,13 +1774,17 @@ def show_services(json): else: status_str = Decore.yellow(status) - pid_str = str(pid) if pid > 0 else '-' + pid_str = str(pid) if pid > 0 else ' ' - row = f"{name:<{PadService.name}}" - row += f"{status_str:<{PadService.status + 9}}" - row += f"{pid_str:>{PadService.pid}}" - row += f" {description}" - print(row) + memory_bytes = int(stats.get('memory-usage', 0)) + uptime_secs = int(stats.get('uptime', 0)) + restart_count = stats.get('restart-count', 0) + + memory_str = format_memory_bytes(memory_bytes) + uptime_str = format_uptime_seconds(uptime_secs) + + print(service_table.row(name, status_str, pid_str, memory_str, + uptime_str, restart_count, description)) def show_hardware(json): diff --git a/src/statd/python/yanger/ietf_system.py b/src/statd/python/yanger/ietf_system.py index 23e08d0f5..ba7aa1b0a 100644 --- a/src/statd/python/yanger/ietf_system.py +++ b/src/statd/python/yanger/ietf_system.py @@ -189,7 +189,12 @@ def add_services(out): "pid": d["pid"], "name": d["identity"], "status": d["status"], - "description": d["description"] + "description": d["description"], + "statistics": { + "memory-usage": str(d.get("memory", 0)), + "uptime": str(d.get("uptime", 0)), + "restart-count": int(d.get("restarts", 0)) + } } services.append(entry) diff --git a/test/case/statd/system/cli/show-services b/test/case/statd/system/cli/show-services index de2ba4992..5a4b9f7d9 100644 --- a/test/case/statd/system/cli/show-services +++ b/test/case/statd/system/cli/show-services @@ -1,24 +1,24 @@ -NAME STATUS PID DESCRIPTION -udevd running 1185 Device event daemon (udev) -dbus running 2248 D-Bus message bus daemon -confd running 3039 Configuration daemon -netopeer running 3548 NETCONF server -dnsmasq running 2249 DHCP/DNS proxy -tty:hvc0 running 3559 Getty on hvc0 -iitod running 2340 LED daemon -klishd running 3560 CLI backend daemon -mdns-alias running 3617 mDNS alias advertiser -mstpd stopped - Spanning Tree daemon -rauc running 3564 Software update service -resolvconf done - Update DNS configuration -statd running 3472 Status daemon -staticd running 3653 Static routing daemon -syslogd running 2241 System log daemon -watchdogd running 2242 System watchdog daemon -zebra running 3587 Zebra routing daemon -mdns running 3616 Avahi mDNS-SD daemon -chronyd running 3618 Chrony NTP v3/v4 daemon -lldpd running 3633 LLDP daemon (IEEE 802.1ab) -nginx running 3635 Web server -rousette running 3636 RESTCONF server -sshd running 3641 OpenSSH daemon +NAME STATUS PID MEM UP RST DESCRIPTION  +udevd running 1185 23m 0 Device event daemon (udev) +dbus running 2248 23m 0 D-Bus message bus daemon +confd running 3039 23m 0 Configuration daemon +netopeer running 3548 23m 0 NETCONF server +dnsmasq running 2249 23m 0 DHCP/DNS proxy +tty:hvc0 running 3559 23m 0 Getty on hvc0 +iitod running 2340 23m 0 LED daemon +klishd running 3560 23m 0 CLI backend daemon +mdns-alias running 3617 23m 0 mDNS alias advertiser +mstpd stopped 0 Spanning Tree daemon +rauc running 3564 23m 0 Software update service +resolvconf done 2 Update DNS configuration +statd running 3472 23m 0 Status daemon +staticd running 3653 23m 0 Static routing daemon +syslogd running 2241 23m 0 System log daemon +watchdogd running 2242 23m 0 System watchdog daemon +zebra running 3587 23m 0 Zebra routing daemon +mdns running 3616 23m 0 Avahi mDNS-SD daemon +chronyd running 3618 23m 0 Chrony NTP v3/v4 daemon +lldpd running 3633 23m 0 LLDP daemon (IEEE 802.1ab) +nginx running 3635 23m 0 Web server +rousette running 3636 23m 0 RESTCONF server +sshd running 3641 23m 0 OpenSSH daemon diff --git a/test/case/statd/system/ietf-system.json b/test/case/statd/system/ietf-system.json index 02b31b19e..404822eed 100644 --- a/test/case/statd/system/ietf-system.json +++ b/test/case/statd/system/ietf-system.json @@ -135,139 +135,254 @@ "pid": 1185, "name": "udevd", "status": "running", - "description": "Device event daemon (udev)" + "description": "Device event daemon (udev)", + "statistics": { + "memory-usage": "0", + "uptime": "1390", + "restart-count": 0 + } }, { "pid": 2248, "name": "dbus", "status": "running", - "description": "D-Bus message bus daemon" + "description": "D-Bus message bus daemon", + "statistics": { + "memory-usage": "0", + "uptime": "1390", + "restart-count": 0 + } }, { "pid": 3039, "name": "confd", "status": "running", - "description": "Configuration daemon" + "description": "Configuration daemon", + "statistics": { + "memory-usage": "0", + "uptime": "1390", + "restart-count": 0 + } }, { "pid": 3548, "name": "netopeer", "status": "running", - "description": "NETCONF server" + "description": "NETCONF server", + "statistics": { + "memory-usage": "0", + "uptime": "1388", + "restart-count": 0 + } }, { "pid": 2249, "name": "dnsmasq", "status": "running", - "description": "DHCP/DNS proxy" + "description": "DHCP/DNS proxy", + "statistics": { + "memory-usage": "0", + "uptime": "1390", + "restart-count": 0 + } }, { "pid": 3559, "name": "tty:hvc0", "status": "running", - "description": "Getty on hvc0" + "description": "Getty on hvc0", + "statistics": { + "memory-usage": "0", + "uptime": "1388", + "restart-count": 0 + } }, { "pid": 2340, "name": "iitod", "status": "running", - "description": "LED daemon" + "description": "LED daemon", + "statistics": { + "memory-usage": "0", + "uptime": "1390", + "restart-count": 0 + } }, { "pid": 3560, "name": "klishd", "status": "running", - "description": "CLI backend daemon" + "description": "CLI backend daemon", + "statistics": { + "memory-usage": "0", + "uptime": "1388", + "restart-count": 0 + } }, { "pid": 3617, "name": "mdns-alias", "status": "running", - "description": "mDNS alias advertiser " + "description": "mDNS alias advertiser ", + "statistics": { + "memory-usage": "0", + "uptime": "1388", + "restart-count": 0 + } }, { "pid": 0, "name": "mstpd", "status": "stopped", - "description": "Spanning Tree daemon" + "description": "Spanning Tree daemon", + "statistics": { + "memory-usage": "0", + "uptime": "0", + "restart-count": 0 + } }, { "pid": 3564, "name": "rauc", "status": "running", - "description": "Software update service" + "description": "Software update service", + "statistics": { + "memory-usage": "0", + "uptime": "1388", + "restart-count": 0 + } }, { "pid": 0, "name": "resolvconf", "status": "done", - "description": "Update DNS configuration" + "description": "Update DNS configuration", + "statistics": { + "memory-usage": "0", + "uptime": "0", + "restart-count": 2 + } }, { "pid": 3472, "name": "statd", "status": "running", - "description": "Status daemon" + "description": "Status daemon", + "statistics": { + "memory-usage": "0", + "uptime": "1389", + "restart-count": 0 + } }, { "pid": 3653, "name": "staticd", "status": "running", - "description": "Static routing daemon" + "description": "Static routing daemon", + "statistics": { + "memory-usage": "0", + "uptime": "1388", + "restart-count": 0 + } }, { "pid": 2241, "name": "syslogd", "status": "running", - "description": "System log daemon" + "description": "System log daemon", + "statistics": { + "memory-usage": "0", + "uptime": "1390", + "restart-count": 0 + } }, { "pid": 2242, "name": "watchdogd", "status": "running", - "description": "System watchdog daemon" + "description": "System watchdog daemon", + "statistics": { + "memory-usage": "0", + "uptime": "1390", + "restart-count": 0 + } }, { "pid": 3587, "name": "zebra", "status": "running", - "description": "Zebra routing daemon" + "description": "Zebra routing daemon", + "statistics": { + "memory-usage": "0", + "uptime": "1388", + "restart-count": 0 + } }, { "pid": 3616, "name": "mdns", "status": "running", - "description": "Avahi mDNS-SD daemon" + "description": "Avahi mDNS-SD daemon", + "statistics": { + "memory-usage": "0", + "uptime": "1388", + "restart-count": 0 + } }, { "pid": 3618, "name": "chronyd", "status": "running", - "description": "Chrony NTP v3/v4 daemon" + "description": "Chrony NTP v3/v4 daemon", + "statistics": { + "memory-usage": "0", + "uptime": "1388", + "restart-count": 0 + } }, { "pid": 3633, "name": "lldpd", "status": "running", - "description": "LLDP daemon (IEEE 802.1ab)" + "description": "LLDP daemon (IEEE 802.1ab)", + "statistics": { + "memory-usage": "0", + "uptime": "1388", + "restart-count": 0 + } }, { "pid": 3635, "name": "nginx", "status": "running", - "description": "Web server" + "description": "Web server", + "statistics": { + "memory-usage": "0", + "uptime": "1388", + "restart-count": 0 + } }, { "pid": 3636, "name": "rousette", "status": "running", - "description": "RESTCONF server" + "description": "RESTCONF server", + "statistics": { + "memory-usage": "0", + "uptime": "1388", + "restart-count": 0 + } }, { "pid": 3641, "name": "sshd", "status": "running", - "description": "OpenSSH daemon" + "description": "OpenSSH daemon", + "statistics": { + "memory-usage": "0", + "uptime": "1388", + "restart-count": 0 + } } ] } diff --git a/test/case/statd/system/operational.json b/test/case/statd/system/operational.json index e2621efc6..cee7cb7da 100644 --- a/test/case/statd/system/operational.json +++ b/test/case/statd/system/operational.json @@ -90,144 +90,291 @@ ] } }, + "infix-system:resource-usage": { + "filesystem": [ + { + "available": "0", + "mount-point": "/", + "size": "70912", + "used": "70912" + }, + { + "available": "79314", + "mount-point": "/var", + "size": "86459", + "used": "267" + }, + { + "available": "12861", + "mount-point": "/cfg", + "size": "14073", + "used": "66" + } + ], + "load-average": { + "load-15min": "0.01", + "load-1min": "0.16", + "load-5min": "0.03" + }, + "memory": { + "available": "259640", + "free": "187776", + "total": "355076" + } + }, "infix-system:services": { "service": [ { "description": "Device event daemon (udev)", "name": "udevd", "pid": 1185, + "statistics": { + "memory-usage": "0", + "restart-count": 0, + "uptime": "1390" + }, "status": "running" }, { "description": "D-Bus message bus daemon", "name": "dbus", "pid": 2248, + "statistics": { + "memory-usage": "0", + "restart-count": 0, + "uptime": "1390" + }, "status": "running" }, { "description": "Configuration daemon", "name": "confd", "pid": 3039, + "statistics": { + "memory-usage": "0", + "restart-count": 0, + "uptime": "1390" + }, "status": "running" }, { "description": "NETCONF server", "name": "netopeer", "pid": 3548, + "statistics": { + "memory-usage": "0", + "restart-count": 0, + "uptime": "1388" + }, "status": "running" }, { "description": "DHCP/DNS proxy", "name": "dnsmasq", "pid": 2249, + "statistics": { + "memory-usage": "0", + "restart-count": 0, + "uptime": "1390" + }, "status": "running" }, { "description": "Getty on hvc0", "name": "tty:hvc0", "pid": 3559, + "statistics": { + "memory-usage": "0", + "restart-count": 0, + "uptime": "1388" + }, "status": "running" }, { "description": "LED daemon", "name": "iitod", "pid": 2340, + "statistics": { + "memory-usage": "0", + "restart-count": 0, + "uptime": "1390" + }, "status": "running" }, { "description": "CLI backend daemon", "name": "klishd", "pid": 3560, + "statistics": { + "memory-usage": "0", + "restart-count": 0, + "uptime": "1388" + }, "status": "running" }, { "description": "mDNS alias advertiser ", "name": "mdns-alias", "pid": 3617, + "statistics": { + "memory-usage": "0", + "restart-count": 0, + "uptime": "1388" + }, "status": "running" }, { "description": "Spanning Tree daemon", "name": "mstpd", "pid": 0, + "statistics": { + "memory-usage": "0", + "restart-count": 0, + "uptime": "0" + }, "status": "stopped" }, { "description": "Software update service", "name": "rauc", "pid": 3564, + "statistics": { + "memory-usage": "0", + "restart-count": 0, + "uptime": "1388" + }, "status": "running" }, { "description": "Update DNS configuration", "name": "resolvconf", "pid": 0, + "statistics": { + "memory-usage": "0", + "restart-count": 2, + "uptime": "0" + }, "status": "done" }, { "description": "Status daemon", "name": "statd", "pid": 3472, + "statistics": { + "memory-usage": "0", + "restart-count": 0, + "uptime": "1389" + }, "status": "running" }, { "description": "Static routing daemon", "name": "staticd", "pid": 3653, + "statistics": { + "memory-usage": "0", + "restart-count": 0, + "uptime": "1388" + }, "status": "running" }, { "description": "System log daemon", "name": "syslogd", "pid": 2241, + "statistics": { + "memory-usage": "0", + "restart-count": 0, + "uptime": "1390" + }, "status": "running" }, { "description": "System watchdog daemon", "name": "watchdogd", "pid": 2242, + "statistics": { + "memory-usage": "0", + "restart-count": 0, + "uptime": "1390" + }, "status": "running" }, { "description": "Zebra routing daemon", "name": "zebra", "pid": 3587, + "statistics": { + "memory-usage": "0", + "restart-count": 0, + "uptime": "1388" + }, "status": "running" }, { "description": "Avahi mDNS-SD daemon", "name": "mdns", "pid": 3616, + "statistics": { + "memory-usage": "0", + "restart-count": 0, + "uptime": "1388" + }, "status": "running" }, { "description": "Chrony NTP v3/v4 daemon", "name": "chronyd", "pid": 3618, + "statistics": { + "memory-usage": "0", + "restart-count": 0, + "uptime": "1388" + }, "status": "running" }, { "description": "LLDP daemon (IEEE 802.1ab)", "name": "lldpd", "pid": 3633, + "statistics": { + "memory-usage": "0", + "restart-count": 0, + "uptime": "1388" + }, "status": "running" }, { "description": "Web server", "name": "nginx", "pid": 3635, + "statistics": { + "memory-usage": "0", + "restart-count": 0, + "uptime": "1388" + }, "status": "running" }, { "description": "RESTCONF server", "name": "rousette", "pid": 3636, + "statistics": { + "memory-usage": "0", + "restart-count": 0, + "uptime": "1388" + }, "status": "running" }, { "description": "OpenSSH daemon", "name": "sshd", "pid": 3641, + "statistics": { + "memory-usage": "0", + "restart-count": 0, + "uptime": "1388" + }, "status": "running" } ]