summaryrefslogtreecommitdiffstats
path: root/library/stat.c
diff options
context:
space:
mode:
Diffstat (limited to 'library/stat.c')
-rw-r--r--library/stat.c1443
1 files changed, 1443 insertions, 0 deletions
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