libumem.c revision 4f364e7c95ee7fd9d5bbeddc1940e92405bb0e72
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Copyright (c) 2012, Joyent, Inc. All rights reserved.
*/
#include "umem.h"
#include <libproc.h>
#include <mdb/mdb_modapi.h>
#include "kgrep.h"
#include "leaky.h"
#include "misc.h"
#include "proc_kludges.h"
#include <umem_impl.h>
#include <sys/vmem_impl_user.h>
#include <thr_uberdata.h>
#include <stdio.h>
#include "umem_pagesize.h"
typedef struct datafmt {
char *hdr1;
char *hdr2;
char *dashes;
char *fmt;
} datafmt_t;
static datafmt_t ptcfmt[] = {
{ " ", "tid", "---", "%3u " },
{ " memory", " cached", "-------", "%7lH " },
{ " %", "cap", "---", "%3u " },
{ " %", NULL, "---", "%3u " },
{ NULL, NULL, NULL, NULL }
};
static datafmt_t umemfmt[] = {
{ "cache ", "name ",
"-------------------------", "%-25s " },
{ " buf", " size", "------", "%6u " },
{ " buf", " in use", "-------", "%7u " },
{ " buf", " in ptc", "-------", "%7s " },
{ " buf", " total", "-------", "%7u " },
{ " memory", " in use", "-------", "%7H " },
{ " alloc", " succeed", "---------", "%9u " },
{ "alloc", " fail", "-----", "%5llu" },
{ NULL, NULL, NULL, NULL }
};
static datafmt_t vmemfmt[] = {
{ "vmem ", "name ",
"-------------------------", "%-*s " },
{ " memory", " in use", "---------", "%9H " },
{ " memory", " total", "----------", "%10H " },
{ " memory", " import", "---------", "%9H " },
{ " alloc", " succeed", "---------", "%9llu " },
{ "alloc", " fail", "-----", "%5llu " },
{ NULL, NULL, NULL, NULL }
};
/*ARGSUSED*/
static int
umastat_cpu_avail(uintptr_t addr, const umem_cpu_cache_t *ccp, int *avail)
{
if (ccp->cc_rounds > 0)
*avail += ccp->cc_rounds;
if (ccp->cc_prounds > 0)
*avail += ccp->cc_prounds;
return (WALK_NEXT);
}
/*ARGSUSED*/
static int
umastat_cpu_alloc(uintptr_t addr, const umem_cpu_cache_t *ccp, int *alloc)
{
*alloc += ccp->cc_alloc;
return (WALK_NEXT);
}
/*ARGSUSED*/
static int
umastat_slab_avail(uintptr_t addr, const umem_slab_t *sp, int *avail)
{
*avail += sp->slab_chunks - sp->slab_refcnt;
return (WALK_NEXT);
}
typedef struct umastat_vmem {
uintptr_t kv_addr;
struct umastat_vmem *kv_next;
int kv_meminuse;
int kv_alloc;
int kv_fail;
} umastat_vmem_t;
/*ARGSUSED*/
static int
umastat_cache_nptc(uintptr_t addr, const umem_cache_t *cp, int *nptc)
{
if (!(cp->cache_flags & UMF_PTC))
return (WALK_NEXT);
(*nptc)++;
return (WALK_NEXT);
}
/*ARGSUSED*/
static int
umastat_cache_hdr(uintptr_t addr, const umem_cache_t *cp, void *ignored)
{
if (!(cp->cache_flags & UMF_PTC))
return (WALK_NEXT);
mdb_printf("%3d ", cp->cache_bufsize);
return (WALK_NEXT);
}
/*ARGSUSED*/
static int
umastat_lwp_ptc(uintptr_t addr, void *buf, int *nbufs)
{
(*nbufs)++;
return (WALK_NEXT);
}
/*ARGSUSED*/
static int
umastat_lwp_cache(uintptr_t addr, const umem_cache_t *cp, ulwp_t *ulwp)
{
char walk[60];
int nbufs = 0;
if (!(cp->cache_flags & UMF_PTC))
return (WALK_NEXT);
(void) snprintf(walk, sizeof (walk), "umem_ptc_%d", cp->cache_bufsize);
if (mdb_pwalk(walk, (mdb_walk_cb_t)umastat_lwp_ptc,
&nbufs, (uintptr_t)ulwp->ul_self) == -1) {
mdb_warn("unable to walk '%s'", walk);
return (WALK_ERR);
}
mdb_printf("%3d ", ulwp->ul_tmem.tm_size ?
(nbufs * cp->cache_bufsize * 100) / ulwp->ul_tmem.tm_size : 0);
return (WALK_NEXT);
}
/*ARGSUSED*/
static int
umastat_lwp(uintptr_t addr, const ulwp_t *ulwp, void *ignored)
{
size_t size;
datafmt_t *dfp = ptcfmt;
mdb_printf((dfp++)->fmt, ulwp->ul_lwpid);
mdb_printf((dfp++)->fmt, ulwp->ul_tmem.tm_size);
if (umem_readvar(&size, "umem_ptc_size") == -1) {
mdb_warn("unable to read 'umem_ptc_size'");
return (WALK_ERR);
}
mdb_printf((dfp++)->fmt, (ulwp->ul_tmem.tm_size * 100) / size);
if (mdb_walk("umem_cache",
(mdb_walk_cb_t)umastat_lwp_cache, (void *)ulwp) == -1) {
mdb_warn("can't walk 'umem_cache'");
return (WALK_ERR);
}
mdb_printf("\n");
return (WALK_NEXT);
}
/*ARGSUSED*/
static int
umastat_cache_ptc(uintptr_t addr, const void *ignored, int *nptc)
{
(*nptc)++;
return (WALK_NEXT);
}
static int
umastat_cache(uintptr_t addr, const umem_cache_t *cp, umastat_vmem_t **kvp)
{
umastat_vmem_t *kv;
datafmt_t *dfp = umemfmt;
char buf[10];
int magsize;
int avail, alloc, total, nptc = 0;
size_t meminuse = (cp->cache_slab_create - cp->cache_slab_destroy) *
cp->cache_slabsize;
mdb_walk_cb_t cpu_avail = (mdb_walk_cb_t)umastat_cpu_avail;
mdb_walk_cb_t cpu_alloc = (mdb_walk_cb_t)umastat_cpu_alloc;
mdb_walk_cb_t slab_avail = (mdb_walk_cb_t)umastat_slab_avail;
magsize = umem_get_magsize(cp);
alloc = cp->cache_slab_alloc + cp->cache_full.ml_alloc;
avail = cp->cache_full.ml_total * magsize;
total = cp->cache_buftotal;
(void) mdb_pwalk("umem_cpu_cache", cpu_alloc, &alloc, addr);
(void) mdb_pwalk("umem_cpu_cache", cpu_avail, &avail, addr);
(void) mdb_pwalk("umem_slab_partial", slab_avail, &avail, addr);
if (cp->cache_flags & UMF_PTC) {
char walk[60];
(void) snprintf(walk, sizeof (walk),
"umem_ptc_%d", cp->cache_bufsize);
if (mdb_walk(walk,
(mdb_walk_cb_t)umastat_cache_ptc, &nptc) == -1) {
mdb_warn("unable to walk '%s'", walk);
return (WALK_ERR);
}
(void) snprintf(buf, sizeof (buf), "%d", nptc);
}
for (kv = *kvp; kv != NULL; kv = kv->kv_next) {
if (kv->kv_addr == (uintptr_t)cp->cache_arena)
goto out;
}
kv = mdb_zalloc(sizeof (umastat_vmem_t), UM_SLEEP | UM_GC);
kv->kv_next = *kvp;
kv->kv_addr = (uintptr_t)cp->cache_arena;
*kvp = kv;
out:
kv->kv_meminuse += meminuse;
kv->kv_alloc += alloc;
kv->kv_fail += cp->cache_alloc_fail;
mdb_printf((dfp++)->fmt, cp->cache_name);
mdb_printf((dfp++)->fmt, cp->cache_bufsize);
mdb_printf((dfp++)->fmt, total - avail);
mdb_printf((dfp++)->fmt, cp->cache_flags & UMF_PTC ? buf : "-");
mdb_printf((dfp++)->fmt, total);
mdb_printf((dfp++)->fmt, meminuse);
mdb_printf((dfp++)->fmt, alloc);
mdb_printf((dfp++)->fmt, cp->cache_alloc_fail);
mdb_printf("\n");
return (WALK_NEXT);
}
static int
umastat_vmem_totals(uintptr_t addr, const vmem_t *v, umastat_vmem_t *kv)
{
while (kv != NULL && kv->kv_addr != addr)
kv = kv->kv_next;
if (kv == NULL || kv->kv_alloc == 0)
return (WALK_NEXT);
mdb_printf("Total [%s]%*s %6s %7s %7s %7s %7H %9u %5u\n", v->vm_name,
17 - strlen(v->vm_name), "", "", "", "", "",
kv->kv_meminuse, kv->kv_alloc, kv->kv_fail);
return (WALK_NEXT);
}
/*ARGSUSED*/
static int
umastat_vmem(uintptr_t addr, const vmem_t *v, void *ignored)
{
datafmt_t *dfp = vmemfmt;
uintptr_t paddr;
vmem_t parent;
int ident = 0;
for (paddr = (uintptr_t)v->vm_source; paddr != NULL; ident += 4) {
if (mdb_vread(&parent, sizeof (parent), paddr) == -1) {
mdb_warn("couldn't trace %p's ancestry", addr);
ident = 0;
break;
}
paddr = (uintptr_t)parent.vm_source;
}
mdb_printf("%*s", ident, "");
mdb_printf((dfp++)->fmt, 25 - ident, v->vm_name);
mdb_printf((dfp++)->fmt, v->vm_kstat.vk_mem_inuse);
mdb_printf((dfp++)->fmt, v->vm_kstat.vk_mem_total);
mdb_printf((dfp++)->fmt, v->vm_kstat.vk_mem_import);
mdb_printf((dfp++)->fmt, v->vm_kstat.vk_alloc);
mdb_printf((dfp++)->fmt, v->vm_kstat.vk_fail);
mdb_printf("\n");
return (WALK_NEXT);
}
/*ARGSUSED*/
int
umastat(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
umastat_vmem_t *kv = NULL;
datafmt_t *dfp;
int nptc = 0, i;
if (argc != 0)
return (DCMD_USAGE);
/*
* We need to determine if we have any caches that have per-thread
* caching enabled.
*/
if (mdb_walk("umem_cache",
(mdb_walk_cb_t)umastat_cache_nptc, &nptc) == -1) {
mdb_warn("can't walk 'umem_cache'");
return (DCMD_ERR);
}
if (nptc) {
for (dfp = ptcfmt; dfp->hdr2 != NULL; dfp++)
mdb_printf("%s ", dfp->hdr1);
for (i = 0; i < nptc; i++)
mdb_printf("%s ", dfp->hdr1);
mdb_printf("\n");
for (dfp = ptcfmt; dfp->hdr2 != NULL; dfp++)
mdb_printf("%s ", dfp->hdr2);
if (mdb_walk("umem_cache",
(mdb_walk_cb_t)umastat_cache_hdr, NULL) == -1) {
mdb_warn("can't walk 'umem_cache'");
return (DCMD_ERR);
}
mdb_printf("\n");
for (dfp = ptcfmt; dfp->hdr2 != NULL; dfp++)
mdb_printf("%s ", dfp->dashes);
for (i = 0; i < nptc; i++)
mdb_printf("%s ", dfp->dashes);
mdb_printf("\n");
if (mdb_walk("ulwp", (mdb_walk_cb_t)umastat_lwp, NULL) == -1) {
mdb_warn("can't walk 'ulwp'");
return (DCMD_ERR);
}
mdb_printf("\n");
}
for (dfp = umemfmt; dfp->hdr1 != NULL; dfp++)
mdb_printf("%s%s", dfp == umemfmt ? "" : " ", dfp->hdr1);
mdb_printf("\n");
for (dfp = umemfmt; dfp->hdr1 != NULL; dfp++)
mdb_printf("%s%s", dfp == umemfmt ? "" : " ", dfp->hdr2);
mdb_printf("\n");
for (dfp = umemfmt; dfp->hdr1 != NULL; dfp++)
mdb_printf("%s%s", dfp == umemfmt ? "" : " ", dfp->dashes);
mdb_printf("\n");
if (mdb_walk("umem_cache", (mdb_walk_cb_t)umastat_cache, &kv) == -1) {
mdb_warn("can't walk 'umem_cache'");
return (DCMD_ERR);
}
for (dfp = umemfmt; dfp->hdr1 != NULL; dfp++)
mdb_printf("%s%s", dfp == umemfmt ? "" : " ", dfp->dashes);
mdb_printf("\n");
if (mdb_walk("vmem", (mdb_walk_cb_t)umastat_vmem_totals, kv) == -1) {
mdb_warn("can't walk 'vmem'");
return (DCMD_ERR);
}
for (dfp = umemfmt; dfp->hdr1 != NULL; dfp++)
mdb_printf("%s ", dfp->dashes);
mdb_printf("\n");
mdb_printf("\n");
for (dfp = vmemfmt; dfp->hdr1 != NULL; dfp++)
mdb_printf("%s ", dfp->hdr1);
mdb_printf("\n");
for (dfp = vmemfmt; dfp->hdr1 != NULL; dfp++)
mdb_printf("%s ", dfp->hdr2);
mdb_printf("\n");
for (dfp = vmemfmt; dfp->hdr1 != NULL; dfp++)
mdb_printf("%s ", dfp->dashes);
mdb_printf("\n");
if (mdb_walk("vmem", (mdb_walk_cb_t)umastat_vmem, NULL) == -1) {
mdb_warn("can't walk 'vmem'");
return (DCMD_ERR);
}
for (dfp = vmemfmt; dfp->hdr1 != NULL; dfp++)
mdb_printf("%s ", dfp->dashes);
mdb_printf("\n");
return (DCMD_OK);
}
/*
* kmdb doesn't use libproc, and thus doesn't have any prmap_t's to walk.
* We have other ways to grep kmdb's address range.
*/
#ifndef _KMDB
typedef struct ugrep_walk_data {
kgrep_cb_func *ug_cb;
void *ug_cbdata;
} ugrep_walk_data_t;
/*ARGSUSED*/
int
ugrep_mapping_cb(uintptr_t addr, const void *prm_arg, void *data)
{
ugrep_walk_data_t *ug = data;
const prmap_t *prm = prm_arg;
return (ug->ug_cb(prm->pr_vaddr, prm->pr_vaddr + prm->pr_size,
ug->ug_cbdata));
}
int
kgrep_subr(kgrep_cb_func *cb, void *cbdata)
{
ugrep_walk_data_t ug;
prockludge_add_walkers();
ug.ug_cb = cb;
ug.ug_cbdata = cbdata;
if (mdb_walk(KLUDGE_MAPWALK_NAME, ugrep_mapping_cb, &ug) == -1) {
mdb_warn("Unable to walk "KLUDGE_MAPWALK_NAME);
return (DCMD_ERR);
}
prockludge_remove_walkers();
return (DCMD_OK);
}
size_t
kgrep_subr_pagesize(void)
{
return (PAGESIZE);
}
#endif /* !_KMDB */
static const mdb_dcmd_t dcmds[] = {
/* from libumem.c */
{ "umastat", NULL, "umem allocator stats", umastat },
/* from misc.c */
{ "umem_debug", NULL, "toggle umem dcmd/walk debugging", umem_debug},
/* from umem.c */
{ "umem_status", NULL, "Print umem status and message buffer",
umem_status },
{ "allocdby", ":", "given a thread, print its allocated buffers",
allocdby },
{ "bufctl", ":[-vh] [-a addr] [-c caller] [-e earliest] [-l latest] "
"[-t thd]", "print or filter a bufctl", bufctl, bufctl_help },
{ "bufctl_audit", ":", "print a bufctl_audit", bufctl_audit },
{ "freedby", ":", "given a thread, print its freed buffers", freedby },
{ "umalog", "[ fail | slab ]",
"display umem transaction log and stack traces", umalog },
{ "umausers", "[-ef] [cache ...]", "display current medium and large "
"users of the umem allocator", umausers },
{ "umem_cache", "?", "print a umem cache", umem_cache },
{ "umem_log", "?", "dump umem transaction log", umem_log },
{ "umem_malloc_dist", "[-dg] [-b maxbins] [-B minbinsize]",
"report distribution of outstanding malloc()s",
umem_malloc_dist, umem_malloc_dist_help },
{ "umem_malloc_info", "?[-dg] [-b maxbins] [-B minbinsize]",
"report information about malloc()s by cache",
umem_malloc_info, umem_malloc_info_help },
{ "umem_verify", "?", "check integrity of umem-managed memory",
umem_verify },
{ "vmem", "?", "print a vmem_t", vmem },
{ "vmem_seg", ":[-sv] [-c caller] [-e earliest] [-l latest] "
"[-m minsize] [-M maxsize] [-t thread] [-T type]",
"print or filter a vmem_seg", vmem_seg, vmem_seg_help },
#ifndef _KMDB
/* from ../genunix/kgrep.c + libumem.c */
{ "ugrep", KGREP_USAGE, "search user address space for a pointer",
kgrep, kgrep_help },
/* from ../genunix/leaky.c + leaky_subr.c */
{ "findleaks", FINDLEAKS_USAGE, "search for potential memory leaks",
findleaks, findleaks_help },
#endif
{ NULL }
};
static const mdb_walker_t walkers[] = {
/* from umem.c */
{ "allocdby", "given a thread, walk its allocated bufctls",
allocdby_walk_init, allocdby_walk_step, allocdby_walk_fini },
{ "bufctl", "walk a umem cache's bufctls",
bufctl_walk_init, umem_walk_step, umem_walk_fini },
{ "bufctl_history", "walk the available history of a bufctl",
bufctl_history_walk_init, bufctl_history_walk_step,
bufctl_history_walk_fini },
{ "freectl", "walk a umem cache's free bufctls",
freectl_walk_init, umem_walk_step, umem_walk_fini },
{ "freedby", "given a thread, walk its freed bufctls",
freedby_walk_init, allocdby_walk_step, allocdby_walk_fini },
{ "freemem", "walk a umem cache's free memory",
freemem_walk_init, umem_walk_step, umem_walk_fini },
{ "umem", "walk a umem cache",
umem_walk_init, umem_walk_step, umem_walk_fini },
{ "umem_cpu", "walk the umem CPU structures",
umem_cpu_walk_init, umem_cpu_walk_step, umem_cpu_walk_fini },
{ "umem_cpu_cache", "given a umem cache, walk its per-CPU caches",
umem_cpu_cache_walk_init, umem_cpu_cache_walk_step, NULL },
{ "umem_hash", "given a umem cache, walk its allocated hash table",
umem_hash_walk_init, umem_hash_walk_step, umem_hash_walk_fini },
{ "umem_log", "walk the umem transaction log",
umem_log_walk_init, umem_log_walk_step, umem_log_walk_fini },
{ "umem_slab", "given a umem cache, walk its slabs",
umem_slab_walk_init, umem_slab_walk_step, NULL },
{ "umem_slab_partial",
"given a umem cache, walk its partially allocated slabs (min 1)",
umem_slab_walk_partial_init, umem_slab_walk_step, NULL },
{ "vmem", "walk vmem structures in pre-fix, depth-first order",
vmem_walk_init, vmem_walk_step, vmem_walk_fini },
{ "vmem_alloc", "given a vmem_t, walk its allocated vmem_segs",
vmem_alloc_walk_init, vmem_seg_walk_step, vmem_seg_walk_fini },
{ "vmem_free", "given a vmem_t, walk its free vmem_segs",
vmem_free_walk_init, vmem_seg_walk_step, vmem_seg_walk_fini },
{ "vmem_postfix", "walk vmem structures in post-fix, depth-first order",
vmem_walk_init, vmem_postfix_walk_step, vmem_walk_fini },
{ "vmem_seg", "given a vmem_t, walk all of its vmem_segs",
vmem_seg_walk_init, vmem_seg_walk_step, vmem_seg_walk_fini },
{ "vmem_span", "given a vmem_t, walk its spanning vmem_segs",
vmem_span_walk_init, vmem_seg_walk_step, vmem_seg_walk_fini },
#ifndef _KMDB
/* from ../genunix/leaky.c + leaky_subr.c */
{ "leak", "given a leak ctl, walk other leaks w/ that stacktrace",
leaky_walk_init, leaky_walk_step, leaky_walk_fini },
{ "leakbuf", "given a leak ctl, walk addr of leaks w/ that stacktrace",
leaky_walk_init, leaky_buf_walk_step, leaky_walk_fini },
#endif
{ NULL }
};
static const mdb_modinfo_t modinfo = {MDB_API_VERSION, dcmds, walkers};
const mdb_modinfo_t *
_mdb_init(void)
{
if (umem_init() != 0)
return (NULL);
return (&modinfo);
}
void
_mdb_fini(void)
{
#ifndef _KMDB
leaky_cleanup(1);
#endif
}