diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 20:34:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 20:34:44 +0000 |
commit | e3be059d4da38aa36f1aee1d56f8ceb943d92f1c (patch) | |
tree | 26edef31e4e503dd1c92a112de174f366dd61802 /library | |
parent | Initial commit. (diff) | |
download | procps-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 '')
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; +} |