/* * slab.c - slab related functions for libproc * * Chris Rivera * Robert Love * * Copyright (C) 2003 Chris Rivera * Copyright 2004, Albert Cahalan * Copyright (C) 2015 Craig Small * * 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 #include #include #include #include #include #include #include #include #include #include #include "procps-private.h" #define SLABINFO_FILE "/proc/slabinfo" #define SLABINFO_LINE_LEN 2048 #define SLAB_INFO_NAME_LEN 128 struct slabinfo_stats { unsigned long total_size; /* size of all objects */ unsigned long active_size; /* size of all active objects */ unsigned int nr_objs; /* number of objects, among all caches */ unsigned int nr_active_objs; /* number of active objects, among all caches */ unsigned int nr_pages; /* number of pages consumed by all objects */ unsigned int nr_slabs; /* number of slabs, among all caches */ unsigned int nr_active_slabs; /* number of active slabs, among all caches */ unsigned int nr_caches; /* number of caches */ unsigned int nr_active_caches; /* number of active caches */ unsigned int avg_obj_size; /* average object size */ unsigned int min_obj_size; /* size of smallest object */ unsigned int max_obj_size; /* size of largest object */ }; struct slabinfo_node { char name[SLAB_INFO_NAME_LEN]; /* name of this cache */ unsigned long cache_size; /* size of entire cache */ unsigned int nr_objs; /* number of objects in this cache */ unsigned int nr_active_objs; /* number of active objects */ unsigned int obj_size; /* size of each object */ unsigned int objs_per_slab; /* number of objects per slab */ unsigned int pages_per_slab; /* number of pages per slab */ unsigned int nr_slabs; /* number of slabs in this cache */ unsigned int nr_active_slabs; /* number of active slabs */ unsigned int use; /* percent full: total / active */ }; struct procps_slabinfo { int refcount; FILE *slabinfo_fp; struct slabinfo_stats stats; struct slabinfo_node *nodes; /* first slabnode of this list */ int nodes_alloc; /* nodes alloc()ed */ int nodes_used; /* nodes using alloced memory */ struct stacks_anchor *stacked; }; struct stack_vectors { struct stacks_anchor *owner; struct slabnode_stack **heads; }; struct stacks_anchor { int depth; int inuse; struct stack_vectors *vectors; struct stacks_anchor *self; struct stacks_anchor *next; }; /* * Zero out the slabnode data, keeping the memory allocated. */ static void slabnodes_clear ( struct procps_slabinfo *info) { if (info == NULL || info->nodes == NULL || info->nodes_alloc < 1) return; memset(info->nodes, 0, sizeof(struct slabinfo_node)*info->nodes_alloc); info->nodes_used = 0; } /* Alloc up more slabnode memory, if required */ static int slabnodes_alloc ( struct procps_slabinfo *info) { struct slabinfo_node *new_nodes; int new_count; if (info == NULL) return -EINVAL; if (info->nodes_used < info->nodes_alloc) return 0; /* Increment the allocated number of slabs */ new_count = info->nodes_alloc * 5/4+30; new_nodes = realloc(info->nodes, sizeof(struct slabinfo_node) * new_count); if (!new_nodes) return -ENOMEM; info->nodes = new_nodes; info->nodes_alloc = new_count; return 0; } /* * get_slabnode - allocate slab_info structures using a free list * * In the fast path, we simply return a node off the free list. In the slow * list, we malloc() a new node. The free list is never automatically reaped, * both for simplicity and because the number of slab caches is fairly * constant. */ static int get_slabnode ( struct procps_slabinfo *info, struct slabinfo_node **node) { int retval; if (!info) return -EINVAL; if (info->nodes_used == info->nodes_alloc) { if ((retval = slabnodes_alloc(info)) < 0) return retval; } *node = &(info->nodes[info->nodes_used++]); return 0; } /* parse_slabinfo20: * * sactual parse routine for slabinfo 2.x (2.6 kernels) * Note: difference between 2.0 and 2.1 is in the ": globalstat" part where version 2.1 * has extra column . We don't use ": globalstat" part in both versions. * * Formats (we don't use "statistics" extensions) * * slabinfo - version: 2.1 * # name \ * : tunables \ * : slabdata * * slabinfo - version: 2.1 (statistics) * # name \ * : tunables \ * : slabdata \ * : globalstat \ * : cpustat * * slabinfo - version: 2.0 * # name \ * : tunables \ * : slabdata * * slabinfo - version: 2.0 (statistics) * # name \ * : tunables \ * : slabdata \ * : globalstat \ * : cpustat */ static int parse_slabinfo20 ( struct procps_slabinfo *info) { struct slabinfo_node *node; char buffer[SLABINFO_LINE_LEN]; int retval; int page_size = getpagesize(); struct slabinfo_stats *stats = &(info->stats); stats->min_obj_size = INT_MAX; stats->max_obj_size = 0; while (fgets(buffer, SLABINFO_LINE_LEN, info->slabinfo_fp )) { if (buffer[0] == '#') continue; if ((retval = get_slabnode(info, &node)) < 0) return retval; if (sscanf(buffer, "%" STRINGIFY(SLAB_INFO_NAME_LEN) "s" \ "%u %u %u %u %u : tunables %*u %*u %*u : slabdata %u %u %*u", node->name, &node->nr_active_objs, &node->nr_objs, &node->obj_size, &node->objs_per_slab, &node->pages_per_slab, &node->nr_active_slabs, &node->nr_slabs) < 8) { if (errno != 0) return -errno; return -EINVAL; } if (!node->name[0]) snprintf(node->name, sizeof(node->name), "%s", "unknown"); if (node->obj_size < stats->min_obj_size) stats->min_obj_size = node->obj_size; if (node->obj_size > stats->max_obj_size) stats->max_obj_size = node->obj_size; node->cache_size = (unsigned long)node->nr_slabs * node->pages_per_slab * page_size; if (node->nr_objs) { node->use = (unsigned int)100 * (node->nr_active_objs / node->nr_objs); stats->nr_active_caches++; } else node->use = 0; stats->nr_objs += node->nr_objs; stats->nr_active_objs += node->nr_active_objs; stats->total_size += (unsigned long)node->nr_objs * node->obj_size; stats->active_size += (unsigned long)node->nr_active_objs * node->obj_size; stats->nr_pages += node->nr_slabs * node->pages_per_slab; stats->nr_slabs += node->nr_slabs; stats->nr_active_slabs += node->nr_active_slabs; stats->nr_caches++; } if (stats->nr_objs) stats->avg_obj_size = stats->total_size / stats->nr_objs; return 0; } /* * procps_slabinfo_new(): * * @info: location of returned new structure * * Returns: 0 on success <0 on failure */ PROCPS_EXPORT int procps_slabinfo_new ( struct procps_slabinfo **info) { struct procps_slabinfo *si; if (info == NULL) return -EINVAL; si = calloc(1, sizeof(struct procps_slabinfo)); if (!si) return -ENOMEM; si->refcount = 1; si->slabinfo_fp = NULL; si->nodes_alloc = 0; si->nodes_used = 0; si->nodes = NULL; *info = si; return 0; } /* procps_slabinfo_read(): * * Read the data out of /proc/slabinfo putting the information * into the supplied info container * * Returns: 0 on success, negative on error */ PROCPS_EXPORT int procps_slabinfo_read ( struct procps_slabinfo *info) { char line[SLABINFO_LINE_LEN]; int retval, major, minor; if (info == NULL) return -1; memset(&(info->stats), 0, sizeof(struct slabinfo_stats)); if ((retval = slabnodes_alloc(info)) < 0) return retval; slabnodes_clear(info); if (NULL == info->slabinfo_fp && (info->slabinfo_fp = fopen(SLABINFO_FILE, "r")) == NULL) return -errno; if (fseek(info->slabinfo_fp, 0L, SEEK_SET) < 0) return -errno; /* Parse the version string */ if (!fgets(line, SLABINFO_LINE_LEN, info->slabinfo_fp)) return -errno; if (sscanf(line, "slabinfo - version: %d.%d", &major, &minor) != 2) return -EINVAL; if (major == 2) retval = parse_slabinfo20(info); else return -ERANGE; return retval; } PROCPS_EXPORT int procps_slabinfo_ref ( struct procps_slabinfo *info) { if (info == NULL) return -EINVAL; info->refcount++; return info->refcount; } PROCPS_EXPORT int procps_slabinfo_unref ( struct procps_slabinfo **info) { if (info == NULL || *info == NULL) return -EINVAL; (*info)->refcount--; if ((*info)->refcount == 0) { if ((*info)->slabinfo_fp) { fclose((*info)->slabinfo_fp); (*info)->slabinfo_fp = NULL; } if ((*info)->stacked) { do { struct stacks_anchor *p = (*info)->stacked; (*info)->stacked = (*info)->stacked->next; free(p); } while((*info)->stacked); } free((*info)->nodes); free(*info); *info = NULL; return 0; } return (*info)->refcount; } PROCPS_EXPORT unsigned long procps_slabs_get ( struct procps_slabinfo *info, enum slabs_item item) { /* note: most of the results we might return are actually just unsigned int, but we must accommodate the largest potential result and so return an unsigned long */ if (info == NULL) return -EINVAL; switch (item) { case PROCPS_SLABS_OBJS: return info->stats.nr_objs; case PROCPS_SLABS_AOBJS: return info->stats.nr_active_objs; case PROCPS_SLABS_PAGES: return info->stats.nr_pages; case PROCPS_SLABS_SLABS: return info->stats.nr_slabs; case PROCPS_SLABS_ASLABS: return info->stats.nr_active_slabs; case PROCPS_SLABS_CACHES: return info->stats.nr_caches; case PROCPS_SLABS_ACACHES: return info->stats.nr_active_caches; case PROCPS_SLABS_SIZE_AVG: return info->stats.avg_obj_size; case PROCPS_SLABS_SIZE_MIN: return info->stats.min_obj_size; case PROCPS_SLABS_SIZE_MAX: return info->stats.max_obj_size; case PROCPS_SLABS_SIZE_TOTAL: return info->stats.total_size; case PROCPS_SLABS_SIZE_ACTIVE: return info->stats.active_size; default: return 0; } } PROCPS_EXPORT int procps_slabs_getstack ( struct procps_slabinfo *info, struct slab_result *these) { if (info == NULL || these == NULL) return -EINVAL; for (;;) { switch (these->item) { case PROCPS_SLABS_OBJS: these->result.u_int = info->stats.nr_objs; break; case PROCPS_SLABS_AOBJS: these->result.u_int = info->stats.nr_active_objs; break; case PROCPS_SLABS_PAGES: these->result.u_int = info->stats.nr_pages; break; case PROCPS_SLABS_SLABS: these->result.u_int = info->stats.nr_slabs; break; case PROCPS_SLABS_ASLABS: these->result.u_int = info->stats.nr_active_slabs; break; case PROCPS_SLABS_CACHES: these->result.u_int = info->stats.nr_caches; break; case PROCPS_SLABS_ACACHES: these->result.u_int = info->stats.nr_active_caches; break; case PROCPS_SLABS_SIZE_AVG: these->result.u_int = info->stats.avg_obj_size; break; case PROCPS_SLABS_SIZE_MIN: these->result.u_int = info->stats.min_obj_size; break; case PROCPS_SLABS_SIZE_MAX: these->result.u_int = info->stats.max_obj_size; break; case PROCPS_SLABS_SIZE_TOTAL: these->result.ul_int = info->stats.total_size; break; case PROCPS_SLABS_SIZE_ACTIVE: these->result.ul_int = info->stats.active_size; break; case PROCPS_SLABS_noop: // don't disturb potential user data in the result struct break; case PROCPS_SLABS_stack_end: return 0; default: return -EINVAL; } ++these; } } /* * procps_slabnode_getname(): * * @info: slabinfo structure with data read in * @nodeid: number of node we want the name for * * Find the name of the given node * * Returns: name or NULL on error */ PROCPS_EXPORT const char *procps_slabnode_getname ( struct procps_slabinfo *info, int nodeid) { if (info == NULL) return NULL; if (nodeid > info->nodes_used) return NULL; return info->nodes[nodeid].name; } PROCPS_EXPORT unsigned long procps_slabnode_get ( struct procps_slabinfo *info, enum slabnode_item item, int nodeid) { /* note: most of the results we might return are actually just unsigned int, but we must accommodate the largest potential result and so return an unsigned long */ if (info == NULL) return -EINVAL; switch (item) { case PROCPS_SLABNODE_SIZE: return info->nodes[nodeid].cache_size; case PROCPS_SLABNODE_OBJS: return info->nodes[nodeid].nr_objs; case PROCPS_SLABNODE_AOBJS: return info->nodes[nodeid].nr_active_objs; case PROCPS_SLABNODE_OBJ_SIZE: return info->nodes[nodeid].obj_size; case PROCPS_SLABNODE_OBJS_PER_SLAB: return info->nodes[nodeid].objs_per_slab; case PROCPS_SLABNODE_PAGES_PER_SLAB: return info->nodes[nodeid].pages_per_slab; case PROCPS_SLABNODE_SLABS: return info->nodes[nodeid].nr_slabs; case PROCPS_SLABNODE_ASLABS: return info->nodes[nodeid].nr_active_slabs; case PROCPS_SLABNODE_USE: return info->nodes[nodeid].use; default: return 0; } } PROCPS_EXPORT int procps_slabnode_getstack ( struct procps_slabinfo *info, struct slab_result *these, int nodeid) { if (info == NULL || these == NULL) return -EINVAL; if (nodeid > info->nodes_used) return -EINVAL; for (;;) { switch (these->item) { case PROCPS_SLABNODE_SIZE: these->result.ul_int = info->nodes[nodeid].cache_size; break; case PROCPS_SLABNODE_OBJS: these->result.u_int = info->nodes[nodeid].nr_objs; break; case PROCPS_SLABNODE_AOBJS: these->result.u_int = info->nodes[nodeid].nr_active_objs; break; case PROCPS_SLABNODE_OBJ_SIZE: these->result.u_int = info->nodes[nodeid].obj_size; break; case PROCPS_SLABNODE_OBJS_PER_SLAB: these->result.u_int = info->nodes[nodeid].objs_per_slab; break; case PROCPS_SLABNODE_PAGES_PER_SLAB: these->result.u_int = info->nodes[nodeid].pages_per_slab; break; case PROCPS_SLABNODE_SLABS: these->result.u_int = info->nodes[nodeid].nr_slabs; break; case PROCPS_SLABNODE_ASLABS: these->result.u_int = info->nodes[nodeid].nr_active_slabs; break; case PROCPS_SLABNODE_USE: these->result.u_int = info->nodes[nodeid].use; break; case PROCPS_SLABNODE_NAME: these->result.str = info->nodes[nodeid].name; break; case PROCPS_SLABNODE_noop: // don't disturb potential user data in the result struct break; case PROCPS_SLABNODE_stack_end: return 0; default: return -EINVAL; } ++these; } } PROCPS_EXPORT int procps_slabnode_stack_fill ( struct procps_slabinfo *info, struct slabnode_stack *stack, int nodeid) { int rc; if (info == NULL || stack == NULL || stack->head == NULL) return -EINVAL; if ((rc = procps_slabinfo_read(info)) < 0) return rc; return procps_slabnode_getstack(info, stack->head, nodeid); } /* * procps_slabnode_count(): * * @info: read in slabinfo structure * * Returns: number of nodes in @info or <0 on error */ PROCPS_EXPORT int procps_slabnode_count ( struct procps_slabinfo *info) { int rc = 0; if (!info) return -EINVAL; if (!info->nodes_used) rc = procps_slabinfo_read(info); if (rc < 0) return rc; return info->nodes_used; } PROCPS_EXPORT int procps_slabnode_stacks_fill ( struct procps_slabinfo *info, struct slabnode_stack **stacks, int maxstacks) { int i, rc; if (info == NULL || *stacks == NULL) return -EINVAL; if (maxstacks < 1) return -EINVAL; if ((rc = procps_slabinfo_read(info)) < 0) return rc; if (maxstacks > info->stacked->depth) maxstacks = info->stacked->depth; if (maxstacks > info->nodes_used) maxstacks = info->nodes_used; for (i = 0; i < maxstacks; i++) { if (stacks[i] == NULL) break; if ((rc = procps_slabnode_getstack(info, stacks[i]->head, i) < 0)) return rc; } info->stacked->inuse = i; return info->stacked->inuse; } static void stacks_validate (struct slabnode_stack **v, const char *who) { #if 0 #include int i, t, x, n = 0; struct stack_vectors *p = (struct stack_vectors *)v - 1; fprintf(stderr, "%s: called by '%s'\n", __func__, who); fprintf(stderr, "%s: owned by %p (whose self = %p)\n", __func__, p->owner, p->owner->self); for (x = 0; v[x]; x++) { struct slabnode_stack *h = v[x]; struct slab_result *r = h->head; fprintf(stderr, "%s: vector[%02d] = %p", __func__, x, h); for (i = 0; r->item < PROCPS_SLABNODE_stack_end; i++, r++) ; t = i + 1; fprintf(stderr, ", stack %d found %d elements\n", n, i); ++n; } fprintf(stderr, "%s: found %d stack(s), each %d bytes (including eos)\n", __func__, x, (int)sizeof(struct slab_result) * t); fprintf(stderr, "%s: found %d stack(s)\n", __func__, x); fprintf(stderr, "%s: sizeof(struct slabnode_stack) = %2d\n", __func__, (int)sizeof(struct slabnode_stack)); fprintf(stderr, "%s: sizeof(struct slab_result) = %2d\n", __func__, (int)sizeof(struct slab_result)); fputc('\n', stderr); return; #endif } static struct slab_result *stack_make ( struct slab_result *p, int maxitems, enum slabnode_item *items) { struct slab_result *p_sav = p; int i; for (i = 0; i < maxitems; i++) { p->item = items[i]; // note: we rely on calloc to initialize actual result ++p; } return p_sav; } static int stack_items_valid ( int maxitems, enum slabnode_item *items) { int i; for (i = 0; i < maxitems; i++) { if (items[i] < 0) return 0; if (items[i] > PROCPS_SLABNODE_stack_end) return 0; } if (items[maxitems -1] != PROCPS_SLABNODE_stack_end) return 0; return 1; } /* * procps_slabnode_stacks_alloc(): * * Allocate and initialize one or more stacks each of which is anchored in an * associated slabnode_stack structure (which may include extra user space). * * All such stacks will will have their result structures properly primed with * 'items', while the result itself will be zeroed. * * Returns an array of pointers representing the 'heads' of each new stack. */ PROCPS_EXPORT struct slabnode_stack **procps_slabnode_stacks_alloc ( struct procps_slabinfo *info, int maxstacks, int maxitems, enum slabnode_item *items) { struct stacks_anchor *p_blob; struct stack_vectors *p_vect; struct slabnode_stack *p_head; size_t vect_size, head_size, list_size, blob_size; void *v_head, *v_list; int i; if (info == NULL || items == NULL) return NULL; if (maxstacks < 1 || maxitems < 1) return NULL; if (!stack_items_valid(maxitems, items)) return NULL; vect_size = sizeof(struct stack_vectors); // address vector struct vect_size += sizeof(void *) * maxstacks; // plus vectors themselves vect_size += sizeof(void *); // plus NULL delimiter head_size = sizeof(struct slabnode_stack); // a head struct list_size = sizeof(struct slab_result) * maxitems; // a results stack blob_size = sizeof(struct stacks_anchor); // the anchor itself blob_size += vect_size; // all vectors + delims blob_size += head_size * maxstacks; // all head structs blob_size += list_size * maxstacks; // all results stacks /* note: all memory is allocated in a single blob, facilitating a later free(). as a minimum, it's important that the result structures themselves always be contiguous for any given stack (just as they are when defined statically). */ if (NULL == (p_blob = calloc(1, blob_size))) return NULL; p_blob->next = info->stacked; info->stacked = p_blob; p_blob->self = p_blob; p_blob->vectors = (void *)p_blob + sizeof(struct stacks_anchor); p_vect = p_blob->vectors; p_vect->owner = p_blob->self; p_vect->heads = (void *)p_vect + sizeof(struct stack_vectors); v_head = (void *)p_vect + vect_size; v_list = v_head + (head_size * maxstacks); for (i = 0; i < maxstacks; i++) { p_head = (struct slabnode_stack *)v_head; p_head->head = stack_make((struct slab_result *)v_list, maxitems, items); p_blob->vectors->heads[i] = p_head; v_list += list_size; v_head += head_size; } p_blob->depth = maxstacks; stacks_validate(p_blob->vectors->heads, __func__); return p_blob->vectors->heads; } /* * procps_slabnode_stack_alloc(): * * Allocate and initialize a single result stack under a simplified interface. * * Such a stack will will have its result structures properly primed with * 'items', while the result itself will be zeroed. * */ PROCPS_EXPORT struct slabnode_stack *procps_slabnode_stack_alloc ( struct procps_slabinfo *info, int maxitems, enum slabnode_item *items) { struct slabnode_stack **v; if (info == NULL || items == NULL || maxitems < 1) return NULL; v = procps_slabnode_stacks_alloc(info, 1, maxitems, items); if (!v) return NULL; stacks_validate(v, __func__); return v[0]; } static int stacks_sort ( const struct slabnode_stack **A, const struct slabnode_stack **B, enum slabnode_item *offset) { const struct slab_result *a = (*A)->head + *offset; const struct slab_result *b = (*B)->head + *offset; // note: everything will be sorted high-to-low switch (a->item) { case PROCPS_SLABNODE_noop: case PROCPS_SLABNODE_stack_end: break; case PROCPS_SLABNODE_NAME: return strcoll(a->result.str, b->result.str); case PROCPS_SLABNODE_SIZE: if ( a->result.ul_int > b->result.ul_int ) return -1; if ( a->result.ul_int < b->result.ul_int ) return +1; break; default: if ( a->result.u_int > b->result.u_int ) return -1; if ( a->result.u_int < b->result.u_int ) return +1; break; } return 0; } /* * procps_slabnode_stacks_sort(): * * Sort stacks anchored as 'heads' in the passed slabnode_stack pointers * array based on the designated sort enumerator. * * Returns those same addresses sorted. * * Note: all of the stacks must be homogeneous (of equal length and content). */ PROCPS_EXPORT struct slabnode_stack **procps_slabnode_stacks_sort ( struct procps_slabinfo *info, struct slabnode_stack **stacks, int numstacked, enum slabnode_item sort) { #define QSORT_r int (*)(const void *, const void *, void *) struct slab_result *p = stacks[0]->head; int offset = 0;; if (info == NULL || stacks == NULL) return NULL; if (sort < 0 || sort > PROCPS_SLABNODE_noop) return NULL; if (numstacked > info->stacked->depth) return NULL; if (numstacked < 2) return stacks; if (numstacked > info->stacked->inuse) numstacked = info->stacked->inuse; for (;;) { if (p->item == sort) break; ++offset; if (p->item == PROCPS_SLABNODE_stack_end) return NULL; ++p; } qsort_r(stacks, numstacked, sizeof(void *), (QSORT_r)stacks_sort, &offset); return stacks; #undef QSORT_r }