xref: /illumos-gate/usr/src/cmd/mdb/common/modules/genunix/taskq.c (revision 489f6310fe8952e87fc1dce8af87990fcfd90f18)
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