From 67d9d2be89b4075c8d7dbac439d6e4a08a31387f Mon Sep 17 00:00:00 2001 From: Richard Alpe Date: Tue, 2 Dec 2025 12:35:05 +0100 Subject: [PATCH 1/4] confd: extend infix-system with service runtime statistics Add statistics container to service model with memory usage, uptime, and restart count tracking. Updates YANG revision to 2025-12-02. Signed-off-by: Richard Alpe --- src/confd/yang/confd.inc | 2 +- src/confd/yang/confd/infix-system.yang | 27 +++++++++++++++++++ ...0-18.yang => infix-system@2025-12-02.yang} | 0 3 files changed, 28 insertions(+), 1 deletion(-) rename src/confd/yang/confd/{infix-system@2025-10-18.yang => infix-system@2025-12-02.yang} (100%) 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 From 312b5215c79603de8876d14eca773c59d134c7b6 Mon Sep 17 00:00:00 2001 From: Richard Alpe Date: Tue, 2 Dec 2025 12:37:35 +0100 Subject: [PATCH 2/4] statd: populate service runtime statistic Populate operational runtime statistics for services. Signed-off-by: Richard Alpe --- src/statd/python/yanger/ietf_system.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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) From 868965bf7931ddfd3e8cc6c4ef65e2fc84e5c722 Mon Sep 17 00:00:00 2001 From: Richard Alpe Date: Tue, 2 Dec 2025 12:40:01 +0100 Subject: [PATCH 3/4] cli: print service statistics using new table framework Replace manual f-string formatting with SimpleTable/Column classes. This new "framework" handles ANSI colors and padding. Removing the hassle of manually calculating padding. You simply specify the header with max number of chars the data can be and if you want left/right padding and the "framework" calculates the padding for you. We use this new "framework" to pretty print the newly added services statistics. Signed-off-by: Richard Alpe --- src/statd/python/cli_pretty/cli_pretty.py | 134 +++++++++++++++++++--- 1 file changed, 116 insertions(+), 18 deletions(-) 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): From 9a26231b741f4c28cb8855cedef157b0b7c48ad8 Mon Sep 17 00:00:00 2001 From: Richard Alpe Date: Tue, 2 Dec 2025 16:27:31 +0100 Subject: [PATCH 4/4] test: update unit data with service statistics Signed-off-by: Richard Alpe --- test/case/statd/system/cli/show-services | 48 +++---- test/case/statd/system/ietf-system.json | 161 +++++++++++++++++++---- test/case/statd/system/operational.json | 147 +++++++++++++++++++++ 3 files changed, 309 insertions(+), 47 deletions(-) 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" } ]