summaryrefslogtreecommitdiffstats
path: root/library
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 20:34:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 20:34:44 +0000
commite3be059d4da38aa36f1aee1d56f8ceb943d92f1c (patch)
tree26edef31e4e503dd1c92a112de174f366dd61802 /library
parentInitial commit. (diff)
downloadprocps-e3be059d4da38aa36f1aee1d56f8ceb943d92f1c.tar.xz
procps-e3be059d4da38aa36f1aee1d56f8ceb943d92f1c.zip
Adding upstream version 2:4.0.4.upstream/2%4.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'library')
-rw-r--r--library/devname.c364
-rw-r--r--library/diskstats.c1027
-rw-r--r--library/escape.c157
-rw-r--r--library/include/devname.h10
-rw-r--r--library/include/diskstats.h135
-rw-r--r--library/include/escape.h35
-rw-r--r--library/include/meminfo.h216
-rw-r--r--library/include/misc.h90
-rw-r--r--library/include/numa.h30
-rw-r--r--library/include/pids.h287
-rw-r--r--library/include/procps-private.h28
-rw-r--r--library/include/pwcache.h38
-rw-r--r--library/include/readproc.h303
-rw-r--r--library/include/slabinfo.h140
-rw-r--r--library/include/stat.h175
-rw-r--r--library/include/vmstat.h382
-rw-r--r--library/include/wchan.h27
-rw-r--r--library/include/xtra-procps-debug.h208
-rw-r--r--library/libproc2.pc.in11
-rw-r--r--library/libproc2.sym67
-rw-r--r--library/meminfo.c1016
-rw-r--r--library/namespace.c116
-rw-r--r--library/numa.c114
-rw-r--r--library/pids.c1700
-rw-r--r--library/pwcache.c99
-rw-r--r--library/readproc.c1627
-rw-r--r--library/slabinfo.c1044
-rw-r--r--library/stat.c1443
-rw-r--r--library/sysinfo.c167
-rw-r--r--library/tests/test_Itemtables.c91
-rw-r--r--library/tests/test_namespace.c76
-rw-r--r--library/tests/test_pids.c75
-rw-r--r--library/tests/test_sysinfo.c64
-rw-r--r--library/tests/test_uptime.c97
-rw-r--r--library/tests/test_version.c72
-rw-r--r--library/uptime.c268
-rw-r--r--library/version.c71
-rw-r--r--library/vmstat.c1513
-rw-r--r--library/wchan.c54
39 files changed, 13437 insertions, 0 deletions
diff --git a/library/devname.c b/library/devname.c
new file mode 100644
index 0000000..6a23653
--- /dev/null
+++ b/library/devname.c
@@ -0,0 +1,364 @@
+/*
+ * devname - device name functions
+ *
+ * Copyright © 2011-2023 Jim Warner <james.warner@comcast.net>
+ * Copyright © 2017-2023 Craig Small <csmall@dropbear.xyz>
+ * Copyright © 1998-2009 Albert Cahalan
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include "misc.h"
+#include "devname.h"
+
+// This is the buffer size for a tty name. Any path is legal,
+// which makes PAGE_SIZE appropriate (see kernel source), but
+// that is only 99% portable and utmp only holds 32 anyway.
+// We need at least 20 for guess_name().
+#define TTY_NAME_SIZE 128
+
+/* Who uses what:
+ *
+ * dev_to_tty top, ps
+ */
+
+#ifdef MAJOR_IN_MKDEV
+#include <sys/mkdev.h>
+#elif defined MAJOR_IN_SYSMACROS
+#include <sys/sysmacros.h>
+#else
+#define major(d) ( ((unsigned)(d)>>8u) & 0xfffu )
+#define minor(d) ( ((unsigned)(d)&0xffu) | (((unsigned)(d)&0xfff00000u)>>12u) )
+#endif
+
+typedef struct tty_map_node {
+ struct tty_map_node *next;
+ unsigned short devfs_type; // bool
+ unsigned short major_number;
+ unsigned minor_first;
+ unsigned minor_last;
+ char name[16];
+} tty_map_node;
+
+static __thread tty_map_node *tty_map = NULL;
+
+/* Load /proc/tty/drivers for device name mapping use. */
+static void load_drivers(void){
+ char buf[10000];
+ char *p;
+ int fd;
+ int bytes;
+ fd = open("/proc/tty/drivers",O_RDONLY);
+ if(fd == -1) goto fail;
+ bytes = read(fd, buf, sizeof(buf) - 1);
+ if(bytes == -1) goto fail;
+ buf[bytes] = '\0';
+ p = buf;
+ while(( p = strstr(p, " /dev/") )){ // " /dev/" is the second column
+ tty_map_node *tmn;
+ size_t len;
+ char *end;
+ p += 6;
+ end = strchr(p, ' ');
+ if(!end) continue;
+ len = end - p;
+ if (!(tmn = calloc(1, sizeof(tty_map_node))))
+ goto fail;
+ tmn->next = tty_map;
+ tty_map = tmn;
+ /* if we have a devfs type name such as /dev/tts/%d then strip the %d but
+ keep a flag. */
+ if(len >= 3 && !strncmp(end - 2, "%d", 2)){
+ len -= 2;
+ tmn->devfs_type = 1;
+ }
+ if(len >= sizeof tmn->name)
+ len = sizeof tmn->name - 1; // mangle it to avoid overflow
+ memcpy(tmn->name, p, len);
+ p = end; /* set p to point past the %d as well if there is one */
+ while(*p == ' ') p++;
+ tmn->major_number = atoi(p);
+ p += strspn(p, "0123456789");
+ while(*p == ' ') p++;
+ switch(sscanf(p, "%u-%u", &tmn->minor_first, &tmn->minor_last)){
+ default:
+ /* Can't finish parsing this line so we remove it from the list */
+ tty_map = tty_map->next;
+ free(tmn);
+ break;
+ case 1:
+ tmn->minor_last = tmn->minor_first;
+ break;
+ case 2:
+ break;
+ }
+ }
+fail:
+ if(fd != -1) close(fd);
+ if(!tty_map) tty_map = (tty_map_node *)-1;
+}
+
+/* Try to guess the device name from /proc/tty/drivers info. */
+static int driver_name(char *restrict const buf, unsigned maj, unsigned min){
+ struct stat sbuf;
+ tty_map_node *tmn;
+ if(!tty_map) load_drivers();
+ if(tty_map == (tty_map_node *)-1) return 0;
+ tmn = tty_map;
+ for(;;){
+ if(!tmn) return 0;
+ if(tmn->major_number == maj && tmn->minor_first <= min && tmn->minor_last >= min) break;
+ tmn = tmn->next;
+ }
+ sprintf(buf, "/dev/%s%d", tmn->name, min); /* like "/dev/ttyZZ255" */
+ if(stat(buf, &sbuf) < 0){
+ sprintf(buf, "/dev/%s/%d", tmn->name, min); /* like "/dev/pts/255" */
+ if(stat(buf, &sbuf) < 0){
+ if(tmn->devfs_type) return 0;
+ sprintf(buf, "/dev/%s", tmn->name); /* like "/dev/ttyZZ255" */
+ if(stat(buf, &sbuf) < 0) return 0;
+ }
+ }
+ if(min != minor(sbuf.st_rdev)) return 0;
+ if(maj != major(sbuf.st_rdev)) return 0;
+ return 1;
+}
+
+// major 204 is a mess -- "Low-density serial ports"
+static const char low_density_names[][6] = {
+"LU0", "LU1", "LU2", "LU3",
+"FB0",
+"SA0", "SA1", "SA2",
+"SC0", "SC1", "SC2", "SC3",
+"FW0", "FW1", "FW2", "FW3",
+"AM0", "AM1", "AM2", "AM3", "AM4", "AM5", "AM6", "AM7",
+"AM8", "AM9", "AM10", "AM11", "AM12", "AM13", "AM14", "AM15",
+"DB0", "DB1", "DB2", "DB3", "DB4", "DB5", "DB6", "DB7",
+"SG0",
+"SMX0", "SMX1", "SMX2",
+"MM0", "MM1",
+"CPM0", "CPM1", "CPM2", "CPM3", /* "CPM4", "CPM5", */ // bad allocation?
+"IOC0", "IOC1", "IOC2", "IOC3", "IOC4", "IOC5", "IOC6", "IOC7",
+"IOC8", "IOC9", "IOC10", "IOC11", "IOC12", "IOC13", "IOC14", "IOC15",
+"IOC16", "IOC17", "IOC18", "IOC19", "IOC20", "IOC21", "IOC22", "IOC23",
+"IOC24", "IOC25", "IOC26", "IOC27", "IOC28", "IOC29", "IOC30", "IOC31",
+"VR0", "VR1",
+"IOC84", "IOC85", "IOC86", "IOC87", "IOC88", "IOC89", "IOC90", "IOC91",
+"IOC92", "IOC93", "IOC94", "IOC95", "IOC96", "IOC97", "IOC98", "IOC99",
+"IOC100", "IOC101", "IOC102", "IOC103", "IOC104", "IOC105", "IOC106", "IOC107",
+"IOC108", "IOC109", "IOC110", "IOC111", "IOC112", "IOC113", "IOC114", "IOC115",
+"SIOC0", "SIOC1", "SIOC2", "SIOC3", "SIOC4", "SIOC5", "SIOC6", "SIOC7",
+"SIOC8", "SIOC9", "SIOC10", "SIOC11", "SIOC12", "SIOC13", "SIOC14", "SIOC15",
+"SIOC16", "SIOC17", "SIOC18", "SIOC19", "SIOC20", "SIOC21", "SIOC22", "SIOC23",
+"SIOC24", "SIOC25", "SIOC26", "SIOC27", "SIOC28", "SIOC29", "SIOC30", "SIOC31",
+"PSC0", "PSC1", "PSC2", "PSC3", "PSC4", "PSC5",
+"AT0", "AT1", "AT2", "AT3", "AT4", "AT5", "AT6", "AT7",
+"AT8", "AT9", "AT10", "AT11", "AT12", "AT13", "AT14", "AT15",
+"NX0", "NX1", "NX2", "NX3", "NX4", "NX5", "NX6", "NX7",
+"NX8", "NX9", "NX10", "NX11", "NX12", "NX13", "NX14", "NX15",
+"J0", // minor is 186
+"UL0","UL1","UL2","UL3",
+"xvc0", // FAIL -- "/dev/xvc0" lacks "tty" prefix
+"PZ0","PZ1","PZ2","PZ3",
+"TX0","TX1","TX2","TX3","TX4","TX5","TX6","TX7",
+"SC0","SC1","SC2","SC3",
+"MAX0","MAX1","MAX2","MAX3",
+};
+
+#if 0
+// test code
+#include <stdio.h>
+#define AS(x) (sizeof(x)/sizeof((x)[0]))
+int main(int argc, char *argv[]){
+ int i = 0;
+ while(i<AS(low_density_names)){
+ printf("%3d = /dev/tty%.*s\n",i,sizeof low_density_names[i],low_density_names[i]);
+ i++;
+ }
+ return 0;
+}
+#endif
+
+/* Try to guess the device name (useful until /proc/PID/tty is added) */
+static int guess_name(char *restrict const buf, unsigned maj, unsigned min){
+ struct stat sbuf;
+#ifndef __CYGWIN__
+ int t0, t1;
+#endif
+ unsigned tmpmin = min;
+
+ switch(maj){
+ case 3: /* /dev/[pt]ty[p-za-o][0-9a-z] is 936 */
+ if(tmpmin > 255) return 0; // should never happen; array index protection
+#ifdef __CYGWIN__
+ sprintf(buf, "cons%d", tmpmin);
+ /* Skip stat call. The reason is that cons devices are local to
+ * the processes running in that console. Calling stat from another
+ * console or pty will return -1. */
+ return 1;
+#else
+ t0 = "pqrstuvwxyzabcde"[tmpmin>>4];
+ t1 = "0123456789abcdef"[tmpmin&0x0f];
+ sprintf(buf, "/dev/tty%c%c", t0, t1);
+#endif
+ break;
+ case 4:
+ if(min<64){
+ sprintf(buf, "/dev/tty%d", min);
+ break;
+ }
+ sprintf(buf, "/dev/ttyS%d", min-64);
+ break;
+ case 11: sprintf(buf, "/dev/ttyB%d", min); break;
+ case 17: sprintf(buf, "/dev/ttyH%d", min); break;
+ case 19: sprintf(buf, "/dev/ttyC%d", min); break;
+ case 22: sprintf(buf, "/dev/ttyD%d", min); break; /* devices.txt */
+ case 23: sprintf(buf, "/dev/ttyD%d", min); break; /* driver code */
+ case 24: sprintf(buf, "/dev/ttyE%d", min); break;
+ case 32: sprintf(buf, "/dev/ttyX%d", min); break;
+ case 43: sprintf(buf, "/dev/ttyI%d", min); break;
+ case 46: sprintf(buf, "/dev/ttyR%d", min); break;
+ case 48: sprintf(buf, "/dev/ttyL%d", min); break;
+ case 57: sprintf(buf, "/dev/ttyP%d", min); break;
+ case 71: sprintf(buf, "/dev/ttyF%d", min); break;
+ case 75: sprintf(buf, "/dev/ttyW%d", min); break;
+ case 78: sprintf(buf, "/dev/ttyM%d", min); break; /* conflict */
+ case 105: sprintf(buf, "/dev/ttyV%d", min); break;
+ case 112: sprintf(buf, "/dev/ttyM%d", min); break; /* conflict */
+#ifdef __CYGWIN__
+ case 136: sprintf(buf, "/dev/pty%d", min); break;
+#else
+ /* 136 ... 143 are /dev/pts/0, /dev/pts/1, /dev/pts/2 ... */
+ case 136 ... 143: sprintf(buf, "/dev/pts/%d", min+(maj-136)*256); break;
+#endif
+ case 148: sprintf(buf, "/dev/ttyT%d", min); break;
+ case 154: sprintf(buf, "/dev/ttySR%d", min); break;
+ case 156: sprintf(buf, "/dev/ttySR%d", min+256); break;
+ case 164: sprintf(buf, "/dev/ttyCH%d", min); break;
+ case 166: sprintf(buf, "/dev/ttyACM%d", min); break; /* bummer, 9-char */
+ case 172: sprintf(buf, "/dev/ttyMX%d", min); break;
+ case 174: sprintf(buf, "/dev/ttySI%d", min); break;
+ case 188: sprintf(buf, "/dev/ttyUSB%d", min); break; /* bummer, 9-char */
+ case 204:
+ if(min >= sizeof low_density_names / sizeof low_density_names[0]) return 0;
+ memcpy(buf,"/dev/tty",8);
+ memcpy(buf+8, low_density_names[min], sizeof low_density_names[0]);
+ buf[8 + sizeof low_density_names[0]] = '\0';
+// snprintf(buf, 9 + sizeof low_density_names[0], "/dev/tty%.*s", sizeof low_density_names[0], low_density_names[min]);
+ break;
+ case 208: sprintf(buf, "/dev/ttyU%d", min); break;
+ case 216: sprintf(buf, "/dev/ttyUB%d", min); break; // "/dev/rfcomm%d" now?
+ case 224: sprintf(buf, "/dev/ttyY%d", min); break;
+ case 227: sprintf(buf, "/dev/3270/tty%d", min); break; /* bummer, HUGE */
+ case 229: sprintf(buf, "/dev/iseries/vtty%d", min); break; /* bummer, HUGE */
+ case 256: sprintf(buf, "/dev/ttyEQ%d", min); break;
+ default: return 0;
+ }
+ if(stat(buf, &sbuf) < 0) return 0;
+ if(min != minor(sbuf.st_rdev)) return 0;
+ if(maj != major(sbuf.st_rdev)) return 0;
+ return 1;
+}
+
+/* Linux 2.2 can give us filenames that might be correct.
+ * Useful names could be in /proc/PID/fd/2 (stderr, seldom redirected)
+ * and in /proc/PID/fd/255 (used by bash to remember the tty).
+ */
+static int link_name(char *restrict const buf, unsigned maj, unsigned min, int pid, const char *restrict name){
+ struct stat sbuf;
+ char path[32];
+ ssize_t count;
+ const int len = snprintf(path, sizeof path, "/proc/%d/%s", pid, name); /* often permission denied */
+ if(len <= 0 || (size_t)len >= sizeof path) return 0;
+ count = readlink(path,buf,TTY_NAME_SIZE-1);
+ if(count <= 0 || count >= TTY_NAME_SIZE-1) return 0;
+ buf[count] = '\0';
+ if(stat(buf, &sbuf) < 0) return 0;
+ if(min != minor(sbuf.st_rdev)) return 0;
+ if(maj != major(sbuf.st_rdev)) return 0;
+ return 1;
+}
+
+#ifdef USE_PROC_CTTY
+/* Cygwin keeps the name to the controlling tty in a virtual file called
+ /proc/PID/ctty, including a trailing LF (sigh). */
+static int ctty_name(char *restrict const buf, int pid) {
+ char path[32];
+ FILE *fp;
+ char *lf;
+ sprintf (path, "/proc/%d/ctty", pid); /* often permission denied */
+ fp = fopen (path, "r");
+ if (!fp)
+ return 0;
+ if (!fgets (buf,TTY_NAME_SIZE,fp))
+ {
+ fclose (fp);
+ return 0;
+ }
+ fclose (fp);
+ lf = strchr (buf, '\n');
+ if (lf)
+ *lf = (lf == buf ? '?' : '\0');
+ return 1;
+}
+#endif
+
+/* number --> name */
+unsigned dev_to_tty(char *restrict ret, unsigned chop, dev_t dev_t_dev, int pid, unsigned int flags) {
+ static __thread char buf[TTY_NAME_SIZE];
+ char *restrict tmp = buf;
+ unsigned dev = dev_t_dev;
+ unsigned i = 0;
+ int c;
+#ifdef USE_PROC_CTTY
+ if( ctty_name(tmp, pid )) goto abbrev;
+#endif
+ if(dev == 0u) goto no_tty;
+ if(driver_name(tmp, major(dev), minor(dev) )) goto abbrev;
+ if( link_name(tmp, major(dev), minor(dev), pid, "fd/2" )) goto abbrev;
+ if( guess_name(tmp, major(dev), minor(dev) )) goto abbrev;
+ if( link_name(tmp, major(dev), minor(dev), pid, "fd/255")) goto abbrev;
+ // fall through if unable to find a device file
+no_tty:
+ strcpy(ret, chop >= 1 ? "?" : "");
+ return 1;
+abbrev:
+ if((flags&ABBREV_DEV) && !strncmp(tmp,"/dev/",5) && tmp[5]) tmp += 5;
+ if((flags&ABBREV_TTY) && !strncmp(tmp,"tty", 3) && tmp[3]) tmp += 3;
+ if((flags&ABBREV_PTS) && !strncmp(tmp,"pts/", 4) && tmp[4]) tmp += 4;
+ /* gotta check before we chop or we may chop someone else's memory */
+ if(chop + (unsigned long)(tmp-buf) < sizeof buf)
+ tmp[chop] = '\0';
+ /* replace non-ASCII characters with '?' and return the number of chars */
+ while(i < chop){
+ c = *tmp;
+ tmp++;
+ if(!c) break;
+ i++;
+ if(c<=' ') c = '?';
+ if(c>126) c = '?';
+ *ret = c;
+ ret++;
+ }
+ *ret = '\0';
+ return i;
+}
diff --git a/library/diskstats.c b/library/diskstats.c
new file mode 100644
index 0000000..b4e41f3
--- /dev/null
+++ b/library/diskstats.c
@@ -0,0 +1,1027 @@
+/*
+ * diskstats.c - disk I/O related definitions for libproc2
+ *
+ * Copyright © 2015-2023 Craig Small <csmall@dropbear.xyz>
+ * Copyright © 2015-2023 Jim Warner <james.warner@comcast.net>
+ * Copyright © 2003 Albert Cahalan
+ * Copyright © 2003 Fabian Frederick
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "procps-private.h"
+#include "diskstats.h"
+
+/* The following define will cause the 'node_add' function to maintain our |
+ nodes list in ascending alphabetical order which could be used to avoid |
+ a sort on name. Without it, we default to a 'pull-up' stack at slightly |
+ more effort than a simple 'push-down' list to duplicate prior behavior. | */
+//#define ALPHABETIC_NODES
+
+#define DISKSTATS_LINE_LEN 1024
+#define DISKSTATS_NAME_LEN 34
+#define DISKSTATS_FILE "/proc/diskstats"
+#define SYSBLOCK_DIR "/sys/block"
+
+#define STACKS_INCR 64 // amount reap stack allocations grow
+#define STR_COMPARE strverscmp
+
+/* ----------------------------------------------------------------------- +
+ this provision can help ensure that our Item_table remains synchronized |
+ with the enumerators found in the associated header file. It's intended |
+ to only be used locally (& temporarily) at some point before a release! | */
+// #define ITEMTABLE_DEBUG //--------------------------------------------- |
+// ----------------------------------------------------------------------- +
+
+
+struct dev_data {
+ unsigned long reads;
+ unsigned long reads_merged;
+ unsigned long read_sectors;
+ unsigned long read_time;
+ unsigned long writes;
+ unsigned long writes_merged;
+ unsigned long write_sectors;
+ unsigned long write_time;
+ unsigned long io_inprogress;
+ unsigned long io_time;
+ unsigned long io_wtime;
+};
+
+struct dev_node {
+ char name[DISKSTATS_NAME_LEN+1];
+ int type;
+ int major;
+ int minor;
+ time_t stamped;
+ struct dev_data new;
+ struct dev_data old;
+ struct dev_node *next;
+};
+
+struct stacks_extent {
+ int ext_numstacks;
+ struct stacks_extent *next;
+ struct diskstats_stack **stacks;
+};
+
+struct ext_support {
+ int numitems; // includes 'logical_end' delimiter
+ enum diskstats_item *items; // includes 'logical_end' delimiter
+ struct stacks_extent *extents; // anchor for these extents
+};
+
+struct fetch_support {
+ struct diskstats_stack **anchor; // fetch consolidated extents
+ int n_alloc; // number of above pointers allocated
+ int n_inuse; // number of above pointers occupied
+ int n_alloc_save; // last known reap.stacks allocation
+ struct diskstats_reaped results; // count + stacks for return to caller
+};
+
+struct diskstats_info {
+ int refcount;
+ FILE *diskstats_fp;
+ time_t old_stamp; // previous read seconds
+ time_t new_stamp; // current read seconds
+ struct dev_node *nodes; // dev nodes anchor
+ struct ext_support select_ext; // supports concurrent select/reap
+ struct ext_support fetch_ext; // supports concurrent select/reap
+ struct fetch_support fetch; // support for procps_diskstats_reap
+ struct diskstats_result get_this; // used by procps_diskstats_get
+};
+
+
+// ___ Results 'Set' Support ||||||||||||||||||||||||||||||||||||||||||||||||||
+
+#define setNAME(e) set_diskstats_ ## e
+#define setDECL(e) static void setNAME(e) \
+ (struct diskstats_result *R, struct dev_node *N)
+
+// regular assignment
+#define DEV_set(e,t,x) setDECL(e) { R->result. t = N-> x; }
+#define REG_set(e,t,x) setDECL(e) { R->result. t = N->new. x; }
+// delta assignment
+#define HST_set(e,t,x) setDECL(e) { R->result. t = ( N->new. x - N->old. x ); }
+
+setDECL(noop) { (void)R; (void)N; }
+setDECL(extra) { (void)N; R->result.ul_int = 0; }
+
+DEV_set(NAME, str, name)
+DEV_set(TYPE, s_int, type)
+DEV_set(MAJOR, s_int, major)
+DEV_set(MINOR, s_int, minor)
+
+REG_set(READS, ul_int, reads)
+REG_set(READS_MERGED, ul_int, reads_merged)
+REG_set(READ_SECTORS, ul_int, read_sectors)
+REG_set(READ_TIME, ul_int, read_time)
+REG_set(WRITES, ul_int, writes)
+REG_set(WRITES_MERGED, ul_int, writes_merged)
+REG_set(WRITE_SECTORS, ul_int, write_sectors)
+REG_set(WRITE_TIME, ul_int, write_time)
+REG_set(IO_TIME, ul_int, io_time)
+REG_set(WEIGHTED_TIME, ul_int, io_wtime)
+
+REG_set(IO_INPROGRESS, s_int, io_inprogress)
+
+HST_set(DELTA_READS, s_int, reads)
+HST_set(DELTA_READS_MERGED, s_int, reads_merged)
+HST_set(DELTA_READ_SECTORS, s_int, read_sectors)
+HST_set(DELTA_READ_TIME, s_int, read_time)
+HST_set(DELTA_WRITES, s_int, writes)
+HST_set(DELTA_WRITES_MERGED, s_int, writes_merged)
+HST_set(DELTA_WRITE_SECTORS, s_int, write_sectors)
+HST_set(DELTA_WRITE_TIME, s_int, write_time)
+HST_set(DELTA_IO_TIME, s_int, io_time)
+HST_set(DELTA_WEIGHTED_TIME, s_int, io_wtime)
+
+#undef setDECL
+#undef DEV_set
+#undef REG_set
+#undef HST_set
+
+
+// ___ Sorting Support ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
+struct sort_parms {
+ int offset;
+ enum diskstats_sort_order order;
+};
+
+#define srtNAME(t) sort_diskstats_ ## t
+#define srtDECL(t) static int srtNAME(t) \
+ (const struct diskstats_stack **A, const struct diskstats_stack **B, struct sort_parms *P)
+
+srtDECL(s_int) {
+ const struct diskstats_result *a = (*A)->head + P->offset; \
+ const struct diskstats_result *b = (*B)->head + P->offset; \
+ return P->order * (a->result.s_int - b->result.s_int);
+}
+
+srtDECL(ul_int) {
+ const struct diskstats_result *a = (*A)->head + P->offset; \
+ const struct diskstats_result *b = (*B)->head + P->offset; \
+ if ( a->result.ul_int > b->result.ul_int ) return P->order > 0 ? 1 : -1; \
+ if ( a->result.ul_int < b->result.ul_int ) return P->order > 0 ? -1 : 1; \
+ return 0;
+}
+
+srtDECL(str) {
+ const struct diskstats_result *a = (*A)->head + P->offset;
+ const struct diskstats_result *b = (*B)->head + P->offset;
+ return P->order * STR_COMPARE(a->result.str, b->result.str);
+}
+
+srtDECL(noop) { \
+ (void)A; (void)B; (void)P; \
+ return 0;
+}
+
+#undef srtDECL
+
+
+// ___ Controlling Table ||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
+typedef void (*SET_t)(struct diskstats_result *, struct dev_node *);
+#ifdef ITEMTABLE_DEBUG
+#define RS(e) (SET_t)setNAME(e), DISKSTATS_ ## e, STRINGIFY(DISKSTATS_ ## e)
+#else
+#define RS(e) (SET_t)setNAME(e)
+#endif
+
+typedef int (*QSR_t)(const void *, const void *, void *);
+#define QS(t) (QSR_t)srtNAME(t)
+
+#define TS(t) STRINGIFY(t)
+#define TS_noop ""
+
+ /*
+ * Need it be said?
+ * This table must be kept in the exact same order as
+ * those *enum diskstats_item* guys ! */
+static struct {
+ SET_t setsfunc; // the actual result setting routine
+#ifdef ITEMTABLE_DEBUG
+ int enumnumb; // enumerator (must match position!)
+ char *enum2str; // enumerator name as a char* string
+#endif
+ QSR_t sortfunc; // sort cmp func for a specific type
+ char *type2str; // the result type as a string value
+} Item_table[] = {
+/* setsfunc sortfunc type2str
+ ------------------------ ----------- ---------- */
+ { RS(noop), QS(noop), TS_noop },
+ { RS(extra), QS(ul_int), TS_noop },
+
+ { RS(NAME), QS(str), TS(str) },
+ { RS(TYPE), QS(s_int), TS(s_int) },
+ { RS(MAJOR), QS(s_int), TS(s_int) },
+ { RS(MINOR), QS(s_int), TS(s_int) },
+
+ { RS(READS), QS(ul_int), TS(ul_int) },
+ { RS(READS_MERGED), QS(ul_int), TS(ul_int) },
+ { RS(READ_SECTORS), QS(ul_int), TS(ul_int) },
+ { RS(READ_TIME), QS(ul_int), TS(ul_int) },
+ { RS(WRITES), QS(ul_int), TS(ul_int) },
+ { RS(WRITES_MERGED), QS(ul_int), TS(ul_int) },
+ { RS(WRITE_SECTORS), QS(ul_int), TS(ul_int) },
+ { RS(WRITE_TIME), QS(ul_int), TS(ul_int) },
+ { RS(IO_TIME), QS(ul_int), TS(ul_int) },
+ { RS(WEIGHTED_TIME), QS(ul_int), TS(ul_int) },
+
+ { RS(IO_INPROGRESS), QS(s_int), TS(s_int) },
+
+ { RS(DELTA_READS), QS(s_int), TS(s_int) },
+ { RS(DELTA_READS_MERGED), QS(s_int), TS(s_int) },
+ { RS(DELTA_READ_SECTORS), QS(s_int), TS(s_int) },
+ { RS(DELTA_READ_TIME), QS(s_int), TS(s_int) },
+ { RS(DELTA_WRITES), QS(s_int), TS(s_int) },
+ { RS(DELTA_WRITES_MERGED), QS(s_int), TS(s_int) },
+ { RS(DELTA_WRITE_SECTORS), QS(s_int), TS(s_int) },
+ { RS(DELTA_WRITE_TIME), QS(s_int), TS(s_int) },
+ { RS(DELTA_IO_TIME), QS(s_int), TS(s_int) },
+ { RS(DELTA_WEIGHTED_TIME), QS(s_int), TS(s_int) },
+};
+
+ /* please note,
+ * this enum MUST be 1 greater than the highest value of any enum */
+enum diskstats_item DISKSTATS_logical_end = MAXTABLE(Item_table);
+
+#undef setNAME
+#undef srtNAME
+#undef RS
+#undef QS
+
+
+// ___ Private Functions ||||||||||||||||||||||||||||||||||||||||||||||||||||||
+// --- dev_node specific support ----------------------------------------------
+
+static struct dev_node *node_add (
+ struct diskstats_info *info,
+ struct dev_node *this)
+{
+ struct dev_node *prev, *walk;
+
+#ifdef ALPHABETIC_NODES
+ if (!info->nodes
+ || (STR_COMPARE(this->name, info->nodes->name) < 0)) {
+ this->next = info->nodes;
+ info->nodes = this;
+ return this;
+ }
+ prev = info->nodes;
+ walk = info->nodes->next;
+ while (walk) {
+ if (STR_COMPARE(this->name, walk->name) < 0)
+ break;
+ prev = walk;
+ walk = walk->next;
+ }
+ prev->next = this;
+ this->next = walk;
+#else
+ if (!info->nodes)
+ info->nodes = this;
+ else {
+ walk = info->nodes;
+ do {
+ prev = walk;
+ walk = walk->next;
+ } while (walk);
+ prev->next = this;
+ }
+#endif
+ return this;
+} // end: node_add
+
+
+static void node_classify (
+ struct dev_node *this)
+{
+ DIR *dirp;
+ struct dirent *dent;
+
+ /* all disks start off as partitions. this function
+ checks /sys/block and changes a device found there
+ into a disk. if /sys/block cannot have the directory
+ read, all devices are then treated as disks. */
+ this->type = DISKSTATS_TYPE_PARTITION;
+
+ if (!(dirp = opendir(SYSBLOCK_DIR))) {
+ this->type = DISKSTATS_TYPE_DISK;
+ return;
+ }
+ while ((dent = readdir(dirp))) {
+ if (strcmp(this->name, dent->d_name) == 0) {
+ this->type = DISKSTATS_TYPE_DISK;
+ break;
+ }
+ }
+ closedir(dirp);
+} // end: node_classify
+
+
+static struct dev_node *node_cut (
+ struct diskstats_info *info,
+ struct dev_node *this)
+{
+ struct dev_node *node = info->nodes;
+
+ if (this) {
+ if (this == node) {
+ info->nodes = node->next;
+ return this;
+ }
+ do {
+ if (this == node->next) {
+ node->next = node->next->next;
+ return this;
+ }
+ node = node->next;
+ } while (node);
+ }
+ return NULL;
+} // end: node_cut
+
+
+static struct dev_node *node_get (
+ struct diskstats_info *info,
+ const char *name)
+{
+ struct dev_node *node = info->nodes;
+
+ while (node) {
+ if (strcmp(name, node->name) == 0)
+ break;
+ node = node->next;
+ }
+ if (node) {
+ /* if this disk or partition has somehow gotten stale, we'll lose
+ it and then pretend it was never actually found ...
+ [ we test against both stamps in case a 'read' was avoided ] */
+ if (node->stamped != info->old_stamp
+ && (node->stamped != info->new_stamp)) {
+ free(node_cut(info, node));
+ node = NULL;
+ }
+ }
+ return node;
+} // end: node_get
+
+
+static int node_update (
+ struct diskstats_info *info,
+ struct dev_node *source)
+{
+ struct dev_node *target = node_get(info, source->name);
+
+ if (!target) {
+ if (!(target = malloc(sizeof(struct dev_node))))
+ return 0;
+ memcpy(target, source, sizeof(struct dev_node));
+ // let's not distort the deltas when a new node is created ...
+ memcpy(&target->old, &target->new, sizeof(struct dev_data));
+ node_classify(target);
+ node_add(info, target);
+ return 1;
+ }
+ // remember history from last time around ...
+ memcpy(&source->old, &target->new, sizeof(struct dev_data));
+ // preserve some stuff from the existing node struct ...
+ source->type = target->type;
+ source->next = target->next;
+ // finally 'update' the existing node struct ...
+ memcpy(target, source, sizeof(struct dev_node));
+ return 1;
+} // end: node_update
+
+
+// ___ Private Functions ||||||||||||||||||||||||||||||||||||||||||||||||||||||
+// --- generalized support ----------------------------------------------------
+
+static inline void diskstats_assign_results (
+ struct diskstats_stack *stack,
+ struct dev_node *node)
+{
+ struct diskstats_result *this = stack->head;
+
+ for (;;) {
+ enum diskstats_item item = this->item;
+ if (item >= DISKSTATS_logical_end)
+ break;
+ Item_table[item].setsfunc(this, node);
+ ++this;
+ }
+ return;
+} // end: diskstats_assign_results
+
+
+static void diskstats_extents_free_all (
+ struct ext_support *this)
+{
+ while (this->extents) {
+ struct stacks_extent *p = this->extents;
+ this->extents = this->extents->next;
+ free(p);
+ };
+} // end: diskstats_extents_free_all
+
+
+static inline struct diskstats_result *diskstats_itemize_stack (
+ struct diskstats_result *p,
+ int depth,
+ enum diskstats_item *items)
+{
+ struct diskstats_result *p_sav = p;
+ int i;
+
+ for (i = 0; i < depth; i++) {
+ p->item = items[i];
+ ++p;
+ }
+ return p_sav;
+} // end: diskstats_itemize_stack
+
+
+static inline int diskstats_items_check_failed (
+ enum diskstats_item *items,
+ int numitems)
+{
+ int i;
+
+ /* if an enum is passed instead of an address of one or more enums, ol' gcc
+ * will silently convert it to an address (possibly NULL). only clang will
+ * offer any sort of warning like the following:
+ *
+ * warning: incompatible integer to pointer conversion passing 'int' to parameter of type 'enum diskstats_item *'
+ * my_stack = procps_diskstats_select(info, DISKSTATS_noop, num);
+ * ^~~~~~~~~~~~~~~~
+ */
+ if (numitems < 1
+ || (void *)items < (void *)(unsigned long)(2 * DISKSTATS_logical_end))
+ return 1;
+
+ for (i = 0; i < numitems; i++) {
+ // a diskstats_item is currently unsigned, but we'll protect our future
+ if (items[i] < 0)
+ return 1;
+ if (items[i] >= DISKSTATS_logical_end)
+ return 1;
+ }
+
+ return 0;
+} // end: diskstats_items_check_failed
+
+
+/*
+ * diskstats_read_failed:
+ *
+ * @info: info structure created at procps_diskstats_new
+ *
+ * Read the data out of /proc/diskstats putting the information
+ * into the supplied info structure
+ *
+ * Returns: 0 on success, 1 on error
+ */
+static int diskstats_read_failed (
+ struct diskstats_info *info)
+{
+ static const char *fmtstr = "%d %d %" STRINGIFY(DISKSTATS_NAME_LEN) \
+ "s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu";
+ char buf[DISKSTATS_LINE_LEN];
+ struct dev_node node;
+ int rc;
+
+ if (!info->diskstats_fp
+ && (!(info->diskstats_fp = fopen(DISKSTATS_FILE, "r"))))
+ return 1;
+
+ if (fseek(info->diskstats_fp, 0L, SEEK_SET) == -1)
+ return 1;
+
+ info->old_stamp = info->new_stamp;
+ info->new_stamp = time(NULL);
+
+ while (fgets(buf, DISKSTATS_LINE_LEN, info->diskstats_fp)) {
+ // clear out the soon to be 'current'values
+ memset(&node, 0, sizeof(struct dev_node));
+
+ rc = sscanf(buf, fmtstr
+ , &node.major
+ , &node.minor
+ , &node.name[0]
+ , &node.new.reads
+ , &node.new.reads_merged
+ , &node.new.read_sectors
+ , &node.new.read_time
+ , &node.new.writes
+ , &node.new.writes_merged
+ , &node.new.write_sectors
+ , &node.new.write_time
+ , &node.new.io_inprogress
+ , &node.new.io_time
+ , &node.new.io_wtime);
+
+ if (rc != 14) {
+ errno = ERANGE;
+ return 1;
+ }
+ node.stamped = info->new_stamp;
+ if (!node_update(info, &node))
+ return 1; // here, errno was set to ENOMEM
+ }
+
+ return 0;
+} // end: diskstats_read_failed
+
+
+/*
+ * diskstats_stacks_alloc():
+ *
+ * Allocate and initialize one or more stacks each of which is anchored in an
+ * associated context structure.
+ *
+ * All such stacks will have their result structures properly primed with
+ * 'items', while the result itself will be zeroed.
+ *
+ * Returns a stacks_extent struct anchoring the 'heads' of each new stack.
+ */
+static struct stacks_extent *diskstats_stacks_alloc (
+ struct ext_support *this,
+ int maxstacks)
+{
+ struct stacks_extent *p_blob;
+ struct diskstats_stack **p_vect;
+ struct diskstats_stack *p_head;
+ size_t vect_size, head_size, list_size, blob_size;
+ void *v_head, *v_list;
+ int i;
+
+ vect_size = sizeof(void *) * maxstacks; // size of the addr vectors |
+ vect_size += sizeof(void *); // plus NULL addr delimiter |
+ head_size = sizeof(struct diskstats_stack); // size of that head struct |
+ list_size = sizeof(struct diskstats_result) * this->numitems; // any single results stack |
+ blob_size = sizeof(struct stacks_extent); // the extent anchor itself |
+ blob_size += vect_size; // plus room for addr vects |
+ blob_size += head_size * maxstacks; // plus room for head thing |
+ blob_size += list_size * maxstacks; // plus room for our stacks |
+
+ /* note: all of our memory is allocated in one single blob, facilitating some later free(). |
+ as a minimum, it's important that all of those result structs themselves always be |
+ contiguous within every stack since they will be accessed via a relative position. | */
+ if (NULL == (p_blob = calloc(1, blob_size)))
+ return NULL;
+
+ p_blob->next = this->extents; // push this extent onto... |
+ this->extents = p_blob; // ...some existing extents |
+ p_vect = (void *)p_blob + sizeof(struct stacks_extent); // prime our vector pointer |
+ p_blob->stacks = p_vect; // set actual vectors start |
+ v_head = (void *)p_vect + vect_size; // prime head pointer start |
+ v_list = v_head + (head_size * maxstacks); // prime our stacks pointer |
+
+ for (i = 0; i < maxstacks; i++) {
+ p_head = (struct diskstats_stack *)v_head;
+ p_head->head = diskstats_itemize_stack((struct diskstats_result *)v_list, this->numitems, this->items);
+ p_blob->stacks[i] = p_head;
+ v_list += list_size;
+ v_head += head_size;
+ }
+ p_blob->ext_numstacks = maxstacks;
+ return p_blob;
+} // end: diskstats_stacks_alloc
+
+
+static int diskstats_stacks_fetch (
+ struct diskstats_info *info)
+{
+ #define n_alloc info->fetch.n_alloc
+ #define n_inuse info->fetch.n_inuse
+ #define n_saved info->fetch.n_alloc_save
+ struct stacks_extent *ext;
+ struct dev_node *node;
+
+ // initialize stuff -----------------------------------
+ if (!info->fetch.anchor) {
+ if (!(info->fetch.anchor = calloc(sizeof(void *), STACKS_INCR)))
+ return -ENOMEM;
+ n_alloc = STACKS_INCR;
+ }
+ if (!info->fetch_ext.extents) {
+ if (!(ext = diskstats_stacks_alloc(&info->fetch_ext, n_alloc)))
+ return -1; // here, errno was set to ENOMEM
+ memcpy(info->fetch.anchor, ext->stacks, sizeof(void *) * n_alloc);
+ }
+
+ // iterate stuff --------------------------------------
+ n_inuse = 0;
+ node = info->nodes;
+ while (node) {
+ if (!(n_inuse < n_alloc)) {
+ n_alloc += STACKS_INCR;
+ if ((!(info->fetch.anchor = realloc(info->fetch.anchor, sizeof(void *) * n_alloc)))
+ || (!(ext = diskstats_stacks_alloc(&info->fetch_ext, STACKS_INCR))))
+ return -1; // here, errno was set to ENOMEM
+ memcpy(info->fetch.anchor + n_inuse, ext->stacks, sizeof(void *) * STACKS_INCR);
+ }
+ diskstats_assign_results(info->fetch.anchor[n_inuse], node);
+ ++n_inuse;
+ node = node->next;
+ }
+
+ // finalize stuff -------------------------------------
+ /* note: we go to this trouble of maintaining a duplicate of the consolidated |
+ extent stacks addresses represented as our 'anchor' since these ptrs |
+ are exposed to a user (um, not that we don't trust 'em or anything). |
+ plus, we can NULL delimit these ptrs which we couldn't do otherwise. | */
+ if (n_saved < n_inuse + 1) {
+ n_saved = n_inuse + 1;
+ if (!(info->fetch.results.stacks = realloc(info->fetch.results.stacks, sizeof(void *) * n_saved)))
+ return -1;
+ }
+ memcpy(info->fetch.results.stacks, info->fetch.anchor, sizeof(void *) * n_inuse);
+ info->fetch.results.stacks[n_inuse] = NULL;
+ info->fetch.results.total = n_inuse;
+
+ return n_inuse;
+ #undef n_alloc
+ #undef n_inuse
+ #undef n_saved
+} // end: diskstats_stacks_fetch
+
+
+static int diskstats_stacks_reconfig_maybe (
+ struct ext_support *this,
+ enum diskstats_item *items,
+ int numitems)
+{
+ if (diskstats_items_check_failed(items, numitems))
+ return -1;
+ /* is this the first time or have things changed since we were last called?
+ if so, gotta' redo all of our stacks stuff ... */
+ if (this->numitems != numitems + 1
+ || memcmp(this->items, items, sizeof(enum diskstats_item) * numitems)) {
+ // allow for our DISKSTATS_logical_end
+ if (!(this->items = realloc(this->items, sizeof(enum diskstats_item) * (numitems + 1))))
+ return -1; // here, errno was set to ENOMEM
+ memcpy(this->items, items, sizeof(enum diskstats_item) * numitems);
+ this->items[numitems] = DISKSTATS_logical_end;
+ this->numitems = numitems + 1;
+ diskstats_extents_free_all(this);
+ return 1;
+ }
+ return 0;
+} // end: diskstats_stacks_reconfig_maybe
+
+
+// ___ Public Functions |||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
+// --- standard required functions --------------------------------------------
+
+/*
+ * procps_diskstats_new():
+ *
+ * @info: location of returned new structure
+ *
+ * Returns: < 0 on failure, 0 on success along with
+ * a pointer to a new context struct
+ */
+PROCPS_EXPORT int procps_diskstats_new (
+ struct diskstats_info **info)
+{
+ struct diskstats_info *p;
+
+#ifdef ITEMTABLE_DEBUG
+ int i, failed = 0;
+ for (i = 0; i < MAXTABLE(Item_table); i++) {
+ if (i != Item_table[i].enumnumb) {
+ fprintf(stderr, "%s: enum/table error: Item_table[%d] was %s, but its value is %d\n"
+ , __FILE__, i, Item_table[i].enum2str, Item_table[i].enumnumb);
+ failed = 1;
+ }
+ }
+ if (failed) _Exit(EXIT_FAILURE);
+#endif
+
+ if (info == NULL || *info != NULL)
+ return -EINVAL;
+ if (!(p = calloc(1, sizeof(struct diskstats_info))))
+ return -ENOMEM;
+
+ p->refcount = 1;
+
+ /* do a priming read here for the following potential benefits: |
+ 1) ensure there will be no problems with subsequent access |
+ 2) make delta results potentially useful, even if 1st time |
+ 3) elimnate need for history distortions 1st time 'switch' | */
+ if (diskstats_read_failed(p)) {
+ procps_diskstats_unref(&p);
+ return -errno;
+ }
+
+ *info = p;
+ return 0;
+} // end: procps_diskstats_new
+
+
+PROCPS_EXPORT int procps_diskstats_ref (
+ struct diskstats_info *info)
+{
+ if (info == NULL)
+ return -EINVAL;
+
+ info->refcount++;
+ return info->refcount;
+} // end: procps_diskstats_ref
+
+
+PROCPS_EXPORT int procps_diskstats_unref (
+ struct diskstats_info **info)
+{
+ struct dev_node *node;
+
+ if (info == NULL || *info == NULL)
+ return -EINVAL;
+
+ (*info)->refcount--;
+
+ if ((*info)->refcount < 1) {
+ int errno_sav = errno;
+
+ if ((*info)->diskstats_fp) {
+ fclose((*info)->diskstats_fp);
+ (*info)->diskstats_fp = NULL;
+ }
+ node = (*info)->nodes;
+ while (node) {
+ struct dev_node *p = node;
+ node = p->next;
+ free(p);
+ }
+ if ((*info)->select_ext.extents)
+ diskstats_extents_free_all((&(*info)->select_ext));
+ if ((*info)->select_ext.items)
+ free((*info)->select_ext.items);
+
+ if ((*info)->fetch.anchor)
+ free((*info)->fetch.anchor);
+ if ((*info)->fetch.results.stacks)
+ free((*info)->fetch.results.stacks);
+
+ if ((*info)->fetch_ext.extents)
+ diskstats_extents_free_all(&(*info)->fetch_ext);
+ if ((*info)->fetch_ext.items)
+ free((*info)->fetch_ext.items);
+
+ free(*info);
+ *info = NULL;
+
+ errno = errno_sav;
+ return 0;
+ }
+ return (*info)->refcount;
+} // end: procps_diskstats_unref
+
+
+// --- variable interface functions -------------------------------------------
+
+PROCPS_EXPORT struct diskstats_result *procps_diskstats_get (
+ struct diskstats_info *info,
+ const char *name,
+ enum diskstats_item item)
+{
+ struct dev_node *node;
+ time_t cur_secs;
+
+ errno = EINVAL;
+ if (info == NULL)
+ return NULL;
+ if (item < 0 || item >= DISKSTATS_logical_end)
+ return NULL;
+ errno = 0;
+
+ /* we will NOT read the diskstat file with every call - rather, we'll offer
+ a granularity of 1 second between reads ... */
+ cur_secs = time(NULL);
+ if (1 <= cur_secs - info->new_stamp) {
+ if (diskstats_read_failed(info))
+ return NULL;
+ }
+
+ info->get_this.item = item;
+ // with 'get', we must NOT honor the usual 'noop' guarantee
+ info->get_this.result.ul_int = 0;
+
+ if (!(node = node_get(info, name))) {
+ errno = ENXIO;
+ return NULL;
+ }
+ Item_table[item].setsfunc(&info->get_this, node);
+
+ return &info->get_this;
+} // end: procps_diskstats_get
+
+
+/* procps_diskstats_reap():
+ *
+ * Harvest all the requested disks information providing
+ * the result stacks along with the total number of harvested.
+ *
+ * Returns: pointer to a diskstats_reaped struct on success, NULL on error.
+ */
+PROCPS_EXPORT struct diskstats_reaped *procps_diskstats_reap (
+ struct diskstats_info *info,
+ enum diskstats_item *items,
+ int numitems)
+{
+ errno = EINVAL;
+ if (info == NULL || items == NULL)
+ return NULL;
+ if (0 > diskstats_stacks_reconfig_maybe(&info->fetch_ext, items, numitems))
+ return NULL; // here, errno may be overridden with ENOMEM
+ errno = 0;
+
+ if (diskstats_read_failed(info))
+ return NULL;
+ if (0 > diskstats_stacks_fetch(info))
+ return NULL;
+
+ return &info->fetch.results;
+} // end: procps_diskstats_reap
+
+
+/* procps_diskstats_select():
+ *
+ * Obtain all the requested disk/partition information then return
+ * it in a single library provided results stack.
+ *
+ * Returns: pointer to a diskstats_stack struct on success, NULL on error.
+ */
+PROCPS_EXPORT struct diskstats_stack *procps_diskstats_select (
+ struct diskstats_info *info,
+ const char *name,
+ enum diskstats_item *items,
+ int numitems)
+{
+ struct dev_node *node;
+
+ errno = EINVAL;
+ if (info == NULL || items == NULL)
+ return NULL;
+ if (0 > diskstats_stacks_reconfig_maybe(&info->select_ext, items, numitems))
+ return NULL; // here, errno may be overridden with ENOMEM
+ errno = 0;
+
+ if (!info->select_ext.extents
+ && (!diskstats_stacks_alloc(&info->select_ext, 1)))
+ return NULL;
+
+ if (diskstats_read_failed(info))
+ return NULL;
+ if (!(node = node_get(info, name))) {
+ errno = ENXIO;
+ return NULL;
+ }
+
+ diskstats_assign_results(info->select_ext.extents->stacks[0], node);
+
+ return info->select_ext.extents->stacks[0];
+} // end: procps_diskstats_select
+
+
+/*
+ * procps_diskstats_sort():
+ *
+ * Sort stacks anchored in the passed stack pointers array
+ * based on the designated sort enumerator and specified order.
+ *
+ * Returns those same addresses sorted.
+ *
+ * Note: all of the stacks must be homogeneous (of equal length and content).
+ */
+PROCPS_EXPORT struct diskstats_stack **procps_diskstats_sort (
+ struct diskstats_info *info,
+ struct diskstats_stack *stacks[],
+ int numstacked,
+ enum diskstats_item sortitem,
+ enum diskstats_sort_order order)
+{
+ struct diskstats_result *p;
+ struct sort_parms parms;
+ int offset;
+
+ errno = EINVAL;
+ if (info == NULL || stacks == NULL)
+ return NULL;
+ // a diskstats_item is currently unsigned, but we'll protect our future
+ if (sortitem < 0 || sortitem >= DISKSTATS_logical_end)
+ return NULL;
+ if (order != DISKSTATS_SORT_ASCEND && order != DISKSTATS_SORT_DESCEND)
+ return NULL;
+ if (numstacked < 2)
+ return stacks;
+
+ offset = 0;
+ p = stacks[0]->head;
+ for (;;) {
+ if (p->item == sortitem)
+ break;
+ ++offset;
+ if (p->item >= DISKSTATS_logical_end)
+ return NULL;
+ ++p;
+ }
+ errno = 0;
+
+ parms.offset = offset;
+ parms.order = order;
+
+ qsort_r(stacks, numstacked, sizeof(void *), (QSR_t)Item_table[p->item].sortfunc, &parms);
+ return stacks;
+} // end: procps_diskstats_sort
+
+
+// --- special debugging function(s) ------------------------------------------
+/*
+ * The following isn't part of the normal programming interface. Rather,
+ * it exists to validate result types referenced in application programs.
+ *
+ * It's used only when:
+ * 1) the 'XTRA_PROCPS_DEBUG' has been defined, or
+ * 2) an #include of 'xtra-procps-debug.h' is used
+ */
+
+PROCPS_EXPORT struct diskstats_result *xtra_diskstats_get (
+ struct diskstats_info *info,
+ const char *name,
+ enum diskstats_item actual_enum,
+ const char *typestr,
+ const char *file,
+ int lineno)
+{
+ struct diskstats_result *r = procps_diskstats_get(info, name, actual_enum);
+
+ if (actual_enum < 0 || actual_enum >= DISKSTATS_logical_end) {
+ fprintf(stderr, "%s line %d: invalid item = %d, type = %s\n"
+ , file, lineno, actual_enum, typestr);
+ }
+ if (r) {
+ char *str = Item_table[r->item].type2str;
+ if (str[0]
+ && (strcmp(typestr, str)))
+ fprintf(stderr, "%s line %d: was %s, expected %s\n", file, lineno, typestr, str);
+ }
+ return r;
+} // end: xtra_diskstats_get_
+
+
+PROCPS_EXPORT struct diskstats_result *xtra_diskstats_val (
+ int relative_enum,
+ const char *typestr,
+ const struct diskstats_stack *stack,
+ struct diskstats_info *info,
+ const char *file,
+ int lineno)
+{
+ char *str;
+ int i;
+
+ for (i = 0; stack->head[i].item < DISKSTATS_logical_end; i++)
+ ;
+ if (relative_enum < 0 || relative_enum >= i) {
+ fprintf(stderr, "%s line %d: invalid relative_enum = %d, valid range = 0-%d\n"
+ , file, lineno, relative_enum, i-1);
+ return NULL;
+ }
+ str = Item_table[stack->head[relative_enum].item].type2str;
+ if (str[0]
+ && (strcmp(typestr, str))) {
+ fprintf(stderr, "%s line %d: was %s, expected %s\n", file, lineno, typestr, str);
+ }
+ return &stack->head[relative_enum];
+ (void)info;
+} // end: xtra_diskstats_val
diff --git a/library/escape.c b/library/escape.c
new file mode 100644
index 0000000..f988672
--- /dev/null
+++ b/library/escape.c
@@ -0,0 +1,157 @@
+/*
+ * escape.c - printing handling
+ *
+ * Copyright © 2011-2023 Jim Warner <james.warner@comcast.net>
+ * Copyright © 2016-2023 Craig Small <csmall@dropbear.xyz>
+ * Copyright © 1998-2005 Albert Cahalan
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "escape.h"
+#include "readproc.h"
+#include "nls.h"
+
+#define SECURE_ESCAPE_ARGS(dst, bytes) do { \
+ if ((bytes) <= 0) return 0; \
+ *(dst) = '\0'; \
+ if ((bytes) >= INT_MAX) return 0; \
+} while (0)
+
+static const char UTF_tab[] = {
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x00 - 0x0F
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x10 - 0x1F
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20 - 0x2F
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x30 - 0x3F
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 - 0x4F
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x50 - 0x5F
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6F
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x70 - 0x7F
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0x80 - 0x8F
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0x90 - 0x9F
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0xA0 - 0xAF
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0xB0 - 0xBF
+ -1,-1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xC0 - 0xCF
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xD0 - 0xDF
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0xE0 - 0xEF
+ 4, 4, 4, 4, 4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0xF0 - 0xFF
+};
+
+static const unsigned char ESC_tab[] = {
+ "@..............................." // 0x00 - 0x1F
+ "||||||||||||||||||||||||||||||||" // 0x20 - 0x3F
+ "||||||||||||||||||||||||||||||||" // 0x40 - 0x5f
+ "|||||||||||||||||||||||||||||||." // 0x60 - 0x7F
+ "????????????????????????????????" // 0x80 - 0x9F
+ "????????????????????????????????" // 0xA0 - 0xBF
+ "????????????????????????????????" // 0xC0 - 0xDF
+ "????????????????????????????????" // 0xE0 - 0xFF
+};
+
+static inline void esc_all (unsigned char *str) {
+ unsigned char c;
+
+ // if bad locale/corrupt str, replace non-printing stuff
+ while (*str) {
+ if ((c = ESC_tab[*str]) != '|')
+ *str = c;
+ ++str;
+ }
+}
+
+static inline void esc_ctl (unsigned char *str, int len) {
+ #define setQ { *str = '?'; n = 1; goto next_up; }
+ int i, n, x;
+
+ for (i = 0; i < len; ) {
+ n = UTF_tab[*str];
+ /* even with a proper locale, strings might be corrupt or we
+ might encounter one of those 32 unicode multibyte control
+ characters which begin at U+0080 (0xc280) */
+ if (n < 0 || i + n > len
+ || (*str == 0xc2 && str[1] >= 0x80 && str[1] <= 0x9f)) {
+ setQ
+ }
+ /* let's validate those utf-8 continuation bytes too, all of
+ which must take the binary form of 10xxxxxx */
+ for (x = 1; x < n; x++) {
+ if (str[x] < 0x80 || str[x] > 0xbf) {
+ setQ
+ }
+ }
+ // and eliminate those non-printing control characters
+ if (*str < 0x20 || *str == 0x7f)
+ *str = '?';
+next_up:
+ str += n;
+ i += n;
+ }
+ #undef setQ
+}
+
+int escape_str (char *dst, const char *src, int bufsize) {
+ static __thread int utf_sw = 0;
+ int n;
+
+ if (utf_sw == 0) {
+ char *enc = nl_langinfo(CODESET);
+ utf_sw = enc && strcasecmp(enc, "UTF-8") == 0 ? 1 : -1;
+ }
+ SECURE_ESCAPE_ARGS(dst, bufsize);
+ n = snprintf(dst, bufsize, "%s", src);
+ if (n < 0) {
+ *dst = '\0';
+ return 0;
+ }
+ if (n >= bufsize) n = bufsize-1;
+ if (utf_sw < 0)
+ esc_all((unsigned char *)dst);
+ else
+ esc_ctl((unsigned char *)dst, n);
+ return n;
+}
+
+int escape_command (char *outbuf, const proc_t *pp, int bytes, unsigned flags) {
+ int overhead = 0;
+ int end = 0;
+
+ if (flags & ESC_BRACKETS)
+ overhead += 2;
+ if (flags & ESC_DEFUNCT) {
+ if (pp->state == 'Z') overhead += 10; // chars in " <defunct>"
+ else flags &= ~ESC_DEFUNCT;
+ }
+ if (overhead + 1 >= bytes) {
+ // if no room for even one byte of the command name
+ outbuf[0] = '\0';
+ return 0;
+ }
+ if (flags & ESC_BRACKETS)
+ outbuf[end++] = '[';
+ end += escape_str(outbuf+end, pp->cmd, bytes-overhead);
+ // we want "[foo] <defunct>", not "[foo <defunct>]"
+ if (flags & ESC_BRACKETS)
+ outbuf[end++] = ']';
+ if (flags & ESC_DEFUNCT) {
+ memcpy(outbuf+end, " <defunct>", 10);
+ end += 10;
+ }
+ outbuf[end] = '\0';
+ return end; // bytes, not including the NUL
+}
diff --git a/library/include/devname.h b/library/include/devname.h
new file mode 100644
index 0000000..467cd29
--- /dev/null
+++ b/library/include/devname.h
@@ -0,0 +1,10 @@
+#ifndef PROC_DEVNAME_H
+#define PROC_DEVNAME_H
+
+#define ABBREV_DEV 1 /* remove /dev/ */
+#define ABBREV_TTY 2 /* remove tty */
+#define ABBREV_PTS 4 /* remove pts/ */
+
+unsigned dev_to_tty(char *__restrict ret, unsigned chop, dev_t dev_t_dev, int pid, unsigned int flags);
+
+#endif
diff --git a/library/include/diskstats.h b/library/include/diskstats.h
new file mode 100644
index 0000000..d5a4b1f
--- /dev/null
+++ b/library/include/diskstats.h
@@ -0,0 +1,135 @@
+/*
+ * diskstats.h - disk I/O related declarations for libproc2
+ *
+ * Copyright © 2015-2023 Jim Warner <james.warner@comcast.net>
+ * Copyright © 2015-2023 Craig Small <csmall@dropbear.xyz>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#ifndef PROCPS_DISKSTATS_H
+#define PROCPS_DISKSTATS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum diskstats_item {
+ DISKSTATS_noop, // ( never altered )
+ DISKSTATS_extra, // ( reset to zero )
+ // returns origin, see proc(5)
+ // ------- -------------------
+ DISKSTATS_NAME, // str /proc/diskstats
+ DISKSTATS_TYPE, // s_int "
+ DISKSTATS_MAJOR, // s_int "
+ DISKSTATS_MINOR, // s_int "
+
+ DISKSTATS_READS, // ul_int "
+ DISKSTATS_READS_MERGED, // ul_int "
+ DISKSTATS_READ_SECTORS, // ul_int "
+ DISKSTATS_READ_TIME, // ul_int "
+ DISKSTATS_WRITES, // ul_int "
+ DISKSTATS_WRITES_MERGED, // ul_int "
+ DISKSTATS_WRITE_SECTORS, // ul_int "
+ DISKSTATS_WRITE_TIME, // ul_int "
+ DISKSTATS_IO_TIME, // ul_int "
+ DISKSTATS_WEIGHTED_TIME, // ul_int "
+
+ DISKSTATS_IO_INPROGRESS, // s_int "
+
+ DISKSTATS_DELTA_READS, // s_int derived from above
+ DISKSTATS_DELTA_READS_MERGED, // s_int "
+ DISKSTATS_DELTA_READ_SECTORS, // s_int "
+ DISKSTATS_DELTA_READ_TIME, // s_int "
+ DISKSTATS_DELTA_WRITES, // s_int "
+ DISKSTATS_DELTA_WRITES_MERGED, // s_int "
+ DISKSTATS_DELTA_WRITE_SECTORS, // s_int "
+ DISKSTATS_DELTA_WRITE_TIME, // s_int "
+ DISKSTATS_DELTA_IO_TIME, // s_int "
+ DISKSTATS_DELTA_WEIGHTED_TIME // s_int "
+};
+
+enum diskstats_sort_order {
+ DISKSTATS_SORT_ASCEND = +1,
+ DISKSTATS_SORT_DESCEND = -1
+};
+
+
+struct diskstats_result {
+ enum diskstats_item item;
+ union {
+ signed int s_int;
+ unsigned long ul_int;
+ char *str;
+ } result;
+};
+
+struct diskstats_stack {
+ struct diskstats_result *head;
+};
+
+struct diskstats_reaped {
+ int total;
+ struct diskstats_stack **stacks;
+};
+
+struct diskstats_info;
+
+
+#define DISKSTATS_TYPE_DISK -11111
+#define DISKSTATS_TYPE_PARTITION -22222
+
+#define DISKSTATS_GET( info, name, actual_enum, type ) ( { \
+ struct diskstats_result *r = procps_diskstats_get( info, name, actual_enum ); \
+ r ? r->result . type : 0; } )
+
+#define DISKSTATS_VAL( relative_enum, type, stack, info ) \
+ stack -> head [ relative_enum ] . result . type
+
+
+int procps_diskstats_new (struct diskstats_info **info);
+int procps_diskstats_ref (struct diskstats_info *info);
+int procps_diskstats_unref (struct diskstats_info **info);
+
+struct diskstats_result *procps_diskstats_get (
+ struct diskstats_info *info,
+ const char *name,
+ enum diskstats_item item);
+
+struct diskstats_reaped *procps_diskstats_reap (
+ struct diskstats_info *info,
+ enum diskstats_item *items,
+ int numitems);
+
+struct diskstats_stack *procps_diskstats_select (
+ struct diskstats_info *info,
+ const char *name,
+ enum diskstats_item *items,
+ int numitems);
+
+struct diskstats_stack **procps_diskstats_sort (
+ struct diskstats_info *info,
+ struct diskstats_stack *stacks[],
+ int numstacked,
+ enum diskstats_item sortitem,
+ enum diskstats_sort_order order);
+
+
+#ifdef XTRA_PROCPS_DEBUG
+# include "xtra-procps-debug.h"
+#endif
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/library/include/escape.h b/library/include/escape.h
new file mode 100644
index 0000000..1f0b0f9
--- /dev/null
+++ b/library/include/escape.h
@@ -0,0 +1,35 @@
+/*
+ * escape.h - printing handling
+ *
+ * Copyright © 2011-2023 Jim Warner <james.warner@comcast.net>
+ * Copyright © 2016-2023 Craig Small <csmall@dropbear.xyz>
+ * Copyright © 1998-2005 Albert Cahalan
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PROCPS_PROC_ESCAPE_H
+#define PROCPS_PROC_ESCAPE_H
+
+#include "readproc.h"
+
+#define ESC_BRACKETS 0x2 // if using cmd, put '[' and ']' around it
+#define ESC_DEFUNCT 0x4 // mark zombies with " <defunct>"
+
+int escape_command (char *outbuf, const proc_t *pp, int bytes, unsigned flags);
+
+int escape_str (char *dst, const char *src, int bufsize);
+
+#endif
diff --git a/library/include/meminfo.h b/library/include/meminfo.h
new file mode 100644
index 0000000..edca8d2
--- /dev/null
+++ b/library/include/meminfo.h
@@ -0,0 +1,216 @@
+/*
+ * meminfo.h - memory related declarations for libproc2
+ *
+ * Copyright © 2015-2023 Jim Warner <james.warner@comcast.net>
+ * Copyright © 2015-2023 Craig Small <csmall@dropbear.xyz>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PROCPS_MEMINFO_H
+#define PROCPS_MEMINFO_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum meminfo_item {
+ MEMINFO_noop, // ( never altered )
+ MEMINFO_extra, // ( reset to zero )
+ /*
+ note: all of the following values are expressed as KiB
+ */
+ // returns origin, see proc(5)
+ // ------- -------------------
+ MEMINFO_MEM_ACTIVE, // ul_int /proc/meminfo
+ MEMINFO_MEM_ACTIVE_ANON, // ul_int "
+ MEMINFO_MEM_ACTIVE_FILE, // ul_int "
+ MEMINFO_MEM_ANON, // ul_int "
+ MEMINFO_MEM_AVAILABLE, // ul_int "
+ MEMINFO_MEM_BOUNCE, // ul_int "
+ MEMINFO_MEM_BUFFERS, // ul_int "
+ MEMINFO_MEM_CACHED, // ul_int "
+ MEMINFO_MEM_CACHED_ALL, // ul_int derived from MEM_CACHED + MEM_SLAB_RECLAIM
+ MEMINFO_MEM_CMA_FREE, // ul_int /proc/meminfo
+ MEMINFO_MEM_CMA_TOTAL, // ul_int "
+ MEMINFO_MEM_COMMITTED_AS, // ul_int "
+ MEMINFO_MEM_COMMIT_LIMIT, // ul_int "
+ MEMINFO_MEM_DIRECTMAP_1G, // ul_int "
+ MEMINFO_MEM_DIRECTMAP_2M, // ul_int "
+ MEMINFO_MEM_DIRECTMAP_4K, // ul_int "
+ MEMINFO_MEM_DIRECTMAP_4M, // ul_int "
+ MEMINFO_MEM_DIRTY, // ul_int "
+ MEMINFO_MEM_FILE_HUGEPAGES, // ul_int "
+ MEMINFO_MEM_FILE_PMDMAPPED, // ul_int "
+ MEMINFO_MEM_FREE, // ul_int "
+ MEMINFO_MEM_HARD_CORRUPTED, // ul_int "
+ MEMINFO_MEM_HIGH_FREE, // ul_int "
+ MEMINFO_MEM_HIGH_TOTAL, // ul_int "
+ MEMINFO_MEM_HIGH_USED, // ul_int derived from MEM_HIGH_TOTAL - MEM_HIGH_FREE
+ MEMINFO_MEM_HUGETBL, // ul_int /proc/meminfo
+ MEMINFO_MEM_HUGE_ANON, // ul_int "
+ MEMINFO_MEM_HUGE_FREE, // ul_int "
+ MEMINFO_MEM_HUGE_RSVD, // ul_int "
+ MEMINFO_MEM_HUGE_SIZE, // ul_int "
+ MEMINFO_MEM_HUGE_SURPLUS, // ul_int "
+ MEMINFO_MEM_HUGE_TOTAL, // ul_int "
+ MEMINFO_MEM_INACTIVE, // ul_int "
+ MEMINFO_MEM_INACTIVE_ANON, // ul_int "
+ MEMINFO_MEM_INACTIVE_FILE, // ul_int "
+ MEMINFO_MEM_KERNEL_RECLAIM, // ul_int "
+ MEMINFO_MEM_KERNEL_STACK, // ul_int "
+ MEMINFO_MEM_LOCKED, // ul_int "
+ MEMINFO_MEM_LOW_FREE, // ul_int "
+ MEMINFO_MEM_LOW_TOTAL, // ul_int "
+ MEMINFO_MEM_LOW_USED, // ul_int derived from MEM_LOW_TOTAL - MEM_LOW_FREE
+ MEMINFO_MEM_MAPPED, // ul_int /proc/meminfo
+ MEMINFO_MEM_MAP_COPY, // ul_int "
+ MEMINFO_MEM_NFS_UNSTABLE, // ul_int "
+ MEMINFO_MEM_PAGE_TABLES, // ul_int "
+ MEMINFO_MEM_PER_CPU, // ul_int "
+ MEMINFO_MEM_SHADOWCALLSTACK, // ul_int "
+ MEMINFO_MEM_SHARED, // ul_int "
+ MEMINFO_MEM_SHMEM_HUGE, // ul_int "
+ MEMINFO_MEM_SHMEM_HUGE_MAP, // ul_int "
+ MEMINFO_MEM_SLAB, // ul_int "
+ MEMINFO_MEM_SLAB_RECLAIM, // ul_int "
+ MEMINFO_MEM_SLAB_UNRECLAIM, // ul_int "
+ MEMINFO_MEM_TOTAL, // ul_int "
+ MEMINFO_MEM_UNEVICTABLE, // ul_int "
+ MEMINFO_MEM_USED, // ul_int derived from MEM_TOTAL - MEM_AVAILABLE
+ MEMINFO_MEM_VM_ALLOC_CHUNK, // ul_int /proc/meminfo
+ MEMINFO_MEM_VM_ALLOC_TOTAL, // ul_int "
+ MEMINFO_MEM_VM_ALLOC_USED, // ul_int "
+ MEMINFO_MEM_WRITEBACK, // ul_int "
+ MEMINFO_MEM_WRITEBACK_TMP, // ul_int "
+
+ MEMINFO_DELTA_ACTIVE, // s_int derived from above
+ MEMINFO_DELTA_ACTIVE_ANON, // s_int "
+ MEMINFO_DELTA_ACTIVE_FILE, // s_int "
+ MEMINFO_DELTA_ANON, // s_int "
+ MEMINFO_DELTA_AVAILABLE, // s_int "
+ MEMINFO_DELTA_BOUNCE, // s_int "
+ MEMINFO_DELTA_BUFFERS, // s_int "
+ MEMINFO_DELTA_CACHED, // s_int "
+ MEMINFO_DELTA_CACHED_ALL, // s_int "
+ MEMINFO_DELTA_CMA_FREE, // s_int "
+ MEMINFO_DELTA_CMA_TOTAL, // s_int "
+ MEMINFO_DELTA_COMMITTED_AS, // s_int "
+ MEMINFO_DELTA_COMMIT_LIMIT, // s_int "
+ MEMINFO_DELTA_DIRECTMAP_1G, // s_int "
+ MEMINFO_DELTA_DIRECTMAP_2M, // s_int "
+ MEMINFO_DELTA_DIRECTMAP_4K, // s_int "
+ MEMINFO_DELTA_DIRECTMAP_4M, // s_int "
+ MEMINFO_DELTA_DIRTY, // s_int "
+ MEMINFO_DELTA_FILE_HUGEPAGES, // s_int "
+ MEMINFO_DELTA_FILE_PMDMAPPED, // s_int "
+ MEMINFO_DELTA_FREE, // s_int "
+ MEMINFO_DELTA_HARD_CORRUPTED, // s_int "
+ MEMINFO_DELTA_HIGH_FREE, // s_int "
+ MEMINFO_DELTA_HIGH_TOTAL, // s_int "
+ MEMINFO_DELTA_HIGH_USED, // s_int "
+ MEMINFO_DELTA_HUGETBL, // s_int "
+ MEMINFO_DELTA_HUGE_ANON, // s_int "
+ MEMINFO_DELTA_HUGE_FREE, // s_int "
+ MEMINFO_DELTA_HUGE_RSVD, // s_int "
+ MEMINFO_DELTA_HUGE_SIZE, // s_int "
+ MEMINFO_DELTA_HUGE_SURPLUS, // s_int "
+ MEMINFO_DELTA_HUGE_TOTAL, // s_int "
+ MEMINFO_DELTA_INACTIVE, // s_int "
+ MEMINFO_DELTA_INACTIVE_ANON, // s_int "
+ MEMINFO_DELTA_INACTIVE_FILE, // s_int "
+ MEMINFO_DELTA_KERNEL_RECLAIM, // s_int "
+ MEMINFO_DELTA_KERNEL_STACK, // s_int "
+ MEMINFO_DELTA_LOCKED, // s_int "
+ MEMINFO_DELTA_LOW_FREE, // s_int "
+ MEMINFO_DELTA_LOW_TOTAL, // s_int "
+ MEMINFO_DELTA_LOW_USED, // s_int "
+ MEMINFO_DELTA_MAPPED, // s_int "
+ MEMINFO_DELTA_MAP_COPY, // s_int "
+ MEMINFO_DELTA_NFS_UNSTABLE, // s_int "
+ MEMINFO_DELTA_PAGE_TABLES, // s_int "
+ MEMINFO_DELTA_PER_CPU, // s_int "
+ MEMINFO_DELTA_SHADOWCALLSTACK, // s_int "
+ MEMINFO_DELTA_SHARED, // s_int "
+ MEMINFO_DELTA_SHMEM_HUGE, // s_int "
+ MEMINFO_DELTA_SHMEM_HUGE_MAP, // s_int "
+ MEMINFO_DELTA_SLAB, // s_int "
+ MEMINFO_DELTA_SLAB_RECLAIM, // s_int "
+ MEMINFO_DELTA_SLAB_UNRECLAIM, // s_int "
+ MEMINFO_DELTA_TOTAL, // s_int "
+ MEMINFO_DELTA_UNEVICTABLE, // s_int "
+ MEMINFO_DELTA_USED, // s_int "
+ MEMINFO_DELTA_VM_ALLOC_CHUNK, // s_int "
+ MEMINFO_DELTA_VM_ALLOC_TOTAL, // s_int "
+ MEMINFO_DELTA_VM_ALLOC_USED, // s_int "
+ MEMINFO_DELTA_WRITEBACK, // s_int "
+ MEMINFO_DELTA_WRITEBACK_TMP, // s_int "
+
+ MEMINFO_SWAP_CACHED, // ul_int /proc/meminfo
+ MEMINFO_SWAP_FREE, // ul_int "
+ MEMINFO_SWAP_TOTAL, // ul_int "
+ MEMINFO_SWAP_USED, // ul_int derived from SWAP_TOTAL - SWAP_FREE
+
+ MEMINFO_SWAP_DELTA_CACHED, // s_int derived from above
+ MEMINFO_SWAP_DELTA_FREE, // s_int "
+ MEMINFO_SWAP_DELTA_TOTAL, // s_int "
+ MEMINFO_SWAP_DELTA_USED // s_int "
+};
+
+
+struct meminfo_result {
+ enum meminfo_item item;
+ union {
+ signed int s_int;
+ unsigned long ul_int;
+ } result;
+};
+
+struct meminfo_stack {
+ struct meminfo_result *head;
+};
+
+struct meminfo_info;
+
+
+#define MEMINFO_GET( info, actual_enum, type ) ( { \
+ struct meminfo_result *r = procps_meminfo_get( info, actual_enum ); \
+ r ? r->result . type : 0; } )
+
+#define MEMINFO_VAL( relative_enum, type, stack, info ) \
+ stack -> head [ relative_enum ] . result . type
+
+
+int procps_meminfo_new (struct meminfo_info **info);
+int procps_meminfo_ref (struct meminfo_info *info);
+int procps_meminfo_unref (struct meminfo_info **info);
+
+struct meminfo_result *procps_meminfo_get (
+ struct meminfo_info *info,
+ enum meminfo_item item);
+
+struct meminfo_stack *procps_meminfo_select (
+ struct meminfo_info *info,
+ enum meminfo_item *items,
+ int numitems);
+
+
+#ifdef XTRA_PROCPS_DEBUG
+# include "xtra-procps-debug.h"
+#endif
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/library/include/misc.h b/library/include/misc.h
new file mode 100644
index 0000000..bbfa5e2
--- /dev/null
+++ b/library/include/misc.h
@@ -0,0 +1,90 @@
+/*
+ * misc.h - miscellaneous declarations for libproc2
+ *
+ * Copyright © 2015-2023 Craig Small <csmall@dropbear.xyz>
+ * Copyright © 2021-2023 Jim Warner <james.warner@comcast.net>
+ * Copyright © 1998-2003 Albert Cahalan
+ * Copyright © 1992-1998 Michael K. Johnson <johnsonm@redhat.com>
+ * Copyright © 1996 Charles Blake <cblake@bbn.com>
+ * Copyright © 1993 J. Cowley
+ * Copyright © 1995 Martin Schulze <joey@infodrom.north.de>
+ * Copyright © ???? Larry Greenfield <greenfie@gauss.rutgers.edu>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PROC_MISC_H
+#define PROC_MISC_H
+#include <sys/types.h>
+#include <dirent.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+// //////////////////////////////////////////////////////////////////
+// Platform Particulars /////////////////////////////////////////////
+
+long procps_cpu_count (void);
+long procps_hertz_get (void);
+unsigned int procps_pid_length (void);
+
+ // Convenience macros for composing/decomposing version codes
+#define LINUX_VERSION(x,y,z) (0x10000*((x)&0x7fff) + 0x100*((y)&0xff) + ((z)&0xff))
+#define LINUX_VERSION_MAJOR(x) (((x)>>16) & 0xFF)
+#define LINUX_VERSION_MINOR(x) (((x)>> 8) & 0xFF)
+#define LINUX_VERSION_PATCH(x) ( (x) & 0xFF)
+
+int procps_linux_version(void);
+
+
+// //////////////////////////////////////////////////////////////////
+// Runtime Particulars //////////////////////////////////////////////
+
+int procps_loadavg (double *av1, double *av5, double *av15);
+int procps_uptime (double *uptime_secs, double *idle_secs);
+char *procps_uptime_sprint (void);
+char *procps_uptime_sprint_short (void);
+
+
+// //////////////////////////////////////////////////////////////////
+// Namespace Particulars ////////////////////////////////////////////
+
+enum namespace_type {
+ PROCPS_NS_CGROUP,
+ PROCPS_NS_IPC,
+ PROCPS_NS_MNT,
+ PROCPS_NS_NET,
+ PROCPS_NS_PID,
+ PROCPS_NS_TIME,
+ PROCPS_NS_USER,
+ PROCPS_NS_UTS,
+ PROCPS_NS_COUNT // total namespaces (fencepost)
+};
+
+struct procps_ns {
+ unsigned long ns[PROCPS_NS_COUNT];
+};
+
+const char *procps_ns_get_name (const int id);
+int procps_ns_get_id (const char *name);
+int procps_ns_read_pid (const int pid, struct procps_ns *nsp);
+
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/library/include/numa.h b/library/include/numa.h
new file mode 100644
index 0000000..5a4368b
--- /dev/null
+++ b/library/include/numa.h
@@ -0,0 +1,30 @@
+/*
+ * NUMA node support for <PIDS> & <STAT> interfaces
+ *
+ * Copyright © 2017-2023 Jim Warner <james.warner@comcast.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PROCPS_NUMA_H
+#define PROCPS_NUMA_H
+
+void numa_init (void);
+void numa_uninit (void);
+
+extern int (*numa_max_node) (void);
+extern int (*numa_node_of_cpu) (int);
+
+#endif
diff --git a/library/include/pids.h b/library/include/pids.h
new file mode 100644
index 0000000..8245e36
--- /dev/null
+++ b/library/include/pids.h
@@ -0,0 +1,287 @@
+/*
+ * pids.h - process related declarations for libproc2
+ *
+ * Copyright © 2015-2023 Jim Warner <james.warner@comcast.net>
+ * Copyright © 2015-2023 Craig Small <csmall@dropbear.xyz>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PROCPS_PIDS_H
+#define PROCPS_PIDS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum pids_item {
+ PIDS_noop, // ( never altered )
+ PIDS_extra, // ( reset to zero )
+ // returns origin, see proc(5)
+ // ------- -------------------
+ PIDS_ADDR_CODE_END, // ul_int stat: end_code
+ PIDS_ADDR_CODE_START, // ul_int stat: start_code
+ PIDS_ADDR_CURR_EIP, // ul_int stat: eip
+ PIDS_ADDR_CURR_ESP, // ul_int stat: esp
+ PIDS_ADDR_STACK_START, // ul_int stat: start_stack
+ PIDS_AUTOGRP_ID, // s_int autogroup
+ PIDS_AUTOGRP_NICE, // s_int autogroup
+ PIDS_CGNAME, // str derived from CGROUP ':name='
+ PIDS_CGROUP, // str cgroup
+ PIDS_CGROUP_V, // strv cgroup, as *str[]
+ PIDS_CMD, // str stat: comm or status: Name
+ PIDS_CMDLINE, // str cmdline
+ PIDS_CMDLINE_V, // strv cmdline, as *str[]
+ PIDS_ENVIRON, // str environ
+ PIDS_ENVIRON_V, // strv environ, as *str[]
+ PIDS_EXE, // str exe
+ PIDS_EXIT_SIGNAL, // s_int stat: exit_signal
+ PIDS_FLAGS, // ul_int stat: flags
+ PIDS_FLT_MAJ, // ul_int stat: maj_flt
+ PIDS_FLT_MAJ_C, // ul_int derived from stat: maj_flt + cmaj_flt
+ PIDS_FLT_MAJ_DELTA, // s_int derived from FLT_MAJ
+ PIDS_FLT_MIN, // ul_int stat: min_flt
+ PIDS_FLT_MIN_C, // ul_int derived from stat: min_flt + cmin_flt
+ PIDS_FLT_MIN_DELTA, // s_int derived from FLT_MIN
+ PIDS_ID_EGID, // u_int status: Gid
+ PIDS_ID_EGROUP, // str derived from EGID, see getgrgid(3)
+ PIDS_ID_EUID, // u_int status: Uid
+ PIDS_ID_EUSER, // str derived from EUID, see getpwuid(3)
+ PIDS_ID_FGID, // u_int status: Gid
+ PIDS_ID_FGROUP, // str derived from FGID, see getgrgid(3)
+ PIDS_ID_FUID, // u_int status: Uid
+ PIDS_ID_FUSER, // str derived from FUID, see getpwuid(3)
+ PIDS_ID_LOGIN, // s_int loginuid
+ PIDS_ID_PGRP, // s_int stat: pgrp
+ PIDS_ID_PID, // s_int from /proc/<pid>
+ PIDS_ID_PPID, // s_int stat: ppid or status: PPid
+ PIDS_ID_RGID, // u_int status: Gid
+ PIDS_ID_RGROUP, // str derived from RGID, see getgrgid(3)
+ PIDS_ID_RUID, // u_int status: Uid
+ PIDS_ID_RUSER, // str derived from RUID, see getpwuid(3)
+ PIDS_ID_SESSION, // s_int stat: sid
+ PIDS_ID_SGID, // u_int status: Gid
+ PIDS_ID_SGROUP, // str derived from SGID, see getgrgid(3)
+ PIDS_ID_SUID, // u_int status: Uid
+ PIDS_ID_SUSER, // str derived from SUID, see getpwuid(3)
+ PIDS_ID_TGID, // s_int status: Tgid
+ PIDS_ID_TID, // s_int from /proc/<pid>/task/<tid>
+ PIDS_ID_TPGID, // s_int stat: tty_pgrp
+ PIDS_IO_READ_BYTES, // ul_int io: read_bytes
+ PIDS_IO_READ_CHARS, // ul_int io: rchar
+ PIDS_IO_READ_OPS, // ul_int io: syscr
+ PIDS_IO_WRITE_BYTES, // ul_int io: write_bytes
+ PIDS_IO_WRITE_CBYTES, // ul_int io: cancelled_write_bytes
+ PIDS_IO_WRITE_CHARS, // ul_int io: wchar
+ PIDS_IO_WRITE_OPS, // ul_int io: syscw
+ PIDS_LXCNAME, // str derived from CGROUP 'lxc.payload'
+ PIDS_MEM_CODE, // ul_int derived from MEM_CODE_PGS, as KiB
+ PIDS_MEM_CODE_PGS, // ul_int statm: trs
+ PIDS_MEM_DATA, // ul_int derived from MEM_DATA_PGS, as KiB
+ PIDS_MEM_DATA_PGS, // ul_int statm: drs
+ PIDS_MEM_RES, // ul_int derived from MEM_RES_PGS, as KiB
+ PIDS_MEM_RES_PGS, // ul_int statm: resident
+ PIDS_MEM_SHR, // ul_int derived from MEM_SHR_PGS, as KiB
+ PIDS_MEM_SHR_PGS, // ul_int statm: shared
+ PIDS_MEM_VIRT, // ul_int derived from MEM_VIRT_PGS, as KiB
+ PIDS_MEM_VIRT_PGS, // ul_int statm: size
+ PIDS_NICE, // s_int stat: nice
+ PIDS_NLWP, // s_int stat: num_threads or status: Threads
+ PIDS_NS_CGROUP, // ul_int ns/
+ PIDS_NS_IPC, // ul_int "
+ PIDS_NS_MNT, // ul_int "
+ PIDS_NS_NET, // ul_int "
+ PIDS_NS_PID, // ul_int "
+ PIDS_NS_TIME, // ul_int "
+ PIDS_NS_USER, // ul_int "
+ PIDS_NS_UTS, // ul_int "
+ PIDS_OOM_ADJ, // s_int oom_score_adj
+ PIDS_OOM_SCORE, // s_int oom_score
+ PIDS_PRIORITY, // s_int stat: priority
+ PIDS_PRIORITY_RT, // s_int stat: rt_priority
+ PIDS_PROCESSOR, // s_int stat: task_cpu
+ PIDS_PROCESSOR_NODE, // s_int derived from PROCESSOR, see numa(3)
+ PIDS_RSS, // ul_int stat: rss
+ PIDS_RSS_RLIM, // ul_int stat: rsslim
+ PIDS_SCHED_CLASS, // s_int stat: policy
+ PIDS_SD_MACH, // str derived from PID/TID, see sd-login(3)
+ PIDS_SD_OUID, // str "
+ PIDS_SD_SEAT, // str "
+ PIDS_SD_SESS, // str "
+ PIDS_SD_SLICE, // str "
+ PIDS_SD_UNIT, // str "
+ PIDS_SD_UUNIT, // str "
+ PIDS_SIGBLOCKED, // str status: SigBlk
+ PIDS_SIGCATCH, // str status: SigCgt
+ PIDS_SIGIGNORE, // str status: SigIgn
+ PIDS_SIGNALS, // str status: ShdPnd
+ PIDS_SIGPENDING, // str status: SigPnd
+ PIDS_SMAP_ANONYMOUS, // ul_int smaps_rollup: Anonymous
+ PIDS_SMAP_HUGE_ANON, // ul_int smaps_rollup: AnonHugePages
+ PIDS_SMAP_HUGE_FILE, // ul_int smaps_rollup: FilePmdMapped
+ PIDS_SMAP_HUGE_SHMEM, // ul_int smaps_rollup: ShmemPmdMapped
+ PIDS_SMAP_HUGE_TLBPRV, // ul_int smaps_rollup: Private_Hugetlb
+ PIDS_SMAP_HUGE_TLBSHR, // ul_int smaps_rollup: Shared_Hugetlb
+ PIDS_SMAP_LAZY_FREE, // ul_int smaps_rollup: LazyFree
+ PIDS_SMAP_LOCKED, // ul_int smaps_rollup: Locked
+ PIDS_SMAP_PRV_CLEAN, // ul_int smaps_rollup: Private_Clean
+ PIDS_SMAP_PRV_DIRTY, // ul_int smaps_rollup: Private_Dirty
+ PIDS_SMAP_PRV_TOTAL, // ul_int derived from SMAP_PRV_CLEAN + SMAP_PRV_DIRTY
+ PIDS_SMAP_PSS, // ul_int smaps_rollup: Pss
+ PIDS_SMAP_PSS_ANON, // ul_int smaps_rollup: Pss_Anon
+ PIDS_SMAP_PSS_FILE, // ul_int smaps_rollup: Pss_File
+ PIDS_SMAP_PSS_SHMEM, // ul_int smaps_rollup: Pss_Shmem
+ PIDS_SMAP_REFERENCED, // ul_int smaps_rollup: Referenced
+ PIDS_SMAP_RSS, // ul_int smaps_rollup: Rss
+ PIDS_SMAP_SHR_CLEAN, // ul_int smaps_rollup: Shared_Clean
+ PIDS_SMAP_SHR_DIRTY, // ul_int smaps_rollup: Shared_Dirty
+ PIDS_SMAP_SWAP, // ul_int smaps_rollup: Swap
+ PIDS_SMAP_SWAP_PSS, // ul_int smaps_rollup: SwapPss
+ PIDS_STATE, // s_ch stat: state or status: State
+ PIDS_SUPGIDS, // str status: Groups
+ PIDS_SUPGROUPS, // str derived from SUPGIDS, see getgrgid(3)
+ PIDS_TICS_ALL, // ull_int derived from stat: stime + utime
+ PIDS_TICS_ALL_C, // ull_int derived from stat: stime + utime + cstime + cutime
+ PIDS_TICS_ALL_DELTA, // u_int derived from TICS_ALL
+ PIDS_TICS_BEGAN, // ull_int stat: start_time
+ PIDS_TICS_BLKIO, // ull_int stat: blkio_ticks
+ PIDS_TICS_GUEST, // ull_int stat: gtime
+ PIDS_TICS_GUEST_C, // ull_int derived from stat: gtime + cgtime
+ PIDS_TICS_SYSTEM, // ull_int stat: stime
+ PIDS_TICS_SYSTEM_C, // ull_int derived from stat: stime + cstime
+ PIDS_TICS_USER, // ull_int stat: utime
+ PIDS_TICS_USER_C, // ull_int derived from stat: utime + cutime
+ PIDS_TIME_ALL, // real * derived from stat: (utime + stime) / hertz
+ PIDS_TIME_ALL_C, // real * derived from stat: (utime + stime + cutime + cstime) / hertz
+ PIDS_TIME_ELAPSED, // real * derived from stat: (/proc/uptime - start_time) / hertz
+ PIDS_TIME_START, // real * derived from stat: start_time / hertz
+ PIDS_TTY, // s_int stat: tty_nr
+ PIDS_TTY_NAME, // str derived from TTY
+ PIDS_TTY_NUMBER, // str derived from TTY as str
+ PIDS_UTILIZATION, // real derived from TIME_ALL / TIME_ELAPSED, as percentage
+ PIDS_UTILIZATION_C, // real derived from TIME_ALL_C / TIME_ELAPSED, as percentage
+ PIDS_VM_DATA, // ul_int status: VmData
+ PIDS_VM_EXE, // ul_int status: VmExe
+ PIDS_VM_LIB, // ul_int status: VmLib
+ PIDS_VM_RSS, // ul_int status: VmRSS
+ PIDS_VM_RSS_ANON, // ul_int status: RssAnon
+ PIDS_VM_RSS_FILE, // ul_int status: RssFile
+ PIDS_VM_RSS_LOCKED, // ul_int status: VmLck
+ PIDS_VM_RSS_SHARED, // ul_int status: RssShmem
+ PIDS_VM_SIZE, // ul_int status: VmSize
+ PIDS_VM_STACK, // ul_int status: VmStk
+ PIDS_VM_SWAP, // ul_int status: VmSwap
+ PIDS_VM_USED, // ul_int derived from status: VmRSS + VmSwap
+ PIDS_VSIZE_BYTES, // ul_int stat: vsize
+ PIDS_WCHAN_NAME // str wchan
+};
+ // * while these are all expressed as seconds, each can be
+ // converted into tics/jiffies with no loss of precision
+ // when multiplied by hertz obtained via procps_misc(3).
+enum pids_fetch_type {
+ PIDS_FETCH_TASKS_ONLY,
+ PIDS_FETCH_THREADS_TOO
+};
+
+enum pids_select_type {
+ PIDS_SELECT_PID = 0x10000,
+ PIDS_SELECT_PID_THREADS = 0x10001,
+ PIDS_SELECT_UID = 0x20000,
+ PIDS_SELECT_UID_THREADS = 0x20001
+};
+
+enum pids_sort_order {
+ PIDS_SORT_ASCEND = +1,
+ PIDS_SORT_DESCEND = -1
+};
+
+
+struct pids_result {
+ enum pids_item item;
+ union {
+ signed char s_ch;
+ signed int s_int;
+ unsigned int u_int;
+ unsigned long ul_int;
+ unsigned long long ull_int;
+ char *str;
+ char **strv;
+ double real;
+ } result;
+};
+
+struct pids_stack {
+ struct pids_result *head;
+};
+
+struct pids_counts {
+ int total;
+ int running, sleeping, stopped, zombied, other;
+};
+
+struct pids_fetch {
+ struct pids_counts *counts;
+ struct pids_stack **stacks;
+};
+
+struct pids_info;
+
+
+#define PIDS_VAL( relative_enum, type, stack, info ) \
+ stack -> head [ relative_enum ] . result . type
+
+
+int procps_pids_new (struct pids_info **info, enum pids_item *items, int numitems);
+int procps_pids_ref (struct pids_info *info);
+int procps_pids_unref (struct pids_info **info);
+
+struct pids_stack *fatal_proc_unmounted (
+ struct pids_info *info,
+ int return_self);
+
+struct pids_stack *procps_pids_get (
+ struct pids_info *info,
+ enum pids_fetch_type which);
+
+struct pids_fetch *procps_pids_reap (
+ struct pids_info *info,
+ enum pids_fetch_type which);
+
+int procps_pids_reset (
+ struct pids_info *info,
+ enum pids_item *newitems,
+ int newnumitems);
+
+struct pids_fetch *procps_pids_select (
+ struct pids_info *info,
+ unsigned *these,
+ int numthese,
+ enum pids_select_type which);
+
+struct pids_stack **procps_pids_sort (
+ struct pids_info *info,
+ struct pids_stack *stacks[],
+ int numstacked,
+ enum pids_item sortitem,
+ enum pids_sort_order order);
+
+
+#ifdef XTRA_PROCPS_DEBUG
+# include "xtra-procps-debug.h"
+#endif
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/library/include/procps-private.h b/library/include/procps-private.h
new file mode 100644
index 0000000..835ea21
--- /dev/null
+++ b/library/include/procps-private.h
@@ -0,0 +1,28 @@
+/*
+ * libprocps - Library to read proc filesystem
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#ifndef PROCPS_PRIVATE_H
+#define PROCPS_PRIVATE_H
+
+#define PROCPS_EXPORT __attribute__ ((visibility("default")))
+
+#define STRINGIFY_ARG(a) #a
+#define STRINGIFY(a) STRINGIFY_ARG(a)
+
+#define MAXTABLE(t) (int)(sizeof(t) / sizeof(t[0]))
+
+#endif
diff --git a/library/include/pwcache.h b/library/include/pwcache.h
new file mode 100644
index 0000000..524a2af
--- /dev/null
+++ b/library/include/pwcache.h
@@ -0,0 +1,38 @@
+/*
+ * pwcache.c - memory cache passwd file handling
+ *
+ * Copyright © 2011-2023 Jim Warner <james.warner@comcast.net>
+ * Copyright © 2015-2023 Craig Small <csmall@dropbear.xyz>
+ * Copyright © 2002 Albert Cahalan
+ *
+ * Older version:
+ * Copyright © 1992-1998 Michael K. Johnson <johnsonm@redhat.com>
+ * Note: most likely none of his code remains
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PROCPS_PROC_PWCACHE_H
+#define PROCPS_PROC_PWCACHE_H
+
+#include <sys/types.h>
+
+// used in pwcache and in readproc to set size of username or groupname
+#define P_G_SZ 33
+
+char *pwcache_get_user(uid_t uid);
+char *pwcache_get_group(gid_t gid);
+
+#endif
diff --git a/library/include/readproc.h b/library/include/readproc.h
new file mode 100644
index 0000000..33c630e
--- /dev/null
+++ b/library/include/readproc.h
@@ -0,0 +1,303 @@
+/*
+ * readproc - interface to process table
+ *
+ * Copyright © 2002-2023 Craig Small <csmall@dropbear.xyz>
+ * Copyright © 2011-2023 Jim Warner <james.warner@comcast.net>
+ * Copyright © 1998-2010 Albert Cahalan
+ * Copyright © 1998 Michael K. Johnson
+ * Copyright © 1996 Charles L. Blake.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PROCPS_PROC_READPROC_H
+#define PROCPS_PROC_READPROC_H
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <unistd.h>
+#include "misc.h"
+
+// the following is development only, forcing display of "[ duplicate ENUM ]" strings
+// #define FALSE_THREADS /* set most child string fields to NULL */
+
+
+// This is to help document a transition from pid to tgid/tid caused
+// by the introduction of thread support. It is used in cases where
+// neither tgid nor tid seemed correct. (in other words, FIXME)
+#define XXXID tid
+
+// Basic data structure which holds all information we can get about a process.
+// (unless otherwise specified, fields are read from /proc/#/stat)
+//
+// Most of it comes from task_struct in linux/sched.h
+//
+typedef struct proc_t {
+ int
+ tid, // (special) task id, the POSIX thread ID (see also: tgid)
+ ppid; // stat,status pid of parent process
+ char
+ state, // stat,status single-char code for process state (S=sleeping)
+ pad_1, // n/a padding
+ pad_2, // n/a padding
+ pad_3; // n/a padding
+ unsigned long long
+ utime, // stat user-mode CPU time accumulated by process
+ stime, // stat kernel-mode CPU time accumulated by process
+ cutime, // stat cumulative utime of process and reaped children
+ cstime, // stat cumulative stime of process and reaped children
+ start_time, // stat start time of process -- seconds since system boot
+ blkio_tics, // stat time spent waiting for block IO
+ gtime, // stat guest time of the task in jiffies
+ cgtime; // stat guest time of the task children in jiffies
+ int // next 3 fields are NOT filled in by readproc
+ pcpu, // stat (special) elapsed tics for %CPU usage calculation
+ maj_delta, // stat (special) major page faults since last update
+ min_delta; // stat (special) minor page faults since last update
+ char
+ // Linux 2.1.7x and up have 64 signals. Allow 64, plus '\0' and padding.
+ signal[18], // status mask of pending signals
+ blocked[18], // status mask of blocked signals
+ sigignore[18], // status mask of ignored signals
+ sigcatch[18], // status mask of caught signals
+ _sigpnd[18]; // status mask of PER TASK pending signals
+ unsigned long
+ start_code, // stat address of beginning of code segment
+ end_code, // stat address of end of code segment
+ start_stack, // stat address of the bottom of stack for the process
+ kstk_esp, // stat kernel stack pointer
+ kstk_eip, // stat kernel instruction pointer
+ wchan, // stat (special) address of kernel wait channel proc is sleeping in
+ rss, // stat identical to 'resident'
+ alarm; // stat ?
+ int
+ priority, // stat kernel scheduling priority
+ nice; // stat standard unix nice level of process
+ unsigned long
+ // the next 7 members come from /proc/#/statm
+ size, // statm total virtual memory (as # pages)
+ resident, // statm resident non-swapped memory (as # pages)
+ share, // statm shared (mmap'd) memory (as # pages)
+ trs, // statm text (exe) resident set (as # pages)
+ lrs, // statm library resident set (always 0 w/ 2.6)
+ drs, // statm data+stack resident set (as # pages)
+ dt; // statm dirty pages (always 0 w/ 2.6)
+ unsigned long
+ vm_size, // status equals 'size' (as kb)
+ vm_lock, // status locked pages (as kb)
+ vm_rss, // status equals 'rss' and/or 'resident' (as kb)
+ vm_rss_anon, // status the 'anonymous' portion of vm_rss (as kb)
+ vm_rss_file, // status the 'file-backed' portion of vm_rss (as kb)
+ vm_rss_shared, // status the 'shared' portion of vm_rss (as kb)
+ vm_data, // status data only size (as kb)
+ vm_stack, // status stack only size (as kb)
+ vm_swap, // status based on linux-2.6.34 "swap ents" (as kb)
+ vm_exe, // status equals 'trs' (as kb)
+ vm_lib, // status total, not just used, library pages (as kb)
+ vsize, // stat number of pages of virtual memory ...
+ rss_rlim, // stat resident set size limit?
+ flags, // stat kernel flags for the process
+ min_flt, // stat number of minor page faults since process start
+ maj_flt, // stat number of major page faults since process start
+ cmin_flt, // stat cumulative min_flt of process and child processes
+ cmaj_flt, // stat cumulative maj_flt of process and child processes
+ rchar, // io characters read
+ wchar, // io characters written
+ syscr, // io number of read I/O operations
+ syscw, // io number of write I/O operations
+ read_bytes, // io number of bytes fetched from the storage layer
+ write_bytes, // io number of bytes sent to the storage layer
+ cancelled_write_bytes, // io number of bytes truncating pagecache
+ smap_Rss, // smaps_rollup mapping currently resident in RAM
+ smap_Pss, // " Rss divided by total processes sharing it
+ smap_Pss_Anon, // " proportional share of 'anonymous' memory
+ smap_Pss_File, // " proportional share of 'file' memory
+ smap_Pss_Shmem, // " proportional share of 'shmem' memory
+ smap_Shared_Clean, // " unmodified shared memory
+ smap_Shared_Dirty, // " altered shared memory
+ smap_Private_Clean, // " unmodified private memory
+ smap_Private_Dirty, // " altered private memory
+ smap_Referenced, // " memory marked as referenced/accessed
+ smap_Anonymous, // " memory not belonging to any file
+ smap_LazyFree, // " memory marked by madvise(MADV_FREE)
+ smap_AnonHugePages, // " memory backed by transparent huge pages
+ smap_ShmemPmdMapped, // " shmem/tmpfs memory backed by huge pages
+ smap_FilePmdMapped, // " file memory backed by huge pages
+ smap_Shared_Hugetlb, // " hugetlbfs backed memory *not* counted in Rss/Pss
+ smap_Private_Hugetlb, // " hugetlbfs backed memory *not* counted in Rss/Pss
+ smap_Swap, // " swapped would-be-anonymous memory (includes swapped out shmem)
+ smap_SwapPss, // " the proportional share of 'Swap' (excludes swapped out shmem)
+ smap_Locked; // " memory amount locked to RAM
+ char
+ *environ, // (special) environment as string (/proc/#/environ)
+ *cmdline, // (special) command line as string (/proc/#/cmdline)
+ *cgroup, // (special) cgroup as string (/proc/#/cgroup)
+ *cgname, // (special) name portion of above (if possible)
+ *supgid, // status supplementary gids as comma delimited str
+ *supgrp, // supp grp names as comma delimited str, derived from supgid
+ **environ_v, // (special) environment string vectors (/proc/#/environ)
+ **cmdline_v, // (special) command line string vectors (/proc/#/cmdline)
+ **cgroup_v; // (special) cgroup string vectors (/proc/#/cgroup)
+ char
+ *euser, // stat(),status effective user name
+ *ruser, // status real user name
+ *suser, // status saved user name
+ *fuser, // status filesystem user name
+ *rgroup, // status real group name
+ *egroup, // status effective group name
+ *sgroup, // status saved group name
+ *fgroup, // status filesystem group name
+ *cmd; // stat,status basename of executable file in call to exec(2)
+ int
+ rtprio, // stat real-time priority
+ sched, // stat scheduling class
+ pgrp, // stat process group id
+ session, // stat session id
+ nlwp, // stat,status number of threads, or 0 if no clue
+ tgid, // (special) thread group ID, the POSIX PID (see also: tid)
+ tty; // stat full device number of controlling terminal
+ /* FIXME: int uids & gids should be uid_t or gid_t from pwd.h */
+ uid_t euid; gid_t egid; // stat(),status effective
+ uid_t ruid; gid_t rgid; // status real
+ uid_t suid; gid_t sgid; // status saved
+ uid_t fuid; gid_t fgid; // status fs (used for file access only)
+ int
+ tpgid, // stat terminal process group id
+ exit_signal, // stat might not be SIGCHLD
+ processor; // stat current (or most recent?) CPU
+ int
+ oom_score, // oom_score (badness for OOM killer)
+ oom_adj; // oom_adj (adjustment to OOM score)
+ struct procps_ns ns; // (ns subdir) inode number of namespaces
+ char
+ *sd_mach, // n/a systemd vm/container name
+ *sd_ouid, // n/a systemd session owner uid
+ *sd_seat, // n/a systemd login session seat
+ *sd_sess, // n/a systemd login session id
+ *sd_slice, // n/a systemd slice unit
+ *sd_unit, // n/a systemd system unit id
+ *sd_uunit; // n/a systemd user unit id
+ char
+ *lxcname, // n/a lxc container name
+ *exe; // exe executable path + name
+ int
+ luid, // loginuid user id at login
+ autogrp_id, // autogroup autogroup number (id)
+ autogrp_nice; // autogroup autogroup nice value
+} proc_t;
+
+// PROCTAB: data structure holding the persistent information readproc needs
+// from openproc(). The setup is intentionally similar to the dirent interface
+// and other system table interfaces (utmp+wtmp come to mind).
+
+#define PROCPATHLEN 64 // must hold /proc/2000222000/task/2000222000/cmdline
+
+typedef struct PROCTAB {
+ DIR *procfs;
+// char deBug0[64];
+ DIR *taskdir; // for threads
+// char deBug1[64];
+ pid_t taskdir_user; // for threads
+ int(*finder)(struct PROCTAB *__restrict const, proc_t *__restrict const);
+ proc_t*(*reader)(struct PROCTAB *__restrict const, proc_t *__restrict const);
+ int(*taskfinder)(struct PROCTAB *__restrict const, const proc_t *__restrict const, proc_t *__restrict const, char *__restrict const);
+ proc_t*(*taskreader)(struct PROCTAB *__restrict const, proc_t *__restrict const, char *__restrict const);
+ pid_t *pids; // pids of the procs
+ uid_t *uids; // uids of procs
+ int nuid; // cannot really sentinel-terminate unsigned short[]
+ int i; // generic
+ int hide_kernel; // getenv LIBPROC_HIDE_KERNEL was set
+ unsigned flags;
+ unsigned u; // generic
+ void * vp; // generic
+ char path[PROCPATHLEN]; // must hold /proc/2000222000/task/2000222000/cmdline
+ unsigned pathlen; // length of string in the above (w/o '\0')
+} PROCTAB;
+
+
+// openproc/readproctab:
+//
+// Return PROCTAB* / *proc_t[] or NULL on error ((probably) "/proc" cannot be
+// opened.) By default readproc will consider all processes as valid to parse
+// and return, but not actually fill in the cmdline, environ, and /proc/#/statm
+// derived memory fields.
+//
+// `flags' (a bitwise-or of PROC_* below) modifies the default behavior. The
+// "fill" options will cause more of the proc_t to be filled in. The "filter"
+// options all use the second argument as the pointer to a list of objects:
+// process status', process id's, user id's. The third
+// argument is the length of the list (currently only used for lists of user
+// id's since uid_t supports no convenient termination sentinel.)
+
+#define PROC_FILLMEM 0x00000001 // read statm
+#define PROC_FILLARG 0x00000002 // alloc and fill in `cmdline' vectors
+#define PROC_FILLENV 0x00000004 // alloc and fill in `environ' vectors
+#define PROC_FILLUSR 0x00000008 // resolve user id number -> user name
+#define PROC_FILLGRP 0x00000010 // resolve group id number -> group name
+#define PROC_FILLSTATUS 0x00000020 // read status
+#define PROC_FILLSTAT 0x00000040 // read stat
+#define PROC_FILLCGROUP 0x00000080 // alloc and fill in `cgroup` vectors
+#define PROC_FILLOOM 0x00000100 // fill in proc_t oom_score and oom_adj
+#define PROC_FILLNS 0x00000200 // fill in proc_t namespace information
+#define PROC_FILLSYSTEMD 0x00000400 // fill in proc_t systemd information
+#define PROC_FILL_LXC 0x00000800 // fill in proc_t lxcname, if possible
+#define PROC_FILL_LUID 0x00001000 // fill in proc_t luid (login user id)
+#define PROC_FILL_EXE 0x00002000 // fill in proc_t exe path + pgm name
+#define PROC_FILLIO 0x00004000 // fill in proc_t io information
+#define PROC_FILLSMAPS 0x00008000 // fill in proc_t smaps_rollup stuff
+
+// consider only processes with one of the passed:
+#define PROC_PID 0x00010000 // process id numbers ( 0 terminated )
+#define PROC_UID 0x00020000 // user id numbers ( length needed )
+// Note: the above 2 values must NOT change without also changing pids.h !!!
+
+#define PROC_EDITCGRPCVT 0x00040000 // edit `cgroup' as regular string
+#define PROC_EDITCMDLCVT 0x00080000 // edit `cmdline' as regular string
+#define PROC_EDITENVRCVT 0x00100000 // edit `environ' as regular string
+
+// these three also require the PROC_FILLSTATUS flage
+#define PROC_FILL_OUSERS ( 0x00200000 | PROC_FILLSTATUS ) // obtain other user names
+#define PROC_FILL_OGROUPS ( 0x00400000 | PROC_FILLSTATUS ) // obtain other group names
+#define PROC_FILL_SUPGRP ( 0x00800000 | PROC_FILLSTATUS ) // obtain supplementary group names
+
+// and let's put new flags here ...
+#define PROC_FILLAUTOGRP 0x01000000 // fill in proc_t autogroup stuff
+
+// it helps to give app code a few spare bits
+#define PROC_SPARE_1 0x10000000
+#define PROC_SPARE_2 0x20000000
+#define PROC_SPARE_3 0x40000000
+#define PROC_SPARE_4 0x80000000
+
+// Function definitions
+// Initialize a PROCTAB structure holding needed call-to-call persistent data
+PROCTAB *openproc(unsigned flags, ... /* pid_t *| uid_t *| dev_t *| char *[, int n] */ );
+// Retrieve the next process or task matching the criteria set by the openproc().
+//
+// Note: When NULL is used as the readproc 'p' or readeither 'x'
+// parameter, the library will allocate the necessary proc_t storage.
+//
+// Alternatively, you may provide your own reuseable buffer address
+// in which case that buffer *MUST* be initialized to zero one time
+// only before first use. Thereafter, the library will manage such
+// a passed proc_t, freeing any additional acquired memory associated
+// with the previous process or thread.
+proc_t *readproc(PROCTAB *__restrict const PT, proc_t *__restrict p);
+proc_t *readeither(PROCTAB *__restrict const PT, proc_t *__restrict x);
+int look_up_our_self(void);
+void closeproc(PROCTAB *PT);
+char **vectorize_this_str(const char *src);
+
+#endif
diff --git a/library/include/slabinfo.h b/library/include/slabinfo.h
new file mode 100644
index 0000000..f63ca79
--- /dev/null
+++ b/library/include/slabinfo.h
@@ -0,0 +1,140 @@
+/*
+ * slabinfo.h - slab pools related declarations for libproc2
+ *
+ * Copyright © 2015-2023 Jim Warner <james.warner@comcast.net>
+ * Copyright © 2015-2023 Craig Small <csmall@dropbear.xyz>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PROCPS_SLABINFO_H
+#define PROCPS_SLABINFO_H
+
+#ifdef __cplusplus
+extern "C "{
+#endif
+
+enum slabinfo_item {
+ SLABINFO_noop, // ( never altered )
+ SLABINFO_extra, // ( reset to zero )
+ // returns origin, see proc(5)
+ // ------- -------------------
+ SLAB_NAME, // str /proc/slabinfo
+ SLAB_NUM_OBJS, // u_int "
+ SLAB_ACTIVE_OBJS, // u_int "
+ SLAB_OBJ_SIZE, // u_int "
+ SLAB_OBJ_PER_SLAB, // u_int "
+ SLAB_NUMS_SLABS, // u_int "
+ SLAB_ACTIVE_SLABS, // u_int "
+ SLAB_PAGES_PER_SLAB, // u_int "
+ SLAB_PERCENT_USED, // u_int derived from ACTIVE_OBJS / NUM_OBJS
+ SLAB_SIZE_TOTAL, // ul_int derived from page size * NUMS_SLABS * PAGES_PER_SLAB
+
+ SLABS_CACHES_TOTAL, // u_int derived from all caches
+ SLABS_CACHES_ACTIVE, // u_int "
+ SLABS_NUM_OBJS, // u_int "
+ SLABS_ACTIVE_OBJS, // u_int "
+ SLABS_OBJ_SIZE_AVG, // u_int "
+ SLABS_OBJ_SIZE_MIN, // u_int "
+ SLABS_OBJ_SIZE_MAX, // u_int "
+ SLABS_NUMS_SLABS, // u_int "
+ SLABS_ACTIVE_SLABS, // u_int "
+ SLABS_PAGES_TOTAL, // u_int "
+ SLABS_SIZE_ACTIVE, // ul_int "
+ SLABS_SIZE_TOTAL, // ul_int "
+
+ SLABS_DELTA_CACHES_TOTAL, // s_int derived from above
+ SLABS_DELTA_CACHES_ACTIVE, // s_int "
+ SLABS_DELTA_NUM_OBJS, // s_int "
+ SLABS_DELTA_ACTIVE_OBJS, // s_int "
+ SLABS_DELTA_OBJ_SIZE_AVG, // s_int "
+ SLABS_DELTA_OBJ_SIZE_MIN, // s_int "
+ SLABS_DELTA_OBJ_SIZE_MAX, // s_int "
+ SLABS_DELTA_NUMS_SLABS, // s_int "
+ SLABS_DELTA_ACTIVE_SLABS, // s_int "
+ SLABS_DELTA_PAGES_TOTAL, // s_int "
+ SLABS_DELTA_SIZE_ACTIVE, // s_int "
+ SLABS_DELTA_SIZE_TOTAL // s_int "
+};
+
+enum slabinfo_sort_order {
+ SLABINFO_SORT_ASCEND = +1,
+ SLABINFO_SORT_DESCEND = -1
+};
+
+
+struct slabinfo_result {
+ enum slabinfo_item item;
+ union {
+ signed int s_int;
+ unsigned int u_int;
+ unsigned long ul_int;
+ char *str;
+ } result;
+};
+
+struct slabinfo_stack {
+ struct slabinfo_result *head;
+};
+
+struct slabinfo_reaped {
+ int total;
+ struct slabinfo_stack **stacks;
+};
+
+struct slabinfo_info;
+
+
+#define SLABINFO_GET( info, actual_enum, type ) ( { \
+ struct slabinfo_result *r = procps_slabinfo_get( info, actual_enum ); \
+ r ? r->result . type : 0; } )
+
+#define SLABINFO_VAL( relative_enum, type, stack, info ) \
+ stack -> head [ relative_enum ] . result . type
+
+
+int procps_slabinfo_new (struct slabinfo_info **info);
+int procps_slabinfo_ref (struct slabinfo_info *info);
+int procps_slabinfo_unref (struct slabinfo_info **info);
+
+struct slabinfo_result *procps_slabinfo_get (
+ struct slabinfo_info *info,
+ enum slabinfo_item item);
+
+struct slabinfo_reaped *procps_slabinfo_reap (
+ struct slabinfo_info *info,
+ enum slabinfo_item *items,
+ int numitems);
+
+struct slabinfo_stack *procps_slabinfo_select (
+ struct slabinfo_info *info,
+ enum slabinfo_item *items,
+ int numitems);
+
+struct slabinfo_stack **procps_slabinfo_sort (
+ struct slabinfo_info *info,
+ struct slabinfo_stack *stacks[],
+ int numstacked,
+ enum slabinfo_item sortitem,
+ enum slabinfo_sort_order order);
+
+
+#ifdef XTRA_PROCPS_DEBUG
+# include "xtra-procps-debug.h"
+#endif
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/library/include/stat.h b/library/include/stat.h
new file mode 100644
index 0000000..7adefa6
--- /dev/null
+++ b/library/include/stat.h
@@ -0,0 +1,175 @@
+/*
+ * stat.h - cpu/numa related declarations for libproc2
+ *
+ * Copyright © 2015-2023 Jim Warner <james.warner@comcast.net>
+ * Copyright © 2015-2023 Craig Small <csmall@dropbear.xyz>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PROCPS_STAT_H
+#define PROCPS_STAT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum stat_item {
+ STAT_noop, // ( never altered )
+ STAT_extra, // ( reset to zero )
+ // returns origin, see proc(5)
+ // ------- -------------------
+ STAT_TIC_ID, // s_int /proc/stat, cpu or numa node id
+ STAT_TIC_ID_CORE, // s_int /proc/cpuinfo: 'core id', -1 = n/a
+ STAT_TIC_NUMA_NODE, // s_int [ CPU ID based, see: numa(3) ]
+ STAT_TIC_NUM_CONTRIBUTORS, // s_int [ total CPUs contributing to TIC counts ]
+ STAT_TIC_TYPE_CORE, // s_int [ 2 = P-core, 1 = E-core, 0 = n/a ]
+
+ STAT_TIC_USER, // ull_int /proc/stat
+ STAT_TIC_NICE, // ull_int "
+ STAT_TIC_SYSTEM, // ull_int "
+ STAT_TIC_IDLE, // ull_int "
+ STAT_TIC_IOWAIT, // ull_int "
+ STAT_TIC_IRQ, // ull_int "
+ STAT_TIC_SOFTIRQ, // ull_int "
+ STAT_TIC_STOLEN, // ull_int "
+ STAT_TIC_GUEST, // ull_int "
+ STAT_TIC_GUEST_NICE, // ull_int "
+
+ STAT_TIC_DELTA_USER, // sl_int derived from above
+ STAT_TIC_DELTA_NICE, // sl_int "
+ STAT_TIC_DELTA_SYSTEM, // sl_int "
+ STAT_TIC_DELTA_IDLE, // sl_int "
+ STAT_TIC_DELTA_IOWAIT, // sl_int "
+ STAT_TIC_DELTA_IRQ, // sl_int "
+ STAT_TIC_DELTA_SOFTIRQ, // sl_int "
+ STAT_TIC_DELTA_STOLEN, // sl_int "
+ STAT_TIC_DELTA_GUEST, // sl_int "
+ STAT_TIC_DELTA_GUEST_NICE, // sl_int "
+
+ STAT_TIC_SUM_USER, // ull_int derived from USER + NICE tics
+ STAT_TIC_SUM_SYSTEM, // ull_int derived from SYSTEM + IRQ + SOFTIRQ tics
+ STAT_TIC_SUM_IDLE, // ull_int derived from IDLE + IOWAIT tics
+ STAT_TIC_SUM_BUSY, // ull_int derived from SUM_TOTAL - SUM_IDLE tics
+ STAT_TIC_SUM_TOTAL, // ull_int derived from sum of all 10 tics
+
+ STAT_TIC_SUM_DELTA_USER, // sl_int derived from above
+ STAT_TIC_SUM_DELTA_SYSTEM, // sl_int "
+ STAT_TIC_SUM_DELTA_IDLE, // sl_int "
+ STAT_TIC_SUM_DELTA_BUSY, // sl_int "
+ STAT_TIC_SUM_DELTA_TOTAL, // sl_int "
+
+ STAT_SYS_CTX_SWITCHES, // ul_int /proc/stat
+ STAT_SYS_INTERRUPTS, // ul_int "
+ STAT_SYS_PROC_BLOCKED, // ul_int "
+ STAT_SYS_PROC_CREATED, // ul_int "
+ STAT_SYS_PROC_RUNNING, // ul_int "
+ STAT_SYS_TIME_OF_BOOT, // ul_int "
+
+ STAT_SYS_DELTA_CTX_SWITCHES, // s_int derived from above
+ STAT_SYS_DELTA_INTERRUPTS, // s_int "
+ STAT_SYS_DELTA_PROC_BLOCKED, // s_int "
+ STAT_SYS_DELTA_PROC_CREATED, // s_int "
+ STAT_SYS_DELTA_PROC_RUNNING // s_int "
+};
+
+enum stat_reap_type {
+ STAT_REAP_CPUS_ONLY,
+ STAT_REAP_NUMA_NODES_TOO
+};
+
+enum stat_sort_order {
+ STAT_SORT_ASCEND = +1,
+ STAT_SORT_DESCEND = -1
+};
+
+
+struct stat_result {
+ enum stat_item item;
+ union {
+ signed int s_int;
+ signed long sl_int;
+ unsigned long ul_int;
+ unsigned long long ull_int;
+ } result;
+};
+
+struct stat_stack {
+ struct stat_result *head;
+};
+
+struct stat_reap {
+ int total;
+ struct stat_stack **stacks;
+};
+
+struct stat_reaped {
+ struct stat_stack *summary;
+ struct stat_reap *cpus;
+ struct stat_reap *numa;
+};
+
+struct stat_info;
+
+
+ // STAT_TIC_ID value for /proc/stat cpu summary
+#define STAT_SUMMARY_ID -11111
+ // STAT_TIC_NUMA_NODE value for STAT_REAP_CPUS_ONLY or
+ // for STAT_REAP_NUMA_NODES_TOO when node was inactive
+#define STAT_NODE_INVALID -22222
+
+
+#define STAT_GET( info, actual_enum, type ) ( { \
+ struct stat_result *r = procps_stat_get( info, actual_enum ); \
+ r ? r->result . type : 0; } )
+
+#define STAT_VAL( relative_enum, type, stack, info ) \
+ stack -> head [ relative_enum ] . result . type
+
+
+int procps_stat_new (struct stat_info **info);
+int procps_stat_ref (struct stat_info *info);
+int procps_stat_unref (struct stat_info **info);
+
+struct stat_result *procps_stat_get (
+ struct stat_info *info,
+ enum stat_item item);
+
+struct stat_reaped *procps_stat_reap (
+ struct stat_info *info,
+ enum stat_reap_type what,
+ enum stat_item *items,
+ int numitems);
+
+struct stat_stack *procps_stat_select (
+ struct stat_info *info,
+ enum stat_item *items,
+ int numitems);
+
+struct stat_stack **procps_stat_sort (
+ struct stat_info *info,
+ struct stat_stack *stacks[],
+ int numstacked,
+ enum stat_item sortitem,
+ enum stat_sort_order order);
+
+
+#ifdef XTRA_PROCPS_DEBUG
+# include "xtra-procps-debug.h"
+#endif
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/library/include/vmstat.h b/library/include/vmstat.h
new file mode 100644
index 0000000..9b94931
--- /dev/null
+++ b/library/include/vmstat.h
@@ -0,0 +1,382 @@
+/*
+ * vmstat.h - virtual memory related declarations for libproc2
+ *
+ * Copyright © 2015-2023 Jim Warner <james.warner@comcast.net>
+ * Copyright © 2015-2023 Craig Small <csmall@dropbear.xyz>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#ifndef PROCPS_VMSTAT_H
+#define PROCPS_VMSTAT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum vmstat_item {
+ VMSTAT_noop, // ( never altered )
+ VMSTAT_extra, // ( reset to zero )
+ // returns origin, see proc(5)
+ // ------- -------------------
+ VMSTAT_ALLOCSTALL_DMA, // ul_int /proc/vmstat
+ VMSTAT_ALLOCSTALL_DMA32, // ul_int "
+ VMSTAT_ALLOCSTALL_HIGH, // ul_int "
+ VMSTAT_ALLOCSTALL_MOVABLE, // ul_int "
+ VMSTAT_ALLOCSTALL_NORMAL, // ul_int "
+ VMSTAT_BALLOON_DEFLATE, // ul_int "
+ VMSTAT_BALLOON_INFLATE, // ul_int "
+ VMSTAT_BALLOON_MIGRATE, // ul_int "
+ VMSTAT_COMPACT_DAEMON_FREE_SCANNED, // ul_int "
+ VMSTAT_COMPACT_DAEMON_MIGRATE_SCANNED, // ul_int "
+ VMSTAT_COMPACT_DAEMON_WAKE, // ul_int "
+ VMSTAT_COMPACT_FAIL, // ul_int "
+ VMSTAT_COMPACT_FREE_SCANNED, // ul_int "
+ VMSTAT_COMPACT_ISOLATED, // ul_int "
+ VMSTAT_COMPACT_MIGRATE_SCANNED, // ul_int "
+ VMSTAT_COMPACT_STALL, // ul_int "
+ VMSTAT_COMPACT_SUCCESS, // ul_int "
+ VMSTAT_DROP_PAGECACHE, // ul_int "
+ VMSTAT_DROP_SLAB, // ul_int "
+ VMSTAT_HTLB_BUDDY_ALLOC_FAIL, // ul_int "
+ VMSTAT_HTLB_BUDDY_ALLOC_SUCCESS, // ul_int "
+ VMSTAT_KSWAPD_HIGH_WMARK_HIT_QUICKLY, // ul_int "
+ VMSTAT_KSWAPD_INODESTEAL, // ul_int "
+ VMSTAT_KSWAPD_LOW_WMARK_HIT_QUICKLY, // ul_int "
+ VMSTAT_NR_ACTIVE_ANON, // ul_int "
+ VMSTAT_NR_ACTIVE_FILE, // ul_int "
+ VMSTAT_NR_ANON_PAGES, // ul_int "
+ VMSTAT_NR_ANON_TRANSPARENT_HUGEPAGES, // ul_int "
+ VMSTAT_NR_BOUNCE, // ul_int "
+ VMSTAT_NR_DIRTIED, // ul_int "
+ VMSTAT_NR_DIRTY, // ul_int "
+ VMSTAT_NR_DIRTY_BACKGROUND_THRESHOLD, // ul_int "
+ VMSTAT_NR_DIRTY_THRESHOLD, // ul_int "
+ VMSTAT_NR_FILE_HUGEPAGES, // ul_int "
+ VMSTAT_NR_FILE_PAGES, // ul_int "
+ VMSTAT_NR_FILE_PMDMAPPED, // ul_int "
+ VMSTAT_NR_FOLL_PIN_ACQUIRED, // ul_int "
+ VMSTAT_NR_FOLL_PIN_RELEASED, // ul_int "
+ VMSTAT_NR_FREE_CMA, // ul_int "
+ VMSTAT_NR_FREE_PAGES, // ul_int "
+ VMSTAT_NR_INACTIVE_ANON, // ul_int "
+ VMSTAT_NR_INACTIVE_FILE, // ul_int "
+ VMSTAT_NR_ISOLATED_ANON, // ul_int "
+ VMSTAT_NR_ISOLATED_FILE, // ul_int "
+ VMSTAT_NR_KERNEL_MISC_RECLAIMABLE, // ul_int "
+ VMSTAT_NR_KERNEL_STACK, // ul_int "
+ VMSTAT_NR_MAPPED, // ul_int "
+ VMSTAT_NR_MLOCK, // ul_int "
+ VMSTAT_NR_PAGE_TABLE_PAGES, // ul_int "
+ VMSTAT_NR_SHADOW_CALL_STACK, // ul_int "
+ VMSTAT_NR_SHMEM, // ul_int "
+ VMSTAT_NR_SHMEM_HUGEPAGES, // ul_int "
+ VMSTAT_NR_SHMEM_PMDMAPPED, // ul_int "
+ VMSTAT_NR_SLAB_RECLAIMABLE, // ul_int "
+ VMSTAT_NR_SLAB_UNRECLAIMABLE, // ul_int "
+ VMSTAT_NR_UNEVICTABLE, // ul_int "
+ VMSTAT_NR_UNSTABLE, // ul_int "
+ VMSTAT_NR_VMSCAN_IMMEDIATE_RECLAIM, // ul_int "
+ VMSTAT_NR_VMSCAN_WRITE, // ul_int "
+ VMSTAT_NR_WRITEBACK, // ul_int "
+ VMSTAT_NR_WRITEBACK_TEMP, // ul_int "
+ VMSTAT_NR_WRITTEN, // ul_int "
+ VMSTAT_NR_ZONE_ACTIVE_ANON, // ul_int "
+ VMSTAT_NR_ZONE_ACTIVE_FILE, // ul_int "
+ VMSTAT_NR_ZONE_INACTIVE_ANON, // ul_int "
+ VMSTAT_NR_ZONE_INACTIVE_FILE, // ul_int "
+ VMSTAT_NR_ZONE_UNEVICTABLE, // ul_int "
+ VMSTAT_NR_ZONE_WRITE_PENDING, // ul_int "
+ VMSTAT_NR_ZSPAGES, // ul_int "
+ VMSTAT_NUMA_FOREIGN, // ul_int "
+ VMSTAT_NUMA_HINT_FAULTS, // ul_int "
+ VMSTAT_NUMA_HINT_FAULTS_LOCAL, // ul_int "
+ VMSTAT_NUMA_HIT, // ul_int "
+ VMSTAT_NUMA_HUGE_PTE_UPDATES, // ul_int "
+ VMSTAT_NUMA_INTERLEAVE, // ul_int "
+ VMSTAT_NUMA_LOCAL, // ul_int "
+ VMSTAT_NUMA_MISS, // ul_int "
+ VMSTAT_NUMA_OTHER, // ul_int "
+ VMSTAT_NUMA_PAGES_MIGRATED, // ul_int "
+ VMSTAT_NUMA_PTE_UPDATES, // ul_int "
+ VMSTAT_OOM_KILL, // ul_int "
+ VMSTAT_PAGEOUTRUN, // ul_int "
+ VMSTAT_PGACTIVATE, // ul_int "
+ VMSTAT_PGALLOC_DMA, // ul_int "
+ VMSTAT_PGALLOC_DMA32, // ul_int "
+ VMSTAT_PGALLOC_HIGH, // ul_int "
+ VMSTAT_PGALLOC_MOVABLE, // ul_int "
+ VMSTAT_PGALLOC_NORMAL, // ul_int "
+ VMSTAT_PGDEACTIVATE, // ul_int "
+ VMSTAT_PGFAULT, // ul_int "
+ VMSTAT_PGFREE, // ul_int "
+ VMSTAT_PGINODESTEAL, // ul_int "
+ VMSTAT_PGLAZYFREE, // ul_int "
+ VMSTAT_PGLAZYFREED, // ul_int "
+ VMSTAT_PGMAJFAULT, // ul_int "
+ VMSTAT_PGMIGRATE_FAIL, // ul_int "
+ VMSTAT_PGMIGRATE_SUCCESS, // ul_int "
+ VMSTAT_PGPGIN, // ul_int "
+ VMSTAT_PGPGOUT, // ul_int "
+ VMSTAT_PGREFILL, // ul_int "
+ VMSTAT_PGROTATED, // ul_int "
+ VMSTAT_PGSCAN_ANON, // ul_int "
+ VMSTAT_PGSCAN_DIRECT, // ul_int "
+ VMSTAT_PGSCAN_DIRECT_THROTTLE, // ul_int "
+ VMSTAT_PGSCAN_FILE, // ul_int "
+ VMSTAT_PGSCAN_KSWAPD, // ul_int "
+ VMSTAT_PGSKIP_DMA, // ul_int "
+ VMSTAT_PGSKIP_DMA32, // ul_int "
+ VMSTAT_PGSKIP_HIGH, // ul_int "
+ VMSTAT_PGSKIP_MOVABLE, // ul_int "
+ VMSTAT_PGSKIP_NORMAL, // ul_int "
+ VMSTAT_PGSTEAL_ANON, // ul_int "
+ VMSTAT_PGSTEAL_DIRECT, // ul_int "
+ VMSTAT_PGSTEAL_FILE, // ul_int "
+ VMSTAT_PGSTEAL_KSWAPD, // ul_int "
+ VMSTAT_PSWPIN, // ul_int "
+ VMSTAT_PSWPOUT, // ul_int "
+ VMSTAT_SLABS_SCANNED, // ul_int "
+ VMSTAT_SWAP_RA, // ul_int "
+ VMSTAT_SWAP_RA_HIT, // ul_int "
+ VMSTAT_THP_COLLAPSE_ALLOC, // ul_int "
+ VMSTAT_THP_COLLAPSE_ALLOC_FAILED, // ul_int "
+ VMSTAT_THP_DEFERRED_SPLIT_PAGE, // ul_int "
+ VMSTAT_THP_FAULT_ALLOC, // ul_int "
+ VMSTAT_THP_FAULT_FALLBACK, // ul_int "
+ VMSTAT_THP_FAULT_FALLBACK_CHARGE, // ul_int "
+ VMSTAT_THP_FILE_ALLOC, // ul_int "
+ VMSTAT_THP_FILE_FALLBACK, // ul_int "
+ VMSTAT_THP_FILE_FALLBACK_CHARGE, // ul_int "
+ VMSTAT_THP_FILE_MAPPED, // ul_int "
+ VMSTAT_THP_SPLIT_PAGE, // ul_int "
+ VMSTAT_THP_SPLIT_PAGE_FAILED, // ul_int "
+ VMSTAT_THP_SPLIT_PMD, // ul_int "
+ VMSTAT_THP_SPLIT_PUD, // ul_int "
+ VMSTAT_THP_SWPOUT, // ul_int "
+ VMSTAT_THP_SWPOUT_FALLBACK, // ul_int "
+ VMSTAT_THP_ZERO_PAGE_ALLOC, // ul_int "
+ VMSTAT_THP_ZERO_PAGE_ALLOC_FAILED, // ul_int "
+ VMSTAT_UNEVICTABLE_PGS_CLEARED, // ul_int "
+ VMSTAT_UNEVICTABLE_PGS_CULLED, // ul_int "
+ VMSTAT_UNEVICTABLE_PGS_MLOCKED, // ul_int "
+ VMSTAT_UNEVICTABLE_PGS_MUNLOCKED, // ul_int "
+ VMSTAT_UNEVICTABLE_PGS_RESCUED, // ul_int "
+ VMSTAT_UNEVICTABLE_PGS_SCANNED, // ul_int "
+ VMSTAT_UNEVICTABLE_PGS_STRANDED, // ul_int "
+ VMSTAT_WORKINGSET_ACTIVATE, // ul_int "
+ VMSTAT_WORKINGSET_NODERECLAIM, // ul_int "
+ VMSTAT_WORKINGSET_NODES, // ul_int "
+ VMSTAT_WORKINGSET_REFAULT, // ul_int "
+ VMSTAT_WORKINGSET_RESTORE, // ul_int "
+ VMSTAT_ZONE_RECLAIM_FAILED, // ul_int "
+
+ VMSTAT_DELTA_ALLOCSTALL_DMA, // sl_int derived from above
+ VMSTAT_DELTA_ALLOCSTALL_DMA32, // sl_int "
+ VMSTAT_DELTA_ALLOCSTALL_HIGH, // sl_int "
+ VMSTAT_DELTA_ALLOCSTALL_MOVABLE, // sl_int "
+ VMSTAT_DELTA_ALLOCSTALL_NORMAL, // sl_int "
+ VMSTAT_DELTA_BALLOON_DEFLATE, // sl_int "
+ VMSTAT_DELTA_BALLOON_INFLATE, // sl_int "
+ VMSTAT_DELTA_BALLOON_MIGRATE, // sl_int "
+ VMSTAT_DELTA_COMPACT_DAEMON_FREE_SCANNED, // sl_int "
+ VMSTAT_DELTA_COMPACT_DAEMON_MIGRATE_SCANNED, // sl_int "
+ VMSTAT_DELTA_COMPACT_DAEMON_WAKE, // sl_int "
+ VMSTAT_DELTA_COMPACT_FAIL, // sl_int "
+ VMSTAT_DELTA_COMPACT_FREE_SCANNED, // sl_int "
+ VMSTAT_DELTA_COMPACT_ISOLATED, // sl_int "
+ VMSTAT_DELTA_COMPACT_MIGRATE_SCANNED, // sl_int "
+ VMSTAT_DELTA_COMPACT_STALL, // sl_int "
+ VMSTAT_DELTA_COMPACT_SUCCESS, // sl_int "
+ VMSTAT_DELTA_DROP_PAGECACHE, // sl_int "
+ VMSTAT_DELTA_DROP_SLAB, // sl_int "
+ VMSTAT_DELTA_HTLB_BUDDY_ALLOC_FAIL, // sl_int "
+ VMSTAT_DELTA_HTLB_BUDDY_ALLOC_SUCCESS, // sl_int "
+ VMSTAT_DELTA_KSWAPD_HIGH_WMARK_HIT_QUICKLY, // sl_int "
+ VMSTAT_DELTA_KSWAPD_INODESTEAL, // sl_int "
+ VMSTAT_DELTA_KSWAPD_LOW_WMARK_HIT_QUICKLY, // sl_int "
+ VMSTAT_DELTA_NR_ACTIVE_ANON, // sl_int "
+ VMSTAT_DELTA_NR_ACTIVE_FILE, // sl_int "
+ VMSTAT_DELTA_NR_ANON_PAGES, // sl_int "
+ VMSTAT_DELTA_NR_ANON_TRANSPARENT_HUGEPAGES, // sl_int "
+ VMSTAT_DELTA_NR_BOUNCE, // sl_int "
+ VMSTAT_DELTA_NR_DIRTIED, // sl_int "
+ VMSTAT_DELTA_NR_DIRTY, // sl_int "
+ VMSTAT_DELTA_NR_DIRTY_BACKGROUND_THRESHOLD, // sl_int "
+ VMSTAT_DELTA_NR_DIRTY_THRESHOLD, // sl_int "
+ VMSTAT_DELTA_NR_FILE_HUGEPAGES, // sl_int "
+ VMSTAT_DELTA_NR_FILE_PAGES, // sl_int "
+ VMSTAT_DELTA_NR_FILE_PMDMAPPED, // sl_int "
+ VMSTAT_DELTA_NR_FOLL_PIN_ACQUIRED, // sl_int "
+ VMSTAT_DELTA_NR_FOLL_PIN_RELEASED, // sl_int "
+ VMSTAT_DELTA_NR_FREE_CMA, // sl_int "
+ VMSTAT_DELTA_NR_FREE_PAGES, // sl_int "
+ VMSTAT_DELTA_NR_INACTIVE_ANON, // sl_int "
+ VMSTAT_DELTA_NR_INACTIVE_FILE, // sl_int "
+ VMSTAT_DELTA_NR_ISOLATED_ANON, // sl_int "
+ VMSTAT_DELTA_NR_ISOLATED_FILE, // sl_int "
+ VMSTAT_DELTA_NR_KERNEL_MISC_RECLAIMABLE, // sl_int "
+ VMSTAT_DELTA_NR_KERNEL_STACK, // sl_int "
+ VMSTAT_DELTA_NR_MAPPED, // sl_int "
+ VMSTAT_DELTA_NR_MLOCK, // sl_int "
+ VMSTAT_DELTA_NR_PAGE_TABLE_PAGES, // sl_int "
+ VMSTAT_DELTA_NR_SHADOW_CALL_STACK, // sl_int "
+ VMSTAT_DELTA_NR_SHMEM, // sl_int "
+ VMSTAT_DELTA_NR_SHMEM_HUGEPAGES, // sl_int "
+ VMSTAT_DELTA_NR_SHMEM_PMDMAPPED, // sl_int "
+ VMSTAT_DELTA_NR_SLAB_RECLAIMABLE, // sl_int "
+ VMSTAT_DELTA_NR_SLAB_UNRECLAIMABLE, // sl_int "
+ VMSTAT_DELTA_NR_UNEVICTABLE, // sl_int "
+ VMSTAT_DELTA_NR_UNSTABLE, // sl_int "
+ VMSTAT_DELTA_NR_VMSCAN_IMMEDIATE_RECLAIM, // sl_int "
+ VMSTAT_DELTA_NR_VMSCAN_WRITE, // sl_int "
+ VMSTAT_DELTA_NR_WRITEBACK, // sl_int "
+ VMSTAT_DELTA_NR_WRITEBACK_TEMP, // sl_int "
+ VMSTAT_DELTA_NR_WRITTEN, // sl_int "
+ VMSTAT_DELTA_NR_ZONE_ACTIVE_ANON, // sl_int "
+ VMSTAT_DELTA_NR_ZONE_ACTIVE_FILE, // sl_int "
+ VMSTAT_DELTA_NR_ZONE_INACTIVE_ANON, // sl_int "
+ VMSTAT_DELTA_NR_ZONE_INACTIVE_FILE, // sl_int "
+ VMSTAT_DELTA_NR_ZONE_UNEVICTABLE, // sl_int "
+ VMSTAT_DELTA_NR_ZONE_WRITE_PENDING, // sl_int "
+ VMSTAT_DELTA_NR_ZSPAGES, // sl_int "
+ VMSTAT_DELTA_NUMA_FOREIGN, // sl_int "
+ VMSTAT_DELTA_NUMA_HINT_FAULTS, // sl_int "
+ VMSTAT_DELTA_NUMA_HINT_FAULTS_LOCAL, // sl_int "
+ VMSTAT_DELTA_NUMA_HIT, // sl_int "
+ VMSTAT_DELTA_NUMA_HUGE_PTE_UPDATES, // sl_int "
+ VMSTAT_DELTA_NUMA_INTERLEAVE, // sl_int "
+ VMSTAT_DELTA_NUMA_LOCAL, // sl_int "
+ VMSTAT_DELTA_NUMA_MISS, // sl_int "
+ VMSTAT_DELTA_NUMA_OTHER, // sl_int "
+ VMSTAT_DELTA_NUMA_PAGES_MIGRATED, // sl_int "
+ VMSTAT_DELTA_NUMA_PTE_UPDATES, // sl_int "
+ VMSTAT_DELTA_OOM_KILL, // sl_int "
+ VMSTAT_DELTA_PAGEOUTRUN, // sl_int "
+ VMSTAT_DELTA_PGACTIVATE, // sl_int "
+ VMSTAT_DELTA_PGALLOC_DMA, // sl_int "
+ VMSTAT_DELTA_PGALLOC_DMA32, // sl_int "
+ VMSTAT_DELTA_PGALLOC_HIGH, // sl_int "
+ VMSTAT_DELTA_PGALLOC_MOVABLE, // sl_int "
+ VMSTAT_DELTA_PGALLOC_NORMAL, // sl_int "
+ VMSTAT_DELTA_PGDEACTIVATE, // sl_int "
+ VMSTAT_DELTA_PGFAULT, // sl_int "
+ VMSTAT_DELTA_PGFREE, // sl_int "
+ VMSTAT_DELTA_PGINODESTEAL, // sl_int "
+ VMSTAT_DELTA_PGLAZYFREE, // sl_int "
+ VMSTAT_DELTA_PGLAZYFREED, // sl_int "
+ VMSTAT_DELTA_PGMAJFAULT, // sl_int "
+ VMSTAT_DELTA_PGMIGRATE_FAIL, // sl_int "
+ VMSTAT_DELTA_PGMIGRATE_SUCCESS, // sl_int "
+ VMSTAT_DELTA_PGPGIN, // sl_int "
+ VMSTAT_DELTA_PGPGOUT, // sl_int "
+ VMSTAT_DELTA_PGREFILL, // sl_int "
+ VMSTAT_DELTA_PGROTATED, // sl_int "
+ VMSTAT_DELTA_PGSCAN_ANON, // sl_int "
+ VMSTAT_DELTA_PGSCAN_DIRECT, // sl_int "
+ VMSTAT_DELTA_PGSCAN_DIRECT_THROTTLE, // sl_int "
+ VMSTAT_DELTA_PGSCAN_FILE, // sl_int "
+ VMSTAT_DELTA_PGSCAN_KSWAPD, // sl_int "
+ VMSTAT_DELTA_PGSKIP_DMA, // sl_int "
+ VMSTAT_DELTA_PGSKIP_DMA32, // sl_int "
+ VMSTAT_DELTA_PGSKIP_HIGH, // sl_int "
+ VMSTAT_DELTA_PGSKIP_MOVABLE, // sl_int "
+ VMSTAT_DELTA_PGSKIP_NORMAL, // sl_int "
+ VMSTAT_DELTA_PGSTEAL_ANON, // sl_int "
+ VMSTAT_DELTA_PGSTEAL_DIRECT, // sl_int "
+ VMSTAT_DELTA_PGSTEAL_FILE, // sl_int "
+ VMSTAT_DELTA_PGSTEAL_KSWAPD, // sl_int "
+ VMSTAT_DELTA_PSWPIN, // sl_int "
+ VMSTAT_DELTA_PSWPOUT, // sl_int "
+ VMSTAT_DELTA_SLABS_SCANNED, // sl_int "
+ VMSTAT_DELTA_SWAP_RA, // sl_int "
+ VMSTAT_DELTA_SWAP_RA_HIT, // sl_int "
+ VMSTAT_DELTA_THP_COLLAPSE_ALLOC, // sl_int "
+ VMSTAT_DELTA_THP_COLLAPSE_ALLOC_FAILED, // sl_int "
+ VMSTAT_DELTA_THP_DEFERRED_SPLIT_PAGE, // sl_int "
+ VMSTAT_DELTA_THP_FAULT_ALLOC, // sl_int "
+ VMSTAT_DELTA_THP_FAULT_FALLBACK, // sl_int "
+ VMSTAT_DELTA_THP_FAULT_FALLBACK_CHARGE, // sl_int "
+ VMSTAT_DELTA_THP_FILE_ALLOC, // sl_int "
+ VMSTAT_DELTA_THP_FILE_FALLBACK, // sl_int "
+ VMSTAT_DELTA_THP_FILE_FALLBACK_CHARGE, // sl_int "
+ VMSTAT_DELTA_THP_FILE_MAPPED, // sl_int "
+ VMSTAT_DELTA_THP_SPLIT_PAGE, // sl_int "
+ VMSTAT_DELTA_THP_SPLIT_PAGE_FAILED, // sl_int "
+ VMSTAT_DELTA_THP_SPLIT_PMD, // sl_int "
+ VMSTAT_DELTA_THP_SPLIT_PUD, // sl_int "
+ VMSTAT_DELTA_THP_SWPOUT, // sl_int "
+ VMSTAT_DELTA_THP_SWPOUT_FALLBACK, // sl_int "
+ VMSTAT_DELTA_THP_ZERO_PAGE_ALLOC, // sl_int "
+ VMSTAT_DELTA_THP_ZERO_PAGE_ALLOC_FAILED, // sl_int "
+ VMSTAT_DELTA_UNEVICTABLE_PGS_CLEARED, // sl_int "
+ VMSTAT_DELTA_UNEVICTABLE_PGS_CULLED, // sl_int "
+ VMSTAT_DELTA_UNEVICTABLE_PGS_MLOCKED, // sl_int "
+ VMSTAT_DELTA_UNEVICTABLE_PGS_MUNLOCKED, // sl_int "
+ VMSTAT_DELTA_UNEVICTABLE_PGS_RESCUED, // sl_int "
+ VMSTAT_DELTA_UNEVICTABLE_PGS_SCANNED, // sl_int "
+ VMSTAT_DELTA_UNEVICTABLE_PGS_STRANDED, // sl_int "
+ VMSTAT_DELTA_WORKINGSET_ACTIVATE, // sl_int "
+ VMSTAT_DELTA_WORKINGSET_NODERECLAIM, // sl_int "
+ VMSTAT_DELTA_WORKINGSET_NODES, // sl_int "
+ VMSTAT_DELTA_WORKINGSET_REFAULT, // sl_int "
+ VMSTAT_DELTA_WORKINGSET_RESTORE, // sl_int "
+ VMSTAT_DELTA_ZONE_RECLAIM_FAILED // sl_int "
+};
+
+
+struct vmstat_result {
+ enum vmstat_item item;
+ union {
+ signed long sl_int;
+ unsigned long ul_int;
+ } result;
+};
+
+struct vmstat_stack {
+ struct vmstat_result *head;
+};
+
+struct vmstat_info;
+
+
+#define VMSTAT_GET( info, actual_enum, type ) ( { \
+ struct vmstat_result *r = procps_vmstat_get( info, actual_enum ); \
+ r ? r->result . type : 0; } )
+
+#define VMSTAT_VAL( relative_enum, type, stack, info ) \
+ stack -> head [ relative_enum ] . result . type
+
+
+int procps_vmstat_new (struct vmstat_info **info);
+int procps_vmstat_ref (struct vmstat_info *info);
+int procps_vmstat_unref (struct vmstat_info **info);
+
+struct vmstat_result *procps_vmstat_get (
+ struct vmstat_info *info,
+ enum vmstat_item item);
+
+struct vmstat_stack *procps_vmstat_select (
+ struct vmstat_info *info,
+ enum vmstat_item *items,
+ int numitems);
+
+
+#ifdef XTRA_PROCPS_DEBUG
+# include "xtra-procps-debug.h"
+#endif
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/library/include/wchan.h b/library/include/wchan.h
new file mode 100644
index 0000000..ef1d689
--- /dev/null
+++ b/library/include/wchan.h
@@ -0,0 +1,27 @@
+/*
+ * wchan.c - kernel symbol handling
+ *
+ * Copyright © 2015-2023 Jim Warner <james.warner@comcast.net>
+ * Copyright © 1998-2003 Albert Cahalan
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PROCPS_PROC_WCHAN_H
+#define PROCPS_PROC_WCHAN_H
+
+extern const char *lookup_wchan (int pid);
+
+#endif
diff --git a/library/include/xtra-procps-debug.h b/library/include/xtra-procps-debug.h
new file mode 100644
index 0000000..ead8075
--- /dev/null
+++ b/library/include/xtra-procps-debug.h
@@ -0,0 +1,208 @@
+/*
+ * libproc2 - Library to read proc filesystem
+ *
+ * Copyright © 2016-2023 Jim Warner <james.warner@comcast.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#define STRINGIFY_ARG(a) #a
+#define STRINGIFY(a) STRINGIFY_ARG(a)
+
+
+// --- DISKSTATS ------------------------------------------
+#if defined(PROCPS_DISKSTATS_H) && !defined(PROCPS_DISKSTATS_H_DEBUG)
+#define PROCPS_DISKSTATS_H_DEBUG
+
+struct diskstats_result *xtra_diskstats_get (
+ struct diskstats_info *info,
+ const char *name,
+ enum diskstats_item actual_enum,
+ const char *typestr,
+ const char *file,
+ int lineno);
+
+# undef DISKSTATS_GET
+#define DISKSTATS_GET( info, name, actual_enum, type ) ( { \
+ struct diskstats_result *r; \
+ r = xtra_diskstats_get(info, name, actual_enum , STRINGIFY(type), __FILE__, __LINE__); \
+ r ? r->result . type : 0; } )
+
+struct diskstats_result *xtra_diskstats_val (
+ int relative_enum,
+ const char *typestr,
+ const struct diskstats_stack *stack,
+ struct diskstats_info *info,
+ const char *file,
+ int lineno);
+
+# undef DISKSTATS_VAL
+#define DISKSTATS_VAL( relative_enum, type, stack, info ) ( { \
+ struct diskstats_result *r; \
+ r = xtra_diskstats_val(relative_enum, STRINGIFY(type), stack, info, __FILE__, __LINE__); \
+ r ? r->result . type : 0; } )
+#endif // . . . . . . . . . .
+
+
+// --- MEMINFO --------------------------------------------
+#if defined(PROCPS_MEMINFO_H) && !defined(PROCPS_MEMINFO_H_DEBUG)
+#define PROCPS_MEMINFO_H_DEBUG
+
+struct meminfo_result *xtra_meminfo_get (
+ struct meminfo_info *info,
+ enum meminfo_item actual_enum,
+ const char *typestr,
+ const char *file,
+ int lineno);
+
+# undef MEMINFO_GET
+#define MEMINFO_GET( info, actual_enum, type ) ( { \
+ struct meminfo_result *r; \
+ r = xtra_meminfo_get(info, actual_enum , STRINGIFY(type), __FILE__, __LINE__); \
+ r ? r->result . type : 0; } )
+
+struct meminfo_result *xtra_meminfo_val (
+ int relative_enum,
+ const char *typestr,
+ const struct meminfo_stack *stack,
+ struct meminfo_info *info,
+ const char *file,
+ int lineno);
+
+# undef MEMINFO_VAL
+#define MEMINFO_VAL( relative_enum, type, stack, info ) ( { \
+ struct meminfo_result *r; \
+ r = xtra_meminfo_val(relative_enum, STRINGIFY(type), stack, info, __FILE__, __LINE__); \
+ r ? r->result . type : 0; } )
+#endif // . . . . . . . . . .
+
+
+// --- PIDS -----------------------------------------------
+#if defined(PROCPS_PIDS_H) && !defined(PROCPS_PIDS_H_DEBUG)
+#define PROCPS_PIDS_H_DEBUG
+
+struct pids_result *xtra_pids_val (
+ int relative_enum,
+ const char *typestr,
+ const struct pids_stack *stack,
+ struct pids_info *info,
+ const char *file,
+ int lineno);
+
+# undef PIDS_VAL
+#define PIDS_VAL( relative_enum, type, stack, info ) ( { \
+ struct pids_result *r; \
+ r = xtra_pids_val(relative_enum, STRINGIFY(type), stack, info, __FILE__, __LINE__); \
+ r ? r->result . type : 0; } )
+#endif // . . . . . . . . . .
+
+
+// --- SLABINFO -------------------------------------------
+#if defined(PROCPS_SLABINFO_H) && !defined(PROCPS_SLABINFO_H_DEBUG)
+#define PROCPS_SLABINFO_H_DEBUG
+
+struct slabinfo_result *xtra_slabinfo_get (
+ struct slabinfo_info *info,
+ enum slabinfo_item actual_enum,
+ const char *typestr,
+ const char *file,
+ int lineno);
+
+# undef SLABINFO_GET
+#define SLABINFO_GET( info, actual_enum, type ) ( { \
+ struct slabinfo_result *r; \
+ r = xtra_slabinfo_get(info, actual_enum , STRINGIFY(type), __FILE__, __LINE__); \
+ r ? r->result . type : 0; } )
+
+struct slabinfo_result *xtra_slabinfo_val (
+ int relative_enum,
+ const char *typestr,
+ const struct slabinfo_stack *stack,
+ struct slabinfo_info *info,
+ const char *file,
+ int lineno);
+
+# undef SLABINFO_VAL
+#define SLABINFO_VAL( relative_enum, type, stack, info ) ( { \
+ struct slabinfo_result *r; \
+ r = xtra_slabinfo_val(relative_enum, STRINGIFY(type), stack, info, __FILE__, __LINE__); \
+ r ? r->result . type : 0; } )
+#endif // . . . . . . . . . .
+
+
+// --- STAT -----------------------------------------------
+#if defined(PROCPS_STAT_H) && !defined(PROCPS_STAT_H_DEBUG)
+#define PROCPS_STAT_H_DEBUG
+
+struct stat_result *xtra_stat_get (
+ struct stat_info *info,
+ enum stat_item actual_enum,
+ const char *typestr,
+ const char *file,
+ int lineno);
+
+# undef STAT_GET
+#define STAT_GET( info, actual_enum, type ) ( { \
+ struct stat_result *r; \
+ r = xtra_stat_get(info, actual_enum , STRINGIFY(type), __FILE__, __LINE__); \
+ r ? r->result . type : 0; } )
+
+struct stat_result *xtra_stat_val (
+ int relative_enum,
+ const char *typestr,
+ const struct stat_stack *stack,
+ struct stat_info *info,
+ const char *file,
+ int lineno);
+
+# undef STAT_VAL
+#define STAT_VAL( relative_enum, type, stack, info ) ( { \
+ struct stat_result *r; \
+ r = xtra_stat_val(relative_enum, STRINGIFY(type), stack, info, __FILE__, __LINE__); \
+ r ? r->result . type : 0; } )
+#endif // . . . . . . . . . .
+
+
+// --- VMSTAT ---------------------------------------------
+#if defined(PROCPS_VMSTAT_H) && !defined(PROCPS_VMSTAT_H_DEBUG)
+#define PROCPS_VMSTAT_H_DEBUG
+
+struct vmstat_result *xtra_vmstat_get (
+ struct vmstat_info *info,
+ enum vmstat_item actual_enum,
+ const char *typestr,
+ const char *file,
+ int lineno);
+
+# undef VMSTAT_GET
+#define VMSTAT_GET( info, actual_enum, type ) ( { \
+ struct vmstat_result *r; \
+ r = xtra_vmstat_get(info, actual_enum , STRINGIFY(type), __FILE__, __LINE__); \
+ r ? r->result . type : 0; } )
+
+struct vmstat_result *xtra_vmstat_val (
+ int relative_enum,
+ const char *typestr,
+ const struct vmstat_stack *stack,
+ struct vmstat_info *info,
+ const char *file,
+ int lineno);
+
+# undef VMSTAT_VAL
+#define VMSTAT_VAL( relative_enum, type, stack, info ) ( { \
+ struct vmstat_result *r; \
+ r = xtra_vmstat_val(relative_enum, STRINGIFY(type), stack, info, __FILE__, __LINE__); \
+ r ? r->result . type : 0; } )
+#endif // . . . . . . . . . .
diff --git a/library/libproc2.pc.in b/library/libproc2.pc.in
new file mode 100644
index 0000000..99fd97e
--- /dev/null
+++ b/library/libproc2.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libproc2
+Description: Library to control and query process state
+Version: @VERSION@
+Libs: -L${libdir} -lproc2
+Libs.private:
+Cflags: -I${includedir}
diff --git a/library/libproc2.sym b/library/libproc2.sym
new file mode 100644
index 0000000..bb84ba9
--- /dev/null
+++ b/library/libproc2.sym
@@ -0,0 +1,67 @@
+LIBPROC_2 {
+global:
+ fatal_proc_unmounted;
+ procps_cpu_count;
+ procps_diskstats_new;
+ procps_diskstats_ref;
+ procps_diskstats_unref;
+ procps_diskstats_get;
+ procps_diskstats_reap;
+ procps_diskstats_select;
+ procps_diskstats_sort;
+ procps_hertz_get;
+ procps_linux_version;
+ procps_loadavg;
+ procps_meminfo_new;
+ procps_meminfo_ref;
+ procps_meminfo_unref;
+ procps_meminfo_get;
+ procps_meminfo_select;
+ procps_ns_get_name;
+ procps_ns_get_id;
+ procps_ns_read_pid;
+ procps_pid_length;
+ procps_pids_new;
+ procps_pids_ref;
+ procps_pids_unref;
+ procps_pids_get;
+ procps_pids_reap;
+ procps_pids_reset;
+ procps_pids_select;
+ procps_pids_sort;
+ procps_slabinfo_new;
+ procps_slabinfo_ref;
+ procps_slabinfo_unref;
+ procps_slabinfo_get;
+ procps_slabinfo_reap;
+ procps_slabinfo_select;
+ procps_slabinfo_sort;
+ procps_stat_new;
+ procps_stat_ref;
+ procps_stat_unref;
+ procps_stat_get;
+ procps_stat_reap;
+ procps_stat_select;
+ procps_stat_sort;
+ procps_uptime;
+ procps_uptime_sprint;
+ procps_uptime_sprint_short;
+ procps_vmstat_new;
+ procps_vmstat_ref;
+ procps_vmstat_unref;
+ procps_vmstat_get;
+ procps_vmstat_select;
+ xtra_diskstats_get;
+ xtra_diskstats_val;
+ xtra_meminfo_get;
+ xtra_meminfo_val;
+ xtra_pids_val;
+ xtra_slabinfo_get;
+ xtra_slabinfo_val;
+ xtra_stat_get;
+ xtra_stat_val;
+ xtra_vmstat_get;
+ xtra_vmstat_val;
+local:
+ *;
+};
diff --git a/library/meminfo.c b/library/meminfo.c
new file mode 100644
index 0000000..4289a23
--- /dev/null
+++ b/library/meminfo.c
@@ -0,0 +1,1016 @@
+/*
+ * meminfo.c - memory related definitions for libproc2
+ *
+ * Copyright © 2015-2023 Jim Warner <james.warner@comcast.net>
+ * Copyright © 2015-2023 Craig Small <csmall@dropbear.xyz>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <search.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "procps-private.h"
+#include "meminfo.h"
+
+
+#define MEMINFO_FILE "/proc/meminfo"
+#define MEMINFO_BUFF 8192
+
+/* ------------------------------------------------------------------------- +
+ this provision can be used to ensure that our Item_table was synchronized |
+ with those enumerators found in the associated header file. It's intended |
+ to only be used locally (& temporarily) at some point prior to a release! | */
+// #define ITEMTABLE_DEBUG //----------------------------------------------- |
+// ------------------------------------------------------------------------- +
+
+
+struct meminfo_data {
+ unsigned long Active;
+ unsigned long Active_anon; // as: Active(anon): man 5 proc: 'to be documented'
+ unsigned long Active_file; // as: Active(file): man 5 proc: 'to be documented'
+ unsigned long AnonHugePages;
+ unsigned long AnonPages;
+ unsigned long Bounce;
+ unsigned long Buffers;
+ unsigned long Cached;
+ unsigned long CmaFree;
+ unsigned long CmaTotal;
+ unsigned long CommitLimit;
+ unsigned long Committed_AS;
+ unsigned long DirectMap1G;
+ unsigned long DirectMap2M;
+ unsigned long DirectMap4M;
+ unsigned long DirectMap4k;
+ unsigned long Dirty;
+ unsigned long FileHugePages;
+ unsigned long FilePmdMapped;
+ unsigned long HardwareCorrupted; // man 5 proc: 'to be documented'
+ unsigned long HighFree;
+ unsigned long HighTotal;
+ unsigned long HugePages_Free;
+ unsigned long HugePages_Rsvd;
+ unsigned long HugePages_Surp;
+ unsigned long HugePages_Total;
+ unsigned long Hugepagesize;
+ unsigned long Hugetlb;
+ unsigned long Inactive;
+ unsigned long Inactive_anon; // as: Inactive(anon): man 5 proc: 'to be documented'
+ unsigned long Inactive_file; // as: Inactive(file): man 5 proc: 'to be documented'
+ unsigned long KReclaimable;
+ unsigned long KernelStack;
+ unsigned long LowFree;
+ unsigned long LowTotal;
+ unsigned long Mapped;
+ unsigned long MemAvailable;
+ unsigned long MemFree;
+ unsigned long MemTotal;
+ unsigned long Mlocked; // man 5 proc: 'to be documented'
+ unsigned long MmapCopy; // man 5 proc: 'to be documented'
+ unsigned long NFS_Unstable;
+ unsigned long PageTables;
+ unsigned long Percpu;
+ unsigned long SReclaimable;
+ unsigned long SUnreclaim;
+ unsigned long ShadowCallStack;
+ unsigned long Shmem;
+ unsigned long ShmemHugePages;
+ unsigned long ShmemPmdMapped;
+ unsigned long Slab;
+ unsigned long SwapCached;
+ unsigned long SwapFree;
+ unsigned long SwapTotal;
+ unsigned long Unevictable; // man 5 proc: 'to be documented'
+ unsigned long VmallocChunk;
+ unsigned long VmallocTotal;
+ unsigned long VmallocUsed;
+ unsigned long Writeback;
+ unsigned long WritebackTmp;
+
+ unsigned long derived_mem_cached;
+ unsigned long derived_mem_hi_used;
+ unsigned long derived_mem_lo_used;
+ unsigned long derived_mem_used;
+ unsigned long derived_swap_used;
+};
+
+struct mem_hist {
+ struct meminfo_data new;
+ struct meminfo_data old;
+};
+
+struct stacks_extent {
+ int ext_numstacks;
+ struct stacks_extent *next;
+ struct meminfo_stack **stacks;
+};
+
+struct meminfo_info {
+ int refcount;
+ int meminfo_fd;
+ struct mem_hist hist;
+ int numitems;
+ enum meminfo_item *items;
+ struct stacks_extent *extents;
+ struct hsearch_data hashtab;
+ struct meminfo_result get_this;
+ time_t sav_secs;
+};
+
+
+// ___ Results 'Set' Support ||||||||||||||||||||||||||||||||||||||||||||||||||
+
+#define setNAME(e) set_meminfo_ ## e
+#define setDECL(e) static void setNAME(e) \
+ (struct meminfo_result *R, struct mem_hist *H)
+
+// regular assignment
+#define MEM_set(e,t,x) setDECL(e) { R->result. t = H->new. x; }
+// delta assignment
+#define HST_set(e,t,x) setDECL(e) { R->result. t = ( H->new. x - H->old. x ); }
+
+setDECL(noop) { (void)R; (void)H; }
+setDECL(extra) { (void)H; R->result.ul_int = 0; }
+
+MEM_set(MEM_ACTIVE, ul_int, Active)
+MEM_set(MEM_ACTIVE_ANON, ul_int, Active_anon)
+MEM_set(MEM_ACTIVE_FILE, ul_int, Active_file)
+MEM_set(MEM_ANON, ul_int, AnonPages)
+MEM_set(MEM_AVAILABLE, ul_int, MemAvailable)
+MEM_set(MEM_BOUNCE, ul_int, Bounce)
+MEM_set(MEM_BUFFERS, ul_int, Buffers)
+MEM_set(MEM_CACHED, ul_int, Cached)
+MEM_set(MEM_CACHED_ALL, ul_int, derived_mem_cached)
+MEM_set(MEM_CMA_FREE, ul_int, CmaFree)
+MEM_set(MEM_CMA_TOTAL, ul_int, CmaTotal)
+MEM_set(MEM_COMMITTED_AS, ul_int, Committed_AS)
+MEM_set(MEM_COMMIT_LIMIT, ul_int, CommitLimit)
+MEM_set(MEM_DIRECTMAP_1G, ul_int, DirectMap1G)
+MEM_set(MEM_DIRECTMAP_2M, ul_int, DirectMap2M)
+MEM_set(MEM_DIRECTMAP_4K, ul_int, DirectMap4k)
+MEM_set(MEM_DIRECTMAP_4M, ul_int, DirectMap4M)
+MEM_set(MEM_DIRTY, ul_int, Dirty)
+MEM_set(MEM_FILE_HUGEPAGES, ul_int, FileHugePages)
+MEM_set(MEM_FILE_PMDMAPPED, ul_int, FilePmdMapped)
+MEM_set(MEM_FREE, ul_int, MemFree)
+MEM_set(MEM_HARD_CORRUPTED, ul_int, HardwareCorrupted)
+MEM_set(MEM_HIGH_FREE, ul_int, HighFree)
+MEM_set(MEM_HIGH_TOTAL, ul_int, HighTotal)
+MEM_set(MEM_HIGH_USED, ul_int, derived_mem_hi_used)
+MEM_set(MEM_HUGETBL, ul_int, Hugetlb)
+MEM_set(MEM_HUGE_ANON, ul_int, AnonHugePages)
+MEM_set(MEM_HUGE_FREE, ul_int, HugePages_Free)
+MEM_set(MEM_HUGE_RSVD, ul_int, HugePages_Rsvd)
+MEM_set(MEM_HUGE_SIZE, ul_int, Hugepagesize)
+MEM_set(MEM_HUGE_SURPLUS, ul_int, HugePages_Surp)
+MEM_set(MEM_HUGE_TOTAL, ul_int, HugePages_Total)
+MEM_set(MEM_INACTIVE, ul_int, Inactive)
+MEM_set(MEM_INACTIVE_ANON, ul_int, Inactive_anon)
+MEM_set(MEM_INACTIVE_FILE, ul_int, Inactive_file)
+MEM_set(MEM_KERNEL_RECLAIM, ul_int, KReclaimable)
+MEM_set(MEM_KERNEL_STACK, ul_int, KernelStack)
+MEM_set(MEM_LOCKED, ul_int, Mlocked)
+MEM_set(MEM_LOW_FREE, ul_int, LowFree)
+MEM_set(MEM_LOW_TOTAL, ul_int, LowTotal)
+MEM_set(MEM_LOW_USED, ul_int, derived_mem_lo_used)
+MEM_set(MEM_MAPPED, ul_int, Mapped)
+MEM_set(MEM_MAP_COPY, ul_int, MmapCopy)
+MEM_set(MEM_NFS_UNSTABLE, ul_int, NFS_Unstable)
+MEM_set(MEM_PAGE_TABLES, ul_int, PageTables)
+MEM_set(MEM_PER_CPU, ul_int, Percpu)
+MEM_set(MEM_SHADOWCALLSTACK, ul_int, ShadowCallStack)
+MEM_set(MEM_SHARED, ul_int, Shmem)
+MEM_set(MEM_SHMEM_HUGE, ul_int, ShmemHugePages)
+MEM_set(MEM_SHMEM_HUGE_MAP, ul_int, ShmemPmdMapped)
+MEM_set(MEM_SLAB, ul_int, Slab)
+MEM_set(MEM_SLAB_RECLAIM, ul_int, SReclaimable)
+MEM_set(MEM_SLAB_UNRECLAIM, ul_int, SUnreclaim)
+MEM_set(MEM_TOTAL, ul_int, MemTotal)
+MEM_set(MEM_UNEVICTABLE, ul_int, Unevictable)
+MEM_set(MEM_USED, ul_int, derived_mem_used)
+MEM_set(MEM_VM_ALLOC_CHUNK, ul_int, VmallocChunk)
+MEM_set(MEM_VM_ALLOC_TOTAL, ul_int, VmallocTotal)
+MEM_set(MEM_VM_ALLOC_USED, ul_int, VmallocUsed)
+MEM_set(MEM_WRITEBACK, ul_int, Writeback)
+MEM_set(MEM_WRITEBACK_TMP, ul_int, WritebackTmp)
+
+HST_set(DELTA_ACTIVE, s_int, Active)
+HST_set(DELTA_ACTIVE_ANON, s_int, Active_anon)
+HST_set(DELTA_ACTIVE_FILE, s_int, Active_file)
+HST_set(DELTA_ANON, s_int, AnonPages)
+HST_set(DELTA_AVAILABLE, s_int, MemAvailable)
+HST_set(DELTA_BOUNCE, s_int, Bounce)
+HST_set(DELTA_BUFFERS, s_int, Buffers)
+HST_set(DELTA_CACHED, s_int, Cached)
+HST_set(DELTA_CACHED_ALL, s_int, derived_mem_cached)
+HST_set(DELTA_CMA_FREE, s_int, CmaFree)
+HST_set(DELTA_CMA_TOTAL, s_int, CmaTotal)
+HST_set(DELTA_COMMITTED_AS, s_int, Committed_AS)
+HST_set(DELTA_COMMIT_LIMIT, s_int, CommitLimit)
+HST_set(DELTA_DIRECTMAP_1G, s_int, DirectMap1G)
+HST_set(DELTA_DIRECTMAP_2M, s_int, DirectMap2M)
+HST_set(DELTA_DIRECTMAP_4K, s_int, DirectMap4k)
+HST_set(DELTA_DIRECTMAP_4M, s_int, DirectMap4M)
+HST_set(DELTA_DIRTY, s_int, Dirty)
+HST_set(DELTA_FILE_HUGEPAGES, s_int, FileHugePages)
+HST_set(DELTA_FILE_PMDMAPPED, s_int, FilePmdMapped)
+HST_set(DELTA_FREE, s_int, MemFree)
+HST_set(DELTA_HARD_CORRUPTED, s_int, HardwareCorrupted)
+HST_set(DELTA_HIGH_FREE, s_int, HighFree)
+HST_set(DELTA_HIGH_TOTAL, s_int, HighTotal)
+HST_set(DELTA_HIGH_USED, s_int, derived_mem_hi_used)
+HST_set(DELTA_HUGETBL, s_int, Hugetlb)
+HST_set(DELTA_HUGE_ANON, s_int, AnonHugePages)
+HST_set(DELTA_HUGE_FREE, s_int, HugePages_Free)
+HST_set(DELTA_HUGE_RSVD, s_int, HugePages_Rsvd)
+HST_set(DELTA_HUGE_SIZE, s_int, Hugepagesize)
+HST_set(DELTA_HUGE_SURPLUS, s_int, HugePages_Surp)
+HST_set(DELTA_HUGE_TOTAL, s_int, HugePages_Total)
+HST_set(DELTA_INACTIVE, s_int, Inactive)
+HST_set(DELTA_INACTIVE_ANON, s_int, Inactive_anon)
+HST_set(DELTA_INACTIVE_FILE, s_int, Inactive_file)
+HST_set(DELTA_KERNEL_RECLAIM, s_int, KReclaimable)
+HST_set(DELTA_KERNEL_STACK, s_int, KernelStack)
+HST_set(DELTA_LOCKED, s_int, Mlocked)
+HST_set(DELTA_LOW_FREE, s_int, LowFree)
+HST_set(DELTA_LOW_TOTAL, s_int, LowTotal)
+HST_set(DELTA_LOW_USED, s_int, derived_mem_lo_used)
+HST_set(DELTA_MAPPED, s_int, Mapped)
+HST_set(DELTA_MAP_COPY, s_int, MmapCopy)
+HST_set(DELTA_NFS_UNSTABLE, s_int, NFS_Unstable)
+HST_set(DELTA_PAGE_TABLES, s_int, PageTables)
+HST_set(DELTA_PER_CPU, s_int, Percpu)
+HST_set(DELTA_SHADOWCALLSTACK, s_int, ShadowCallStack)
+HST_set(DELTA_SHARED, s_int, Shmem)
+HST_set(DELTA_SHMEM_HUGE, s_int, ShmemHugePages)
+HST_set(DELTA_SHMEM_HUGE_MAP, s_int, ShmemPmdMapped)
+HST_set(DELTA_SLAB, s_int, Slab)
+HST_set(DELTA_SLAB_RECLAIM, s_int, SReclaimable)
+HST_set(DELTA_SLAB_UNRECLAIM, s_int, SUnreclaim)
+HST_set(DELTA_TOTAL, s_int, MemTotal)
+HST_set(DELTA_UNEVICTABLE, s_int, Unevictable)
+HST_set(DELTA_USED, s_int, derived_mem_used)
+HST_set(DELTA_VM_ALLOC_CHUNK, s_int, VmallocChunk)
+HST_set(DELTA_VM_ALLOC_TOTAL, s_int, VmallocTotal)
+HST_set(DELTA_VM_ALLOC_USED, s_int, VmallocUsed)
+HST_set(DELTA_WRITEBACK, s_int, Writeback)
+HST_set(DELTA_WRITEBACK_TMP, s_int, WritebackTmp)
+
+MEM_set(SWAP_CACHED, ul_int, SwapCached)
+MEM_set(SWAP_FREE, ul_int, SwapFree)
+MEM_set(SWAP_TOTAL, ul_int, SwapTotal)
+MEM_set(SWAP_USED, ul_int, derived_swap_used)
+
+HST_set(SWAP_DELTA_CACHED, s_int, SwapCached)
+HST_set(SWAP_DELTA_FREE, s_int, SwapFree)
+HST_set(SWAP_DELTA_TOTAL, s_int, SwapTotal)
+HST_set(SWAP_DELTA_USED, s_int, derived_swap_used)
+
+#undef setDECL
+#undef MEM_set
+#undef HST_set
+
+
+// ___ Controlling Table ||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
+typedef void (*SET_t)(struct meminfo_result *, struct mem_hist *);
+#ifdef ITEMTABLE_DEBUG
+#define RS(e) (SET_t)setNAME(e), MEMINFO_ ## e, STRINGIFY(MEMINFO_ ## e)
+#else
+#define RS(e) (SET_t)setNAME(e)
+#endif
+
+#define TS(t) STRINGIFY(t)
+#define TS_noop ""
+
+ /*
+ * Need it be said?
+ * This table must be kept in the exact same order as
+ * those 'enum meminfo_item' guys ! */
+static struct {
+ SET_t setsfunc; // the actual result setting routine
+#ifdef ITEMTABLE_DEBUG
+ int enumnumb; // enumerator (must match position!)
+ char *enum2str; // enumerator name as a char* string
+#endif
+ char *type2str; // the result type as a string value
+} Item_table[] = {
+/* setsfunc type2str
+ ------------------------- ---------- */
+ { RS(noop), TS_noop },
+ { RS(extra), TS_noop },
+
+ { RS(MEM_ACTIVE), TS(ul_int) },
+ { RS(MEM_ACTIVE_ANON), TS(ul_int) },
+ { RS(MEM_ACTIVE_FILE), TS(ul_int) },
+ { RS(MEM_ANON), TS(ul_int) },
+ { RS(MEM_AVAILABLE), TS(ul_int) },
+ { RS(MEM_BOUNCE), TS(ul_int) },
+ { RS(MEM_BUFFERS), TS(ul_int) },
+ { RS(MEM_CACHED), TS(ul_int) },
+ { RS(MEM_CACHED_ALL), TS(ul_int) },
+ { RS(MEM_CMA_FREE), TS(ul_int) },
+ { RS(MEM_CMA_TOTAL), TS(ul_int) },
+ { RS(MEM_COMMITTED_AS), TS(ul_int) },
+ { RS(MEM_COMMIT_LIMIT), TS(ul_int) },
+ { RS(MEM_DIRECTMAP_1G), TS(ul_int) },
+ { RS(MEM_DIRECTMAP_2M), TS(ul_int) },
+ { RS(MEM_DIRECTMAP_4K), TS(ul_int) },
+ { RS(MEM_DIRECTMAP_4M), TS(ul_int) },
+ { RS(MEM_DIRTY), TS(ul_int) },
+ { RS(MEM_FILE_HUGEPAGES), TS(ul_int) },
+ { RS(MEM_FILE_PMDMAPPED), TS(ul_int) },
+ { RS(MEM_FREE), TS(ul_int) },
+ { RS(MEM_HARD_CORRUPTED), TS(ul_int) },
+ { RS(MEM_HIGH_FREE), TS(ul_int) },
+ { RS(MEM_HIGH_TOTAL), TS(ul_int) },
+ { RS(MEM_HIGH_USED), TS(ul_int) },
+ { RS(MEM_HUGETBL), TS(ul_int) },
+ { RS(MEM_HUGE_ANON), TS(ul_int) },
+ { RS(MEM_HUGE_FREE), TS(ul_int) },
+ { RS(MEM_HUGE_RSVD), TS(ul_int) },
+ { RS(MEM_HUGE_SIZE), TS(ul_int) },
+ { RS(MEM_HUGE_SURPLUS), TS(ul_int) },
+ { RS(MEM_HUGE_TOTAL), TS(ul_int) },
+ { RS(MEM_INACTIVE), TS(ul_int) },
+ { RS(MEM_INACTIVE_ANON), TS(ul_int) },
+ { RS(MEM_INACTIVE_FILE), TS(ul_int) },
+ { RS(MEM_KERNEL_RECLAIM), TS(ul_int) },
+ { RS(MEM_KERNEL_STACK), TS(ul_int) },
+ { RS(MEM_LOCKED), TS(ul_int) },
+ { RS(MEM_LOW_FREE), TS(ul_int) },
+ { RS(MEM_LOW_TOTAL), TS(ul_int) },
+ { RS(MEM_LOW_USED), TS(ul_int) },
+ { RS(MEM_MAPPED), TS(ul_int) },
+ { RS(MEM_MAP_COPY), TS(ul_int) },
+ { RS(MEM_NFS_UNSTABLE), TS(ul_int) },
+ { RS(MEM_PAGE_TABLES), TS(ul_int) },
+ { RS(MEM_PER_CPU), TS(ul_int) },
+ { RS(MEM_SHADOWCALLSTACK), TS(ul_int) },
+ { RS(MEM_SHARED), TS(ul_int) },
+ { RS(MEM_SHMEM_HUGE), TS(ul_int) },
+ { RS(MEM_SHMEM_HUGE_MAP), TS(ul_int) },
+ { RS(MEM_SLAB), TS(ul_int) },
+ { RS(MEM_SLAB_RECLAIM), TS(ul_int) },
+ { RS(MEM_SLAB_UNRECLAIM), TS(ul_int) },
+ { RS(MEM_TOTAL), TS(ul_int) },
+ { RS(MEM_UNEVICTABLE), TS(ul_int) },
+ { RS(MEM_USED), TS(ul_int) },
+ { RS(MEM_VM_ALLOC_CHUNK), TS(ul_int) },
+ { RS(MEM_VM_ALLOC_TOTAL), TS(ul_int) },
+ { RS(MEM_VM_ALLOC_USED), TS(ul_int) },
+ { RS(MEM_WRITEBACK), TS(ul_int) },
+ { RS(MEM_WRITEBACK_TMP), TS(ul_int) },
+
+ { RS(DELTA_ACTIVE), TS(s_int) },
+ { RS(DELTA_ACTIVE_ANON), TS(s_int) },
+ { RS(DELTA_ACTIVE_FILE), TS(s_int) },
+ { RS(DELTA_ANON), TS(s_int) },
+ { RS(DELTA_AVAILABLE), TS(s_int) },
+ { RS(DELTA_BOUNCE), TS(s_int) },
+ { RS(DELTA_BUFFERS), TS(s_int) },
+ { RS(DELTA_CACHED), TS(s_int) },
+ { RS(DELTA_CACHED_ALL), TS(s_int) },
+ { RS(DELTA_CMA_FREE), TS(s_int) },
+ { RS(DELTA_CMA_TOTAL), TS(s_int) },
+ { RS(DELTA_COMMITTED_AS), TS(s_int) },
+ { RS(DELTA_COMMIT_LIMIT), TS(s_int) },
+ { RS(DELTA_DIRECTMAP_1G), TS(s_int) },
+ { RS(DELTA_DIRECTMAP_2M), TS(s_int) },
+ { RS(DELTA_DIRECTMAP_4K), TS(s_int) },
+ { RS(DELTA_DIRECTMAP_4M), TS(s_int) },
+ { RS(DELTA_DIRTY), TS(s_int) },
+ { RS(DELTA_FILE_HUGEPAGES), TS(s_int) },
+ { RS(DELTA_FILE_PMDMAPPED), TS(s_int) },
+ { RS(DELTA_FREE), TS(s_int) },
+ { RS(DELTA_HARD_CORRUPTED), TS(s_int) },
+ { RS(DELTA_HIGH_FREE), TS(s_int) },
+ { RS(DELTA_HIGH_TOTAL), TS(s_int) },
+ { RS(DELTA_HIGH_USED), TS(s_int) },
+ { RS(DELTA_HUGETBL), TS(s_int) },
+ { RS(DELTA_HUGE_ANON), TS(s_int) },
+ { RS(DELTA_HUGE_FREE), TS(s_int) },
+ { RS(DELTA_HUGE_RSVD), TS(s_int) },
+ { RS(DELTA_HUGE_SIZE), TS(s_int) },
+ { RS(DELTA_HUGE_SURPLUS), TS(s_int) },
+ { RS(DELTA_HUGE_TOTAL), TS(s_int) },
+ { RS(DELTA_INACTIVE), TS(s_int) },
+ { RS(DELTA_INACTIVE_ANON), TS(s_int) },
+ { RS(DELTA_INACTIVE_FILE), TS(s_int) },
+ { RS(DELTA_KERNEL_RECLAIM), TS(s_int) },
+ { RS(DELTA_KERNEL_STACK), TS(s_int) },
+ { RS(DELTA_LOCKED), TS(s_int) },
+ { RS(DELTA_LOW_FREE), TS(s_int) },
+ { RS(DELTA_LOW_TOTAL), TS(s_int) },
+ { RS(DELTA_LOW_USED), TS(s_int) },
+ { RS(DELTA_MAPPED), TS(s_int) },
+ { RS(DELTA_MAP_COPY), TS(s_int) },
+ { RS(DELTA_NFS_UNSTABLE), TS(s_int) },
+ { RS(DELTA_PAGE_TABLES), TS(s_int) },
+ { RS(DELTA_PER_CPU), TS(s_int) },
+ { RS(DELTA_SHADOWCALLSTACK), TS(s_int) },
+ { RS(DELTA_SHARED), TS(s_int) },
+ { RS(DELTA_SHMEM_HUGE), TS(s_int) },
+ { RS(DELTA_SHMEM_HUGE_MAP), TS(s_int) },
+ { RS(DELTA_SLAB), TS(s_int) },
+ { RS(DELTA_SLAB_RECLAIM), TS(s_int) },
+ { RS(DELTA_SLAB_UNRECLAIM), TS(s_int) },
+ { RS(DELTA_TOTAL), TS(s_int) },
+ { RS(DELTA_UNEVICTABLE), TS(s_int) },
+ { RS(DELTA_USED), TS(s_int) },
+ { RS(DELTA_VM_ALLOC_CHUNK), TS(s_int) },
+ { RS(DELTA_VM_ALLOC_TOTAL), TS(s_int) },
+ { RS(DELTA_VM_ALLOC_USED), TS(s_int) },
+ { RS(DELTA_WRITEBACK), TS(s_int) },
+ { RS(DELTA_WRITEBACK_TMP), TS(s_int) },
+
+ { RS(SWAP_CACHED), TS(ul_int) },
+ { RS(SWAP_FREE), TS(ul_int) },
+ { RS(SWAP_TOTAL), TS(ul_int) },
+ { RS(SWAP_USED), TS(ul_int) },
+
+ { RS(SWAP_DELTA_CACHED), TS(s_int) },
+ { RS(SWAP_DELTA_FREE), TS(s_int) },
+ { RS(SWAP_DELTA_TOTAL), TS(s_int) },
+ { RS(SWAP_DELTA_USED), TS(s_int) },
+};
+
+ /* please note,
+ * this enum MUST be 1 greater than the highest value of any enum */
+enum meminfo_item MEMINFO_logical_end = MAXTABLE(Item_table);
+
+#undef setNAME
+#undef RS
+
+
+// ___ Private Functions ||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
+static inline void meminfo_assign_results (
+ struct meminfo_stack *stack,
+ struct mem_hist *hist)
+{
+ struct meminfo_result *this = stack->head;
+
+ for (;;) {
+ enum meminfo_item item = this->item;
+ if (item >= MEMINFO_logical_end)
+ break;
+ Item_table[item].setsfunc(this, hist);
+ ++this;
+ }
+ return;
+} // end: meminfo_assign_results
+
+
+static void meminfo_extents_free_all (
+ struct meminfo_info *info)
+{
+ while (info->extents) {
+ struct stacks_extent *p = info->extents;
+ info->extents = info->extents->next;
+ free(p);
+ };
+} // end: meminfo_extents_free_all
+
+
+static inline struct meminfo_result *meminfo_itemize_stack (
+ struct meminfo_result *p,
+ int depth,
+ enum meminfo_item *items)
+{
+ struct meminfo_result *p_sav = p;
+ int i;
+
+ for (i = 0; i < depth; i++) {
+ p->item = items[i];
+ ++p;
+ }
+ return p_sav;
+} // end: meminfo_itemize_stack
+
+
+static inline int meminfo_items_check_failed (
+ int numitems,
+ enum meminfo_item *items)
+{
+ int i;
+
+ /* if an enum is passed instead of an address of one or more enums, ol' gcc
+ * will silently convert it to an address (possibly NULL). only clang will
+ * offer any sort of warning like the following:
+ *
+ * warning: incompatible integer to pointer conversion passing 'int' to parameter of type 'enum meminfo_item *'
+ * my_stack = procps_meminfo_select(info, MEMINFO_noop, num);
+ * ^~~~~~~~~~~~~~~~
+ */
+ if (numitems < 1
+ || (void *)items < (void *)(unsigned long)(2 * MEMINFO_logical_end))
+ return 1;
+
+ for (i = 0; i < numitems; i++) {
+ // a meminfo_item is currently unsigned, but we'll protect our future
+ if (items[i] < 0)
+ return 1;
+ if (items[i] >= MEMINFO_logical_end)
+ return 1;
+ }
+
+ return 0;
+} // end: meminfo_items_check_failed
+
+
+static int meminfo_make_hash_failed (
+ struct meminfo_info *info)
+{
+ #define htVAL(f) e.key = STRINGIFY(f); e.data = &info->hist.new. f; \
+ if (!hsearch_r(e, ENTER, &ep, &info->hashtab)) return 1;
+ #define htXTRA(k,f) e.key = STRINGIFY(k); e.data = &info->hist.new. f; \
+ if (!hsearch_r(e, ENTER, &ep, &info->hashtab)) return 1;
+ ENTRY e, *ep;
+ size_t n;
+
+ // will also include those derived fields (more is better)
+ n = sizeof(struct meminfo_data) / sizeof(unsigned long);
+ // we'll follow the hsearch recommendation of an extra 25%
+ if (!hcreate_r(n + (n / 4), &info->hashtab))
+ return 1;
+
+ htVAL(Active)
+ htXTRA(Active(anon), Active_anon)
+ htXTRA(Active(file), Active_file)
+ htVAL(AnonHugePages)
+ htVAL(AnonPages)
+ htVAL(Bounce)
+ htVAL(Buffers)
+ htVAL(Cached)
+ htVAL(CmaFree)
+ htVAL(CmaTotal)
+ htVAL(CommitLimit)
+ htVAL(Committed_AS)
+ htVAL(DirectMap1G)
+ htVAL(DirectMap2M)
+ htVAL(DirectMap4M)
+ htVAL(DirectMap4k)
+ htVAL(Dirty)
+ htVAL(FileHugePages)
+ htVAL(FilePmdMapped)
+ htVAL(HardwareCorrupted)
+ htVAL(HighFree)
+ htVAL(HighTotal)
+ htVAL(HugePages_Free)
+ htVAL(HugePages_Rsvd)
+ htVAL(HugePages_Surp)
+ htVAL(HugePages_Total)
+ htVAL(Hugepagesize)
+ htVAL(Hugetlb)
+ htVAL(Inactive)
+ htXTRA(Inactive(anon), Inactive_anon)
+ htXTRA(Inactive(file), Inactive_file)
+ htVAL(KReclaimable)
+ htVAL(KernelStack)
+ htVAL(LowFree)
+ htVAL(LowTotal)
+ htVAL(Mapped)
+ htVAL(MemAvailable)
+ htVAL(MemFree)
+ htVAL(MemTotal)
+ htVAL(Mlocked)
+ htVAL(MmapCopy)
+ htVAL(NFS_Unstable)
+ htVAL(PageTables)
+ htVAL(Percpu)
+ htVAL(SReclaimable)
+ htVAL(SUnreclaim)
+ htVAL(ShadowCallStack)
+ htVAL(Shmem)
+ htVAL(ShmemHugePages)
+ htVAL(ShmemPmdMapped)
+ htVAL(Slab)
+ htVAL(SwapCached)
+ htVAL(SwapFree)
+ htVAL(SwapTotal)
+ htVAL(Unevictable)
+ htVAL(VmallocChunk)
+ htVAL(VmallocTotal)
+ htVAL(VmallocUsed)
+ htVAL(Writeback)
+ htVAL(WritebackTmp)
+
+ return 0;
+ #undef htVAL
+ #undef htXTRA
+} // end: meminfo_make_hash_failed
+
+
+/*
+ * meminfo_read_failed():
+ *
+ * Read the data out of /proc/meminfo putting the information
+ * into the supplied info structure
+ */
+static int meminfo_read_failed (
+ struct meminfo_info *info)
+{
+ /* a 'memory history reference' macro for readability,
+ so we can focus the field names ... */
+ #define mHr(f) info->hist.new. f
+ char buf[MEMINFO_BUFF];
+ char *head, *tail;
+ int size;
+ unsigned long *valptr;
+ signed long mem_used;
+
+ // remember history from last time around
+ memcpy(&info->hist.old, &info->hist.new, sizeof(struct meminfo_data));
+ // clear out the soon to be 'current' values
+ memset(&info->hist.new, 0, sizeof(struct meminfo_data));
+
+ if (-1 == info->meminfo_fd
+ && (-1 == (info->meminfo_fd = open(MEMINFO_FILE, O_RDONLY))))
+ return 1;
+
+ if (lseek(info->meminfo_fd, 0L, SEEK_SET) == -1)
+ return 1;
+
+ for (;;) {
+ if ((size = read(info->meminfo_fd, buf, sizeof(buf)-1)) < 0) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ return 1;
+ }
+ break;
+ }
+ if (size == 0) {
+ errno = EIO;
+ return 1;
+ }
+ buf[size] = '\0';
+
+ head = buf;
+
+ for (;;) {
+ static __thread ENTRY e; // keep coverity off our backs (e.data)
+ ENTRY *ep;
+
+ if (!(tail = strchr(head, ':')))
+ break;
+ *tail = '\0';
+ valptr = NULL;
+
+ e.key = head;
+ if (hsearch_r(e, FIND, &ep, &info->hashtab))
+ valptr = ep->data;
+ head = tail + 1;
+ if (valptr)
+ *valptr = strtoul(head, NULL, 10);
+
+ if (!(tail = strchr(head, '\n')))
+ break;
+ head = tail + 1;
+ }
+
+ if (0 == mHr(MemAvailable))
+ mHr(MemAvailable) = mHr(MemFree);
+ mHr(derived_mem_cached) = mHr(Cached) + mHr(SReclaimable);
+
+ /* if 'available' is greater than 'total' or our calculation of mem_used
+ overflows, that's symptomatic of running within a lxc container where
+ such values will be dramatically distorted over those of the host. */
+ if (mHr(MemAvailable) > mHr(MemTotal))
+ mHr(MemAvailable) = mHr(MemFree);
+ mem_used = mHr(MemTotal) - mHr(MemAvailable);
+ if (mem_used < 0)
+ mem_used = mHr(MemTotal) - mHr(MemFree);
+ mHr(derived_mem_used) = (unsigned long)mem_used;
+
+ if (mHr(HighFree) < mHr(HighTotal))
+ mHr(derived_mem_hi_used) = mHr(HighTotal) - mHr(HighFree);
+
+ if (0 == mHr(LowTotal)) {
+ mHr(LowTotal) = mHr(MemTotal);
+ mHr(LowFree) = mHr(MemFree);
+ }
+ if (mHr(LowFree) < mHr(LowTotal))
+ mHr(derived_mem_lo_used) = mHr(LowTotal) - mHr(LowFree);
+
+ if (mHr(SwapFree) < mHr(SwapTotal))
+ mHr(derived_swap_used) = mHr(SwapTotal) - mHr(SwapFree);
+
+ return 0;
+ #undef mHr
+} // end: meminfo_read_failed
+
+
+/*
+ * meminfo_stacks_alloc():
+ *
+ * Allocate and initialize one or more stacks each of which is anchored in an
+ * associated context structure.
+ *
+ * All such stacks will have their result structures properly primed with
+ * 'items', while the result itself will be zeroed.
+ *
+ * Returns a stacks_extent struct anchoring the 'heads' of each new stack.
+ */
+static struct stacks_extent *meminfo_stacks_alloc (
+ struct meminfo_info *info,
+ int maxstacks)
+{
+ struct stacks_extent *p_blob;
+ struct meminfo_stack **p_vect;
+ struct meminfo_stack *p_head;
+ size_t vect_size, head_size, list_size, blob_size;
+ void *v_head, *v_list;
+ int i;
+
+ vect_size = sizeof(void *) * maxstacks; // size of the addr vectors |
+ vect_size += sizeof(void *); // plus NULL addr delimiter |
+ head_size = sizeof(struct meminfo_stack); // size of that head struct |
+ list_size = sizeof(struct meminfo_result)*info->numitems; // any single results stack |
+ blob_size = sizeof(struct stacks_extent); // the extent anchor itself |
+ blob_size += vect_size; // plus room for addr vects |
+ blob_size += head_size * maxstacks; // plus room for head thing |
+ blob_size += list_size * maxstacks; // plus room for our stacks |
+
+ /* note: all of this memory is allocated in a single blob, facilitating a later free(). |
+ as a minimum, it is important that the result structures themselves always are |
+ contiguous within each stack since they're accessed through relative position. | */
+ if (NULL == (p_blob = calloc(1, blob_size)))
+ return NULL;
+
+ p_blob->next = info->extents; // push this extent onto... |
+ info->extents = p_blob; // ...some existing extents |
+ p_vect = (void *)p_blob + sizeof(struct stacks_extent); // prime our vector pointer |
+ p_blob->stacks = p_vect; // set actual vectors start |
+ v_head = (void *)p_vect + vect_size; // prime head pointer start |
+ v_list = v_head + (head_size * maxstacks); // prime our stacks pointer |
+
+ for (i = 0; i < maxstacks; i++) {
+ p_head = (struct meminfo_stack *)v_head;
+ p_head->head = meminfo_itemize_stack((struct meminfo_result *)v_list, info->numitems, info->items);
+ p_blob->stacks[i] = p_head;
+ v_list += list_size;
+ v_head += head_size;
+ }
+ p_blob->ext_numstacks = maxstacks;
+ return p_blob;
+} // end: meminfo_stacks_alloc
+
+
+// ___ Public Functions |||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
+// --- standard required functions --------------------------------------------
+
+/*
+ * procps_meminfo_new:
+ *
+ * Create a new container to hold the stat information
+ *
+ * The initial refcount is 1, and needs to be decremented
+ * to release the resources of the structure.
+ *
+ * Returns: < 0 on failure, 0 on success along with
+ * a pointer to a new context struct
+ */
+PROCPS_EXPORT int procps_meminfo_new (
+ struct meminfo_info **info)
+{
+ struct meminfo_info *p;
+
+#ifdef ITEMTABLE_DEBUG
+ int i, failed = 0;
+ for (i = 0; i < MAXTABLE(Item_table); i++) {
+ if (i != Item_table[i].enumnumb) {
+ fprintf(stderr, "%s: enum/table error: Item_table[%d] was %s, but its value is %d\n"
+ , __FILE__, i, Item_table[i].enum2str, Item_table[i].enumnumb);
+ failed = 1;
+ }
+ }
+ if (failed) _Exit(EXIT_FAILURE);
+#endif
+
+ if (info == NULL || *info != NULL)
+ return -EINVAL;
+ if (!(p = calloc(1, sizeof(struct meminfo_info))))
+ return -ENOMEM;
+
+ p->refcount = 1;
+ p->meminfo_fd = -1;
+
+ if (meminfo_make_hash_failed(p)) {
+ free(p);
+ return -errno;
+ }
+
+ /* do a priming read here for the following potential benefits: |
+ 1) ensure there will be no problems with subsequent access |
+ 2) make delta results potentially useful, even if 1st time |
+ 3) elimnate need for history distortions 1st time 'switch' | */
+ if (meminfo_read_failed(p)) {
+ procps_meminfo_unref(&p);
+ return -errno;
+ }
+
+ *info = p;
+ return 0;
+} // end: procps_meminfo_new
+
+
+PROCPS_EXPORT int procps_meminfo_ref (
+ struct meminfo_info *info)
+{
+ if (info == NULL)
+ return -EINVAL;
+
+ info->refcount++;
+ return info->refcount;
+} // end: procps_meminfo_ref
+
+
+PROCPS_EXPORT int procps_meminfo_unref (
+ struct meminfo_info **info)
+{
+ if (info == NULL || *info == NULL)
+ return -EINVAL;
+
+ (*info)->refcount--;
+
+ if ((*info)->refcount < 1) {
+ int errno_sav = errno;
+
+ if ((*info)->meminfo_fd != -1)
+ close((*info)->meminfo_fd);
+
+ if ((*info)->extents)
+ meminfo_extents_free_all((*info));
+ if ((*info)->items)
+ free((*info)->items);
+ hdestroy_r(&(*info)->hashtab);
+
+ free(*info);
+ *info = NULL;
+
+ errno = errno_sav;
+ return 0;
+ }
+ return (*info)->refcount;
+} // end: procps_meminfo_unref
+
+
+// --- variable interface functions -------------------------------------------
+
+PROCPS_EXPORT struct meminfo_result *procps_meminfo_get (
+ struct meminfo_info *info,
+ enum meminfo_item item)
+{
+ time_t cur_secs;
+
+ errno = EINVAL;
+ if (info == NULL)
+ return NULL;
+ if (item < 0 || item >= MEMINFO_logical_end)
+ return NULL;
+ errno = 0;
+
+ /* we will NOT read the meminfo file with every call - rather, we'll offer
+ a granularity of 1 second between reads ... */
+ cur_secs = time(NULL);
+ if (1 <= cur_secs - info->sav_secs) {
+ if (meminfo_read_failed(info))
+ return NULL;
+ info->sav_secs = cur_secs;
+ }
+
+ info->get_this.item = item;
+ // with 'get', we must NOT honor the usual 'noop' guarantee
+ info->get_this.result.ul_int = 0;
+ Item_table[item].setsfunc(&info->get_this, &info->hist);
+
+ return &info->get_this;
+} // end: procps_meminfo_get
+
+
+/* procps_meminfo_select():
+ *
+ * Harvest all the requested MEM and/or SWAP information then return
+ * it in a results stack.
+ *
+ * Returns: pointer to a meminfo_stack struct on success, NULL on error.
+ */
+PROCPS_EXPORT struct meminfo_stack *procps_meminfo_select (
+ struct meminfo_info *info,
+ enum meminfo_item *items,
+ int numitems)
+{
+ errno = EINVAL;
+ if (info == NULL || items == NULL)
+ return NULL;
+ if (meminfo_items_check_failed(numitems, items))
+ return NULL;
+ errno = 0;
+
+ /* is this the first time or have things changed since we were last called?
+ if so, gotta' redo all of our stacks stuff ... */
+ if (info->numitems != numitems + 1
+ || memcmp(info->items, items, sizeof(enum meminfo_item) * numitems)) {
+ // allow for our MEMINFO_logical_end
+ if (!(info->items = realloc(info->items, sizeof(enum meminfo_item) * (numitems + 1))))
+ return NULL;
+ memcpy(info->items, items, sizeof(enum meminfo_item) * numitems);
+ info->items[numitems] = MEMINFO_logical_end;
+ info->numitems = numitems + 1;
+ if (info->extents)
+ meminfo_extents_free_all(info);
+ }
+ if (!info->extents
+ && (!meminfo_stacks_alloc(info, 1)))
+ return NULL;
+
+ if (meminfo_read_failed(info))
+ return NULL;
+ meminfo_assign_results(info->extents->stacks[0], &info->hist);
+
+ return info->extents->stacks[0];
+} // end: procps_meminfo_select
+
+
+// --- special debugging function(s) ------------------------------------------
+/*
+ * The following isn't part of the normal programming interface. Rather,
+ * it exists to validate result types referenced in application programs.
+ *
+ * It's used only when:
+ * 1) the 'XTRA_PROCPS_DEBUG' has been defined, or
+ * 2) an #include of 'xtra-procps-debug.h' is used
+ */
+
+PROCPS_EXPORT struct meminfo_result *xtra_meminfo_get (
+ struct meminfo_info *info,
+ enum meminfo_item actual_enum,
+ const char *typestr,
+ const char *file,
+ int lineno)
+{
+ struct meminfo_result *r = procps_meminfo_get(info, actual_enum);
+
+ if (actual_enum < 0 || actual_enum >= MEMINFO_logical_end) {
+ fprintf(stderr, "%s line %d: invalid item = %d, type = %s\n"
+ , file, lineno, actual_enum, typestr);
+ }
+ if (r) {
+ char *str = Item_table[r->item].type2str;
+ if (str[0]
+ && (strcmp(typestr, str)))
+ fprintf(stderr, "%s line %d: was %s, expected %s\n", file, lineno, typestr, str);
+ }
+ return r;
+} // end: xtra_meminfo_get_
+
+
+PROCPS_EXPORT struct meminfo_result *xtra_meminfo_val (
+ int relative_enum,
+ const char *typestr,
+ const struct meminfo_stack *stack,
+ struct meminfo_info *info,
+ const char *file,
+ int lineno)
+{
+ char *str;
+ int i;
+
+ for (i = 0; stack->head[i].item < MEMINFO_logical_end; i++)
+ ;
+ if (relative_enum < 0 || relative_enum >= i) {
+ fprintf(stderr, "%s line %d: invalid relative_enum = %d, valid range = 0-%d\n"
+ , file, lineno, relative_enum, i-1);
+ return NULL;
+ }
+ str = Item_table[stack->head[relative_enum].item].type2str;
+ if (str[0]
+ && (strcmp(typestr, str))) {
+ fprintf(stderr, "%s line %d: was %s, expected %s\n", file, lineno, typestr, str);
+ }
+ return &stack->head[relative_enum];
+ (void)info;
+} // end: xtra_meminfo_val
diff --git a/library/namespace.c b/library/namespace.c
new file mode 100644
index 0000000..04e4c45
--- /dev/null
+++ b/library/namespace.c
@@ -0,0 +1,116 @@
+/*
+ * namespace.c - Library API for Linux namespaces
+ *
+ * Copyright © 2015-2023 Jim Warner <james.warner@comcast.net>
+ * Copyright © 2015-2023 Craig Small <csmall@dropbear.xyz>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "misc.h"
+#include "procps-private.h"
+
+#define NSPATHLEN 64
+
+static const char *ns_names[] = {
+ [PROCPS_NS_CGROUP] = "cgroup",
+ [PROCPS_NS_IPC] = "ipc",
+ [PROCPS_NS_MNT] = "mnt",
+ [PROCPS_NS_NET] = "net",
+ [PROCPS_NS_PID] = "pid",
+ [PROCPS_NS_TIME] = "time",
+ [PROCPS_NS_USER] = "user",
+ [PROCPS_NS_UTS] = "uts"
+};
+
+
+/*
+ * procps_ns_get_name:
+ *
+ * Find the name of the namespace with the given ID
+ *
+ * @id: The ID of the required namespace, see
+ * namespace_type
+ *
+ * Returns: static string of the namespace
+ */
+PROCPS_EXPORT const char *procps_ns_get_name(const int id)
+{
+ if (id >= PROCPS_NS_COUNT || id < 0)
+ return NULL;
+ return ns_names[id];
+}
+
+/*
+ * procps_ns_get_id:
+ *
+ * Find the namespace ID that matches the given
+ * name.
+ *
+ * @name: the name of the required namespace
+ *
+ * Returns: ID of found name
+ * < 0 means error
+ */
+PROCPS_EXPORT int procps_ns_get_id(const char *name)
+{
+ int i;
+
+ if (name == NULL)
+ return -EINVAL;
+ for (i=0; i < PROCPS_NS_COUNT; i++)
+ if (!strcmp(ns_names[i], name))
+ return i;
+ return -EINVAL;
+}
+
+/*
+ * procs_ns_read_pid:
+ *
+ * Find all namespaces for the given process.
+ * @pid: Process ID for required process
+ * @nsp: Pointer to the struct procps_ns
+ *
+ * Returns:
+ * 0 on success
+ * < 0 on error
+ */
+PROCPS_EXPORT int procps_ns_read_pid(
+ const int pid,
+ struct procps_ns *nsp)
+{
+ char path[NSPATHLEN+1];
+ struct stat st;
+ int i;
+
+ if (nsp == NULL)
+ return -EINVAL;
+ if (pid < 1)
+ return -EINVAL;
+
+ for (i=0; i < PROCPS_NS_COUNT; i++) {
+ snprintf(path, NSPATHLEN, "/proc/%d/ns/%s", pid, ns_names[i]);
+ if (0 == stat(path, &st))
+ nsp->ns[i] = (unsigned long)st.st_ino;
+ else
+ nsp->ns[i] = 0;
+ }
+ return 0;
+}
diff --git a/library/numa.c b/library/numa.c
new file mode 100644
index 0000000..3fca413
--- /dev/null
+++ b/library/numa.c
@@ -0,0 +1,114 @@
+/*
+ * NUMA node support for <PIDS> & <STAT> interfaces
+ *
+ * Copyright © 2017-2023 Jim Warner <james.warner@comcast.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef NUMA_DISABLE
+#include <dlfcn.h>
+#endif
+#include <stdlib.h>
+
+#include "numa.h"
+
+/*
+ * We're structured so that if numa_init() is NOT called or that ./configure |
+ * --disable-numa WAS specified, then calls to both of our primary functions |
+ * of numa_max_node() plus numa_node_of_cpu() would always return a negative |
+ * 1 which signifies that NUMA information isn't available. That ./configure |
+ * option might be required when libdl.so (necessary for dlopen) is missing. |
+ */
+
+
+/* ------------------------------------------------------------------------- +
+ a strictly development #define, existing specifically for the top program |
+ ( and it has no affect if ./configure --disable-numa has been specified ) | */
+//#define PRETEND_NUMA // pretend there are 3 'discontiguous' numa nodes |
+// ------------------------------------------------------------------------- +
+
+
+static int null_max_node (void) { return -1; }
+static int null_node_of_cpu (int n) { (void)n; return -1; }
+
+
+#ifndef NUMA_DISABLE
+ #ifdef PRETEND_NUMA
+static int fake_max_node (void) { return 3; }
+static int fake_node_of_cpu (int n) { return (1 == (n % 4)) ? 0 : (n % 4); }
+ #endif
+#endif
+
+
+#ifndef NUMA_DISABLE
+static void *libnuma_handle;
+#endif
+int (*numa_max_node) (void) = null_max_node;
+int (*numa_node_of_cpu) (int) = null_node_of_cpu;
+
+
+void numa_init (void) {
+ static int initialized;
+
+ if (initialized)
+ return;
+
+#ifndef NUMA_DISABLE
+ #ifndef PRETEND_NUMA
+ // we'll try for the most recent version, then a version we know works...
+ if ((libnuma_handle = dlopen("libnuma.so", RTLD_LAZY))
+ || (libnuma_handle = dlopen("libnuma.so.1", RTLD_LAZY))) {
+ numa_max_node = dlsym(libnuma_handle, "numa_max_node");
+ numa_node_of_cpu = dlsym(libnuma_handle, "numa_node_of_cpu");
+ if (numa_max_node == NULL
+ || (numa_node_of_cpu == NULL)) {
+ // this dlclose is safe - we've yet to call numa_node_of_cpu
+ // ( there's one other dlclose which has now been disabled )
+ dlclose(libnuma_handle);
+ libnuma_handle = NULL;
+ numa_max_node = null_max_node;
+ numa_node_of_cpu = null_node_of_cpu;
+ }
+ }
+ #else
+ libnuma_handle = (void *)-1;
+ numa_max_node = fake_max_node;
+ numa_node_of_cpu = fake_node_of_cpu;
+ #endif
+#endif
+ initialized = 1;
+} // end: numa_init
+
+
+void numa_uninit (void) {
+#ifndef PRETEND_NUMA
+ /* note: we'll skip a dlcose() to avoid the following libnuma memory
+ * leak which is triggered after a call to numa_node_of_cpu():
+ * ==1234== LEAK SUMMARY:
+ * ==1234== definitely lost: 512 bytes in 1 blocks
+ * ==1234== indirectly lost: 48 bytes in 2 blocks
+ * ==1234== ...
+ * [ thanks very much libnuma for all the pains you have caused us ]
+ */
+// if (libnuma_handle)
+// dlclose(libnuma_handle);
+#endif
+} // end: numa_uninit
+
+
+#if defined(PRETEND_NUMA) && defined(NUMA_DISABLE)
+# warning 'PRETEND_NUMA' ignored, 'NUMA_DISABLE' is active
+#endif
diff --git a/library/pids.c b/library/pids.c
new file mode 100644
index 0000000..6ae94ad
--- /dev/null
+++ b/library/pids.c
@@ -0,0 +1,1700 @@
+/*
+ * pids.c - process related definitions for libproc2
+ *
+ * Copyright © 2015-2023 Jim Warner <james.warner@comcast.net>
+ * Copyright © 2015-2023 Craig Small <csmall@dropbear.xyz>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+//efine _GNU_SOURCE // for qsort_r
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "devname.h"
+#include "misc.h"
+#include "numa.h"
+#include "readproc.h"
+#include "wchan.h"
+
+#include "procps-private.h"
+#include "pids.h"
+
+
+//#define UNREF_RPTHASH // report hash details at uref() time
+
+#define FILL_ID_MAX 255 // upper limit with select of pid/uid
+#define STACKS_INIT 1024 // amount of initial stack allocation
+#define STACKS_GROW 128 // amount reap stack allocations grow
+#define NEWOLD_INIT 1024 // amount for initial hist allocation
+#define NEWOLD_GROW 128 // amt by which hist allocations grow
+
+/* ------------------------------------------------------------------------- +
+ this provision can be used to ensure that our Item_table was synchronized |
+ with those enumerators found in the associated header file. It's intended |
+ to only be used locally (& temporarily) at some point prior to a release! | */
+// #define ITEMTABLE_DEBUG //----------------------------------------------- |
+// ------------------------------------------------------------------------- +
+
+
+struct stacks_extent {
+ int ext_numstacks;
+ struct stacks_extent *next;
+ struct pids_stack **stacks;
+};
+
+struct fetch_support {
+ struct pids_stack **anchor; // reap/select consolidated extents
+ int n_alloc; // number of above pointers allocated
+ int n_inuse; // number of above pointers occupied
+ int n_alloc_save; // last known results.stacks allocation
+ struct pids_fetch results; // counts + stacks for return to caller
+ struct pids_counts counts; // actual counts pointed to by 'results'
+};
+
+typedef void (*SET_t)(struct pids_info *, struct pids_result *, proc_t *);
+
+struct pids_info {
+ int refcount;
+ int maxitems; // includes 'logical_end' delimiter
+ enum pids_item *items; // includes 'logical_end' delimiter
+ struct stacks_extent *extents; // anchor for all resettable extents
+ struct stacks_extent *otherexts; // anchor for invariant extents // <=== currently unused
+ struct fetch_support fetch; // support for procps_pids_reap, select, fatal
+ int history_yes; // need historical data
+ struct history_info *hist; // pointer to historical support data
+ proc_t*(*read_something)(PROCTAB*, proc_t*); // readproc/readeither via which
+ unsigned pgs2k_shift; // to convert some proc vaules
+ unsigned oldflags; // the old library PROC_FILL flagss
+ PROCTAB *fetch_PT; // oldlib interface for 'select' & 'reap'
+ unsigned long hertz; // for the 'TIME' & 'UTILIZATION' calculations
+ unsigned long long boot_tics; // for TIME_ELAPSED & 'UTILIZATION' calculations
+ PROCTAB *get_PT; // oldlib interface for active 'get'
+ struct stacks_extent *get_ext; // for active 'get' (also within 'extents')
+ enum pids_fetch_type get_type; // last known type of 'get' request
+ int seterr; // an ENOMEM encountered during assign
+ proc_t get_proc; // the proc_t used by procps_pids_get
+ proc_t fetch_proc; // the proc_t used by pids_stacks_fetch
+ SET_t *func_array; // extracted Item_table 'setsfunc' pointers
+};
+
+
+// ___ Free Storage Support |||||||||||||||||||||||||||||||||||||||||||||||||||
+
+#define freNAME(t) free_pids_ ## t
+
+static void freNAME(str) (struct pids_result *R) {
+ if (R->result.str) free(R->result.str);
+}
+
+static void freNAME(strv) (struct pids_result *R) {
+ if (R->result.strv && *R->result.strv) free(*R->result.strv);
+}
+
+
+// ___ Results 'Set' Support ||||||||||||||||||||||||||||||||||||||||||||||||||
+
+#define setNAME(e) set_pids_ ## e
+#define setDECL(e) static void setNAME(e) \
+ (struct pids_info *I, struct pids_result *R, proc_t *P)
+
+/* convert pages to kib */
+#define CVT_set(e,t,x) setDECL(e) { \
+ R->result. t = (long)(P-> x) << I -> pgs2k_shift; }
+/* strdup of a static char array */
+#define DUP_set(e,x) setDECL(e) { \
+ freNAME(str)(R); \
+ if (!(R->result.str = strdup(P-> x))) I->seterr = 1; }
+/* regular assignment copy */
+#define REG_set(e,t,x) setDECL(e) { \
+ (void)I; R->result. t = P-> x; }
+/* take ownership of a normal single string if possible, else return
+ some sort of hint that they duplicated this char * item ... */
+#define STR_set(e,x) setDECL(e) { \
+ freNAME(str)(R); \
+ if (NULL != P-> x) { R->result.str = P-> x; P-> x = NULL; } \
+ else { R->result.str = strdup("[ duplicate " STRINGIFY(e) " ]"); \
+ if (!R->result.str) I->seterr = 1; } }
+/* take ownership of true vectorized strings if possible, else return
+ some sort of hint that they duplicated this char ** item ... */
+#define VEC_set(e,x) setDECL(e) { \
+ freNAME(strv)(R); \
+ if (NULL != P-> x) { R->result.strv = P-> x; P-> x = NULL; } \
+ else { R->result.strv = vectorize_this_str("[ duplicate " STRINGIFY(e) " ]"); \
+ if (!R->result.strv) I->seterr = 1; } }
+
+
+setDECL(noop) { (void)I; (void)R; (void)P; }
+setDECL(extra) { (void)I; (void)P; R->result.ull_int = 0; }
+
+REG_set(ADDR_CODE_END, ul_int, end_code)
+REG_set(ADDR_CODE_START, ul_int, start_code)
+REG_set(ADDR_CURR_EIP, ul_int, kstk_eip)
+REG_set(ADDR_CURR_ESP, ul_int, kstk_esp)
+REG_set(ADDR_STACK_START, ul_int, start_stack)
+REG_set(AUTOGRP_ID, s_int, autogrp_id)
+REG_set(AUTOGRP_NICE, s_int, autogrp_nice)
+STR_set(CGNAME, cgname)
+STR_set(CGROUP, cgroup)
+VEC_set(CGROUP_V, cgroup_v)
+STR_set(CMD, cmd)
+STR_set(CMDLINE, cmdline)
+VEC_set(CMDLINE_V, cmdline_v)
+STR_set(ENVIRON, environ)
+VEC_set(ENVIRON_V, environ_v)
+STR_set(EXE, exe)
+REG_set(EXIT_SIGNAL, s_int, exit_signal)
+REG_set(FLAGS, ul_int, flags)
+REG_set(FLT_MAJ, ul_int, maj_flt)
+setDECL(FLT_MAJ_C) { (void)I; R->result.ul_int = P->maj_flt + P->cmaj_flt; }
+REG_set(FLT_MAJ_DELTA, s_int, maj_delta)
+REG_set(FLT_MIN, ul_int, min_flt)
+setDECL(FLT_MIN_C) { (void)I; R->result.ul_int = P->min_flt + P->cmin_flt; }
+REG_set(FLT_MIN_DELTA, s_int, min_delta)
+REG_set(ID_EGID, u_int, egid)
+REG_set(ID_EGROUP, str, egroup)
+REG_set(ID_EUID, u_int, euid)
+REG_set(ID_EUSER, str, euser)
+REG_set(ID_FGID, u_int, fgid)
+REG_set(ID_FGROUP, str, fgroup)
+REG_set(ID_FUID, u_int, fuid)
+REG_set(ID_FUSER, str, fuser)
+REG_set(ID_LOGIN, s_int, luid)
+REG_set(ID_PGRP, s_int, pgrp)
+REG_set(ID_PID, s_int, tid)
+REG_set(ID_PPID, s_int, ppid)
+REG_set(ID_RGID, u_int, rgid)
+REG_set(ID_RGROUP, str, rgroup)
+REG_set(ID_RUID, u_int, ruid)
+REG_set(ID_RUSER, str, ruser)
+REG_set(ID_SESSION, s_int, session)
+REG_set(ID_SGID, u_int, sgid)
+REG_set(ID_SGROUP, str, sgroup)
+REG_set(ID_SUID, u_int, suid)
+REG_set(ID_SUSER, str, suser)
+REG_set(ID_TGID, s_int, tgid)
+REG_set(ID_TID, s_int, tid)
+REG_set(ID_TPGID, s_int, tpgid)
+REG_set(IO_READ_BYTES, ul_int, read_bytes)
+REG_set(IO_READ_CHARS, ul_int, rchar)
+REG_set(IO_READ_OPS, ul_int, syscr)
+REG_set(IO_WRITE_BYTES, ul_int, write_bytes)
+REG_set(IO_WRITE_CBYTES, ul_int, cancelled_write_bytes)
+REG_set(IO_WRITE_CHARS, ul_int, wchar)
+REG_set(IO_WRITE_OPS, ul_int, syscw)
+REG_set(LXCNAME, str, lxcname)
+CVT_set(MEM_CODE, ul_int, trs)
+REG_set(MEM_CODE_PGS, ul_int, trs)
+CVT_set(MEM_DATA, ul_int, drs)
+REG_set(MEM_DATA_PGS, ul_int, drs)
+CVT_set(MEM_RES, ul_int, resident)
+REG_set(MEM_RES_PGS, ul_int, resident)
+CVT_set(MEM_SHR, ul_int, share)
+REG_set(MEM_SHR_PGS, ul_int, share)
+CVT_set(MEM_VIRT, ul_int, size)
+REG_set(MEM_VIRT_PGS, ul_int, size)
+REG_set(NICE, s_int, nice)
+REG_set(NLWP, s_int, nlwp)
+REG_set(NS_CGROUP, ul_int, ns.ns[0])
+REG_set(NS_IPC, ul_int, ns.ns[1])
+REG_set(NS_MNT, ul_int, ns.ns[2])
+REG_set(NS_NET, ul_int, ns.ns[3])
+REG_set(NS_PID, ul_int, ns.ns[4])
+REG_set(NS_TIME, ul_int, ns.ns[5])
+REG_set(NS_USER, ul_int, ns.ns[6])
+REG_set(NS_UTS, ul_int, ns.ns[7])
+REG_set(OOM_ADJ, s_int, oom_adj)
+REG_set(OOM_SCORE, s_int, oom_score)
+REG_set(PRIORITY, s_int, priority)
+REG_set(PRIORITY_RT, s_int, rtprio)
+REG_set(PROCESSOR, s_int, processor)
+setDECL(PROCESSOR_NODE) { (void)I; R->result.s_int = numa_node_of_cpu(P->processor); }
+REG_set(RSS, ul_int, rss)
+REG_set(RSS_RLIM, ul_int, rss_rlim)
+REG_set(SCHED_CLASS, s_int, sched)
+STR_set(SD_MACH, sd_mach)
+STR_set(SD_OUID, sd_ouid)
+STR_set(SD_SEAT, sd_seat)
+STR_set(SD_SESS, sd_sess)
+STR_set(SD_SLICE, sd_slice)
+STR_set(SD_UNIT, sd_unit)
+STR_set(SD_UUNIT, sd_uunit)
+DUP_set(SIGBLOCKED, blocked)
+DUP_set(SIGCATCH, sigcatch)
+DUP_set(SIGIGNORE, sigignore)
+DUP_set(SIGNALS, signal)
+DUP_set(SIGPENDING, _sigpnd)
+REG_set(SMAP_ANONYMOUS, ul_int, smap_Anonymous)
+REG_set(SMAP_HUGE_ANON, ul_int, smap_AnonHugePages)
+REG_set(SMAP_HUGE_FILE, ul_int, smap_FilePmdMapped)
+REG_set(SMAP_HUGE_SHMEM, ul_int, smap_ShmemPmdMapped)
+REG_set(SMAP_HUGE_TLBPRV, ul_int, smap_Private_Hugetlb)
+REG_set(SMAP_HUGE_TLBSHR, ul_int, smap_Shared_Hugetlb)
+REG_set(SMAP_LAZY_FREE, ul_int, smap_LazyFree)
+REG_set(SMAP_LOCKED, ul_int, smap_Locked)
+REG_set(SMAP_PRV_CLEAN, ul_int, smap_Private_Clean)
+REG_set(SMAP_PRV_DIRTY, ul_int, smap_Private_Dirty)
+setDECL(SMAP_PRV_TOTAL) { (void)I; R->result.ul_int = P->smap_Private_Clean + P->smap_Private_Dirty; }
+REG_set(SMAP_PSS, ul_int, smap_Pss)
+REG_set(SMAP_PSS_ANON, ul_int, smap_Pss_Anon)
+REG_set(SMAP_PSS_FILE, ul_int, smap_Pss_File)
+REG_set(SMAP_PSS_SHMEM, ul_int, smap_Pss_Shmem)
+REG_set(SMAP_REFERENCED, ul_int, smap_Referenced)
+REG_set(SMAP_RSS, ul_int, smap_Rss)
+REG_set(SMAP_SHR_CLEAN, ul_int, smap_Shared_Clean)
+REG_set(SMAP_SHR_DIRTY, ul_int, smap_Shared_Dirty)
+REG_set(SMAP_SWAP, ul_int, smap_Swap)
+REG_set(SMAP_SWAP_PSS, ul_int, smap_SwapPss)
+REG_set(STATE, s_ch, state)
+STR_set(SUPGIDS, supgid)
+STR_set(SUPGROUPS, supgrp)
+setDECL(TICS_ALL) { (void)I; R->result.ull_int = P->utime + P->stime; }
+setDECL(TICS_ALL_C) { (void)I; R->result.ull_int = P->utime + P->stime + P->cutime + P->cstime; }
+REG_set(TICS_ALL_DELTA, u_int, pcpu)
+REG_set(TICS_BEGAN, ull_int, start_time)
+REG_set(TICS_BLKIO, ull_int, blkio_tics)
+REG_set(TICS_GUEST, ull_int, gtime)
+setDECL(TICS_GUEST_C) { (void)I; R->result.ull_int = P->gtime + P->cgtime; }
+REG_set(TICS_SYSTEM, ull_int, stime)
+setDECL(TICS_SYSTEM_C) { (void)I; R->result.ull_int = P->stime + P->cstime; }
+REG_set(TICS_USER, ull_int, utime)
+setDECL(TICS_USER_C) { (void)I; R->result.ull_int = P->utime + P->cutime; }
+setDECL(TIME_ALL) { R->result.real = ((double)P->utime + P->stime) / I->hertz; }
+setDECL(TIME_ALL_C) { R->result.real = ((double)P->utime + P->stime + P->cutime + P->cstime) / I->hertz; }
+setDECL(TIME_ELAPSED) { double t = I->boot_tics - P->start_time; if (t > 0) R->result.real = t / I->hertz; }
+setDECL(TIME_START) { R->result.real = (double)P->start_time / I->hertz; }
+REG_set(TTY, s_int, tty)
+setDECL(TTY_NAME) { char buf[64]; freNAME(str)(R); dev_to_tty(buf, sizeof(buf), P->tty, P->tid, ABBREV_DEV); if (!(R->result.str = strdup(buf))) I->seterr = 1; }
+setDECL(TTY_NUMBER) { char buf[64]; freNAME(str)(R); dev_to_tty(buf, sizeof(buf), P->tty, P->tid, ABBREV_DEV|ABBREV_TTY|ABBREV_PTS); if (!(R->result.str = strdup(buf))) I->seterr = 1; }
+setDECL(UTILIZATION) { double t = I->boot_tics - P->start_time; if (t > 0) R->result.real = ((P->utime + P->stime) * 100.0f) / t; }
+setDECL(UTILIZATION_C) { double t = I->boot_tics - P->start_time; if (t > 0) R->result.real = ((P->utime + P->stime + P->cutime + P->cstime) * 100.0f) / t; }
+REG_set(VM_DATA, ul_int, vm_data)
+REG_set(VM_EXE, ul_int, vm_exe)
+REG_set(VM_LIB, ul_int, vm_lib)
+REG_set(VM_RSS, ul_int, vm_rss)
+REG_set(VM_RSS_ANON, ul_int, vm_rss_anon)
+REG_set(VM_RSS_FILE, ul_int, vm_rss_file)
+REG_set(VM_RSS_LOCKED, ul_int, vm_lock)
+REG_set(VM_RSS_SHARED, ul_int, vm_rss_shared)
+REG_set(VM_SIZE, ul_int, vm_size)
+REG_set(VM_STACK, ul_int, vm_stack)
+REG_set(VM_SWAP, ul_int, vm_swap)
+setDECL(VM_USED) { (void)I; R->result.ul_int = P->vm_swap + P->vm_rss; }
+REG_set(VSIZE_BYTES, ul_int, vsize)
+setDECL(WCHAN_NAME) { freNAME(str)(R); if (!(R->result.str = strdup(lookup_wchan(P->tid)))) I->seterr = 1;; }
+
+#undef setDECL
+#undef CVT_set
+#undef DUP_set
+#undef REG_set
+#undef STR_set
+#undef VEC_set
+
+
+// ___ Sorting Support ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
+struct sort_parms {
+ int offset;
+ enum pids_sort_order order;
+};
+
+#define srtNAME(t) sort_pids_ ## t
+#define srtDECL(t) static int srtNAME(t) \
+ (const struct pids_stack **A, const struct pids_stack **B, struct sort_parms *P)
+
+#define NUM_srt(T) srtDECL(T) { \
+ const struct pids_result *a = (*A)->head + P->offset; \
+ const struct pids_result *b = (*B)->head + P->offset; \
+ return P->order * (a->result. T - b->result. T); }
+
+#define REG_srt(T) srtDECL(T) { \
+ const struct pids_result *a = (*A)->head + P->offset; \
+ const struct pids_result *b = (*B)->head + P->offset; \
+ if ( a->result. T > b->result. T ) return P->order > 0 ? 1 : -1; \
+ if ( a->result. T < b->result. T ) return P->order > 0 ? -1 : 1; \
+ return 0; }
+
+NUM_srt(s_ch)
+NUM_srt(s_int)
+
+REG_srt(u_int)
+REG_srt(ul_int)
+REG_srt(ull_int)
+
+REG_srt(real)
+
+srtDECL(str) {
+ const struct pids_result *a = (*A)->head + P->offset;
+ const struct pids_result *b = (*B)->head + P->offset;
+ return P->order * strcoll(a->result.str, b->result.str);
+}
+
+srtDECL(strv) {
+ const struct pids_result *a = (*A)->head + P->offset;
+ const struct pids_result *b = (*B)->head + P->offset;
+ if (!a->result.strv || !b->result.strv) return 0;
+ return P->order * strcoll((*a->result.strv), (*b->result.strv));
+}
+
+srtDECL(strvers) {
+ const struct pids_result *a = (*A)->head + P->offset;
+ const struct pids_result *b = (*B)->head + P->offset;
+ return P->order * strverscmp(a->result.str, b->result.str);
+}
+
+srtDECL(noop) {
+ (void)A; (void)B; (void)P;
+ return 0;
+}
+
+#undef srtDECL
+#undef NUM_srt
+#undef REG_srt
+
+
+// ___ Controlling Table ||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
+#define f_either PROC_SPARE_1 // either status or stat (favor stat)
+#define f_exe PROC_FILL_EXE
+#define f_grp PROC_FILLGRP
+#define f_io PROC_FILLIO
+#define f_login PROC_FILL_LUID
+#define f_lxc PROC_FILL_LXC
+#define f_ns PROC_FILLNS
+#define f_oom PROC_FILLOOM
+#define f_smaps PROC_FILLSMAPS
+#define f_stat PROC_FILLSTAT
+#define f_statm PROC_FILLMEM
+#define f_status PROC_FILLSTATUS
+#define f_systemd PROC_FILLSYSTEMD
+#define f_usr PROC_FILLUSR
+ // these next three will yield true verctorized strings
+#define v_arg PROC_FILLARG
+#define v_cgroup PROC_FILLCGROUP
+#define v_env PROC_FILLENV
+ // these next three will yield a single string (never vectorized)
+#define x_cgroup PROC_EDITCGRPCVT
+#define x_cmdline PROC_EDITCMDLCVT
+#define x_environ PROC_EDITENVRCVT
+ // these next three will also force PROC_FILLSTATUS
+#define x_ogroup PROC_FILL_OGROUPS
+#define x_ouser PROC_FILL_OUSERS
+#define x_supgrp PROC_FILL_SUPGRP
+ // placed here so an 'f' prefix wouldn't make 'em first
+#define z_autogrp PROC_FILLAUTOGRP
+
+typedef void (*FRE_t)(struct pids_result *);
+typedef int (*QSR_t)(const void *, const void *, void *);
+
+#ifdef ITEMTABLE_DEBUG
+#define RS(e) (SET_t)setNAME(e), PIDS_ ## e, STRINGIFY(PIDS_ ## e)
+#else
+#define RS(e) (SET_t)setNAME(e)
+#endif
+#define FF(t) (FRE_t)freNAME(t)
+#define QS(t) (QSR_t)srtNAME(t)
+#define TS(t) STRINGIFY(t)
+#define TS_noop ""
+
+ /*
+ * Need it be said?
+ * This table must be kept in the exact same order as
+ * those 'enum pids_item' guys ! */
+static struct {
+ SET_t setsfunc; // the actual result setting routine
+#ifdef ITEMTABLE_DEBUG
+ int enumnumb; // enumerator (must match position!)
+ char *enum2str; // enumerator name as a char* string
+#endif
+ unsigned oldflags; // PROC_FILLxxxx flags for this item
+ FRE_t freefunc; // free function for strings storage
+ QSR_t sortfunc; // sort cmp func for a specific type
+ int needhist; // a result requires history support
+ char *type2str; // the result type as a string value
+} Item_table[] = {
+/* setsfunc oldflags freefunc sortfunc needhist type2str
+ --------------------- ---------- --------- ------------- -------- ----------- */
+ { RS(noop), 0, NULL, QS(noop), 0, TS_noop }, // user only, never altered
+ { RS(extra), 0, NULL, QS(ull_int), 0, TS_noop }, // user only, reset to zero
+
+ { RS(ADDR_CODE_END), f_stat, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(ADDR_CODE_START), f_stat, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(ADDR_CURR_EIP), f_stat, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(ADDR_CURR_ESP), f_stat, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(ADDR_STACK_START), f_stat, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(AUTOGRP_ID), z_autogrp, NULL, QS(s_int), 0, TS(s_int) },
+ { RS(AUTOGRP_NICE), z_autogrp, NULL, QS(s_int), 0, TS(s_int) },
+ { RS(CGNAME), x_cgroup, FF(str), QS(str), 0, TS(str) },
+ { RS(CGROUP), x_cgroup, FF(str), QS(str), 0, TS(str) },
+ { RS(CGROUP_V), v_cgroup, FF(strv), QS(strv), 0, TS(strv) },
+ { RS(CMD), f_either, FF(str), QS(str), 0, TS(str) },
+ { RS(CMDLINE), x_cmdline, FF(str), QS(str), 0, TS(str) },
+ { RS(CMDLINE_V), v_arg, FF(strv), QS(strv), 0, TS(strv) },
+ { RS(ENVIRON), x_environ, FF(str), QS(str), 0, TS(str) },
+ { RS(ENVIRON_V), v_env, FF(strv), QS(strv), 0, TS(strv) },
+ { RS(EXE), f_exe, FF(str), QS(str), 0, TS(str) },
+ { RS(EXIT_SIGNAL), f_stat, NULL, QS(s_int), 0, TS(s_int) },
+ { RS(FLAGS), f_stat, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(FLT_MAJ), f_stat, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(FLT_MAJ_C), f_stat, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(FLT_MAJ_DELTA), f_stat, NULL, QS(s_int), +1, TS(s_int) },
+ { RS(FLT_MIN), f_stat, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(FLT_MIN_C), f_stat, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(FLT_MIN_DELTA), f_stat, NULL, QS(s_int), +1, TS(s_int) },
+ { RS(ID_EGID), 0, NULL, QS(u_int), 0, TS(u_int) }, // oldflags: free w/ simple_read
+ { RS(ID_EGROUP), f_grp, NULL, QS(str), 0, TS(str) },
+ { RS(ID_EUID), 0, NULL, QS(u_int), 0, TS(u_int) }, // oldflags: free w/ simple_read
+ { RS(ID_EUSER), f_usr, NULL, QS(str), 0, TS(str) }, // freefunc NULL w/ cached string
+ { RS(ID_FGID), f_status, NULL, QS(u_int), 0, TS(u_int) },
+ { RS(ID_FGROUP), x_ogroup, NULL, QS(str), 0, TS(str) },
+ { RS(ID_FUID), f_status, NULL, QS(u_int), 0, TS(u_int) },
+ { RS(ID_FUSER), x_ouser, NULL, QS(str), 0, TS(str) }, // freefunc NULL w/ cached string
+ { RS(ID_LOGIN), f_login, NULL, QS(s_int), 0, TS(s_int) },
+ { RS(ID_PGRP), f_stat, NULL, QS(s_int), 0, TS(s_int) },
+ { RS(ID_PID), 0, NULL, QS(s_int), 0, TS(s_int) }, // oldflags: free w/ simple_nextpid
+ { RS(ID_PPID), f_either, NULL, QS(s_int), 0, TS(s_int) },
+ { RS(ID_RGID), f_status, NULL, QS(u_int), 0, TS(u_int) },
+ { RS(ID_RGROUP), x_ogroup, NULL, QS(str), 0, TS(str) },
+ { RS(ID_RUID), f_status, NULL, QS(u_int), 0, TS(u_int) },
+ { RS(ID_RUSER), x_ouser, NULL, QS(str), 0, TS(str) }, // freefunc NULL w/ cached string
+ { RS(ID_SESSION), f_stat, NULL, QS(s_int), 0, TS(s_int) },
+ { RS(ID_SGID), f_status, NULL, QS(u_int), 0, TS(u_int) },
+ { RS(ID_SGROUP), x_ogroup, NULL, QS(str), 0, TS(str) },
+ { RS(ID_SUID), f_status, NULL, QS(u_int), 0, TS(u_int) },
+ { RS(ID_SUSER), x_ouser, NULL, QS(str), 0, TS(str) }, // freefunc NULL w/ cached string
+ { RS(ID_TGID), 0, NULL, QS(s_int), 0, TS(s_int) }, // oldflags: free w/ simple_nextpid
+ { RS(ID_TID), 0, NULL, QS(s_int), 0, TS(s_int) }, // oldflags: free w/ simple_nexttid
+ { RS(ID_TPGID), f_stat, NULL, QS(s_int), 0, TS(s_int) },
+ { RS(IO_READ_BYTES), f_io, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(IO_READ_CHARS), f_io, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(IO_READ_OPS), f_io, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(IO_WRITE_BYTES), f_io, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(IO_WRITE_CBYTES), f_io, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(IO_WRITE_CHARS), f_io, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(IO_WRITE_OPS), f_io, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(LXCNAME), f_lxc, NULL, QS(str), 0, TS(str) }, // freefunc NULL w/ cached string
+ { RS(MEM_CODE), f_statm, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(MEM_CODE_PGS), f_statm, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(MEM_DATA), f_statm, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(MEM_DATA_PGS), f_statm, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(MEM_RES), f_statm, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(MEM_RES_PGS), f_statm, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(MEM_SHR), f_statm, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(MEM_SHR_PGS), f_statm, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(MEM_VIRT), f_statm, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(MEM_VIRT_PGS), f_statm, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(NICE), f_stat, NULL, QS(s_int), 0, TS(s_int) },
+ { RS(NLWP), f_either, NULL, QS(s_int), 0, TS(s_int) },
+ { RS(NS_CGROUP), f_ns, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(NS_IPC), f_ns, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(NS_MNT), f_ns, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(NS_NET), f_ns, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(NS_PID), f_ns, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(NS_TIME), f_ns, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(NS_USER), f_ns, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(NS_UTS), f_ns, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(OOM_ADJ), f_oom, NULL, QS(s_int), 0, TS(s_int) },
+ { RS(OOM_SCORE), f_oom, NULL, QS(s_int), 0, TS(s_int) },
+ { RS(PRIORITY), f_stat, NULL, QS(s_int), 0, TS(s_int) },
+ { RS(PRIORITY_RT), f_stat, NULL, QS(s_int), 0, TS(s_int) },
+ { RS(PROCESSOR), f_stat, NULL, QS(s_int), 0, TS(s_int) },
+ { RS(PROCESSOR_NODE), f_stat, NULL, QS(s_int), 0, TS(s_int) },
+ { RS(RSS), f_stat, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(RSS_RLIM), f_stat, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(SCHED_CLASS), f_stat, NULL, QS(s_int), 0, TS(s_int) },
+ { RS(SD_MACH), f_systemd, FF(str), QS(str), 0, TS(str) },
+ { RS(SD_OUID), f_systemd, FF(str), QS(str), 0, TS(str) },
+ { RS(SD_SEAT), f_systemd, FF(str), QS(str), 0, TS(str) },
+ { RS(SD_SESS), f_systemd, FF(str), QS(str), 0, TS(str) },
+ { RS(SD_SLICE), f_systemd, FF(str), QS(str), 0, TS(str) },
+ { RS(SD_UNIT), f_systemd, FF(str), QS(str), 0, TS(str) },
+ { RS(SD_UUNIT), f_systemd, FF(str), QS(str), 0, TS(str) },
+ { RS(SIGBLOCKED), f_status, FF(str), QS(str), 0, TS(str) },
+ { RS(SIGCATCH), f_status, FF(str), QS(str), 0, TS(str) },
+ { RS(SIGIGNORE), f_status, FF(str), QS(str), 0, TS(str) },
+ { RS(SIGNALS), f_status, FF(str), QS(str), 0, TS(str) },
+ { RS(SIGPENDING), f_status, FF(str), QS(str), 0, TS(str) },
+ { RS(SMAP_ANONYMOUS), f_smaps, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(SMAP_HUGE_ANON), f_smaps, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(SMAP_HUGE_FILE), f_smaps, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(SMAP_HUGE_SHMEM), f_smaps, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(SMAP_HUGE_TLBPRV), f_smaps, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(SMAP_HUGE_TLBSHR), f_smaps, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(SMAP_LAZY_FREE), f_smaps, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(SMAP_LOCKED), f_smaps, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(SMAP_PRV_CLEAN), f_smaps, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(SMAP_PRV_DIRTY), f_smaps, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(SMAP_PRV_TOTAL), f_smaps, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(SMAP_PSS), f_smaps, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(SMAP_PSS_ANON), f_smaps, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(SMAP_PSS_FILE), f_smaps, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(SMAP_PSS_SHMEM), f_smaps, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(SMAP_REFERENCED), f_smaps, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(SMAP_RSS), f_smaps, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(SMAP_SHR_CLEAN), f_smaps, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(SMAP_SHR_DIRTY), f_smaps, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(SMAP_SWAP), f_smaps, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(SMAP_SWAP_PSS), f_smaps, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(STATE), f_either, NULL, QS(s_ch), 0, TS(s_ch) },
+ { RS(SUPGIDS), f_status, FF(str), QS(str), 0, TS(str) },
+ { RS(SUPGROUPS), x_supgrp, FF(str), QS(str), 0, TS(str) },
+ { RS(TICS_ALL), f_stat, NULL, QS(ull_int), 0, TS(ull_int) },
+ { RS(TICS_ALL_C), f_stat, NULL, QS(ull_int), 0, TS(ull_int) },
+ { RS(TICS_ALL_DELTA), f_stat, NULL, QS(u_int), +1, TS(u_int) },
+ { RS(TICS_BEGAN), f_stat, NULL, QS(ull_int), 0, TS(ull_int) },
+ { RS(TICS_BLKIO), f_stat, NULL, QS(ull_int), 0, TS(ull_int) },
+ { RS(TICS_GUEST), f_stat, NULL, QS(ull_int), 0, TS(ull_int) },
+ { RS(TICS_GUEST_C), f_stat, NULL, QS(ull_int), 0, TS(ull_int) },
+ { RS(TICS_SYSTEM), f_stat, NULL, QS(ull_int), 0, TS(ull_int) },
+ { RS(TICS_SYSTEM_C), f_stat, NULL, QS(ull_int), 0, TS(ull_int) },
+ { RS(TICS_USER), f_stat, NULL, QS(ull_int), 0, TS(ull_int) },
+ { RS(TICS_USER_C), f_stat, NULL, QS(ull_int), 0, TS(ull_int) },
+ { RS(TIME_ALL), f_stat, NULL, QS(real), 0, TS(real) },
+ { RS(TIME_ALL_C), f_stat, NULL, QS(real), 0, TS(real) },
+ { RS(TIME_ELAPSED), f_stat, NULL, QS(real), 0, TS(real) },
+ { RS(TIME_START), f_stat, NULL, QS(real), 0, TS(real) },
+ { RS(TTY), f_stat, NULL, QS(s_int), 0, TS(s_int) },
+ { RS(TTY_NAME), f_stat, FF(str), QS(strvers), 0, TS(str) },
+ { RS(TTY_NUMBER), f_stat, FF(str), QS(strvers), 0, TS(str) },
+ { RS(UTILIZATION), f_stat, NULL, QS(real), 0, TS(real) },
+ { RS(UTILIZATION_C), f_stat, NULL, QS(real), 0, TS(real) },
+ { RS(VM_DATA), f_status, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(VM_EXE), f_status, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(VM_LIB), f_status, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(VM_RSS), f_status, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(VM_RSS_ANON), f_status, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(VM_RSS_FILE), f_status, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(VM_RSS_LOCKED), f_status, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(VM_RSS_SHARED), f_status, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(VM_SIZE), f_status, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(VM_STACK), f_status, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(VM_SWAP), f_status, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(VM_USED), f_status, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(VSIZE_BYTES), f_stat, NULL, QS(ul_int), 0, TS(ul_int) },
+ { RS(WCHAN_NAME), 0, FF(str), QS(str), 0, TS(str) }, // oldflags: tid already free
+};
+
+ /* please note,
+ * this enum MUST be 1 greater than the highest value of any enum */
+enum pids_item PIDS_logical_end = MAXTABLE(Item_table);
+
+#undef setNAME
+#undef freNAME
+#undef srtNAME
+#undef RS
+#undef FF
+#undef QS
+
+//#undef f_either // needed later
+#undef f_exe
+#undef f_grp
+#undef f_io
+#undef f_login
+#undef f_lxc
+#undef f_ns
+#undef f_oom
+#undef f_smaps
+//#undef f_stat // needed later
+#undef f_statm
+//#undef f_status // needed later
+#undef f_systemd
+#undef f_usr
+#undef v_arg
+#undef v_cgroup
+#undef v_env
+#undef x_cgroup
+#undef x_cmdline
+#undef x_environ
+#undef x_ogroup
+#undef x_ouser
+#undef x_supgrp
+#undef z_autogrp
+
+
+// ___ History Support Private Functions ||||||||||||||||||||||||||||||||||||||
+// ( stolen from top when he wasn't looking ) -------------------------------
+
+#define HHASH_SIZE 4096
+#define _HASH_PID_(K) (K & (HHASH_SIZE - 1))
+
+#define Hr(x) info->hist->x // 'hist ref', minimize stolen impact
+
+typedef unsigned long long TIC_t;
+
+typedef struct HST_t {
+ TIC_t tics; // last frame's tics count
+ unsigned long maj, min; // last frame's maj/min_flt counts
+ int pid; // record 'key'
+ int lnk; // next on hash chain
+} HST_t;
+
+
+struct history_info {
+ int num_tasks; // used as index (tasks tallied)
+ int HHist_siz; // max number of HST_t structs
+ HST_t *PHist_sav; // alternating 'old/new' HST_t anchors
+ HST_t *PHist_new;
+ int HHash_one [HHASH_SIZE]; // the actual hash tables
+ int HHash_two [HHASH_SIZE]; // (accessed via PHash_sav/PHash_new)
+ int HHash_nul [HHASH_SIZE]; // an 'empty' hash table image
+ int *PHash_sav; // alternating 'old/new' hash tables
+ int *PHash_new; // (aka. the 'one/two' actual tables)
+};
+
+
+static void pids_config_history (
+ struct pids_info *info)
+{
+ int i;
+
+ for (i = 0; i < HHASH_SIZE; i++) // make the 'empty' table image
+ Hr(HHash_nul[i]) = -1;
+ memcpy(Hr(HHash_one), Hr(HHash_nul), sizeof(Hr(HHash_nul)));
+ memcpy(Hr(HHash_two), Hr(HHash_nul), sizeof(Hr(HHash_nul)));
+ Hr(PHash_sav) = Hr(HHash_one); // alternating 'old/new' hash tables
+ Hr(PHash_new) = Hr(HHash_two);
+} // end: pids_config_history
+
+
+static inline HST_t *pids_histget (
+ struct pids_info *info,
+ int pid)
+{
+ int V = Hr(PHash_sav[_HASH_PID_(pid)]);
+
+ while (-1 < V) {
+ if (Hr(PHist_sav[V].pid) == pid)
+ return &Hr(PHist_sav[V]);
+ V = Hr(PHist_sav[V].lnk);
+ }
+ return NULL;
+} // end: pids_histget
+
+
+static inline void pids_histput (
+ struct pids_info *info,
+ unsigned this)
+{
+ int V = _HASH_PID_(Hr(PHist_new[this].pid));
+
+ Hr(PHist_new[this].lnk) = Hr(PHash_new[V]);
+ Hr(PHash_new[V] = this);
+} // end: pids_histput
+
+#undef _HASH_PID_
+
+
+static inline int pids_make_hist (
+ struct pids_info *info,
+ proc_t *p)
+{
+ TIC_t tics;
+ HST_t *h;
+ int slot = info->hist->num_tasks;
+
+ if (slot + 1 >= Hr(HHist_siz)) {
+ Hr(HHist_siz) += NEWOLD_GROW;
+ Hr(PHist_sav) = realloc(Hr(PHist_sav), sizeof(HST_t) * Hr(HHist_siz));
+ Hr(PHist_new) = realloc(Hr(PHist_new), sizeof(HST_t) * Hr(HHist_siz));
+ if (!Hr(PHist_sav) || !Hr(PHist_new))
+ return 0;
+ }
+ Hr(PHist_new[slot].pid) = p->tid;
+ Hr(PHist_new[slot].maj) = p->maj_flt;
+ Hr(PHist_new[slot].min) = p->min_flt;
+ Hr(PHist_new[slot].tics) = tics = (p->utime + p->stime);
+
+ pids_histput(info, slot);
+
+ if ((h = pids_histget(info, p->tid))) {
+ tics -= h->tics;
+ p->maj_delta = p->maj_flt - h->maj;
+ p->min_delta = p->min_flt - h->min;
+ }
+ /* here we're saving elapsed tics, which will include any
+ tasks not previously seen via that pids_histget() guy! */
+ p->pcpu = tics;
+
+ info->hist->num_tasks++;
+ return 1;
+} // end: pids_make_hist
+
+
+static inline void pids_toggle_history (
+ struct pids_info *info)
+{
+ void *v;
+
+ v = Hr(PHist_sav);
+ Hr(PHist_sav) = Hr(PHist_new);
+ Hr(PHist_new) = v;
+
+ v = Hr(PHash_sav);
+ Hr(PHash_sav) = Hr(PHash_new);
+ Hr(PHash_new) = v;
+ memcpy(Hr(PHash_new), Hr(HHash_nul), sizeof(Hr(HHash_nul)));
+
+ info->hist->num_tasks = 0;
+} // end: pids_toggle_history
+
+
+#ifdef UNREF_RPTHASH
+static void pids_unref_rpthash (
+ struct pids_info *info)
+{
+ int i, j, pop, total_occupied, maxdepth, maxdepth_sav, numdepth
+ , cross_foot, sz = HHASH_SIZE * (int)sizeof(int)
+ , hsz = (int)sizeof(HST_t) * Hr(HHist_siz);
+ int depths[HHASH_SIZE];
+
+ for (i = 0, total_occupied = 0, maxdepth = 0; i < HHASH_SIZE; i++) {
+ int V = Hr(PHash_new[i]);
+ j = 0;
+ if (-1 < V) {
+ ++total_occupied;
+ while (-1 < V) {
+ V = Hr(PHist_new[V].lnk);
+ if (-1 < V) j++;
+ }
+ }
+ depths[i] = j;
+ if (maxdepth < j) maxdepth = j;
+ }
+ maxdepth_sav = maxdepth;
+
+ fprintf(stderr,
+ "\n History Memory Costs:"
+ "\n\tHST_t size = %d, total allocated = %d,"
+ "\n\tthus PHist_new & PHist_sav consumed %dk (%d) total bytes."
+ "\n"
+ "\n\tTwo hash tables provide for %d entries each + 1 extra 'empty' image,"
+ "\n\tthus %dk (%d) bytes per table for %dk (%d) total bytes."
+ "\n"
+ "\n\tGrand total = %dk (%d) bytes."
+ "\n"
+ "\n Hash Results Report:"
+ "\n\tTotal hashed = %d"
+ "\n\tLevel-0 hash entries = %d (%d%% occupied)"
+ "\n\tMax Depth = %d"
+ "\n\n"
+ , (int)sizeof(HST_t), Hr(HHist_siz)
+ , hsz / 1024, hsz
+ , HHASH_SIZE
+ , sz / 1024, sz, (sz * 3) / 1024, sz * 3
+ , (hsz + (sz * 3)) / 1024, hsz + (sz * 3)
+ , info->hist->num_tasks
+ , total_occupied, (total_occupied * 100) / HHASH_SIZE
+ , maxdepth);
+
+ if (total_occupied) {
+ for (pop = total_occupied, cross_foot = 0; maxdepth; maxdepth--) {
+ for (i = 0, numdepth = 0; i < HHASH_SIZE; i++)
+ if (depths[i] == maxdepth) ++numdepth;
+ if (numdepth) fprintf(stderr,
+ "\t %5d (%3d%%) hash table entries at depth %d\n"
+ , numdepth, (numdepth * 100) / total_occupied, maxdepth);
+ pop -= numdepth;
+ cross_foot += numdepth;
+ if (0 == pop && cross_foot == total_occupied) break;
+ }
+ if (pop) {
+ fprintf(stderr, "\t %5d (%3d%%) unchained entries (at depth 0)\n"
+ , pop, (pop * 100) / total_occupied);
+ cross_foot += pop;
+ }
+ fprintf(stderr,
+ "\t -----\n"
+ "\t %5d total entries occupied\n", cross_foot);
+
+ if (maxdepth_sav > 1) {
+ fprintf(stderr, "\n PIDs at max depth: ");
+ for (i = 0; i < HHASH_SIZE; i++)
+ if (depths[i] == maxdepth_sav) {
+ j = Hr(PHash_new[i]);
+ fprintf(stderr, "\n\tpos %4d: %05d", i, Hr(PHist_new[j].pid));
+ while (-1 < j) {
+ j = Hr(PHist_new[j].lnk);
+ if (-1 < j) fprintf(stderr, ", %05d", Hr(PHist_new[j].pid));
+ }
+ }
+ fprintf(stderr, "\n");
+ }
+ }
+} // end: pids_unref_rpthash
+#endif // UNREF_RPTHASH
+
+#undef Hr
+#undef HHASH_SIZE
+
+
+// ___ Standard Private Functions |||||||||||||||||||||||||||||||||||||||||||||
+
+static inline int pids_assign_results (
+ struct pids_info *info,
+ struct pids_stack *stack,
+ proc_t *p)
+{
+ struct pids_result *this = stack->head;
+ SET_t *that = &info->func_array[0];
+
+ info->seterr = 0;
+ while (*that) {
+ (*that)(info, this, p);
+ ++this;
+ ++that;
+ }
+ return !info->seterr;
+} // end: pids_assign_results
+
+
+static inline void pids_cleanup_stack (
+ struct pids_result *this)
+{
+ for (;;) {
+ enum pids_item item = this->item;
+ if (item >= PIDS_logical_end)
+ break;
+ if (Item_table[item].freefunc)
+ Item_table[item].freefunc(this);
+ this->result.ull_int = 0;
+ ++this;
+ }
+} // end: pids_cleanup_stack
+
+
+static inline void pids_cleanup_stacks_all (
+ struct pids_info *info)
+{
+ struct stacks_extent *ext = info->extents;
+ int i;
+
+ while (ext) {
+ for (i = 0; ext->stacks[i]; i++)
+ pids_cleanup_stack(ext->stacks[i]->head);
+ ext = ext->next;
+ };
+} // end: pids_cleanup_stacks_all
+
+
+#if 0 // not currently needed after 'fatal_proc_unmounted' was refactored
+ /*
+ * This routine exists in case we ever want to offer something like
+ * 'static' or 'invarient' results stacks. By unsplicing an extent
+ * from the info anchor it will be isolated from future reset/free. */
+static struct stacks_extent *pids_extent_cut (
+ struct pids_info *info,
+ struct stacks_extent *ext)
+{
+ struct stacks_extent *p = info->extents;
+
+ if (ext) {
+ if (ext == p) {
+ info->extents = p->next;
+ return ext;
+ }
+ do {
+ if (ext == p->next) {
+ p->next = p->next->next;
+ return ext;
+ }
+ p = p->next;
+ } while (p);
+ }
+ return NULL;
+} // end: pids_extent_cut
+#endif // ----------------------------------------------------------------
+
+
+static inline struct pids_result *pids_itemize_stack (
+ struct pids_result *p,
+ int depth,
+ enum pids_item *items)
+{
+ struct pids_result *p_sav = p;
+ int i;
+
+ for (i = 0; i < depth; i++) {
+ p->item = items[i];
+ ++p;
+ }
+ return p_sav;
+} // end: pids_itemize_stack
+
+
+static void pids_itemize_stacks_all (
+ struct pids_info *info)
+{
+ struct stacks_extent *ext = info->extents;
+
+ while (ext) {
+ int i;
+ for (i = 0; ext->stacks[i]; i++)
+ pids_itemize_stack(ext->stacks[i]->head, info->maxitems, info->items);
+ ext = ext->next;
+ };
+} // end: pids_itemize_stacks_all
+
+
+static inline int pids_items_check_failed (
+ enum pids_item *items,
+ int numitems)
+{
+ int i;
+
+ /* if an enum is passed instead of an address of one or more enums, ol' gcc
+ * will silently convert it to an address (possibly NULL). only clang will
+ * offer any sort of warning like the following:
+ *
+ * warning: incompatible integer to pointer conversion passing 'int' to parameter of type 'enum pids_item *'
+ * if (procps_pids_new(&info, PIDS_noop, 3) < 0)
+ * ^~~~~~~~~~~~~~~~
+ */
+ if (numitems < 1
+ || (void *)items < (void *)0x8000) // twice as big as our largest enum
+ return 1;
+
+ for (i = 0; i < numitems; i++) {
+ // a pids_item is currently unsigned, but we'll protect our future
+ if (items[i] < 0)
+ return 1;
+ if (items[i] >= PIDS_logical_end) {
+ return 1;
+ }
+ }
+ return 0;
+} // end: pids_items_check_failed
+
+
+static inline void pids_libflags_set (
+ struct pids_info *info)
+{
+ enum pids_item e;
+ int i;
+
+ info->oldflags = info->history_yes = 0;
+ for (i = 0; i < info->maxitems; i++) {
+ if (((e = info->items[i])) >= PIDS_logical_end)
+ break;
+ info->oldflags |= Item_table[e].oldflags;
+ info->history_yes |= Item_table[e].needhist;
+ }
+ if (info->oldflags & f_either) {
+ if (!(info->oldflags & (f_stat | f_status)))
+ info->oldflags |= f_stat;
+ }
+ return;
+} // end: pids_libflags_set
+
+
+static inline void pids_oldproc_close (
+ PROCTAB **this)
+{
+ if (*this != NULL) {
+ int errsav = errno;
+ closeproc(*this);
+ *this = NULL;
+ errno = errsav;
+ }
+} // end: pids_oldproc_close
+
+
+static inline int pids_oldproc_open (
+ PROCTAB **this,
+ unsigned flags,
+ ...)
+{
+ va_list vl;
+ int *ids;
+ int num = 0;
+
+ if (*this == NULL) {
+ va_start(vl, flags);
+ ids = va_arg(vl, int*);
+ if (flags & PROC_UID) num = va_arg(vl, int);
+ va_end(vl);
+ if (NULL == (*this = openproc(flags, ids, num)))
+ return 0;
+ }
+ return 1;
+} // end: pids_oldproc_open
+
+
+static int pids_prep_func_array (
+ struct pids_info *info)
+{
+ int i;
+
+ if (!(info->func_array = realloc(info->func_array, sizeof(SET_t) * info->maxitems)))
+ return 0;
+ for (i = 0; i < info->maxitems -1; i++)
+ info->func_array[i] = Item_table[info->items[i]].setsfunc;
+ info->func_array[i] = NULL;
+ return 1;
+} // end: pids_prep_func_array
+
+
+static inline int pids_proc_tally (
+ struct pids_info *info,
+ struct pids_counts *counts,
+ proc_t *p)
+{
+ switch (p->state) {
+ case 'R':
+ ++counts->running;
+ break;
+ case 'D': // 'D' (disk sleep)
+ case 'S':
+ ++counts->sleeping;
+ break;
+ case 't': // 't' (tracing stop)
+ case 'T':
+ ++counts->stopped;
+ break;
+ case 'Z':
+ ++counts->zombied;
+ break;
+ default:
+ /* currently: 'I' (idle),
+ 'P' (parked),
+ 'X' (dead - actually 'dying' & probably never seen)
+ */
+ ++counts->other;
+ break;
+ }
+ ++counts->total;
+
+ if (info->history_yes)
+ return pids_make_hist(info, p);
+ return 1;
+} // end: pids_proc_tally
+
+
+/*
+ * pids_stacks_alloc():
+ *
+ * Allocate and initialize one or more stacks each of which is anchored in an
+ * associated context structure.
+ *
+ * All such stacks will will have their result structures properly primed with
+ * 'items', while the result itself will be zeroed.
+ *
+ * Returns an array of pointers representing the 'heads' of each new stack.
+ */
+static struct stacks_extent *pids_stacks_alloc (
+ struct pids_info *info,
+ int maxstacks)
+{
+ struct stacks_extent *p_blob;
+ struct pids_stack **p_vect;
+ struct pids_stack *p_head;
+ size_t vect_size, head_size, list_size, blob_size;
+ void *v_head, *v_list;
+ int i;
+
+ vect_size = sizeof(void *) * maxstacks; // size of the addr vectors |
+ vect_size += sizeof(void *); // plus NULL addr delimiter |
+ head_size = sizeof(struct pids_stack); // size of that head struct |
+ list_size = sizeof(struct pids_result) * info->maxitems; // any single results stack |
+ blob_size = sizeof(struct stacks_extent); // the extent anchor itself |
+ blob_size += vect_size; // plus room for addr vects |
+ blob_size += head_size * maxstacks; // plus room for head thing |
+ blob_size += list_size * maxstacks; // plus room for our stacks |
+
+ /* note: all of our memory is allocated in a single blob, facilitating a later free(). |
+ as a minimum, it is important that the result structures themselves always be |
+ contiguous for every stack since they are accessed through relative position. | */
+ if (NULL == (p_blob = calloc(1, blob_size)))
+ return NULL;
+
+ p_blob->next = info->extents; // push this extent onto... |
+ info->extents = p_blob; // ...some existing extents |
+ p_vect = (void *)p_blob + sizeof(struct stacks_extent); // prime our vector pointer |
+ p_blob->stacks = p_vect; // set actual vectors start |
+ v_head = (void *)p_vect + vect_size; // prime head pointer start |
+ v_list = v_head + (head_size * maxstacks); // prime our stacks pointer |
+
+ for (i = 0; i < maxstacks; i++) {
+ p_head = (struct pids_stack *)v_head;
+ p_head->head = pids_itemize_stack((struct pids_result *)v_list, info->maxitems, info->items);
+ p_blob->stacks[i] = p_head;
+ v_list += list_size;
+ v_head += head_size;
+ }
+ p_blob->ext_numstacks = maxstacks;
+ return p_blob;
+} // end: pids_stacks_alloc
+
+
+static int pids_stacks_fetch (
+ struct pids_info *info)
+{
+ #define n_alloc info->fetch.n_alloc
+ #define n_inuse info->fetch.n_inuse
+ #define n_saved info->fetch.n_alloc_save
+ struct stacks_extent *ext;
+
+ // initialize stuff -----------------------------------
+ if (!info->fetch.anchor) {
+ if (!(info->fetch.anchor = calloc(STACKS_INIT, sizeof(void *))))
+ return -1;
+ if (!(ext = pids_stacks_alloc(info, STACKS_INIT)))
+ return -1; // here, errno was set to ENOMEM
+ memcpy(info->fetch.anchor, ext->stacks, sizeof(void *) * STACKS_INIT);
+ n_alloc = STACKS_INIT;
+ }
+ pids_toggle_history(info);
+ memset(&info->fetch.counts, 0, sizeof(struct pids_counts));
+
+ // iterate stuff --------------------------------------
+ n_inuse = 0;
+ while (info->read_something(info->fetch_PT, &info->fetch_proc)) {
+ if (!(n_inuse < n_alloc)) {
+ n_alloc += STACKS_GROW;
+ if (!(info->fetch.anchor = realloc(info->fetch.anchor, sizeof(void *) * n_alloc))
+ || (!(ext = pids_stacks_alloc(info, STACKS_GROW))))
+ return -1; // here, errno was set to ENOMEM
+ memcpy(info->fetch.anchor + n_inuse, ext->stacks, sizeof(void *) * STACKS_GROW);
+ }
+ if (!pids_proc_tally(info, &info->fetch.counts, &info->fetch_proc))
+ return -1; // here, errno was set to ENOMEM
+ if (!pids_assign_results(info, info->fetch.anchor[n_inuse++], &info->fetch_proc))
+ return -1; // here, errno was set to ENOMEM
+ }
+ /* while the possibility is extremely remote, the readproc.c (read_something) |
+ simple_readproc and simple_readtask guys could have encountered this error |
+ in which case they would have returned a NULL, thus ending our while loop. | */
+ if (errno == ENOMEM)
+ return -1;
+
+ // finalize stuff -------------------------------------
+ /* note: we go to this trouble of maintaining a duplicate of the consolidated |
+ extent stacks addresses represented as our 'anchor' since these ptrs |
+ are exposed to a user (um, not that we don't trust 'em or anything). |
+ plus, we can NULL delimit these ptrs which we couldn't do otherwise. | */
+ if (n_saved < n_inuse + 1) {
+ n_saved = n_inuse + 1;
+ if (!(info->fetch.results.stacks = realloc(info->fetch.results.stacks, sizeof(void *) * n_saved)))
+ return -1;
+ }
+ memcpy(info->fetch.results.stacks, info->fetch.anchor, sizeof(void *) * n_inuse);
+ info->fetch.results.stacks[n_inuse] = NULL;
+
+ return n_inuse; // callers beware, this might be zero !
+ #undef n_alloc
+ #undef n_inuse
+ #undef n_saved
+} // end: pids_stacks_fetch
+
+
+// ___ Public Functions |||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
+// --- standard required functions --------------------------------------------
+
+/*
+ * procps_pids_new():
+ *
+ * @info: location of returned new structure
+ *
+ * Returns: < 0 on failure, 0 on success along with
+ * a pointer to a new context struct
+ */
+PROCPS_EXPORT int procps_pids_new (
+ struct pids_info **info,
+ enum pids_item *items,
+ int numitems)
+{
+ struct pids_info *p;
+ int pgsz;
+
+#ifdef ITEMTABLE_DEBUG
+ int i, failed = 0;
+ for (i = 0; i < MAXTABLE(Item_table); i++) {
+ if (i != Item_table[i].enumnumb) {
+ fprintf(stderr, "%s: enum/table error: Item_table[%d] was %s, but its value is %d\n"
+ , __FILE__, i, Item_table[i].enum2str, Item_table[i].enumnumb);
+ failed = 1;
+ }
+ }
+ if (PIDS_SELECT_PID != PROC_PID) {
+ fprintf(stderr, "%s: header error: PIDS_SELECT_PID = 0x%04x, PROC_PID = 0x%04x\n"
+ , __FILE__, PIDS_SELECT_PID, PROC_PID);
+ failed = 1;
+ }
+ if (PIDS_SELECT_PID_THREADS != PIDS_SELECT_PID + 1) {
+ fprintf(stderr, "%s: header error: PIDS_SELECT_PID_THREADS = 0x%04x, should be 0x%04x\n"
+ , __FILE__, PIDS_SELECT_PID_THREADS, PIDS_SELECT_PID + 1);
+ failed = 1;
+ }
+ if (PIDS_SELECT_UID != PROC_UID) {
+ fprintf(stderr, "%s: header error: PIDS_SELECT_UID = 0x%04x, PROC_UID = 0x%04x\n"
+ , __FILE__, PIDS_SELECT_UID, PROC_UID);
+ failed = 1;
+ }
+ if (PIDS_SELECT_UID_THREADS != PIDS_SELECT_UID + 1) {
+ fprintf(stderr, "%s: header error: PIDS_SELECT_UID_THREADS = 0x%04x, should be 0x%04x\n"
+ , __FILE__, PIDS_SELECT_UID_THREADS, PIDS_SELECT_UID + 1);
+ failed = 1;
+ }
+ // our select() function & select enumerators assume the following ...
+ if (PIDS_FETCH_THREADS_TOO != 1) {
+ fprintf(stderr, "%s: header error: PIDS_FETCH_THREADS_TOO = %d, should be 1\n"
+ , __FILE__, PIDS_FETCH_THREADS_TOO);
+ failed = 1;
+ }
+ if (failed) _Exit(EXIT_FAILURE);
+#endif
+
+ if (info == NULL || *info != NULL)
+ return -EINVAL;
+ if (!(p = calloc(1, sizeof(struct pids_info))))
+ return -ENOMEM;
+
+ /* if we're without items or numitems, a later call to
+ procps_pids_reset() will become mandatory */
+ if (items && numitems) {
+ if (pids_items_check_failed(items, numitems)) {
+ free(p);
+ return -EINVAL;
+ }
+ // allow for our PIDS_logical_end
+ p->maxitems = numitems + 1;
+ if (!(p->items = calloc(p->maxitems, sizeof(enum pids_item)))) {
+ free(p);
+ return -ENOMEM;
+ }
+ memcpy(p->items, items, sizeof(enum pids_item) * numitems);
+ p->items[numitems] = PIDS_logical_end;
+ pids_libflags_set(p);
+ if (!pids_prep_func_array(p))
+ return -ENOMEM;
+ }
+
+ if (!(p->hist = calloc(1, sizeof(struct history_info)))
+ || (!(p->hist->PHist_new = calloc(NEWOLD_INIT, sizeof(HST_t))))
+ || (!(p->hist->PHist_sav = calloc(NEWOLD_INIT, sizeof(HST_t))))) {
+ free(p->items);
+ if (p->hist) {
+ free(p->hist->PHist_sav); // this & next might be NULL ...
+ free(p->hist->PHist_new);
+ free(p->hist);
+ }
+ free(p);
+ return -ENOMEM;
+ }
+ p->hist->HHist_siz = NEWOLD_INIT;
+ pids_config_history(p);
+
+ pgsz = getpagesize();
+ while (pgsz > 1024) { pgsz >>= 1; p->pgs2k_shift++; }
+ p->hertz = procps_hertz_get();
+
+ numa_init();
+
+ p->fetch.results.counts = &p->fetch.counts;
+
+ p->refcount = 1;
+ *info = p;
+ return 0;
+} // end: procps_pids_new
+
+
+PROCPS_EXPORT int procps_pids_ref (
+ struct pids_info *info)
+{
+ if (info == NULL)
+ return -EINVAL;
+
+ info->refcount++;
+ return info->refcount;
+} // end: procps_pids_ref
+
+
+PROCPS_EXPORT int procps_pids_unref (
+ struct pids_info **info)
+{
+ if (info == NULL || *info == NULL)
+ return -EINVAL;
+
+ (*info)->refcount--;
+
+ if ((*info)->refcount < 1) {
+#ifdef UNREF_RPTHASH
+ pids_unref_rpthash(*info);
+#endif
+ if ((*info)->extents) {
+ pids_cleanup_stacks_all(*info);
+ do {
+ struct stacks_extent *p = (*info)->extents;
+ (*info)->extents = (*info)->extents->next;
+ free(p);
+ } while ((*info)->extents);
+ }
+ if ((*info)->otherexts) {
+ struct stacks_extent *nextext, *ext = (*info)->otherexts;
+ while (ext) {
+ nextext = ext->next;
+ pids_cleanup_stack(ext->stacks[0]->head);
+ free(ext);
+ ext = nextext;
+ };
+ }
+ if ((*info)->fetch.anchor)
+ free((*info)->fetch.anchor);
+ if ((*info)->fetch.results.stacks)
+ free((*info)->fetch.results.stacks);
+
+ if ((*info)->items)
+ free((*info)->items);
+ if ((*info)->hist) {
+ free((*info)->hist->PHist_sav);
+ free((*info)->hist->PHist_new);
+ free((*info)->hist);
+ }
+
+ if ((*info)->get_ext)
+ pids_oldproc_close(&(*info)->get_PT);
+
+ if ((*info)->func_array)
+ free((*info)->func_array);
+
+ numa_uninit();
+
+ free(*info);
+ *info = NULL;
+ return 0;
+ }
+ return (*info)->refcount;
+} // end: procps_pids_unref
+
+
+// --- variable interface functions -------------------------------------------
+
+PROCPS_EXPORT struct pids_stack *fatal_proc_unmounted (
+ struct pids_info *info,
+ int return_self)
+{
+ struct pids_fetch *fetched;
+ unsigned tid;
+
+ /* this is very likely the *only* newlib function where the
+ context (pids_info) of NULL will ever be permitted */
+ if (!look_up_our_self()
+ || (!return_self))
+ return NULL;
+
+ tid = getpid();
+ if (!(fetched = procps_pids_select(info, &tid, 1, PIDS_SELECT_PID)))
+ return NULL;
+ return fetched->stacks[0];
+} // end: fatal_proc_unmounted
+
+
+PROCPS_EXPORT struct pids_stack *procps_pids_get (
+ struct pids_info *info,
+ enum pids_fetch_type which)
+{
+ double up_secs;
+
+ errno = EINVAL;
+ if (info == NULL)
+ return NULL;
+ if (which != PIDS_FETCH_TASKS_ONLY && which != PIDS_FETCH_THREADS_TOO)
+ return NULL;
+ /* with items & numitems technically optional at 'new' time, it's
+ expected 'reset' will have been called -- but just in case ... */
+ if (!info->maxitems)
+ return NULL;
+
+ if (!info->get_ext) {
+ if (!(info->get_ext = pids_stacks_alloc(info, 1)))
+ return NULL; // here, errno was overridden with ENOMEM
+fresh_start:
+ if (!pids_oldproc_open(&info->get_PT, info->oldflags))
+ return NULL; // here, errno was overridden with ENOMEM/others
+ info->get_type = which;
+ info->read_something = which ? readeither : readproc;
+ }
+
+ if (info->get_type != which) {
+ pids_oldproc_close(&info->get_PT);
+ goto fresh_start;
+ }
+ errno = 0;
+
+ /* when in a namespace with proc mounted subset=pid,
+ we will be restricted to process information only */
+ info->boot_tics = 0;
+ if (0 >= procps_uptime(&up_secs, NULL))
+ info->boot_tics = up_secs * info->hertz;
+
+ if (NULL == info->read_something(info->get_PT, &info->get_proc))
+ return NULL;
+ if (!pids_assign_results(info, info->get_ext->stacks[0], &info->get_proc))
+ return NULL;
+ return info->get_ext->stacks[0];
+} // end: procps_pids_get
+
+
+/* procps_pids_reap():
+ *
+ * Harvest all the available tasks/threads and provide the result
+ * stacks along with a summary of the information gathered.
+ *
+ * Returns: pointer to a pids_fetch struct on success, NULL on error.
+ */
+PROCPS_EXPORT struct pids_fetch *procps_pids_reap (
+ struct pids_info *info,
+ enum pids_fetch_type which)
+{
+ double up_secs;
+ int rc;
+
+ errno = EINVAL;
+ if (info == NULL)
+ return NULL;
+ if (which != PIDS_FETCH_TASKS_ONLY && which != PIDS_FETCH_THREADS_TOO)
+ return NULL;
+ /* with items & numitems technically optional at 'new' time, it's
+ expected 'reset' will have been called -- but just in case ... */
+ if (!info->maxitems)
+ return NULL;
+ errno = 0;
+
+ if (!pids_oldproc_open(&info->fetch_PT, info->oldflags))
+ return NULL;
+ info->read_something = which ? readeither : readproc;
+
+ /* when in a namespace with proc mounted subset=pid,
+ we will be restricted to process information only */
+ info->boot_tics = 0;
+ if (0 >= procps_uptime(&up_secs, NULL))
+ info->boot_tics = up_secs * info->hertz;
+
+ rc = pids_stacks_fetch(info);
+
+ pids_oldproc_close(&info->fetch_PT);
+ // we better have found at least 1 pid
+ return (rc > 0) ? &info->fetch.results : NULL;
+} // end: procps_pids_reap
+
+
+PROCPS_EXPORT int procps_pids_reset (
+ struct pids_info *info,
+ enum pids_item *newitems,
+ int newnumitems)
+{
+ if (info == NULL || newitems == NULL)
+ return -EINVAL;
+ if (pids_items_check_failed(newitems, newnumitems))
+ return -EINVAL;
+
+ pids_cleanup_stacks_all(info);
+
+ /* shame on this caller, they didn't change anything. and unless they have
+ altered the depth of the stacks we're not gonna change anything either! */
+ if (info->maxitems == newnumitems + 1
+ && !memcmp(info->items, newitems, sizeof(enum pids_item) * newnumitems))
+ return 0;
+
+ if (info->maxitems < newnumitems + 1) {
+ while (info->extents) {
+ struct stacks_extent *p = info->extents;
+ info->extents = p->next;
+ free(p);
+ };
+ if (info->get_ext) {
+ pids_oldproc_close(&info->get_PT);
+ info->get_ext = NULL;
+ }
+ if (info->fetch.anchor) {
+ free(info->fetch.anchor);
+ info->fetch.anchor = NULL;
+ }
+ // allow for our PIDS_logical_end
+ info->maxitems = newnumitems + 1;
+ if (!(info->items = realloc(info->items, sizeof(enum pids_item) * info->maxitems)))
+ return -ENOMEM;
+ }
+
+ memcpy(info->items, newitems, sizeof(enum pids_item) * newnumitems);
+ info->items[newnumitems] = PIDS_logical_end;
+ // account for above PIDS_logical_end
+ info->maxitems = newnumitems + 1;
+
+ // if extents were freed above, this next guy will have no effect
+ // so we'll rely on pids_stacks_alloc() to itemize ...
+ pids_itemize_stacks_all(info);
+ pids_libflags_set(info);
+ if (!pids_prep_func_array(info))
+ return -ENOMEM;
+
+ return 0;
+} // end: procps_pids_reset
+
+
+/* procps_pids_select():
+ *
+ * Harvest any processes matching the specified PID or UID and provide the
+ * result stacks along with a summary of the information gathered.
+ *
+ * Returns: pointer to a pids_fetch struct on success, NULL on error.
+ */
+PROCPS_EXPORT struct pids_fetch *procps_pids_select (
+ struct pids_info *info,
+ unsigned *these,
+ int numthese,
+ enum pids_select_type which)
+{
+ unsigned ids[FILL_ID_MAX + 1];
+ double up_secs;
+ int rc;
+
+ errno = EINVAL;
+ if (info == NULL || these == NULL)
+ return NULL;
+ if (numthese < 1 || numthese > FILL_ID_MAX)
+ return NULL;
+ if ((which != PIDS_SELECT_PID && which != PIDS_SELECT_UID)
+ && ((which != PIDS_SELECT_PID_THREADS && which != PIDS_SELECT_UID_THREADS)))
+ return NULL;
+ /* with items & numitems technically optional at 'new' time, it's
+ expected 'reset' will have been called -- but just in case ... */
+ if (!info->maxitems)
+ return NULL;
+ errno = 0;
+
+ // this zero delimiter is really only needed with PIDS_SELECT_PID
+ memcpy(ids, these, sizeof(unsigned) * numthese);
+ ids[numthese] = 0;
+
+ if (!pids_oldproc_open(&info->fetch_PT, (info->oldflags | which), ids, numthese))
+ return NULL;
+ info->read_something = (which & PIDS_FETCH_THREADS_TOO) ? readeither : readproc;
+
+ /* when in a namespace with proc mounted subset=pid,
+ we will be restricted to process information only */
+ info->boot_tics = 0;
+ if (0 >= procps_uptime(&up_secs, NULL))
+ info->boot_tics = up_secs * info->hertz;
+
+ rc = pids_stacks_fetch(info);
+
+ pids_oldproc_close(&info->fetch_PT);
+ // no guarantee any pids/uids were found
+ return (rc >= 0) ? &info->fetch.results : NULL;
+} // end: procps_pids_select
+
+
+/*
+ * procps_pids_sort():
+ *
+ * Sort stacks anchored in the passed stack pointers array
+ * based on the designated sort enumerator and specified order.
+ *
+ * Returns those same addresses sorted.
+ *
+ * Note: all of the stacks must be homogeneous (of equal length and content).
+ */
+PROCPS_EXPORT struct pids_stack **procps_pids_sort (
+ struct pids_info *info,
+ struct pids_stack *stacks[],
+ int numstacked,
+ enum pids_item sortitem,
+ enum pids_sort_order order)
+{
+ struct sort_parms parms;
+ struct pids_result *p;
+ int offset;
+
+ errno = EINVAL;
+ if (info == NULL || stacks == NULL)
+ return NULL;
+ // a pids_item is currently unsigned, but we'll protect our future
+ if (sortitem < 0 || sortitem >= PIDS_logical_end)
+ return NULL;
+ if (order != PIDS_SORT_ASCEND && order != PIDS_SORT_DESCEND)
+ return NULL;
+ if (numstacked < 2)
+ return stacks;
+
+ offset = 0;
+ p = stacks[0]->head;
+ for (;;) {
+ if (p->item == sortitem)
+ break;
+ ++offset;
+ if (offset >= info->maxitems)
+ return NULL;
+ if (p->item >= PIDS_logical_end)
+ return NULL;
+ ++p;
+ }
+ errno = 0;
+
+ parms.offset = offset;
+ parms.order = order;
+
+ qsort_r(stacks, numstacked, sizeof(void *), (QSR_t)Item_table[p->item].sortfunc, &parms);
+ return stacks;
+} // end: procps_pids_sort
+
+
+// --- special debugging function(s) ------------------------------------------
+/*
+ * The following isn't part of the normal programming interface. Rather,
+ * it exists to validate result types referenced in application programs.
+ *
+ * It's used only when:
+ * 1) the 'XTRA_PROCPS_DEBUG' has been defined, or
+ * 2) an #include of 'xtra-procps-debug.h' is used
+ */
+
+PROCPS_EXPORT struct pids_result *xtra_pids_val (
+ int relative_enum,
+ const char *typestr,
+ const struct pids_stack *stack,
+ struct pids_info *info,
+ const char *file,
+ int lineno)
+{
+ char *str;
+ int i;
+
+ for (i = 0; stack->head[i].item < PIDS_logical_end; i++)
+ ;
+ if (relative_enum < 0 || relative_enum >= i) {
+ fprintf(stderr, "%s line %d: invalid relative_enum = %d, valid range = 0-%d\n"
+ , file, lineno, relative_enum, i-1);
+ return NULL;
+ }
+ str = Item_table[stack->head[relative_enum].item].type2str;
+ if (str[0]
+ && (strcmp(typestr, str))) {
+ fprintf(stderr, "%s line %d: was %s, expected %s\n", file, lineno, typestr, str);
+ }
+ return &stack->head[relative_enum];
+ (void)info;
+} // end: xtra_pids_val
diff --git a/library/pwcache.c b/library/pwcache.c
new file mode 100644
index 0000000..f75e207
--- /dev/null
+++ b/library/pwcache.c
@@ -0,0 +1,99 @@
+/*
+ * pwcache.c - memory cache passwd file handling
+ *
+ * Copyright © 2011-2023 Jim Warner <james.warner@comcast.net>
+ * Copyright © 2015-2023 Craig Small <csmall@dropbear.xyz>
+ * Copyright © 2002 Albert Cahalan
+ *
+ * Older version:
+ * Copyright © 1992-1998 Michael K. Johnson <johnsonm@redhat.com>
+ * Note: most likely none of his code remains
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <stdlib.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include "pwcache.h"
+#include "procps-private.h"
+
+// might as well fill cache lines... else we waste memory anyway
+
+#define HASHSIZE 64 /* power of 2 */
+#define HASH(x) ((x) & (HASHSIZE - 1))
+
+static char ERRname[] = "?";
+
+static __thread struct pwbuf {
+ struct pwbuf *next;
+ uid_t uid;
+ char name[P_G_SZ];
+} *pwhash[HASHSIZE];
+
+char *pwcache_get_user(uid_t uid) {
+ struct pwbuf **p;
+ struct passwd *pw;
+
+ p = &pwhash[HASH(uid)];
+ while (*p) {
+ if ((*p)->uid == uid)
+ return((*p)->name);
+ p = &(*p)->next;
+ }
+ if (!(*p = (struct pwbuf *)malloc(sizeof(struct pwbuf))))
+ return ERRname;
+ (*p)->uid = uid;
+ pw = getpwuid(uid);
+ if(!pw || strlen(pw->pw_name) >= P_G_SZ)
+ sprintf((*p)->name, "%u", uid);
+ else
+ strcpy((*p)->name, pw->pw_name);
+
+ (*p)->next = NULL;
+ return((*p)->name);
+}
+
+static __thread struct grpbuf {
+ struct grpbuf *next;
+ gid_t gid;
+ char name[P_G_SZ];
+} *grphash[HASHSIZE];
+
+char *pwcache_get_group(gid_t gid) {
+ struct grpbuf **g;
+ struct group *gr;
+
+ g = &grphash[HASH(gid)];
+ while (*g) {
+ if ((*g)->gid == gid)
+ return((*g)->name);
+ g = &(*g)->next;
+ }
+ if (!(*g = (struct grpbuf *)malloc(sizeof(struct grpbuf))))
+ return ERRname;;
+ (*g)->gid = gid;
+ gr = getgrgid(gid);
+ if (!gr || strlen(gr->gr_name) >= P_G_SZ)
+ sprintf((*g)->name, "%u", gid);
+ else
+ strcpy((*g)->name, gr->gr_name);
+ (*g)->next = NULL;
+ return((*g)->name);
+}
diff --git a/library/readproc.c b/library/readproc.c
new file mode 100644
index 0000000..2dfe4c9
--- /dev/null
+++ b/library/readproc.c
@@ -0,0 +1,1627 @@
+/*
+ * readproc - interface to process table
+ *
+ * Copyright © 2002-2023 Craig Small <csmall@dropbear.xyz>
+ * Copyright © 2011-2023 Jim Warner <james.warner@comcast.net>
+ * Copyright © 1998-2010 Albert Cahalan
+ * Copyright © 2010-2011 Jan Görig <jgorig@redhat.com>
+ * Copyright © 1998 Michael K. Johnson
+ * Copyright © 1996 Charles L. Blake.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <limits.h>
+#include <stdint.h>
+#ifdef WITH_SYSTEMD
+#include <systemd/sd-login.h>
+#endif
+#ifdef WITH_ELOGIND
+#include <elogind/sd-login.h>
+#endif
+
+#include "devname.h"
+#include "escape.h"
+#include "misc.h"
+#include "pwcache.h"
+#include "readproc.h"
+
+// sometimes it's easier to do this manually, w/o gcc helping
+#ifdef PROF
+extern void __cyg_profile_func_enter(void*,void*);
+#define ENTER(x) __cyg_profile_func_enter((void*)x,(void*)x)
+#define LEAVE(x) __cyg_profile_func_exit((void*)x,(void*)x)
+#else
+#define ENTER(x)
+#define LEAVE(x)
+#endif
+
+#ifdef FALSE_THREADS
+#define IS_THREAD(q) ( q->tid != q->tgid )
+#endif
+
+// utility buffers of MAX_BUFSZ bytes each, available to
+// any function following an openproc() call
+static __thread char *src_buffer,
+ *dst_buffer;
+#define MAX_BUFSZ 1024*64*2
+
+// dynamic 'utility' buffer support for file2str() calls
+struct utlbuf_s {
+ char *buf; // dynamically grown buffer
+ int siz; // current len of the above
+} utlbuf_s;
+
+static int task_dir_missing;
+
+
+// free any additional dynamically acquired storage associated with a proc_t
+static inline void free_acquired (proc_t *p) {
+ /*
+ * here we free those items that might exist even when not explicitly |
+ * requested by our caller. it is expected that pid.c will then free |
+ * any remaining dynamic memory which might be dangling off a proc_t. | */
+ if (p->cgname) free(p->cgname);
+ if (p->cgroup) free(p->cgroup);
+ if (p->cmd) free(p->cmd);
+ if (p->sd_mach) free(p->sd_mach);
+ if (p->sd_ouid) free(p->sd_ouid);
+ if (p->sd_seat) free(p->sd_seat);
+ if (p->sd_sess) free(p->sd_sess);
+ if (p->sd_slice) free(p->sd_slice);
+ if (p->sd_unit) free(p->sd_unit);
+ if (p->sd_uunit) free(p->sd_uunit);
+ if (p->supgid) free(p->supgid);
+
+ memset(p, '\0', sizeof(proc_t));
+}
+
+
+///////////////////////////////////////////////////////////////////////////
+
+typedef struct status_table_struct {
+ unsigned char name[8]; // /proc/*/status field name
+ unsigned char len; // name length
+#ifdef LABEL_OFFSET
+ long offset; // jump address offset
+#else
+ void *addr;
+#endif
+} status_table_struct;
+
+#ifdef LABEL_OFFSET
+#define F(x) {#x, sizeof(#x)-1, (long)(&&case_##x-&&base)},
+#else
+#define F(x) {#x, sizeof(#x)-1, &&case_##x},
+#endif
+#define NUL {"", 0, 0},
+
+#define GPERF_TABLE_SIZE 128
+
+// Derived from:
+// gperf -7 --language=ANSI-C --key-positions=1,3,4 -C -n -c <if-not-piped>
+// ( --key-positions verified by omission & reported "Computed positions" )
+//
+// Suggested method:
+// Grep this file for "case_", then strip those down to the name.
+// Eliminate duplicates (due to #ifs), the ' case_' prefix and
+// any c comments. Leave the colon and newline so that "Pid:\n",
+// "Threads:\n", etc. would be lines, but no quote, no escape, etc.
+//
+// After a pipe through gperf, insert the resulting 'asso_values'
+// into our 'asso' array. Then convert the gperf 'wordlist' array
+// into our 'table' array by wrapping the string literals within
+// the F macro and replacing empty strings with the NUL define.
+//
+// In the status_table_struct watch out for name size (grrr, expanding)
+// and the number of entries. Currently, the table is padded to 128
+// entries and we therefore mask with 127.
+
+static int status2proc (char *S, proc_t *restrict P, int is_proc) {
+ long Threads = 0;
+ long Tgid = 0;
+ long Pid = 0;
+
+ // 128 entries because we trust the kernel to use ASCII names
+ static const unsigned char asso[] =
+ {
+ 101, 101, 101, 101, 101, 101, 101, 101, 101, 101,
+ 101, 101, 101, 101, 101, 101, 101, 101, 101, 101,
+ 101, 101, 101, 101, 101, 101, 101, 101, 101, 101,
+ 101, 101, 101, 101, 101, 101, 101, 101, 101, 101,
+ 101, 101, 101, 101, 101, 101, 101, 101, 101, 101,
+ 101, 101, 101, 101, 101, 101, 101, 101, 6, 101,
+ 101, 101, 101, 101, 101, 45, 55, 25, 31, 50,
+ 50, 10, 0, 35, 101, 101, 21, 101, 30, 101,
+ 20, 36, 0, 5, 0, 40, 0, 0, 101, 101,
+ 101, 101, 101, 101, 101, 101, 101, 30, 101, 15,
+ 0, 1, 101, 10, 101, 10, 101, 101, 101, 25,
+ 101, 40, 0, 101, 0, 50, 6, 40, 101, 1,
+ 35, 101, 101, 101, 101, 101, 101, 101
+ };
+
+ static const status_table_struct table[GPERF_TABLE_SIZE] = {
+ F(VmHWM)
+ F(Threads)
+ NUL NUL NUL
+ F(VmRSS)
+ F(VmSwap)
+ NUL NUL NUL
+ F(Tgid)
+ F(VmStk)
+ NUL NUL NUL
+ F(VmSize)
+ F(Gid)
+ NUL NUL NUL
+ F(VmPTE)
+ F(VmPeak)
+ NUL NUL NUL
+ F(ShdPnd)
+ F(Pid)
+ NUL NUL NUL
+ F(PPid)
+ F(VmLib)
+ NUL NUL NUL
+ F(SigPnd)
+ F(VmLck)
+ NUL NUL NUL
+ F(SigCgt)
+ F(State)
+ NUL NUL NUL
+ F(CapPrm)
+ F(Uid)
+ NUL NUL NUL
+ F(SigIgn)
+ F(SigQ)
+ NUL NUL NUL
+ F(RssShmem)
+ F(Name)
+ NUL NUL NUL
+ F(CapInh)
+ F(VmData)
+ NUL NUL NUL
+ F(FDSize)
+ NUL NUL NUL NUL
+ F(SigBlk)
+ NUL NUL NUL NUL
+ F(CapEff)
+ NUL NUL NUL NUL
+ F(CapBnd)
+ NUL NUL NUL NUL
+ F(VmExe)
+ NUL NUL NUL NUL
+ F(Groups)
+ NUL NUL NUL NUL
+ F(RssAnon)
+ NUL NUL NUL NUL
+ F(RssFile)
+ };
+
+#undef F
+#undef NUL
+
+ENTER(0x220);
+
+ goto base;
+
+ for(;;){
+ char *colon;
+ status_table_struct entry;
+
+ // advance to next line
+ S = strchr(S, '\n');
+ if(!S) break; // if no newline
+ S++;
+
+ // examine a field name (hash and compare)
+ base:
+ if(!*S) break;
+ if((!S[0] || !S[1] || !S[2] || !S[3])) break;
+ entry = table[(GPERF_TABLE_SIZE -1) & (asso[S[3]&127] + asso[S[2]&127] + asso[S[0]&127])];
+ colon = strchr(S, ':');
+ if(!colon) break;
+ if(colon[1]!='\t') break;
+ if(colon-S != entry.len) continue;
+ if(memcmp(entry.name,S,colon-S)) continue;
+
+ S = colon+2; // past the '\t'
+
+#ifdef LABEL_OFFSET
+ goto *(&&base + entry.offset);
+#else
+ goto *entry.addr;
+#endif
+
+ case_Name:
+ { char buf[64], raw[64];
+ unsigned u = 0;
+ while(u < sizeof(raw) - 1u){
+ int c = *S++;
+ if(c=='\n') break;
+ if(c=='\0') break; // should never happen
+ if(c=='\\'){
+ c = *S++;
+ if(c=='\n') break; // should never happen
+ if(!c) break; // should never happen
+ if(c=='n') c='\n'; // else we assume it is '\\'
+ }
+ raw[u++] = c;
+ }
+ raw[u] = '\0';
+#ifdef FALSE_THREADS
+ if (!IS_THREAD(P)) {
+#endif
+ if (!P->cmd) {
+ escape_str(buf, raw, sizeof(buf));
+ if (!(P->cmd = strdup(buf))) return 1;
+ }
+#ifdef FALSE_THREADS
+ }
+#endif
+ S--; // put back the '\n' or '\0'
+ continue;
+ }
+ case_ShdPnd:
+ memcpy(P->signal, S, 16);
+ P->signal[16] = '\0';
+ continue;
+ case_SigBlk:
+ memcpy(P->blocked, S, 16);
+ P->blocked[16] = '\0';
+ continue;
+ case_SigCgt:
+ memcpy(P->sigcatch, S, 16);
+ P->sigcatch[16] = '\0';
+ continue;
+ case_SigIgn:
+ memcpy(P->sigignore, S, 16);
+ P->sigignore[16] = '\0';
+ continue;
+ case_SigPnd:
+ memcpy(P->_sigpnd, S, 16);
+ P->_sigpnd[16] = '\0';
+ continue;
+ case_State:
+ P->state = *S;
+ continue;
+ case_Tgid:
+ Tgid = strtol(S,&S,10);
+ continue;
+ case_Pid:
+ Pid = strtol(S,&S,10);
+ continue;
+ case_PPid:
+ P->ppid = strtol(S,&S,10);
+ continue;
+ case_Threads:
+ Threads = strtol(S,&S,10);
+ continue;
+ case_Uid:
+ P->ruid = strtol(S,&S,10);
+ P->euid = strtol(S,&S,10);
+ P->suid = strtol(S,&S,10);
+ P->fuid = strtol(S,&S,10);
+ continue;
+ case_Gid:
+ P->rgid = strtol(S,&S,10);
+ P->egid = strtol(S,&S,10);
+ P->sgid = strtol(S,&S,10);
+ P->fgid = strtol(S,&S,10);
+ continue;
+ case_VmData:
+ P->vm_data = (unsigned long)strtol(S,&S,10);
+ continue;
+ case_VmExe:
+ P->vm_exe = (unsigned long)strtol(S,&S,10);
+ continue;
+ case_VmLck:
+ P->vm_lock = (unsigned long)strtol(S,&S,10);
+ continue;
+ case_VmLib:
+ P->vm_lib = (unsigned long)strtol(S,&S,10);
+ continue;
+ case_VmRSS:
+ P->vm_rss = (unsigned long)strtol(S,&S,10);
+ continue;
+ case_RssAnon: // subset of VmRSS, linux-4.5
+ P->vm_rss_anon = (unsigned long)strtol(S,&S,10);
+ continue;
+ case_RssFile: // subset of VmRSS, linux-4.5
+ P->vm_rss_file = (unsigned long)strtol(S,&S,10);
+ continue;
+ case_RssShmem: // subset of VmRSS, linux-4.5
+ P->vm_rss_shared = (unsigned long)strtol(S,&S,10);
+ continue;
+ case_VmSize:
+ P->vm_size = (unsigned long)strtol(S,&S,10);
+ continue;
+ case_VmStk:
+ P->vm_stack = (unsigned long)strtol(S,&S,10);
+ continue;
+ case_VmSwap: // Linux 2.6.34
+ P->vm_swap = (unsigned long)strtol(S,&S,10);
+ continue;
+ case_Groups:
+ { char *ss = S, *nl = strchr(S, '\n');
+ size_t j;
+
+#ifdef FALSE_THREADS
+ if (IS_THREAD(P)) continue;
+#endif
+ while (' ' == *ss || '\t' == *ss) ss++;
+ if (ss >= nl) continue;
+ j = nl ? (size_t)(nl - ss) : strlen(ss);
+ if (j > 0 && j < INT_MAX) {
+ P->supgid = malloc(j+1); // +1 in case space disappears
+ if (!P->supgid)
+ return 1;
+ memcpy(P->supgid, ss, j);
+ if (' ' != P->supgid[--j]) ++j;
+ P->supgid[j] = '\0'; // whack the space or the newline
+ for ( ; j; j--)
+ if (' ' == P->supgid[j])
+ P->supgid[j] = ',';
+ }
+ continue;
+ }
+ case_CapBnd:
+ case_CapEff:
+ case_CapInh:
+ case_CapPrm:
+ case_FDSize:
+ case_SigQ:
+ case_VmHWM: // 2005, peak VmRSS unless VmRSS is bigger
+ case_VmPTE:
+ case_VmPeak: // 2005, peak VmSize unless VmSize is bigger
+ continue;
+ }
+
+#if 0
+ // recent kernels supply per-tgid pending signals
+ if(is_proc && *ShdPnd){
+ memcpy(P->signal, ShdPnd, 16);
+ P->signal[16] = '\0';
+ }
+#endif
+
+ // recent kernels supply per-tgid pending signals
+ if(!is_proc || !P->signal[0]){
+ memcpy(P->signal, P->_sigpnd, 16);
+ P->signal[16] = '\0';
+ }
+
+ // Linux 2.4.13-pre1 to max 2.4.xx have a useless "Tgid"
+ // that is not initialized for built-in kernel tasks.
+ // Only 2.6.0 and above have "Threads" (nlwp) info.
+
+ if(Threads){
+ P->nlwp = Threads;
+ P->tgid = Tgid; // the POSIX PID value
+ P->tid = Pid; // the thread ID
+ }else{
+ P->nlwp = 1;
+ P->tgid = Pid;
+ P->tid = Pid;
+ }
+
+#ifdef FALSE_THREADS
+ if (!IS_THREAD(P)) {
+#endif
+ if (!P->supgid) {
+ P->supgid = strdup("-");
+ if (!P->supgid)
+ return 1;
+ }
+#ifdef FALSE_THREADS
+ }
+#endif
+LEAVE(0x220);
+ return 0;
+}
+#undef GPERF_TABLE_SIZE
+
+
+static int supgrps_from_supgids (proc_t *p) {
+ char *g, *s;
+ int t;
+
+#ifdef FALSE_THREADS
+ if (IS_THREAD(p)) return 0;
+#endif
+ if (!p->supgid || '-' == *p->supgid)
+ goto wrap_up;
+
+ s = p->supgid;
+ t = 0;
+ do {
+ const int max = P_G_SZ+2;
+ char *end = NULL;
+ gid_t gid;
+ int len;
+
+ while (',' == *s) ++s;
+ gid = strtol(s, &end, 10);
+ if (end <= s) break;
+ s = end;
+ g = pwcache_get_group(gid);
+
+ if ((t >= INT_MAX - max)
+ || (!(p->supgrp = realloc(p->supgrp, t + max))))
+ return 1;
+
+ len = snprintf(p->supgrp+t, max, "%s%s", t ? "," : "", g);
+ if (len <= 0) (p->supgrp+t)[len = 0] = '\0';
+ else if (len >= max) len = max-1;
+ t += len;
+ } while (*s);
+
+wrap_up:
+ if (!p->supgrp
+ && !(p->supgrp = strdup("-")))
+ return 1;
+ return 0;
+}
+
+
+///////////////////////////////////////////////////////////////////////
+
+static inline void oomscore2proc(const char *S, proc_t *restrict P)
+{
+ sscanf(S, "%d", &P->oom_score);
+}
+
+static inline void oomadj2proc(const char *S, proc_t *restrict P)
+{
+ sscanf(S, "%d", &P->oom_adj);
+}
+
+
+///////////////////////////////////////////////////////////////////////
+
+static int sd2proc (proc_t *restrict p) {
+#if defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)
+ char buf[64];
+ uid_t uid;
+
+ if (0 > sd_pid_get_machine_name(p->tid, &p->sd_mach)) {
+ if (!(p->sd_mach = strdup("-")))
+ return 1;
+ }
+ if (0 > sd_pid_get_owner_uid(p->tid, &uid)) {
+ if (!(p->sd_ouid = strdup("-")))
+ return 1;
+ } else {
+ snprintf(buf, sizeof(buf), "%d", (int)uid);
+ if (!(p->sd_ouid = strdup(buf)))
+ return 1;
+ }
+ if (0 > sd_pid_get_session(p->tid, &p->sd_sess)) {
+ if (!(p->sd_sess = strdup("-")))
+ return 1;
+ if (!(p->sd_seat = strdup("-")))
+ return 1;
+ } else {
+ if (0 > sd_session_get_seat(p->sd_sess, &p->sd_seat))
+ if (!(p->sd_seat = strdup("-")))
+ return 1;
+ }
+ if (0 > sd_pid_get_slice(p->tid, &p->sd_slice))
+ if (!(p->sd_slice = strdup("-")))
+ return 1;
+ if (0 > sd_pid_get_unit(p->tid, &p->sd_unit))
+ if (!(p->sd_unit = strdup("-")))
+ return 1;
+ if (0 > sd_pid_get_user_unit(p->tid, &p->sd_uunit))
+ if (!(p->sd_uunit = strdup("-")))
+ return 1;
+#else
+ if (!(p->sd_mach = strdup("?")))
+ return 1;
+ if (!(p->sd_ouid = strdup("?")))
+ return 1;
+ if (!(p->sd_seat = strdup("?")))
+ return 1;
+ if (!(p->sd_sess = strdup("?")))
+ return 1;
+ if (!(p->sd_slice = strdup("?")))
+ return 1;
+ if (!(p->sd_unit = strdup("?")))
+ return 1;
+ if (!(p->sd_uunit = strdup("?")))
+ return 1;
+#endif
+ return 0;
+}
+
+
+///////////////////////////////////////////////////////////////////////
+
+// Reads /proc/*/stat files, being careful not to trip over processes with
+// names like ":-) 1 2 3 4 5 6".
+static int stat2proc (const char *S, proc_t *restrict P) {
+ char buf[64], raw[64];
+ size_t num;
+ char *tmp;
+
+ENTER(0x160);
+
+ /* fill in default values for older kernels */
+ P->processor = 0;
+ P->rtprio = -1;
+ P->sched = -1;
+ P->nlwp = 0;
+
+ S = strchr(S, '(');
+ if (!S) return 0;
+ S++;
+ tmp = strrchr(S, ')');
+ if (!tmp || !tmp[1]) return 0;
+#ifdef FALSE_THREADS
+ if (!IS_THREAD(P)) {
+#endif
+ if (!P->cmd) {
+ num = tmp - S;
+ memcpy(raw, S, num);
+ raw[num] = '\0';
+ escape_str(buf, raw, sizeof(buf));
+ if (!(P->cmd = strdup(buf))) return 1;
+ }
+#ifdef FALSE_THREADS
+ }
+#endif
+ S = tmp + 2; // skip ") "
+
+ sscanf(S,
+ "%c " // state
+ "%d %d %d %d %d " // ppid, pgrp, sid, tty_nr, tty_pgrp
+ "%lu %lu %lu %lu %lu " // flags, min_flt, cmin_flt, maj_flt, cmaj_flt
+ "%llu %llu %llu %llu " // utime, stime, cutime, cstime
+ "%d %d " // priority, nice
+ "%d " // num_threads
+ "%lu " // 'alarm' == it_real_value (obsolete, always 0)
+ "%llu " // start_time
+ "%lu " // vsize
+ "%lu " // rss
+ "%lu %lu %lu %lu %lu %lu " // rsslim, start_code, end_code, start_stack, esp, eip
+ "%*s %*s %*s %*s " // pending, blocked, sigign, sigcatch <=== DISCARDED
+ "%lu %*u %*u " // 0 (former wchan), 0, 0 <=== Placeholders only
+ "%d %d " // exit_signal, task_cpu
+ "%d %d " // rt_priority, policy (sched)
+ "%llu %llu %llu", // blkio_ticks, gtime, cgtime
+ &P->state,
+ &P->ppid, &P->pgrp, &P->session, &P->tty, &P->tpgid,
+ &P->flags, &P->min_flt, &P->cmin_flt, &P->maj_flt, &P->cmaj_flt,
+ &P->utime, &P->stime, &P->cutime, &P->cstime,
+ &P->priority, &P->nice,
+ &P->nlwp,
+ &P->alarm,
+ &P->start_time,
+ &P->vsize,
+ &P->rss,
+ &P->rss_rlim, &P->start_code, &P->end_code, &P->start_stack, &P->kstk_esp, &P->kstk_eip,
+/* P->signal, P->blocked, P->sigignore, P->sigcatch, */ /* can't use */
+ &P->wchan, /* &P->nswap, &P->cnswap, */ /* nswap and cnswap dead for 2.4.xx and up */
+/* -- Linux 2.0.35 ends here -- */
+ &P->exit_signal, &P->processor, /* 2.2.1 ends with "exit_signal" */
+/* -- Linux 2.2.8 to 2.5.17 end here -- */
+ &P->rtprio, &P->sched, /* both added to 2.5.18 */
+ &P->blkio_tics, &P->gtime, &P->cgtime
+ );
+
+ if(!P->nlwp)
+ P->nlwp = 1;
+
+ return 0;
+LEAVE(0x160);
+}
+
+
+/////////////////////////////////////////////////////////////////////////
+
+static void statm2proc(const char *s, proc_t *restrict P) {
+ sscanf(s, "%lu %lu %lu %lu %lu %lu %lu",
+ &P->size, &P->resident, &P->share,
+ &P->trs, &P->lrs, &P->drs, &P->dt);
+}
+
+static void io2proc(const char *s, proc_t *restrict P) {
+ sscanf(s, "rchar: %lu wchar: %lu syscr: %lu syscw: %lu read_bytes: %lu write_bytes: %lu cancelled_write_bytes: %lu",
+ &P->rchar, &P->wchar, &P->syscr,
+ &P->syscw, &P->read_bytes, &P->write_bytes, &P->cancelled_write_bytes);
+}
+
+ // Assuming permissions have allowed the read of smaps_rollup, this
+ // guy will extract some %lu data. Considering the number of items,
+ // we are between small enough to use a sscanf and large enough for
+ // a search.h approach. Thus we roll (get it?) our own custom code.
+static void smaps2proc (const char *s, proc_t *restrict P) {
+ #define enMAX (int)((sizeof(smaptab) / sizeof(smaptab[0])))
+ // 1st proc_t data field
+ #define fZERO tid
+ // a smaptab entry generator
+ #define mkENT(F) { #F ":", -1, offsetof(proc_t, smap_ ## F) }
+ // make a target field
+ #define mkOBJ(X) ( (unsigned long *)((void *)&P->fZERO + smaptab[X].offs) )
+ static struct {
+ const char *item;
+ int slen;
+ int offs;
+ } smaptab[] = {
+ /* Size smaps only, not rollup */
+ /* KernelPageSize " */
+ /* MMUPageSize " */
+ mkENT(Rss),
+ mkENT(Pss),
+ mkENT(Pss_Anon), /* rollup only, not smaps */
+ mkENT(Pss_File), /* " */
+ mkENT(Pss_Shmem), /* " */
+ mkENT(Shared_Clean),
+ mkENT(Shared_Dirty),
+ mkENT(Private_Clean),
+ mkENT(Private_Dirty),
+ mkENT(Referenced),
+ mkENT(Anonymous),
+ mkENT(LazyFree),
+ mkENT(AnonHugePages),
+ mkENT(ShmemPmdMapped),
+ mkENT(FilePmdMapped),
+ mkENT(Shared_Hugetlb),
+ mkENT(Private_Hugetlb),
+ mkENT(Swap),
+ mkENT(SwapPss),
+ mkENT(Locked)
+ /* THPeligible smaps only, not rollup */
+ /* ProtectionKey " */
+ /* VmFlags " */
+ };
+ char *head, *tail;
+ int i;
+
+ if (smaptab[0].slen < 0) {
+ for (i = 0; i < enMAX; i++)
+ smaptab[i].slen = (int)strlen(smaptab[i].item);
+ }
+ for (i = 0; i < enMAX; i++) {
+ if (!(head = strstr(s, smaptab[i].item)))
+ continue;
+ head += smaptab[i].slen;
+ *mkOBJ(i) = strtoul(head, &tail, 10);
+ // saves some overhead BUT makes us dependent on current order
+ s = tail;
+ }
+ #undef enMAX
+ #undef fZERO
+ #undef mkENT
+ #undef mkOBJ
+}
+
+static int file2str(const char *directory, const char *what, struct utlbuf_s *ub) {
+ #define buffGRW 1024
+ char path[PROCPATHLEN];
+ int fd, num, tot_read = 0, len;
+
+ /* on first use we preallocate a buffer of minimum size to emulate
+ former 'local static' behavior -- even if this read fails, that
+ buffer will likely soon be used for another subdirectory anyway
+ ( besides, with the calloc call we will never need use memcpy ) */
+ if (ub->buf) ub->buf[0] = '\0';
+ else {
+ ub->buf = calloc(1, (ub->siz = buffGRW));
+ if (!ub->buf) return -1;
+ }
+ len = snprintf(path, sizeof path, "%s/%s", directory, what);
+ if (len <= 0 || (size_t)len >= sizeof path) return -1;
+ if (-1 == (fd = open(path, O_RDONLY, 0))) return -1;
+ while (0 < (num = read(fd, ub->buf + tot_read, ub->siz - tot_read))) {
+ tot_read += num;
+ if (tot_read < ub->siz) break;
+ if (ub->siz >= INT_MAX - buffGRW) {
+ tot_read--;
+ break;
+ }
+ if (!(ub->buf = realloc(ub->buf, (ub->siz += buffGRW)))) {
+ close(fd);
+ return -1;
+ }
+ };
+ ub->buf[tot_read] = '\0';
+ close(fd);
+ if (tot_read < 1) return -1;
+ return tot_read;
+ #undef buffGRW
+}
+
+
+static char **file2strvec(const char *directory, const char *what) {
+ char buf[2048]; /* read buf bytes at a time */
+ char *p, *rbuf = 0, *endbuf, **q, **ret, *strp;
+ int fd, tot = 0, n, c, end_of_file = 0;
+ int align;
+
+ const int len = snprintf(buf, sizeof buf, "%s/%s", directory, what);
+ if(len <= 0 || (size_t)len >= sizeof buf) return NULL;
+ fd = open(buf, O_RDONLY, 0);
+ if(fd==-1) return NULL;
+
+ /* read whole file into a memory buffer, allocating as we go */
+ while ((n = read(fd, buf, sizeof buf - 1)) >= 0) {
+ if (n < (int)(sizeof buf - 1))
+ end_of_file = 1;
+ if (n <= 0 && tot <= 0) { /* nothing read now, nothing read before */
+ break; /* process died between our open and read */
+ }
+ /* ARG_LEN is our guesstimated median length of a command-line argument
+ or environment variable (the minimum is 1, the maximum is 131072) */
+ #define ARG_LEN 64
+ if (tot >= INT_MAX / (ARG_LEN + (int)sizeof(char*)) * ARG_LEN - n) {
+ end_of_file = 1; /* integer overflow: null-terminate and break */
+ n = 0; /* but tot > 0 */
+ }
+ #undef ARG_LEN
+ if (end_of_file &&
+ ((n > 0 && buf[n-1] != '\0') || /* last read char not null */
+ (n <= 0 && rbuf && rbuf[tot-1] != '\0'))) /* last read char not null */
+
+ buf[n++] = '\0'; /* so append null-terminator */
+
+ if (n <= 0) break; /* unneeded (end_of_file = 1) but avoid realloc */
+ rbuf = realloc(rbuf, tot + n); /* allocate more memory */
+ if (!rbuf) {
+ close(fd);
+ return NULL;
+ }
+ memcpy(rbuf + tot, buf, n); /* copy buffer into it */
+ tot += n; /* increment total byte ctr */
+ if (end_of_file)
+ break;
+ }
+ close(fd);
+ if (n < 0 || tot <= 0) { /* error, or nothing read */
+ if (rbuf) free(rbuf);
+ return NULL; /* read error */
+ }
+
+ rbuf[tot-1] = '\0'; /* belt and suspenders (the while loop did it, too) */
+ endbuf = rbuf + tot; /* count space for pointers */
+ align = (sizeof(char*)-1) - ((tot + sizeof(char*)-1) & (sizeof(char*)-1));
+ c = sizeof(char*); /* one extra for NULL term */
+ for (p = rbuf; p < endbuf; p++) {
+ if (!*p || *p == '\n') {
+ if (c >= INT_MAX - (tot + (int)sizeof(char*) + align)) break;
+ c += sizeof(char*);
+ }
+ if (*p == '\n')
+ *p = 0;
+ }
+
+ rbuf = realloc(rbuf, tot + c + align); /* make room for ptrs AT END */
+ if (!rbuf) return NULL;
+ endbuf = rbuf + tot; /* addr just past data buf */
+ q = ret = (char**) (endbuf+align); /* ==> free(*ret) to dealloc */
+ for (strp = p = rbuf; p < endbuf; p++) {
+ if (!*p) { /* NUL char implies that */
+ if (c < 2 * (int)sizeof(char*)) break;
+ c -= sizeof(char*);
+ *q++ = strp; /* point ptrs to the strings */
+ strp = p+1; /* next string -> next char */
+ }
+ }
+ *q = 0; /* null ptr list terminator */
+ return ret;
+}
+
+
+ // this is the former under utilized 'read_cmdline', which has been
+ // generalized in support of these new libproc flags:
+ // PROC_EDITCGRPCVT, PROC_EDITCMDLCVT and PROC_EDITENVRCVT
+static int read_unvectored(char *restrict const dst, unsigned sz, const char *whom, const char *what, char sep) {
+ char path[PROCPATHLEN];
+ int fd, len;
+ unsigned n = 0;
+
+ if(sz <= 0) return 0;
+ if(sz >= INT_MAX) sz = INT_MAX-1;
+ dst[0] = '\0';
+
+ len = snprintf(path, sizeof(path), "%s/%s", whom, what);
+ if(len <= 0 || (size_t)len >= sizeof(path)) return 0;
+ fd = open(path, O_RDONLY);
+ if(fd==-1) return 0;
+
+ for(;;){
+ ssize_t r = read(fd,dst+n,sz-n);
+ if(r==-1){
+ if(errno==EINTR) continue;
+ break;
+ }
+ if(r<=0) break; // EOF
+ n += r;
+ if(n==sz) { // filled the buffer
+ --n; // make room for '\0'
+ break;
+ }
+ }
+ close(fd);
+ if(n){
+ unsigned i = n;
+ while(i && dst[i-1]=='\0') --i; // skip trailing zeroes
+ while(i--)
+ if(dst[i]=='\n' || dst[i]=='\0') dst[i]=sep;
+ if(dst[n-1]==' ') dst[n-1]='\0';
+ }
+ dst[n] = '\0';
+ return n;
+}
+
+
+char **vectorize_this_str (const char *src) {
+ #define pSZ (sizeof(char*))
+ char *cpy, **vec;
+ size_t adj, tot;
+
+ tot = strlen(src) + 1; // prep for our vectors
+ if (tot < 1 || tot >= INT_MAX) tot = INT_MAX-1; // integer overflow?
+ adj = (pSZ-1) - ((tot + pSZ-1) & (pSZ-1)); // calc alignment bytes
+ cpy = calloc(1, tot + adj + (2 * pSZ)); // get new larger buffer
+ if (!cpy) return NULL; // oops, looks like ENOMEM
+ snprintf(cpy, tot, "%s", src); // duplicate their string
+ vec = (char**)(cpy + tot + adj); // prep pointer to pointers
+ *vec = cpy; // point 1st vector to string
+ *(vec+1) = NULL; // null ptr 'list' delimit
+ return vec; // ==> free(*vec) to dealloc
+ #undef pSZ
+}
+
+
+ // This littl' guy just serves those true vectorized fields
+ // ( when a /proc source field didn't exist )
+static int vectorize_dash_rc (char ***vec) {
+ if (!(*vec = vectorize_this_str("-")))
+ return 1;
+ return 0;
+}
+
+
+ // This routine reads a 'cgroup' for the designated proc_t and
+ // guarantees the caller a valid proc_t.cgroup pointer.
+static int fill_cgroup_cvt (const char *directory, proc_t *restrict p) {
+ #define vMAX ( MAX_BUFSZ - (int)(dst - dst_buffer) )
+ char *src, *dst, *grp, *eob, *name;
+ int tot, x, len;
+
+ *(dst = dst_buffer) = '\0'; // empty destination
+ tot = read_unvectored(src_buffer, MAX_BUFSZ, directory, "cgroup", '\0');
+ for (src = src_buffer, eob = src_buffer + tot; src < eob; src += x) {
+ x = 1; // loop assist
+ if (!*src) continue;
+ x = strlen((grp = src));
+ if ('/' == grp[x - 1]) continue; // skip empty root cgroups
+#if 0
+ grp += strspn(grp, "0123456789:"); // jump past group number
+#endif
+ if (vMAX <= 1) break;
+ len = snprintf(dst, vMAX, "%s", (dst > dst_buffer) ? "," : "");
+ if (len < 0 || len >= vMAX) break;
+ dst += len;
+ dst += escape_str(dst, grp, vMAX);
+ }
+ if (!(p->cgroup = strdup(dst_buffer[0] ? dst_buffer : "-")))
+ return 1;
+ name = strstr(p->cgroup, ":name=");
+ if (name && *(name+6)) name += 6; else name = p->cgroup;
+ if (!(p->cgname = strdup(name)))
+ return 1;
+ return 0;
+ #undef vMAX
+}
+
+
+ // This routine reads a 'cmdline' for the designated proc_t, "escapes"
+ // the result into a single string while guaranteeing the caller a
+ // valid proc_t.cmdline pointer.
+static int fill_cmdline_cvt (const char *directory, proc_t *restrict p) {
+ #define uFLG ( ESC_BRACKETS | ESC_DEFUNCT )
+ if (read_unvectored(src_buffer, MAX_BUFSZ, directory, "cmdline", ' '))
+ escape_str(dst_buffer, src_buffer, MAX_BUFSZ);
+ else
+ escape_command(dst_buffer, p, MAX_BUFSZ, uFLG);
+ p->cmdline = strdup(dst_buffer[0] ? dst_buffer : "?");
+ if (!p->cmdline)
+ return 1;
+ return 0;
+ #undef uFLG
+}
+
+
+ // This routine reads an 'environ' for the designated proc_t and
+ // guarantees the caller a valid proc_t.environ pointer.
+static int fill_environ_cvt (const char *directory, proc_t *restrict p) {
+ dst_buffer[0] = '\0';
+ if (read_unvectored(src_buffer, MAX_BUFSZ, directory, "environ", ' '))
+ escape_str(dst_buffer, src_buffer, MAX_BUFSZ);
+ p->environ = strdup(dst_buffer[0] ? dst_buffer : "-");
+ if (!p->environ)
+ return 1;
+ return 0;
+}
+
+
+ // Provide the means to value proc_t.lxcname (perhaps only with "-") while
+ // tracking all names already seen thus avoiding the overhead of repeating
+ // malloc() and free() calls.
+static char *lxc_containers (const char *path) {
+ static __thread struct utlbuf_s ub = { NULL, 0 }; // util buffer for whole cgroup
+ static char lxc_none[] = "-";
+ static char lxc_oops[] = "?"; // used when memory alloc fails
+ /*
+ try to locate the lxc delimiter eyecatcher somewhere in a task's cgroup
+ directory -- the following are from nested privileged plus unprivileged
+ containers, where the '/lxc/' delimiter precedes the container name ...
+ 10:cpuset:/lxc/lxc-P/lxc/lxc-P-nested
+ 10:cpuset:/user.slice/user-1000.slice/session-c2.scope/lxc/lxc-U/lxc/lxc-U-nested
+
+ ... some minor complications are the potential addition of more cgroups
+ for a controller displacing the lxc name (normally last on a line), and
+ environments with unexpected /proc/##/cgroup ordering/contents as with:
+ 10:cpuset:/lxc/lxc-P/lxc/lxc-P-nested/MY-NEW-CGROUP
+ or
+ 2:name=systemd:/
+ 1:cpuset,cpu,cpuacct,devices,freezer,net_cls,blkio,perf_event,net_prio:/lxc/lxc-P
+ */
+ if (file2str(path, "cgroup", &ub) > 0) {
+ /* ouch, the next defaults could be changed at lxc ./configure time
+ ( and a changed 'lxc.cgroup.pattern' is only available to root ) */
+ static const char *lxc_delm1 = "lxc.payload."; // with lxc-4.0.0
+ static const char *lxc_delm2 = "lxc.payload/"; // thru lxc-3.2.1
+ static const char *lxc_delm3 = "lxc/"; // thru lxc-3.0.3
+ const char *delim;
+ char *p1;
+
+ if ((p1 = strstr(ub.buf, (delim = lxc_delm1)))
+ || ((p1 = strstr(ub.buf, (delim = lxc_delm2)))
+ || ((p1 = strstr(ub.buf, (delim = lxc_delm3)))))) {
+ static __thread struct lxc_ele {
+ struct lxc_ele *next;
+ char *name;
+ } *anchor = NULL;
+ struct lxc_ele *ele = anchor;
+ int delim_len = strlen(delim);
+ char *p2;
+
+ if ((p2 = strchr(p1, '\n'))) // isolate a controller's line
+ *p2 = '\0';
+ do { // deal with nested containers
+ p2 = p1 + delim_len;
+ p1 = strstr(p2, delim);
+ } while (p1);
+ if ((p1 = strchr(p2, '/'))) // isolate name only substring
+ *p1 = '\0';
+ while (ele) { // have we already seen a name
+ if (!strcmp(ele->name, p2))
+ return ele->name; // return just a recycled name
+ ele = ele->next;
+ }
+ if (!(ele = (struct lxc_ele *)malloc(sizeof(struct lxc_ele))))
+ return lxc_oops;
+ if (!(ele->name = strdup(p2))) {
+ free(ele);
+ return lxc_oops;
+ }
+ ele->next = anchor; // push the new container name
+ anchor = ele;
+ return ele->name; // return a new container name
+ }
+ }
+ return lxc_none;
+}
+
+
+ // Provide the user id at login (or -1 if not available)
+static int login_uid (const char *path) {
+ char buf[PROCPATHLEN];
+ int fd, id, in;
+
+ id = -1;
+ snprintf(buf, sizeof(buf), "%s/loginuid", path);
+ if ((fd = open(buf, O_RDONLY, 0)) != -1) {
+ in = read(fd, buf, sizeof(buf) - 1);
+ close(fd);
+ if (in > 0) {
+ buf[in] = '\0';
+ id = atoi(buf);
+ }
+ }
+ return id;
+}
+
+
+static char *readlink_exe (const char *path){
+ char buf[PROCPATHLEN];
+ int in;
+
+ snprintf(buf, sizeof(buf), "%s/exe", path);
+ in = (int)readlink(buf, src_buffer, MAX_BUFSZ-1);
+ if (in > 0) {
+ src_buffer[in] = '\0';
+ escape_str(dst_buffer, src_buffer, MAX_BUFSZ);
+ return strdup(dst_buffer);
+ }
+ return strdup("-");
+}
+
+
+ // Provide the autogroup fields (or -1 if not available)
+static void autogroup_fill (const char *path, proc_t *p) {
+ char buf[PROCPATHLEN];
+ int fd, in;
+
+ p->autogrp_id = -1;
+ snprintf(buf, sizeof(buf), "%s/autogroup", path);
+ if ((fd = open(buf, O_RDONLY, 0)) != -1) {
+ in = read(fd, buf, sizeof(buf) - 1);
+ close(fd);
+ if (in > 0) {
+ buf[in] = '\0';
+ sscanf(buf, "/autogroup-%d nice %d"
+ , &p->autogrp_id, &p->autogrp_nice);
+ }
+ }
+}
+
+
+///////////////////////////////////////////////////////////////////////
+
+/* These are some nice GNU C expression subscope "inline" functions.
+ * The can be used with arbitrary types and evaluate their arguments
+ * exactly once.
+ */
+
+/* Test if item X of type T is present in the 0 terminated list L */
+# define XinL(T, X, L) ( { \
+ T x = (X), *l = (L); \
+ while (*l && *l != x) l++; \
+ *l == x; \
+ } )
+
+/* Test if item X of type T is present in the list L of length N */
+# define XinLN(T, X, L, N) ( { \
+ T x = (X), *l = (L); \
+ int i = 0, n = (N); \
+ while (i < n && l[i] != x) i++; \
+ i < n && l[i] == x; \
+ } )
+
+
+//////////////////////////////////////////////////////////////////////////////////
+// This reads process info from /proc in the traditional way, for one process.
+// The pid (tgid? tid?) is already in p, and a path to it in path, with some
+// room to spare.
+static proc_t *simple_readproc(PROCTAB *restrict const PT, proc_t *restrict const p) {
+ static __thread struct utlbuf_s ub = { NULL, 0 }; // buf for stat,statm,status
+ static __thread struct stat sb; // stat() buffer
+ char *restrict const path = PT->path;
+ unsigned flags = PT->flags;
+ int rc = 0;
+
+ if (stat(path, &sb) == -1) /* no such dirent (anymore) */
+ goto next_proc;
+
+ if ((flags & PROC_UID) && !XinLN(uid_t, sb.st_uid, PT->uids, PT->nuid))
+ goto next_proc; /* not one of the requested uids */
+
+ p->euid = sb.st_uid; /* need a way to get real uid */
+ p->egid = sb.st_gid; /* need a way to get real gid */
+
+ if (flags & PROC_FILLSTAT) { // read /proc/#/stat
+ if (file2str(path, "stat", &ub) == -1)
+ goto next_proc;
+ rc += stat2proc(ub.buf, p);
+ }
+
+ if (flags & PROC_FILLIO) { // read /proc/#/io
+ if (file2str(path, "io", &ub) != -1)
+ io2proc(ub.buf, p);
+ }
+
+ if (flags & PROC_FILLSMAPS) { // read /proc/#/smaps_rollup
+ if (file2str(path, "smaps_rollup", &ub) != -1)
+ smaps2proc(ub.buf, p);
+ }
+
+ if (flags & PROC_FILLMEM) { // read /proc/#/statm
+ if (file2str(path, "statm", &ub) != -1)
+ statm2proc(ub.buf, p);
+ }
+
+ if (flags & PROC_FILLSTATUS) { // read /proc/#/status
+ if (file2str(path, "status", &ub) != -1){
+ rc += status2proc(ub.buf, p, 1);
+ if (flags & (PROC_FILL_SUPGRP & ~PROC_FILLSTATUS))
+ rc += supgrps_from_supgids(p);
+ if (flags & (PROC_FILL_OUSERS & ~PROC_FILLSTATUS)) {
+ p->ruser = pwcache_get_user(p->ruid);
+ p->suser = pwcache_get_user(p->suid);
+ p->fuser = pwcache_get_user(p->fuid);
+ }
+ if (flags & (PROC_FILL_OGROUPS & ~PROC_FILLSTATUS)) {
+ p->rgroup = pwcache_get_group(p->rgid);
+ p->sgroup = pwcache_get_group(p->sgid);
+ p->fgroup = pwcache_get_group(p->fgid);
+ }
+ }
+ }
+
+ // if multithreaded, some values are crap
+ if(p->nlwp > 1)
+ p->wchan = ~0ul;
+
+ /* some number->text resolving which is time consuming */
+ /* ( names are cached, so memcpy to arrays was silly ) */
+ if (flags & PROC_FILLUSR)
+ p->euser = pwcache_get_user(p->euid);
+ if (flags & PROC_FILLGRP)
+ p->egroup = pwcache_get_group(p->egid);
+
+ if (flags & PROC_FILLENV) // read /proc/#/environ
+ if (!(p->environ_v = file2strvec(path, "environ")))
+ rc += vectorize_dash_rc(&p->environ_v);
+ if (flags & PROC_EDITENVRCVT)
+ rc += fill_environ_cvt(path, p);
+
+ if (flags & PROC_FILLARG) // read /proc/#/cmdline
+ if (!(p->cmdline_v = file2strvec(path, "cmdline")))
+ rc += vectorize_dash_rc(&p->cmdline_v);
+ if (flags & PROC_EDITCMDLCVT)
+ rc += fill_cmdline_cvt(path, p);
+
+ if ((flags & PROC_FILLCGROUP)) // read /proc/#/cgroup
+ if (!(p->cgroup_v = file2strvec(path, "cgroup")))
+ rc += vectorize_dash_rc(&p->cgroup_v);
+ if (flags & PROC_EDITCGRPCVT)
+ rc += fill_cgroup_cvt(path, p);
+
+ if (flags & PROC_FILLOOM) {
+ if (file2str(path, "oom_score", &ub) != -1)
+ oomscore2proc(ub.buf, p);
+ if (file2str(path, "oom_score_adj", &ub) != -1)
+ oomadj2proc(ub.buf, p);
+ }
+
+ if (flags & PROC_FILLNS) // read /proc/#/ns/*
+ procps_ns_read_pid(p->tid, &(p->ns));
+
+
+ if (flags & PROC_FILLSYSTEMD) // get sd-login.h stuff
+ rc += sd2proc(p);
+
+ if (flags & PROC_FILL_LXC) // value the lxc name
+ p->lxcname = lxc_containers(path);
+
+ if (flags & PROC_FILL_LUID) // value the login user id
+ p->luid = login_uid(path);
+
+ if (flags & PROC_FILL_EXE) {
+ if (!(p->exe = readlink_exe(path)))
+ rc += 1;
+ }
+
+ if (flags & PROC_FILLAUTOGRP) // value the 2 autogroup fields
+ autogroup_fill(path, p);
+
+ // openproc() ensured that a ppid will be present when needed ...
+ if (rc == 0) {
+ if (PT->hide_kernel && (p->ppid == 2 || p->tid == 2)) {
+ free_acquired(p);
+ return NULL;
+ }
+ return p;
+ }
+ errno = ENOMEM;
+next_proc:
+ return NULL;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////
+// This reads /proc/*/task/* data, for one task.
+// t is the POSIX thread (task group member, generally not the leader)
+// path is a path to the task, with some room to spare.
+static proc_t *simple_readtask(PROCTAB *restrict const PT, proc_t *restrict const t, char *restrict const path) {
+ static __thread struct utlbuf_s ub = { NULL, 0 }; // buf for stat,statm,status
+ static __thread struct stat sb; // stat() buffer
+ unsigned flags = PT->flags;
+ int rc = 0;
+
+ if (stat(path, &sb) == -1) /* no such dirent (anymore) */
+ goto next_task;
+
+// if ((flags & PROC_UID) && !XinLN(uid_t, sb.st_uid, PT->uids, PT->nuid))
+// goto next_task; /* not one of the requested uids */
+
+ t->euid = sb.st_uid; /* need a way to get real uid */
+ t->egid = sb.st_gid; /* need a way to get real gid */
+
+ if (flags & PROC_FILLSTAT) { // read /proc/#/task/#/stat
+ if (file2str(path, "stat", &ub) == -1)
+ goto next_task;
+ rc += stat2proc(ub.buf, t);
+ }
+
+ if (flags & PROC_FILLIO) { // read /proc/#/task/#/io
+ if (file2str(path, "io", &ub) != -1)
+ io2proc(ub.buf, t);
+ }
+
+ if (flags & PROC_FILLSMAPS) { // read /proc/#/task/#/smaps_rollup
+ if (file2str(path, "smaps_rollup", &ub) != -1)
+ smaps2proc(ub.buf, t);
+ }
+
+ if (flags & PROC_FILLMEM) { // read /proc/#/task/#/statm
+ if (file2str(path, "statm", &ub) != -1)
+ statm2proc(ub.buf, t);
+ }
+
+ if (flags & PROC_FILLSTATUS) { // read /proc/#/task/#/status
+ if (file2str(path, "status", &ub) != -1) {
+ rc += status2proc(ub.buf, t, 0);
+ if (flags & (PROC_FILL_SUPGRP & ~PROC_FILLSTATUS))
+ rc += supgrps_from_supgids(t);
+ if (flags & (PROC_FILL_OUSERS & ~PROC_FILLSTATUS)) {
+ t->ruser = pwcache_get_user(t->ruid);
+ t->suser = pwcache_get_user(t->suid);
+ t->fuser = pwcache_get_user(t->fuid);
+ }
+ if (flags & (PROC_FILL_OGROUPS & ~PROC_FILLSTATUS)) {
+ t->rgroup = pwcache_get_group(t->rgid);
+ t->sgroup = pwcache_get_group(t->sgid);
+ t->fgroup = pwcache_get_group(t->fgid);
+ }
+ }
+ }
+
+ /* some number->text resolving which is time consuming */
+ /* ( names are cached, so memcpy to arrays was silly ) */
+ if (flags & PROC_FILLUSR)
+ t->euser = pwcache_get_user(t->euid);
+ if (flags & PROC_FILLGRP)
+ t->egroup = pwcache_get_group(t->egid);
+
+#ifdef FALSE_THREADS
+ if (!IS_THREAD(t)) {
+#endif
+ if (flags & PROC_FILLARG) // read /proc/#/task/#/cmdline
+ if (!(t->cmdline_v = file2strvec(path, "cmdline")))
+ rc += vectorize_dash_rc(&t->cmdline_v);
+ if (flags & PROC_EDITCMDLCVT)
+ rc += fill_cmdline_cvt(path, t);
+
+ if (flags & PROC_FILLENV) // read /proc/#/task/#/environ
+ if (!(t->environ_v = file2strvec(path, "environ")))
+ rc += vectorize_dash_rc(&t->environ_v);
+ if (flags & PROC_EDITENVRCVT)
+ rc += fill_environ_cvt(path, t);
+
+ if ((flags & PROC_FILLCGROUP)) // read /proc/#/task/#/cgroup
+ if (!(t->cgroup_v = file2strvec(path, "cgroup")))
+ rc += vectorize_dash_rc(&t->cgroup_v);
+ if (flags & PROC_EDITCGRPCVT)
+ rc += fill_cgroup_cvt(path, t);
+
+ if (flags & PROC_FILLSYSTEMD) // get sd-login.h stuff
+ rc += sd2proc(t);
+
+ if (flags & PROC_FILL_EXE) {
+ if (!(t->exe = readlink_exe(path)))
+ rc += 1;
+ }
+#ifdef FALSE_THREADS
+ }
+#endif
+
+ if (flags & PROC_FILLOOM) {
+ if (file2str(path, "oom_score", &ub) != -1)
+ oomscore2proc(ub.buf, t);
+ if (file2str(path, "oom_score_adj", &ub) != -1)
+ oomadj2proc(ub.buf, t);
+ }
+ if (flags & PROC_FILLNS) // read /proc/#/task/#/ns/*
+ procps_ns_read_pid(t->tid, &(t->ns));
+
+ if (flags & PROC_FILL_LXC)
+ t->lxcname = lxc_containers(path);
+
+ if (flags & PROC_FILL_LUID)
+ t->luid = login_uid(path);
+
+ if (flags & PROC_FILLAUTOGRP) // value the 2 autogroup fields
+ autogroup_fill(path, t);
+
+ // openproc() ensured that a ppid will be present when needed ...
+ if (rc == 0) {
+ if (PT->hide_kernel && (t->ppid == 2 || t->tid == 2)) {
+ free_acquired(t);
+ return NULL;
+ }
+ return t;
+ }
+ errno = ENOMEM;
+next_task:
+ return NULL;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////
+// This finds processes in /proc in the traditional way.
+// Return non-zero on success.
+static int simple_nextpid(PROCTAB *restrict const PT, proc_t *restrict const p) {
+ static __thread struct dirent *ent; /* dirent handle */
+ char *restrict const path = PT->path;
+ for (;;) {
+ ent = readdir(PT->procfs);
+ if (!ent || !ent->d_name[0]) break;
+ if (*ent->d_name > '0' && *ent->d_name <= '9') {
+ errno = 0;
+ p->tgid = strtoul(ent->d_name, NULL, 10);
+ if (errno == 0) {
+ p->tid = p->tgid;
+ snprintf(path, PROCPATHLEN, "/proc/%d", p->tgid);
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////
+// This finds tasks in /proc/*/task/ in the traditional way.
+// Return non-zero on success.
+static int simple_nexttid(PROCTAB *restrict const PT, const proc_t *restrict const p, proc_t *restrict const t, char *restrict const path) {
+ static __thread struct dirent *ent; /* dirent handle */
+ if(PT->taskdir_user != p->tgid){
+ if(PT->taskdir){
+ closedir(PT->taskdir);
+ }
+ // use "path" as some tmp space
+ snprintf(path, PROCPATHLEN, "/proc/%d/task", p->tgid);
+ PT->taskdir = opendir(path);
+ if(!PT->taskdir) return 0;
+ PT->taskdir_user = p->tgid;
+ }
+ for (;;) {
+ ent = readdir(PT->taskdir);
+ if(!ent || !ent->d_name[0]) return 0;
+ if(*ent->d_name > '0' && *ent->d_name <= '9') break;
+ }
+ t->tid = strtoul(ent->d_name, NULL, 10);
+ t->tgid = p->tgid;
+//t->ppid = p->ppid; // cover for kernel behavior? we want both actually...?
+ snprintf(path, PROCPATHLEN, "/proc/%d/task/%.10s", p->tgid, ent->d_name);
+ return 1;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////
+// This "finds" processes in a list that was given to openproc().
+// Return non-zero on success. (tgid is a real headache)
+static int listed_nextpid (PROCTAB *PT, proc_t *p) {
+ static __thread struct utlbuf_s ub = { NULL, 0 };
+ pid_t pid = *(PT->pids)++;
+ char *path = PT->path;
+
+ if (pid) {
+ snprintf(path, PROCPATHLEN, "/proc/%d", pid);
+ p->tid = p->tgid = pid; // this tgid may be a huge fib |
+
+ /* the 'status' directory is the only place where we find the |
+ task's real tgid. it's a bit expensive, but remember we're |
+ dealing with fewer processes, unlike the other 'next' guys |
+ (plus we need not parse the whole thing like status2proc)! | */
+
+ if (file2str(path, "status", &ub) != -1) {
+ char *str = strstr(ub.buf, "Tgid:");
+ if (str)
+ p->tgid = atoi(str + 5); // this tgid is the proper one |
+ }
+ }
+ return pid;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////
+/* readproc: return a pointer to a proc_t filled with requested info about the
+ * next process available matching the restriction set. If no more such
+ * processes are available, return a null pointer (boolean false). Use the
+ * passed buffer instead of allocating space if it is non-NULL. */
+
+/* This is optimized so that if a PID list is given, only those files are
+ * searched for in /proc. If other lists are given in addition to the PID list,
+ * the same logic can follow through as for the no-PID list case. This is
+ * fairly complex, but it does try to not to do any unnecessary work.
+ */
+proc_t *readproc(PROCTAB *restrict const PT, proc_t *restrict p) {
+ proc_t *ret;
+
+ free_acquired(p);
+
+ for(;;){
+ // fills in the path, plus p->tid and p->tgid
+ if (!PT->finder(PT,p)) goto out;
+
+ // go read the process data
+ ret = PT->reader(PT,p);
+ if(ret) return ret;
+ }
+
+out:
+ return NULL;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////
+// readeither: return a pointer to a proc_t filled with requested info about
+// the next unique process or task available. If no more are available,
+// return a null pointer (boolean false).
+proc_t *readeither (PROCTAB *restrict const PT, proc_t *restrict x) {
+ static __thread proc_t skel_p; // skeleton proc_t, only uses tid + tgid
+ static __thread proc_t *new_p; // for process/task transitions
+ static __thread int canary;
+ char path[PROCPATHLEN];
+ proc_t *ret;
+
+ free_acquired(x);
+
+ if (new_p) {
+ if (new_p->tid != canary) new_p = NULL;
+ goto next_task;
+ }
+
+next_proc:
+ new_p = NULL;
+ for (;;) {
+ // fills in the PT->path, plus skel_p.tid and skel_p.tgid
+ if (!PT->finder(PT,&skel_p)) goto end_procs; // simple_nextpid
+ if (!task_dir_missing) break;
+ if ((ret = PT->reader(PT,x))) return ret; // simple_readproc
+ }
+
+next_task:
+ // fills in our path, plus x->tid and x->tgid
+ if ((!(PT->taskfinder(PT,&skel_p,x,path))) // simple_nexttid
+ || (!(ret = PT->taskreader(PT,x,path)))) { // simple_readtask
+ goto next_proc;
+ }
+ if (!new_p) {
+ new_p = ret;
+ canary = new_p->tid;
+ }
+ return ret;
+
+end_procs:
+ return NULL;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////
+
+// initiate a process table scan
+PROCTAB *openproc(unsigned flags, ...) {
+ va_list ap;
+ struct stat sbuf;
+ static __thread int did_stat;
+ static __thread int hide_kernel = -1;
+ PROCTAB *PT = calloc(1, sizeof(PROCTAB));
+
+ if (!PT)
+ return NULL;
+ if (hide_kernel < 0)
+ hide_kernel = (NULL != getenv("LIBPROC_HIDE_KERNEL"));
+ if (!did_stat){
+ task_dir_missing = stat("/proc/self/task", &sbuf);
+ did_stat = 1;
+ }
+ PT->taskdir = NULL;
+ PT->taskdir_user = -1;
+ PT->taskfinder = simple_nexttid;
+ PT->taskreader = simple_readtask;
+
+ PT->reader = simple_readproc;
+ if (flags & PROC_PID){
+ PT->procfs = NULL;
+ PT->finder = listed_nextpid;
+ }else{
+ PT->procfs = opendir("/proc");
+ if (!PT->procfs) { free(PT); return NULL; }
+ PT->finder = simple_nextpid;
+ }
+ PT->flags = flags;
+
+ va_start(ap, flags);
+ if (flags & PROC_PID)
+ PT->pids = va_arg(ap, pid_t*);
+ else if (flags & PROC_UID){
+ PT->uids = va_arg(ap, uid_t*);
+ PT->nuid = va_arg(ap, int);
+ }
+ va_end(ap);
+
+ if (hide_kernel > 0) {
+ PT->hide_kernel = 1;
+ // we'll need the ppid, ensure it's obtained via cheapest means ...
+ if (!(PT->flags & (PROC_FILLSTAT | PROC_FILLSTATUS)))
+ PT->flags |= PROC_FILLSTAT;
+ }
+
+ if (!src_buffer
+ && !(src_buffer = malloc(MAX_BUFSZ))) {
+ closedir(PT->procfs);
+ free(PT);
+ return NULL;
+ }
+ if (!dst_buffer
+ && !(dst_buffer = malloc(MAX_BUFSZ))) {
+ closedir(PT->procfs);
+ free(src_buffer);
+ free(PT);
+ return NULL;
+ }
+
+ return PT;
+}
+
+
+// terminate a process table scan
+void closeproc(PROCTAB *PT) {
+ if (PT){
+ if (PT->procfs) closedir(PT->procfs);
+ if (PT->taskdir) closedir(PT->taskdir);
+ memset(PT,'#',sizeof(PROCTAB));
+ free(PT);
+ }
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////
+int look_up_our_self(void) {
+ struct utlbuf_s ub = { NULL, 0 };
+ int rc = 0;
+ proc_t p;
+
+ memset(&p, 0, sizeof(proc_t));
+ if(file2str("/proc/self", "stat", &ub) == -1){
+ fprintf(stderr, "Error, do this: mount -t proc proc /proc\n");
+ _exit(47);
+ }
+ rc = stat2proc(ub.buf, &p); // parse /proc/self/stat
+ free_acquired(&p);
+ free(ub.buf);
+ return !rc;
+}
+
+#undef IS_THREAD
+#undef MAX_BUFSZ
diff --git a/library/slabinfo.c b/library/slabinfo.c
new file mode 100644
index 0000000..b87f353
--- /dev/null
+++ b/library/slabinfo.c
@@ -0,0 +1,1044 @@
+/*
+ * slabinfo.c - slab pools related definitions for libproc2
+ *
+ * Copyright © 2015-2023 Jim Warner <james.warner@comcast.net>
+ * Copyright © 2015-2023 Craig Small <csmall@dropbear.xyz>
+ * Copyright © 2004-2006 Albert Cahalan
+ * Copyright © 2003 Chris Rivera
+ * Copyright © 2003 Fabian Frederick
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "procps-private.h"
+#include "slabinfo.h"
+
+
+#define SLABINFO_FILE "/proc/slabinfo"
+#define SLABINFO_LINE_LEN 2048
+#define SLABINFO_NAME_LEN 128
+
+#define STACKS_INCR 128 // amount reap stack allocations grow
+
+/* ---------------------------------------------------------------------------- +
+ this #define will be used to help ensure that our Item_table is synchronized |
+ with all the enumerators found in the associated header file. It is intended |
+ to only be defined locally (and temporarily) at some point prior to release! | */
+// #define ITEMTABLE_DEBUG //-------------------------------------------------- |
+// ---------------------------------------------------------------------------- +
+
+/*
+ Because 'select' could, at most, return only node[0] values and since 'reap' |
+ would be forced to duplicate global slabs stuff in every node results stack, |
+ the following #define can be used to enforce strictly logical return values. |
+ select: allow only SLABINFO & SLABS items
+ reap: allow only SLABINFO & SLAB items
+ Without the #define, these functions always return something even if just 0. |
+ get: return only SLABS results, else 0
+ select: return only SLABINFO & SLABS results, else zero
+ reap: return any requested, even when duplicated in each cache's stack */
+//#define ENFORCE_LOGICAL // ensure only logical items accepted by select/reap
+
+
+struct slabs_summ {
+ unsigned int nr_objs; // number of objects, among all caches
+ unsigned int nr_active_objs; // number of active objects, among all caches
+ unsigned int nr_pages; // number of pages consumed by all objects
+ unsigned int nr_slabs; // number of slabs, among all caches
+ unsigned int nr_active_slabs; // number of active slabs, among all caches
+ unsigned int nr_caches; // number of caches
+ unsigned int nr_active_caches; // number of active caches
+ unsigned int avg_obj_size; // average object size
+ unsigned int min_obj_size; // size of smallest object
+ unsigned int max_obj_size; // size of largest object
+ unsigned long active_size; // size of all active objects
+ unsigned long total_size; // size of all objects
+};
+
+struct slabs_node {
+ char name[SLABINFO_NAME_LEN+1]; // name of this cache
+ unsigned long cache_size; // size of entire cache
+ unsigned int nr_objs; // number of objects in this cache
+ unsigned int nr_active_objs; // number of active objects
+ unsigned int obj_size; // size of each object
+ unsigned int objs_per_slab; // number of objects per slab
+ unsigned int pages_per_slab; // number of pages per slab
+ unsigned int nr_slabs; // number of slabs in this cache
+ unsigned int nr_active_slabs; // number of active slabs
+ unsigned int use; // percent full: total / active
+};
+
+struct slabs_hist {
+ struct slabs_summ new;
+ struct slabs_summ old;
+};
+
+struct stacks_extent {
+ int ext_numstacks;
+ struct stacks_extent *next;
+ struct slabinfo_stack **stacks;
+};
+
+struct ext_support {
+ int numitems; // includes 'logical_end' delimiter
+ enum slabinfo_item *items; // includes 'logical_end' delimiter
+ struct stacks_extent *extents; // anchor for these extents
+#ifdef ENFORCE_LOGICAL
+ enum slabinfo_item lowest; // range of allowable enums
+ enum slabinfo_item highest;
+#endif
+};
+
+struct fetch_support {
+ struct slabinfo_stack **anchor; // fetch consolidated extents
+ int n_alloc; // number of above pointers allocated
+ int n_inuse; // number of above pointers occupied
+ int n_alloc_save; // last known reap.stacks allocation
+ struct slabinfo_reaped results; // count + stacks for return to caller
+};
+
+struct slabinfo_info {
+ int refcount;
+ FILE *slabinfo_fp;
+ int nodes_alloc; // nodes alloc()ed
+ int nodes_used; // nodes using alloced memory
+ struct slabs_node *nodes; // first slabnode of this list
+ struct slabs_hist slabs; // new/old slabs_summ data
+ struct ext_support select_ext; // supports concurrent select/reap
+ struct ext_support fetch_ext; // supports concurrent select/reap
+ struct fetch_support fetch; // support for procps_slabinfo_reap
+ struct slabs_node nul_node; // used by slabinfo_get/select
+ struct slabinfo_result get_this; // used by slabinfo_get
+ time_t sav_secs; // used by slabinfo_get
+};
+
+
+// ___ Results 'Set' Support ||||||||||||||||||||||||||||||||||||||||||||||||||
+
+#define setNAME(e) set_ ## e
+#define setDECL(e) static void setNAME(e) \
+ (struct slabinfo_result *R, struct slabs_hist *S, struct slabs_node *N)
+
+// regular assignment
+#define REG_set(e,t,x) setDECL(e) { (void)N; R->result. t = S->new. x; }
+#define NOD_set(e,t,x) setDECL(e) { (void)S; R->result. t = N-> x; }
+// delta assignment
+#define HST_set(e,t,x) setDECL(e) { (void)N; R->result. t = (signed long)S->new. x - S->old. x; }
+
+setDECL(SLABINFO_noop) { (void)R; (void)S; (void)N; }
+setDECL(SLABINFO_extra) { (void)S; (void)N; R->result.ul_int = 0; }
+
+NOD_set(SLAB_NAME, str, name)
+NOD_set(SLAB_NUM_OBJS, u_int, nr_objs)
+NOD_set(SLAB_ACTIVE_OBJS, u_int, nr_active_objs)
+NOD_set(SLAB_OBJ_SIZE, u_int, obj_size)
+NOD_set(SLAB_OBJ_PER_SLAB, u_int, objs_per_slab)
+NOD_set(SLAB_NUMS_SLABS, u_int, nr_slabs)
+NOD_set(SLAB_ACTIVE_SLABS, u_int, nr_active_slabs)
+NOD_set(SLAB_PAGES_PER_SLAB, u_int, pages_per_slab)
+NOD_set(SLAB_PERCENT_USED, u_int, use)
+NOD_set(SLAB_SIZE_TOTAL, ul_int, cache_size)
+
+REG_set(SLABS_CACHES_TOTAL, u_int, nr_caches)
+REG_set(SLABS_CACHES_ACTIVE, u_int, nr_active_caches)
+REG_set(SLABS_NUM_OBJS, u_int, nr_objs)
+REG_set(SLABS_ACTIVE_OBJS, u_int, nr_active_objs)
+REG_set(SLABS_OBJ_SIZE_AVG, u_int, avg_obj_size)
+REG_set(SLABS_OBJ_SIZE_MIN, u_int, min_obj_size)
+REG_set(SLABS_OBJ_SIZE_MAX, u_int, max_obj_size)
+REG_set(SLABS_NUMS_SLABS, u_int, nr_slabs)
+REG_set(SLABS_ACTIVE_SLABS, u_int, nr_active_slabs)
+REG_set(SLABS_PAGES_TOTAL, u_int, nr_pages)
+REG_set(SLABS_SIZE_ACTIVE, ul_int, active_size)
+REG_set(SLABS_SIZE_TOTAL, ul_int, total_size)
+
+HST_set(SLABS_DELTA_CACHES_TOTAL, s_int, nr_caches)
+HST_set(SLABS_DELTA_CACHES_ACTIVE, s_int, nr_active_caches)
+HST_set(SLABS_DELTA_NUM_OBJS, s_int, nr_objs)
+HST_set(SLABS_DELTA_ACTIVE_OBJS, s_int, nr_active_objs)
+HST_set(SLABS_DELTA_OBJ_SIZE_AVG, s_int, avg_obj_size)
+HST_set(SLABS_DELTA_OBJ_SIZE_MIN, s_int, min_obj_size)
+HST_set(SLABS_DELTA_OBJ_SIZE_MAX, s_int, max_obj_size)
+HST_set(SLABS_DELTA_NUMS_SLABS, s_int, nr_slabs)
+HST_set(SLABS_DELTA_ACTIVE_SLABS, s_int, nr_active_slabs)
+HST_set(SLABS_DELTA_PAGES_TOTAL, s_int, nr_pages)
+HST_set(SLABS_DELTA_SIZE_ACTIVE, s_int, active_size)
+HST_set(SLABS_DELTA_SIZE_TOTAL, s_int, total_size)
+
+#undef setDECL
+#undef REG_set
+#undef NOD_set
+#undef HST_set
+
+
+// ___ Sorting Support ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
+struct sort_parms {
+ int offset;
+ enum slabinfo_sort_order order;
+};
+
+#define srtNAME(t) sort_slabinfo_ ## t
+#define srtDECL(t) static int srtNAME(t) \
+ (const struct slabinfo_stack **A, const struct slabinfo_stack **B, struct sort_parms *P)
+
+srtDECL(u_int) {
+ const struct slabinfo_result *a = (*A)->head + P->offset; \
+ const struct slabinfo_result *b = (*B)->head + P->offset; \
+ if ( a->result.u_int > b->result.u_int ) return P->order > 0 ? 1 : -1; \
+ if ( a->result.u_int < b->result.u_int ) return P->order > 0 ? -1 : 1; \
+ return 0;
+}
+
+srtDECL(ul_int) {
+ const struct slabinfo_result *a = (*A)->head + P->offset; \
+ const struct slabinfo_result *b = (*B)->head + P->offset; \
+ if ( a->result.ul_int > b->result.ul_int ) return P->order > 0 ? 1 : -1; \
+ if ( a->result.ul_int < b->result.ul_int ) return P->order > 0 ? -1 : 1; \
+ return 0;
+}
+
+srtDECL(str) {
+ const struct slabinfo_result *a = (*A)->head + P->offset;
+ const struct slabinfo_result *b = (*B)->head + P->offset;
+ return P->order * strcoll(a->result.str, b->result.str);
+}
+
+srtDECL(noop) { \
+ (void)A; (void)B; (void)P; \
+ return 0;
+}
+
+#undef srtDECL
+
+
+// ___ Controlling Table ||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
+typedef void (*SET_t)(struct slabinfo_result *, struct slabs_hist *, struct slabs_node *);
+#ifdef ITEMTABLE_DEBUG
+#define RS(e) (SET_t)setNAME(e), e, STRINGIFY(e)
+#else
+#define RS(e) (SET_t)setNAME(e)
+#endif
+
+typedef int (*QSR_t)(const void *, const void *, void *);
+#define QS(t) (QSR_t)srtNAME(t)
+
+#define TS(t) STRINGIFY(t)
+#define TS_noop ""
+
+ /*
+ * Need it be said?
+ * This table must be kept in the exact same order as
+ * those *enum slabinfo_item* guys ! */
+static struct {
+ SET_t setsfunc; // the actual result setting routine
+#ifdef ITEMTABLE_DEBUG
+ int enumnumb; // enumerator (must match position!)
+ char *enum2str; // enumerator name as a char* string
+#endif
+ QSR_t sortfunc; // sort cmp func for a specific type
+ char *type2str; // the result type as a string value
+} Item_table[] = {
+/* setsfunc sortfunc type2str
+ ------------------------------ ----------- ---------- */
+ { RS(SLABINFO_noop), QS(noop), TS_noop },
+ { RS(SLABINFO_extra), QS(ul_int), TS_noop },
+
+ { RS(SLAB_NAME), QS(str), TS(str) },
+ { RS(SLAB_NUM_OBJS), QS(u_int), TS(u_int) },
+ { RS(SLAB_ACTIVE_OBJS), QS(u_int), TS(u_int) },
+ { RS(SLAB_OBJ_SIZE), QS(u_int), TS(u_int) },
+ { RS(SLAB_OBJ_PER_SLAB), QS(u_int), TS(u_int) },
+ { RS(SLAB_NUMS_SLABS), QS(u_int), TS(u_int) },
+ { RS(SLAB_ACTIVE_SLABS), QS(u_int), TS(u_int) },
+ { RS(SLAB_PAGES_PER_SLAB), QS(u_int), TS(u_int) },
+ { RS(SLAB_PERCENT_USED), QS(u_int), TS(u_int) },
+ { RS(SLAB_SIZE_TOTAL), QS(ul_int), TS(ul_int) },
+
+ { RS(SLABS_CACHES_TOTAL), QS(noop), TS(u_int) },
+ { RS(SLABS_CACHES_ACTIVE), QS(noop), TS(u_int) },
+ { RS(SLABS_NUM_OBJS), QS(noop), TS(u_int) },
+ { RS(SLABS_ACTIVE_OBJS), QS(noop), TS(u_int) },
+ { RS(SLABS_OBJ_SIZE_AVG), QS(noop), TS(u_int) },
+ { RS(SLABS_OBJ_SIZE_MIN), QS(noop), TS(u_int) },
+ { RS(SLABS_OBJ_SIZE_MAX), QS(noop), TS(u_int) },
+ { RS(SLABS_NUMS_SLABS), QS(noop), TS(u_int) },
+ { RS(SLABS_ACTIVE_SLABS), QS(noop), TS(u_int) },
+ { RS(SLABS_PAGES_TOTAL), QS(noop), TS(u_int) },
+ { RS(SLABS_SIZE_ACTIVE), QS(noop), TS(ul_int) },
+ { RS(SLABS_SIZE_TOTAL), QS(noop), TS(ul_int) },
+
+ { RS(SLABS_DELTA_CACHES_TOTAL), QS(noop), TS(s_int) },
+ { RS(SLABS_DELTA_CACHES_ACTIVE), QS(noop), TS(s_int) },
+ { RS(SLABS_DELTA_NUM_OBJS), QS(noop), TS(s_int) },
+ { RS(SLABS_DELTA_ACTIVE_OBJS), QS(noop), TS(s_int) },
+ { RS(SLABS_DELTA_OBJ_SIZE_AVG), QS(noop), TS(s_int) },
+ { RS(SLABS_DELTA_OBJ_SIZE_MIN), QS(noop), TS(s_int) },
+ { RS(SLABS_DELTA_OBJ_SIZE_MAX), QS(noop), TS(s_int) },
+ { RS(SLABS_DELTA_NUMS_SLABS), QS(noop), TS(s_int) },
+ { RS(SLABS_DELTA_ACTIVE_SLABS), QS(noop), TS(s_int) },
+ { RS(SLABS_DELTA_PAGES_TOTAL), QS(noop), TS(s_int) },
+ { RS(SLABS_DELTA_SIZE_ACTIVE), QS(noop), TS(s_int) },
+ { RS(SLABS_DELTA_SIZE_TOTAL), QS(noop), TS(s_int) },
+};
+
+ /* please note,
+ * this enum MUST be 1 greater than the highest value of any enum */
+enum slabinfo_item SLABINFO_logical_end = MAXTABLE(Item_table);
+
+#undef setNAME
+#undef srtNAME
+#undef RS
+#undef QS
+
+
+// ___ Private Functions ||||||||||||||||||||||||||||||||||||||||||||||||||||||
+// --- slabnode specific support ----------------------------------------------
+
+/* Alloc up more slabnode memory, if required
+ */
+static int alloc_slabnodes (
+ struct slabinfo_info *info)
+{
+ struct slabs_node *new_nodes;
+ int new_count;
+
+ if (info->nodes_used < info->nodes_alloc)
+ return 1;
+ /* Increment the allocated number of slabs */
+ new_count = info->nodes_alloc * 5/4+30;
+
+ new_nodes = realloc(info->nodes, sizeof(struct slabs_node) * new_count);
+ if (!new_nodes)
+ return 0;
+ info->nodes = new_nodes;
+ info->nodes_alloc = new_count;
+ return 1;
+} // end: alloc_slabnodes
+
+
+/*
+ * get_slabnode - allocate slab_info structures using a free list
+ *
+ * In the fast path, we simply return a node off the free list. In the slow
+ * list, we malloc() a new node. The free list is never automatically reaped,
+ * both for simplicity and because the number of slab caches is fairly
+ * constant.
+ */
+static int get_slabnode (
+ struct slabinfo_info *info,
+ struct slabs_node **node)
+{
+ if (info->nodes_used == info->nodes_alloc) {
+ if (!alloc_slabnodes(info))
+ return 0; // here, errno was set to ENOMEM
+ }
+ *node = &(info->nodes[info->nodes_used++]);
+ return 1;
+} // end: get_slabnode
+
+
+/* parse_slabinfo20:
+ *
+ * Actual parse routine for slabinfo 2.x (2.6 kernels)
+ * Note: difference between 2.0 and 2.1 is in the ": globalstat" part where version 2.1
+ * has extra column <nodeallocs>. We don't use ": globalstat" part in both versions.
+ *
+ * Formats (we don't use "statistics" extensions)
+ *
+ * slabinfo - version: 2.1
+ * # name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> \
+ * : tunables <batchcount> <limit> <sharedfactor> \
+ * : slabdata <active_slabs> <num_slabs> <sharedavail>
+ *
+ * slabinfo - version: 2.1 (statistics)
+ * # name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> \
+ * : tunables <batchcount> <limit> <sharedfactor> \
+ * : slabdata <active_slabs> <num_slabs> <sharedavail> \
+ * : globalstat <listallocs> <maxobjs> <grown> <reaped> <error> <maxfreeable> <freelimit> <nodeallocs> \
+ * : cpustat <allochit> <allocmiss> <freehit> <freemiss>
+ *
+ * slabinfo - version: 2.0
+ * # name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> \
+ * : tunables <batchcount> <limit> <sharedfactor> \
+ * : slabdata <active_slabs> <num_slabs> <sharedavail>
+ *
+ * slabinfo - version: 2.0 (statistics)
+ * # name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> \
+ * : tunables <batchcount> <limit> <sharedfactor> \
+ * : slabdata <active_slabs> <num_slabs> <sharedavail> \
+ * : globalstat <listallocs> <maxobjs> <grown> <reaped> <error> <maxfreeable> <freelimit> \
+ * : cpustat <allochit> <allocmiss> <freehit> <freemiss>
+ */
+static int parse_slabinfo20 (
+ struct slabinfo_info *info)
+{
+ struct slabs_node *node;
+ char buffer[SLABINFO_LINE_LEN];
+ int page_size = getpagesize();
+ struct slabs_summ *slabs = &(info->slabs.new);
+
+ slabs->min_obj_size = INT_MAX;
+ slabs->max_obj_size = 0;
+
+ while (fgets(buffer, SLABINFO_LINE_LEN, info->slabinfo_fp )) {
+ if (buffer[0] == '#')
+ continue;
+
+ if (!get_slabnode(info, &node))
+ return 1; // here, errno was set to ENOMEM
+
+ if (sscanf(buffer,
+ "%" STRINGIFY(SLABINFO_NAME_LEN) "s" \
+ "%u %u %u %u %u : tunables %*u %*u %*u : slabdata %u %u %*u",
+ node->name,
+ &node->nr_active_objs, &node->nr_objs,
+ &node->obj_size, &node->objs_per_slab,
+ &node->pages_per_slab, &node->nr_active_slabs,
+ &node->nr_slabs) < 8) {
+ errno = ERANGE;
+ return 1;
+ }
+
+ if (!node->name[0])
+ snprintf(node->name, sizeof(node->name), "%s", "unknown");
+
+ if (node->obj_size < slabs->min_obj_size)
+ slabs->min_obj_size = node->obj_size;
+ if (node->obj_size > slabs->max_obj_size)
+ slabs->max_obj_size = node->obj_size;
+
+ /* cache_size is not accurate, it's the upper limit of memory used by this slab.
+ * When system using slub(most common case) is under high memory pressure, there
+ * are slab order fallbacks, which means pages_per_slab is not constant and may decrease.
+ */
+ node->cache_size = (unsigned long)node->nr_slabs * node->pages_per_slab * page_size;
+
+ if (node->nr_objs) {
+ node->use = (unsigned int)(100 * ((float)node->nr_active_objs / node->nr_objs));
+ slabs->nr_active_caches++;
+ } else
+ node->use = 0;
+
+ slabs->nr_objs += node->nr_objs;
+ slabs->nr_active_objs += node->nr_active_objs;
+ slabs->total_size += (unsigned long)node->nr_objs * node->obj_size;
+ slabs->active_size += (unsigned long)node->nr_active_objs * node->obj_size;
+ slabs->nr_pages += node->nr_slabs * node->pages_per_slab;
+ slabs->nr_slabs += node->nr_slabs;
+ slabs->nr_active_slabs += node->nr_active_slabs;
+ slabs->nr_caches++;
+ }
+
+ if (slabs->nr_objs)
+ slabs->avg_obj_size = slabs->total_size / slabs->nr_objs;
+
+ return 0;
+} // end: parse_slabinfo20
+
+
+/* slabinfo_read_failed():
+ *
+ * Read the data out of /proc/slabinfo putting the information
+ * into the supplied info container
+ *
+ * Returns: 0 on success, 1 on error
+ */
+static int slabinfo_read_failed (
+ struct slabinfo_info *info)
+{
+ char line[SLABINFO_LINE_LEN];
+ int major, minor;
+
+ memcpy(&info->slabs.old, &info->slabs.new, sizeof(struct slabs_summ));
+ memset(&(info->slabs.new), 0, sizeof(struct slabs_summ));
+ if (!alloc_slabnodes(info))
+ return 1; // here, errno was set to ENOMEM
+
+ memset(info->nodes, 0, sizeof(struct slabs_node)*info->nodes_alloc);
+ info->nodes_used = 0;
+
+ if (NULL == info->slabinfo_fp
+ && (info->slabinfo_fp = fopen(SLABINFO_FILE, "r")) == NULL)
+ return 1;
+
+ if (fseek(info->slabinfo_fp, 0L, SEEK_SET) < 0)
+ return 1;
+
+ /* Parse the version string */
+ if (!fgets(line, SLABINFO_LINE_LEN, info->slabinfo_fp))
+ return 1;
+
+ if (2 != sscanf(line, "slabinfo - version: %d.%d", &major, &minor)
+ || (major != 2)) {
+ errno = ERANGE;
+ return 1;
+ }
+
+ return parse_slabinfo20(info);
+} // end: slabinfo_read_failed
+
+
+// ___ Private Functions ||||||||||||||||||||||||||||||||||||||||||||||||||||||
+// --- generalized support ----------------------------------------------------
+
+static inline void slabinfo_assign_results (
+ struct slabinfo_stack *stack,
+ struct slabs_hist *summ,
+ struct slabs_node *node)
+{
+ struct slabinfo_result *this = stack->head;
+
+ for (;;) {
+ enum slabinfo_item item = this->item;
+ if (item >= SLABINFO_logical_end)
+ break;
+ Item_table[item].setsfunc(this, summ, node);
+ ++this;
+ }
+ return;
+} // end: slabinfo_assign_results
+
+
+static void slabinfo_extents_free_all (
+ struct ext_support *this)
+{
+ while (this->extents) {
+ struct stacks_extent *p = this->extents;
+ this->extents = this->extents->next;
+ free(p);
+ };
+} // end: slabinfo_extents_free_all
+
+
+static inline struct slabinfo_result *slabinfo_itemize_stack (
+ struct slabinfo_result *p,
+ int depth,
+ enum slabinfo_item *items)
+{
+ struct slabinfo_result *p_sav = p;
+ int i;
+
+ for (i = 0; i < depth; i++) {
+ p->item = items[i];
+ ++p;
+ }
+ return p_sav;
+} // end: slabinfo_itemize_stack
+
+
+static inline int slabinfo_items_check_failed (
+ struct ext_support *this,
+ enum slabinfo_item *items,
+ int numitems)
+{
+ int i;
+
+ /* if an enum is passed instead of an address of one or more enums, ol' gcc
+ * will silently convert it to an address (possibly NULL). only clang will
+ * offer any sort of warning like the following:
+ *
+ * warning: incompatible integer to pointer conversion passing 'int' to parameter of type 'enum slabinfo_item *'
+ * my_stack = procps_slabinfo_select(info, SLABINFO_noop, num);
+ * ^~~~~~~~~~~~~~~~
+ */
+ if (numitems < 1
+ || (void *)items < (void *)(unsigned long)(2 * SLABINFO_logical_end))
+ return 1;
+
+ for (i = 0; i < numitems; i++) {
+#ifdef ENFORCE_LOGICAL
+ if (items[i] == SLABINFO_noop
+ || (items[i] == SLABINFO_extra))
+ continue;
+ if (items[i] < this->lowest
+ || (items[i] > this->highest))
+ return 1;
+#else
+ // a slabinfo_item is currently unsigned, but we'll protect our future
+ if (items[i] < 0)
+ return 1;
+ if (items[i] >= SLABINFO_logical_end)
+ return 1;
+ (void)this;
+#endif
+ }
+
+ return 0;
+} // end: slabinfo_items_check_failed
+
+
+/*
+ * slabinfo_stacks_alloc():
+ *
+ * Allocate and initialize one or more stacks each of which is anchored in an
+ * associated context structure.
+ *
+ * All such stacks will have their result structures properly primed with
+ * 'items', while the result itself will be zeroed.
+ *
+ * Returns a stacks_extent struct anchoring the 'heads' of each new stack.
+ */
+static struct stacks_extent *slabinfo_stacks_alloc (
+ struct ext_support *this,
+ int maxstacks)
+{
+ struct stacks_extent *p_blob;
+ struct slabinfo_stack **p_vect;
+ struct slabinfo_stack *p_head;
+ size_t vect_size, head_size, list_size, blob_size;
+ void *v_head, *v_list;
+ int i;
+
+ vect_size = sizeof(void *) * maxstacks; // size of the addr vectors |
+ vect_size += sizeof(void *); // plus NULL addr delimiter |
+ head_size = sizeof(struct slabinfo_stack); // size of that head struct |
+ list_size = sizeof(struct slabinfo_result)*this->numitems; // any single results stack |
+ blob_size = sizeof(struct stacks_extent); // the extent anchor itself |
+ blob_size += vect_size; // plus room for addr vects |
+ blob_size += head_size * maxstacks; // plus room for head thing |
+ blob_size += list_size * maxstacks; // plus room for our stacks |
+
+ /* note: all of our memory is allocated in one single blob, facilitating a later free(). |
+ as a minimum, it is important that those result structures themselves always be |
+ contiguous within each stack since they are accessed through relative position. | */
+ if (NULL == (p_blob = calloc(1, blob_size)))
+ return NULL;
+
+ p_blob->next = this->extents; // push this extent onto... |
+ this->extents = p_blob; // ...some existing extents |
+ p_vect = (void *)p_blob + sizeof(struct stacks_extent); // prime our vector pointer |
+ p_blob->stacks = p_vect; // set actual vectors start |
+ v_head = (void *)p_vect + vect_size; // prime head pointer start |
+ v_list = v_head + (head_size * maxstacks); // prime our stacks pointer |
+
+ for (i = 0; i < maxstacks; i++) {
+ p_head = (struct slabinfo_stack *)v_head;
+ p_head->head = slabinfo_itemize_stack((struct slabinfo_result *)v_list, this->numitems, this->items);
+ p_blob->stacks[i] = p_head;
+ v_list += list_size;
+ v_head += head_size;
+ }
+ p_blob->ext_numstacks = maxstacks;
+ return p_blob;
+} // end: slabinfo_stacks_alloc
+
+
+static int slabinfo_stacks_fetch (
+ struct slabinfo_info *info)
+{
+ #define n_alloc info->fetch.n_alloc
+ #define n_inuse info->fetch.n_inuse
+ #define n_saved info->fetch.n_alloc_save
+ struct stacks_extent *ext;
+
+ // initialize stuff -----------------------------------
+ if (!info->fetch.anchor) {
+ if (!(info->fetch.anchor = calloc(sizeof(void *), STACKS_INCR)))
+ return -1;
+ n_alloc = STACKS_INCR;
+ }
+ if (!info->fetch_ext.extents) {
+ if (!(ext = slabinfo_stacks_alloc(&info->fetch_ext, n_alloc)))
+ return -1; // here, errno was set to ENOMEM
+ memcpy(info->fetch.anchor, ext->stacks, sizeof(void *) * n_alloc);
+ }
+
+ // iterate stuff --------------------------------------
+ n_inuse = 0;
+ while (n_inuse < info->nodes_used) {
+ if (!(n_inuse < n_alloc)) {
+ n_alloc += STACKS_INCR;
+ if ((!(info->fetch.anchor = realloc(info->fetch.anchor, sizeof(void *) * n_alloc)))
+ || (!(ext = slabinfo_stacks_alloc(&info->fetch_ext, STACKS_INCR))))
+ return -1; // here, errno was set to ENOMEM
+ memcpy(info->fetch.anchor + n_inuse, ext->stacks, sizeof(void *) * STACKS_INCR);
+ }
+ slabinfo_assign_results(info->fetch.anchor[n_inuse], &info->slabs, &info->nodes[n_inuse]);
+ ++n_inuse;
+ }
+
+ // finalize stuff -------------------------------------
+ /* note: we go to this trouble of maintaining a duplicate of the consolidated |
+ extent stacks addresses represented as our 'anchor' since these ptrs |
+ are exposed to a user (um, not that we don't trust 'em or anything). |
+ plus, we can NULL delimit these ptrs which we couldn't do otherwise. | */
+ if (n_saved < n_inuse + 1) {
+ n_saved = n_inuse + 1;
+ if (!(info->fetch.results.stacks = realloc(info->fetch.results.stacks, sizeof(void *) * n_saved)))
+ return -1;
+ }
+ memcpy(info->fetch.results.stacks, info->fetch.anchor, sizeof(void *) * n_inuse);
+ info->fetch.results.stacks[n_inuse] = NULL;
+ info->fetch.results.total = n_inuse;
+
+ return n_inuse;
+ #undef n_alloc
+ #undef n_inuse
+ #undef n_saved
+} // end: slabinfo_stacks_fetch
+
+
+static int slabinfo_stacks_reconfig_maybe (
+ struct ext_support *this,
+ enum slabinfo_item *items,
+ int numitems)
+{
+ if (slabinfo_items_check_failed(this, items, numitems))
+ return -1;
+ /* is this the first time or have things changed since we were last called?
+ if so, gotta' redo all of our stacks stuff ... */
+ if (this->numitems != numitems + 1
+ || memcmp(this->items, items, sizeof(enum slabinfo_item) * numitems)) {
+ // allow for our SLABINFO_logical_end
+ if (!(this->items = realloc(this->items, sizeof(enum slabinfo_item) * (numitems + 1))))
+ return -1;
+ memcpy(this->items, items, sizeof(enum slabinfo_item) * numitems);
+ this->items[numitems] = SLABINFO_logical_end;
+ this->numitems = numitems + 1;
+ slabinfo_extents_free_all(this);
+ return 1;
+ }
+ return 0;
+} // end: slabinfo_stacks_reconfig_maybe
+
+
+// ___ Public Functions |||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
+// --- standard required functions --------------------------------------------
+
+/*
+ * procps_slabinfo_new():
+ *
+ * @info: location of returned new structure
+ *
+ * Returns: < 0 on failure, 0 on success along with
+ * a pointer to a new context struct
+ */
+PROCPS_EXPORT int procps_slabinfo_new (
+ struct slabinfo_info **info)
+{
+ struct slabinfo_info *p;
+
+#ifdef ITEMTABLE_DEBUG
+ int i, failed = 0;
+ for (i = 0; i < MAXTABLE(Item_table); i++) {
+ if (i != Item_table[i].enumnumb) {
+ fprintf(stderr, "%s: enum/table error: Item_table[%d] was %s, but its value is %d\n"
+ , __FILE__, i, Item_table[i].enum2str, Item_table[i].enumnumb);
+ failed = 1;
+ }
+ }
+ if (failed) _Exit(EXIT_FAILURE);
+#endif
+
+ if (info == NULL || *info != NULL)
+ return -EINVAL;
+ if (!(p = calloc(1, sizeof(struct slabinfo_info))))
+ return -ENOMEM;
+
+#ifdef ENFORCE_LOGICAL
+ p->select_ext.lowest = SLABS_CACHES_TOTAL;
+ p->select_ext.highest = SLABS_DELTA_SIZE_TOTAL;
+ p->fetch_ext.lowest = SLAB_NAME;
+ p->fetch_ext.highest = SLAB_SIZE_TOTAL;
+#endif
+
+ p->refcount = 1;
+
+ /* do a priming read here for the following potential benefits: |
+ 1) see if that caller's permissions were sufficient (root) |
+ 2) make delta results potentially useful, even if 1st time |
+ 3) elimnate need for history distortions 1st time 'switch' | */
+ if (slabinfo_read_failed(p)) {
+ procps_slabinfo_unref(&p);
+ return -errno;
+ }
+
+ *info = p;
+ return 0;
+} // end: procps_slabinfo_new
+
+
+PROCPS_EXPORT int procps_slabinfo_ref (
+ struct slabinfo_info *info)
+{
+ if (info == NULL)
+ return -EINVAL;
+
+ info->refcount++;
+ return info->refcount;
+} // end: procps_slabinfo_ref
+
+
+PROCPS_EXPORT int procps_slabinfo_unref (
+ struct slabinfo_info **info)
+{
+ if (info == NULL || *info == NULL)
+ return -EINVAL;
+
+ (*info)->refcount--;
+
+ if ((*info)->refcount < 1) {
+ int errno_sav = errno;
+
+ if ((*info)->slabinfo_fp) {
+ fclose((*info)->slabinfo_fp);
+ (*info)->slabinfo_fp = NULL;
+ }
+ if ((*info)->select_ext.extents)
+ slabinfo_extents_free_all((&(*info)->select_ext));
+ if ((*info)->select_ext.items)
+ free((*info)->select_ext.items);
+
+ if ((*info)->fetch.anchor)
+ free((*info)->fetch.anchor);
+ if ((*info)->fetch.results.stacks)
+ free((*info)->fetch.results.stacks);
+
+ if ((*info)->fetch_ext.extents)
+ slabinfo_extents_free_all(&(*info)->fetch_ext);
+ if ((*info)->fetch_ext.items)
+ free((*info)->fetch_ext.items);
+
+ free((*info)->nodes);
+
+ free(*info);
+ *info = NULL;
+
+ errno = errno_sav;
+ return 0;
+ }
+ return (*info)->refcount;
+} // end: procps_slabinfo_unref
+
+
+// --- variable interface functions -------------------------------------------
+
+PROCPS_EXPORT struct slabinfo_result *procps_slabinfo_get (
+ struct slabinfo_info *info,
+ enum slabinfo_item item)
+{
+ time_t cur_secs;
+
+ errno = EINVAL;
+ if (info == NULL)
+ return NULL;
+ if (item < 0 || item >= SLABINFO_logical_end)
+ return NULL;
+ errno = 0;
+
+ /* we will NOT read the slabinfo file with every call - rather, we'll offer
+ a granularity of 1 second between reads ... */
+ cur_secs = time(NULL);
+ if (1 <= cur_secs - info->sav_secs) {
+ if (slabinfo_read_failed(info))
+ return NULL;
+ info->sav_secs = cur_secs;
+ }
+
+ info->get_this.item = item;
+ // with 'get', we must NOT honor the usual 'noop' guarantee
+ info->get_this.result.ul_int = 0;
+ Item_table[item].setsfunc(&info->get_this, &info->slabs, &info->nul_node);
+
+ return &info->get_this;
+} // end: procps_slabinfo_get
+
+
+/* procps_slabinfo_reap():
+ *
+ * Harvest all the requested SLAB (individual nodes) information
+ * providing the result stacks along with the total number of nodes.
+ *
+ * Returns: pointer to a slabinfo_reaped struct on success, NULL on error.
+ */
+PROCPS_EXPORT struct slabinfo_reaped *procps_slabinfo_reap (
+ struct slabinfo_info *info,
+ enum slabinfo_item *items,
+ int numitems)
+{
+ errno = EINVAL;
+ if (info == NULL || items == NULL)
+ return NULL;
+ if (0 > slabinfo_stacks_reconfig_maybe(&info->fetch_ext, items, numitems))
+ return NULL; // here, errno may be overridden with ENOMEM
+ errno = 0;
+
+ if (slabinfo_read_failed(info))
+ return NULL;
+ if (0 > slabinfo_stacks_fetch(info))
+ return NULL;
+
+ return &info->fetch.results;
+} // end: procps_slabinfo_reap
+
+
+/* procps_slabinfo_select():
+ *
+ * Obtain all the requested SLABS (global) information then return
+ * it in a single library provided results stack.
+ *
+ * Returns: pointer to a slabinfo_stack struct on success, NULL on error.
+ */
+PROCPS_EXPORT struct slabinfo_stack *procps_slabinfo_select (
+ struct slabinfo_info *info,
+ enum slabinfo_item *items,
+ int numitems)
+{
+ errno = EINVAL;
+ if (info == NULL || items == NULL)
+ return NULL;
+ if (0 > slabinfo_stacks_reconfig_maybe(&info->select_ext, items, numitems))
+ return NULL; // here, errno may be overridden with ENOMEM
+ errno = 0;
+
+ if (!info->select_ext.extents
+ && (!slabinfo_stacks_alloc(&info->select_ext, 1)))
+ return NULL;
+
+ if (slabinfo_read_failed(info))
+ return NULL;
+ slabinfo_assign_results(info->select_ext.extents->stacks[0], &info->slabs, &info->nul_node);
+
+ return info->select_ext.extents->stacks[0];
+} // end: procps_slabinfo_select
+
+
+/*
+ * procps_slabinfo_sort():
+ *
+ * Sort stacks anchored in the passed stack pointers array
+ * based on the designated sort enumerator and specified order.
+ *
+ * Returns those same addresses sorted.
+ *
+ * Note: all of the stacks must be homogeneous (of equal length and content).
+ */
+PROCPS_EXPORT struct slabinfo_stack **procps_slabinfo_sort (
+ struct slabinfo_info *info,
+ struct slabinfo_stack *stacks[],
+ int numstacked,
+ enum slabinfo_item sortitem,
+ enum slabinfo_sort_order order)
+{
+ struct slabinfo_result *p;
+ struct sort_parms parms;
+ int offset;
+
+ errno = EINVAL;
+ if (info == NULL || stacks == NULL)
+ return NULL;
+ // a slabinfo_item is currently unsigned, but we'll protect our future
+ if (sortitem < 0 || sortitem >= SLABINFO_logical_end)
+ return NULL;
+ if (order != SLABINFO_SORT_ASCEND && order != SLABINFO_SORT_DESCEND)
+ return NULL;
+ if (numstacked < 2)
+ return stacks;
+
+ offset = 0;
+ p = stacks[0]->head;
+ for (;;) {
+ if (p->item == sortitem)
+ break;
+ ++offset;
+ if (p->item >= SLABINFO_logical_end)
+ return NULL;
+ ++p;
+ }
+ errno = 0;
+
+ parms.offset = offset;
+ parms.order = order;
+
+ qsort_r(stacks, numstacked, sizeof(void *), (QSR_t)Item_table[p->item].sortfunc, &parms);
+ return stacks;
+} // end: procps_slabinfo_sort
+
+
+// --- special debugging function(s) ------------------------------------------
+/*
+ * The following isn't part of the normal programming interface. Rather,
+ * it exists to validate result types referenced in application programs.
+ *
+ * It's used only when:
+ * 1) the 'XTRA_PROCPS_DEBUG' has been defined, or
+ * 2) an #include of 'xtra-procps-debug.h' is used
+ */
+
+PROCPS_EXPORT struct slabinfo_result *xtra_slabinfo_get (
+ struct slabinfo_info *info,
+ enum slabinfo_item actual_enum,
+ const char *typestr,
+ const char *file,
+ int lineno)
+{
+ struct slabinfo_result *r = procps_slabinfo_get(info, actual_enum);
+
+ if (actual_enum < 0 || actual_enum >= SLABINFO_logical_end) {
+ fprintf(stderr, "%s line %d: invalid item = %d, type = %s\n"
+ , file, lineno, actual_enum, typestr);
+ }
+ if (r) {
+ char *str = Item_table[r->item].type2str;
+ if (str[0]
+ && (strcmp(typestr, str)))
+ fprintf(stderr, "%s line %d: was %s, expected %s\n", file, lineno, typestr, str);
+ }
+ return r;
+} // end: xtra_slabinfo_get_
+
+
+PROCPS_EXPORT struct slabinfo_result *xtra_slabinfo_val (
+ int relative_enum,
+ const char *typestr,
+ const struct slabinfo_stack *stack,
+ struct slabinfo_info *info,
+ const char *file,
+ int lineno)
+{
+ char *str;
+ int i;
+
+ for (i = 0; stack->head[i].item < SLABINFO_logical_end; i++)
+ ;
+ if (relative_enum < 0 || relative_enum >= i) {
+ fprintf(stderr, "%s line %d: invalid relative_enum = %d, valid range = 0-%d\n"
+ , file, lineno, relative_enum, i-1);
+ return NULL;
+ }
+ str = Item_table[stack->head[relative_enum].item].type2str;
+ if (str[0]
+ && (strcmp(typestr, str))) {
+ fprintf(stderr, "%s line %d: was %s, expected %s\n", file, lineno, typestr, str);
+ }
+ return &stack->head[relative_enum];
+ (void)info;
+} // end: xtra_slabinfo_val
diff --git a/library/stat.c b/library/stat.c
new file mode 100644
index 0000000..131cad5
--- /dev/null
+++ b/library/stat.c
@@ -0,0 +1,1443 @@
+/*
+ * stat.c - cpu/numa related definitions for libproc2
+ *
+ * Copyright © 2015-2023 Jim Warner <james.warner@comcast.net>
+ * Copyright © 2015-2023 Craig Small <csmall@dropbear.xyz>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "numa.h"
+
+#include "procps-private.h"
+#include "stat.h"
+
+
+#define STAT_FILE "/proc/stat"
+#define CORE_FILE "/proc/cpuinfo"
+
+#define CORE_BUFSIZ 1024 // buf size for line of /proc/cpuinfo
+#define BUFFER_INCR 8192 // amount i/p buffer allocations grow
+#define STACKS_INCR 64 // amount reap stack allocations grow
+#define NEWOLD_INCR 64 // amount jiffs hist allocations grow
+
+#define ECORE_BEGIN 10 // PRETEND_E_CORES begin at this cpu#
+
+/* ------------------------------------------------------------------------- +
+ this provision just does what its name sugggests - it will create several |
+ E-Core cpus for testing that STAT_TIC_ID_CORE & STAT_TIC_TYPE_CORE stuff! |*/
+// #define PRETEND_E_CORES //----------------------------------------------- |
+// ------------------------------------------------------------------------- +
+
+/* ------------------------------------------------------------------------- +
+ this provision can be used to ensure that our Item_table was synchronized |
+ with those enumerators found in the associated header file. It's intended |
+ to only be used locally (& temporarily) at some point prior to a release! | */
+// #define ITEMTABLE_DEBUG //----------------------------------------------- |
+// ------------------------------------------------------------------------- +
+
+/* ------------------------------------------------------------------------- +
+ because 'reap' would be forced to duplicate the global SYS stuff in every |
+ TIC type results stack, the following #define can be used to enforce that |
+ only STAT_noop and STAT_extra plus all the STAT_TIC items will be allowed | */
+//#define ENFORCE_LOGICAL // ensure only logical items are accepted by reap |
+// ------------------------------------------------------------------------- +
+
+/* --------------------------------------------------------------------------+
+ this next define is equivalent to the master top's CPU_ZEROTICS provision |
+ except that here in newlib we'll take an opposite approach to our default | */
+//#define CPU_IDLE_FORCED // show as 100% idle if fewer ticks than expected |
+// --------------------------------------------------------------------------+
+
+#ifdef CPU_IDLE_FORCED
+ /* this is the % used in establishing a ticks threshold below which some |
+ cpu will be treated 'idle' rather than reflect misleading tick values | */
+#define TICS_THRESHOLD ( 100 / 20 )
+#endif
+
+struct stat_jifs {
+ unsigned long long user, nice, system, idle, iowait, irq, sirq, stolen, guest, gnice;
+ unsigned long long xusr, xsys, xidl, xbsy, xtot;
+};
+
+struct stat_core {
+ int id;
+ int type; // 2 = p-core, 1 = e-core, 0 = unsure
+ int thread_1;
+ int thread_2;
+ struct stat_core *next;
+};
+
+struct stat_data {
+ unsigned long intr;
+ unsigned long ctxt;
+ unsigned long btime;
+ unsigned long procs_created;
+ unsigned long procs_blocked;
+ unsigned long procs_running;
+};
+
+struct hist_sys {
+ struct stat_data new;
+ struct stat_data old;
+};
+
+struct hist_tic {
+ int id;
+ int numa_node;
+ int count;
+ struct stat_jifs new;
+ struct stat_jifs old;
+#ifdef CPU_IDLE_FORCED
+ unsigned long edge; // only valued/valid with cpu summary
+#endif
+ struct stat_core *core;
+ int saved_id;
+};
+
+struct stacks_extent {
+ int ext_numstacks;
+ struct stacks_extent *next;
+ struct stat_stack **stacks;
+};
+
+struct item_support {
+ int num; // includes 'logical_end' delimiter
+ enum stat_item *enums; // includes 'logical_end' delimiter
+};
+
+struct ext_support {
+ struct item_support *items; // how these stacks are configured
+ struct stacks_extent *extents; // anchor for these extents
+};
+
+struct tic_support {
+ int n_alloc; // number of below structs allocated
+ int n_inuse; // number of below structs occupied
+ struct hist_tic *tics; // actual new/old jiffies
+};
+
+struct reap_support {
+ int total; // independently obtained # of cpus/nodes
+ struct ext_support fetch; // extents plus items details
+ struct tic_support hist; // cpu and node jiffies management
+ int n_alloc; // last known anchor pointers allocation
+ struct stat_stack **anchor; // reapable stacks (consolidated extents)
+ int n_alloc_save; // last known results.stacks allocation
+ struct stat_reap result; // summary + stacks returned to caller
+};
+
+struct stat_info {
+ int refcount;
+ FILE *stat_fp;
+ char *stat_buf; // grows to accommodate all /proc/stat
+ int stat_buf_size; // current size for the above stat_buf
+ int cpu_count_hwm; // if changed, triggers new cores scan
+ struct hist_sys sys_hist; // SYS type management
+ struct hist_tic cpu_hist; // TIC type management for cpu summary
+ struct reap_support cpus; // TIC type management for real cpus
+ struct reap_support nodes; // TIC type management for numa nodes
+ struct ext_support cpu_summary; // supports /proc/stat line #1 results
+ struct ext_support select; // support for 'procps_stat_select()'
+ struct stat_reaped results; // for return to caller after a reap
+ struct stat_result get_this; // for return to caller after a get
+ struct item_support reap_items; // items used for reap (shared among 3)
+ struct item_support select_items; // items unique to select
+ time_t sav_secs; // used by procps_stat_get to limit i/o
+ struct stat_core *cores; // linked list, also linked from hist_tic
+};
+
+// ___ Results 'Set' Support ||||||||||||||||||||||||||||||||||||||||||||||||||
+
+#define setNAME(e) set_stat_ ## e
+#define setDECL(e) static void setNAME(e) \
+ (struct stat_result *R, struct hist_sys *S, struct hist_tic *T)
+
+// regular assignment
+#define TIC_set(e,t,x) setDECL(e) { \
+ (void)S; R->result. t = T->new. x; }
+#define SYS_set(e,t,x) setDECL(e) { \
+ (void)T; R->result. t = S->new. x; }
+// delta assignment
+#define TICsetH(e,t,x) setDECL(e) { \
+ (void)S; R->result. t = ( T->new. x - T->old. x ); \
+ if (R->result. t < 0) R->result. t = 0; }
+#define SYSsetH(e,t,x) setDECL(e) { \
+ (void)T; R->result. t = ( S->new. x - S->old. x ); }
+
+setDECL(noop) { (void)R; (void)S; (void)T; }
+setDECL(extra) { (void)S; (void)T; R->result.ull_int = 0; }
+
+setDECL(TIC_ID) { (void)S; R->result.s_int = T->id; }
+setDECL(TIC_ID_CORE) { (void)S; R->result.s_int = (T->core) ? T->core->id : -1; }
+setDECL(TIC_NUMA_NODE) { (void)S; R->result.s_int = T->numa_node; }
+setDECL(TIC_NUM_CONTRIBUTORS) { (void)S; R->result.s_int = T->count; }
+setDECL(TIC_TYPE_CORE) { (void)S; R->result.s_int = (T->core) ? T->core->type : 0; }
+
+TIC_set(TIC_USER, ull_int, user)
+TIC_set(TIC_NICE, ull_int, nice)
+TIC_set(TIC_SYSTEM, ull_int, system)
+TIC_set(TIC_IDLE, ull_int, idle)
+TIC_set(TIC_IOWAIT, ull_int, iowait)
+TIC_set(TIC_IRQ, ull_int, irq)
+TIC_set(TIC_SOFTIRQ, ull_int, sirq)
+TIC_set(TIC_STOLEN, ull_int, stolen)
+TIC_set(TIC_GUEST, ull_int, guest)
+TIC_set(TIC_GUEST_NICE, ull_int, gnice)
+
+TICsetH(TIC_DELTA_USER, sl_int, user)
+TICsetH(TIC_DELTA_NICE, sl_int, nice)
+TICsetH(TIC_DELTA_SYSTEM, sl_int, system)
+TICsetH(TIC_DELTA_IDLE, sl_int, idle)
+TICsetH(TIC_DELTA_IOWAIT, sl_int, iowait)
+TICsetH(TIC_DELTA_IRQ, sl_int, irq)
+TICsetH(TIC_DELTA_SOFTIRQ, sl_int, sirq)
+TICsetH(TIC_DELTA_STOLEN, sl_int, stolen)
+TICsetH(TIC_DELTA_GUEST, sl_int, guest)
+TICsetH(TIC_DELTA_GUEST_NICE, sl_int, gnice)
+
+TIC_set(TIC_SUM_USER, ull_int, xusr)
+TIC_set(TIC_SUM_SYSTEM, ull_int, xsys)
+TIC_set(TIC_SUM_IDLE, ull_int, xidl)
+TIC_set(TIC_SUM_BUSY, ull_int, xbsy)
+TIC_set(TIC_SUM_TOTAL, ull_int, xtot)
+
+TICsetH(TIC_SUM_DELTA_USER, sl_int, xusr)
+TICsetH(TIC_SUM_DELTA_SYSTEM, sl_int, xsys)
+TICsetH(TIC_SUM_DELTA_IDLE, sl_int, xidl)
+TICsetH(TIC_SUM_DELTA_BUSY, sl_int, xbsy)
+TICsetH(TIC_SUM_DELTA_TOTAL, sl_int, xtot)
+
+SYS_set(SYS_CTX_SWITCHES, ul_int, ctxt)
+SYS_set(SYS_INTERRUPTS, ul_int, intr)
+SYS_set(SYS_PROC_BLOCKED, ul_int, procs_blocked)
+SYS_set(SYS_PROC_CREATED, ul_int, procs_created)
+SYS_set(SYS_PROC_RUNNING, ul_int, procs_running)
+SYS_set(SYS_TIME_OF_BOOT, ul_int, btime)
+
+SYSsetH(SYS_DELTA_CTX_SWITCHES, s_int, ctxt)
+SYSsetH(SYS_DELTA_INTERRUPTS, s_int, intr)
+SYSsetH(SYS_DELTA_PROC_BLOCKED, s_int, procs_blocked)
+SYSsetH(SYS_DELTA_PROC_CREATED, s_int, procs_created)
+SYSsetH(SYS_DELTA_PROC_RUNNING, s_int, procs_running)
+
+#undef setDECL
+#undef TIC_set
+#undef SYS_set
+#undef TICsetH
+#undef SYSsetH
+
+
+// ___ Sorting Support ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
+struct sort_parms {
+ int offset;
+ enum stat_sort_order order;
+};
+
+#define srtNAME(t) sort_stat_ ## t
+#define srtDECL(t) static int srtNAME(t) \
+ (const struct stat_stack **A, const struct stat_stack **B, struct sort_parms *P)
+
+srtDECL(s_int) {
+ const struct stat_result *a = (*A)->head + P->offset; \
+ const struct stat_result *b = (*B)->head + P->offset; \
+ return P->order * (a->result.s_int - b->result.s_int);
+}
+
+srtDECL(sl_int) {
+ const struct stat_result *a = (*A)->head + P->offset; \
+ const struct stat_result *b = (*B)->head + P->offset; \
+ return P->order * (a->result.sl_int - b->result.sl_int);
+}
+
+srtDECL(ul_int) {
+ const struct stat_result *a = (*A)->head + P->offset; \
+ const struct stat_result *b = (*B)->head + P->offset; \
+ if ( a->result.ul_int > b->result.ul_int ) return P->order > 0 ? 1 : -1; \
+ if ( a->result.ul_int < b->result.ul_int ) return P->order > 0 ? -1 : 1; \
+ return 0;
+}
+
+srtDECL(ull_int) {
+ const struct stat_result *a = (*A)->head + P->offset; \
+ const struct stat_result *b = (*B)->head + P->offset; \
+ if ( a->result.ull_int > b->result.ull_int ) return P->order > 0 ? 1 : -1; \
+ if ( a->result.ull_int < b->result.ull_int ) return P->order > 0 ? -1 : 1; \
+ return 0;
+}
+
+srtDECL(noop) { \
+ (void)A; (void)B; (void)P; \
+ return 0;
+}
+
+#undef srtDECL
+
+
+// ___ Controlling Table ||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
+typedef void (*SET_t)(struct stat_result *, struct hist_sys *, struct hist_tic *);
+#ifdef ITEMTABLE_DEBUG
+#define RS(e) (SET_t)setNAME(e), STAT_ ## e, STRINGIFY(STAT_ ## e)
+#else
+#define RS(e) (SET_t)setNAME(e)
+#endif
+
+typedef int (*QSR_t)(const void *, const void *, void *);
+#define QS(t) (QSR_t)srtNAME(t)
+
+#define TS(t) STRINGIFY(t)
+#define TS_noop ""
+
+ /*
+ * Need it be said?
+ * This table must be kept in the exact same order as
+ * those 'enum stat_item' guys ! */
+static struct {
+ SET_t setsfunc; // the actual result setting routine
+#ifdef ITEMTABLE_DEBUG
+ int enumnumb; // enumerator (must match position!)
+ char *enum2str; // enumerator name as a char* string
+#endif
+ QSR_t sortfunc; // sort cmp func for a specific type
+ char *type2str; // the result type as a string value
+} Item_table[] = {
+/* setsfunc sortfunc type2str
+ --------------------------- ------------ ----------- */
+ { RS(noop), QS(noop), TS_noop },
+ { RS(extra), QS(ull_int), TS_noop },
+
+ { RS(TIC_ID), QS(s_int), TS(s_int) },
+ { RS(TIC_ID_CORE), QS(s_int), TS(s_int) },
+ { RS(TIC_NUMA_NODE), QS(s_int), TS(s_int) },
+ { RS(TIC_NUM_CONTRIBUTORS), QS(s_int), TS(s_int) },
+ { RS(TIC_TYPE_CORE), QS(s_int), TS(s_int) },
+ { RS(TIC_USER), QS(ull_int), TS(ull_int) },
+ { RS(TIC_NICE), QS(ull_int), TS(ull_int) },
+ { RS(TIC_SYSTEM), QS(ull_int), TS(ull_int) },
+ { RS(TIC_IDLE), QS(ull_int), TS(ull_int) },
+ { RS(TIC_IOWAIT), QS(ull_int), TS(ull_int) },
+ { RS(TIC_IRQ), QS(ull_int), TS(ull_int) },
+ { RS(TIC_SOFTIRQ), QS(ull_int), TS(ull_int) },
+ { RS(TIC_STOLEN), QS(ull_int), TS(ull_int) },
+ { RS(TIC_GUEST), QS(ull_int), TS(ull_int) },
+ { RS(TIC_GUEST_NICE), QS(ull_int), TS(ull_int) },
+
+ { RS(TIC_DELTA_USER), QS(sl_int), TS(sl_int) },
+ { RS(TIC_DELTA_NICE), QS(sl_int), TS(sl_int) },
+ { RS(TIC_DELTA_SYSTEM), QS(sl_int), TS(sl_int) },
+ { RS(TIC_DELTA_IDLE), QS(sl_int), TS(sl_int) },
+ { RS(TIC_DELTA_IOWAIT), QS(sl_int), TS(sl_int) },
+ { RS(TIC_DELTA_IRQ), QS(sl_int), TS(sl_int) },
+ { RS(TIC_DELTA_SOFTIRQ), QS(sl_int), TS(sl_int) },
+ { RS(TIC_DELTA_STOLEN), QS(sl_int), TS(sl_int) },
+ { RS(TIC_DELTA_GUEST), QS(sl_int), TS(sl_int) },
+ { RS(TIC_DELTA_GUEST_NICE), QS(sl_int), TS(sl_int) },
+
+ { RS(TIC_SUM_USER), QS(ull_int), TS(ull_int) },
+ { RS(TIC_SUM_SYSTEM), QS(ull_int), TS(ull_int) },
+ { RS(TIC_SUM_IDLE), QS(ull_int), TS(ull_int) },
+ { RS(TIC_SUM_BUSY), QS(ull_int), TS(ull_int) },
+ { RS(TIC_SUM_TOTAL), QS(ull_int), TS(ull_int) },
+
+ { RS(TIC_SUM_DELTA_USER), QS(sl_int), TS(sl_int) },
+ { RS(TIC_SUM_DELTA_SYSTEM), QS(sl_int), TS(sl_int) },
+ { RS(TIC_SUM_DELTA_IDLE), QS(sl_int), TS(sl_int) },
+ { RS(TIC_SUM_DELTA_BUSY), QS(sl_int), TS(sl_int) },
+ { RS(TIC_SUM_DELTA_TOTAL), QS(sl_int), TS(sl_int) },
+
+ { RS(SYS_CTX_SWITCHES), QS(ul_int), TS(ul_int) },
+ { RS(SYS_INTERRUPTS), QS(ul_int), TS(ul_int) },
+ { RS(SYS_PROC_BLOCKED), QS(ul_int), TS(ul_int) },
+ { RS(SYS_PROC_CREATED), QS(ul_int), TS(ul_int) },
+ { RS(SYS_PROC_RUNNING), QS(ul_int), TS(ul_int) },
+ { RS(SYS_TIME_OF_BOOT), QS(ul_int), TS(ul_int) },
+
+ { RS(SYS_DELTA_CTX_SWITCHES), QS(s_int), TS(s_int) },
+ { RS(SYS_DELTA_INTERRUPTS), QS(s_int), TS(s_int) },
+ { RS(SYS_DELTA_PROC_BLOCKED), QS(s_int), TS(s_int) },
+ { RS(SYS_DELTA_PROC_CREATED), QS(s_int), TS(s_int) },
+ { RS(SYS_DELTA_PROC_RUNNING), QS(s_int), TS(s_int) },
+};
+
+ /* please note,
+ * 1st enum MUST be kept in sync with highest TIC type
+ * 2nd enum MUST be 1 greater than the highest value of any enum */
+#ifdef ENFORCE_LOGICAL
+enum stat_item STAT_TIC_highest = STAT_TIC_DELTA_GUEST_NICE;
+#endif
+enum stat_item STAT_logical_end = MAXTABLE(Item_table);
+
+#undef setNAME
+#undef srtNAME
+#undef RS
+#undef QS
+
+
+// ___ Private Functions ||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
+static inline void stat_assign_results (
+ struct stat_stack *stack,
+ struct hist_sys *sys_hist,
+ struct hist_tic *tic_hist)
+{
+ struct stat_result *this = stack->head;
+
+ for (;;) {
+ enum stat_item item = this->item;
+ if (item >= STAT_logical_end)
+ break;
+ Item_table[item].setsfunc(this, sys_hist, tic_hist);
+ ++this;
+ }
+ return;
+} // end: stat_assign_results
+
+
+#define E_CORE 1
+#define P_CORE 2
+#define VACANT -1
+
+static int stat_core_add (
+ struct stat_info *info,
+ int a_core,
+ int a_cpu)
+{
+ struct stat_core *last = NULL, *core = info->cores;
+
+ while (core) {
+ if (core->id == a_core) {
+ if (a_cpu == core->thread_1
+ || (a_cpu == core->thread_2))
+ return 1;
+ core->thread_2 = a_cpu;
+ core->type = P_CORE;
+ return 1;
+ }
+ last = core;
+ core = core->next;
+ }
+ if (!(core = calloc(1, sizeof(struct stat_core))))
+ return 0;
+ if (last) last->next = core;
+ else info->cores = core;
+ core->id = a_core;
+ core->thread_1 = a_cpu;
+ core->thread_2 = VACANT;
+ return 1;
+} // end: stat_core_add
+
+
+static void stat_cores_check (
+ struct stat_info *info)
+{
+ struct stat_core *core;
+#ifndef PRETEND_E_CORES
+ int p_core = 0;
+
+ core = info->cores;
+ while (core) {
+ if (core->type == P_CORE) {
+ p_core = 1;
+ break;
+ }
+ core = core->next;
+ }
+ if (p_core) {
+ core = info->cores;
+ do {
+ if (core->thread_2 == VACANT)
+ core->type = E_CORE;
+ } while ((core = core->next));
+ }
+#else
+ core = info->cores;
+ while (core) {
+ core->type = P_CORE;
+ if (core->thread_1 > ECORE_BEGIN
+ || (core->thread_2 > ECORE_BEGIN))
+ core->type = E_CORE;
+ core = core->next;
+ }
+#endif
+} // end: stat_cores_check
+
+#undef E_CORE
+#undef P_CORE
+#undef VACANT
+
+
+static void stat_cores_link (
+ struct stat_info *info,
+ struct hist_tic *this)
+{
+ struct stat_core *core = info->cores;
+
+ while (core) {
+ if (this->id == core->thread_1
+ || (this->id == core->thread_2)) {
+ this->core = core;
+ break;
+ }
+ core = core->next;
+ }
+} // end: stat_cores_link
+
+
+static int stat_cores_verify (
+ struct stat_info *info)
+{
+ char buf[CORE_BUFSIZ];
+ int a_cpu, a_core;
+ FILE *fp;
+
+ // be tolerant of a missing CORE_FILE ...
+ if (!(fp = fopen(CORE_FILE, "r")))
+ return 1;
+ for (;;) {
+ if (NULL == fgets(buf, sizeof(buf), fp))
+ break;
+ if (buf[0] != 'p') continue;
+ if (!strstr(buf, "processor"))
+ continue;
+ sscanf(buf, "processor : %d", &a_cpu);
+ for (;;) {
+ // be tolerant of missing empty line on last processor entry ...
+ if (NULL == fgets(buf, sizeof(buf), fp))
+ goto wrap_up;
+ // be tolerant of a missing 'core id' on any processor entry ...
+ if (buf[0] == '\n') {
+ a_core = a_cpu;
+ break;
+ }
+ if (buf[0] != 'c') continue;
+ if (!strstr(buf, "core id"))
+ continue;
+ sscanf(buf, "core id : %d", &a_core);
+ break;
+ }
+ if (!stat_core_add(info, a_core, a_cpu)) {
+ fclose(fp);
+ return 0;
+ }
+ }
+wrap_up:
+ fclose(fp);
+ stat_cores_check(info);
+ return 1;
+} // end: stat_cores_verify
+
+
+static inline void stat_derive_unique (
+ struct hist_tic *this)
+{
+ /* note: we calculate these derived values in a manner consistent with
+ the calculations for cgroup accounting, as nearly as possible
+ ( see linux sources: ./kernel/cgroup/rstat.c, root_cgroup_cputime ) */
+ this->new.xusr
+ = this->new.user
+ + this->new.nice;
+ this->new.xsys
+ = this->new.system
+ + this->new.irq
+ + this->new.sirq;
+ this->new.xidl
+ = this->new.idle
+ + this->new.iowait;
+ this->new.xtot
+ = this->new.xusr + this->new.xsys + this->new.xidl
+ + this->new.stolen
+ + this->new.guest
+ + this->new.gnice;
+ this->new.xbsy
+ = this->new.xtot - this->new.xidl;
+
+ // don't distort deltas when cpus are taken offline or brought online
+ if (this->new.xusr < this->old.xusr
+ || (this->new.xsys < this->old.xsys)
+ || (this->new.xidl < this->old.xidl)
+ || (this->new.xbsy < this->old.xbsy)
+ || (this->new.xtot < this->old.xtot))
+ memcpy(&this->old, &this->new, sizeof(struct stat_jifs));
+} // end: stat_derive_unique
+
+
+static void stat_extents_free_all (
+ struct ext_support *this)
+{
+ while (this->extents) {
+ struct stacks_extent *p = this->extents;
+ this->extents = this->extents->next;
+ free(p);
+ };
+} // end: stat_extents_free_all
+
+
+static inline struct stat_result *stat_itemize_stack (
+ struct stat_result *p,
+ int depth,
+ enum stat_item *items)
+{
+ struct stat_result *p_sav = p;
+ int i;
+
+ for (i = 0; i < depth; i++) {
+ p->item = items[i];
+ ++p;
+ }
+ return p_sav;
+} // end: stat_itemize_stack
+
+
+static inline int stat_items_check_failed (
+ int numitems,
+ enum stat_item *items)
+{
+ int i;
+
+ /* if an enum is passed instead of an address of one or more enums, ol' gcc
+ * will silently convert it to an address (possibly NULL). only clang will
+ * offer any sort of warning like the following:
+ *
+ * warning: incompatible integer to pointer conversion passing 'int' to parameter of type 'enum stat_item *'
+ * my_stack = procps_stat_select(info, STAT_noop, num);
+ * ^~~~~~~~~~~~~~~~
+ */
+ if (numitems < 1
+ || (void *)items < (void *)(unsigned long)(2 * STAT_logical_end))
+ return 1;
+
+ for (i = 0; i < numitems; i++) {
+ // a stat_item is currently unsigned, but we'll protect our future
+ if (items[i] < 0)
+ return 1;
+ if (items[i] >= STAT_logical_end) {
+ return 1;
+ }
+ }
+ return 0;
+} // end: stat_items_check_failed
+
+
+static int stat_make_numa_hist (
+ struct stat_info *info)
+{
+ struct hist_tic *cpu_ptr, *nod_ptr;
+ int i, node;
+
+ /* are numa nodes dynamic like online cpus can be?
+ ( and be careful, this libnuma call returns the highest node id in use, )
+ ( NOT an actual number of nodes - some of those 'slots' might be unused ) */
+ if (!(info->nodes.total = numa_max_node() + 1))
+ return 0;
+
+ if (info->nodes.hist.n_alloc == 0
+ || (info->nodes.total >= info->nodes.hist.n_alloc)) {
+ info->nodes.hist.n_alloc = info->nodes.total + NEWOLD_INCR;
+ info->nodes.hist.tics = realloc(info->nodes.hist.tics, info->nodes.hist.n_alloc * sizeof(struct hist_tic));
+ if (info->nodes.hist.tics == NULL)
+ return -ENOMEM;
+ }
+
+ // forget all of the prior node statistics & anticipate unassigned slots
+ memset(info->nodes.hist.tics, 0, info->nodes.hist.n_alloc * sizeof(struct hist_tic));
+ nod_ptr = info->nodes.hist.tics;
+ for (i = 0; i < info->nodes.total; i++) {
+ nod_ptr->numa_node = STAT_NODE_INVALID;
+ nod_ptr->id = i;
+ ++nod_ptr;
+ }
+
+ // spin thru each cpu and value the jiffs for it's numa node
+ for (i = 0; i < info->cpus.hist.n_inuse; i++) {
+ cpu_ptr = info->cpus.hist.tics + i;
+ if (-1 < (node = numa_node_of_cpu(cpu_ptr->id))) {
+ nod_ptr = info->nodes.hist.tics + node;
+ nod_ptr->new.user += cpu_ptr->new.user; nod_ptr->old.user += cpu_ptr->old.user;
+ nod_ptr->new.nice += cpu_ptr->new.nice; nod_ptr->old.nice += cpu_ptr->old.nice;
+ nod_ptr->new.system += cpu_ptr->new.system; nod_ptr->old.system += cpu_ptr->old.system;
+ nod_ptr->new.idle += cpu_ptr->new.idle; nod_ptr->old.idle += cpu_ptr->old.idle;
+ nod_ptr->new.iowait += cpu_ptr->new.iowait; nod_ptr->old.iowait += cpu_ptr->old.iowait;
+ nod_ptr->new.irq += cpu_ptr->new.irq; nod_ptr->old.irq += cpu_ptr->old.irq;
+ nod_ptr->new.sirq += cpu_ptr->new.sirq; nod_ptr->old.sirq += cpu_ptr->old.sirq;
+ nod_ptr->new.stolen += cpu_ptr->new.stolen; nod_ptr->old.stolen += cpu_ptr->old.stolen;
+ nod_ptr->new.guest += cpu_ptr->new.guest; nod_ptr->old.guest += cpu_ptr->old.guest;
+ nod_ptr->new.gnice += cpu_ptr->new.gnice; nod_ptr->old.gnice += cpu_ptr->old.gnice;
+
+ nod_ptr->new.xusr += cpu_ptr->new.xusr; nod_ptr->old.xusr += cpu_ptr->old.xusr;
+ nod_ptr->new.xsys += cpu_ptr->new.xsys; nod_ptr->old.xsys += cpu_ptr->old.xsys;
+ nod_ptr->new.xidl += cpu_ptr->new.xidl; nod_ptr->old.xidl += cpu_ptr->old.xidl;
+ nod_ptr->new.xbsy += cpu_ptr->new.xbsy; nod_ptr->old.xbsy += cpu_ptr->old.xbsy;
+ nod_ptr->new.xtot += cpu_ptr->new.xtot; nod_ptr->old.xtot += cpu_ptr->old.xtot;
+
+ cpu_ptr->numa_node = nod_ptr->numa_node = node;
+ nod_ptr->count++; ;
+ }
+ }
+ info->nodes.hist.n_inuse = info->nodes.total;
+ return info->nodes.hist.n_inuse;
+} // end: stat_make_numa_hist
+
+
+static int stat_read_failed (
+ struct stat_info *info)
+{
+ struct hist_tic *sum_ptr, *cpu_ptr;
+ char *bp, *b;
+ int i, rc, num, tot_read;
+ unsigned long long llnum;
+
+ if (!info->cpus.hist.n_alloc) {
+ info->cpus.hist.tics = calloc(NEWOLD_INCR, sizeof(struct hist_tic));
+ if (!(info->cpus.hist.tics))
+ return 1;
+ info->cpus.hist.n_alloc = NEWOLD_INCR;
+ info->cpus.hist.n_inuse = 0;
+ }
+
+ if (!info->stat_fp
+ && (!(info->stat_fp = fopen(STAT_FILE, "r"))))
+ return 1;
+ fflush(info->stat_fp);
+ rewind(info->stat_fp);
+
+ #define maxSIZ info->stat_buf_size
+ #define curSIZ ( maxSIZ - tot_read )
+ #define curPOS ( info->stat_buf + tot_read )
+ /* we slurp in the entire directory thus avoiding repeated calls to fread, |
+ especially for a massively parallel environment. additionally, each cpu |
+ line is then frozen in time rather than changing until we get around to |
+ accessing it. this helps to minimize (not eliminate) some distortions. | */
+ tot_read = 0;
+ while ((0 < (num = fread(curPOS, 1, curSIZ, info->stat_fp)))) {
+ tot_read += num;
+ if (tot_read < maxSIZ)
+ break;
+ maxSIZ += BUFFER_INCR;
+ if (!(info->stat_buf = realloc(info->stat_buf, maxSIZ)))
+ return 1;
+ };
+ #undef maxSIZ
+ #undef curSIZ
+ #undef curPOS
+
+ if (!feof(info->stat_fp)) {
+ errno = EIO;
+ return 1;
+ }
+ info->stat_buf[tot_read] = '\0';
+ bp = info->stat_buf;
+
+ sum_ptr = &info->cpu_hist;
+ // remember summary from last time around
+ memcpy(&sum_ptr->old, &sum_ptr->new, sizeof(struct stat_jifs));
+
+ sum_ptr->id = STAT_SUMMARY_ID; // mark as summary
+ sum_ptr->numa_node = STAT_NODE_INVALID; // mark as invalid
+
+ // now value the cpu summary tics from line #1
+#ifdef __CYGWIN__
+ if (4 > sscanf(bp, "cpu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu"
+#else
+ if (8 > sscanf(bp, "cpu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu"
+#endif
+ , &sum_ptr->new.user, &sum_ptr->new.nice, &sum_ptr->new.system
+ , &sum_ptr->new.idle, &sum_ptr->new.iowait, &sum_ptr->new.irq
+ , &sum_ptr->new.sirq, &sum_ptr->new.stolen
+ , &sum_ptr->new.guest, &sum_ptr->new.gnice)) {
+ errno = ERANGE;
+ return 1;
+ }
+ stat_derive_unique(sum_ptr);
+#ifdef CPU_IDLE_FORCED
+ /* if any cpu accumulated substantially fewer tics than what is expected |
+ we'll force it to be treated as 'idle' so as not to return misleading |
+ statistics (and that sum_ptr->count also serves as first time switch) | */
+ if (sum_ptr->count) sum_ptr->edge =
+ ((sum_ptr->new.xtot - sum_ptr->old.xtot) / sum_ptr->count) / TICS_THRESHOLD;
+#endif
+
+ i = 0;
+reap_em_again:
+ cpu_ptr = info->cpus.hist.tics + i; // adapt to relocated if reap_em_again
+
+ do {
+ static int once_sw;
+
+ bp = 1 + strchr(bp, '\n');
+ // remember this cpu from last time around
+ memcpy(&cpu_ptr->old, &cpu_ptr->new, sizeof(struct stat_jifs));
+ // next can be overridden under 'stat_make_numa_hist'
+ cpu_ptr->numa_node = STAT_NODE_INVALID;
+ cpu_ptr->count = 1;
+
+#ifdef __CYGWIN__
+ if (4 > (rc = sscanf(bp, "cpu%d %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu"
+#else
+ if (8 > (rc = sscanf(bp, "cpu%d %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu"
+#endif
+ , &cpu_ptr->id
+ , &cpu_ptr->new.user, &cpu_ptr->new.nice, &cpu_ptr->new.system
+ , &cpu_ptr->new.idle, &cpu_ptr->new.iowait, &cpu_ptr->new.irq
+ , &cpu_ptr->new.sirq, &cpu_ptr->new.stolen
+ , &cpu_ptr->new.guest, &cpu_ptr->new.gnice))) {
+ break; // we must tolerate cpus taken offline
+ }
+ stat_derive_unique(cpu_ptr);
+
+ // force a one time core link for cpu0 (if possible) ...
+ if (!once_sw)
+ once_sw = cpu_ptr->saved_id = -1;
+
+ /* this happens if cpus are taken offline/brought back online
+ so we better force the proper current core association ... */
+ if (cpu_ptr->saved_id != cpu_ptr->id) {
+ cpu_ptr->saved_id = cpu_ptr->id;
+ cpu_ptr->core = NULL;
+ stat_cores_link(info, cpu_ptr);
+ }
+
+#ifdef CPU_IDLE_FORCED
+ // first time through (that priming read) sum_ptr->edge will be zero |
+ if (cpu_ptr->new.xtot < sum_ptr->edge) {
+ cpu_ptr->old.xtot = cpu_ptr->old.xbsy = cpu_ptr->old.xidl = cpu_ptr->old.xusr = cpu_ptr->old.xsys
+ = cpu_ptr->new.xbsy = cpu_ptr->new.xusr = cpu_ptr->new.xsys = 0;
+ cpu_ptr->new.xtot = cpu_ptr->new.xidl = 1;
+ }
+#endif
+ ++cpu_ptr;
+ ++i;
+ } while (i < info->cpus.hist.n_alloc);
+
+ if (i == info->cpus.hist.n_alloc && rc >= 8) {
+ info->cpus.hist.n_alloc += NEWOLD_INCR;
+ info->cpus.hist.tics = realloc(info->cpus.hist.tics, info->cpus.hist.n_alloc * sizeof(struct hist_tic));
+ if (!(info->cpus.hist.tics))
+ return 1;
+ goto reap_em_again;
+ }
+
+ info->cpus.total = info->cpus.hist.n_inuse = sum_ptr->count = i;
+ /* whoa, if a new cpu was brought online, we better
+ ensure that no new cores have now become visible */
+ if (info->cpu_count_hwm < info->cpus.total) {
+ /* next means it's not the first time, so we'll re-verify.
+ otherwise, procps_stat_new() already setup any cores so
+ that they could be linked above during tics processing. */
+ if (info->cpu_count_hwm) {
+ if (!stat_cores_verify(info))
+ return 1;
+ }
+ info->cpu_count_hwm = info->cpus.total;
+ }
+
+ // remember sys_hist stuff from last time around
+ memcpy(&info->sys_hist.old, &info->sys_hist.new, sizeof(struct stat_data));
+
+ llnum = 0;
+ if ((b = strstr(bp, "intr ")))
+ sscanf(b, "intr %llu", &llnum);
+ info->sys_hist.new.intr = llnum;
+
+ llnum = 0;
+ if ((b = strstr(bp, "ctxt ")))
+ sscanf(b, "ctxt %llu", &llnum);
+ info->sys_hist.new.ctxt = llnum;
+
+ llnum = 0;
+ if ((b = strstr(bp, "btime ")))
+ sscanf(b, "btime %llu", &llnum);
+ info->sys_hist.new.btime = llnum;
+
+ llnum = 0;
+ if ((b = strstr(bp, "processes ")))
+ sscanf(b, "processes %llu", &llnum);
+ info->sys_hist.new.procs_created = llnum;
+
+ llnum = 0;
+ if ((b = strstr(bp, "procs_blocked ")))
+ sscanf(b, "procs_blocked %llu", &llnum);
+ info->sys_hist.new.procs_blocked = llnum;
+
+ llnum = 0;
+ if ((b = strstr(bp, "procs_running ")))
+ sscanf(b, "procs_running %llu", &llnum);
+ if (llnum)
+ llnum--; //exclude itself
+ info->sys_hist.new.procs_running = llnum;
+
+ return 0;
+} // end: stat_read_failed
+
+
+/*
+ * stat_stacks_alloc():
+ *
+ * Allocate and initialize one or more stacks each of which is anchored in an
+ * associated context structure.
+ *
+ * All such stacks will have their result structures properly primed with
+ * 'items', while the result itself will be zeroed.
+ *
+ * Returns a stack_extent struct anchoring the 'heads' of each new stack.
+ */
+static struct stacks_extent *stat_stacks_alloc (
+ struct ext_support *this,
+ int maxstacks)
+{
+ struct stacks_extent *p_blob;
+ struct stat_stack **p_vect;
+ struct stat_stack *p_head;
+ size_t vect_size, head_size, list_size, blob_size;
+ void *v_head, *v_list;
+ int i;
+
+ vect_size = sizeof(void *) * maxstacks; // size of the addr vectors |
+ vect_size += sizeof(void *); // plus NULL addr delimiter |
+ head_size = sizeof(struct stat_stack); // size of that head struct |
+ list_size = sizeof(struct stat_result) * this->items->num; // any single results stack |
+ blob_size = sizeof(struct stacks_extent); // the extent anchor itself |
+ blob_size += vect_size; // plus room for addr vects |
+ blob_size += head_size * maxstacks; // plus room for head thing |
+ blob_size += list_size * maxstacks; // plus room for our stacks |
+
+ /* note: all of our memory is allocated in one single blob, facilitating a later free(). |
+ as a minimum, it is important that those result structures themselves always be |
+ contiguous within each stack since they are accessed through relative position. | */
+ if (NULL == (p_blob = calloc(1, blob_size)))
+ return NULL;
+
+ p_blob->next = this->extents; // push this extent onto... |
+ this->extents = p_blob; // ...some existing extents |
+ p_vect = (void *)p_blob + sizeof(struct stacks_extent); // prime our vector pointer |
+ p_blob->stacks = p_vect; // set actual vectors start |
+ v_head = (void *)p_vect + vect_size; // prime head pointer start |
+ v_list = v_head + (head_size * maxstacks); // prime our stacks pointer |
+
+ for (i = 0; i < maxstacks; i++) {
+ p_head = (struct stat_stack *)v_head;
+ p_head->head = stat_itemize_stack((struct stat_result *)v_list, this->items->num, this->items->enums);
+ p_blob->stacks[i] = p_head;
+ v_list += list_size;
+ v_head += head_size;
+ }
+ p_blob->ext_numstacks = maxstacks;
+ return p_blob;
+} // end: stat_stacks_alloc
+
+
+static int stat_stacks_fetch (
+ struct stat_info *info,
+ struct reap_support *this)
+{
+ #define n_alloc this->n_alloc
+ #define n_inuse this->hist.n_inuse
+ #define n_saved this->n_alloc_save
+ struct stacks_extent *ext;
+ int i;
+
+ // initialize stuff -----------------------------------
+ if (!this->anchor) {
+ if (!(this->anchor = calloc(sizeof(void *), STACKS_INCR)))
+ return -1;
+ n_alloc = STACKS_INCR;
+ }
+ if (!this->fetch.extents) {
+ if (!(ext = stat_stacks_alloc(&this->fetch, n_alloc)))
+ return -1; // here, errno was set to ENOMEM
+ memcpy(this->anchor, ext->stacks, sizeof(void *) * n_alloc);
+ }
+
+ // iterate stuff --------------------------------------
+ for (i = 0; i < n_inuse; i++) {
+ if (!(i < n_alloc)) {
+ n_alloc += STACKS_INCR;
+ if ((!(this->anchor = realloc(this->anchor, sizeof(void *) * n_alloc)))
+ || (!(ext = stat_stacks_alloc(&this->fetch, STACKS_INCR))))
+ return -1; // here, errno was set to ENOMEM
+ memcpy(this->anchor + i, ext->stacks, sizeof(void *) * STACKS_INCR);
+ }
+ stat_assign_results(this->anchor[i], &info->sys_hist, &this->hist.tics[i]);
+ }
+
+ // finalize stuff -------------------------------------
+ /* note: we go to this trouble of maintaining a duplicate of the consolidated |
+ extent stacks addresses represented as our 'anchor' since these ptrs |
+ are exposed to a user (um, not that we don't trust 'em or anything). |
+ plus, we can NULL delimit these ptrs which we couldn't do otherwise. | */
+ if (n_saved < i + 1) {
+ n_saved = i + 1;
+ if (!(this->result.stacks = realloc(this->result.stacks, sizeof(void *) * n_saved)))
+ return -1;
+ }
+ memcpy(this->result.stacks, this->anchor, sizeof(void *) * i);
+ this->result.stacks[i] = NULL;
+ this->result.total = i;
+
+ // callers beware, this might be zero (maybe no libnuma.so) ...
+ return this->result.total;
+ #undef n_alloc
+ #undef n_inuse
+ #undef n_saved
+} // end: stat_stacks_fetch
+
+
+static int stat_stacks_reconfig_maybe (
+ struct ext_support *this,
+ enum stat_item *items,
+ int numitems)
+{
+ if (stat_items_check_failed(numitems, items))
+ return -1;
+ /* is this the first time or have things changed since we were last called?
+ if so, gotta' redo all of our stacks stuff ... */
+ if (this->items->num != numitems + 1
+ || memcmp(this->items->enums, items, sizeof(enum stat_item) * numitems)) {
+ // allow for our STAT_logical_end
+ if (!(this->items->enums = realloc(this->items->enums, sizeof(enum stat_item) * (numitems + 1))))
+ return -1;
+ memcpy(this->items->enums, items, sizeof(enum stat_item) * numitems);
+ this->items->enums[numitems] = STAT_logical_end;
+ this->items->num = numitems + 1;
+ stat_extents_free_all(this);
+ return 1;
+ }
+ return 0;
+} // end: stat_stacks_reconfig_maybe
+
+
+static struct stat_stack *stat_update_single_stack (
+ struct stat_info *info,
+ struct ext_support *this)
+{
+ if (!this->extents
+ && !(stat_stacks_alloc(this, 1)))
+ return NULL;
+
+ stat_assign_results(this->extents->stacks[0], &info->sys_hist, &info->cpu_hist);
+
+ return this->extents->stacks[0];
+} // end: stat_update_single_stack
+
+
+
+// ___ Public Functions |||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
+// --- standard required functions --------------------------------------------
+
+/*
+ * procps_stat_new:
+ *
+ * Create a new container to hold the stat information
+ *
+ * The initial refcount is 1, and needs to be decremented
+ * to release the resources of the structure.
+ *
+ * Returns: < 0 on failure, 0 on success along with
+ * a pointer to a new context struct
+ */
+PROCPS_EXPORT int procps_stat_new (
+ struct stat_info **info)
+{
+ struct stat_info *p;
+
+#ifdef ITEMTABLE_DEBUG
+ int i, failed = 0;
+ for (i = 0; i < MAXTABLE(Item_table); i++) {
+ if (i != Item_table[i].enumnumb) {
+ fprintf(stderr, "%s: enum/table error: Item_table[%d] was %s, but its value is %d\n"
+ , __FILE__, i, Item_table[i].enum2str, Item_table[i].enumnumb);
+ failed = 1;
+ }
+ }
+ if (failed) _Exit(EXIT_FAILURE);
+#endif
+
+ if (info == NULL || *info != NULL)
+ return -EINVAL;
+ if (!(p = calloc(1, sizeof(struct stat_info))))
+ return -ENOMEM;
+ if (!(p->stat_buf = calloc(1, BUFFER_INCR))) {
+ free(p);
+ return -ENOMEM;
+ }
+ p->stat_buf_size = BUFFER_INCR;
+ p->refcount = 1;
+
+ p->results.cpus = &p->cpus.result;
+ p->results.numa = &p->nodes.result;
+
+ // these 3 are for reap, sharing a single set of items
+ p->cpu_summary.items = p->cpus.fetch.items = p->nodes.fetch.items = &p->reap_items;
+
+ // the select guy has its own set of items
+ p->select.items = &p->select_items;
+
+ numa_init();
+
+ // identify the current P-cores and E-cores, if any
+ if (!stat_cores_verify(p)) {
+ procps_stat_unref(&p);
+ return -errno;
+ }
+
+ /* do a priming read here for the following potential benefits: |
+ 1) ensure there will be no problems with subsequent access |
+ 2) make delta results potentially useful, even if 1st time |
+ 3) elimnate need for history distortions 1st time 'switch' | */
+ if (stat_read_failed(p)) {
+ procps_stat_unref(&p);
+ return -errno;
+ }
+
+ *info = p;
+ return 0;
+} // end :procps_stat_new
+
+
+PROCPS_EXPORT int procps_stat_ref (
+ struct stat_info *info)
+{
+ if (info == NULL)
+ return -EINVAL;
+
+ info->refcount++;
+ return info->refcount;
+} // end: procps_stat_ref
+
+
+PROCPS_EXPORT int procps_stat_unref (
+ struct stat_info **info)
+{
+ if (info == NULL || *info == NULL)
+ return -EINVAL;
+
+ (*info)->refcount--;
+
+ if ((*info)->refcount < 1) {
+ int errno_sav = errno;
+
+ if ((*info)->stat_fp)
+ fclose((*info)->stat_fp);
+ if ((*info)->stat_buf)
+ free((*info)->stat_buf);
+
+ if ((*info)->cpus.anchor)
+ free((*info)->cpus.anchor);
+ if ((*info)->cpus.result.stacks)
+ free((*info)->cpus.result.stacks);
+ if ((*info)->cpus.hist.tics)
+ free((*info)->cpus.hist.tics);
+ if ((*info)->cpus.fetch.extents)
+ stat_extents_free_all(&(*info)->cpus.fetch);
+
+ if ((*info)->nodes.anchor)
+ free((*info)->nodes.anchor);
+ if ((*info)->nodes.result.stacks)
+ free((*info)->nodes.result.stacks);
+ if ((*info)->nodes.hist.tics)
+ free((*info)->nodes.hist.tics);
+ if ((*info)->nodes.fetch.extents)
+ stat_extents_free_all(&(*info)->nodes.fetch);
+
+ if ((*info)->cpu_summary.extents)
+ stat_extents_free_all(&(*info)->cpu_summary);
+
+ if ((*info)->select.extents)
+ stat_extents_free_all(&(*info)->select);
+
+ if ((*info)->reap_items.enums)
+ free((*info)->reap_items.enums);
+ if ((*info)->select_items.enums)
+ free((*info)->select_items.enums);
+
+ if ((*info)->cores) {
+ struct stat_core *next, *this = (*info)->cores;
+ while (this) {
+ next = this->next;
+ free(this);
+ this = next;
+ };
+ }
+
+ numa_uninit();
+
+ free(*info);
+ *info = NULL;
+
+ errno = errno_sav;
+ return 0;
+ }
+ return (*info)->refcount;
+} // end: procps_stat_unref
+
+
+// --- variable interface functions -------------------------------------------
+
+PROCPS_EXPORT struct stat_result *procps_stat_get (
+ struct stat_info *info,
+ enum stat_item item)
+{
+ time_t cur_secs;
+
+ errno = EINVAL;
+ if (info == NULL)
+ return NULL;
+ if (item < 0 || item >= STAT_logical_end)
+ return NULL;
+ errno = 0;
+
+ /* we will NOT read the source file with every call - rather, we'll offer
+ a granularity of 1 second between reads ... */
+ cur_secs = time(NULL);
+ if (1 <= cur_secs - info->sav_secs) {
+ if (stat_read_failed(info))
+ return NULL;
+ info->sav_secs = cur_secs;
+ }
+
+ info->get_this.item = item;
+ // with 'get', we must NOT honor the usual 'noop' guarantee
+ info->get_this.result.ull_int = 0;
+ Item_table[item].setsfunc(&info->get_this, &info->sys_hist, &info->cpu_hist);
+
+ return &info->get_this;
+} // end: procps_stat_get
+
+
+/* procps_stat_reap():
+ *
+ * Harvest all the requested NUMA NODE and/or CPU information providing the
+ * result stacks along with totals and the cpu summary.
+ *
+ * Returns: pointer to a stat_reaped struct on success, NULL on error.
+ */
+PROCPS_EXPORT struct stat_reaped *procps_stat_reap (
+ struct stat_info *info,
+ enum stat_reap_type what,
+ enum stat_item *items,
+ int numitems)
+{
+ int rc;
+
+ errno = EINVAL;
+ if (info == NULL || items == NULL)
+ return NULL;
+ if (what != STAT_REAP_CPUS_ONLY && what != STAT_REAP_NUMA_NODES_TOO)
+ return NULL;
+
+#ifdef ENFORCE_LOGICAL
+{ int i;
+ // those STAT_SYS_type enum's make sense only to 'select' ...
+ for (i = 0; i < numitems; i++) {
+ if (items[i] > STAT_TIC_highest)
+ return NULL;
+ }
+}
+#endif
+ if (0 > (rc = stat_stacks_reconfig_maybe(&info->cpu_summary, items, numitems)))
+ return NULL; // here, errno may be overridden with ENOMEM
+ if (rc) {
+ stat_extents_free_all(&info->cpus.fetch);
+ stat_extents_free_all(&info->nodes.fetch);
+ }
+ errno = 0;
+
+ if (stat_read_failed(info))
+ return NULL;
+ info->results.summary = stat_update_single_stack(info, &info->cpu_summary);
+
+ /* unlike the other 'reap' functions, <stat> provides for two separate |
+ stacks pointer arrays exposed to callers. Thus, to keep our promise |
+ of NULL delimit we must ensure a minimal array for the optional one | */
+ if (!info->nodes.result.stacks
+ && (!(info->nodes.result.stacks = malloc(sizeof(void *)))))
+ return NULL;
+ info->nodes.result.total = 0;
+ info->nodes.result.stacks[0] = NULL;
+
+ switch (what) {
+ case STAT_REAP_CPUS_ONLY:
+ if (0 > stat_stacks_fetch(info, &info->cpus))
+ return NULL;
+ break;
+ case STAT_REAP_NUMA_NODES_TOO:
+ /* note: if we're doing numa at all, we must do this numa history |
+ before we build (fetch) cpu stacks since that stat_read_failed |
+ guy always marks (temporarily) all the cpu node ids as invalid | */
+ if (0 > stat_make_numa_hist(info))
+ return NULL;
+ if (0 > stat_stacks_fetch(info, &info->nodes))
+ return NULL;
+ if (0 > stat_stacks_fetch(info, &info->cpus))
+ return NULL;
+ break;
+ default:
+ return NULL;
+ };
+
+ return &info->results;
+} // end: procps_stat_reap
+
+
+/* procps_stat_select():
+ *
+ * Harvest all the requested TIC and/or SYS information then return
+ * it in a results stack.
+ *
+ * Returns: pointer to a stat_stack struct on success, NULL on error.
+ */
+PROCPS_EXPORT struct stat_stack *procps_stat_select (
+ struct stat_info *info,
+ enum stat_item *items,
+ int numitems)
+{
+ errno = EINVAL;
+ if (info == NULL || items == NULL)
+ return NULL;
+ if (0 > stat_stacks_reconfig_maybe(&info->select, items, numitems))
+ return NULL; // here, errno may be overridden with ENOMEM
+ errno = 0;
+
+ if (stat_read_failed(info))
+ return NULL;
+
+ return stat_update_single_stack(info, &info->select);
+} // end: procps_stat_select
+
+
+/*
+ * procps_stat_sort():
+ *
+ * Sort stacks anchored in the passed stack pointers array
+ * based on the designated sort enumerator and specified order.
+ *
+ * Returns those same addresses sorted.
+ *
+ * Note: all of the stacks must be homogeneous (of equal length and content).
+ */
+PROCPS_EXPORT struct stat_stack **procps_stat_sort (
+ struct stat_info *info,
+ struct stat_stack *stacks[],
+ int numstacked,
+ enum stat_item sortitem,
+ enum stat_sort_order order)
+{
+ struct stat_result *p;
+ struct sort_parms parms;
+ int offset;
+
+ errno = EINVAL;
+ if (info == NULL || stacks == NULL)
+ return NULL;
+ // a stat_item is currently unsigned, but we'll protect our future
+ if (sortitem < 0 || sortitem >= STAT_logical_end)
+ return NULL;
+ if (order != STAT_SORT_ASCEND && order != STAT_SORT_DESCEND)
+ return NULL;
+ if (numstacked < 2)
+ return stacks;
+
+ offset = 0;
+ p = stacks[0]->head;
+ for (;;) {
+ if (p->item == sortitem)
+ break;
+ ++offset;
+ if (p->item >= STAT_logical_end)
+ return NULL;
+ ++p;
+ }
+ errno = 0;
+
+ parms.offset = offset;
+ parms.order = order;
+
+ qsort_r(stacks, numstacked, sizeof(void *), (QSR_t)Item_table[p->item].sortfunc, &parms);
+ return stacks;
+} // end: procps_stat_sort
+
+
+// --- special debugging function(s) ------------------------------------------
+/*
+ * The following isn't part of the normal programming interface. Rather,
+ * it exists to validate result types referenced in application programs.
+ *
+ * It's used only when:
+ * 1) the 'XTRA_PROCPS_DEBUG' has been defined, or
+ * 2) an #include of 'xtra-procps-debug.h' is used
+ */
+
+PROCPS_EXPORT struct stat_result *xtra_stat_get (
+ struct stat_info *info,
+ enum stat_item actual_enum,
+ const char *typestr,
+ const char *file,
+ int lineno)
+{
+ struct stat_result *r = procps_stat_get(info, actual_enum);
+
+ if (actual_enum < 0 || actual_enum >= STAT_logical_end) {
+ fprintf(stderr, "%s line %d: invalid item = %d, type = %s\n"
+ , file, lineno, actual_enum, typestr);
+ }
+ if (r) {
+ char *str = Item_table[r->item].type2str;
+ if (str[0]
+ && (strcmp(typestr, str)))
+ fprintf(stderr, "%s line %d: was %s, expected %s\n", file, lineno, typestr, str);
+ }
+ return r;
+} // end: xtra_stat_get_
+
+
+PROCPS_EXPORT struct stat_result *xtra_stat_val (
+ int relative_enum,
+ const char *typestr,
+ const struct stat_stack *stack,
+ struct stat_info *info,
+ const char *file,
+ int lineno)
+{
+ char *str;
+ int i;
+
+ for (i = 0; stack->head[i].item < STAT_logical_end; i++)
+ ;
+ if (relative_enum < 0 || relative_enum >= i) {
+ fprintf(stderr, "%s line %d: invalid relative_enum = %d, valid range = 0-%d\n"
+ , file, lineno, relative_enum, i-1);
+ return NULL;
+ }
+ str = Item_table[stack->head[relative_enum].item].type2str;
+ if (str[0]
+ && (strcmp(typestr, str))) {
+ fprintf(stderr, "%s line %d: was %s, expected %s\n", file, lineno, typestr, str);
+ }
+ return &stack->head[relative_enum];
+ (void)info;
+} // end: xtra_stat_val
diff --git a/library/sysinfo.c b/library/sysinfo.c
new file mode 100644
index 0000000..35a9922
--- /dev/null
+++ b/library/sysinfo.c
@@ -0,0 +1,167 @@
+/*
+ * File for parsing top-level /proc entities.
+ *
+ * Copyright © 2011-2023 Jim Warner <james.warner@comcast.net>
+ * Copyright © 2011-2023 Craig Small <csmall@dropbear.xyz>
+ * Copyright © 1998-2008 Albert Cahalan
+ * Copyright © 1992-1998 Michael K. Johnson <johnsonm@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <locale.h>
+#include <errno.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+#ifdef __CYGWIN__
+#include <sys/param.h>
+#endif
+#include "misc.h"
+#include "procps-private.h"
+
+
+#define LOADAVG_FILE "/proc/loadavg"
+
+/* evals 'x' twice */
+#define SET_IF_DESIRED(x,y) do{ if(x) *(x) = (y); }while(0)
+
+/* return minimum of two values */
+#ifndef __CYGWIN__
+#define MIN(x,y) ((x) < (y) ? (x) : (y))
+#endif
+
+/*
+ * procps_hertz_get:
+ *
+ *
+ * Some values in /proc are expressed in units of 1/HZ seconds, where HZ
+ * is the kernel clock tick rate. One of these units is called a jiffy.
+ * The HZ value used in the kernel may vary according to hacker desire.
+ *
+ * On some architectures, the kernel provides an ELF note to indicate
+ * HZ.
+ *
+ * Returns:
+ * The discovered or assumed hertz value
+ */
+PROCPS_EXPORT long procps_hertz_get(void)
+{
+ long hz;
+
+#ifdef _SC_CLK_TCK
+ if ((hz = sysconf(_SC_CLK_TCK)) > 0)
+ return hz;
+#endif
+#ifdef HZ
+ return(HZ);
+#endif
+ /* Last resort, assume 100 */
+ return 100;
+}
+
+/*
+ * procps_loadavg:
+ * @av1: location to store 1 minute load average
+ * @av5: location to store 5 minute load average
+ * @av15: location to store 15 minute load average
+ *
+ * Find the 1,5 and 15 minute load average of the system
+ *
+ * Returns: 0 on success <0 on error
+ */
+PROCPS_EXPORT int procps_loadavg(
+ double *restrict av1,
+ double *restrict av5,
+ double *restrict av15)
+{
+ double avg_1=0, avg_5=0, avg_15=0;
+ locale_t tmplocale;
+ int retval=0;
+ FILE *fp;
+
+ if ((fp = fopen(LOADAVG_FILE, "r")) == NULL)
+ return -errno;
+
+ tmplocale = newlocale(LC_NUMERIC_MASK, "C", (locale_t)0);
+ uselocale(tmplocale);
+ if (fscanf(fp, "%lf %lf %lf", &avg_1, &avg_5, &avg_15) < 3)
+ retval = -ERANGE;
+
+ fclose(fp);
+ uselocale(LC_GLOBAL_LOCALE);
+ freelocale(tmplocale);
+ SET_IF_DESIRED(av1, avg_1);
+ SET_IF_DESIRED(av5, avg_5);
+ SET_IF_DESIRED(av15, avg_15);
+ return retval;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+#define PROCFS_PID_MAX "/proc/sys/kernel/pid_max"
+#define DEFAULT_PID_LENGTH 5
+
+/*
+ * procps_pid_length
+ *
+ * Return the length of the maximum possible pid.
+ *
+ * Returns either the strlen of PROCFS_PID_MAX or the
+ * best-guess DEFAULT_PID_LENGTH
+ */
+PROCPS_EXPORT unsigned int procps_pid_length(void)
+{
+ FILE *fp;
+ char pidbuf[24];
+ static __thread int pid_length=0;
+
+ if (pid_length)
+ return pid_length;
+
+ pid_length = DEFAULT_PID_LENGTH;
+ if ((fp = fopen(PROCFS_PID_MAX, "r")) != NULL) {
+ if (fgets(pidbuf, sizeof(pidbuf), fp) != NULL) {
+ pid_length = strlen(pidbuf);
+ if (pidbuf[pid_length-1] == '\n')
+ --pid_length;
+ }
+ fclose(fp);
+ }
+ return pid_length;
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+/* procps_cpu_count:
+ *
+ * Returns the number of CPUs that are currently online.
+ *
+ */
+long procps_cpu_count(void)
+{
+ long cpus;
+
+ cpus = sysconf(_SC_NPROCESSORS_ONLN);
+ if (cpus < 1)
+ return 1;
+ return cpus;
+}
+
diff --git a/library/tests/test_Itemtables.c b/library/tests/test_Itemtables.c
new file mode 100644
index 0000000..4204700
--- /dev/null
+++ b/library/tests/test_Itemtables.c
@@ -0,0 +1,91 @@
+/*
+ * libprocps - Library to read proc filesystem
+ * Tests for Item_table/enumerator synchronization
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdlib.h>
+
+#include "diskstats.h"
+#include "meminfo.h"
+#include "pids.h"
+#include "slabinfo.h"
+#include "stat.h"
+#include "vmstat.h"
+
+#include "tests.h"
+
+static int check_diskstats (void *data) {
+ struct diskstats_info *ctx = NULL;
+ testname = "Itemtable check, diskstats";
+ if (0 == procps_diskstats_new(&ctx))
+ procps_diskstats_unref(&ctx);
+ return 1;
+}
+
+static int check_meminfo (void *data) {
+ struct meminfo_info *ctx = NULL;
+ testname = "Itemtable check, meminfo";
+ if (0 == procps_meminfo_new(&ctx))
+ procps_meminfo_unref(&ctx);
+ return 1;
+}
+
+static int check_pids (void *data) {
+ struct pids_info *ctx = NULL;
+ testname = "Itemtable check, pids";
+ if (0 == procps_pids_new(&ctx, NULL, 0))
+ procps_pids_unref(&ctx);
+ return 1;
+}
+
+static int check_slabinfo (void *data) {
+ struct slabinfo_info *ctx = NULL;
+ testname = "Itemtable check, slabinfo";
+ if (0 == procps_slabinfo_new(&ctx))
+ procps_slabinfo_unref(&ctx);
+ return 1;
+}
+
+static int check_stat (void *data) {
+ struct stat_info *ctx = NULL;
+ testname = "Itemtable check, stat";
+ if (0 == procps_stat_new(&ctx))
+ procps_stat_unref(&ctx);
+ return 1;
+}
+
+static int check_vmstat (void *data) {
+ struct vmstat_info *ctx = NULL;
+ testname = "Itemtable check, vmstat";
+ if (0 == procps_vmstat_new(&ctx))
+ procps_vmstat_unref(&ctx);
+ return 1;
+}
+
+static TestFunction test_funcs[] = {
+ check_diskstats,
+ check_meminfo,
+ check_pids,
+ check_slabinfo,
+ check_stat,
+ check_vmstat,
+ NULL
+};
+
+int main (void) {
+ return run_tests(test_funcs, NULL);
+}
diff --git a/library/tests/test_namespace.c b/library/tests/test_namespace.c
new file mode 100644
index 0000000..2c55c4f
--- /dev/null
+++ b/library/tests/test_namespace.c
@@ -0,0 +1,76 @@
+/*
+ * libprocps - Library to read proc filesystem
+ * Tests for namespace library calls
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "misc.h"
+#include "tests.h"
+
+int check_name_minus(void *data)
+{
+ testname = "procps_ns_get_name() negative id";
+ return (procps_ns_get_name(-1) == NULL);
+}
+
+int check_name_over(void *data)
+{
+ testname = "procps_ns_get_name() id over limit";
+ return (procps_ns_get_name(999) == NULL);
+}
+
+int check_name_ipc(void *data)
+{
+ testname = "procps_ns_get_name() ipc";
+ return (strcmp(procps_ns_get_name(PROCPS_NS_IPC),"ipc")==0);
+}
+
+int check_id_null(void *data)
+{
+ testname = "procps_ns_get_id(NULL)";
+ return (procps_ns_get_id(NULL) < 0);
+}
+
+int check_id_unfound(void *data)
+{
+ testname = "procps_ns_get_id(unknown)";
+ return (procps_ns_get_id("foobar") < 0);
+}
+
+int check_id_mnt(void *data)
+{
+ testname = "procps_ns_get_id(mnt)";
+ return (procps_ns_get_id("mnt") == PROCPS_NS_MNT);
+}
+
+TestFunction test_funcs[] = {
+ check_name_minus,
+ check_name_over,
+ check_name_ipc,
+ check_id_null,
+ check_id_unfound,
+ check_id_mnt,
+ NULL
+};
+
+int main(int argc, char *argv[])
+{
+ return run_tests(test_funcs, NULL);
+}
+
diff --git a/library/tests/test_pids.c b/library/tests/test_pids.c
new file mode 100644
index 0000000..eda0b73
--- /dev/null
+++ b/library/tests/test_pids.c
@@ -0,0 +1,75 @@
+/*
+ * libprocps - Library to read proc filesystem
+ * Tests for pids library calls
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include "pids.h"
+#include "tests.h"
+
+enum pids_item items[] = { PIDS_ID_PID, PIDS_ID_PID };
+enum pids_item items2[] = { PIDS_ID_PID, PIDS_VM_RSS };
+
+int check_pids_new_nullinfo(void *data)
+{
+ testname = "procps_pids_new() info=NULL returns -EINVAL";
+ return (procps_pids_new(NULL, items, 0) == -EINVAL);
+}
+
+int check_pids_new_toomany(void *data)
+{
+ struct pids_info *info;
+ testname = "procps_pids_new() too many items returns -EINVAL";
+ return (procps_pids_new(&info, items, 1) == -EINVAL);
+}
+
+int check_pids_new_and_unref(void *data)
+{
+ struct pids_info *info = NULL;
+ testname = "procps_pids new then unref";
+ return ( (procps_pids_new(&info, items, 2) == 0) &&
+ (procps_pids_unref(&info) == 0) &&
+ info == NULL);
+}
+
+int check_fatal_proc_unmounted(void *data)
+{
+ struct pids_info *info = NULL;
+ struct pids_stack *stack;
+ testname = "check_fatal_proc_unmounted";
+
+ return ( (procps_pids_new(&info, items2, 2) == 0) &&
+ ( (stack = fatal_proc_unmounted(info, 1)) != NULL) &&
+ ( PIDS_VAL(0, s_int, stack, info) > 0) &&
+ ( PIDS_VAL(1, ul_int, stack, info) > 0));
+}
+
+TestFunction test_funcs[] = {
+ check_pids_new_nullinfo,
+ // skipped, ask Jim check_pids_new_toomany,
+ check_pids_new_and_unref,
+ check_fatal_proc_unmounted,
+ NULL };
+
+int main(int argc, char *argv[])
+{
+ return run_tests(test_funcs, NULL);
+}
+
+
diff --git a/library/tests/test_sysinfo.c b/library/tests/test_sysinfo.c
new file mode 100644
index 0000000..8390ccd
--- /dev/null
+++ b/library/tests/test_sysinfo.c
@@ -0,0 +1,64 @@
+/*
+ * libprocps - Library to read proc filesystem
+ * Tests for sysinfo library calls
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "misc.h"
+#include "tests.h"
+
+int check_hertz(void *data)
+{
+ long hz;
+ testname = "procps_hertz_get()";
+
+ hz = procps_hertz_get();
+ return (hz > 0);
+}
+
+int check_loadavg(void *data)
+{
+ double a,b,c;
+ testname = "procps_loadavg()";
+
+ if (procps_loadavg(&a, &b, &c) == 0)
+ return 1;
+ return (a>0 && b>0 && c>0);
+}
+
+int check_loadavg_null(void *data)
+{
+ testname = "procps_loadavg() with NULLs";
+ if (procps_loadavg(NULL, NULL, NULL) == 0)
+ return 1;
+ return 0;
+}
+
+TestFunction test_funcs[] = {
+ check_hertz,
+ check_loadavg,
+ check_loadavg_null,
+ NULL,
+};
+
+int main(int argc, char *argv[])
+{
+ return run_tests(test_funcs, NULL);
+}
+
+
diff --git a/library/tests/test_uptime.c b/library/tests/test_uptime.c
new file mode 100644
index 0000000..35d3e2e
--- /dev/null
+++ b/library/tests/test_uptime.c
@@ -0,0 +1,97 @@
+/*
+ * libprocps - Library to read proc filesystem
+ * Tests for version library calls
+ *
+ * Copyright 2016 Craig Small <csmall@dropbear.xyz>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "misc.h"
+#include "tests.h"
+
+int check_uptime(void *data)
+{
+ testname = "procps_uptime()";
+ double up=0, idle=0;
+ int rc;
+ rc = procps_uptime(&up, &idle);
+ return (rc == 0 && up > 0 && idle > 0);
+}
+
+int check_uptime_nullup(void *data)
+{
+ double idle=0;
+ int rc;
+ testname = "procps_uptime() (up=NULL)";
+ rc = procps_uptime(NULL, &idle);
+ return (rc == 0 && idle > 0);
+}
+
+int check_uptime_nullidle(void *data)
+{
+ double up=0;
+ int rc;
+ testname = "procps_uptime() (idle=NULL)";
+ rc = procps_uptime(&up, NULL);
+ return (rc == 0 && up > 0);
+}
+
+int check_uptime_nullall(void *data)
+{
+ int rc;
+ testname = "procps_uptime() (up,idle=NULL)";
+ rc = procps_uptime(NULL, NULL);
+ return (rc == 0);
+}
+
+int check_uptime_sprint(void *data)
+{
+ char *str;
+ testname = "procps_uptime_sprint()";
+
+ str = procps_uptime_sprint();
+
+ return (str != NULL && str[0] != '\0');
+}
+
+int check_uptime_sprint_short(void *data)
+{
+ char *str;
+ testname = "procps_uptime_sprint_short()";
+
+ str = procps_uptime_sprint_short();
+
+ return (str != NULL && str[0] != '\0');
+}
+
+TestFunction test_funcs[] = {
+ check_uptime,
+ check_uptime_nullup,
+ check_uptime_nullidle,
+ check_uptime_nullall,
+ check_uptime_sprint,
+ check_uptime_sprint_short,
+ NULL,
+};
+
+int main(int argc, char *argv[])
+{
+ return run_tests(test_funcs, NULL);
+}
+
+
diff --git a/library/tests/test_version.c b/library/tests/test_version.c
new file mode 100644
index 0000000..f2f5e1f
--- /dev/null
+++ b/library/tests/test_version.c
@@ -0,0 +1,72 @@
+/*
+ * libprocps - Library to read proc filesystem
+ * Tests for version library calls
+ *
+ * Copyright 2016 Craig Small <csmall@dropbear.xyz>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "misc.h"
+#include "tests.h"
+
+int check_linux_version(void *data)
+{
+ testname = "procps_linux_version()";
+ return (procps_linux_version() > 0);
+}
+
+int check_conversion(void *data)
+{
+ testname = "LINUX_VERSION macro";
+ struct testvals {
+ int retval;
+ int major, minor, patch;
+ };
+
+ struct testvals *tv;
+ struct testvals tvs[] = {
+ { 132096, 2, 4, 0 },
+ { 132635, 2, 6, 27 },
+ { 199936, 3, 13, 0 },
+ { 263426, 4, 5, 2 },
+ { 0, 0, 0, 0}
+ };
+
+ for (tv=tvs; tv->major != 0; tv++)
+ {
+ if (LINUX_VERSION(tv->major, tv->minor, tv->patch) != tv->retval) {
+ fprintf(stderr, "Failed %d != %d\n", LINUX_VERSION(tv->major, tv->minor,
+ tv->patch), tv->retval);
+ return 0;
+ }
+ }
+ return 1;
+}
+
+TestFunction test_funcs[] = {
+ check_conversion,
+ check_linux_version,
+ NULL
+};
+
+int main(int argc, char *argv[])
+{
+ return run_tests(test_funcs, NULL);
+}
+
+
diff --git a/library/uptime.c b/library/uptime.c
new file mode 100644
index 0000000..1826343
--- /dev/null
+++ b/library/uptime.c
@@ -0,0 +1,268 @@
+/*
+ * uptime - uptime related functions - part of libproc2
+ *
+ * Copyright © 2015-2023 Craig Small <csmall@dropbear.xyz>
+ * Copyright © 2015-2023 Jim Warner <james.warner@comcast.net>
+ * Copyright © 1998-2003 Albert Cahalan
+ * Copyright © 1992-1998 Michael K. Johnson <johnsonm@redhat.com>
+ * Copyright © 1993 J. Cowley
+ * Copyright © ???? Larry Greenfield <greenfie@gauss.rutgers.edu>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <errno.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <utmp.h>
+#ifdef WITH_SYSTEMD
+#include <systemd/sd-daemon.h>
+#include <systemd/sd-login.h>
+#endif
+#ifdef WITH_ELOGIND
+#include <elogind/sd-daemon.h>
+#include <elogind/sd-login.h>
+#endif
+
+#include "misc.h"
+#include "procps-private.h"
+
+#define UPTIME_FILE "/proc/uptime"
+
+static __thread char upbuf[256];
+static __thread char shortbuf[256];
+
+static int count_users(void)
+{
+ int numuser = 0;
+ struct utmp *ut;
+
+#if defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)
+ if (sd_booted() > 0)
+ return sd_get_sessions(NULL);
+#endif
+
+ setutent();
+ while ((ut = getutent())) {
+ if ((ut->ut_type == USER_PROCESS) && (ut->ut_name[0] != '\0'))
+ numuser++;
+ }
+ endutent();
+
+ return numuser;
+}
+
+/*
+ * uptime:
+ *
+ * Find the uptime and idle time of the system.
+ * These numbers are found in /proc/uptime
+ * Unlike other procps functions this closes the file each time
+ * Either uptime_secs or idle_secs can be null
+ *
+ * Returns: 0 on success and <0 on failure
+ */
+PROCPS_EXPORT int procps_uptime(
+ double *restrict uptime_secs,
+ double *restrict idle_secs)
+{
+ double up=0, idle=0;
+ locale_t tmplocale;
+ FILE *fp;
+ int rc;
+
+ if ((fp = fopen(UPTIME_FILE, "r")) == NULL)
+ return -errno;
+
+ tmplocale = newlocale(LC_NUMERIC_MASK, "C", (locale_t)0);
+ uselocale(tmplocale);
+ rc = fscanf(fp, "%lf %lf", &up, &idle);
+ fclose(fp);
+ uselocale(LC_GLOBAL_LOCALE);
+ freelocale(tmplocale);
+
+ if (uptime_secs)
+ *uptime_secs = up;
+ if (idle_secs)
+ *idle_secs = idle;
+
+ if (rc < 2)
+ return -ERANGE;
+ return 0;
+}
+
+/*
+ * procps_uptime_sprint:
+ *
+ * Print current time in nice format
+ *
+ * Returns a statically allocated upbuf or NULL on error
+ */
+PROCPS_EXPORT char *procps_uptime_sprint(void)
+{
+ int upminutes, uphours, updays, users;
+ int pos;
+ time_t realseconds;
+ struct tm realtime;
+ double uptime_secs, idle_secs;
+ double av1, av5, av15;
+
+ upbuf[0] = '\0';
+ if (time(&realseconds) < 0)
+ return upbuf;
+ localtime_r(&realseconds, &realtime);
+
+ if (procps_uptime(&uptime_secs, &idle_secs) < 0)
+ return upbuf;
+
+ updays = ((int) uptime_secs / (60*60*24));
+ uphours = ((int) uptime_secs / (60*60)) % 24;
+ upminutes = ((int) uptime_secs / (60)) % 60;
+
+ pos = sprintf(upbuf, " %02d:%02d:%02d up ",
+ realtime.tm_hour, realtime.tm_min, realtime.tm_sec);
+
+ if (updays)
+ pos += sprintf(upbuf + pos, "%d %s, ", updays, (updays > 1) ? "days" : "day");
+
+ if (uphours)
+ pos += sprintf(upbuf + pos, "%2d:%02d, ", uphours, upminutes);
+ else
+ pos += sprintf(upbuf + pos, "%d min, ", upminutes);
+
+ users = count_users();
+ procps_loadavg(&av1, &av5, &av15);
+
+ if (users < 0)
+ pos += sprintf(upbuf + pos, " ? ");
+ else
+ pos += sprintf(upbuf + pos, "%2d ", users);
+
+ pos += sprintf(upbuf + pos, "%s, load average: %.2f, %.2f, %.2f",
+ users > 1 ? "users" : "user",
+ av1, av5, av15);
+
+ return upbuf;
+}
+
+/*
+ * procps_uptime_sprint_short:
+ *
+ * Print current time in nice format
+ *
+ * Returns a statically allocated buffer or NULL on error
+ */
+PROCPS_EXPORT char *procps_uptime_sprint_short(void)
+{
+ int updecades, upyears, upweeks, updays, uphours = 0, upminutes = 0;
+ int pos = 3;
+ int comma = 0;
+ double uptime_secs, idle_secs;
+
+ shortbuf[0] = '\0';
+ if (procps_uptime(&uptime_secs, &idle_secs) < 0)
+ return shortbuf;
+
+ if (uptime_secs>60*60*24*365*10) {
+ updecades = (int) uptime_secs / (60*60*24*365*10);
+ uptime_secs -= updecades*60*60*24*365*10;
+ }
+ else {
+ updecades = 0;
+ }
+ if (uptime_secs>60*60*24*365) {
+ upyears = (int) uptime_secs / (60*60*24*365);
+ uptime_secs -= upyears*60*60*24*365;
+ }
+ else {
+ upyears = 0;
+ }
+ if (uptime_secs>60*60*24*7) {
+ upweeks = (int) uptime_secs / (60*60*24*7);
+ uptime_secs -= upweeks*60*60*24*7;
+ }
+ else {
+ upweeks = 0;
+ }
+ if (uptime_secs>60*60*24) {
+ updays = (int) uptime_secs / (60*60*24);
+ uptime_secs -= updays*60*60*24;
+ }
+ else {
+ updays = 0;
+ }
+ if (uptime_secs>60*60) {
+ uphours = (int) uptime_secs / (60*60);
+ uptime_secs -= uphours*60*60;
+ }
+ if (uptime_secs>60) {
+ upminutes = (int) uptime_secs / 60;
+ uptime_secs -= upminutes*60;
+ }
+ /*updecades = (int) uptime_secs / (60*60*24*365*10);
+ upyears = ((int) uptime_secs / (60*60*24*365)) % 10;
+ upweeks = ((int) uptime_secs / (60*60*24*7)) % 52;
+ updays = ((int) uptime_secs / (60*60*24)) % 7;
+ uphours = ((int) uptime_secs / (60*60)) % 24;
+ upminutes = ((int) uptime_secs / (60)) % 60;
+*/
+ strcat(shortbuf, "up ");
+
+ if (updecades) {
+ pos += sprintf(shortbuf + pos, "%d %s",
+ updecades, updecades > 1 ? "decades" : "decade");
+ comma += 1;
+ }
+
+ if (upyears) {
+ pos += sprintf(shortbuf + pos, "%s%d %s",
+ comma > 0 ? ", " : "", upyears,
+ upyears > 1 ? "years" : "year");
+ comma += 1;
+ }
+
+ if (upweeks) {
+ pos += sprintf(shortbuf + pos, "%s%d %s",
+ comma > 0 ? ", " : "", upweeks,
+ upweeks > 1 ? "weeks" : "week");
+ comma += 1;
+ }
+
+ if (updays) {
+ pos += sprintf(shortbuf + pos, "%s%d %s",
+ comma > 0 ? ", " : "", updays,
+ updays > 1 ? "days" : "day");
+ comma += 1;
+ }
+
+ if (uphours) {
+ pos += sprintf(shortbuf + pos, "%s%d %s",
+ comma > 0 ? ", " : "", uphours,
+ uphours > 1 ? "hours" : "hour");
+ comma += 1;
+ }
+
+ if (upminutes || (!upminutes && uptime_secs < 60)) {
+ pos += sprintf(shortbuf + pos, "%s%d %s",
+ comma > 0 ? ", " : "", upminutes,
+ upminutes != 1 ? "minutes" : "minute");
+ comma += 1;
+ }
+ return shortbuf;
+}
diff --git a/library/version.c b/library/version.c
new file mode 100644
index 0000000..eab3404
--- /dev/null
+++ b/library/version.c
@@ -0,0 +1,71 @@
+/*
+ * libproc2 - Library to read proc filesystem
+ *
+ * Copyright © 2002-2023 Craig Small <csmall@dropbear.xyz>
+ * Copyright © 2013-2023 Jim Warner <james.warner@comcast.net>
+ * Copyright © 2011-2012 Sami Kerola <kerolasa@iki.fi>
+ * Copyright © 2002-2003 Albert Cahalan
+ * Copyright © 1996 Charles Blake <cblake@bbn.com>
+ * Copyright © 1995 Martin Schulze <joey@infodrom.north.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include <errno.h>
+#include <stdio.h>
+#include "misc.h"
+#include "procps-private.h"
+
+#ifdef __CYGWIN__
+#define PROCFS_OSRELEASE "/proc/version"
+#define PROCFS_OSPATTERN "%*s version %u.%u.%u"
+#else
+#define PROCFS_OSRELEASE "/proc/sys/kernel/osrelease"
+#define PROCFS_OSPATTERN "%u.%u.%u"
+#endif
+
+/*
+ * procps_linux_version
+ *
+ * Return the current running Linux version release as shown in
+ * the procps filesystem.
+ *
+ * There are three ways you can get OS release:
+ * 1) /proc/sys/kernel/osrelease - returns correct version of procfs
+ * 2) /proc/version - returns version of kernel e.g. BSD this is wrong
+ * 3) uname and uts.release - same as /proc/version field #3
+ *
+ * Returns: version as an integer
+ * Negative value means an error
+ */
+PROCPS_EXPORT int procps_linux_version(void)
+{
+ FILE *fp;
+ char buf[256];
+ unsigned int x = 0, y = 0, z = 0;
+ int version_string_depth;
+
+ if ((fp = fopen(PROCFS_OSRELEASE, "r")) == NULL)
+ return -errno;
+ if (fgets(buf, 256, fp) == NULL) {
+ fclose(fp);
+ return -EIO;
+ }
+ fclose(fp);
+ version_string_depth = sscanf(buf, PROCFS_OSPATTERN, &x, &y, &z);
+ if ((version_string_depth < 2) || /* Non-standard for all known kernels */
+ ((version_string_depth < 3) && (x < 3))) /* Non-standard for 2.x.x kernels */
+ return -ERANGE;
+ return LINUX_VERSION(x,y,z);
+}
diff --git a/library/vmstat.c b/library/vmstat.c
new file mode 100644
index 0000000..f9ae715
--- /dev/null
+++ b/library/vmstat.c
@@ -0,0 +1,1513 @@
+/*
+ * vmstat.c - virtual memory related definitions for libproc2
+ *
+ * Copyright © 2015-2023 Jim Warner <james.warner@comcast.net>
+ * Copyright © 2015-2023 Craig Small <csmall@dropbear.xyz>
+ * Copyright © 2003 Albert Cahalan
+ * Copyright © 1996 Charles Blake <cblake@bbn.com>
+ * Copyright © 1995 Martin Schulze <joey@infodrom.north.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <search.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "procps-private.h"
+#include "vmstat.h"
+
+
+#define VMSTAT_FILE "/proc/vmstat"
+#define VMSTAT_BUFF 8192
+
+/* ------------------------------------------------------------- +
+ this provision can be used to help ensure that our Item_table |
+ was synchronized with the enumerators found in the associated |
+ header file. It's intended to be used locally (& temporarily) |
+ at least once at some point prior to publishing new releases! | */
+// #define ITEMTABLE_DEBUG //----------------------------------- |
+// ------------------------------------------------------------- +
+
+/*
+ * Perhaps someday we'll all learn what is in these fields. But |
+ * that might require available linux documentation progressing |
+ * beyond a state that was acknowledged in the following thread |
+ *
+ * http://www.spinics.net/lists/linux-man/msg09096.html
+ */
+struct vmstat_data {
+ unsigned long allocstall_dma;
+ unsigned long allocstall_dma32;
+ unsigned long allocstall_high;
+ unsigned long allocstall_movable;
+ unsigned long allocstall_normal;
+ unsigned long balloon_deflate;
+ unsigned long balloon_inflate;
+ unsigned long balloon_migrate;
+ unsigned long compact_daemon_free_scanned;
+ unsigned long compact_daemon_migrate_scanned;
+ unsigned long compact_daemon_wake;
+ unsigned long compact_fail;
+ unsigned long compact_free_scanned;
+ unsigned long compact_isolated;
+ unsigned long compact_migrate_scanned;
+ unsigned long compact_stall;
+ unsigned long compact_success;
+ unsigned long drop_pagecache;
+ unsigned long drop_slab;
+ unsigned long htlb_buddy_alloc_fail;
+ unsigned long htlb_buddy_alloc_success;
+ unsigned long kswapd_high_wmark_hit_quickly;
+ unsigned long kswapd_inodesteal;
+ unsigned long kswapd_low_wmark_hit_quickly;
+ unsigned long nr_active_anon;
+ unsigned long nr_active_file;
+ unsigned long nr_anon_pages;
+ unsigned long nr_anon_transparent_hugepages;
+ unsigned long nr_bounce;
+ unsigned long nr_dirtied;
+ unsigned long nr_dirty;
+ unsigned long nr_dirty_background_threshold;
+ unsigned long nr_dirty_threshold;
+ unsigned long nr_file_hugepages;
+ unsigned long nr_file_pages;
+ unsigned long nr_file_pmdmapped;
+ unsigned long nr_foll_pin_acquired;
+ unsigned long nr_foll_pin_released;
+ unsigned long nr_free_cma;
+ unsigned long nr_free_pages;
+ unsigned long nr_inactive_anon;
+ unsigned long nr_inactive_file;
+ unsigned long nr_isolated_anon;
+ unsigned long nr_isolated_file;
+ unsigned long nr_kernel_misc_reclaimable;
+ unsigned long nr_kernel_stack;
+ unsigned long nr_mapped;
+ unsigned long nr_mlock;
+ unsigned long nr_page_table_pages;
+ unsigned long nr_shadow_call_stack;
+ unsigned long nr_shmem;
+ unsigned long nr_shmem_hugepages;
+ unsigned long nr_shmem_pmdmapped;
+ unsigned long nr_slab_reclaimable;
+ unsigned long nr_slab_unreclaimable;
+/* nr_tlb_local_flush_all; CONFIG_DEBUG_TLBFLUSH only */
+/* nr_tlb_local_flush_one; CONFIG_DEBUG_TLBFLUSH only */
+/* nr_tlb_remote_flush; CONFIG_DEBUG_TLBFLUSH only */
+/* nr_tlb_remote_flush_received; CONFIG_DEBUG_TLBFLUSH only */
+ unsigned long nr_unevictable;
+ unsigned long nr_unstable;
+ unsigned long nr_vmscan_immediate_reclaim;
+ unsigned long nr_vmscan_write;
+ unsigned long nr_writeback;
+ unsigned long nr_writeback_temp;
+ unsigned long nr_written;
+ unsigned long nr_zone_active_anon;
+ unsigned long nr_zone_active_file;
+ unsigned long nr_zone_inactive_anon;
+ unsigned long nr_zone_inactive_file;
+ unsigned long nr_zone_unevictable;
+ unsigned long nr_zone_write_pending;
+ unsigned long nr_zspages;
+ unsigned long numa_foreign;
+ unsigned long numa_hint_faults;
+ unsigned long numa_hint_faults_local;
+ unsigned long numa_hit;
+ unsigned long numa_huge_pte_updates;
+ unsigned long numa_interleave;
+ unsigned long numa_local;
+ unsigned long numa_miss;
+ unsigned long numa_other;
+ unsigned long numa_pages_migrated;
+ unsigned long numa_pte_updates;
+ unsigned long oom_kill;
+ unsigned long pageoutrun;
+ unsigned long pgactivate;
+ unsigned long pgalloc_dma;
+ unsigned long pgalloc_dma32;
+ unsigned long pgalloc_high;
+ unsigned long pgalloc_movable;
+ unsigned long pgalloc_normal;
+ unsigned long pgdeactivate;
+ unsigned long pgfault;
+ unsigned long pgfree;
+ unsigned long pginodesteal;
+ unsigned long pglazyfree;
+ unsigned long pglazyfreed;
+ unsigned long pgmajfault;
+ unsigned long pgmigrate_fail;
+ unsigned long pgmigrate_success;
+ unsigned long pgpgin;
+ unsigned long pgpgout;
+ unsigned long pgrefill;
+ unsigned long pgrotated;
+ unsigned long pgscan_anon;
+ unsigned long pgscan_direct;
+ unsigned long pgscan_direct_throttle;
+ unsigned long pgscan_file;
+ unsigned long pgscan_kswapd;
+ unsigned long pgskip_dma;
+ unsigned long pgskip_dma32;
+ unsigned long pgskip_high;
+ unsigned long pgskip_movable;
+ unsigned long pgskip_normal;
+ unsigned long pgsteal_anon;
+ unsigned long pgsteal_direct;
+ unsigned long pgsteal_file;
+ unsigned long pgsteal_kswapd;
+ unsigned long pswpin;
+ unsigned long pswpout;
+ unsigned long slabs_scanned;
+ unsigned long swap_ra;
+ unsigned long swap_ra_hit;
+ unsigned long thp_collapse_alloc;
+ unsigned long thp_collapse_alloc_failed;
+ unsigned long thp_deferred_split_page;
+ unsigned long thp_fault_alloc;
+ unsigned long thp_fault_fallback;
+ unsigned long thp_fault_fallback_charge;
+ unsigned long thp_file_alloc;
+ unsigned long thp_file_fallback;
+ unsigned long thp_file_fallback_charge;
+ unsigned long thp_file_mapped;
+ unsigned long thp_split_page;
+ unsigned long thp_split_page_failed;
+ unsigned long thp_split_pmd;
+ unsigned long thp_split_pud;
+ unsigned long thp_swpout;
+ unsigned long thp_swpout_fallback;
+ unsigned long thp_zero_page_alloc;
+ unsigned long thp_zero_page_alloc_failed;
+ unsigned long unevictable_pgs_cleared;
+ unsigned long unevictable_pgs_culled;
+ unsigned long unevictable_pgs_mlocked;
+ unsigned long unevictable_pgs_munlocked;
+ unsigned long unevictable_pgs_rescued;
+ unsigned long unevictable_pgs_scanned;
+ unsigned long unevictable_pgs_stranded;
+/* vmacache_find_calls; CONFIG_DEBUG_VM_VMACACHE only */
+/* vmacache_find_hits; CONFIG_DEBUG_VM_VMACACHE only */
+ unsigned long workingset_activate;
+ unsigned long workingset_nodereclaim;
+ unsigned long workingset_nodes;
+ unsigned long workingset_refault;
+ unsigned long workingset_restore;
+ unsigned long zone_reclaim_failed;
+};
+
+struct vmstat_hist {
+ struct vmstat_data new;
+ struct vmstat_data old;
+};
+
+struct stacks_extent {
+ int ext_numstacks;
+ struct stacks_extent *next;
+ struct vmstat_stack **stacks;
+};
+
+struct vmstat_info {
+ int refcount;
+ int vmstat_fd;
+ struct vmstat_hist hist;
+ int numitems;
+ enum vmstat_item *items;
+ struct stacks_extent *extents;
+ struct hsearch_data hashtab;
+ struct vmstat_result get_this;
+ time_t sav_secs;
+};
+
+
+// ___ Results 'Set' Support ||||||||||||||||||||||||||||||||||||||||||||||||||
+
+#define setNAME(e) set_vmstat_ ## e
+#define setDECL(e) static void setNAME(e) \
+ (struct vmstat_result *R, struct vmstat_hist *H)
+
+// regular assignment
+#define REG_set(e,x) setDECL(e) { R->result.ul_int = H->new. x; }
+// delta assignment
+#define HST_set(e,x) setDECL(e) { R->result.sl_int = ( H->new. x - H->old. x ); }
+
+setDECL(noop) { (void)R; (void)H; }
+setDECL(extra) { (void)H; R->result.ul_int = 0; }
+
+REG_set(ALLOCSTALL_DMA, allocstall_dma)
+REG_set(ALLOCSTALL_DMA32, allocstall_dma32)
+REG_set(ALLOCSTALL_HIGH, allocstall_high)
+REG_set(ALLOCSTALL_MOVABLE, allocstall_movable)
+REG_set(ALLOCSTALL_NORMAL, allocstall_normal)
+REG_set(BALLOON_DEFLATE, balloon_deflate)
+REG_set(BALLOON_INFLATE, balloon_inflate)
+REG_set(BALLOON_MIGRATE, balloon_migrate)
+REG_set(COMPACT_DAEMON_FREE_SCANNED, compact_daemon_free_scanned)
+REG_set(COMPACT_DAEMON_MIGRATE_SCANNED, compact_daemon_migrate_scanned)
+REG_set(COMPACT_DAEMON_WAKE, compact_daemon_wake)
+REG_set(COMPACT_FAIL, compact_fail)
+REG_set(COMPACT_FREE_SCANNED, compact_free_scanned)
+REG_set(COMPACT_ISOLATED, compact_isolated)
+REG_set(COMPACT_MIGRATE_SCANNED, compact_migrate_scanned)
+REG_set(COMPACT_STALL, compact_stall)
+REG_set(COMPACT_SUCCESS, compact_success)
+REG_set(DROP_PAGECACHE, drop_pagecache)
+REG_set(DROP_SLAB, drop_slab)
+REG_set(HTLB_BUDDY_ALLOC_FAIL, htlb_buddy_alloc_fail)
+REG_set(HTLB_BUDDY_ALLOC_SUCCESS, htlb_buddy_alloc_success)
+REG_set(KSWAPD_HIGH_WMARK_HIT_QUICKLY, kswapd_high_wmark_hit_quickly)
+REG_set(KSWAPD_INODESTEAL, kswapd_inodesteal)
+REG_set(KSWAPD_LOW_WMARK_HIT_QUICKLY, kswapd_low_wmark_hit_quickly)
+REG_set(NR_ACTIVE_ANON, nr_active_anon)
+REG_set(NR_ACTIVE_FILE, nr_active_file)
+REG_set(NR_ANON_PAGES, nr_anon_pages)
+REG_set(NR_ANON_TRANSPARENT_HUGEPAGES, nr_anon_transparent_hugepages)
+REG_set(NR_BOUNCE, nr_bounce)
+REG_set(NR_DIRTIED, nr_dirtied)
+REG_set(NR_DIRTY, nr_dirty)
+REG_set(NR_DIRTY_BACKGROUND_THRESHOLD, nr_dirty_background_threshold)
+REG_set(NR_DIRTY_THRESHOLD, nr_dirty_threshold)
+REG_set(NR_FILE_HUGEPAGES, nr_file_hugepages)
+REG_set(NR_FILE_PAGES, nr_file_pages)
+REG_set(NR_FILE_PMDMAPPED, nr_file_pmdmapped)
+REG_set(NR_FOLL_PIN_ACQUIRED, nr_foll_pin_acquired)
+REG_set(NR_FOLL_PIN_RELEASED, nr_foll_pin_released)
+REG_set(NR_FREE_CMA, nr_free_cma)
+REG_set(NR_FREE_PAGES, nr_free_pages)
+REG_set(NR_INACTIVE_ANON, nr_inactive_anon)
+REG_set(NR_INACTIVE_FILE, nr_inactive_file)
+REG_set(NR_ISOLATED_ANON, nr_isolated_anon)
+REG_set(NR_ISOLATED_FILE, nr_isolated_file)
+REG_set(NR_KERNEL_MISC_RECLAIMABLE, nr_kernel_misc_reclaimable)
+REG_set(NR_KERNEL_STACK, nr_kernel_stack)
+REG_set(NR_MAPPED, nr_mapped)
+REG_set(NR_MLOCK, nr_mlock)
+REG_set(NR_PAGE_TABLE_PAGES, nr_page_table_pages)
+REG_set(NR_SHADOW_CALL_STACK, nr_shadow_call_stack)
+REG_set(NR_SHMEM, nr_shmem)
+REG_set(NR_SHMEM_HUGEPAGES, nr_shmem_hugepages)
+REG_set(NR_SHMEM_PMDMAPPED, nr_shmem_pmdmapped)
+REG_set(NR_SLAB_RECLAIMABLE, nr_slab_reclaimable)
+REG_set(NR_SLAB_UNRECLAIMABLE, nr_slab_unreclaimable)
+REG_set(NR_UNEVICTABLE, nr_unevictable)
+REG_set(NR_UNSTABLE, nr_unstable)
+REG_set(NR_VMSCAN_IMMEDIATE_RECLAIM, nr_vmscan_immediate_reclaim)
+REG_set(NR_VMSCAN_WRITE, nr_vmscan_write)
+REG_set(NR_WRITEBACK, nr_writeback)
+REG_set(NR_WRITEBACK_TEMP, nr_writeback_temp)
+REG_set(NR_WRITTEN, nr_written)
+REG_set(NR_ZONE_ACTIVE_ANON, nr_zone_active_anon)
+REG_set(NR_ZONE_ACTIVE_FILE, nr_zone_active_file)
+REG_set(NR_ZONE_INACTIVE_ANON, nr_zone_inactive_anon)
+REG_set(NR_ZONE_INACTIVE_FILE, nr_zone_inactive_file)
+REG_set(NR_ZONE_UNEVICTABLE, nr_zone_unevictable)
+REG_set(NR_ZONE_WRITE_PENDING, nr_zone_write_pending)
+REG_set(NR_ZSPAGES, nr_zspages)
+REG_set(NUMA_FOREIGN, numa_foreign)
+REG_set(NUMA_HINT_FAULTS, numa_hint_faults)
+REG_set(NUMA_HINT_FAULTS_LOCAL, numa_hint_faults_local)
+REG_set(NUMA_HIT, numa_hit)
+REG_set(NUMA_HUGE_PTE_UPDATES, numa_huge_pte_updates)
+REG_set(NUMA_INTERLEAVE, numa_interleave)
+REG_set(NUMA_LOCAL, numa_local)
+REG_set(NUMA_MISS, numa_miss)
+REG_set(NUMA_OTHER, numa_other)
+REG_set(NUMA_PAGES_MIGRATED, numa_pages_migrated)
+REG_set(NUMA_PTE_UPDATES, numa_pte_updates)
+REG_set(OOM_KILL, oom_kill)
+REG_set(PAGEOUTRUN, pageoutrun)
+REG_set(PGACTIVATE, pgactivate)
+REG_set(PGALLOC_DMA, pgalloc_dma)
+REG_set(PGALLOC_DMA32, pgalloc_dma32)
+REG_set(PGALLOC_HIGH, pgalloc_high)
+REG_set(PGALLOC_MOVABLE, pgalloc_movable)
+REG_set(PGALLOC_NORMAL, pgalloc_normal)
+REG_set(PGDEACTIVATE, pgdeactivate)
+REG_set(PGFAULT, pgfault)
+REG_set(PGFREE, pgfree)
+REG_set(PGINODESTEAL, pginodesteal)
+REG_set(PGLAZYFREE, pglazyfree)
+REG_set(PGLAZYFREED, pglazyfreed)
+REG_set(PGMAJFAULT, pgmajfault)
+REG_set(PGMIGRATE_FAIL, pgmigrate_fail)
+REG_set(PGMIGRATE_SUCCESS, pgmigrate_success)
+REG_set(PGPGIN, pgpgin)
+REG_set(PGPGOUT, pgpgout)
+REG_set(PGREFILL, pgrefill)
+REG_set(PGROTATED, pgrotated)
+REG_set(PGSCAN_ANON, pgscan_anon)
+REG_set(PGSCAN_DIRECT, pgscan_direct)
+REG_set(PGSCAN_DIRECT_THROTTLE, pgscan_direct_throttle)
+REG_set(PGSCAN_FILE, pgscan_file)
+REG_set(PGSCAN_KSWAPD, pgscan_kswapd)
+REG_set(PGSKIP_DMA, pgskip_dma)
+REG_set(PGSKIP_DMA32, pgskip_dma32)
+REG_set(PGSKIP_HIGH, pgskip_high)
+REG_set(PGSKIP_MOVABLE, pgskip_movable)
+REG_set(PGSKIP_NORMAL, pgskip_normal)
+REG_set(PGSTEAL_ANON, pgsteal_anon)
+REG_set(PGSTEAL_DIRECT, pgsteal_direct)
+REG_set(PGSTEAL_FILE, pgsteal_file)
+REG_set(PGSTEAL_KSWAPD, pgsteal_kswapd)
+REG_set(PSWPIN, pswpin)
+REG_set(PSWPOUT, pswpout)
+REG_set(SLABS_SCANNED, slabs_scanned)
+REG_set(SWAP_RA, swap_ra)
+REG_set(SWAP_RA_HIT, swap_ra_hit)
+REG_set(THP_COLLAPSE_ALLOC, thp_collapse_alloc)
+REG_set(THP_COLLAPSE_ALLOC_FAILED, thp_collapse_alloc_failed)
+REG_set(THP_DEFERRED_SPLIT_PAGE, thp_deferred_split_page)
+REG_set(THP_FAULT_ALLOC, thp_fault_alloc)
+REG_set(THP_FAULT_FALLBACK, thp_fault_fallback)
+REG_set(THP_FAULT_FALLBACK_CHARGE, thp_fault_fallback_charge)
+REG_set(THP_FILE_ALLOC, thp_file_alloc)
+REG_set(THP_FILE_FALLBACK, thp_file_fallback)
+REG_set(THP_FILE_FALLBACK_CHARGE, thp_file_fallback_charge)
+REG_set(THP_FILE_MAPPED, thp_file_mapped)
+REG_set(THP_SPLIT_PAGE, thp_split_page)
+REG_set(THP_SPLIT_PAGE_FAILED, thp_split_page_failed)
+REG_set(THP_SPLIT_PMD, thp_split_pmd)
+REG_set(THP_SPLIT_PUD, thp_split_pud)
+REG_set(THP_SWPOUT, thp_swpout)
+REG_set(THP_SWPOUT_FALLBACK, thp_swpout_fallback)
+REG_set(THP_ZERO_PAGE_ALLOC, thp_zero_page_alloc)
+REG_set(THP_ZERO_PAGE_ALLOC_FAILED, thp_zero_page_alloc_failed)
+REG_set(UNEVICTABLE_PGS_CLEARED, unevictable_pgs_cleared)
+REG_set(UNEVICTABLE_PGS_CULLED, unevictable_pgs_culled)
+REG_set(UNEVICTABLE_PGS_MLOCKED, unevictable_pgs_mlocked)
+REG_set(UNEVICTABLE_PGS_MUNLOCKED, unevictable_pgs_munlocked)
+REG_set(UNEVICTABLE_PGS_RESCUED, unevictable_pgs_rescued)
+REG_set(UNEVICTABLE_PGS_SCANNED, unevictable_pgs_scanned)
+REG_set(UNEVICTABLE_PGS_STRANDED, unevictable_pgs_stranded)
+REG_set(WORKINGSET_ACTIVATE, workingset_activate)
+REG_set(WORKINGSET_NODERECLAIM, workingset_nodereclaim)
+REG_set(WORKINGSET_NODES, workingset_nodes)
+REG_set(WORKINGSET_REFAULT, workingset_refault)
+REG_set(WORKINGSET_RESTORE, workingset_restore)
+REG_set(ZONE_RECLAIM_FAILED, zone_reclaim_failed)
+
+HST_set(DELTA_ALLOCSTALL_DMA, allocstall_dma)
+HST_set(DELTA_ALLOCSTALL_DMA32, allocstall_dma32)
+HST_set(DELTA_ALLOCSTALL_HIGH, allocstall_high)
+HST_set(DELTA_ALLOCSTALL_MOVABLE, allocstall_movable)
+HST_set(DELTA_ALLOCSTALL_NORMAL, allocstall_normal)
+HST_set(DELTA_BALLOON_DEFLATE, balloon_deflate)
+HST_set(DELTA_BALLOON_INFLATE, balloon_inflate)
+HST_set(DELTA_BALLOON_MIGRATE, balloon_migrate)
+HST_set(DELTA_COMPACT_DAEMON_FREE_SCANNED, compact_daemon_free_scanned)
+HST_set(DELTA_COMPACT_DAEMON_MIGRATE_SCANNED, compact_daemon_migrate_scanned)
+HST_set(DELTA_COMPACT_DAEMON_WAKE, compact_daemon_wake)
+HST_set(DELTA_COMPACT_FAIL, compact_fail)
+HST_set(DELTA_COMPACT_FREE_SCANNED, compact_free_scanned)
+HST_set(DELTA_COMPACT_ISOLATED, compact_isolated)
+HST_set(DELTA_COMPACT_MIGRATE_SCANNED, compact_migrate_scanned)
+HST_set(DELTA_COMPACT_STALL, compact_stall)
+HST_set(DELTA_COMPACT_SUCCESS, compact_success)
+HST_set(DELTA_DROP_PAGECACHE, drop_pagecache)
+HST_set(DELTA_DROP_SLAB, drop_slab)
+HST_set(DELTA_HTLB_BUDDY_ALLOC_FAIL, htlb_buddy_alloc_fail)
+HST_set(DELTA_HTLB_BUDDY_ALLOC_SUCCESS, htlb_buddy_alloc_success)
+HST_set(DELTA_KSWAPD_HIGH_WMARK_HIT_QUICKLY, kswapd_high_wmark_hit_quickly)
+HST_set(DELTA_KSWAPD_INODESTEAL, kswapd_inodesteal)
+HST_set(DELTA_KSWAPD_LOW_WMARK_HIT_QUICKLY, kswapd_low_wmark_hit_quickly)
+HST_set(DELTA_NR_ACTIVE_ANON, nr_active_anon)
+HST_set(DELTA_NR_ACTIVE_FILE, nr_active_file)
+HST_set(DELTA_NR_ANON_PAGES, nr_anon_pages)
+HST_set(DELTA_NR_ANON_TRANSPARENT_HUGEPAGES, nr_anon_transparent_hugepages)
+HST_set(DELTA_NR_BOUNCE, nr_bounce)
+HST_set(DELTA_NR_DIRTIED, nr_dirtied)
+HST_set(DELTA_NR_DIRTY, nr_dirty)
+HST_set(DELTA_NR_DIRTY_BACKGROUND_THRESHOLD, nr_dirty_background_threshold)
+HST_set(DELTA_NR_DIRTY_THRESHOLD, nr_dirty_threshold)
+HST_set(DELTA_NR_FILE_HUGEPAGES, nr_file_hugepages)
+HST_set(DELTA_NR_FILE_PAGES, nr_file_pages)
+HST_set(DELTA_NR_FILE_PMDMAPPED, nr_file_pmdmapped)
+HST_set(DELTA_NR_FOLL_PIN_ACQUIRED, nr_foll_pin_acquired)
+HST_set(DELTA_NR_FOLL_PIN_RELEASED, nr_foll_pin_released)
+HST_set(DELTA_NR_FREE_CMA, nr_free_cma)
+HST_set(DELTA_NR_FREE_PAGES, nr_free_pages)
+HST_set(DELTA_NR_INACTIVE_ANON, nr_inactive_anon)
+HST_set(DELTA_NR_INACTIVE_FILE, nr_inactive_file)
+HST_set(DELTA_NR_ISOLATED_ANON, nr_isolated_anon)
+HST_set(DELTA_NR_ISOLATED_FILE, nr_isolated_file)
+HST_set(DELTA_NR_KERNEL_MISC_RECLAIMABLE, nr_kernel_misc_reclaimable)
+HST_set(DELTA_NR_KERNEL_STACK, nr_kernel_stack)
+HST_set(DELTA_NR_MAPPED, nr_mapped)
+HST_set(DELTA_NR_MLOCK, nr_mlock)
+HST_set(DELTA_NR_PAGE_TABLE_PAGES, nr_page_table_pages)
+HST_set(DELTA_NR_SHADOW_CALL_STACK, nr_shadow_call_stack)
+HST_set(DELTA_NR_SHMEM, nr_shmem)
+HST_set(DELTA_NR_SHMEM_HUGEPAGES, nr_shmem_hugepages)
+HST_set(DELTA_NR_SHMEM_PMDMAPPED, nr_shmem_pmdmapped)
+HST_set(DELTA_NR_SLAB_RECLAIMABLE, nr_slab_reclaimable)
+HST_set(DELTA_NR_SLAB_UNRECLAIMABLE, nr_slab_unreclaimable)
+HST_set(DELTA_NR_UNEVICTABLE, nr_unevictable)
+HST_set(DELTA_NR_UNSTABLE, nr_unstable)
+HST_set(DELTA_NR_VMSCAN_IMMEDIATE_RECLAIM, nr_vmscan_immediate_reclaim)
+HST_set(DELTA_NR_VMSCAN_WRITE, nr_vmscan_write)
+HST_set(DELTA_NR_WRITEBACK, nr_writeback)
+HST_set(DELTA_NR_WRITEBACK_TEMP, nr_writeback_temp)
+HST_set(DELTA_NR_WRITTEN, nr_written)
+HST_set(DELTA_NR_ZONE_ACTIVE_ANON, nr_zone_active_anon)
+HST_set(DELTA_NR_ZONE_ACTIVE_FILE, nr_zone_active_file)
+HST_set(DELTA_NR_ZONE_INACTIVE_ANON, nr_zone_inactive_anon)
+HST_set(DELTA_NR_ZONE_INACTIVE_FILE, nr_zone_inactive_file)
+HST_set(DELTA_NR_ZONE_UNEVICTABLE, nr_zone_unevictable)
+HST_set(DELTA_NR_ZONE_WRITE_PENDING, nr_zone_write_pending)
+HST_set(DELTA_NR_ZSPAGES, nr_zspages)
+HST_set(DELTA_NUMA_FOREIGN, numa_foreign)
+HST_set(DELTA_NUMA_HINT_FAULTS, numa_hint_faults)
+HST_set(DELTA_NUMA_HINT_FAULTS_LOCAL, numa_hint_faults_local)
+HST_set(DELTA_NUMA_HIT, numa_hit)
+HST_set(DELTA_NUMA_HUGE_PTE_UPDATES, numa_huge_pte_updates)
+HST_set(DELTA_NUMA_INTERLEAVE, numa_interleave)
+HST_set(DELTA_NUMA_LOCAL, numa_local)
+HST_set(DELTA_NUMA_MISS, numa_miss)
+HST_set(DELTA_NUMA_OTHER, numa_other)
+HST_set(DELTA_NUMA_PAGES_MIGRATED, numa_pages_migrated)
+HST_set(DELTA_NUMA_PTE_UPDATES, numa_pte_updates)
+HST_set(DELTA_OOM_KILL, oom_kill)
+HST_set(DELTA_PAGEOUTRUN, pageoutrun)
+HST_set(DELTA_PGACTIVATE, pgactivate)
+HST_set(DELTA_PGALLOC_DMA, pgalloc_dma)
+HST_set(DELTA_PGALLOC_DMA32, pgalloc_dma32)
+HST_set(DELTA_PGALLOC_HIGH, pgalloc_high)
+HST_set(DELTA_PGALLOC_MOVABLE, pgalloc_movable)
+HST_set(DELTA_PGALLOC_NORMAL, pgalloc_normal)
+HST_set(DELTA_PGDEACTIVATE, pgdeactivate)
+HST_set(DELTA_PGFAULT, pgfault)
+HST_set(DELTA_PGFREE, pgfree)
+HST_set(DELTA_PGINODESTEAL, pginodesteal)
+HST_set(DELTA_PGLAZYFREE, pglazyfree)
+HST_set(DELTA_PGLAZYFREED, pglazyfreed)
+HST_set(DELTA_PGMAJFAULT, pgmajfault)
+HST_set(DELTA_PGMIGRATE_FAIL, pgmigrate_fail)
+HST_set(DELTA_PGMIGRATE_SUCCESS, pgmigrate_success)
+HST_set(DELTA_PGPGIN, pgpgin)
+HST_set(DELTA_PGPGOUT, pgpgout)
+HST_set(DELTA_PGREFILL, pgrefill)
+HST_set(DELTA_PGROTATED, pgrotated)
+HST_set(DELTA_PGSCAN_ANON, pgscan_anon)
+HST_set(DELTA_PGSCAN_DIRECT, pgscan_direct)
+HST_set(DELTA_PGSCAN_DIRECT_THROTTLE, pgscan_direct_throttle)
+HST_set(DELTA_PGSCAN_FILE, pgscan_file)
+HST_set(DELTA_PGSCAN_KSWAPD, pgscan_kswapd)
+HST_set(DELTA_PGSKIP_DMA, pgskip_dma)
+HST_set(DELTA_PGSKIP_DMA32, pgskip_dma32)
+HST_set(DELTA_PGSKIP_HIGH, pgskip_high)
+HST_set(DELTA_PGSKIP_MOVABLE, pgskip_movable)
+HST_set(DELTA_PGSKIP_NORMAL, pgskip_normal)
+HST_set(DELTA_PGSTEAL_ANON, pgsteal_anon)
+HST_set(DELTA_PGSTEAL_DIRECT, pgsteal_direct)
+HST_set(DELTA_PGSTEAL_FILE, pgsteal_file)
+HST_set(DELTA_PGSTEAL_KSWAPD, pgsteal_kswapd)
+HST_set(DELTA_PSWPIN, pswpin)
+HST_set(DELTA_PSWPOUT, pswpout)
+HST_set(DELTA_SLABS_SCANNED, slabs_scanned)
+HST_set(DELTA_SWAP_RA, swap_ra)
+HST_set(DELTA_SWAP_RA_HIT, swap_ra_hit)
+HST_set(DELTA_THP_COLLAPSE_ALLOC, thp_collapse_alloc)
+HST_set(DELTA_THP_COLLAPSE_ALLOC_FAILED, thp_collapse_alloc_failed)
+HST_set(DELTA_THP_DEFERRED_SPLIT_PAGE, thp_deferred_split_page)
+HST_set(DELTA_THP_FAULT_ALLOC, thp_fault_alloc)
+HST_set(DELTA_THP_FAULT_FALLBACK, thp_fault_fallback)
+HST_set(DELTA_THP_FAULT_FALLBACK_CHARGE, thp_fault_fallback_charge)
+HST_set(DELTA_THP_FILE_ALLOC, thp_file_alloc)
+HST_set(DELTA_THP_FILE_FALLBACK, thp_file_fallback)
+HST_set(DELTA_THP_FILE_FALLBACK_CHARGE, thp_file_fallback_charge)
+HST_set(DELTA_THP_FILE_MAPPED, thp_file_mapped)
+HST_set(DELTA_THP_SPLIT_PAGE, thp_split_page)
+HST_set(DELTA_THP_SPLIT_PAGE_FAILED, thp_split_page_failed)
+HST_set(DELTA_THP_SPLIT_PMD, thp_split_pmd)
+HST_set(DELTA_THP_SPLIT_PUD, thp_split_pud)
+HST_set(DELTA_THP_SWPOUT, thp_swpout)
+HST_set(DELTA_THP_SWPOUT_FALLBACK, thp_swpout_fallback)
+HST_set(DELTA_THP_ZERO_PAGE_ALLOC, thp_zero_page_alloc)
+HST_set(DELTA_THP_ZERO_PAGE_ALLOC_FAILED, thp_zero_page_alloc_failed)
+HST_set(DELTA_UNEVICTABLE_PGS_CLEARED, unevictable_pgs_cleared)
+HST_set(DELTA_UNEVICTABLE_PGS_CULLED, unevictable_pgs_culled)
+HST_set(DELTA_UNEVICTABLE_PGS_MLOCKED, unevictable_pgs_mlocked)
+HST_set(DELTA_UNEVICTABLE_PGS_MUNLOCKED, unevictable_pgs_munlocked)
+HST_set(DELTA_UNEVICTABLE_PGS_RESCUED, unevictable_pgs_rescued)
+HST_set(DELTA_UNEVICTABLE_PGS_SCANNED, unevictable_pgs_scanned)
+HST_set(DELTA_UNEVICTABLE_PGS_STRANDED, unevictable_pgs_stranded)
+HST_set(DELTA_WORKINGSET_ACTIVATE, workingset_activate)
+HST_set(DELTA_WORKINGSET_NODERECLAIM, workingset_nodereclaim)
+HST_set(DELTA_WORKINGSET_NODES, workingset_nodes)
+HST_set(DELTA_WORKINGSET_REFAULT, workingset_refault)
+HST_set(DELTA_WORKINGSET_RESTORE, workingset_restore)
+HST_set(DELTA_ZONE_RECLAIM_FAILED, zone_reclaim_failed)
+
+#undef setDECL
+#undef REG_set
+#undef HST_set
+
+
+// ___ Controlling Table ||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
+typedef void (*SET_t)(struct vmstat_result *, struct vmstat_hist *);
+#ifdef ITEMTABLE_DEBUG
+#define RS(e) (SET_t)setNAME(e), VMSTAT_ ## e, STRINGIFY(VMSTAT_ ## e)
+#else
+#define RS(e) (SET_t)setNAME(e)
+#endif
+
+#define TS(t) STRINGIFY(t)
+#define TS_noop ""
+
+ /*
+ * Need it be said?
+ * This table must be kept in the exact same order as
+ * those 'enum vmstat_item' guys ! */
+static struct {
+ SET_t setsfunc; // the actual result setting routine
+#ifdef ITEMTABLE_DEBUG
+ int enumnumb; // enumerator (must match position!)
+ char *enum2str; // enumerator name as a char* string
+#endif
+ char *type2str; // the result type as a string value
+} Item_table[] = {
+/* setsfunc type2str
+ ----------------------------------------- ---------- */
+ { RS(noop), TS_noop },
+ { RS(extra), TS_noop },
+
+ { RS(ALLOCSTALL_DMA), TS(ul_int) },
+ { RS(ALLOCSTALL_DMA32), TS(ul_int) },
+ { RS(ALLOCSTALL_HIGH), TS(ul_int) },
+ { RS(ALLOCSTALL_MOVABLE), TS(ul_int) },
+ { RS(ALLOCSTALL_NORMAL), TS(ul_int) },
+ { RS(BALLOON_DEFLATE), TS(ul_int) },
+ { RS(BALLOON_INFLATE), TS(ul_int) },
+ { RS(BALLOON_MIGRATE), TS(ul_int) },
+ { RS(COMPACT_DAEMON_FREE_SCANNED), TS(ul_int) },
+ { RS(COMPACT_DAEMON_MIGRATE_SCANNED), TS(ul_int) },
+ { RS(COMPACT_DAEMON_WAKE), TS(ul_int) },
+ { RS(COMPACT_FAIL), TS(ul_int) },
+ { RS(COMPACT_FREE_SCANNED), TS(ul_int) },
+ { RS(COMPACT_ISOLATED), TS(ul_int) },
+ { RS(COMPACT_MIGRATE_SCANNED), TS(ul_int) },
+ { RS(COMPACT_STALL), TS(ul_int) },
+ { RS(COMPACT_SUCCESS), TS(ul_int) },
+ { RS(DROP_PAGECACHE), TS(ul_int) },
+ { RS(DROP_SLAB), TS(ul_int) },
+ { RS(HTLB_BUDDY_ALLOC_FAIL), TS(ul_int) },
+ { RS(HTLB_BUDDY_ALLOC_SUCCESS), TS(ul_int) },
+ { RS(KSWAPD_HIGH_WMARK_HIT_QUICKLY), TS(ul_int) },
+ { RS(KSWAPD_INODESTEAL), TS(ul_int) },
+ { RS(KSWAPD_LOW_WMARK_HIT_QUICKLY), TS(ul_int) },
+ { RS(NR_ACTIVE_ANON), TS(ul_int) },
+ { RS(NR_ACTIVE_FILE), TS(ul_int) },
+ { RS(NR_ANON_PAGES), TS(ul_int) },
+ { RS(NR_ANON_TRANSPARENT_HUGEPAGES), TS(ul_int) },
+ { RS(NR_BOUNCE), TS(ul_int) },
+ { RS(NR_DIRTIED), TS(ul_int) },
+ { RS(NR_DIRTY), TS(ul_int) },
+ { RS(NR_DIRTY_BACKGROUND_THRESHOLD), TS(ul_int) },
+ { RS(NR_DIRTY_THRESHOLD), TS(ul_int) },
+ { RS(NR_FILE_HUGEPAGES), TS(ul_int) },
+ { RS(NR_FILE_PAGES), TS(ul_int) },
+ { RS(NR_FILE_PMDMAPPED), TS(ul_int) },
+ { RS(NR_FOLL_PIN_ACQUIRED), TS(ul_int) },
+ { RS(NR_FOLL_PIN_RELEASED), TS(ul_int) },
+ { RS(NR_FREE_CMA), TS(ul_int) },
+ { RS(NR_FREE_PAGES), TS(ul_int) },
+ { RS(NR_INACTIVE_ANON), TS(ul_int) },
+ { RS(NR_INACTIVE_FILE), TS(ul_int) },
+ { RS(NR_ISOLATED_ANON), TS(ul_int) },
+ { RS(NR_ISOLATED_FILE), TS(ul_int) },
+ { RS(NR_KERNEL_MISC_RECLAIMABLE), TS(ul_int) },
+ { RS(NR_KERNEL_STACK), TS(ul_int) },
+ { RS(NR_MAPPED), TS(ul_int) },
+ { RS(NR_MLOCK), TS(ul_int) },
+ { RS(NR_PAGE_TABLE_PAGES), TS(ul_int) },
+ { RS(NR_SHADOW_CALL_STACK), TS(ul_int) },
+ { RS(NR_SHMEM), TS(ul_int) },
+ { RS(NR_SHMEM_HUGEPAGES), TS(ul_int) },
+ { RS(NR_SHMEM_PMDMAPPED), TS(ul_int) },
+ { RS(NR_SLAB_RECLAIMABLE), TS(ul_int) },
+ { RS(NR_SLAB_UNRECLAIMABLE), TS(ul_int) },
+ { RS(NR_UNEVICTABLE), TS(ul_int) },
+ { RS(NR_UNSTABLE), TS(ul_int) },
+ { RS(NR_VMSCAN_IMMEDIATE_RECLAIM), TS(ul_int) },
+ { RS(NR_VMSCAN_WRITE), TS(ul_int) },
+ { RS(NR_WRITEBACK), TS(ul_int) },
+ { RS(NR_WRITEBACK_TEMP), TS(ul_int) },
+ { RS(NR_WRITTEN), TS(ul_int) },
+ { RS(NR_ZONE_ACTIVE_ANON), TS(ul_int) },
+ { RS(NR_ZONE_ACTIVE_FILE), TS(ul_int) },
+ { RS(NR_ZONE_INACTIVE_ANON), TS(ul_int) },
+ { RS(NR_ZONE_INACTIVE_FILE), TS(ul_int) },
+ { RS(NR_ZONE_UNEVICTABLE), TS(ul_int) },
+ { RS(NR_ZONE_WRITE_PENDING), TS(ul_int) },
+ { RS(NR_ZSPAGES), TS(ul_int) },
+ { RS(NUMA_FOREIGN), TS(ul_int) },
+ { RS(NUMA_HINT_FAULTS), TS(ul_int) },
+ { RS(NUMA_HINT_FAULTS_LOCAL), TS(ul_int) },
+ { RS(NUMA_HIT), TS(ul_int) },
+ { RS(NUMA_HUGE_PTE_UPDATES), TS(ul_int) },
+ { RS(NUMA_INTERLEAVE), TS(ul_int) },
+ { RS(NUMA_LOCAL), TS(ul_int) },
+ { RS(NUMA_MISS), TS(ul_int) },
+ { RS(NUMA_OTHER), TS(ul_int) },
+ { RS(NUMA_PAGES_MIGRATED), TS(ul_int) },
+ { RS(NUMA_PTE_UPDATES), TS(ul_int) },
+ { RS(OOM_KILL), TS(ul_int) },
+ { RS(PAGEOUTRUN), TS(ul_int) },
+ { RS(PGACTIVATE), TS(ul_int) },
+ { RS(PGALLOC_DMA), TS(ul_int) },
+ { RS(PGALLOC_DMA32), TS(ul_int) },
+ { RS(PGALLOC_HIGH), TS(ul_int) },
+ { RS(PGALLOC_MOVABLE), TS(ul_int) },
+ { RS(PGALLOC_NORMAL), TS(ul_int) },
+ { RS(PGDEACTIVATE), TS(ul_int) },
+ { RS(PGFAULT), TS(ul_int) },
+ { RS(PGFREE), TS(ul_int) },
+ { RS(PGINODESTEAL), TS(ul_int) },
+ { RS(PGLAZYFREE), TS(ul_int) },
+ { RS(PGLAZYFREED), TS(ul_int) },
+ { RS(PGMAJFAULT), TS(ul_int) },
+ { RS(PGMIGRATE_FAIL), TS(ul_int) },
+ { RS(PGMIGRATE_SUCCESS), TS(ul_int) },
+ { RS(PGPGIN), TS(ul_int) },
+ { RS(PGPGOUT), TS(ul_int) },
+ { RS(PGREFILL), TS(ul_int) },
+ { RS(PGROTATED), TS(ul_int) },
+ { RS(PGSCAN_ANON), TS(ul_int) },
+ { RS(PGSCAN_DIRECT), TS(ul_int) },
+ { RS(PGSCAN_DIRECT_THROTTLE), TS(ul_int) },
+ { RS(PGSCAN_FILE), TS(ul_int) },
+ { RS(PGSCAN_KSWAPD), TS(ul_int) },
+ { RS(PGSKIP_DMA), TS(ul_int) },
+ { RS(PGSKIP_DMA32), TS(ul_int) },
+ { RS(PGSKIP_HIGH), TS(ul_int) },
+ { RS(PGSKIP_MOVABLE), TS(ul_int) },
+ { RS(PGSKIP_NORMAL), TS(ul_int) },
+ { RS(PGSTEAL_ANON), TS(ul_int) },
+ { RS(PGSTEAL_DIRECT), TS(ul_int) },
+ { RS(PGSTEAL_FILE), TS(ul_int) },
+ { RS(PGSTEAL_KSWAPD), TS(ul_int) },
+ { RS(PSWPIN), TS(ul_int) },
+ { RS(PSWPOUT), TS(ul_int) },
+ { RS(SLABS_SCANNED), TS(ul_int) },
+ { RS(SWAP_RA), TS(ul_int) },
+ { RS(SWAP_RA_HIT), TS(ul_int) },
+ { RS(THP_COLLAPSE_ALLOC), TS(ul_int) },
+ { RS(THP_COLLAPSE_ALLOC_FAILED), TS(ul_int) },
+ { RS(THP_DEFERRED_SPLIT_PAGE), TS(ul_int) },
+ { RS(THP_FAULT_ALLOC), TS(ul_int) },
+ { RS(THP_FAULT_FALLBACK), TS(ul_int) },
+ { RS(THP_FAULT_FALLBACK_CHARGE), TS(ul_int) },
+ { RS(THP_FILE_ALLOC), TS(ul_int) },
+ { RS(THP_FILE_FALLBACK), TS(ul_int) },
+ { RS(THP_FILE_FALLBACK_CHARGE), TS(ul_int) },
+ { RS(THP_FILE_MAPPED), TS(ul_int) },
+ { RS(THP_SPLIT_PAGE), TS(ul_int) },
+ { RS(THP_SPLIT_PAGE_FAILED), TS(ul_int) },
+ { RS(THP_SPLIT_PMD), TS(ul_int) },
+ { RS(THP_SPLIT_PUD), TS(ul_int) },
+ { RS(THP_SWPOUT), TS(ul_int) },
+ { RS(THP_SWPOUT_FALLBACK), TS(ul_int) },
+ { RS(THP_ZERO_PAGE_ALLOC), TS(ul_int) },
+ { RS(THP_ZERO_PAGE_ALLOC_FAILED), TS(ul_int) },
+ { RS(UNEVICTABLE_PGS_CLEARED), TS(ul_int) },
+ { RS(UNEVICTABLE_PGS_CULLED), TS(ul_int) },
+ { RS(UNEVICTABLE_PGS_MLOCKED), TS(ul_int) },
+ { RS(UNEVICTABLE_PGS_MUNLOCKED), TS(ul_int) },
+ { RS(UNEVICTABLE_PGS_RESCUED), TS(ul_int) },
+ { RS(UNEVICTABLE_PGS_SCANNED), TS(ul_int) },
+ { RS(UNEVICTABLE_PGS_STRANDED), TS(ul_int) },
+ { RS(WORKINGSET_ACTIVATE), TS(ul_int) },
+ { RS(WORKINGSET_NODERECLAIM), TS(ul_int) },
+ { RS(WORKINGSET_NODES), TS(ul_int) },
+ { RS(WORKINGSET_REFAULT), TS(ul_int) },
+ { RS(WORKINGSET_RESTORE), TS(ul_int) },
+ { RS(ZONE_RECLAIM_FAILED), TS(ul_int) },
+
+ { RS(DELTA_ALLOCSTALL_DMA), TS(sl_int) },
+ { RS(DELTA_ALLOCSTALL_DMA32), TS(sl_int) },
+ { RS(DELTA_ALLOCSTALL_HIGH), TS(sl_int) },
+ { RS(DELTA_ALLOCSTALL_MOVABLE), TS(sl_int) },
+ { RS(DELTA_ALLOCSTALL_NORMAL), TS(sl_int) },
+ { RS(DELTA_BALLOON_DEFLATE), TS(sl_int) },
+ { RS(DELTA_BALLOON_INFLATE), TS(sl_int) },
+ { RS(DELTA_BALLOON_MIGRATE), TS(sl_int) },
+ { RS(DELTA_COMPACT_DAEMON_FREE_SCANNED), TS(sl_int) },
+ { RS(DELTA_COMPACT_DAEMON_MIGRATE_SCANNED), TS(sl_int) },
+ { RS(DELTA_COMPACT_DAEMON_WAKE), TS(sl_int) },
+ { RS(DELTA_COMPACT_FAIL), TS(sl_int) },
+ { RS(DELTA_COMPACT_FREE_SCANNED), TS(sl_int) },
+ { RS(DELTA_COMPACT_ISOLATED), TS(sl_int) },
+ { RS(DELTA_COMPACT_MIGRATE_SCANNED), TS(sl_int) },
+ { RS(DELTA_COMPACT_STALL), TS(sl_int) },
+ { RS(DELTA_COMPACT_SUCCESS), TS(sl_int) },
+ { RS(DELTA_DROP_PAGECACHE), TS(sl_int) },
+ { RS(DELTA_DROP_SLAB), TS(sl_int) },
+ { RS(DELTA_HTLB_BUDDY_ALLOC_FAIL), TS(sl_int) },
+ { RS(DELTA_HTLB_BUDDY_ALLOC_SUCCESS), TS(sl_int) },
+ { RS(DELTA_KSWAPD_HIGH_WMARK_HIT_QUICKLY), TS(sl_int) },
+ { RS(DELTA_KSWAPD_INODESTEAL), TS(sl_int) },
+ { RS(DELTA_KSWAPD_LOW_WMARK_HIT_QUICKLY), TS(sl_int) },
+ { RS(DELTA_NR_ACTIVE_ANON), TS(sl_int) },
+ { RS(DELTA_NR_ACTIVE_FILE), TS(sl_int) },
+ { RS(DELTA_NR_ANON_PAGES), TS(sl_int) },
+ { RS(DELTA_NR_ANON_TRANSPARENT_HUGEPAGES), TS(sl_int) },
+ { RS(DELTA_NR_BOUNCE), TS(sl_int) },
+ { RS(DELTA_NR_DIRTIED), TS(sl_int) },
+ { RS(DELTA_NR_DIRTY), TS(sl_int) },
+ { RS(DELTA_NR_DIRTY_BACKGROUND_THRESHOLD), TS(sl_int) },
+ { RS(DELTA_NR_DIRTY_THRESHOLD), TS(sl_int) },
+ { RS(DELTA_NR_FILE_HUGEPAGES), TS(sl_int) },
+ { RS(DELTA_NR_FILE_PAGES), TS(sl_int) },
+ { RS(DELTA_NR_FILE_PMDMAPPED), TS(sl_int) },
+ { RS(DELTA_NR_FOLL_PIN_ACQUIRED), TS(sl_int) },
+ { RS(DELTA_NR_FOLL_PIN_RELEASED), TS(sl_int) },
+ { RS(DELTA_NR_FREE_CMA), TS(sl_int) },
+ { RS(DELTA_NR_FREE_PAGES), TS(sl_int) },
+ { RS(DELTA_NR_INACTIVE_ANON), TS(sl_int) },
+ { RS(DELTA_NR_INACTIVE_FILE), TS(sl_int) },
+ { RS(DELTA_NR_ISOLATED_ANON), TS(sl_int) },
+ { RS(DELTA_NR_ISOLATED_FILE), TS(sl_int) },
+ { RS(DELTA_NR_KERNEL_MISC_RECLAIMABLE), TS(sl_int) },
+ { RS(DELTA_NR_KERNEL_STACK), TS(sl_int) },
+ { RS(DELTA_NR_MAPPED), TS(sl_int) },
+ { RS(DELTA_NR_MLOCK), TS(sl_int) },
+ { RS(DELTA_NR_PAGE_TABLE_PAGES), TS(sl_int) },
+ { RS(DELTA_NR_SHADOW_CALL_STACK), TS(sl_int) },
+ { RS(DELTA_NR_SHMEM), TS(sl_int) },
+ { RS(DELTA_NR_SHMEM_HUGEPAGES), TS(sl_int) },
+ { RS(DELTA_NR_SHMEM_PMDMAPPED), TS(sl_int) },
+ { RS(DELTA_NR_SLAB_RECLAIMABLE), TS(sl_int) },
+ { RS(DELTA_NR_SLAB_UNRECLAIMABLE), TS(sl_int) },
+ { RS(DELTA_NR_UNEVICTABLE), TS(sl_int) },
+ { RS(DELTA_NR_UNSTABLE), TS(sl_int) },
+ { RS(DELTA_NR_VMSCAN_IMMEDIATE_RECLAIM), TS(sl_int) },
+ { RS(DELTA_NR_VMSCAN_WRITE), TS(sl_int) },
+ { RS(DELTA_NR_WRITEBACK), TS(sl_int) },
+ { RS(DELTA_NR_WRITEBACK_TEMP), TS(sl_int) },
+ { RS(DELTA_NR_WRITTEN), TS(sl_int) },
+ { RS(DELTA_NR_ZONE_ACTIVE_ANON), TS(sl_int) },
+ { RS(DELTA_NR_ZONE_ACTIVE_FILE), TS(sl_int) },
+ { RS(DELTA_NR_ZONE_INACTIVE_ANON), TS(sl_int) },
+ { RS(DELTA_NR_ZONE_INACTIVE_FILE), TS(sl_int) },
+ { RS(DELTA_NR_ZONE_UNEVICTABLE), TS(sl_int) },
+ { RS(DELTA_NR_ZONE_WRITE_PENDING), TS(sl_int) },
+ { RS(DELTA_NR_ZSPAGES), TS(sl_int) },
+ { RS(DELTA_NUMA_FOREIGN), TS(sl_int) },
+ { RS(DELTA_NUMA_HINT_FAULTS), TS(sl_int) },
+ { RS(DELTA_NUMA_HINT_FAULTS_LOCAL), TS(sl_int) },
+ { RS(DELTA_NUMA_HIT), TS(sl_int) },
+ { RS(DELTA_NUMA_HUGE_PTE_UPDATES), TS(sl_int) },
+ { RS(DELTA_NUMA_INTERLEAVE), TS(sl_int) },
+ { RS(DELTA_NUMA_LOCAL), TS(sl_int) },
+ { RS(DELTA_NUMA_MISS), TS(sl_int) },
+ { RS(DELTA_NUMA_OTHER), TS(sl_int) },
+ { RS(DELTA_NUMA_PAGES_MIGRATED), TS(sl_int) },
+ { RS(DELTA_NUMA_PTE_UPDATES), TS(sl_int) },
+ { RS(DELTA_OOM_KILL), TS(sl_int) },
+ { RS(DELTA_PAGEOUTRUN), TS(sl_int) },
+ { RS(DELTA_PGACTIVATE), TS(sl_int) },
+ { RS(DELTA_PGALLOC_DMA), TS(sl_int) },
+ { RS(DELTA_PGALLOC_DMA32), TS(sl_int) },
+ { RS(DELTA_PGALLOC_HIGH), TS(sl_int) },
+ { RS(DELTA_PGALLOC_MOVABLE), TS(sl_int) },
+ { RS(DELTA_PGALLOC_NORMAL), TS(sl_int) },
+ { RS(DELTA_PGDEACTIVATE), TS(sl_int) },
+ { RS(DELTA_PGFAULT), TS(sl_int) },
+ { RS(DELTA_PGFREE), TS(sl_int) },
+ { RS(DELTA_PGINODESTEAL), TS(sl_int) },
+ { RS(DELTA_PGLAZYFREE), TS(sl_int) },
+ { RS(DELTA_PGLAZYFREED), TS(sl_int) },
+ { RS(DELTA_PGMAJFAULT), TS(sl_int) },
+ { RS(DELTA_PGMIGRATE_FAIL), TS(sl_int) },
+ { RS(DELTA_PGMIGRATE_SUCCESS), TS(sl_int) },
+ { RS(DELTA_PGPGIN), TS(sl_int) },
+ { RS(DELTA_PGPGOUT), TS(sl_int) },
+ { RS(DELTA_PGREFILL), TS(sl_int) },
+ { RS(DELTA_PGROTATED), TS(sl_int) },
+ { RS(DELTA_PGSCAN_ANON), TS(sl_int) },
+ { RS(DELTA_PGSCAN_DIRECT), TS(sl_int) },
+ { RS(DELTA_PGSCAN_DIRECT_THROTTLE), TS(sl_int) },
+ { RS(DELTA_PGSCAN_FILE), TS(sl_int) },
+ { RS(DELTA_PGSCAN_KSWAPD), TS(sl_int) },
+ { RS(DELTA_PGSKIP_DMA), TS(sl_int) },
+ { RS(DELTA_PGSKIP_DMA32), TS(sl_int) },
+ { RS(DELTA_PGSKIP_HIGH), TS(sl_int) },
+ { RS(DELTA_PGSKIP_MOVABLE), TS(sl_int) },
+ { RS(DELTA_PGSKIP_NORMAL), TS(sl_int) },
+ { RS(DELTA_PGSTEAL_ANON), TS(sl_int) },
+ { RS(DELTA_PGSTEAL_DIRECT), TS(sl_int) },
+ { RS(DELTA_PGSTEAL_FILE), TS(sl_int) },
+ { RS(DELTA_PGSTEAL_KSWAPD), TS(sl_int) },
+ { RS(DELTA_PSWPIN), TS(sl_int) },
+ { RS(DELTA_PSWPOUT), TS(sl_int) },
+ { RS(DELTA_SLABS_SCANNED), TS(sl_int) },
+ { RS(DELTA_SWAP_RA), TS(sl_int) },
+ { RS(DELTA_SWAP_RA_HIT), TS(sl_int) },
+ { RS(DELTA_THP_COLLAPSE_ALLOC), TS(sl_int) },
+ { RS(DELTA_THP_COLLAPSE_ALLOC_FAILED), TS(sl_int) },
+ { RS(DELTA_THP_DEFERRED_SPLIT_PAGE), TS(sl_int) },
+ { RS(DELTA_THP_FAULT_ALLOC), TS(sl_int) },
+ { RS(DELTA_THP_FAULT_FALLBACK), TS(sl_int) },
+ { RS(DELTA_THP_FAULT_FALLBACK_CHARGE), TS(sl_int) },
+ { RS(DELTA_THP_FILE_ALLOC), TS(sl_int) },
+ { RS(DELTA_THP_FILE_FALLBACK), TS(sl_int) },
+ { RS(DELTA_THP_FILE_FALLBACK_CHARGE), TS(sl_int) },
+ { RS(DELTA_THP_FILE_MAPPED), TS(sl_int) },
+ { RS(DELTA_THP_SPLIT_PAGE), TS(sl_int) },
+ { RS(DELTA_THP_SPLIT_PAGE_FAILED), TS(sl_int) },
+ { RS(DELTA_THP_SPLIT_PMD), TS(sl_int) },
+ { RS(DELTA_THP_SPLIT_PUD), TS(sl_int) },
+ { RS(DELTA_THP_SWPOUT), TS(sl_int) },
+ { RS(DELTA_THP_SWPOUT_FALLBACK), TS(sl_int) },
+ { RS(DELTA_THP_ZERO_PAGE_ALLOC), TS(sl_int) },
+ { RS(DELTA_THP_ZERO_PAGE_ALLOC_FAILED), TS(sl_int) },
+ { RS(DELTA_UNEVICTABLE_PGS_CLEARED), TS(sl_int) },
+ { RS(DELTA_UNEVICTABLE_PGS_CULLED), TS(sl_int) },
+ { RS(DELTA_UNEVICTABLE_PGS_MLOCKED), TS(sl_int) },
+ { RS(DELTA_UNEVICTABLE_PGS_MUNLOCKED), TS(sl_int) },
+ { RS(DELTA_UNEVICTABLE_PGS_RESCUED), TS(sl_int) },
+ { RS(DELTA_UNEVICTABLE_PGS_SCANNED), TS(sl_int) },
+ { RS(DELTA_UNEVICTABLE_PGS_STRANDED), TS(sl_int) },
+ { RS(DELTA_WORKINGSET_ACTIVATE), TS(sl_int) },
+ { RS(DELTA_WORKINGSET_NODERECLAIM), TS(sl_int) },
+ { RS(DELTA_WORKINGSET_NODES), TS(sl_int) },
+ { RS(DELTA_WORKINGSET_REFAULT), TS(sl_int) },
+ { RS(DELTA_WORKINGSET_RESTORE), TS(sl_int) },
+ { RS(DELTA_ZONE_RECLAIM_FAILED), TS(sl_int) },
+};
+
+ /* please note,
+ * this enum MUST be 1 greater than the highest value of any enum */
+enum vmstat_item VMSTAT_logical_end = MAXTABLE(Item_table);
+
+#undef setNAME
+#undef RS
+
+
+// ___ Private Functions ||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
+static inline void vmstat_assign_results (
+ struct vmstat_stack *stack,
+ struct vmstat_hist *hist)
+{
+ struct vmstat_result *this = stack->head;
+
+ for (;;) {
+ enum vmstat_item item = this->item;
+ if (item >= VMSTAT_logical_end)
+ break;
+ Item_table[item].setsfunc(this, hist);
+ ++this;
+ }
+ return;
+} // end: vmstat_assign_results
+
+
+static void vmstat_extents_free_all (
+ struct vmstat_info *info)
+{
+ while (info->extents) {
+ struct stacks_extent *p = info->extents;
+ info->extents = info->extents->next;
+ free(p);
+ };
+} // end: vmstat_extents_free_all
+
+
+static inline struct vmstat_result *vmstat_itemize_stack (
+ struct vmstat_result *p,
+ int depth,
+ enum vmstat_item *items)
+{
+ struct vmstat_result *p_sav = p;
+ int i;
+
+ for (i = 0; i < depth; i++) {
+ p->item = items[i];
+ ++p;
+ }
+ return p_sav;
+} // end: vmstat_itemize_stack
+
+
+static inline int vmstat_items_check_failed (
+ int numitems,
+ enum vmstat_item *items)
+{
+ int i;
+
+ /* if an enum is passed instead of an address of one or more enums, ol' gcc
+ * will silently convert it to an address (possibly NULL). only clang will
+ * offer any sort of warning like the following:
+ *
+ * warning: incompatible integer to pointer conversion passing 'int' to parameter of type 'enum vmstat_item *'
+ * my_stack = procps_vmstat_select(info, VMSTAT_noop, num);
+ * ^~~~~~~~~~~~~~~~
+ */
+ if (numitems < 1
+ || (void *)items < (void *)(unsigned long)(2 * VMSTAT_logical_end))
+ return 1;
+
+ for (i = 0; i < numitems; i++) {
+ // a vmstat_item is currently unsigned, but we'll protect our future
+ if (items[i] < 0)
+ return 1;
+ if (items[i] >= VMSTAT_logical_end)
+ return 1;
+ }
+
+ return 0;
+} // end: vmstat_items_check_failed
+
+
+static int vmstat_make_hash_failed (
+ struct vmstat_info *info)
+{
+ #define htVAL(f) e.key = STRINGIFY(f); e.data = &info->hist.new. f; \
+ if (!hsearch_r(e, ENTER, &ep, &info->hashtab)) return 1;
+ ENTRY e, *ep;
+ size_t n;
+
+ n = sizeof(struct vmstat_data) / sizeof(unsigned long);
+ // we'll follow the hsearch recommendation of an extra 25%
+ if (!hcreate_r(n + (n / 4), &info->hashtab))
+ return 1;
+
+ htVAL(allocstall_dma)
+ htVAL(allocstall_dma32)
+ htVAL(allocstall_high)
+ htVAL(allocstall_movable)
+ htVAL(allocstall_normal)
+ htVAL(balloon_deflate)
+ htVAL(balloon_inflate)
+ htVAL(balloon_migrate)
+ htVAL(compact_daemon_free_scanned)
+ htVAL(compact_daemon_migrate_scanned)
+ htVAL(compact_daemon_wake)
+ htVAL(compact_fail)
+ htVAL(compact_free_scanned)
+ htVAL(compact_isolated)
+ htVAL(compact_migrate_scanned)
+ htVAL(compact_stall)
+ htVAL(compact_success)
+ htVAL(drop_pagecache)
+ htVAL(drop_slab)
+ htVAL(htlb_buddy_alloc_fail)
+ htVAL(htlb_buddy_alloc_success)
+ htVAL(kswapd_high_wmark_hit_quickly)
+ htVAL(kswapd_inodesteal)
+ htVAL(kswapd_low_wmark_hit_quickly)
+ htVAL(nr_active_anon)
+ htVAL(nr_active_file)
+ htVAL(nr_anon_pages)
+ htVAL(nr_anon_transparent_hugepages)
+ htVAL(nr_bounce)
+ htVAL(nr_dirtied)
+ htVAL(nr_dirty)
+ htVAL(nr_dirty_background_threshold)
+ htVAL(nr_dirty_threshold)
+ htVAL(nr_file_hugepages)
+ htVAL(nr_file_pages)
+ htVAL(nr_file_pmdmapped)
+ htVAL(nr_foll_pin_acquired)
+ htVAL(nr_foll_pin_released)
+ htVAL(nr_free_cma)
+ htVAL(nr_free_pages)
+ htVAL(nr_inactive_anon)
+ htVAL(nr_inactive_file)
+ htVAL(nr_isolated_anon)
+ htVAL(nr_isolated_file)
+ htVAL(nr_kernel_misc_reclaimable)
+ htVAL(nr_kernel_stack)
+ htVAL(nr_mapped)
+ htVAL(nr_mlock)
+ htVAL(nr_page_table_pages)
+ htVAL(nr_shadow_call_stack)
+ htVAL(nr_shmem)
+ htVAL(nr_shmem_hugepages)
+ htVAL(nr_shmem_pmdmapped)
+ htVAL(nr_slab_reclaimable)
+ htVAL(nr_slab_unreclaimable)
+ htVAL(nr_unevictable)
+ htVAL(nr_unstable)
+ htVAL(nr_vmscan_immediate_reclaim)
+ htVAL(nr_vmscan_write)
+ htVAL(nr_writeback)
+ htVAL(nr_writeback_temp)
+ htVAL(nr_written)
+ htVAL(nr_zone_active_anon)
+ htVAL(nr_zone_active_file)
+ htVAL(nr_zone_inactive_anon)
+ htVAL(nr_zone_inactive_file)
+ htVAL(nr_zone_unevictable)
+ htVAL(nr_zone_write_pending)
+ htVAL(nr_zspages)
+ htVAL(numa_foreign)
+ htVAL(numa_hint_faults)
+ htVAL(numa_hint_faults_local)
+ htVAL(numa_hit)
+ htVAL(numa_huge_pte_updates)
+ htVAL(numa_interleave)
+ htVAL(numa_local)
+ htVAL(numa_miss)
+ htVAL(numa_other)
+ htVAL(numa_pages_migrated)
+ htVAL(numa_pte_updates)
+ htVAL(oom_kill)
+ htVAL(pageoutrun)
+ htVAL(pgactivate)
+ htVAL(pgalloc_dma)
+ htVAL(pgalloc_dma32)
+ htVAL(pgalloc_high)
+ htVAL(pgalloc_movable)
+ htVAL(pgalloc_normal)
+ htVAL(pgdeactivate)
+ htVAL(pgfault)
+ htVAL(pgfree)
+ htVAL(pginodesteal)
+ htVAL(pglazyfree)
+ htVAL(pglazyfreed)
+ htVAL(pgmajfault)
+ htVAL(pgmigrate_fail)
+ htVAL(pgmigrate_success)
+ htVAL(pgpgin)
+ htVAL(pgpgout)
+ htVAL(pgrefill)
+ htVAL(pgrotated)
+ htVAL(pgscan_anon)
+ htVAL(pgscan_direct)
+ htVAL(pgscan_direct_throttle)
+ htVAL(pgscan_file)
+ htVAL(pgscan_kswapd)
+ htVAL(pgskip_dma)
+ htVAL(pgskip_dma32)
+ htVAL(pgskip_high)
+ htVAL(pgskip_movable)
+ htVAL(pgskip_normal)
+ htVAL(pgsteal_anon)
+ htVAL(pgsteal_direct)
+ htVAL(pgsteal_file)
+ htVAL(pgsteal_kswapd)
+ htVAL(pswpin)
+ htVAL(pswpout)
+ htVAL(slabs_scanned)
+ htVAL(swap_ra)
+ htVAL(swap_ra_hit)
+ htVAL(thp_collapse_alloc)
+ htVAL(thp_collapse_alloc_failed)
+ htVAL(thp_deferred_split_page)
+ htVAL(thp_fault_alloc)
+ htVAL(thp_fault_fallback)
+ htVAL(thp_fault_fallback_charge)
+ htVAL(thp_file_alloc)
+ htVAL(thp_file_fallback)
+ htVAL(thp_file_fallback_charge)
+ htVAL(thp_file_mapped)
+ htVAL(thp_split_page)
+ htVAL(thp_split_page_failed)
+ htVAL(thp_split_pmd)
+ htVAL(thp_split_pud)
+ htVAL(thp_swpout)
+ htVAL(thp_swpout_fallback)
+ htVAL(thp_zero_page_alloc)
+ htVAL(thp_zero_page_alloc_failed)
+ htVAL(unevictable_pgs_cleared)
+ htVAL(unevictable_pgs_culled)
+ htVAL(unevictable_pgs_mlocked)
+ htVAL(unevictable_pgs_munlocked)
+ htVAL(unevictable_pgs_rescued)
+ htVAL(unevictable_pgs_scanned)
+ htVAL(unevictable_pgs_stranded)
+ htVAL(workingset_activate)
+ htVAL(workingset_nodereclaim)
+ htVAL(workingset_nodes)
+ htVAL(workingset_refault)
+ htVAL(workingset_restore)
+ htVAL(zone_reclaim_failed)
+
+ return 0;
+ #undef htVAL
+} // end: vmstat_make_hash_failed
+
+
+/*
+ * vmstat_read_failed():
+ *
+ * Read the data out of /proc/vmstat putting the information
+ * into the supplied info structure
+ */
+static int vmstat_read_failed (
+ struct vmstat_info *info)
+{
+ char buf[VMSTAT_BUFF];
+ char *head, *tail;
+ int size;
+ unsigned long *valptr;
+
+ // remember history from last time around
+ memcpy(&info->hist.old, &info->hist.new, sizeof(struct vmstat_data));
+ // clear out the soon to be 'current' values
+ memset(&info->hist.new, 0, sizeof(struct vmstat_data));
+
+#ifndef __CYGWIN__ /* /proc/vmstat does not exist */
+ if (-1 == info->vmstat_fd
+ && (-1 == (info->vmstat_fd = open(VMSTAT_FILE, O_RDONLY))))
+ return 1;
+
+ if (lseek(info->vmstat_fd, 0L, SEEK_SET) == -1)
+ return 1;
+
+ for (;;) {
+ if ((size = read(info->vmstat_fd, buf, sizeof(buf)-1)) < 0) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ return 1;
+ }
+ break;
+ }
+ if (size == 0) {
+ errno = EIO;
+ return 1;
+ }
+ buf[size] = '\0';
+
+ head = buf;
+
+ for (;;) {
+ static __thread ENTRY e; // keep coverity off our backs (e.data)
+ ENTRY *ep;
+
+ if (!(tail = strchr(head, ' ')))
+ break;
+ *tail = '\0';
+ valptr = NULL;
+
+ e.key = head;
+ if (hsearch_r(e, FIND, &ep, &info->hashtab))
+ valptr = ep->data;
+ head = tail + 1;
+ if (valptr)
+ *valptr = strtoul(head, NULL, 10);
+
+ if (!(tail = strchr(head, '\n')))
+ break;
+ head = tail + 1;
+ }
+#endif /* !__CYGWIN__ */
+ return 0;
+} // end: vmstat_read_failed
+
+
+/*
+ * vmstat_stacks_alloc():
+ *
+ * Allocate and initialize one or more stacks each of which is anchored in an
+ * associated context structure.
+ *
+ * All such stacks will have their result structures properly primed with
+ * 'items', while the result itself will be zeroed.
+ *
+ * Returns a stacks_extent struct anchoring the 'heads' of each new stack.
+ */
+static struct stacks_extent *vmstat_stacks_alloc (
+ struct vmstat_info *info,
+ int maxstacks)
+{
+ struct stacks_extent *p_blob;
+ struct vmstat_stack **p_vect;
+ struct vmstat_stack *p_head;
+ size_t vect_size, head_size, list_size, blob_size;
+ void *v_head, *v_list;
+ int i;
+
+ vect_size = sizeof(void *) * maxstacks; // size of the addr vectors |
+ vect_size += sizeof(void *); // plus NULL addr delimiter |
+ head_size = sizeof(struct vmstat_stack); // size of that head struct |
+ list_size = sizeof(struct vmstat_result)*info->numitems; // any single results stack |
+ blob_size = sizeof(struct stacks_extent); // the extent anchor itself |
+ blob_size += vect_size; // plus room for addr vects |
+ blob_size += head_size * maxstacks; // plus room for head thing |
+ blob_size += list_size * maxstacks; // plus room for our stacks |
+
+ /* note: all of our memory is allocated in a single blob, facilitating a later free(). |
+ as a minimum, it is important that the result structures themselves always be |
+ contiguous for every stack since they are accessed through relative position. | */
+ if (NULL == (p_blob = calloc(1, blob_size)))
+ return NULL;
+
+ p_blob->next = info->extents; // push this extent onto... |
+ info->extents = p_blob; // ...some existing extents |
+ p_vect = (void *)p_blob + sizeof(struct stacks_extent); // prime our vector pointer |
+ p_blob->stacks = p_vect; // set actual vectors start |
+ v_head = (void *)p_vect + vect_size; // prime head pointer start |
+ v_list = v_head + (head_size * maxstacks); // prime our stacks pointer |
+
+ for (i = 0; i < maxstacks; i++) {
+ p_head = (struct vmstat_stack *)v_head;
+ p_head->head = vmstat_itemize_stack((struct vmstat_result *)v_list, info->numitems, info->items);
+ p_blob->stacks[i] = p_head;
+ v_list += list_size;
+ v_head += head_size;
+ }
+ p_blob->ext_numstacks = maxstacks;
+ return p_blob;
+} // end: vmstat_stacks_alloc
+
+
+// ___ Public Functions |||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
+// --- standard required functions --------------------------------------------
+
+/*
+ * procps_vmstat_new:
+ *
+ * Create a new container to hold the stat information
+ *
+ * The initial refcount is 1, and needs to be decremented
+ * to release the resources of the structure.
+ *
+ * Returns: < 0 on failure, 0 on success along with
+ * a pointer to a new context struct
+ */
+PROCPS_EXPORT int procps_vmstat_new (
+ struct vmstat_info **info)
+{
+ struct vmstat_info *p;
+
+#ifdef ITEMTABLE_DEBUG
+ int i, failed = 0;
+ for (i = 0; i < MAXTABLE(Item_table); i++) {
+ if (i != Item_table[i].enumnumb) {
+ fprintf(stderr, "%s: enum/table error: Item_table[%d] was %s, but its value is %d\n"
+ , __FILE__, i, Item_table[i].enum2str, Item_table[i].enumnumb);
+ failed = 1;
+ }
+ }
+ if (failed) _Exit(EXIT_FAILURE);
+#endif
+
+ if (info == NULL || *info != NULL)
+ return -EINVAL;
+ if (!(p = calloc(1, sizeof(struct vmstat_info))))
+ return -ENOMEM;
+
+ p->refcount = 1;
+ p->vmstat_fd = -1;
+
+ if (vmstat_make_hash_failed(p)) {
+ free(p);
+ return -errno;
+ }
+
+ /* do a priming read here for the following potential benefits: |
+ 1) ensure there will be no problems with subsequent access |
+ 2) make delta results potentially useful, even if 1st time |
+ 3) elimnate need for history distortions 1st time 'switch' | */
+ if (vmstat_read_failed(p)) {
+ procps_vmstat_unref(&p);
+ return -errno;
+ }
+
+ *info = p;
+ return 0;
+} // end: procps_vmstat_new
+
+
+PROCPS_EXPORT int procps_vmstat_ref (
+ struct vmstat_info *info)
+{
+ if (info == NULL)
+ return -EINVAL;
+
+ info->refcount++;
+ return info->refcount;
+} // end: procps_vmstat_ref
+
+
+PROCPS_EXPORT int procps_vmstat_unref (
+ struct vmstat_info **info)
+{
+ if (info == NULL || *info == NULL)
+ return -EINVAL;
+
+ (*info)->refcount--;
+
+ if ((*info)->refcount < 1) {
+ int errno_sav = errno;
+
+ if ((*info)->vmstat_fd != -1)
+ close((*info)->vmstat_fd);
+
+ if ((*info)->extents)
+ vmstat_extents_free_all((*info));
+ if ((*info)->items)
+ free((*info)->items);
+ hdestroy_r(&(*info)->hashtab);
+
+ free(*info);
+ *info = NULL;
+
+ errno = errno_sav;
+ return 0;
+ }
+ return (*info)->refcount;
+} // end: procps_vmstat_unref
+
+
+// --- variable interface functions -------------------------------------------
+
+PROCPS_EXPORT struct vmstat_result *procps_vmstat_get (
+ struct vmstat_info *info,
+ enum vmstat_item item)
+{
+ time_t cur_secs;
+
+ errno = EINVAL;
+ if (info == NULL)
+ return NULL;
+ if (item < 0 || item >= VMSTAT_logical_end)
+ return NULL;
+ errno = 0;
+
+ /* we will NOT read the vmstat file with every call - rather, we'll offer
+ a granularity of 1 second between reads ... */
+ cur_secs = time(NULL);
+ if (1 <= cur_secs - info->sav_secs) {
+ if (vmstat_read_failed(info))
+ return NULL;
+ info->sav_secs = cur_secs;
+ }
+
+ info->get_this.item = item;
+ // with 'get', we must NOT honor the usual 'noop' guarantee
+ info->get_this.result.ul_int = 0;
+ Item_table[item].setsfunc(&info->get_this, &info->hist);
+
+ return &info->get_this;
+} // end: procps_vmstat_get
+
+
+/* procps_vmstat_select():
+ *
+ * Harvest all the requested /proc/vmstat information then return
+ * it in a results stack.
+ *
+ * Returns: pointer to a vmstat_stack struct on success, NULL on error.
+ */
+PROCPS_EXPORT struct vmstat_stack *procps_vmstat_select (
+ struct vmstat_info *info,
+ enum vmstat_item *items,
+ int numitems)
+{
+ errno = EINVAL;
+ if (info == NULL || items == NULL)
+ return NULL;
+ if (vmstat_items_check_failed(numitems, items))
+ return NULL;
+ errno = 0;
+
+ /* is this the first time or have things changed since we were last called?
+ if so, gotta' redo all of our stacks stuff ... */
+ if (info->numitems != numitems + 1
+ || memcmp(info->items, items, sizeof(enum vmstat_item) * numitems)) {
+ // allow for our VMSTAT_logical_end
+ if (!(info->items = realloc(info->items, sizeof(enum vmstat_item) * (numitems + 1))))
+ return NULL;
+ memcpy(info->items, items, sizeof(enum vmstat_item) * numitems);
+ info->items[numitems] = VMSTAT_logical_end;
+ info->numitems = numitems + 1;
+ if (info->extents)
+ vmstat_extents_free_all(info);
+ }
+ if (!info->extents
+ && (!vmstat_stacks_alloc(info, 1)))
+ return NULL;
+
+ if (vmstat_read_failed(info))
+ return NULL;
+ vmstat_assign_results(info->extents->stacks[0], &info->hist);
+
+ return info->extents->stacks[0];
+} // end: procps_vmstat_select
+
+
+// --- special debugging function(s) ------------------------------------------
+/*
+ * The following isn't part of the normal programming interface. Rather,
+ * it exists to validate result types referenced in application programs.
+ *
+ * It's used only when:
+ * 1) the 'XTRA_PROCPS_DEBUG' has been defined, or
+ * 2) an #include of 'xtra-procps-debug.h' is used
+ */
+
+PROCPS_EXPORT struct vmstat_result *xtra_vmstat_get (
+ struct vmstat_info *info,
+ enum vmstat_item actual_enum,
+ const char *typestr,
+ const char *file,
+ int lineno)
+{
+ struct vmstat_result *r = procps_vmstat_get(info, actual_enum);
+
+ if (actual_enum < 0 || actual_enum >= VMSTAT_logical_end) {
+ fprintf(stderr, "%s line %d: invalid item = %d, type = %s\n"
+ , file, lineno, actual_enum, typestr);
+ }
+ if (r) {
+ char *str = Item_table[r->item].type2str;
+ if (str[0]
+ && (strcmp(typestr, str)))
+ fprintf(stderr, "%s line %d: was %s, expected %s\n", file, lineno, typestr, str);
+ }
+ return r;
+} // end: xtra_vmstat_get_
+
+
+PROCPS_EXPORT struct vmstat_result *xtra_vmstat_val (
+ int relative_enum,
+ const char *typestr,
+ const struct vmstat_stack *stack,
+ struct vmstat_info *info,
+ const char *file,
+ int lineno)
+{
+ char *str;
+ int i;
+
+ for (i = 0; stack->head[i].item < VMSTAT_logical_end; i++)
+ ;
+ if (relative_enum < 0 || relative_enum >= i) {
+ fprintf(stderr, "%s line %d: invalid relative_enum = %d, valid range = 0-%d\n"
+ , file, lineno, relative_enum, i-1);
+ return NULL;
+ }
+ str = Item_table[stack->head[relative_enum].item].type2str;
+ if (str[0]
+ && (strcmp(typestr, str))) {
+ fprintf(stderr, "%s line %d: was %s, expected %s\n", file, lineno, typestr, str);
+ }
+ return &stack->head[relative_enum];
+ (void)info;
+} // end: xtra_vmstat_val
diff --git a/library/wchan.c b/library/wchan.c
new file mode 100644
index 0000000..d342fef
--- /dev/null
+++ b/library/wchan.c
@@ -0,0 +1,54 @@
+/*
+ * wchan.c - kernel symbol handling
+ *
+ * Copyright © 2015-2023 Jim Warner <james.warner@comcast.net>
+ * Copyright © 1998-2003 Albert Cahalan
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "wchan.h" // to verify prototype
+
+
+const char *lookup_wchan (int pid) {
+ static __thread char buf[64];
+ const char *ret = buf;
+ ssize_t num;
+ int fd;
+
+ snprintf(buf, sizeof buf, "/proc/%d/wchan", pid);
+ fd = open(buf, O_RDONLY);
+ if (fd==-1) return "?";
+
+ num = read(fd, buf, sizeof buf - 1);
+ close(fd);
+
+ if (num<1) return "?"; // allow for "0"
+ buf[num] = '\0';
+
+ if (buf[0]=='0' && buf[1]=='\0') return "-";
+
+ // lame ppc64 has a '.' in front of every name
+ if (*ret=='.') ret++;
+ while(*ret=='_') ret++;
+
+ return ret;
+}