/*
 * 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 <dlfcn.h>
#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 <proc/stat.h>
#include <proc/sysinfo.h>
#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)
setDECL(SYS_DELTA_PROC_BLOCKED)  { (void)T; R->result.s_int = S->new.procs_blocked - S->old.procs_blocked; }
SYS_hst(SYS_DELTA_PROC_CREATED,  s_int,    procs_created)
setDECL(SYS_DELTA_PROC_RUNNING)  { (void)T; R->result.s_int = S->new.procs_running - S->old.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