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