Adds a compact vmstat applet that matches the default behaviour
of procps's vmstat.
function old new delta
vmstat_main - 872 +872
load_row - 850 +850
print_row - 311 +311
coldescs - 239 +239
.rodata 99981 100096 +115
find_col - 108 +108
packed_usage 34739 34761 +22
applet_main 3224 3232 +8
applet_names 2782 2789 +7
------------------------------------------------------------------------------
(add/remove: 6/0 grow/shrink: 4/0 up/down: 2532/0) Total: 2532 bytes
text data bss dec hex filename
1051556 16691 1664 1069911 105357 busybox_old
1054104 16699 1664 1072467 105d53 busybox_unstripped
---
procps/vmstat.c | 445 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 445 insertions(+)
create mode 100644 procps/vmstat.c
diff --git a/procps/vmstat.c b/procps/vmstat.c
new file mode 100644
index 000000000..ce808e1bf
--- /dev/null
+++ b/procps/vmstat.c
@@ -0,0 +1,445 @@
+/* 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"
+
+#undef CAREFUL /* Pedantic development checks that add some bloat */
+#ifdef CAREFUL
+# define IF_CAREFUL_ELSE(x, y) (x)
+#else
+# define IF_CAREFUL_ELSE(x, y) (y)
+#endif
+
+/* 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++)
+ {
+ unsigned char fromspec = *col.fromspec;
+ if (col.from == *FROM_PROC_STAT_CPU &&
+ IF_CAREFUL_ELSE(fromspec < _STAT_CPU_max,
1))
+ data[i] = num[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)))
+ data[colnum] = SwapTotal - SwapFree;
+ if ((colnum = find_col(*FROM_PROC_MEMINFO, PSEUDO_CACHE)))
+ 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;
+ }
+#ifdef CAREFUL
+ if (i != NCOLS)
+ bb_error_msg_and_die("NCOLS should be %u, not %u", i, NCOLS);
+#endif
+
+ 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 = 100 * value / percent_sum;
+ if ((col.mod & MOD_DECREMENT) && value)
+ value--;
+ if (is_first) {
+ printf("%*u", col.width, value);
+ is_first = 0;
+ } else
+ printf(" %*u", col.width, value);
+ }
+ 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--)
+ putchar('-');
+ }
+ 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 : 1 + col.width,
+ col.label);
+ is_first = 0;
+ }
+ 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.48.1
_______________________________________________
busybox mailing list
[email protected]
https://lists.busybox.net/mailman/listinfo/busybox