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

Reply via email to