/* * 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 */ #include #include #include #include #include #include #include #include #include #include #include #include #include "procps-private.h" /* ------------------------------------------------------------------------- strictly development define(s), largely for the top program ( next has no affect if ./configure --disable-numa has been specified ) */ //#define PRETEND_NUMA // pretend there are 3 'discontiguous' nodes // ------------------------------------------------------------------------- #define STAT_FILE "/proc/stat" #define STACKS_INCR 64 // amount reap stack allocations grow #define NEWOLD_INCR 32 // amount jiffs hist allocations grow struct stat_jifs { unsigned long long user, nice, system, idle, iowait, irq, sirq, stolen, guest, gnice; }; 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; struct stat_jifs new; struct stat_jifs old; }; struct stacks_extent { int ext_numstacks; struct stacks_extent *next; struct stat_stack **stacks; }; struct fetch_support { int numitems; // includes 'logical_end' delimiter enum stat_item *items; // includes 'logical_end' delimiter struct stacks_extent *extents; // anchor for these extents int dirty_stacks; }; 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 fetch_support fetch; // extents plus items details struct tic_support hist; // cpu and node jiffies management int n_anchor_alloc; // last known anchor pointers allocation struct stat_stack **anchor; // reapable stacks (consolidated extents) struct stat_reap result; // summary + stacks returned to caller }; struct procps_statinfo { int refcount; int stat_fd; int stat_was_read; // is stat file history valid? 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 fetch_support cpu_summary; // supports /proc/stat line #1 results struct fetch_support select; // support for 'procps_stat_select()' struct stat_reaped results; // for return to caller after a reap #ifndef NUMA_DISABLE void *libnuma_handle; // if dlopen() for libnuma succeessful int (*our_max_node)(void); // a libnuma function call via dlsym() int (*our_node_of_cpu)(int); // a libnuma function call via dlsym() #endif }; // ___ Results 'Set' Support |||||||||||||||||||||||||||||||||||||||||||||||||| #define setNAME(e) set_results_ ## 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 TIC_hst(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 SYS_hst(e,t,x) setDECL(e) { \ (void)T; R->result. t = ( S->new . x - S->old. x ); \ if (R->result. t < 0) R->result. t = 0; } setDECL(noop) { (void)R; (void)S; (void)T; } setDECL(extra) { (void)R; (void)S; (void)T; } setDECL(TIC_ID) { (void)S; R->result.s_int = T->id; } setDECL(TIC_NUMA_NODE) { (void)S; R->result.s_int = T->numa_node; } 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) TIC_hst(TIC_DELTA_USER, sl_int, user) TIC_hst(TIC_DELTA_NICE, sl_int, nice) TIC_hst(TIC_DELTA_SYSTEM, sl_int, system) TIC_hst(TIC_DELTA_IDLE, sl_int, idle) TIC_hst(TIC_DELTA_IOWAIT, sl_int, iowait) TIC_hst(TIC_DELTA_IRQ, sl_int, irq) TIC_hst(TIC_DELTA_SOFTIRQ, sl_int, sirq) TIC_hst(TIC_DELTA_STOLEN, sl_int, stolen) TIC_hst(TIC_DELTA_GUEST, sl_int, guest) TIC_hst(TIC_DELTA_GUEST_NICE, sl_int, gnice) 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) SYS_hst(SYS_DELTA_CTX_SWITCHES, s_int, ctxt) SYS_hst(SYS_DELTA_INTERRUPTS, s_int, intr) SYS_hst(SYS_DELTA_PROC_BLOCKED, s_int, procs_blocked) SYS_hst(SYS_DELTA_PROC_CREATED, s_int, procs_created) SYS_hst(SYS_DELTA_PROC_RUNNING, s_int, procs_running) // ___ Controlling Table |||||||||||||||||||||||||||||||||||||||||||||||||||||| typedef void (*SET_t)(struct stat_result *, struct hist_sys *, struct hist_tic *); #define RS(e) (SET_t)setNAME(e) /* * 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 } Item_table[] = { /* setsfunc --------------------------- */ { RS(noop), }, { RS(extra), }, { RS(TIC_ID), }, { RS(TIC_NUMA_NODE), }, { RS(TIC_USER), }, { RS(TIC_NICE), }, { RS(TIC_SYSTEM), }, { RS(TIC_IDLE), }, { RS(TIC_IOWAIT), }, { RS(TIC_IRQ), }, { RS(TIC_SOFTIRQ), }, { RS(TIC_STOLEN), }, { RS(TIC_GUEST), }, { RS(TIC_GUEST_NICE), }, { RS(TIC_DELTA_USER), }, { RS(TIC_DELTA_NICE), }, { RS(TIC_DELTA_SYSTEM), }, { RS(TIC_DELTA_IDLE), }, { RS(TIC_DELTA_IOWAIT), }, { RS(TIC_DELTA_IRQ), }, { RS(TIC_DELTA_SOFTIRQ), }, { RS(TIC_DELTA_STOLEN), }, { RS(TIC_DELTA_GUEST), }, { RS(TIC_DELTA_GUEST_NICE), }, { RS(SYS_CTX_SWITCHES), }, { RS(SYS_INTERRUPTS), }, { RS(SYS_PROC_BLOCKED), }, { RS(SYS_PROC_CREATED), }, { RS(SYS_PROC_RUNNING), }, { RS(SYS_TIME_OF_BOOT), }, { RS(SYS_DELTA_CTX_SWITCHES), }, { RS(SYS_DELTA_INTERRUPTS), }, { RS(SYS_DELTA_PROC_BLOCKED), }, { RS(SYS_DELTA_PROC_CREATED), }, { RS(SYS_DELTA_PROC_RUNNING), }, { NULL, } }; /* 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 */ enum stat_item PROCPS_STAT_TIC_highest = PROCPS_STAT_TIC_DELTA_GUEST_NICE; enum stat_item PROCPS_STAT_logical_end = PROCPS_STAT_SYS_DELTA_PROC_RUNNING + 1; // ___ Private Functions |||||||||||||||||||||||||||||||||||||||||||||||||||||| #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 static inline void 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 >= PROCPS_STAT_logical_end) break; Item_table[item].setsfunc(this, sys_hist, tic_hist); ++this; } return; } // end: assign_results static inline void cleanup_stack ( struct stat_result *this) { for (;;) { if (this->item >= PROCPS_STAT_logical_end) break; if (this->item > PROCPS_STAT_noop) this->result.ull_int = 0; ++this; } } // end: cleanup_stack static inline void cleanup_stacks_all ( struct fetch_support *this) { struct stacks_extent *ext = this->extents; int i; while (ext) { for (i = 0; ext->stacks[i]; i++) cleanup_stack(ext->stacks[i]->head); ext = ext->next; }; this->dirty_stacks = 0; } // end: cleanup_stacks_all static void extents_free_all ( struct fetch_support *this) { do { struct stacks_extent *p = this->extents; this->extents = this->extents->next; free(p); } while (this->extents); } // end: extents_free_all static inline struct stat_result *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->result.ull_int = 0; ++p; } return p_sav; } // end: itemize_stack static inline int 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, PROCPS_STAT_noop, num); * ^~~~~~~~~~~~~~~~ */ if (numitems < 1 || (void *)items < (void *)(unsigned long)(2 * PROCPS_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] >= PROCPS_STAT_logical_end) { return -1; } } return 0; } // end: items_check_failed static int make_numa_hist ( struct procps_statinfo *info) { #ifndef NUMA_DISABLE struct hist_tic *cpu_ptr, *nod_ptr; int i, node; if (info->libnuma_handle == NULL || (!info->nodes.total)) { return 0; } /* 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 ) */ info->nodes.total = info->our_max_node() + 1; if (!info->nodes.hist.n_alloc || !(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)) 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->cpus.hist.n_alloc; i++) { nod_ptr->id = nod_ptr->numa_node = PROCPS_STAT_NODE_INVALID; ++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 = info->our_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; /* * note: the above call to 'our_node_of_cpu' will produce a modest * memory leak summarized as: * ==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 pain you've caused us ] */ cpu_ptr->numa_node = node; nod_ptr->id = node; } } info->nodes.hist.n_inuse = info->nodes.total; return info->nodes.hist.n_inuse; #else return 0; #endif } // end: make_numa_hist static int read_stat_failed ( struct procps_statinfo *info) { struct hist_tic *sum_ptr, *cpu_ptr; char buf[8192], *bp, *b; int i, rc, size; unsigned long long llnum = 0; if (info == NULL) return -EINVAL; if (!info->cpus.hist.n_alloc) { info->cpus.hist.tics = calloc(NEWOLD_INCR, sizeof(struct hist_tic)); if (!(info->cpus.hist.tics)) return -ENOMEM; info->cpus.hist.n_alloc = NEWOLD_INCR; info->cpus.hist.n_inuse = 0; } if (-1 == info->stat_fd && (info->stat_fd = open(STAT_FILE, O_RDONLY)) == -1) return -errno; if (lseek(info->stat_fd, 0L, SEEK_SET) == -1) return -errno; for (;;) { if ((size = read(info->stat_fd, buf, sizeof(buf)-1)) < 0) { if (errno == EINTR || errno == EAGAIN) continue; return -errno; } break; } if (size == 0) { return -EIO; } buf[size] = '\0'; bp = 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 = PROCPS_STAT_SUMMARY_ID; // mark as summary sum_ptr->numa_node = PROCPS_STAT_NODE_INVALID; // mark as invalid // now value the cpu summary tics from line #1 if (8 > sscanf(bp, "cpu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu" , &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)) return -1; // let's not distort the deltas the first time thru ... if (!info->stat_was_read) memcpy(&sum_ptr->old, &sum_ptr->new, sizeof(struct stat_jifs)); i = 0; reap_em_again: cpu_ptr = info->cpus.hist.tics + i; // adapt to relocated if reap_em_again do { 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 'make_numa_hist' cpu_ptr->numa_node = PROCPS_STAT_NODE_INVALID; if (8 > (rc = sscanf(bp, "cpu%d %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu" , &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))) { int id_sav = cpu_ptr->id; memmove(cpu_ptr, sum_ptr, sizeof(struct hist_tic)); cpu_ptr->id = id_sav; break; // we must tolerate cpus taken offline } // let's not distort the deltas the first time thru ... if (!info->stat_was_read) memcpy(&cpu_ptr->old, &cpu_ptr->new, sizeof(struct stat_jifs)); ++i; ++cpu_ptr; } 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 -ENOMEM; goto reap_em_again; } info->cpus.total = info->cpus.hist.n_inuse = i; // remember sys_hist stuff from last time around memcpy(&info->sys_hist.old, &info->sys_hist.new, sizeof(struct stat_data)); llnum = 0; b = strstr(bp, "intr "); if(b) sscanf(b, "intr %llu", &llnum); info->sys_hist.new.intr = llnum; llnum = 0; b = strstr(bp, "ctxt "); if(b) sscanf(b, "ctxt %llu", &llnum); info->sys_hist.new.ctxt = llnum; llnum = 0; b = strstr(bp, "btime "); if(b) sscanf(b, "btime %llu", &llnum); info->sys_hist.new.btime = llnum; llnum = 0; b = strstr(bp, "processes "); if(b) sscanf(b, "processes %llu", &llnum); info->sys_hist.new.procs_created = llnum; llnum = 0; b = strstr(bp, "procs_blocked "); if(b) sscanf(b, "procs_blocked %llu", &llnum); info->sys_hist.new.procs_blocked = llnum; llnum = 0; b = strstr(bp, "procs_rnning "); if(b) sscanf(b, "procs_rnning %llu", &llnum); info->sys_hist.new.procs_running = llnum; // let's not distort the deltas the first time thru ... if (!info->stat_was_read) memcpy(&info->sys_hist.old, &info->sys_hist.new, sizeof(struct stat_data)); info->stat_was_read = 1; return 0; } // end: read_stat_failed /* * stacks_alloc(): * * Allocate and initialize one or more stacks each of which is anchored in an * associated stat_stack 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 *stacks_alloc ( struct fetch_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; if (this == NULL || this->items == NULL) return NULL; if (maxstacks < 1) return NULL; 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->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 = 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 = itemize_stack((struct stat_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: stacks_alloc static int stacks_fetch_tics ( struct procps_statinfo *info, struct reap_support *this) { struct stacks_extent *ext; int i; if (this == NULL) return -EINVAL; // initialize stuff ----------------------------------- if (!this->anchor) { if (!(this->anchor = calloc(sizeof(void *), STACKS_INCR))) return -ENOMEM; this->n_anchor_alloc = STACKS_INCR; } if (!this->fetch.extents) { if (!(ext = stacks_alloc(&this->fetch, this->n_anchor_alloc))) return -ENOMEM; memcpy(this->anchor, ext->stacks, sizeof(void *) * this->n_anchor_alloc); } if (this->fetch.dirty_stacks) cleanup_stacks_all(&this->fetch); // iterate stuff -------------------------------------- for (i = 0; i < this->hist.n_inuse; i++) { if (!(i < this->n_anchor_alloc)) { this->n_anchor_alloc += STACKS_INCR; if ((!(this->anchor = realloc(this->anchor, sizeof(void *) * this->n_anchor_alloc))) || (!(ext = stacks_alloc(&this->fetch, STACKS_INCR)))) { return -ENOMEM; } memcpy(this->anchor + i, ext->stacks, sizeof(void *) * STACKS_INCR); } assign_results(this->anchor[i], &info->sys_hist, &this->hist.tics[i]); } // finalize stuff ------------------------------------- this->result.total = i; this->result.stacks = this->anchor; this->fetch.dirty_stacks = 1; return this->result.total; } // end: stacks_fetch_tics static int stacks_reconfig_maybe ( struct fetch_support *this, enum stat_item *items, int numitems) { if (items_check_failed(numitems, items)) return -EINVAL; /* 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 stat_item) * numitems)) { // allow for our PROCPS_STAT_logical_end if (!(this->items = realloc(this->items, sizeof(enum stat_item) * (numitems + 1)))) return -ENOMEM; memcpy(this->items, items, sizeof(enum stat_item) * numitems); this->items[numitems] = PROCPS_STAT_logical_end; this->numitems = numitems + 1; if (this->extents) extents_free_all(this); return 1; } return 0; } // end: stacks_reconfig_maybe static struct stat_stack *update_single_stack ( struct procps_statinfo *info, struct fetch_support *this, enum stat_item *items, int numitems) { if (0 > stacks_reconfig_maybe(this, items, numitems)) return NULL; if (!this->extents && !(this->extents = stacks_alloc(this, 1))) return NULL; if (this->dirty_stacks) cleanup_stacks_all(this); assign_results(this->extents->stacks[0], &info->sys_hist, &info->cpu_hist); this->dirty_stacks = 1; return this->extents->stacks[0]; } // end: update_single_stack #if defined(PRETEND_NUMA) && defined(NUMA_DISABLE) # warning 'PRETEND_NUMA' ignored, 'NUMA_DISABLE' is active #endif // ___ Public 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: a new stat info container */ PROCPS_EXPORT int procps_stat_new ( struct procps_statinfo **info) { struct procps_statinfo *p; if (info == NULL || *info != NULL) return -EINVAL; if (!(p = calloc(1, sizeof(struct procps_statinfo)))) return -ENOMEM; p->refcount = 1; p->stat_fd = -1; p->results.cpus = &p->cpus.result; p->results.nodes = &p->nodes.result; p->cpus.total = procps_cpu_count(); #ifndef NUMA_DISABLE #ifndef PRETEND_NUMA // we'll try for the most recent version, then a version we know works... if ((p->libnuma_handle = dlopen("libnuma.so", RTLD_LAZY)) || (p->libnuma_handle = dlopen("libnuma.so.1", RTLD_LAZY))) { p->our_max_node = dlsym(p->libnuma_handle, "numa_max_node"); p->our_node_of_cpu = dlsym(p->libnuma_handle, "numa_node_of_cpu"); if (p->our_max_node && p->our_node_of_cpu) p->nodes.total = p->our_max_node() + 1; else { dlclose(p->libnuma_handle); p->libnuma_handle = NULL; } } #else p->libnuma_handle = (void *)-1; p->our_max_node = fake_max_node; p->our_node_of_cpu = fake_node_of_cpu; p->nodes.total = fake_max_node() + 1; #endif #endif *info = p; return 0; } // end :procps_stat_new PROCPS_EXPORT int procps_stat_ref ( struct procps_statinfo *info) { if (info == NULL) return -EINVAL; info->refcount++; return info->refcount; } // end: procps_stat_ref PROCPS_EXPORT int procps_stat_unref ( struct procps_statinfo **info) { if (info == NULL || *info == NULL) return -EINVAL; (*info)->refcount--; if ((*info)->refcount == 0) { if ((*info)->cpus.anchor) free((*info)->cpus.anchor); if ((*info)->cpus.hist.tics) free((*info)->cpus.hist.tics); if ((*info)->cpus.fetch.items) free((*info)->cpus.fetch.items); if ((*info)->cpus.fetch.extents) extents_free_all(&(*info)->cpus.fetch); if ((*info)->nodes.anchor) free((*info)->nodes.anchor); if ((*info)->nodes.hist.tics) free((*info)->nodes.hist.tics); if ((*info)->nodes.fetch.items) free((*info)->nodes.fetch.items); if ((*info)->nodes.fetch.extents) extents_free_all(&(*info)->nodes.fetch); if ((*info)->cpu_summary.items) free((*info)->cpu_summary.items); if ((*info)->cpu_summary.extents) extents_free_all(&(*info)->cpu_summary); if ((*info)->select.items) free((*info)->select.items); if ((*info)->select.extents) extents_free_all(&(*info)->select); #ifndef NUMA_DISABLE #ifndef PRETEND_NUMA if ((*info)->libnuma_handle) dlclose((*info)->libnuma_handle); #endif #endif free(*info); *info = NULL; return 0; } return (*info)->refcount; } // end: procps_stat_unref PROCPS_EXPORT signed long long procps_stat_get ( struct procps_statinfo *info, enum stat_item item) { #define vTIC(X) ( info->cpu_hist.new. X - info->cpu_hist.old. X ) #define vSYS(X) ( info->sys_hist.new. X - info->sys_hist.old. X ) static time_t sav_secs; time_t cur_secs; int rc; /* no sense reading the stat with every call from a program like vmstat who chooses not to use the much more efficient 'select' function ... */ cur_secs = time(NULL); if (1 <= cur_secs - sav_secs) { if ((rc = read_stat_failed(info))) return rc; sav_secs = cur_secs; } switch (item) { case PROCPS_STAT_TIC_ID: return info->cpu_hist.id; case PROCPS_STAT_TIC_NUMA_NODE: return info->cpu_hist.numa_node; case PROCPS_STAT_TIC_USER: return info->cpu_hist.new.user; case PROCPS_STAT_TIC_NICE: return info->cpu_hist.new.nice; case PROCPS_STAT_TIC_SYSTEM: return info->cpu_hist.new.system; case PROCPS_STAT_TIC_IDLE: return info->cpu_hist.new.idle; case PROCPS_STAT_TIC_IOWAIT: return info->cpu_hist.new.iowait; case PROCPS_STAT_TIC_IRQ: return info->cpu_hist.new.irq; case PROCPS_STAT_TIC_SOFTIRQ: return info->cpu_hist.new.sirq; case PROCPS_STAT_TIC_STOLEN: return info->cpu_hist.new.stolen; case PROCPS_STAT_TIC_GUEST: return info->cpu_hist.new.guest; case PROCPS_STAT_TIC_GUEST_NICE: return info->cpu_hist.new.gnice; case PROCPS_STAT_TIC_DELTA_USER: return vTIC(user); case PROCPS_STAT_TIC_DELTA_NICE: return vTIC(nice); case PROCPS_STAT_TIC_DELTA_SYSTEM: return vTIC(system); case PROCPS_STAT_TIC_DELTA_IDLE: return vTIC(idle); case PROCPS_STAT_TIC_DELTA_IOWAIT: return vTIC(iowait); case PROCPS_STAT_TIC_DELTA_IRQ: return vTIC(irq); case PROCPS_STAT_TIC_DELTA_SOFTIRQ: return vTIC(sirq); case PROCPS_STAT_TIC_DELTA_STOLEN: return vTIC(stolen); case PROCPS_STAT_TIC_DELTA_GUEST: return vTIC(guest); case PROCPS_STAT_TIC_DELTA_GUEST_NICE: return vTIC(gnice); case PROCPS_STAT_SYS_CTX_SWITCHES: return info->sys_hist.new.ctxt; case PROCPS_STAT_SYS_INTERRUPTS: return info->sys_hist.new.intr; case PROCPS_STAT_SYS_PROC_BLOCKED: return info->sys_hist.new.procs_blocked; case PROCPS_STAT_SYS_PROC_CREATED: return info->sys_hist.new.procs_created; case PROCPS_STAT_SYS_PROC_RUNNING: return info->sys_hist.new.procs_running; case PROCPS_STAT_SYS_TIME_OF_BOOT: return info->sys_hist.new.btime; case PROCPS_STAT_SYS_DELTA_CTX_SWITCHES: return vSYS(ctxt); case PROCPS_STAT_SYS_DELTA_INTERRUPTS: return vSYS(intr); case PROCPS_STAT_SYS_DELTA_PROC_BLOCKED: return vSYS(procs_blocked); case PROCPS_STAT_SYS_DELTA_PROC_CREATED: return vSYS(procs_created); case PROCPS_STAT_SYS_DELTA_PROC_RUNNING: return vSYS(procs_running); case PROCPS_STAT_noop: case PROCPS_STAT_extra: return 0; default: return -EINVAL; } #undef vTIC #undef vSYS } // end: procps_stat_get /* 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 procps_statinfo *info, enum stat_item *items, int numitems) { if (info == NULL || items == NULL) return NULL; if (read_stat_failed(info)) return NULL; return update_single_stack(info, &info->select, items, numitems); } // end: procps_stat_select /* 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 procps_statinfo *info, enum stat_reap_type what, enum stat_item *items, int numitems) { int i, rc; if (info == NULL || items == NULL) return NULL; info->results.summary = NULL; info->cpus.result.total = info->nodes.result.total = 0; info->cpus.result.stacks = info->nodes.result.stacks = NULL; if (what != STAT_REAP_CPUS_ONLY && what != STAT_REAP_CPUS_AND_NODES) return NULL; // those PROCPS_STAT_SYS_type enum's make sense only to 'select' ... for (i = 0; i < numitems; i++) { if (items[i] > PROCPS_STAT_TIC_highest) return NULL; } if ((rc = stacks_reconfig_maybe(&info->cpu_summary, items, numitems)) < 0) return NULL; if (rc) { if ((rc = stacks_reconfig_maybe(&info->cpus.fetch, items, numitems)) < 0 || ((rc = stacks_reconfig_maybe(&info->nodes.fetch, items, numitems)) < 0)) return NULL; } if (read_stat_failed(info)) return NULL; info->results.summary = update_single_stack(info, &info->cpu_summary, items, numitems); switch (what) { case STAT_REAP_CPUS_ONLY: if (!stacks_fetch_tics(info, &info->cpus)) return NULL; break; case STAT_REAP_CPUS_AND_NODES: if (0 > make_numa_hist(info)) return NULL; if (!stacks_fetch_tics(info, &info->cpus)) return NULL; if (!stacks_fetch_tics(info, &info->nodes)) return NULL; break; default: return NULL; }; return &info->results; } // end: procps_stat_reap