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
tqcmp(const void * lhs,const void * rhs)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
tq_count(uintptr_t addr,const void * ignored,void * arg)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
tq_fill(uintptr_t addr,const void * ignored,tq_info_t * ti)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
taskq_help(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
taskq(uintptr_t addr,uint_t flags,int argc,const mdb_arg_t * argv)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
taskq_ent(uintptr_t addr,uint_t flags,int argc,const mdb_arg_t * argv)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
taskq_ent_walk_init(mdb_walk_state_t * wsp)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
taskq_ent_walk_step(mdb_walk_state_t * wsp)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
taskq_thread_walk_init(mdb_walk_state_t * wsp)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
taskq_thread_walk_step(mdb_walk_state_t * wsp)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
taskq_thread_walk_fini(mdb_walk_state_t * wsp)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