1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26/*
27 * Copyright (c) 2012, Joyent, Inc. All rights reserved.
28 */
29
30#include "umem.h"
31#include <libproc.h>
32#include <mdb/mdb_modapi.h>
33
34#include "kgrep.h"
35#include "leaky.h"
36#include "misc.h"
37#include "proc_kludges.h"
38
39#include <umem_impl.h>
40#include <sys/vmem_impl_user.h>
41#include <thr_uberdata.h>
42
43#include "umem_pagesize.h"
44
45typedef struct datafmt {
46	char	*hdr1;
47	char	*hdr2;
48	char	*dashes;
49	char	*fmt;
50} datafmt_t;
51
52static datafmt_t ptcfmt[] = {
53	{ "   ",	"tid",		"---",		"%3u "		},
54	{ " memory",	" cached",	"-------",	"%7lH "		},
55	{ "  %",	"cap",		"---",		"%3u "		},
56	{ "  %",	NULL,		"---",		"%3u "		},
57	{ NULL,		NULL,		NULL,		NULL		}
58};
59
60static datafmt_t umemfmt[] = {
61	{ "cache                    ", "name                     ",
62	"-------------------------", "%-25s "				},
63	{ "   buf",	"  size",	"------",	"%6u "		},
64	{ "    buf",	" in use",	"-------",	"%7u "		},
65	{ "    buf",	" in ptc",	"-------",	"%7s "		},
66	{ "    buf",	"  total",	"-------",	"%7u "		},
67	{ " memory",	" in use",	"-------",	"%7H "		},
68	{ "    alloc",	"  succeed",	"---------",	"%9u "		},
69	{ "alloc",	" fail",	"-----",	"%5llu"		},
70	{ NULL,		NULL,		NULL,		NULL		}
71};
72
73static datafmt_t vmemfmt[] = {
74	{ "vmem                     ", "name                     ",
75	"-------------------------", "%-*s "				},
76	{ "   memory",	"   in use",	"---------",	"%9H "		},
77	{ "    memory",	"     total",	"----------",	"%10H "		},
78	{ "   memory",	"   import",	"---------",	"%9H "		},
79	{ "    alloc",	"  succeed",	"---------",	"%9llu "	},
80	{ "alloc",	" fail",	"-----",	"%5llu "	},
81	{ NULL,		NULL,		NULL,		NULL		}
82};
83
84/*ARGSUSED*/
85static int
86umastat_cpu_avail(uintptr_t addr, const umem_cpu_cache_t *ccp, int *avail)
87{
88	if (ccp->cc_rounds > 0)
89		*avail += ccp->cc_rounds;
90	if (ccp->cc_prounds > 0)
91		*avail += ccp->cc_prounds;
92
93	return (WALK_NEXT);
94}
95
96/*ARGSUSED*/
97static int
98umastat_cpu_alloc(uintptr_t addr, const umem_cpu_cache_t *ccp, int *alloc)
99{
100	*alloc += ccp->cc_alloc;
101
102	return (WALK_NEXT);
103}
104
105/*ARGSUSED*/
106static int
107umastat_slab_avail(uintptr_t addr, const umem_slab_t *sp, int *avail)
108{
109	*avail += sp->slab_chunks - sp->slab_refcnt;
110
111	return (WALK_NEXT);
112}
113
114typedef struct umastat_vmem {
115	uintptr_t kv_addr;
116	struct umastat_vmem *kv_next;
117	int kv_meminuse;
118	int kv_alloc;
119	int kv_fail;
120} umastat_vmem_t;
121
122/*ARGSUSED*/
123static int
124umastat_cache_nptc(uintptr_t addr, const umem_cache_t *cp, int *nptc)
125{
126	if (!(cp->cache_flags & UMF_PTC))
127		return (WALK_NEXT);
128
129	(*nptc)++;
130	return (WALK_NEXT);
131}
132
133/*ARGSUSED*/
134static int
135umastat_cache_hdr(uintptr_t addr, const umem_cache_t *cp, void *ignored)
136{
137	if (!(cp->cache_flags & UMF_PTC))
138		return (WALK_NEXT);
139
140	mdb_printf("%3d ", cp->cache_bufsize);
141	return (WALK_NEXT);
142}
143
144/*ARGSUSED*/
145static int
146umastat_lwp_ptc(uintptr_t addr, void *buf, int *nbufs)
147{
148	(*nbufs)++;
149	return (WALK_NEXT);
150}
151
152/*ARGSUSED*/
153static int
154umastat_lwp_cache(uintptr_t addr, const umem_cache_t *cp, ulwp_t *ulwp)
155{
156	char walk[60];
157	int nbufs = 0;
158
159	if (!(cp->cache_flags & UMF_PTC))
160		return (WALK_NEXT);
161
162	(void) mdb_snprintf(walk, sizeof (walk), "umem_ptc_%d",
163	    cp->cache_bufsize);
164
165	if (mdb_pwalk(walk, (mdb_walk_cb_t)umastat_lwp_ptc,
166	    &nbufs, (uintptr_t)ulwp->ul_self) == -1) {
167		mdb_warn("unable to walk '%s'", walk);
168		return (WALK_ERR);
169	}
170
171	mdb_printf("%3d ", ulwp->ul_tmem.tm_size ?
172	    (nbufs * cp->cache_bufsize * 100) / ulwp->ul_tmem.tm_size : 0);
173
174	return (WALK_NEXT);
175}
176
177/*ARGSUSED*/
178static int
179umastat_lwp(uintptr_t addr, const ulwp_t *ulwp, void *ignored)
180{
181	size_t size;
182	datafmt_t *dfp = ptcfmt;
183
184	mdb_printf((dfp++)->fmt, ulwp->ul_lwpid);
185	mdb_printf((dfp++)->fmt, ulwp->ul_tmem.tm_size);
186
187	if (umem_readvar(&size, "umem_ptc_size") == -1) {
188		mdb_warn("unable to read 'umem_ptc_size'");
189		return (WALK_ERR);
190	}
191
192	mdb_printf((dfp++)->fmt, (ulwp->ul_tmem.tm_size * 100) / size);
193
194	if (mdb_walk("umem_cache",
195	    (mdb_walk_cb_t)umastat_lwp_cache, (void *)ulwp) == -1) {
196		mdb_warn("can't walk 'umem_cache'");
197		return (WALK_ERR);
198	}
199
200	mdb_printf("\n");
201
202	return (WALK_NEXT);
203}
204
205/*ARGSUSED*/
206static int
207umastat_cache_ptc(uintptr_t addr, const void *ignored, int *nptc)
208{
209	(*nptc)++;
210	return (WALK_NEXT);
211}
212
213static int
214umastat_cache(uintptr_t addr, const umem_cache_t *cp, umastat_vmem_t **kvp)
215{
216	umastat_vmem_t *kv;
217	datafmt_t *dfp = umemfmt;
218	char buf[10];
219	int magsize;
220
221	int avail, alloc, total, nptc = 0;
222	size_t meminuse = (cp->cache_slab_create - cp->cache_slab_destroy) *
223	    cp->cache_slabsize;
224
225	mdb_walk_cb_t cpu_avail = (mdb_walk_cb_t)umastat_cpu_avail;
226	mdb_walk_cb_t cpu_alloc = (mdb_walk_cb_t)umastat_cpu_alloc;
227	mdb_walk_cb_t slab_avail = (mdb_walk_cb_t)umastat_slab_avail;
228
229	magsize = umem_get_magsize(cp);
230
231	alloc = cp->cache_slab_alloc + cp->cache_full.ml_alloc;
232	avail = cp->cache_full.ml_total * magsize;
233	total = cp->cache_buftotal;
234
235	(void) mdb_pwalk("umem_cpu_cache", cpu_alloc, &alloc, addr);
236	(void) mdb_pwalk("umem_cpu_cache", cpu_avail, &avail, addr);
237	(void) mdb_pwalk("umem_slab_partial", slab_avail, &avail, addr);
238
239	if (cp->cache_flags & UMF_PTC) {
240		char walk[60];
241
242		(void) mdb_snprintf(walk, sizeof (walk),
243		    "umem_ptc_%d", cp->cache_bufsize);
244
245		if (mdb_walk(walk,
246		    (mdb_walk_cb_t)umastat_cache_ptc, &nptc) == -1) {
247			mdb_warn("unable to walk '%s'", walk);
248			return (WALK_ERR);
249		}
250
251		(void) mdb_snprintf(buf, sizeof (buf), "%d", nptc);
252	}
253
254	for (kv = *kvp; kv != NULL; kv = kv->kv_next) {
255		if (kv->kv_addr == (uintptr_t)cp->cache_arena)
256			goto out;
257	}
258
259	kv = mdb_zalloc(sizeof (umastat_vmem_t), UM_SLEEP | UM_GC);
260	kv->kv_next = *kvp;
261	kv->kv_addr = (uintptr_t)cp->cache_arena;
262	*kvp = kv;
263out:
264	kv->kv_meminuse += meminuse;
265	kv->kv_alloc += alloc;
266	kv->kv_fail += cp->cache_alloc_fail;
267
268	mdb_printf((dfp++)->fmt, cp->cache_name);
269	mdb_printf((dfp++)->fmt, cp->cache_bufsize);
270	mdb_printf((dfp++)->fmt, total - avail);
271	mdb_printf((dfp++)->fmt, cp->cache_flags & UMF_PTC ? buf : "-");
272	mdb_printf((dfp++)->fmt, total);
273	mdb_printf((dfp++)->fmt, meminuse);
274	mdb_printf((dfp++)->fmt, alloc);
275	mdb_printf((dfp++)->fmt, cp->cache_alloc_fail);
276	mdb_printf("\n");
277
278	return (WALK_NEXT);
279}
280
281static int
282umastat_vmem_totals(uintptr_t addr, const vmem_t *v, umastat_vmem_t *kv)
283{
284	while (kv != NULL && kv->kv_addr != addr)
285		kv = kv->kv_next;
286
287	if (kv == NULL || kv->kv_alloc == 0)
288		return (WALK_NEXT);
289
290	mdb_printf("Total [%s]%*s %6s %7s %7s %7s %7H %9u %5u\n", v->vm_name,
291	    17 - strlen(v->vm_name), "", "", "", "", "",
292	    kv->kv_meminuse, kv->kv_alloc, kv->kv_fail);
293
294	return (WALK_NEXT);
295}
296
297/*ARGSUSED*/
298static int
299umastat_vmem(uintptr_t addr, const vmem_t *v, void *ignored)
300{
301	datafmt_t *dfp = vmemfmt;
302	uintptr_t paddr;
303	vmem_t parent;
304	int ident = 0;
305
306	for (paddr = (uintptr_t)v->vm_source; paddr != 0; ident += 4) {
307		if (mdb_vread(&parent, sizeof (parent), paddr) == -1) {
308			mdb_warn("couldn't trace %p's ancestry", addr);
309			ident = 0;
310			break;
311		}
312		paddr = (uintptr_t)parent.vm_source;
313	}
314
315	mdb_printf("%*s", ident, "");
316	mdb_printf((dfp++)->fmt, 25 - ident, v->vm_name);
317	mdb_printf((dfp++)->fmt, v->vm_kstat.vk_mem_inuse);
318	mdb_printf((dfp++)->fmt, v->vm_kstat.vk_mem_total);
319	mdb_printf((dfp++)->fmt, v->vm_kstat.vk_mem_import);
320	mdb_printf((dfp++)->fmt, v->vm_kstat.vk_alloc);
321	mdb_printf((dfp++)->fmt, v->vm_kstat.vk_fail);
322
323	mdb_printf("\n");
324
325	return (WALK_NEXT);
326}
327
328/*ARGSUSED*/
329int
330umastat(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
331{
332	umastat_vmem_t *kv = NULL;
333	datafmt_t *dfp;
334	int nptc = 0, i;
335
336	if (argc != 0)
337		return (DCMD_USAGE);
338
339	/*
340	 * We need to determine if we have any caches that have per-thread
341	 * caching enabled.
342	 */
343	if (mdb_walk("umem_cache",
344	    (mdb_walk_cb_t)umastat_cache_nptc, &nptc) == -1) {
345		mdb_warn("can't walk 'umem_cache'");
346		return (DCMD_ERR);
347	}
348
349	if (nptc) {
350		for (dfp = ptcfmt; dfp->hdr2 != NULL; dfp++)
351			mdb_printf("%s ", dfp->hdr1);
352
353		for (i = 0; i < nptc; i++)
354			mdb_printf("%s ", dfp->hdr1);
355
356		mdb_printf("\n");
357
358		for (dfp = ptcfmt; dfp->hdr2 != NULL; dfp++)
359			mdb_printf("%s ", dfp->hdr2);
360
361		if (mdb_walk("umem_cache",
362		    (mdb_walk_cb_t)umastat_cache_hdr, NULL) == -1) {
363			mdb_warn("can't walk 'umem_cache'");
364			return (DCMD_ERR);
365		}
366
367		mdb_printf("\n");
368
369		for (dfp = ptcfmt; dfp->hdr2 != NULL; dfp++)
370			mdb_printf("%s ", dfp->dashes);
371
372		for (i = 0; i < nptc; i++)
373			mdb_printf("%s ", dfp->dashes);
374
375		mdb_printf("\n");
376
377		if (mdb_walk("ulwp", (mdb_walk_cb_t)umastat_lwp, NULL) == -1) {
378			mdb_warn("can't walk 'ulwp'");
379			return (DCMD_ERR);
380		}
381
382		mdb_printf("\n");
383	}
384
385	for (dfp = umemfmt; dfp->hdr1 != NULL; dfp++)
386		mdb_printf("%s%s", dfp == umemfmt ? "" : " ", dfp->hdr1);
387	mdb_printf("\n");
388
389	for (dfp = umemfmt; dfp->hdr1 != NULL; dfp++)
390		mdb_printf("%s%s", dfp == umemfmt ? "" : " ", dfp->hdr2);
391	mdb_printf("\n");
392
393	for (dfp = umemfmt; dfp->hdr1 != NULL; dfp++)
394		mdb_printf("%s%s", dfp == umemfmt ? "" : " ", dfp->dashes);
395	mdb_printf("\n");
396
397	if (mdb_walk("umem_cache", (mdb_walk_cb_t)umastat_cache, &kv) == -1) {
398		mdb_warn("can't walk 'umem_cache'");
399		return (DCMD_ERR);
400	}
401
402	for (dfp = umemfmt; dfp->hdr1 != NULL; dfp++)
403		mdb_printf("%s%s", dfp == umemfmt ? "" : " ", dfp->dashes);
404	mdb_printf("\n");
405
406	if (mdb_walk("vmem", (mdb_walk_cb_t)umastat_vmem_totals, kv) == -1) {
407		mdb_warn("can't walk 'vmem'");
408		return (DCMD_ERR);
409	}
410
411	for (dfp = umemfmt; dfp->hdr1 != NULL; dfp++)
412		mdb_printf("%s ", dfp->dashes);
413	mdb_printf("\n");
414
415	mdb_printf("\n");
416
417	for (dfp = vmemfmt; dfp->hdr1 != NULL; dfp++)
418		mdb_printf("%s ", dfp->hdr1);
419	mdb_printf("\n");
420
421	for (dfp = vmemfmt; dfp->hdr1 != NULL; dfp++)
422		mdb_printf("%s ", dfp->hdr2);
423	mdb_printf("\n");
424
425	for (dfp = vmemfmt; dfp->hdr1 != NULL; dfp++)
426		mdb_printf("%s ", dfp->dashes);
427	mdb_printf("\n");
428
429	if (mdb_walk("vmem", (mdb_walk_cb_t)umastat_vmem, NULL) == -1) {
430		mdb_warn("can't walk 'vmem'");
431		return (DCMD_ERR);
432	}
433
434	for (dfp = vmemfmt; dfp->hdr1 != NULL; dfp++)
435		mdb_printf("%s ", dfp->dashes);
436	mdb_printf("\n");
437	return (DCMD_OK);
438}
439
440/*
441 * kmdb doesn't use libproc, and thus doesn't have any prmap_t's to walk.
442 * We have other ways to grep kmdb's address range.
443 */
444#ifndef _KMDB
445
446typedef struct ugrep_walk_data {
447	kgrep_cb_func *ug_cb;
448	void *ug_cbdata;
449} ugrep_walk_data_t;
450
451/*ARGSUSED*/
452int
453ugrep_mapping_cb(uintptr_t addr, const void *prm_arg, void *data)
454{
455	ugrep_walk_data_t *ug = data;
456	const prmap_t *prm = prm_arg;
457
458	return (ug->ug_cb(prm->pr_vaddr, prm->pr_vaddr + prm->pr_size,
459	    ug->ug_cbdata));
460}
461
462int
463kgrep_subr(kgrep_cb_func *cb, void *cbdata)
464{
465	ugrep_walk_data_t ug;
466
467	prockludge_add_walkers();
468
469	ug.ug_cb = cb;
470	ug.ug_cbdata = cbdata;
471
472	if (mdb_walk(KLUDGE_MAPWALK_NAME, ugrep_mapping_cb, &ug) == -1) {
473		mdb_warn("Unable to walk "KLUDGE_MAPWALK_NAME);
474		return (DCMD_ERR);
475	}
476
477	prockludge_remove_walkers();
478	return (DCMD_OK);
479}
480
481size_t
482kgrep_subr_pagesize(void)
483{
484	return (PAGESIZE);
485}
486
487#endif /* !_KMDB */
488
489static const mdb_dcmd_t dcmds[] = {
490
491	/* from libumem.c */
492	{ "umastat", NULL, "umem allocator stats", umastat },
493
494	/* from misc.c */
495	{ "umem_debug", NULL, "toggle umem dcmd/walk debugging", umem_debug},
496
497	/* from umem.c */
498	{ "umem_status", NULL, "Print umem status and message buffer",
499		umem_status },
500	{ "allocdby", ":", "given a thread, print its allocated buffers",
501		allocdby },
502	{ "bufctl", ":[-vh] [-a addr] [-c caller] [-e earliest] [-l latest] "
503		"[-t thd]", "print or filter a bufctl", bufctl, bufctl_help },
504	{ "bufctl_audit", ":", "print a bufctl_audit", bufctl_audit },
505	{ "freedby", ":", "given a thread, print its freed buffers", freedby },
506	{ "umalog", "[ fail | slab ]",
507	    "display umem transaction log and stack traces", umalog },
508	{ "umausers", "[-ef] [cache ...]", "display current medium and large "
509		"users of the umem allocator", umausers },
510	{ "umem_cache", "?", "print a umem cache", umem_cache },
511	{ "umem_log", "?", "dump umem transaction log", umem_log },
512	{ "umem_malloc_dist", "[-dg] [-b maxbins] [-B minbinsize]",
513		"report distribution of outstanding malloc()s",
514		umem_malloc_dist, umem_malloc_dist_help },
515	{ "umem_malloc_info", "?[-dg] [-b maxbins] [-B minbinsize]",
516		"report information about malloc()s by cache",
517		umem_malloc_info, umem_malloc_info_help },
518	{ "umem_verify", "?", "check integrity of umem-managed memory",
519		umem_verify },
520	{ "vmem", "?", "print a vmem_t", vmem },
521	{ "vmem_seg", ":[-sv] [-c caller] [-e earliest] [-l latest] "
522		"[-m minsize] [-M maxsize] [-t thread] [-T type]",
523		"print or filter a vmem_seg", vmem_seg, vmem_seg_help },
524
525#ifndef _KMDB
526	/* from ../genunix/kgrep.c + libumem.c */
527	{ "ugrep", KGREP_USAGE, "search user address space for a pointer",
528	    kgrep, kgrep_help },
529
530	/* from ../genunix/leaky.c + leaky_subr.c */
531	{ "findleaks", FINDLEAKS_USAGE, "search for potential memory leaks",
532	    findleaks, findleaks_help },
533#endif
534
535	{ NULL }
536};
537
538static const mdb_walker_t walkers[] = {
539
540	/* from umem.c */
541	{ "allocdby", "given a thread, walk its allocated bufctls",
542		allocdby_walk_init, allocdby_walk_step, allocdby_walk_fini },
543	{ "bufctl", "walk a umem cache's bufctls",
544		bufctl_walk_init, umem_walk_step, umem_walk_fini },
545	{ "bufctl_history", "walk the available history of a bufctl",
546		bufctl_history_walk_init, bufctl_history_walk_step,
547		bufctl_history_walk_fini },
548	{ "freectl", "walk a umem cache's free bufctls",
549		freectl_walk_init, umem_walk_step, umem_walk_fini },
550	{ "freedby", "given a thread, walk its freed bufctls",
551		freedby_walk_init, allocdby_walk_step, allocdby_walk_fini },
552	{ "freemem", "walk a umem cache's free memory",
553		freemem_walk_init, umem_walk_step, umem_walk_fini },
554	{ "umem", "walk a umem cache",
555		umem_walk_init, umem_walk_step, umem_walk_fini },
556	{ "umem_cpu", "walk the umem CPU structures",
557		umem_cpu_walk_init, umem_cpu_walk_step, umem_cpu_walk_fini },
558	{ "umem_cpu_cache", "given a umem cache, walk its per-CPU caches",
559		umem_cpu_cache_walk_init, umem_cpu_cache_walk_step, NULL },
560	{ "umem_hash", "given a umem cache, walk its allocated hash table",
561		umem_hash_walk_init, umem_hash_walk_step, umem_hash_walk_fini },
562	{ "umem_log", "walk the umem transaction log",
563		umem_log_walk_init, umem_log_walk_step, umem_log_walk_fini },
564	{ "umem_slab", "given a umem cache, walk its slabs",
565		umem_slab_walk_init, umem_slab_walk_step, NULL },
566	{ "umem_slab_partial",
567	    "given a umem cache, walk its partially allocated slabs (min 1)",
568		umem_slab_walk_partial_init, umem_slab_walk_step, NULL },
569	{ "vmem", "walk vmem structures in pre-fix, depth-first order",
570		vmem_walk_init, vmem_walk_step, vmem_walk_fini },
571	{ "vmem_alloc", "given a vmem_t, walk its allocated vmem_segs",
572		vmem_alloc_walk_init, vmem_seg_walk_step, vmem_seg_walk_fini },
573	{ "vmem_free", "given a vmem_t, walk its free vmem_segs",
574		vmem_free_walk_init, vmem_seg_walk_step, vmem_seg_walk_fini },
575	{ "vmem_postfix", "walk vmem structures in post-fix, depth-first order",
576		vmem_walk_init, vmem_postfix_walk_step, vmem_walk_fini },
577	{ "vmem_seg", "given a vmem_t, walk all of its vmem_segs",
578		vmem_seg_walk_init, vmem_seg_walk_step, vmem_seg_walk_fini },
579	{ "vmem_span", "given a vmem_t, walk its spanning vmem_segs",
580		vmem_span_walk_init, vmem_seg_walk_step, vmem_seg_walk_fini },
581
582#ifndef _KMDB
583	/* from ../genunix/leaky.c + leaky_subr.c */
584	{ "leak", "given a leak ctl, walk other leaks w/ that stacktrace",
585		leaky_walk_init, leaky_walk_step, leaky_walk_fini },
586	{ "leakbuf", "given a leak ctl, walk addr of leaks w/ that stacktrace",
587		leaky_walk_init, leaky_buf_walk_step, leaky_walk_fini },
588#endif
589
590	{ NULL }
591};
592
593static const mdb_modinfo_t modinfo = {MDB_API_VERSION, dcmds, walkers};
594
595const mdb_modinfo_t *
596_mdb_init(void)
597{
598	if (umem_init() != 0)
599		return (NULL);
600
601	return (&modinfo);
602}
603
604void
605_mdb_fini(void)
606{
607#ifndef _KMDB
608	leaky_cleanup(1);
609#endif
610}
611