diff options
Diffstat (limited to 'library/meminfo.c')
-rw-r--r-- | library/meminfo.c | 1016 |
1 files changed, 1016 insertions, 0 deletions
diff --git a/library/meminfo.c b/library/meminfo.c new file mode 100644 index 0000000..4289a23 --- /dev/null +++ b/library/meminfo.c @@ -0,0 +1,1016 @@ +/* + * meminfo.c - memory 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 <search.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 "procps-private.h" +#include "meminfo.h" + + +#define MEMINFO_FILE "/proc/meminfo" +#define MEMINFO_BUFF 8192 + +/* ------------------------------------------------------------------------- + + 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 //----------------------------------------------- | +// ------------------------------------------------------------------------- + + + +struct meminfo_data { + unsigned long Active; + unsigned long Active_anon; // as: Active(anon): man 5 proc: 'to be documented' + unsigned long Active_file; // as: Active(file): man 5 proc: 'to be documented' + unsigned long AnonHugePages; + unsigned long AnonPages; + unsigned long Bounce; + unsigned long Buffers; + unsigned long Cached; + unsigned long CmaFree; + unsigned long CmaTotal; + unsigned long CommitLimit; + unsigned long Committed_AS; + unsigned long DirectMap1G; + unsigned long DirectMap2M; + unsigned long DirectMap4M; + unsigned long DirectMap4k; + unsigned long Dirty; + unsigned long FileHugePages; + unsigned long FilePmdMapped; + unsigned long HardwareCorrupted; // man 5 proc: 'to be documented' + unsigned long HighFree; + unsigned long HighTotal; + unsigned long HugePages_Free; + unsigned long HugePages_Rsvd; + unsigned long HugePages_Surp; + unsigned long HugePages_Total; + unsigned long Hugepagesize; + unsigned long Hugetlb; + unsigned long Inactive; + unsigned long Inactive_anon; // as: Inactive(anon): man 5 proc: 'to be documented' + unsigned long Inactive_file; // as: Inactive(file): man 5 proc: 'to be documented' + unsigned long KReclaimable; + unsigned long KernelStack; + unsigned long LowFree; + unsigned long LowTotal; + unsigned long Mapped; + unsigned long MemAvailable; + unsigned long MemFree; + unsigned long MemTotal; + unsigned long Mlocked; // man 5 proc: 'to be documented' + unsigned long MmapCopy; // man 5 proc: 'to be documented' + unsigned long NFS_Unstable; + unsigned long PageTables; + unsigned long Percpu; + unsigned long SReclaimable; + unsigned long SUnreclaim; + unsigned long ShadowCallStack; + unsigned long Shmem; + unsigned long ShmemHugePages; + unsigned long ShmemPmdMapped; + unsigned long Slab; + unsigned long SwapCached; + unsigned long SwapFree; + unsigned long SwapTotal; + unsigned long Unevictable; // man 5 proc: 'to be documented' + unsigned long VmallocChunk; + unsigned long VmallocTotal; + unsigned long VmallocUsed; + unsigned long Writeback; + unsigned long WritebackTmp; + + unsigned long derived_mem_cached; + unsigned long derived_mem_hi_used; + unsigned long derived_mem_lo_used; + unsigned long derived_mem_used; + unsigned long derived_swap_used; +}; + +struct mem_hist { + struct meminfo_data new; + struct meminfo_data old; +}; + +struct stacks_extent { + int ext_numstacks; + struct stacks_extent *next; + struct meminfo_stack **stacks; +}; + +struct meminfo_info { + int refcount; + int meminfo_fd; + struct mem_hist hist; + int numitems; + enum meminfo_item *items; + struct stacks_extent *extents; + struct hsearch_data hashtab; + struct meminfo_result get_this; + time_t sav_secs; +}; + + +// ___ Results 'Set' Support |||||||||||||||||||||||||||||||||||||||||||||||||| + +#define setNAME(e) set_meminfo_ ## e +#define setDECL(e) static void setNAME(e) \ + (struct meminfo_result *R, struct mem_hist *H) + +// regular assignment +#define MEM_set(e,t,x) setDECL(e) { R->result. t = H->new. x; } +// delta assignment +#define HST_set(e,t,x) setDECL(e) { R->result. t = ( H->new. x - H->old. x ); } + +setDECL(noop) { (void)R; (void)H; } +setDECL(extra) { (void)H; R->result.ul_int = 0; } + +MEM_set(MEM_ACTIVE, ul_int, Active) +MEM_set(MEM_ACTIVE_ANON, ul_int, Active_anon) +MEM_set(MEM_ACTIVE_FILE, ul_int, Active_file) +MEM_set(MEM_ANON, ul_int, AnonPages) +MEM_set(MEM_AVAILABLE, ul_int, MemAvailable) +MEM_set(MEM_BOUNCE, ul_int, Bounce) +MEM_set(MEM_BUFFERS, ul_int, Buffers) +MEM_set(MEM_CACHED, ul_int, Cached) +MEM_set(MEM_CACHED_ALL, ul_int, derived_mem_cached) +MEM_set(MEM_CMA_FREE, ul_int, CmaFree) +MEM_set(MEM_CMA_TOTAL, ul_int, CmaTotal) +MEM_set(MEM_COMMITTED_AS, ul_int, Committed_AS) +MEM_set(MEM_COMMIT_LIMIT, ul_int, CommitLimit) +MEM_set(MEM_DIRECTMAP_1G, ul_int, DirectMap1G) +MEM_set(MEM_DIRECTMAP_2M, ul_int, DirectMap2M) +MEM_set(MEM_DIRECTMAP_4K, ul_int, DirectMap4k) +MEM_set(MEM_DIRECTMAP_4M, ul_int, DirectMap4M) +MEM_set(MEM_DIRTY, ul_int, Dirty) +MEM_set(MEM_FILE_HUGEPAGES, ul_int, FileHugePages) +MEM_set(MEM_FILE_PMDMAPPED, ul_int, FilePmdMapped) +MEM_set(MEM_FREE, ul_int, MemFree) +MEM_set(MEM_HARD_CORRUPTED, ul_int, HardwareCorrupted) +MEM_set(MEM_HIGH_FREE, ul_int, HighFree) +MEM_set(MEM_HIGH_TOTAL, ul_int, HighTotal) +MEM_set(MEM_HIGH_USED, ul_int, derived_mem_hi_used) +MEM_set(MEM_HUGETBL, ul_int, Hugetlb) +MEM_set(MEM_HUGE_ANON, ul_int, AnonHugePages) +MEM_set(MEM_HUGE_FREE, ul_int, HugePages_Free) +MEM_set(MEM_HUGE_RSVD, ul_int, HugePages_Rsvd) +MEM_set(MEM_HUGE_SIZE, ul_int, Hugepagesize) +MEM_set(MEM_HUGE_SURPLUS, ul_int, HugePages_Surp) +MEM_set(MEM_HUGE_TOTAL, ul_int, HugePages_Total) +MEM_set(MEM_INACTIVE, ul_int, Inactive) +MEM_set(MEM_INACTIVE_ANON, ul_int, Inactive_anon) +MEM_set(MEM_INACTIVE_FILE, ul_int, Inactive_file) +MEM_set(MEM_KERNEL_RECLAIM, ul_int, KReclaimable) +MEM_set(MEM_KERNEL_STACK, ul_int, KernelStack) +MEM_set(MEM_LOCKED, ul_int, Mlocked) +MEM_set(MEM_LOW_FREE, ul_int, LowFree) +MEM_set(MEM_LOW_TOTAL, ul_int, LowTotal) +MEM_set(MEM_LOW_USED, ul_int, derived_mem_lo_used) +MEM_set(MEM_MAPPED, ul_int, Mapped) +MEM_set(MEM_MAP_COPY, ul_int, MmapCopy) +MEM_set(MEM_NFS_UNSTABLE, ul_int, NFS_Unstable) +MEM_set(MEM_PAGE_TABLES, ul_int, PageTables) +MEM_set(MEM_PER_CPU, ul_int, Percpu) +MEM_set(MEM_SHADOWCALLSTACK, ul_int, ShadowCallStack) +MEM_set(MEM_SHARED, ul_int, Shmem) +MEM_set(MEM_SHMEM_HUGE, ul_int, ShmemHugePages) +MEM_set(MEM_SHMEM_HUGE_MAP, ul_int, ShmemPmdMapped) +MEM_set(MEM_SLAB, ul_int, Slab) +MEM_set(MEM_SLAB_RECLAIM, ul_int, SReclaimable) +MEM_set(MEM_SLAB_UNRECLAIM, ul_int, SUnreclaim) +MEM_set(MEM_TOTAL, ul_int, MemTotal) +MEM_set(MEM_UNEVICTABLE, ul_int, Unevictable) +MEM_set(MEM_USED, ul_int, derived_mem_used) +MEM_set(MEM_VM_ALLOC_CHUNK, ul_int, VmallocChunk) +MEM_set(MEM_VM_ALLOC_TOTAL, ul_int, VmallocTotal) +MEM_set(MEM_VM_ALLOC_USED, ul_int, VmallocUsed) +MEM_set(MEM_WRITEBACK, ul_int, Writeback) +MEM_set(MEM_WRITEBACK_TMP, ul_int, WritebackTmp) + +HST_set(DELTA_ACTIVE, s_int, Active) +HST_set(DELTA_ACTIVE_ANON, s_int, Active_anon) +HST_set(DELTA_ACTIVE_FILE, s_int, Active_file) +HST_set(DELTA_ANON, s_int, AnonPages) +HST_set(DELTA_AVAILABLE, s_int, MemAvailable) +HST_set(DELTA_BOUNCE, s_int, Bounce) +HST_set(DELTA_BUFFERS, s_int, Buffers) +HST_set(DELTA_CACHED, s_int, Cached) +HST_set(DELTA_CACHED_ALL, s_int, derived_mem_cached) +HST_set(DELTA_CMA_FREE, s_int, CmaFree) +HST_set(DELTA_CMA_TOTAL, s_int, CmaTotal) +HST_set(DELTA_COMMITTED_AS, s_int, Committed_AS) +HST_set(DELTA_COMMIT_LIMIT, s_int, CommitLimit) +HST_set(DELTA_DIRECTMAP_1G, s_int, DirectMap1G) +HST_set(DELTA_DIRECTMAP_2M, s_int, DirectMap2M) +HST_set(DELTA_DIRECTMAP_4K, s_int, DirectMap4k) +HST_set(DELTA_DIRECTMAP_4M, s_int, DirectMap4M) +HST_set(DELTA_DIRTY, s_int, Dirty) +HST_set(DELTA_FILE_HUGEPAGES, s_int, FileHugePages) +HST_set(DELTA_FILE_PMDMAPPED, s_int, FilePmdMapped) +HST_set(DELTA_FREE, s_int, MemFree) +HST_set(DELTA_HARD_CORRUPTED, s_int, HardwareCorrupted) +HST_set(DELTA_HIGH_FREE, s_int, HighFree) +HST_set(DELTA_HIGH_TOTAL, s_int, HighTotal) +HST_set(DELTA_HIGH_USED, s_int, derived_mem_hi_used) +HST_set(DELTA_HUGETBL, s_int, Hugetlb) +HST_set(DELTA_HUGE_ANON, s_int, AnonHugePages) +HST_set(DELTA_HUGE_FREE, s_int, HugePages_Free) +HST_set(DELTA_HUGE_RSVD, s_int, HugePages_Rsvd) +HST_set(DELTA_HUGE_SIZE, s_int, Hugepagesize) +HST_set(DELTA_HUGE_SURPLUS, s_int, HugePages_Surp) +HST_set(DELTA_HUGE_TOTAL, s_int, HugePages_Total) +HST_set(DELTA_INACTIVE, s_int, Inactive) +HST_set(DELTA_INACTIVE_ANON, s_int, Inactive_anon) +HST_set(DELTA_INACTIVE_FILE, s_int, Inactive_file) +HST_set(DELTA_KERNEL_RECLAIM, s_int, KReclaimable) +HST_set(DELTA_KERNEL_STACK, s_int, KernelStack) +HST_set(DELTA_LOCKED, s_int, Mlocked) +HST_set(DELTA_LOW_FREE, s_int, LowFree) +HST_set(DELTA_LOW_TOTAL, s_int, LowTotal) +HST_set(DELTA_LOW_USED, s_int, derived_mem_lo_used) +HST_set(DELTA_MAPPED, s_int, Mapped) +HST_set(DELTA_MAP_COPY, s_int, MmapCopy) +HST_set(DELTA_NFS_UNSTABLE, s_int, NFS_Unstable) +HST_set(DELTA_PAGE_TABLES, s_int, PageTables) +HST_set(DELTA_PER_CPU, s_int, Percpu) +HST_set(DELTA_SHADOWCALLSTACK, s_int, ShadowCallStack) +HST_set(DELTA_SHARED, s_int, Shmem) +HST_set(DELTA_SHMEM_HUGE, s_int, ShmemHugePages) +HST_set(DELTA_SHMEM_HUGE_MAP, s_int, ShmemPmdMapped) +HST_set(DELTA_SLAB, s_int, Slab) +HST_set(DELTA_SLAB_RECLAIM, s_int, SReclaimable) +HST_set(DELTA_SLAB_UNRECLAIM, s_int, SUnreclaim) +HST_set(DELTA_TOTAL, s_int, MemTotal) +HST_set(DELTA_UNEVICTABLE, s_int, Unevictable) +HST_set(DELTA_USED, s_int, derived_mem_used) +HST_set(DELTA_VM_ALLOC_CHUNK, s_int, VmallocChunk) +HST_set(DELTA_VM_ALLOC_TOTAL, s_int, VmallocTotal) +HST_set(DELTA_VM_ALLOC_USED, s_int, VmallocUsed) +HST_set(DELTA_WRITEBACK, s_int, Writeback) +HST_set(DELTA_WRITEBACK_TMP, s_int, WritebackTmp) + +MEM_set(SWAP_CACHED, ul_int, SwapCached) +MEM_set(SWAP_FREE, ul_int, SwapFree) +MEM_set(SWAP_TOTAL, ul_int, SwapTotal) +MEM_set(SWAP_USED, ul_int, derived_swap_used) + +HST_set(SWAP_DELTA_CACHED, s_int, SwapCached) +HST_set(SWAP_DELTA_FREE, s_int, SwapFree) +HST_set(SWAP_DELTA_TOTAL, s_int, SwapTotal) +HST_set(SWAP_DELTA_USED, s_int, derived_swap_used) + +#undef setDECL +#undef MEM_set +#undef HST_set + + +// ___ Controlling Table |||||||||||||||||||||||||||||||||||||||||||||||||||||| + +typedef void (*SET_t)(struct meminfo_result *, struct mem_hist *); +#ifdef ITEMTABLE_DEBUG +#define RS(e) (SET_t)setNAME(e), MEMINFO_ ## e, STRINGIFY(MEMINFO_ ## e) +#else +#define RS(e) (SET_t)setNAME(e) +#endif + +#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 meminfo_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 + char *type2str; // the result type as a string value +} Item_table[] = { +/* setsfunc type2str + ------------------------- ---------- */ + { RS(noop), TS_noop }, + { RS(extra), TS_noop }, + + { RS(MEM_ACTIVE), TS(ul_int) }, + { RS(MEM_ACTIVE_ANON), TS(ul_int) }, + { RS(MEM_ACTIVE_FILE), TS(ul_int) }, + { RS(MEM_ANON), TS(ul_int) }, + { RS(MEM_AVAILABLE), TS(ul_int) }, + { RS(MEM_BOUNCE), TS(ul_int) }, + { RS(MEM_BUFFERS), TS(ul_int) }, + { RS(MEM_CACHED), TS(ul_int) }, + { RS(MEM_CACHED_ALL), TS(ul_int) }, + { RS(MEM_CMA_FREE), TS(ul_int) }, + { RS(MEM_CMA_TOTAL), TS(ul_int) }, + { RS(MEM_COMMITTED_AS), TS(ul_int) }, + { RS(MEM_COMMIT_LIMIT), TS(ul_int) }, + { RS(MEM_DIRECTMAP_1G), TS(ul_int) }, + { RS(MEM_DIRECTMAP_2M), TS(ul_int) }, + { RS(MEM_DIRECTMAP_4K), TS(ul_int) }, + { RS(MEM_DIRECTMAP_4M), TS(ul_int) }, + { RS(MEM_DIRTY), TS(ul_int) }, + { RS(MEM_FILE_HUGEPAGES), TS(ul_int) }, + { RS(MEM_FILE_PMDMAPPED), TS(ul_int) }, + { RS(MEM_FREE), TS(ul_int) }, + { RS(MEM_HARD_CORRUPTED), TS(ul_int) }, + { RS(MEM_HIGH_FREE), TS(ul_int) }, + { RS(MEM_HIGH_TOTAL), TS(ul_int) }, + { RS(MEM_HIGH_USED), TS(ul_int) }, + { RS(MEM_HUGETBL), TS(ul_int) }, + { RS(MEM_HUGE_ANON), TS(ul_int) }, + { RS(MEM_HUGE_FREE), TS(ul_int) }, + { RS(MEM_HUGE_RSVD), TS(ul_int) }, + { RS(MEM_HUGE_SIZE), TS(ul_int) }, + { RS(MEM_HUGE_SURPLUS), TS(ul_int) }, + { RS(MEM_HUGE_TOTAL), TS(ul_int) }, + { RS(MEM_INACTIVE), TS(ul_int) }, + { RS(MEM_INACTIVE_ANON), TS(ul_int) }, + { RS(MEM_INACTIVE_FILE), TS(ul_int) }, + { RS(MEM_KERNEL_RECLAIM), TS(ul_int) }, + { RS(MEM_KERNEL_STACK), TS(ul_int) }, + { RS(MEM_LOCKED), TS(ul_int) }, + { RS(MEM_LOW_FREE), TS(ul_int) }, + { RS(MEM_LOW_TOTAL), TS(ul_int) }, + { RS(MEM_LOW_USED), TS(ul_int) }, + { RS(MEM_MAPPED), TS(ul_int) }, + { RS(MEM_MAP_COPY), TS(ul_int) }, + { RS(MEM_NFS_UNSTABLE), TS(ul_int) }, + { RS(MEM_PAGE_TABLES), TS(ul_int) }, + { RS(MEM_PER_CPU), TS(ul_int) }, + { RS(MEM_SHADOWCALLSTACK), TS(ul_int) }, + { RS(MEM_SHARED), TS(ul_int) }, + { RS(MEM_SHMEM_HUGE), TS(ul_int) }, + { RS(MEM_SHMEM_HUGE_MAP), TS(ul_int) }, + { RS(MEM_SLAB), TS(ul_int) }, + { RS(MEM_SLAB_RECLAIM), TS(ul_int) }, + { RS(MEM_SLAB_UNRECLAIM), TS(ul_int) }, + { RS(MEM_TOTAL), TS(ul_int) }, + { RS(MEM_UNEVICTABLE), TS(ul_int) }, + { RS(MEM_USED), TS(ul_int) }, + { RS(MEM_VM_ALLOC_CHUNK), TS(ul_int) }, + { RS(MEM_VM_ALLOC_TOTAL), TS(ul_int) }, + { RS(MEM_VM_ALLOC_USED), TS(ul_int) }, + { RS(MEM_WRITEBACK), TS(ul_int) }, + { RS(MEM_WRITEBACK_TMP), TS(ul_int) }, + + { RS(DELTA_ACTIVE), TS(s_int) }, + { RS(DELTA_ACTIVE_ANON), TS(s_int) }, + { RS(DELTA_ACTIVE_FILE), TS(s_int) }, + { RS(DELTA_ANON), TS(s_int) }, + { RS(DELTA_AVAILABLE), TS(s_int) }, + { RS(DELTA_BOUNCE), TS(s_int) }, + { RS(DELTA_BUFFERS), TS(s_int) }, + { RS(DELTA_CACHED), TS(s_int) }, + { RS(DELTA_CACHED_ALL), TS(s_int) }, + { RS(DELTA_CMA_FREE), TS(s_int) }, + { RS(DELTA_CMA_TOTAL), TS(s_int) }, + { RS(DELTA_COMMITTED_AS), TS(s_int) }, + { RS(DELTA_COMMIT_LIMIT), TS(s_int) }, + { RS(DELTA_DIRECTMAP_1G), TS(s_int) }, + { RS(DELTA_DIRECTMAP_2M), TS(s_int) }, + { RS(DELTA_DIRECTMAP_4K), TS(s_int) }, + { RS(DELTA_DIRECTMAP_4M), TS(s_int) }, + { RS(DELTA_DIRTY), TS(s_int) }, + { RS(DELTA_FILE_HUGEPAGES), TS(s_int) }, + { RS(DELTA_FILE_PMDMAPPED), TS(s_int) }, + { RS(DELTA_FREE), TS(s_int) }, + { RS(DELTA_HARD_CORRUPTED), TS(s_int) }, + { RS(DELTA_HIGH_FREE), TS(s_int) }, + { RS(DELTA_HIGH_TOTAL), TS(s_int) }, + { RS(DELTA_HIGH_USED), TS(s_int) }, + { RS(DELTA_HUGETBL), TS(s_int) }, + { RS(DELTA_HUGE_ANON), TS(s_int) }, + { RS(DELTA_HUGE_FREE), TS(s_int) }, + { RS(DELTA_HUGE_RSVD), TS(s_int) }, + { RS(DELTA_HUGE_SIZE), TS(s_int) }, + { RS(DELTA_HUGE_SURPLUS), TS(s_int) }, + { RS(DELTA_HUGE_TOTAL), TS(s_int) }, + { RS(DELTA_INACTIVE), TS(s_int) }, + { RS(DELTA_INACTIVE_ANON), TS(s_int) }, + { RS(DELTA_INACTIVE_FILE), TS(s_int) }, + { RS(DELTA_KERNEL_RECLAIM), TS(s_int) }, + { RS(DELTA_KERNEL_STACK), TS(s_int) }, + { RS(DELTA_LOCKED), TS(s_int) }, + { RS(DELTA_LOW_FREE), TS(s_int) }, + { RS(DELTA_LOW_TOTAL), TS(s_int) }, + { RS(DELTA_LOW_USED), TS(s_int) }, + { RS(DELTA_MAPPED), TS(s_int) }, + { RS(DELTA_MAP_COPY), TS(s_int) }, + { RS(DELTA_NFS_UNSTABLE), TS(s_int) }, + { RS(DELTA_PAGE_TABLES), TS(s_int) }, + { RS(DELTA_PER_CPU), TS(s_int) }, + { RS(DELTA_SHADOWCALLSTACK), TS(s_int) }, + { RS(DELTA_SHARED), TS(s_int) }, + { RS(DELTA_SHMEM_HUGE), TS(s_int) }, + { RS(DELTA_SHMEM_HUGE_MAP), TS(s_int) }, + { RS(DELTA_SLAB), TS(s_int) }, + { RS(DELTA_SLAB_RECLAIM), TS(s_int) }, + { RS(DELTA_SLAB_UNRECLAIM), TS(s_int) }, + { RS(DELTA_TOTAL), TS(s_int) }, + { RS(DELTA_UNEVICTABLE), TS(s_int) }, + { RS(DELTA_USED), TS(s_int) }, + { RS(DELTA_VM_ALLOC_CHUNK), TS(s_int) }, + { RS(DELTA_VM_ALLOC_TOTAL), TS(s_int) }, + { RS(DELTA_VM_ALLOC_USED), TS(s_int) }, + { RS(DELTA_WRITEBACK), TS(s_int) }, + { RS(DELTA_WRITEBACK_TMP), TS(s_int) }, + + { RS(SWAP_CACHED), TS(ul_int) }, + { RS(SWAP_FREE), TS(ul_int) }, + { RS(SWAP_TOTAL), TS(ul_int) }, + { RS(SWAP_USED), TS(ul_int) }, + + { RS(SWAP_DELTA_CACHED), TS(s_int) }, + { RS(SWAP_DELTA_FREE), TS(s_int) }, + { RS(SWAP_DELTA_TOTAL), TS(s_int) }, + { RS(SWAP_DELTA_USED), TS(s_int) }, +}; + + /* please note, + * this enum MUST be 1 greater than the highest value of any enum */ +enum meminfo_item MEMINFO_logical_end = MAXTABLE(Item_table); + +#undef setNAME +#undef RS + + +// ___ Private Functions |||||||||||||||||||||||||||||||||||||||||||||||||||||| + +static inline void meminfo_assign_results ( + struct meminfo_stack *stack, + struct mem_hist *hist) +{ + struct meminfo_result *this = stack->head; + + for (;;) { + enum meminfo_item item = this->item; + if (item >= MEMINFO_logical_end) + break; + Item_table[item].setsfunc(this, hist); + ++this; + } + return; +} // end: meminfo_assign_results + + +static void meminfo_extents_free_all ( + struct meminfo_info *info) +{ + while (info->extents) { + struct stacks_extent *p = info->extents; + info->extents = info->extents->next; + free(p); + }; +} // end: meminfo_extents_free_all + + +static inline struct meminfo_result *meminfo_itemize_stack ( + struct meminfo_result *p, + int depth, + enum meminfo_item *items) +{ + struct meminfo_result *p_sav = p; + int i; + + for (i = 0; i < depth; i++) { + p->item = items[i]; + ++p; + } + return p_sav; +} // end: meminfo_itemize_stack + + +static inline int meminfo_items_check_failed ( + int numitems, + enum meminfo_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 meminfo_item *' + * my_stack = procps_meminfo_select(info, MEMINFO_noop, num); + * ^~~~~~~~~~~~~~~~ + */ + if (numitems < 1 + || (void *)items < (void *)(unsigned long)(2 * MEMINFO_logical_end)) + return 1; + + for (i = 0; i < numitems; i++) { + // a meminfo_item is currently unsigned, but we'll protect our future + if (items[i] < 0) + return 1; + if (items[i] >= MEMINFO_logical_end) + return 1; + } + + return 0; +} // end: meminfo_items_check_failed + + +static int meminfo_make_hash_failed ( + struct meminfo_info *info) +{ + #define htVAL(f) e.key = STRINGIFY(f); e.data = &info->hist.new. f; \ + if (!hsearch_r(e, ENTER, &ep, &info->hashtab)) return 1; + #define htXTRA(k,f) e.key = STRINGIFY(k); e.data = &info->hist.new. f; \ + if (!hsearch_r(e, ENTER, &ep, &info->hashtab)) return 1; + ENTRY e, *ep; + size_t n; + + // will also include those derived fields (more is better) + n = sizeof(struct meminfo_data) / sizeof(unsigned long); + // we'll follow the hsearch recommendation of an extra 25% + if (!hcreate_r(n + (n / 4), &info->hashtab)) + return 1; + + 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(DirectMap4M) + htVAL(DirectMap4k) + htVAL(Dirty) + htVAL(FileHugePages) + htVAL(FilePmdMapped) + htVAL(HardwareCorrupted) + htVAL(HighFree) + htVAL(HighTotal) + htVAL(HugePages_Free) + htVAL(HugePages_Rsvd) + htVAL(HugePages_Surp) + htVAL(HugePages_Total) + htVAL(Hugepagesize) + htVAL(Hugetlb) + htVAL(Inactive) + htXTRA(Inactive(anon), Inactive_anon) + htXTRA(Inactive(file), Inactive_file) + htVAL(KReclaimable) + htVAL(KernelStack) + htVAL(LowFree) + htVAL(LowTotal) + htVAL(Mapped) + htVAL(MemAvailable) + htVAL(MemFree) + htVAL(MemTotal) + htVAL(Mlocked) + htVAL(MmapCopy) + htVAL(NFS_Unstable) + htVAL(PageTables) + htVAL(Percpu) + htVAL(SReclaimable) + htVAL(SUnreclaim) + htVAL(ShadowCallStack) + htVAL(Shmem) + htVAL(ShmemHugePages) + htVAL(ShmemPmdMapped) + 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: meminfo_make_hash_failed + + +/* + * meminfo_read_failed(): + * + * Read the data out of /proc/meminfo putting the information + * into the supplied info structure + */ +static int meminfo_read_failed ( + struct meminfo_info *info) +{ + /* a 'memory history reference' macro for readability, + so we can focus the field names ... */ + #define mHr(f) info->hist.new. f + char buf[MEMINFO_BUFF]; + char *head, *tail; + int size; + unsigned long *valptr; + signed long mem_used; + + // remember history from last time around + memcpy(&info->hist.old, &info->hist.new, sizeof(struct meminfo_data)); + // clear out the soon to be 'current' values + memset(&info->hist.new, 0, sizeof(struct meminfo_data)); + + if (-1 == info->meminfo_fd + && (-1 == (info->meminfo_fd = open(MEMINFO_FILE, O_RDONLY)))) + return 1; + + if (lseek(info->meminfo_fd, 0L, SEEK_SET) == -1) + return 1; + + for (;;) { + if ((size = read(info->meminfo_fd, buf, sizeof(buf)-1)) < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return 1; + } + break; + } + if (size == 0) { + errno = EIO; + return 1; + } + buf[size] = '\0'; + + head = buf; + + for (;;) { + static __thread ENTRY e; // keep coverity off our backs (e.data) + ENTRY *ep; + + if (!(tail = strchr(head, ':'))) + break; + *tail = '\0'; + valptr = NULL; + + e.key = head; + if (hsearch_r(e, FIND, &ep, &info->hashtab)) + valptr = ep->data; + head = tail + 1; + if (valptr) + *valptr = strtoul(head, NULL, 10); + + if (!(tail = strchr(head, '\n'))) + break; + head = tail + 1; + } + + if (0 == mHr(MemAvailable)) + mHr(MemAvailable) = mHr(MemFree); + mHr(derived_mem_cached) = mHr(Cached) + mHr(SReclaimable); + + /* if 'available' is greater than 'total' or our calculation of mem_used + overflows, that's symptomatic of running within a lxc container where + such values will be dramatically distorted over those of the host. */ + if (mHr(MemAvailable) > mHr(MemTotal)) + mHr(MemAvailable) = mHr(MemFree); + mem_used = mHr(MemTotal) - mHr(MemAvailable); + if (mem_used < 0) + mem_used = mHr(MemTotal) - mHr(MemFree); + mHr(derived_mem_used) = (unsigned long)mem_used; + + if (mHr(HighFree) < mHr(HighTotal)) + mHr(derived_mem_hi_used) = mHr(HighTotal) - mHr(HighFree); + + if (0 == mHr(LowTotal)) { + mHr(LowTotal) = mHr(MemTotal); + mHr(LowFree) = mHr(MemFree); + } + if (mHr(LowFree) < mHr(LowTotal)) + mHr(derived_mem_lo_used) = mHr(LowTotal) - mHr(LowFree); + + if (mHr(SwapFree) < mHr(SwapTotal)) + mHr(derived_swap_used) = mHr(SwapTotal) - mHr(SwapFree); + + return 0; + #undef mHr +} // end: meminfo_read_failed + + +/* + * meminfo_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 stacks_extent struct anchoring the 'heads' of each new stack. + */ +static struct stacks_extent *meminfo_stacks_alloc ( + struct meminfo_info *info, + int maxstacks) +{ + struct stacks_extent *p_blob; + struct meminfo_stack **p_vect; + struct meminfo_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 meminfo_stack); // size of that head struct | + list_size = sizeof(struct meminfo_result)*info->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 this memory is allocated in a single blob, facilitating a later free(). | + as a minimum, it is important that the result structures themselves always are | + contiguous within each stack since they're accessed through relative position. | */ + if (NULL == (p_blob = calloc(1, blob_size))) + return NULL; + + p_blob->next = info->extents; // push this extent onto... | + info->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 meminfo_stack *)v_head; + p_head->head = meminfo_itemize_stack((struct meminfo_result *)v_list, info->numitems, info->items); + p_blob->stacks[i] = p_head; + v_list += list_size; + v_head += head_size; + } + p_blob->ext_numstacks = maxstacks; + return p_blob; +} // end: meminfo_stacks_alloc + + +// ___ Public Functions ||||||||||||||||||||||||||||||||||||||||||||||||||||||| + +// --- standard required functions -------------------------------------------- + +/* + * procps_meminfo_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_meminfo_new ( + struct meminfo_info **info) +{ + struct meminfo_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 meminfo_info)))) + return -ENOMEM; + + p->refcount = 1; + p->meminfo_fd = -1; + + if (meminfo_make_hash_failed(p)) { + free(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 (meminfo_read_failed(p)) { + procps_meminfo_unref(&p); + return -errno; + } + + *info = p; + return 0; +} // end: procps_meminfo_new + + +PROCPS_EXPORT int procps_meminfo_ref ( + struct meminfo_info *info) +{ + if (info == NULL) + return -EINVAL; + + info->refcount++; + return info->refcount; +} // end: procps_meminfo_ref + + +PROCPS_EXPORT int procps_meminfo_unref ( + struct meminfo_info **info) +{ + if (info == NULL || *info == NULL) + return -EINVAL; + + (*info)->refcount--; + + if ((*info)->refcount < 1) { + int errno_sav = errno; + + if ((*info)->meminfo_fd != -1) + close((*info)->meminfo_fd); + + if ((*info)->extents) + meminfo_extents_free_all((*info)); + if ((*info)->items) + free((*info)->items); + hdestroy_r(&(*info)->hashtab); + + free(*info); + *info = NULL; + + errno = errno_sav; + return 0; + } + return (*info)->refcount; +} // end: procps_meminfo_unref + + +// --- variable interface functions ------------------------------------------- + +PROCPS_EXPORT struct meminfo_result *procps_meminfo_get ( + struct meminfo_info *info, + enum meminfo_item item) +{ + time_t cur_secs; + + errno = EINVAL; + if (info == NULL) + return NULL; + if (item < 0 || item >= MEMINFO_logical_end) + return NULL; + errno = 0; + + /* we will NOT read the meminfo 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 (meminfo_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.ul_int = 0; + Item_table[item].setsfunc(&info->get_this, &info->hist); + + return &info->get_this; +} // end: procps_meminfo_get + + +/* procps_meminfo_select(): + * + * Harvest all the requested MEM and/or SWAP information then return + * it in a results stack. + * + * Returns: pointer to a meminfo_stack struct on success, NULL on error. + */ +PROCPS_EXPORT struct meminfo_stack *procps_meminfo_select ( + struct meminfo_info *info, + enum meminfo_item *items, + int numitems) +{ + errno = EINVAL; + if (info == NULL || items == NULL) + return NULL; + if (meminfo_items_check_failed(numitems, items)) + return NULL; + errno = 0; + + /* is this the first time or have things changed since we were last called? + if so, gotta' redo all of our stacks stuff ... */ + if (info->numitems != numitems + 1 + || memcmp(info->items, items, sizeof(enum meminfo_item) * numitems)) { + // allow for our MEMINFO_logical_end + if (!(info->items = realloc(info->items, sizeof(enum meminfo_item) * (numitems + 1)))) + return NULL; + memcpy(info->items, items, sizeof(enum meminfo_item) * numitems); + info->items[numitems] = MEMINFO_logical_end; + info->numitems = numitems + 1; + if (info->extents) + meminfo_extents_free_all(info); + } + if (!info->extents + && (!meminfo_stacks_alloc(info, 1))) + return NULL; + + if (meminfo_read_failed(info)) + return NULL; + meminfo_assign_results(info->extents->stacks[0], &info->hist); + + return info->extents->stacks[0]; +} // end: procps_meminfo_select + + +// --- 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 meminfo_result *xtra_meminfo_get ( + struct meminfo_info *info, + enum meminfo_item actual_enum, + const char *typestr, + const char *file, + int lineno) +{ + struct meminfo_result *r = procps_meminfo_get(info, actual_enum); + + if (actual_enum < 0 || actual_enum >= MEMINFO_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_meminfo_get_ + + +PROCPS_EXPORT struct meminfo_result *xtra_meminfo_val ( + int relative_enum, + const char *typestr, + const struct meminfo_stack *stack, + struct meminfo_info *info, + const char *file, + int lineno) +{ + char *str; + int i; + + for (i = 0; stack->head[i].item < MEMINFO_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_meminfo_val |