From a5ab5f0ab7b1e3abf9d2490a67daefc93cec5102 Mon Sep 17 00:00:00 2001 From: Jorge Guzman Date: Fri, 10 Jul 2026 18:17:54 -0300 Subject: [PATCH] nshlib: top: add Linux-like summary and flicker-free refresh Print a summary header before the task list in the style of the Linux top command: uptime, task counts (total/running/sleeping), CPU busy/idle and memory usage, all gathered via sysinfo(). Refresh the screen in place (cursor home + erase-line per row + erase-below) instead of clearing the whole screen every cycle, which removes the flicker. Poll stdin during the update interval and exit on 'q', ESC or Ctrl-C. Previously the only exit path was the SIGINT handler, which requires both CONFIG_ENABLE_ALL_SIGNALS (handler) and CONFIG_TTY_SIGINT (serial console converts Ctrl-C into SIGINT, default n); on configurations missing either option top could not be terminated at all. Tested on linum-stm32h753bi:nsh. Signed-off-by: Jorge Guzman --- nshlib/nsh_proccmds.c | 124 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 120 insertions(+), 4 deletions(-) diff --git a/nshlib/nsh_proccmds.c b/nshlib/nsh_proccmds.c index 02a9869aa..8be9fe2d2 100644 --- a/nshlib/nsh_proccmds.c +++ b/nshlib/nsh_proccmds.c @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -36,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -884,6 +886,107 @@ static void top_exit(int signo, FAR siginfo_t *siginfo, FAR void *context) } #endif /* CONFIG_ENABLE_ALL_SIGNALS */ +/**************************************************************************** + * Name: top_state_is_running + ****************************************************************************/ + +static bool top_state_is_running(FAR const char *state) +{ + return strncmp(state, "Running", 7) == 0 || + strncmp(state, "Ready", 5) == 0 || + strncmp(state, "Pending", 7) == 0; +} + +/**************************************************************************** + * Name: top_summary + * + * Description: + * Print a Linux-top-like summary header: uptime, task counts, CPU + * busy/idle and memory usage. Lines end with an erase-to-eol so the + * screen can be refreshed in place. + * + ****************************************************************************/ + +static void top_summary(FAR struct nsh_vtbl_s *vtbl, + FAR struct nsh_topstatus_s *topstatus) +{ + struct sysinfo info; + unsigned long uptime; + unsigned long busy; + size_t running = 0; + size_t i; + + for (i = 0; i < topstatus->index; i++) + { + if (top_state_is_running(topstatus->status[i]->td_state)) + { + running++; + } + } + + sysinfo(&info); + uptime = info.uptime; + + if (uptime >= 86400) + { + nsh_output(vtbl, VT100_STR_CLEAREOL + "top - up %lud %02lu:%02lu:%02lu\n", + uptime / 86400, (uptime % 86400) / 3600, + (uptime % 3600) / 60, uptime % 60); + } + else + { + nsh_output(vtbl, VT100_STR_CLEAREOL "top - up %02lu:%02lu:%02lu\n", + uptime / 3600, (uptime % 3600) / 60, uptime % 60); + } + + nsh_output(vtbl, VT100_STR_CLEAREOL + "Tasks: %zu total, %zu running, %zu sleeping\n", + topstatus->index, running, topstatus->index - running); + + busy = (info.loads[0] * 1000) >> SI_LOAD_SHIFT; + nsh_output(vtbl, VT100_STR_CLEAREOL + "%%Cpu(s): %2lu.%lu busy, %2lu.%lu idle\n", + busy / 10, busy % 10, + (1000 - busy) / 10, (1000 - busy) % 10); + + nsh_output(vtbl, VT100_STR_CLEAREOL + "Mem : %8lu total, %8lu used, %8lu free\n", + info.totalram, info.totalram - info.freeram, info.freeram); + + nsh_output(vtbl, VT100_STR_CLEAREOL "\n"); +} + +/**************************************************************************** + * Name: top_wait_key + * + * Description: + * Sleep for the update interval, returning true if the user asked to + * quit (q, ESC or Ctrl-C read from stdin). + * + ****************************************************************************/ + +static bool top_wait_key(int delay) +{ + struct pollfd fds; + + fds.fd = STDIN_FILENO; + fds.events = POLLIN; + + if (poll(&fds, 1, delay * 1000) > 0) + { + char c; + + if (read(STDIN_FILENO, &c, 1) == 1 && + (c == 'q' || c == 0x1b || c == 0x03)) + { + return true; + } + } + + return false; +} + #endif /**************************************************************************** @@ -1417,11 +1520,12 @@ int cmd_top(FAR struct nsh_vtbl_s *vtbl, int argc, FAR char **argv) tc = nsh_ioctl(vtbl, TIOCSCTTY, getpid()); } + nsh_output(vtbl, VT100_STR_CLEARSCREEN); + while (!quit) { topstatus.index = 0; - nsh_output(vtbl, "\033[2J\033[1;1H"); - ps_title(vtbl, topstatus.heap); + nsh_output(vtbl, VT100_STR_CURSORHOME); if (pidlist) { @@ -1455,17 +1559,29 @@ int cmd_top(FAR struct nsh_vtbl_s *vtbl, int argc, FAR char **argv) qsort(topstatus.status, topstatus.index, sizeof(topstatus.status[0]), top_cmpcpuload); + top_summary(vtbl, &topstatus); + + nsh_output(vtbl, VT100_STR_CLEAREOL VT100_STR_BOLD); + ps_title(vtbl, topstatus.heap); + nsh_output(vtbl, VT100_STR_MODESOFF); + for (i = 0; i < MIN(topstatus.index, num); i++) { + nsh_output(vtbl, VT100_STR_CLEAREOL); ps_output(vtbl, topstatus.heap, topstatus.status[i]); } if (vtbl->isctty && tc == 0) { - nsh_output(vtbl, "use Ctrl+c' to quit\n"); + nsh_output(vtbl, VT100_STR_CLEAREOL "use 'q' or Ctrl+c to quit\n"); } - sleep(delay); + nsh_output(vtbl, VT100_STR_CLEAREOS); + + if (top_wait_key(delay)) + { + break; + } } if (topstatus.status != NULL)