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 * Copyright 2023-2024 RackTop Systems, Inc. 26 */ 27 28 #include <mdb/mdb_param.h> 29 #include <mdb/mdb_modapi.h> 30 #include <mdb/mdb_ks.h> 31 #include <sys/taskq.h> 32 #include <sys/taskq_impl.h> 33 34 #include "taskq.h" 35 36 typedef struct tqarray_ent { 37 uintptr_t tq_addr; 38 char tq_name[TASKQ_NAMELEN + 1]; 39 int tq_instance; 40 uint_t tq_flags; 41 } tqarray_ent_t; 42 43 typedef struct tq_info { 44 tqarray_ent_t *tqi_array; 45 size_t tqi_count; 46 size_t tqi_size; 47 } tq_info_t; 48 49 /* 50 * We sort taskqs as follows: 51 * 52 * DYNAMIC last 53 * NOINSTANCE first 54 * within NOINSTANCE, sort by order of creation (instance #) 55 * within non-NOINSTANCE, sort by name (case-insensitive) then instance # 56 */ 57 int 58 tqcmp(const void *lhs, const void *rhs) 59 { 60 const tqarray_ent_t *l = lhs; 61 const tqarray_ent_t *r = rhs; 62 uint_t lflags = l->tq_flags; 63 uint_t rflags = r->tq_flags; 64 int ret; 65 66 if ((lflags & TASKQ_DYNAMIC) && !(rflags & TASKQ_DYNAMIC)) 67 return (1); 68 if (!(lflags & TASKQ_DYNAMIC) && (rflags & TASKQ_DYNAMIC)) 69 return (-1); 70 71 if ((lflags & TASKQ_NOINSTANCE) && !(rflags & TASKQ_NOINSTANCE)) 72 return (-1); 73 if (!(lflags & TASKQ_NOINSTANCE) && (rflags & TASKQ_NOINSTANCE)) 74 return (1); 75 76 if (!(lflags & TASKQ_NOINSTANCE) && 77 (ret = strcasecmp(l->tq_name, r->tq_name)) != 0) 78 return (ret); 79 80 if (l->tq_instance < r->tq_instance) 81 return (-1); 82 if (l->tq_instance > r->tq_instance) 83 return (1); 84 return (0); 85 } 86 87 /*ARGSUSED*/ 88 int 89 tq_count(uintptr_t addr, const void *ignored, void *arg) 90 { 91 tq_info_t *ti = arg; 92 93 ti->tqi_size++; 94 return (WALK_NEXT); 95 } 96 97 /*ARGSUSED*/ 98 int 99 tq_fill(uintptr_t addr, const void *ignored, tq_info_t *ti) 100 { 101 int idx = ti->tqi_count; 102 taskq_t tq; 103 tqarray_ent_t *tqe = &ti->tqi_array[idx]; 104 105 if (idx == ti->tqi_size) { 106 mdb_warn("taskq: inadequate slop\n"); 107 return (WALK_ERR); 108 } 109 if (mdb_vread(&tq, sizeof (tq), addr) == -1) { 110 mdb_warn("unable to read taskq_t at %p", addr); 111 return (WALK_NEXT); 112 } 113 114 ti->tqi_count++; 115 tqe->tq_addr = addr; 116 strncpy(tqe->tq_name, tq.tq_name, TASKQ_NAMELEN); 117 tqe->tq_instance = tq.tq_instance; 118 tqe->tq_flags = tq.tq_flags; 119 120 return (WALK_NEXT); 121 } 122 123 /* 124 * Dcmd: taskq 125 * ::taskq :[-atT] [-m min_maxq] [-n name] 126 * With addr, display taskq details 127 * Without addr, list all, filtered 128 */ 129 void 130 taskq_help(void) 131 { 132 mdb_printf("%s", 133 " -a Only show taskqs with active threads.\n" 134 " -t Display active thread stacks in each taskq.\n" 135 " -T Display all thread stacks in each taskq.\n" 136 " -m min_maxq\n" 137 " Only show Dynamic taskqs and taskqs with a MAXQ of at\n" 138 " least min_maxq.\n" 139 " -n name\n" 140 " Only show taskqs which contain name somewhere in their\n" 141 " name.\n"); 142 } 143 144 int 145 taskq(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) 146 { 147 taskq_t tq; 148 149 const char *name = NULL; 150 uintptr_t minmaxq = 0; 151 uint_t active = FALSE; 152 uint_t print_threads = FALSE; 153 uint_t print_threads_all = FALSE; 154 155 size_t tact, tcount, queued, maxq; 156 157 if (mdb_getopts(argc, argv, 158 'a', MDB_OPT_SETBITS, TRUE, &active, 159 'm', MDB_OPT_UINTPTR, &minmaxq, 160 'n', MDB_OPT_STR, &name, 161 't', MDB_OPT_SETBITS, TRUE, &print_threads, 162 'T', MDB_OPT_SETBITS, TRUE, &print_threads_all, 163 NULL) != argc) 164 return (DCMD_USAGE); 165 166 if (!(flags & DCMD_ADDRSPEC)) { 167 size_t idx; 168 tq_info_t tqi; 169 170 bzero(&tqi, sizeof (tqi)); 171 172 if (mdb_walk("taskq_cache", tq_count, &tqi) == -1) { 173 mdb_warn("unable to walk taskq_cache"); 174 return (DCMD_ERR); 175 } 176 tqi.tqi_size += 10; /* slop */ 177 tqi.tqi_array = mdb_zalloc( 178 sizeof (*tqi.tqi_array) * tqi.tqi_size, UM_SLEEP|UM_GC); 179 180 if (mdb_walk("taskq_cache", (mdb_walk_cb_t)tq_fill, 181 &tqi) == -1) { 182 mdb_warn("unable to walk taskq_cache"); 183 return (DCMD_ERR); 184 } 185 qsort(tqi.tqi_array, tqi.tqi_count, sizeof (*tqi.tqi_array), 186 tqcmp); 187 188 flags &= ~DCMD_PIPE; 189 flags |= DCMD_LOOP | DCMD_LOOPFIRST | DCMD_ADDRSPEC; 190 for (idx = 0; idx < tqi.tqi_count; idx++) { 191 int ret = taskq(tqi.tqi_array[idx].tq_addr, flags, 192 argc, argv); 193 if (ret != DCMD_OK) 194 return (ret); 195 flags &= ~DCMD_LOOPFIRST; 196 } 197 198 return (DCMD_OK); 199 } 200 201 if (DCMD_HDRSPEC(flags) && !(flags & DCMD_PIPE_OUT)) { 202 mdb_printf("%<u>%-?s %-31s %4s/%4s %4s %5s %4s%</u>\n", 203 "ADDR", "NAME", "ACT", "THDS", 204 "Q'ED", "MAXQ", "INST"); 205 } 206 207 if (mdb_vread(&tq, sizeof (tq), addr) == -1) { 208 mdb_warn("failed to read taskq_t at %p", addr); 209 return (DCMD_ERR); 210 } 211 212 /* terminate the name, just in case */ 213 tq.tq_name[sizeof (tq.tq_name) - 1] = 0; 214 215 tact = tq.tq_active; 216 tcount = tq.tq_nthreads; 217 queued = tq.tq_tasks - tq.tq_executed; 218 maxq = tq.tq_maxtasks; 219 220 if (tq.tq_flags & TASKQ_DYNAMIC) { 221 size_t bsize = tq.tq_nbuckets * sizeof (*tq.tq_buckets); 222 size_t idx; 223 taskq_bucket_t *b = mdb_zalloc(bsize, UM_SLEEP | UM_GC); 224 225 if (mdb_vread(b, bsize, (uintptr_t)tq.tq_buckets) == -1) { 226 mdb_warn("unable to read buckets for taskq %p", addr); 227 return (DCMD_ERR); 228 } 229 230 tcount += tq.tq_dnthreads; 231 232 /* 233 * There are actually (tq.tq_nbuckets + 1) buckets now, 234 * with the + 1 used as the "idle bucket". That never 235 * has nalloc or nbacklog, so ignoring it here. 236 */ 237 for (idx = 0; idx < tq.tq_nbuckets; idx++) { 238 tact += b[idx].tqbucket_nalloc; 239 queued += b[idx].tqbucket_nbacklog; 240 } 241 } 242 243 /* filter out taskqs that aren't of interest. */ 244 if (name != NULL && strstr(tq.tq_name, name) == NULL) 245 return (DCMD_OK); 246 if (active && tact == 0 && queued == 0) 247 return (DCMD_OK); 248 if (!(tq.tq_flags & TASKQ_DYNAMIC) && maxq < minmaxq) 249 return (DCMD_OK); 250 251 if (flags & DCMD_PIPE_OUT) { 252 mdb_printf("%#lr\n", addr); 253 return (DCMD_OK); 254 } 255 256 mdb_printf("%?p %-31s %4d/%4d %4d ", 257 addr, tq.tq_name, tact, tcount, queued); 258 259 if (tq.tq_flags & TASKQ_DYNAMIC) 260 mdb_printf("%5s ", "-"); 261 else 262 mdb_printf("%5d ", maxq); 263 264 if (tq.tq_flags & TASKQ_NOINSTANCE) 265 mdb_printf("%4s", "-"); 266 else 267 mdb_printf("%4x", tq.tq_instance); 268 269 mdb_printf("\n"); 270 271 if (print_threads || print_threads_all) { 272 int ret; 273 char strbuf[128]; 274 const char *arg = 275 print_threads_all ? "" : "-C \"taskq_thread_wait\""; 276 277 /* 278 * We can't use mdb_pwalk_dcmd() here, because ::stacks needs 279 * to get the full pipeline. 280 */ 281 mdb_snprintf(strbuf, sizeof (strbuf), 282 "%p::walk taskq_thread | ::stacks -a %s", 283 addr, arg); 284 285 (void) mdb_inc_indent(4); 286 ret = mdb_eval(strbuf); 287 (void) mdb_dec_indent(4); 288 289 /* abort, since they could have control-Ced the eval */ 290 if (ret == -1) 291 return (DCMD_ABORT); 292 } 293 294 return (DCMD_OK); 295 } 296 297 /* 298 * Dcmd: "taskq_entry" 299 * Dump a taskq_ent_t given its address. 300 */ 301 /*ARGSUSED*/ 302 int 303 taskq_ent(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) 304 { 305 taskq_ent_t taskq_ent; 306 307 if (!(flags & DCMD_ADDRSPEC)) { 308 return (DCMD_USAGE); 309 } 310 311 if (mdb_vread(&taskq_ent, sizeof (taskq_ent_t), addr) == -1) { 312 mdb_warn("failed to read taskq_ent_t at %p", addr); 313 return (DCMD_ERR); 314 } 315 316 if (DCMD_HDRSPEC(flags)) { 317 mdb_printf("%<u>%-?s %-?s %-s%</u>\n", 318 "ENTRY", "ARG", "FUNCTION"); 319 } 320 321 mdb_printf("%-?p %-?p %a\n", addr, taskq_ent.tqent_arg, 322 taskq_ent.tqent_func); 323 324 return (DCMD_OK); 325 } 326 327 /* 328 * Walker: "taskq_entry"; taskq_ent_walk_{init,step} 329 * 330 * Given the address of the (taskq_t) task queue head, walk the queue listing 331 * the address of every taskq_ent_t. On dynamic taskq, enum. backlog. 332 */ 333 struct mdb_tqe_walk_data { 334 taskq_ent_t tq_ent; /* working buffer */ 335 uintptr_t tq_addr; /* taskq ptr, for debug */ 336 uint_t tq_nbuckets; /* from taskq.tq_nbuckets */ 337 int tq_bidx; /* index of next bucket */ 338 uintptr_t tq_bucket; /* next bucket we'll emum. */ 339 uintptr_t tqent_head; /* ptr to list head we're on */ 340 uintptr_t tqent_cur; /* ptr to current list element */ 341 }; 342 343 int 344 taskq_ent_walk_init(mdb_walk_state_t *wsp) 345 { 346 taskq_t tq; 347 struct mdb_tqe_walk_data *wd; 348 349 if (wsp->walk_addr == 0) { 350 mdb_warn("start address required (taskq_t)\n"); 351 return (WALK_ERR); 352 } 353 354 /* 355 * Get our walk state storage. Auto-GC 356 */ 357 wd = mdb_zalloc(sizeof (*wd), UM_SLEEP | UM_GC); 358 wsp->walk_data = wd; 359 360 /* 361 * Read in taskq head, setup walk data. 362 */ 363 if (mdb_vread((void *)&tq, sizeof (taskq_t), wsp->walk_addr) == -1) { 364 mdb_warn("failed to read taskq_t at %p", wsp->walk_addr); 365 return (WALK_ERR); 366 } 367 wd->tq_addr = wsp->walk_addr; 368 wd->tq_nbuckets = tq.tq_nbuckets; 369 wd->tq_bucket = (uintptr_t)tq.tq_buckets; 370 if (wd->tq_bucket == 0) 371 wd->tq_nbuckets = 0; 372 wd->tq_bidx = -1; /* for tq_task */ 373 374 return (WALK_NEXT); 375 } 376 377 int 378 taskq_ent_walk_step(mdb_walk_state_t *wsp) 379 { 380 struct mdb_tqe_walk_data *wd; 381 int status; 382 383 wd = wsp->walk_data; 384 385 /* 386 * If done in the current bucket, 387 * move to next bucket's list head. 388 */ 389 while (wd->tqent_cur == wd->tqent_head) { 390 391 /* Terminate when no more buckets. */ 392 if (wd->tq_bidx == wd->tq_nbuckets) 393 return (WALK_DONE); 394 395 /* 396 * Setup next list head. First bidx is -1 which 397 * means enumerate in the tq.tq_task list. 398 * Then bidx >= 0 are the taskq buckets. 399 */ 400 if (wd->tq_bidx == -1) { 401 /* enum. in tq_task */ 402 wd->tqent_head = wd->tq_addr + 403 OFFSETOF(taskq_t, tq_task); 404 405 /* next is bucket zero */ 406 wd->tq_bidx = 0; 407 } else { 408 /* enum in tq_bucket.tqbucket_backlog */ 409 wd->tqent_head = wd->tq_bucket + 410 OFFSETOF(taskq_bucket_t, tqbucket_backlog); 411 412 /* next bucket */ 413 wd->tq_bucket += sizeof (taskq_bucket_t); 414 wd->tq_bidx++; 415 } 416 417 /* read the list head, get next pointer */ 418 if (mdb_vread(&wd->tq_ent, sizeof (taskq_ent_t), 419 wd->tqent_head) == -1) { 420 mdb_warn("failed to read taskq_ent_t at %p", 421 wd->tqent_head); 422 wd->tqent_cur = wd->tqent_head; 423 } else { 424 /* Moved to a new list head */ 425 wd->tqent_cur = (uintptr_t)wd->tq_ent.tqent_next; 426 } 427 } 428 wsp->walk_addr = wd->tqent_cur; 429 430 /* read the entry, do callback */ 431 if (mdb_vread(&wd->tq_ent, sizeof (taskq_ent_t), 432 wd->tqent_cur) == -1) { 433 mdb_warn("failed to read taskq_ent_t at %p", wd->tqent_cur); 434 status = WALK_NEXT; 435 /* finish with this bucket */ 436 wd->tqent_cur = wd->tqent_head; 437 } else { 438 status = wsp->walk_callback(wd->tqent_cur, &wd->tq_ent, 439 wsp->walk_cbdata); 440 /* next entry in this bucket */ 441 wd->tqent_cur = (uintptr_t)wd->tq_ent.tqent_next; 442 } 443 444 return (status); 445 } 446 447 /* 448 * Walker: "taskq_thread"; taskq_thread_walk_{init,step,fini} 449 * given a taskq_t, list all of its threads 450 */ 451 typedef struct taskq_thread_info { 452 uintptr_t tti_addr; 453 uintptr_t *tti_tlist; 454 size_t tti_nthreads; 455 size_t tti_idx; 456 457 kthread_t tti_thread; 458 } taskq_thread_info_t; 459 460 int 461 taskq_thread_walk_init(mdb_walk_state_t *wsp) 462 { 463 taskq_thread_info_t *tti; 464 taskq_t tq; 465 uintptr_t *tlist; 466 size_t nthreads; 467 468 tti = wsp->walk_data = mdb_zalloc(sizeof (*tti), UM_SLEEP); 469 tti->tti_addr = wsp->walk_addr; 470 471 if (wsp->walk_addr != 0 && 472 mdb_vread(&tq, sizeof (tq), wsp->walk_addr) != -1 && 473 !(tq.tq_flags & TASKQ_DYNAMIC)) { 474 475 nthreads = tq.tq_nthreads; 476 tlist = mdb_alloc(nthreads * sizeof (*tlist), UM_SLEEP); 477 if (tq.tq_nthreads_max == 1) { 478 tlist[0] = (uintptr_t)tq.tq_thread; 479 480 } else if (mdb_vread(tlist, nthreads * sizeof (*tlist), 481 (uintptr_t)tq.tq_threadlist) == -1) { 482 mdb_warn("unable to read threadlist for taskq_t %p", 483 wsp->walk_addr); 484 mdb_free(tlist, nthreads * sizeof (*tlist)); 485 return (WALK_ERR); 486 } 487 488 tti->tti_tlist = tlist; 489 tti->tti_nthreads = nthreads; 490 return (WALK_NEXT); 491 } 492 493 wsp->walk_addr = 0; 494 if (mdb_layered_walk("thread", wsp) == -1) { 495 mdb_warn("can't walk \"thread\""); 496 return (WALK_ERR); 497 } 498 return (0); 499 } 500 501 int 502 taskq_thread_walk_step(mdb_walk_state_t *wsp) 503 { 504 taskq_thread_info_t *tti = wsp->walk_data; 505 506 const kthread_t *kt = wsp->walk_layer; 507 taskq_t *tq = (taskq_t *)tti->tti_addr; 508 509 if (kt == NULL) { 510 uintptr_t addr; 511 512 if (tti->tti_idx >= tti->tti_nthreads) 513 return (WALK_DONE); 514 515 addr = tti->tti_tlist[tti->tti_idx]; 516 tti->tti_idx++; 517 518 if (addr == 0) 519 return (WALK_NEXT); 520 521 if (mdb_vread(&tti->tti_thread, sizeof (kthread_t), 522 addr) == -1) { 523 mdb_warn("unable to read kthread_t at %p", addr); 524 return (WALK_ERR); 525 } 526 return (wsp->walk_callback(addr, &tti->tti_thread, 527 wsp->walk_cbdata)); 528 } 529 530 if (kt->t_taskq == NULL) 531 return (WALK_NEXT); 532 533 if (tq != NULL && kt->t_taskq != tq) 534 return (WALK_NEXT); 535 536 return (wsp->walk_callback(wsp->walk_addr, kt, wsp->walk_cbdata)); 537 } 538 539 void 540 taskq_thread_walk_fini(mdb_walk_state_t *wsp) 541 { 542 taskq_thread_info_t *tti = wsp->walk_data; 543 544 if (tti->tti_nthreads > 0) { 545 mdb_free(tti->tti_tlist, 546 tti->tti_nthreads * sizeof (*tti->tti_tlist)); 547 } 548 mdb_free(tti, sizeof (*tti)); 549 } 550