root/usr/src/cmd/mdb/common/modules/libumem/libumem.c
/*
 * 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 "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) mdb_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) mdb_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) mdb_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 != 0; 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
}