Applied, thank you.
On Thu, Nov 13, 2025 at 10:52 AM David Leonard <[email protected]> wrote: > > > v2 > > * fixed small bugs in error handling, averaging > * fixed compiler warning > * removed some dev/debug code > > > Adds a compact vmstat applet that matches the default behaviour > of procps's vmstat. > > function old new delta > vmstat_main - 872 +872 > load_row - 852 +852 > print_row - 322 +322 > coldescs - 239 +239 > .rodata 99999 100119 +120 > find_col - 108 +108 > packed_usage 34738 34759 +21 > applet_main 3224 3232 +8 > applet_names 2782 2789 +7 > ------------------------------------------------------------------------------ > (add/remove: 6/0 grow/shrink: 4/0 up/down: 2549/0) Total: 2549 bytes > text data bss dec hex filename > 1052603 16691 1664 1070958 10576e busybox_old > 1055168 16699 1664 1073531 10617b busybox_unstripped > --- > procps/vmstat.c | 426 ++++++++++++++++++++++++++++++++++++++++++++++++ > 1 file changed, 426 insertions(+) > create mode 100644 procps/vmstat.c > > diff --git a/procps/vmstat.c b/procps/vmstat.c > new file mode 100644 > index 000000000..bccd27d63 > --- /dev/null > +++ b/procps/vmstat.c > @@ -0,0 +1,426 @@ > +/* vi: set sw=4 ts=4: */ > +/* > + * Report virtual memory statistics. > + * > + * Copyright (C) 2025 David Leonard > + * > + * Licensed under GPLv2, see file LICENSE in this source tree. > + */ > +//config:config VMSTAT > +//config: bool "vmstat (3 kb)" > +//config: default y > +//config: help > +//config: Report virtual memory statistics > + > +//applet:IF_VMSTAT(APPLET(vmstat, BB_DIR_BIN, BB_SUID_DROP)) > + > +//kbuild:lib-$(CONFIG_VMSTAT) += vmstat.o > + > +#include "libbb.h" > +#include "common_bufsiz.h" > + > +/* Must match option string! */ > +enum { > + OPT_n = 1 << 0, > +}; > + > +#define FROM_PROC_STAT "\1" > +#define FROM_PROC_STAT_CPU "\2" > +#define FROM_PROC_VMSTAT "\3" > +#define FROM_PROC_MEMINFO "\4" > +#define M_DELTA "\x81" /* differentiate */ > +#define M_DPERCENT "\x83" /* DELTA + sum and scale to 100 */ > +#define M_DECREMENT "\x84" /* decrement (exclude self proc) */ > +#define PSEUDO_SWPD "_1" /* SwapTotal - SwapFree */ > +#define PSEUDO_CACHE "_2" /* Cached + SReclaimable */ > + > +/* Column descriptors */ > +static const char coldescs[] = > + /* [grplabel\0] (\width label\0 from [m_mod] fromspec\0)+ */ > + "procs\0" "\2r\0" FROM_PROC_STAT M_DECREMENT "procs_running\0" > + "\2b\0" FROM_PROC_STAT "procs_blocked\0" > + "memory\0" "\6swpd\0" FROM_PROC_MEMINFO PSEUDO_SWPD "\0" > + "\6free\0" FROM_PROC_MEMINFO "MemFree\0" > + "\6buff\0" FROM_PROC_MEMINFO "Buffers\0" > + "\6cache\0" FROM_PROC_MEMINFO PSEUDO_CACHE "\0" > + "swap\0" "\4si\0" FROM_PROC_VMSTAT M_DELTA "pswpin\0" > + "\4so\0" FROM_PROC_VMSTAT M_DELTA "pswpout\0" > + "io\0" "\5bi\0" FROM_PROC_VMSTAT M_DELTA "pgpgin\0" > + "\5bo\0" FROM_PROC_VMSTAT M_DELTA "pgpgout\0" > + "system\0" "\4in\0" FROM_PROC_STAT M_DELTA "intr\0" > + "\4cs\0" FROM_PROC_STAT M_DELTA "ctxt\0" > + "cpu\0" "\2us\0" FROM_PROC_STAT_CPU M_DPERCENT "\x0d" /* user */ > + "\2sy\0" FROM_PROC_STAT_CPU M_DPERCENT "\x0b" /* system > */ > + "\2id\0" FROM_PROC_STAT_CPU M_DPERCENT "\x04" /* idle */ > + "\2wa\0" FROM_PROC_STAT_CPU M_DPERCENT "\x05" /* iowait > */ > + "\2st\0" FROM_PROC_STAT_CPU M_DPERCENT "\x08" /* steal > */ > + "\2gu\0" FROM_PROC_STAT_CPU M_DPERCENT "\x0c" /* guest > */ > + ; > + > +/* Packed row data from coldescs[] is decoded into this structure */ > +struct col { > + const char *grplabel; > + const char *label; > + const char *fromspec; > + unsigned char from; > + unsigned char width; > + unsigned char mod; > +#define MOD_DELTA 0x01 > +#define MOD_PERCENT 0x02 > +#define MOD_DECREMENT 0x04 > +}; > + > +/* Number of columns defined in coldescs[] */ > +#define NCOLS (2+4+2+2+2+6) > + > +/* Globals. Sort by size and access frequency. */ > +struct globals { > + unsigned data1[NCOLS]; > + unsigned data2[NCOLS]; > +}; > +#define G (*(struct globals*)bb_common_bufsiz1) > +#define INIT_G() do { \ > + /* memset(&G, 0, sizeof G); */ \ > +} while (0) > + > + > +//usage:#define vmstat_trivial_usage > +//usage: "[-n] [INTERVAL [COUNT]]" > +//usage:#define vmstat_full_usage "\n\n" > +//usage: "Report virtual memory statistics\n" > +//usage: "\n -n Display the header only once" > + > +/* > + * Advance an iterator over the coldescs[] packed descriptors. > + * col_return - pointer to storage to hold the next unpacked descriptor. > + * cp - pointer to iterator storage; should be initialised with coldescs. > + * Returns 1 when *cp has been advanced, and *col_return filled > + * (i.e. col_return->label will not be NULL). > + * Returns 0 when coldescs[] has been exhausted, and sets col_return->label > + * and col_return->grplabel to NULL. > + */ > +static ALWAYS_INLINE bool next_col(struct col *col_return, const char **cp) > +{ > + if (!**cp) { > + col_return->label = NULL; > + col_return->grplabel = NULL; > + return 0; > + } > + if (**cp > ' ') { > + /* Only the first column of a group gets the grplabel */ > + col_return->grplabel = *cp; > + while (*(*cp)++) > + ; /* Skip over the grplabel */ > + } else > + col_return->grplabel = NULL; > + col_return->width = *(*cp)++; > + col_return->label = *cp; > + while (*(*cp)++) > + ; /* Skip over the label */ > + col_return->from = *(*cp)++; > + if (**cp & 0x80) > + col_return->mod = *(*cp)++; > + else > + col_return->mod = 0; > + col_return->fromspec = *cp; > + while (*(*cp)++ >= ' ') > + ; /* Skip over the fromspec */ > + return 1; > +} > + > +/* Compares two fromspec strings for equality. > + * A fromspec can be a C string, or be terminated inclusively with > + * a byte 1..31. */ > +static bool fromspec_equal(const char *fs1, const char *fs2) > +{ > + while (*fs1 == *fs2) { > + if (*fs1 < ' ') > + return 1; > + fs1++; > + fs2++; > + } > + return 0; > + > +} > + > +/* > + * Finds a column in coldescs[] that has the the given from and fromspec. > + * Returns the index if found, otherwise -1. > + */ > +static int find_col(unsigned char from, const char *fromspec) > +{ > + const char *coli; > + struct col col; > + int i; > + > + for (i = 0, coli = coldescs; next_col(&col, &coli); i++) > + if (col.from == from && > + fromspec_equal(col.fromspec, fromspec)) > + return i; > + return -1; > +} > + > +/* > + * Reads current system state into the data array elements corresponding > + * to coldescs[] columns. > + */ > +static void load_row(unsigned data[static NCOLS]) > +{ > + FILE *fp; > + char label[32]; > + char line[256]; > + int colnum; > + unsigned SwapFree = 0; > + unsigned SwapTotal = 0; > + unsigned Cached = 0; > + unsigned SReclaimable = 0; > + > + memset(data, 0, NCOLS * sizeof *data); > + > + /* > + * Open each FROM_* source and read all their items. These are > + * generally labeled integer items. As we read each item, hunt > + * through coldescs[] to see if a column uses it and, if one does, > + * store the item's value in the corresponding data[] element. > + */ > + > + /* FROM_PROC_STAT and FROM_PROC_STAT_CPU */ > + fp = xfopen_for_read("/proc/stat"); > + while (fgets(line, sizeof(line), fp)) { > + enum stat_cpu { > + STAT_CPU_user = 1, > + STAT_CPU_nice = 2, > + STAT_CPU_system = 3, > + STAT_CPU_idle = 4, > + STAT_CPU_iowait = 5, > + STAT_CPU_irq = 6, > + STAT_CPU_softirq = 7, > + STAT_CPU_steal = 8, > + STAT_CPU_guest = 9, /* included in user */ > + STAT_CPU_guest_nice = 10, /* included in nice */ > + /* computed columns */ > + STAT_CPU_pseudo_sy = 0xb, /* system + irq + softirq */ > + STAT_CPU_pseudo_gu = 0xc, /* guest + guest_nice */ > + STAT_CPU_pseudo_us = 0xd, /* user + nice - pseudo_gu > */ > + _STAT_CPU_max > + }; > + unsigned num[_STAT_CPU_max]; > + int n = sscanf(line, > + "%31s %u %u %u %u %u %u %u %u %u %u", > + label, > + &num[STAT_CPU_user], > +# define _STAT_CPU_first STAT_CPU_user > + &num[STAT_CPU_nice], > + &num[STAT_CPU_system], > + &num[STAT_CPU_idle], > + &num[STAT_CPU_iowait], > + &num[STAT_CPU_irq], > + &num[STAT_CPU_softirq], > + &num[STAT_CPU_steal], > + &num[STAT_CPU_guest], > + &num[STAT_CPU_guest_nice]); > + if (n == 11 && strcmp(label, "cpu") == 0) { > + const char *coli; > + struct col col; > + int i; > + > + num[STAT_CPU_pseudo_sy] = num[STAT_CPU_system] > + + num[STAT_CPU_irq] > + + num[STAT_CPU_softirq] > + ; > + num[STAT_CPU_pseudo_gu] = num[STAT_CPU_guest] > + + num[STAT_CPU_guest_nice] > + ; > + num[STAT_CPU_pseudo_us] = num[STAT_CPU_user] > + + num[STAT_CPU_nice] > + - num[STAT_CPU_pseudo_gu] > + ; > + for (i = 0, coli = coldescs; next_col(&col, &coli); > i++) > + if (col.from == *FROM_PROC_STAT_CPU) > + data[i] = num[(int)*col.fromspec]; > + } > + else if (n >= 2 && > + (colnum = find_col(*FROM_PROC_STAT, label)) != -1) > + data[colnum] = num[_STAT_CPU_first]; > + } > + fclose(fp); > + > + /* FROM_PROC_VMSTAT */ > + fp = xfopen_for_read("/proc/vmstat"); > + while (fgets(line, sizeof(line), fp)) { > + unsigned num; > + > + if (sscanf(line, "%31s %u", label, &num) == 2 && > + (colnum = find_col(*FROM_PROC_VMSTAT, label)) != -1) > + data[colnum] = num; > + } > + fclose(fp); > + > + /* FROM_PROC_MEMINFO */ > + fp = xfopen_for_read("/proc/meminfo"); > + while (fgets(line, sizeof(line), fp)) { > + unsigned num; > + > + if (sscanf(line, "%31[^:]: %u", label, &num) == 2) { > + /* Store some values for computed (pseudo) values */ > + if (strcmp(label, "SwapTotal") == 0) > + SwapTotal = num; > + else if (strcmp(label, "SwapFree") == 0) > + SwapFree = num; > + else if (strcmp(label, "Cached") == 0) > + Cached = num; > + else if (strcmp(label, "SReclaimable") == 0) > + SReclaimable = num; > + > + if ((colnum = find_col(*FROM_PROC_MEMINFO, > + label)) != -1) > + data[colnum] = num; > + } > + } > + fclose(fp); > + > + /* "Pseudo" items computed from other items */ > + if ((colnum = find_col(*FROM_PROC_MEMINFO, PSEUDO_SWPD)) != -1) > + data[colnum] = SwapTotal - SwapFree; > + if ((colnum = find_col(*FROM_PROC_MEMINFO, PSEUDO_CACHE)) != -1) > + data[colnum] = Cached + SReclaimable; > +} > + > +/* Modify, format and print a row of data values */ > +static void print_row(const unsigned data[static NCOLS], > + const unsigned old_data[static NCOLS]) > +{ > + const char *coli; > + struct col col; > + smalluint i; > + bool is_first; > + unsigned percent_sum = 0; > + > + for (coli = coldescs, i = 0; next_col(&col, &coli); i++) > + if (col.mod & MOD_PERCENT) { > + unsigned value = data[i]; > + if (col.mod & MOD_DELTA) > + value -= old_data[i]; > + percent_sum += value; > + } > + > + is_first = 1; > + for (coli = coldescs, i = 0; next_col(&col, &coli); i++) { > + unsigned value = data[i]; > + > + if (col.mod & MOD_DELTA) > + value -= old_data[i]; > + if (col.mod & MOD_PERCENT) > + value = percent_sum ? 100 * value / percent_sum : 0; > + if ((col.mod & MOD_DECREMENT) && value) > + value--; > + printf(" %*u" + is_first, col.width, value); > + is_first = 0; > + } > + bb_putchar('\n'); > +} > + > +/* Print column header rows */ > +static void print_header(void) > +{ > + const char *coli; > + struct col col; > + bool is_first; > + > + /* First header row is the group labels */ > + coli = coldescs; > + next_col(&col, &coli); > + is_first = 1; > + while (col.label) { > + const char *grplabel = col.grplabel; > + smalluint gllen = strlen(grplabel); > + smalluint grpwidth = col.width; > + smalluint nhy = 0; > + > + /* Sum the column widths */ > + next_col(&col, &coli); > + while (col.label && !col.grplabel) { > + grpwidth += 1 + col.width; > + next_col(&col, &coli); > + } > + > + if (is_first) > + is_first = 0; > + else > + bb_putchar(' '); > + > + /* Centre the grplabel within grpwidth hyphens. */ > + while (gllen < grpwidth) { > + bb_putchar('-'); > + grpwidth--; > + if (gllen < grpwidth) > + grpwidth--, nhy++; > + } > + while (*grplabel) > + bb_putchar(*grplabel++); > + while (nhy--) > + bb_putchar('-'); > + } > + bb_putchar('\n'); > + > + /* Second header row is right-justified column labels */ > + coli = coldescs; > + is_first = 1; > + while (next_col(&col, &coli)) { > + printf(" %*s" + is_first, col.width, col.label); > + is_first = 0; > + } > + bb_putchar('\n'); > +} > + > +int vmstat_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; > +int vmstat_main(int argc UNUSED_PARAM, char **argv) > +{ > + int opt; > + unsigned interval = 0; > + int count = 1; > + unsigned height = 24; > + unsigned rows; > + > + INIT_G(); > + > + /* Parse and process arguments */ > + opt = getopt32(argv, "n"); > + argv += optind; > + > + if (*argv) { > + interval = xatoi_positive(*argv); > + count = (interval != 0 ? -1 : 1); > + argv++; > + if (*argv) > + count = xatoi_positive(*argv); > + } > + > + /* Prepare to re-print the header row after it scrolls off */ > + if (opt & OPT_n) > + height = 0; > + else > + get_terminal_width_height(STDOUT_FILENO, NULL, &height); > + > + /* Main loop */ > + for (rows = 0;; rows++) { > + if (!rows || (height > 5 && (rows % (height - 3)) == 0)) > + print_header(); > + > + /* Flip between using data1/2 and data2/1 for old/new */ > + if (rows & 1) { > + load_row(G.data1); > + print_row(G.data1, G.data2); > + } else { > + load_row(G.data2); > + print_row(G.data2, G.data1); > + } > + > + if (count > 0 && --count == 0) > + break; > + > + sleep(interval); > + } > + > + return EXIT_SUCCESS; > +} > -- > 2.43.0 > > _______________________________________________ > busybox mailing list > [email protected] > https://lists.busybox.net/mailman/listinfo/busybox _______________________________________________ busybox mailing list [email protected] https://lists.busybox.net/mailman/listinfo/busybox
