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 #include <stdio.h> 43 44 #include "umem_pagesize.h" 45 46 typedef struct datafmt { 47 char *hdr1; 48 char *hdr2; 49 char *dashes; 50 char *fmt; 51 } datafmt_t; 52 53 static datafmt_t ptcfmt[] = { 54 { " ", "tid", "---", "%3u " }, 55 { " memory", " cached", "-------", "%7lH " }, 56 { " %", "cap", "---", "%3u " }, 57 { " %", NULL, "---", "%3u " }, 58 { NULL, NULL, NULL, NULL } 59 }; 60 61 static datafmt_t umemfmt[] = { 62 { "cache ", "name ", 63 "-------------------------", "%-25s " }, 64 { " buf", " size", "------", "%6u " }, 65 { " buf", " in use", "-------", "%7u " }, 66 { " buf", " in ptc", "-------", "%7s " }, 67 { " buf", " total", "-------", "%7u " }, 68 { " memory", " in use", "-------", "%7H " }, 69 { " alloc", " succeed", "---------", "%9u " }, 70 { "alloc", " fail", "-----", "%5llu" }, 71 { NULL, NULL, NULL, NULL } 72 }; 73 74 static datafmt_t vmemfmt[] = { 75 { "vmem ", "name ", 76 "-------------------------", "%-*s " }, 77 { " memory", " in use", "---------", "%9H " }, 78 { " memory", " total", "----------", "%10H " }, 79 { " memory", " import", "---------", "%9H " }, 80 { " alloc", " succeed", "---------", "%9llu " }, 81 { "alloc", " fail", "-----", "%5llu " }, 82 { NULL, NULL, NULL, NULL } 83 }; 84 85 /*ARGSUSED*/ 86 static int 87 umastat_cpu_avail(uintptr_t addr, const umem_cpu_cache_t *ccp, int *avail) 88 { 89 if (ccp->cc_rounds > 0) 90 *avail += ccp->cc_rounds; 91 if (ccp->cc_prounds > 0) 92 *avail += ccp->cc_prounds; 93 94 return (WALK_NEXT); 95 } 96 97 /*ARGSUSED*/ 98 static int 99 umastat_cpu_alloc(uintptr_t addr, const umem_cpu_cache_t *ccp, int *alloc) 100 { 101 *alloc += ccp->cc_alloc; 102 103 return (WALK_NEXT); 104 } 105 106 /*ARGSUSED*/ 107 static int 108 umastat_slab_avail(uintptr_t addr, const umem_slab_t *sp, int *avail) 109 { 110 *avail += sp->slab_chunks - sp->slab_refcnt; 111 112 return (WALK_NEXT); 113 } 114 115 typedef struct umastat_vmem { 116 uintptr_t kv_addr; 117 struct umastat_vmem *kv_next; 118 int kv_meminuse; 119 int kv_alloc; 120 int kv_fail; 121 } umastat_vmem_t; 122 123 /*ARGSUSED*/ 124 static int 125 umastat_cache_nptc(uintptr_t addr, const umem_cache_t *cp, int *nptc) 126 { 127 if (!(cp->cache_flags & UMF_PTC)) 128 return (WALK_NEXT); 129 130 (*nptc)++; 131 return (WALK_NEXT); 132 } 133 134 /*ARGSUSED*/ 135 static int 136 umastat_cache_hdr(uintptr_t addr, const umem_cache_t *cp, void *ignored) 137 { 138 if (!(cp->cache_flags & UMF_PTC)) 139 return (WALK_NEXT); 140 141 mdb_printf("%3d ", cp->cache_bufsize); 142 return (WALK_NEXT); 143 } 144 145 /*ARGSUSED*/ 146 static int 147 umastat_lwp_ptc(uintptr_t addr, void *buf, int *nbufs) 148 { 149 (*nbufs)++; 150 return (WALK_NEXT); 151 } 152 153 /*ARGSUSED*/ 154 static int 155 umastat_lwp_cache(uintptr_t addr, const umem_cache_t *cp, ulwp_t *ulwp) 156 { 157 char walk[60]; 158 int nbufs = 0; 159 160 if (!(cp->cache_flags & UMF_PTC)) 161 return (WALK_NEXT); 162 163 (void) snprintf(walk, sizeof (walk), "umem_ptc_%d", 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*/ 178 static int 179 umastat_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*/ 206 static int 207 umastat_cache_ptc(uintptr_t addr, const void *ignored, int *nptc) 208 { 209 (*nptc)++; 210 return (WALK_NEXT); 211 } 212 213 static int 214 umastat_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) 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) 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; 263 out: 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 281 static int 282 umastat_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*/ 298 static int 299 umastat_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 != NULL; 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*/ 329 int 330 umastat(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 446 typedef struct ugrep_walk_data { 447 kgrep_cb_func *ug_cb; 448 void *ug_cbdata; 449 } ugrep_walk_data_t; 450 451 /*ARGSUSED*/ 452 int 453 ugrep_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 462 int 463 kgrep_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 481 size_t 482 kgrep_subr_pagesize(void) 483 { 484 return (PAGESIZE); 485 } 486 487 #endif /* !_KMDB */ 488 489 static 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 538 static 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 593 static const mdb_modinfo_t modinfo = {MDB_API_VERSION, dcmds, walkers}; 594 595 const mdb_modinfo_t * 596 _mdb_init(void) 597 { 598 if (umem_init() != 0) 599 return (NULL); 600 601 return (&modinfo); 602 } 603 604 void 605 _mdb_fini(void) 606 { 607 #ifndef _KMDB 608 leaky_cleanup(1); 609 #endif 610 } 611