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
34 typedef 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
41 typedef 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 */
55 int
tqcmp(const void * lhs,const void * rhs)56 tqcmp(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*/
86 int
tq_count(uintptr_t addr,const void * ignored,void * arg)87 tq_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*/
96 int
tq_fill(uintptr_t addr,const void * ignored,tq_info_t * ti)97 tq_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
121 void
taskq_help(void)122 taskq_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
136 int
taskq(uintptr_t addr,uint_t flags,int argc,const mdb_arg_t * argv)137 taskq(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*/
287 int
taskq_ent(uintptr_t addr,uint_t flags,int argc,const mdb_arg_t * argv)288 taskq_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 */
317 int
taskq_ent_walk_init(mdb_walk_state_t * wsp)318 taskq_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
358 int
taskq_ent_walk_step(mdb_walk_state_t * wsp)359 taskq_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
385 typedef 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
394 int
taskq_thread_walk_init(mdb_walk_state_t * wsp)395 taskq_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
435 int
taskq_thread_walk_step(mdb_walk_state_t * wsp)436 taskq_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
473 void
taskq_thread_walk_fini(mdb_walk_state_t * wsp)474 taskq_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