335cbc2e61
After normalizing the <SLABINFO> module, much of which was based on this module, a structure which is crucial to concurrent select/reap support is renamed the same. Such a need for concurrent support was revealed during slabtop testing. That program requests (select) global data after calling for individual node data (reap) but before those stacks have been accessed. Therefore, one set of 'items'/'extents' could not possibly be shared. So now we will know the former struct fetch_support as ext_support. The former name was potentially confusing since the struct was used by both 'select' and 'reap'. However, only 'reap' ever called the 'fetch' function. [ this <STAT> already accommodated concurrent usage. ] [ in fact it contains 3 separate sets of items/exts. ] Signed-off-by: Jim Warner <james.warner@comcast.net>
1021 lines
35 KiB
C
1021 lines
35 KiB
C
/*
|
|
* 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 ext_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 ext_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 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
|
|
#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 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 ); \
|
|
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)
|
|
|
|
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)
|
|
|
|
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)
|
|
setDECL(SYS_DELTA_PROC_BLOCKED) { (void)T; R->result.s_int = S->new.procs_blocked - S->old.procs_blocked; }
|
|
SYSsetH(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; }
|
|
|
|
|
|
// ___ Results 'Get' Support ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|
|
#define getNAME(e) get_results_ ## e
|
|
#define getDECL(e) static signed long getNAME(e) \
|
|
(struct procps_statinfo *I)
|
|
|
|
// regular get
|
|
#define TIC_get(e,x) getDECL(e) { return I->cpu_hist.new . x; }
|
|
#define SYS_get(e,x) getDECL(e) { return I->sys_hist.new . x; }
|
|
// delta get
|
|
#define TICgetH(e,x) getDECL(e) { \
|
|
long long n = I->cpu_hist.new. x - I->cpu_hist.old. x; \
|
|
return n < 0 ? 0 : n; }
|
|
#define SYSgetH(e,x) getDECL(e) { \
|
|
long long n = I->sys_hist.new. x - I->sys_hist.old. x; \
|
|
return n < 0 ? 0 : n; }
|
|
|
|
getDECL(noop) { (void)I; return 0; }
|
|
getDECL(extra) { (void)I; return 0; }
|
|
|
|
getDECL(TIC_ID) { return I->cpu_hist.id; }
|
|
getDECL(TIC_NUMA_NODE) { return I->cpu_hist.numa_node; }
|
|
|
|
TIC_get(TIC_USER, user)
|
|
TIC_get(TIC_NICE, nice)
|
|
TIC_get(TIC_SYSTEM, system)
|
|
TIC_get(TIC_IDLE, idle)
|
|
TIC_get(TIC_IOWAIT, iowait)
|
|
TIC_get(TIC_IRQ, irq)
|
|
TIC_get(TIC_SOFTIRQ, sirq)
|
|
TIC_get(TIC_STOLEN, stolen)
|
|
TIC_get(TIC_GUEST, guest)
|
|
TIC_get(TIC_GUEST_NICE, gnice)
|
|
|
|
TICgetH(TIC_DELTA_USER, user)
|
|
TICgetH(TIC_DELTA_NICE, nice)
|
|
TICgetH(TIC_DELTA_SYSTEM, system)
|
|
TICgetH(TIC_DELTA_IDLE, idle)
|
|
TICgetH(TIC_DELTA_IOWAIT, iowait)
|
|
TICgetH(TIC_DELTA_IRQ, irq)
|
|
TICgetH(TIC_DELTA_SOFTIRQ, sirq)
|
|
TICgetH(TIC_DELTA_STOLEN, stolen)
|
|
TICgetH(TIC_DELTA_GUEST, guest)
|
|
TICgetH(TIC_DELTA_GUEST_NICE, gnice)
|
|
|
|
SYS_get(SYS_CTX_SWITCHES, ctxt)
|
|
SYS_get(SYS_INTERRUPTS, intr)
|
|
SYS_get(SYS_PROC_BLOCKED, procs_blocked)
|
|
SYS_get(SYS_PROC_CREATED, procs_created)
|
|
SYS_get(SYS_PROC_RUNNING, procs_running)
|
|
SYS_get(SYS_TIME_OF_BOOT, btime)
|
|
|
|
SYSgetH(SYS_DELTA_CTX_SWITCHES, ctxt)
|
|
SYSgetH(SYS_DELTA_INTERRUPTS, intr)
|
|
getDECL(SYS_DELTA_PROC_BLOCKED) { return I->sys_hist.new.procs_blocked - I->sys_hist.old.procs_blocked; }
|
|
SYSgetH(SYS_DELTA_PROC_CREATED, procs_created)
|
|
getDECL(SYS_DELTA_PROC_RUNNING) { return I->sys_hist.new.procs_running - I->sys_hist.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)
|
|
|
|
typedef long long (*GET_t)(struct procps_statinfo *);
|
|
#define RG(e) (GET_t)getNAME(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
|
|
GET_t getsfunc; // a routine to return single result
|
|
} Item_table[] = {
|
|
/* setsfunc getsfunc
|
|
--------------------------- ------------------------- */
|
|
{ RS(noop), RG(noop) },
|
|
{ RS(extra), RG(extra) },
|
|
|
|
{ RS(TIC_ID), RG(TIC_ID) },
|
|
{ RS(TIC_NUMA_NODE), RG(TIC_NUMA_NODE) },
|
|
{ RS(TIC_USER), RG(TIC_USER) },
|
|
{ RS(TIC_NICE), RG(TIC_NICE) },
|
|
{ RS(TIC_SYSTEM), RG(TIC_SYSTEM) },
|
|
{ RS(TIC_IDLE), RG(TIC_IDLE) },
|
|
{ RS(TIC_IOWAIT), RG(TIC_IOWAIT) },
|
|
{ RS(TIC_IRQ), RG(TIC_IRQ) },
|
|
{ RS(TIC_SOFTIRQ), RG(TIC_SOFTIRQ) },
|
|
{ RS(TIC_STOLEN), RG(TIC_STOLEN) },
|
|
{ RS(TIC_GUEST), RG(TIC_GUEST) },
|
|
{ RS(TIC_GUEST_NICE), RG(TIC_GUEST_NICE) },
|
|
{ RS(TIC_DELTA_USER), RG(TIC_DELTA_USER) },
|
|
{ RS(TIC_DELTA_NICE), RG(TIC_DELTA_NICE) },
|
|
{ RS(TIC_DELTA_SYSTEM), RG(TIC_DELTA_SYSTEM) },
|
|
{ RS(TIC_DELTA_IDLE), RG(TIC_DELTA_IDLE) },
|
|
{ RS(TIC_DELTA_IOWAIT), RG(TIC_DELTA_IOWAIT) },
|
|
{ RS(TIC_DELTA_IRQ), RG(TIC_DELTA_IRQ) },
|
|
{ RS(TIC_DELTA_SOFTIRQ), RG(TIC_DELTA_SOFTIRQ) },
|
|
{ RS(TIC_DELTA_STOLEN), RG(TIC_DELTA_STOLEN) },
|
|
{ RS(TIC_DELTA_GUEST), RG(TIC_DELTA_GUEST) },
|
|
{ RS(TIC_DELTA_GUEST_NICE), RG(TIC_DELTA_GUEST_NICE) },
|
|
|
|
{ RS(SYS_CTX_SWITCHES), RG(SYS_CTX_SWITCHES) },
|
|
{ RS(SYS_INTERRUPTS), RG(SYS_INTERRUPTS) },
|
|
{ RS(SYS_PROC_BLOCKED), RG(SYS_PROC_BLOCKED) },
|
|
{ RS(SYS_PROC_CREATED), RG(SYS_PROC_CREATED) },
|
|
{ RS(SYS_PROC_RUNNING), RG(SYS_PROC_RUNNING) },
|
|
{ RS(SYS_TIME_OF_BOOT), RG(SYS_TIME_OF_BOOT) },
|
|
{ RS(SYS_DELTA_CTX_SWITCHES), RG(SYS_DELTA_CTX_SWITCHES) },
|
|
{ RS(SYS_DELTA_INTERRUPTS), RG(SYS_DELTA_INTERRUPTS) },
|
|
{ RS(SYS_DELTA_PROC_BLOCKED), RG(SYS_DELTA_PROC_BLOCKED) },
|
|
{ RS(SYS_DELTA_PROC_CREATED), RG(SYS_DELTA_PROC_CREATED) },
|
|
{ RS(SYS_DELTA_PROC_RUNNING), RG(SYS_DELTA_PROC_RUNNING) },
|
|
|
|
{ NULL, 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;
|
|
|
|
#undef setNAME
|
|
#undef setDECL
|
|
#undef TIC_set
|
|
#undef SYS_set
|
|
#undef TICsetH
|
|
#undef SYSsetH
|
|
#undef getNAME
|
|
#undef getDECL
|
|
#undef TIC_get
|
|
#undef SYS_get
|
|
#undef TICgetH
|
|
#undef SYSgetH
|
|
#undef RS
|
|
#undef RG
|
|
|
|
// ___ 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 ext_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 ext_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_running ");
|
|
if(b) sscanf(b, "procs_running %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 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;
|
|
|
|
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 ext_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 ext_support *this,
|
|
enum stat_item *items,
|
|
int numitems)
|
|
{
|
|
if (0 > stacks_reconfig_maybe(this, items, numitems))
|
|
return NULL;
|
|
|
|
if (!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)
|
|
{
|
|
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;
|
|
}
|
|
|
|
if (item < PROCPS_STAT_logical_end)
|
|
return Item_table[item].getsfunc(info);
|
|
return -EINVAL;
|
|
} // 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
|