library: file now parsed with 'hsearch', <MEMINFO> api

After reviewing the hsearch code in glibc, performance
will almost certainly benefit from abandoning a strcmp
approach in favor of hashing, just like that <vmstat>.

[ As an aside, now having struggled toward that goal ]
[ of opaqueness & making our API as user friendly as ]
[ possible, haven't we earned the rights to evaluate ]
[ other implementations? For example, GNU's hsearch? ]

[ We expose none of our 'info' struct details to the ]
[ users, but GNU exposes their 'hsearch_data' thingy ]
[ right there in <search.h>. But worse, they require ]
[ the user to zero it out before 1st use. Jeeze, you ]
[ mean that a function called hcreate_r could not do ]
[ its own memset? Aw, come on GNU! What's with that? ]

Signed-off-by: Jim Warner <james.warner@comcast.net>
This commit is contained in:
Jim Warner 2016-06-10 00:00:00 -05:00 committed by Craig Small
parent 876ec555c3
commit 92c72166db

View File

@ -18,6 +18,7 @@
#include <errno.h>
#include <fcntl.h>
#include <search.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -35,22 +36,21 @@
struct meminfo_data {
/** == man 5 proc shows these as: to be documented, so the hell with 'em */
unsigned long Active;
unsigned long Active_anon; // as: (anon)
unsigned long Active_file; // as: (file)
unsigned long Active_anon; // as: Active(anon):
unsigned long Active_file; // as: Active(file):
unsigned long AnonHugePages;
unsigned long AnonPages;
unsigned long Bounce;
unsigned long Buffers;
unsigned long Cached;
/** unsigned long CmaFree; ----------- */
/** unsigned long CmaTotal; ---------- */
unsigned long CmaFree; // man 5 proc: 'to be documented'
unsigned long CmaTotal; // man 5 proc: 'to be documented'
unsigned long CommitLimit;
unsigned long Committed_AS;
/** unsigned long DirectMap1G; ------- */
/** unsigned long DirectMap2M; ------- */
/** unsigned long DirectMap4k; ------- */
unsigned long DirectMap1G; // man 5 proc: 'to be documented'
unsigned long DirectMap2M; // man 5 proc: 'to be documented'
unsigned long DirectMap4k; // man 5 proc: 'to be documented'
unsigned long Dirty;
unsigned long HardwareCorrupted;
unsigned long HighFree;
@ -61,8 +61,8 @@ struct meminfo_data {
unsigned long HugePages_Total;
unsigned long Hugepagesize;
unsigned long Inactive;
unsigned long Inactive_anon; // as: (anon)
unsigned long Inactive_file; // as: (file)
unsigned long Inactive_anon; // as: Inactive(anon):
unsigned long Inactive_file; // as: Inactive(file):
unsigned long KernelStack;
unsigned long LowFree;
unsigned long LowTotal;
@ -93,7 +93,7 @@ struct meminfo_data {
unsigned long derived_swap_used;
};
struct hist_mem {
struct mem_hist {
struct meminfo_data new;
struct meminfo_data old;
};
@ -109,10 +109,11 @@ struct procps_meminfo {
int meminfo_fd;
int meminfo_was_read;
int dirty_stacks;
struct hist_mem mem_hist;
struct mem_hist hist;
int numitems;
enum meminfo_item *items;
struct stacks_extent *extents;
struct hsearch_data hashtab;
};
@ -120,7 +121,7 @@ struct procps_meminfo {
#define setNAME(e) set_results_ ## e
#define setDECL(e) static void setNAME(e) \
(struct meminfo_result *R, struct hist_mem *H)
(struct meminfo_result *R, struct mem_hist *H)
// regular assignment
#define MEM_set(e,t,x) setDECL(e) { R->result. t = H->new . x; }
@ -231,9 +232,9 @@ MEM_set(SWAP_USED, ul_int, derived_swap_used)
(struct procps_meminfo *I)
// regular get
#define MEM_get(e,x) getDECL(e) { return I->mem_hist.new. x; }
#define MEM_get(e,x) getDECL(e) { return I->hist.new. x; }
// delta get
#define HST_get(e,x) getDECL(e) { return ( I->mem_hist.new. x - I->mem_hist.old. x ); }
#define HST_get(e,x) getDECL(e) { return ( I->hist.new. x - I->hist.old. x ); }
getDECL(noop) { (void)I; return 0; }
getDECL(extra) { (void)I; return 0; }
@ -334,7 +335,7 @@ MEM_get(SWAP_USED, derived_swap_used)
// ___ Controlling Table ||||||||||||||||||||||||||||||||||||||||||||||||||||||
typedef void (*SET_t)(struct meminfo_result *, struct hist_mem *);
typedef void (*SET_t)(struct meminfo_result *, struct mem_hist *);
#define RS(e) (SET_t)setNAME(e)
typedef long (*GET_t)(struct procps_meminfo *);
@ -469,7 +470,7 @@ enum meminfo_item PROCPS_MEMINFO_logical_end = PROCPS_MEMINFO_SWAP_USED + 1;
static inline void assign_results (
struct meminfo_stack *stack,
struct hist_mem *mem_hist)
struct mem_hist *hist)
{
struct meminfo_result *this = stack->head;
@ -477,7 +478,7 @@ static inline void assign_results (
enum meminfo_item item = this->item;
if (item >= PROCPS_MEMINFO_logical_end)
break;
Item_table[item].setsfunc(this, mem_hist);
Item_table[item].setsfunc(this, hist);
++this;
}
return;
@ -570,6 +571,78 @@ static inline int items_check_failed (
} // end: items_check_failed
static int make_hash_failed (
struct procps_meminfo *info)
{
#define htVAL(f) e.key = STRINGIFY(f) ":"; e.data = &info->hist.new. f; \
if (!hsearch_r(e, ENTER, &ep, &info->hashtab)) return -errno;
#define htXTRA(k,f) e.key = STRINGIFY(k) ":"; e.data = &info->hist.new. f; \
if (!hsearch_r(e, ENTER, &ep, &info->hashtab)) return -errno;
ENTRY e, *ep;
size_t n;
// will also include those 4 derived fields (more is better)
n = sizeof(struct meminfo_data) / sizeof(unsigned long);
// we'll follow the hsearch recommendation of an extra 25%
hcreate_r(n + (n / 4), &info->hashtab);
htVAL(Active)
htXTRA(Active(anon), Active_anon)
htXTRA(Active(file), Active_file)
htVAL(AnonHugePages)
htVAL(AnonPages)
htVAL(Bounce)
htVAL(Buffers)
htVAL(Cached)
htVAL(CmaFree)
htVAL(CmaTotal)
htVAL(CommitLimit)
htVAL(Committed_AS)
htVAL(DirectMap1G)
htVAL(DirectMap2M)
htVAL(DirectMap4k)
htVAL(Dirty)
htVAL(HardwareCorrupted)
htVAL(HighFree)
htVAL(HighTotal)
htVAL(HugePages_Free)
htVAL(HugePages_Rsvd)
htVAL(HugePages_Surp)
htVAL(HugePages_Total)
htVAL(Hugepagesize)
htVAL(Inactive)
htXTRA(Inactive(anon), Inactive_anon)
htXTRA(Inactive(file), Inactive_file)
htVAL(KernelStack)
htVAL(LowFree)
htVAL(LowTotal)
htVAL(Mapped)
htVAL(MemAvailable)
htVAL(MemFree)
htVAL(MemTotal)
htVAL(Mlocked)
htVAL(NFS_Unstable)
htVAL(PageTables)
htVAL(SReclaimable)
htVAL(SUnreclaim)
htVAL(Shmem)
htVAL(Slab)
htVAL(SwapCached)
htVAL(SwapFree)
htVAL(SwapTotal)
htVAL(Unevictable)
htVAL(VmallocChunk)
htVAL(VmallocTotal)
htVAL(VmallocUsed)
htVAL(Writeback)
htVAL(WritebackTmp)
return 0;
#undef htVAL
#undef htXTRA
} // end: make_hash_failed
/*
* read_meminfo_failed():
*
@ -581,7 +654,7 @@ PROCPS_EXPORT int read_meminfo_failed (
{
/* a 'memory history reference' macro for readability,
so we can focus the field names ... */
#define mHr(f) info->mem_hist.new. f
#define mHr(f) info->hist.new. f
char buf[8192];
char *head, *tail;
int size;
@ -592,9 +665,9 @@ PROCPS_EXPORT int read_meminfo_failed (
return -1;
// remember history from last time around
memcpy(&info->mem_hist.old, &info->mem_hist.new, sizeof(struct meminfo_data));
memcpy(&info->hist.old, &info->hist.new, sizeof(struct meminfo_data));
// clear out the soon to be 'current' values
memset(&info->mem_hist.new, 0, sizeof(struct meminfo_data));
memset(&info->hist.new, 0, sizeof(struct meminfo_data));
if (-1 == info->meminfo_fd
&& (info->meminfo_fd = open(MEMINFO_FILE, O_RDONLY)) == -1)
@ -615,175 +688,30 @@ PROCPS_EXPORT int read_meminfo_failed (
return 0;
buf[size] = '\0';
/* Scan the file */
head = buf;
do {
static ENTRY e; // just to keep coverity off our backs (e.data)
ENTRY *ep;
tail = strchr(head, ' ');
if (!tail)
break;
*tail = '\0';
valptr = NULL;
switch (*head) {
case 'A':
if (0 == strcmp(head, "Active:"))
valptr = &(mHr(Active));
else
if (0 == strcmp(head, "Active(anon):"))
valptr = &(mHr(Active_anon));
else
if (0 == strcmp(head, "Active(file):"))
valptr = &(mHr(Active_file));
else
if (0 == strcmp(head, "AnonHugePages:"))
valptr = &(mHr(AnonHugePages));
else
if (0 == strcmp(head, "AnonPages:"))
valptr = &(mHr(AnonPages));
break;
case 'B':
if (0 == strcmp(head, "Bounce:"))
valptr = &(mHr(Bounce));
else
if (0 == strcmp(head, "Buffers:"))
valptr = &(mHr(Buffers));
break;
case 'C':
if (0 == strcmp(head, "Cached:"))
valptr = &(mHr(Cached));
else
if (0 == strcmp(head, "CommitLimit:"))
valptr = &(mHr(CommitLimit));
else
if (0 == strcmp(head, "Committed_AS:"))
valptr = &(mHr(Committed_AS));
break;
case 'D':
if (0 == strcmp(head, "Dirty:"))
valptr = &(mHr(Dirty));
break;
case 'H':
if (0 == strcmp(head, "HardwareCorrupted:"))
valptr = &(mHr(HardwareCorrupted));
else
if (0 == strcmp(head, "HighFree:"))
valptr = &(mHr(HighFree));
else
if (0 == strcmp(head, "HighTotal:"))
valptr = &(mHr(HighTotal));
else
if (0 == strcmp(head, "HugePages_Free:"))
valptr = &(mHr(HugePages_Free));
else
if (0 == strcmp(head, "HugePages_Rsvd:"))
valptr = &(mHr(HugePages_Rsvd));
else
if (0 == strcmp(head, "HugePages_Surp:"))
valptr = &(mHr(HugePages_Surp));
else
if (0 == strcmp(head, "HugePages_Total:"))
valptr = &(mHr(HugePages_Total));
else
if (0 == strcmp(head, "Hugepagesize:"))
valptr = &(mHr(Hugepagesize));
break;
case 'I':
if (0 == strcmp(head, "Inactive:"))
valptr = &(mHr(Inactive));
else
if (0 == strcmp(head, "Inactive(anon):"))
valptr = &(mHr(Inactive_anon));
else
if (0 == strcmp(head, "Inactive(file):"))
valptr = &(mHr(Inactive_file));
break;
case 'K':
if (0 == strcmp(head, "KernelStack:"))
valptr = &(mHr(KernelStack));
break;
case 'L':
if (0 == strcmp(head, "LowFree:"))
valptr = &(mHr(LowFree));
else
if (0 == strcmp(head, "LowTotal:"))
valptr = &(mHr(LowTotal));
break;
case 'M':
if (0 == strcmp(head, "Mapped:"))
valptr = &(mHr(Mapped));
else
if (0 == strcmp(head, "MemAvailable:"))
valptr = &(mHr(MemAvailable));
else
if (0 == strcmp(head, "MemFree:"))
valptr = &(mHr(MemFree));
else
if (0 == strcmp(head, "MemTotal:"))
valptr = &(mHr(MemTotal));
else
if (0 == strcmp(head, "Mlocked:"))
valptr = &(mHr(Mlocked));
break;
case 'N':
if (0 == strcmp(head, "NFS_Unstable:"))
valptr = &(mHr(NFS_Unstable));
break;
case 'P':
if (0 == strcmp(head, "PageTables:"))
valptr = &(mHr(PageTables));
break;
case 'S':
if (0 == strcmp(head, "SReclaimable:"))
valptr = &(mHr(SReclaimable));
else
if (0 == strcmp(head, "SUnreclaim:"))
valptr = &(mHr(SUnreclaim));
else
if (0 == strcmp(head, "Shmem:"))
valptr = &(mHr(Shmem));
else
if (0 == strcmp(head, "Slab:"))
valptr = &(mHr(Slab));
else
if (0 == strcmp(head, "SwapCached:"))
valptr = &(mHr(SwapCached));
else
if (0 == strcmp(head, "SwapFree:"))
valptr = &(mHr(SwapFree));
else
if (0 == strcmp(head, "SwapTotal:"))
valptr = &(mHr(SwapTotal));
break;
case 'U':
if (0 == strcmp(head, "Unevictable:"))
valptr = &(mHr(Unevictable));
break;
case 'V':
if (0 == strcmp(head, "VmallocChunk:"))
valptr = &(mHr(VmallocChunk));
else
if (0 == strcmp(head, "VmallocTotal:"))
valptr = &(mHr(VmallocTotal));
else
if (0 == strcmp(head, "VmallocUsed:"))
valptr = &(mHr(VmallocUsed));
break;
case 'W':
if (0 == strcmp(head, "Writeback:"))
valptr = &(mHr(Writeback));
else
if (0 == strcmp(head, "WritebackTmp:"))
valptr = &(mHr(WritebackTmp));
break;
default:
break;
}
e.key = head;
if (hsearch_r(e, FIND, &ep, &info->hashtab))
valptr = ep->data;
head = tail+1;
if (valptr) {
*valptr = strtoul(head, &tail, 10);
}
tail = strchr(head, '\n');
if (!tail)
break;
head = tail + 1;
} while(tail);
@ -817,7 +745,7 @@ PROCPS_EXPORT int read_meminfo_failed (
// let's not distort the deltas the first time thru ...
if (!info->meminfo_was_read)
memcpy(&info->mem_hist.old, &info->mem_hist.new, sizeof(struct meminfo_data));
memcpy(&info->hist.old, &info->hist.new, sizeof(struct meminfo_data));
info->meminfo_was_read = 1;
return 0;
@ -902,6 +830,7 @@ PROCPS_EXPORT int procps_meminfo_new (
struct procps_meminfo **info)
{
struct procps_meminfo *p;
int rc;
if (info == NULL || *info != NULL)
return -EINVAL;
@ -911,6 +840,11 @@ PROCPS_EXPORT int procps_meminfo_new (
p->refcount = 1;
p->meminfo_fd = -1;
if ((rc = make_hash_failed(p))) {
free(p);
return rc;
}
*info = p;
return 0;
} // end: procps_meminfo_new
@ -939,6 +873,7 @@ PROCPS_EXPORT int procps_meminfo_unref (
extents_free_all((*info));
if ((*info)->items)
free((*info)->items);
hdestroy_r(&(*info)->hashtab);
free(*info);
*info = NULL;
return 0;
@ -1014,7 +949,7 @@ PROCPS_EXPORT struct meminfo_stack *procps_meminfo_select (
if (read_meminfo_failed(info))
return NULL;
assign_results(info->extents->stacks[0], &info->mem_hist);
assign_results(info->extents->stacks[0], &info->hist);
info->dirty_stacks = 1;
return info->extents->stacks[0];