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

Reply via email to