xref: /netbsd-src/usr.sbin/lockstat/main.c (revision 500db002748d9818288e46e10f026a2b09548086)
1 /*	$NetBSD: main.c,v 1.15 2009/02/22 15:08:58 ad Exp $	*/
2 
3 /*-
4  * Copyright (c) 2006, 2007 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Andrew Doran.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 /*
33  * TODO:
34  *
35  * - Tracking of times for sleep locks is broken.
36  * - Need better analysis and tracking of events.
37  * - Shouldn't have to parse the namelist here.  We should use something like
38  *   FreeBSD's libelf.
39  * - The way the namelist is searched sucks, is it worth doing something
40  *   better?
41  */
42 
43 #include <sys/cdefs.h>
44 #ifndef lint
45 __RCSID("$NetBSD: main.c,v 1.15 2009/02/22 15:08:58 ad Exp $");
46 #endif /* not lint */
47 
48 #include <sys/types.h>
49 #include <sys/param.h>
50 #include <sys/time.h>
51 #include <sys/fcntl.h>
52 #include <sys/ioctl.h>
53 #include <sys/wait.h>
54 #include <sys/signal.h>
55 #include <sys/sysctl.h>
56 
57 #include <dev/lockstat.h>
58 
59 #include <stdio.h>
60 #include <stdlib.h>
61 #include <string.h>
62 #include <limits.h>
63 #include <unistd.h>
64 #include <err.h>
65 #include <paths.h>
66 #include <util.h>
67 #include <ctype.h>
68 #include <errno.h>
69 #include <stdbool.h>
70 
71 #include "extern.h"
72 
73 #define	_PATH_DEV_LOCKSTAT	"/dev/lockstat"
74 
75 #define	MILLI	1000.0
76 #define	MICRO	1000000.0
77 #define	NANO	1000000000.0
78 #define	PICO	1000000000000.0
79 
80 TAILQ_HEAD(lock_head, lockstruct);
81 typedef struct lock_head locklist_t;
82 TAILQ_HEAD(buf_head, lsbuf);
83 typedef struct buf_head buflist_t;
84 
85 typedef struct lockstruct {
86 	TAILQ_ENTRY(lockstruct)	chain;
87 	buflist_t		bufs;
88 	buflist_t		tosort;
89 	uintptr_t		lock;
90  	double			time;
91 	uint32_t		count;
92 	u_int			flags;
93 	u_int			nbufs;
94 	char			name[NAME_SIZE];
95 } lock_t;
96 
97 typedef struct name {
98 	const char	*name;
99 	int		mask;
100 } name_t;
101 
102 const name_t locknames[] = {
103 	{ "adaptive_mutex", LB_ADAPTIVE_MUTEX },
104 	{ "spin_mutex", LB_SPIN_MUTEX },
105 	{ "rwlock", LB_RWLOCK },
106 	{ "kernel_lock", LB_KERNEL_LOCK },
107 	{ "preemption", LB_NOPREEMPT },
108 	{ "misc", LB_MISC },
109 	{ NULL, 0 }
110 };
111 
112 const name_t eventnames[] = {
113 	{ "spin", LB_SPIN },
114 	{ "sleep_exclusive", LB_SLEEP1 },
115 	{ "sleep_shared", LB_SLEEP2 },
116 	{ NULL, 0 },
117 };
118 
119 const name_t alltypes[] = {
120 	{ "Adaptive mutex spin", LB_ADAPTIVE_MUTEX | LB_SPIN },
121 	{ "Adaptive mutex sleep", LB_ADAPTIVE_MUTEX | LB_SLEEP1 },
122 	{ "Spin mutex spin", LB_SPIN_MUTEX | LB_SPIN },
123 	{ "RW lock sleep (writer)", LB_RWLOCK | LB_SLEEP1 },
124 	{ "RW lock sleep (reader)", LB_RWLOCK | LB_SLEEP2 },
125 	{ "RW lock spin", LB_RWLOCK | LB_SPIN },
126 	{ "Kernel lock spin", LB_KERNEL_LOCK | LB_SPIN },
127 	{ "Kernel preemption defer", LB_NOPREEMPT | LB_SPIN },
128 	{ "Miscellaneous wait", LB_MISC | LB_SPIN },
129 	{ NULL, 0 }
130 };
131 
132 locklist_t	locklist;
133 locklist_t	freelist;
134 locklist_t	sortlist;
135 
136 lsbuf_t		*bufs;
137 lsdisable_t	ld;
138 bool		lflag;
139 bool		fflag;
140 int		nbufs;
141 bool		cflag;
142 int		lsfd;
143 int		displayed;
144 int		bin64;
145 double		tscale;
146 double		cscale;
147 double		cpuscale[sizeof(ld.ld_freq) / sizeof(ld.ld_freq[0])];
148 FILE		*outfp;
149 
150 void	findsym(findsym_t, char *, uintptr_t *, uintptr_t *, bool);
151 void	spawn(int, char **);
152 void	display(int, const char *name);
153 void	listnames(const name_t *);
154 void	collapse(bool, bool);
155 int	matchname(const name_t *, char *);
156 void	makelists(int, int);
157 void	nullsig(int);
158 void	usage(void);
159 int	ncpu(void);
160 lock_t	*morelocks(void);
161 
162 int
163 main(int argc, char **argv)
164 {
165 	int eventtype, locktype, ch, nlfd, fd, i;
166 	bool sflag, pflag, mflag, Mflag;
167 	const char *nlistf, *outf;
168 	char *lockname, *funcname;
169 	const name_t *name;
170 	lsenable_t le;
171 	double ms;
172 	char *p;
173 
174 	nlistf = NULL;
175 	outf = NULL;
176 	lockname = NULL;
177 	funcname = NULL;
178 	eventtype = -1;
179 	locktype = -1;
180 	nbufs = 0;
181 	sflag = false;
182 	pflag = false;
183 	mflag = false;
184 	Mflag = false;
185 
186 	while ((ch = getopt(argc, argv, "E:F:L:MN:T:b:ceflmo:pst")) != -1)
187 		switch (ch) {
188 		case 'E':
189 			eventtype = matchname(eventnames, optarg);
190 			break;
191 		case 'F':
192 			funcname = optarg;
193 			break;
194 		case 'L':
195 			lockname = optarg;
196 			break;
197 		case 'N':
198 			nlistf = optarg;
199 			break;
200 		case 'T':
201 			locktype = matchname(locknames, optarg);
202 			break;
203 		case 'b':
204 			nbufs = (int)strtol(optarg, &p, 0);
205 			if (!isdigit((u_int)*optarg) || *p != '\0')
206 				usage();
207 			break;
208 		case 'c':
209 			cflag = true;
210 			break;
211 		case 'e':
212 			listnames(eventnames);
213 			break;
214 		case 'f':
215 			fflag = true;
216 			break;
217 		case 'l':
218 			lflag = true;
219 			break;
220 		case 'm':
221 			mflag = true;
222 			break;
223 		case 'M':
224 			Mflag = true;
225 			break;
226 		case 'o':
227 			outf = optarg;
228 			break;
229 		case 'p':
230 			pflag = true;
231 			break;
232 		case 's':
233 			sflag = true;
234 			break;
235 		case 't':
236 			listnames(locknames);
237 			break;
238 		default:
239 			usage();
240 		}
241 	argc -= optind;
242 	argv += optind;
243 
244 	if (*argv == NULL)
245 		usage();
246 
247 	if (outf) {
248 		fd = open(outf, O_WRONLY | O_CREAT | O_TRUNC, 0600);
249 		if (fd == -1)
250 			err(EXIT_FAILURE, "opening %s", outf);
251 		outfp = fdopen(fd, "w");
252 	} else
253 		outfp = stdout;
254 
255 	/*
256 	 * Find the name list for resolving symbol names, and load it into
257 	 * memory.
258 	 */
259 	if (nlistf == NULL) {
260 		nlfd = open(_PATH_KSYMS, O_RDONLY);
261 		nlistf = getbootfile();
262 	} else
263 		nlfd = -1;
264 	if (nlfd == -1) {
265 		if ((nlfd = open(nlistf, O_RDONLY)) < 0)
266 			err(EXIT_FAILURE, "cannot open " _PATH_KSYMS " or %s",
267 			    nlistf);
268 	}
269 	if (loadsym32(nlfd) != 0) {
270 		if (loadsym64(nlfd) != 0)
271 			errx(EXIT_FAILURE, "unable to load symbol table");
272 		bin64 = 1;
273 	}
274 	close(nlfd);
275 
276 	memset(&le, 0, sizeof(le));
277 	le.le_nbufs = nbufs;
278 
279 	/*
280 	 * Set up initial filtering.
281 	 */
282 	if (lockname != NULL) {
283 		findsym(LOCK_BYNAME, lockname, &le.le_lockstart,
284 		    &le.le_lockend, true);
285 		le.le_flags |= LE_ONE_LOCK;
286 	}
287 	if (!lflag)
288 		le.le_flags |= LE_CALLSITE;
289 	if (!fflag)
290 		le.le_flags |= LE_LOCK;
291 	if (funcname != NULL) {
292 		if (lflag)
293 			usage();
294 		findsym(FUNC_BYNAME, funcname, &le.le_csstart, &le.le_csend, true);
295 		le.le_flags |= LE_ONE_CALLSITE;
296 	}
297 	le.le_mask = (eventtype & LB_EVENT_MASK) | (locktype & LB_LOCK_MASK);
298 
299 	/*
300 	 * Start tracing.
301 	 */
302 	if ((lsfd = open(_PATH_DEV_LOCKSTAT, O_RDONLY)) < 0)
303 		err(EXIT_FAILURE, "cannot open " _PATH_DEV_LOCKSTAT);
304 	if (ioctl(lsfd, IOC_LOCKSTAT_GVERSION, &ch) < 0)
305 		err(EXIT_FAILURE, "ioctl");
306 	if (ch != LS_VERSION)
307 		errx(EXIT_FAILURE,
308 		    "incompatible lockstat interface version (%d, kernel %d)",
309 			LS_VERSION, ch);
310 	if (ioctl(lsfd, IOC_LOCKSTAT_ENABLE, &le))
311 		err(EXIT_FAILURE, "cannot enable tracing");
312 
313 	/*
314 	 * Execute the traced program.
315 	 */
316 	spawn(argc, argv);
317 
318 	/*
319 	 * Stop tracing, and read the trace buffers from the kernel.
320 	 */
321 	if (ioctl(lsfd, IOC_LOCKSTAT_DISABLE, &ld) == -1) {
322 		if (errno == EOVERFLOW) {
323 			warnx("overflowed available kernel trace buffers");
324 			exit(EXIT_FAILURE);
325 		}
326 		err(EXIT_FAILURE, "cannot disable tracing");
327 	}
328 	if ((bufs = malloc(ld.ld_size)) == NULL)
329 		err(EXIT_FAILURE, "cannot allocate memory for user buffers");
330 	if (read(lsfd, bufs, ld.ld_size) != ld.ld_size)
331 		err(EXIT_FAILURE, "reading from " _PATH_DEV_LOCKSTAT);
332 	if (close(lsfd))
333 		err(EXIT_FAILURE, "close(" _PATH_DEV_LOCKSTAT ")");
334 
335 	/*
336 	 * Figure out how to scale the results.  For internal use we convert
337 	 * all times from CPU frequency based to picoseconds, and values are
338 	 * eventually displayed in ms.
339 	 */
340 	for (i = 0; i < sizeof(ld.ld_freq) / sizeof(ld.ld_freq[0]); i++)
341 		if (ld.ld_freq[i] != 0)
342 			cpuscale[i] = PICO / ld.ld_freq[i];
343 	ms = ld.ld_time.tv_sec * MILLI + ld.ld_time.tv_nsec / MICRO;
344 	if (pflag)
345 		cscale = 1.0 / ncpu();
346 	else
347 		cscale = 1.0;
348 	cscale *= (sflag ? MILLI / ms : 1.0);
349 	tscale = cscale / NANO;
350 	nbufs = (int)(ld.ld_size / sizeof(lsbuf_t));
351 
352 	TAILQ_INIT(&locklist);
353 	TAILQ_INIT(&sortlist);
354 	TAILQ_INIT(&freelist);
355 
356 	if ((mflag | Mflag) != 0)
357 		collapse(mflag, Mflag);
358 
359 	/*
360 	 * Display the results.
361 	 */
362 	fprintf(outfp, "Elapsed time: %.2f seconds.", ms / MILLI);
363 	if (sflag || pflag) {
364 		fprintf(outfp, " Displaying ");
365 		if (pflag)
366 			fprintf(outfp, "per-CPU ");
367 		if (sflag)
368 			fprintf(outfp, "per-second ");
369 		fprintf(outfp, "averages.");
370 	}
371 	putc('\n', outfp);
372 
373 	for (name = alltypes; name->name != NULL; name++) {
374 		if (eventtype != -1 &&
375 		    (name->mask & LB_EVENT_MASK) != eventtype)
376 			continue;
377 		if (locktype != -1 &&
378 		    (name->mask & LB_LOCK_MASK) != locktype)
379 			continue;
380 
381 		display(name->mask, name->name);
382 	}
383 
384 	if (displayed == 0)
385 		fprintf(outfp, "None of the selected events were recorded.\n");
386 	exit(EXIT_SUCCESS);
387 }
388 
389 void
390 usage(void)
391 {
392 
393 	fprintf(stderr,
394 	    "%s: usage:\n"
395 	    "%s [options] <command>\n\n"
396 	    "-b nbuf\t\tset number of event buffers to allocate\n"
397 	    "-c\t\treport percentage of total events by count, not time\n"
398 	    "-E event\t\tdisplay only one type of event\n"
399 	    "-e\t\tlist event types\n"
400 	    "-F func\t\tlimit trace to one function\n"
401 	    "-f\t\ttrace only by function\n"
402 	    "-L lock\t\tlimit trace to one lock (name, or address)\n"
403 	    "-l\t\ttrace only by lock\n"
404 	    "-M\t\tmerge lock addresses within unique objects\n"
405 	    "-m\t\tmerge call sites within unique functions\n"
406 	    "-N nlist\tspecify name list file\n"
407 	    "-o file\t\tsend output to named file, not stdout\n"
408 	    "-p\t\tshow average count/time per CPU, not total\n"
409 	    "-s\t\tshow average count/time per second, not total\n"
410 	    "-T type\t\tdisplay only one type of lock\n"
411 	    "-t\t\tlist lock types\n",
412 	    getprogname(), getprogname());
413 
414 	exit(EXIT_FAILURE);
415 }
416 
417 void
418 nullsig(int junk)
419 {
420 
421 	(void)junk;
422 }
423 
424 void
425 listnames(const name_t *name)
426 {
427 
428 	for (; name->name != NULL; name++)
429 		printf("%s\n", name->name);
430 
431 	exit(EXIT_SUCCESS);
432 }
433 
434 int
435 matchname(const name_t *name, char *string)
436 {
437 	int empty, mask;
438 	char *sp;
439 
440 	empty = 1;
441 	mask = 0;
442 
443 	while ((sp = strsep(&string, ",")) != NULL) {
444 		if (*sp == '\0')
445 			usage();
446 
447 		for (; name->name != NULL; name++) {
448 			if (strcasecmp(name->name, sp) == 0) {
449 				mask |= name->mask;
450 				break;
451 			}
452 		}
453 		if (name->name == NULL)
454 			errx(EXIT_FAILURE, "unknown identifier `%s'", sp);
455 		empty = 0;
456 	}
457 
458 	if (empty)
459 		usage();
460 
461 	return mask;
462 }
463 
464 /*
465  * Return the number of CPUs in the running system.
466  */
467 int
468 ncpu(void)
469 {
470 	int rv, mib[2];
471 	size_t varlen;
472 
473 	mib[0] = CTL_HW;
474 	mib[1] = HW_NCPU;
475 	varlen = sizeof(rv);
476 	if (sysctl(mib, 2, &rv, &varlen, NULL, (size_t)0) < 0)
477 		rv = 1;
478 
479 	return (rv);
480 }
481 
482 /*
483  * Call into the ELF parser and look up a symbol by name or by address.
484  */
485 void
486 findsym(findsym_t find, char *name, uintptr_t *start, uintptr_t *end, bool chg)
487 {
488 	uintptr_t tend, sa, ea;
489 	char *p;
490 	int rv;
491 
492 	if (!chg) {
493 		sa = *start;
494 		start = &sa;
495 		end = &ea;
496 	}
497 
498 	if (end == NULL)
499 		end = &tend;
500 
501 	if (find == LOCK_BYNAME) {
502 		if (isdigit((u_int)name[0])) {
503 			*start = (uintptr_t)strtoul(name, &p, 0);
504 			if (*p == '\0')
505 				return;
506 		}
507 	}
508 
509 	if (bin64)
510 		rv = findsym64(find, name, start, end);
511 	else
512 		rv = findsym32(find, name, start, end);
513 
514 	if (find == FUNC_BYNAME || find == LOCK_BYNAME) {
515 		if (rv == -1)
516 			errx(EXIT_FAILURE, "unable to find symbol `%s'", name);
517 		return;
518 	}
519 
520 	if (rv == -1)
521 		snprintf(name, NAME_SIZE, "%016lx", (long)*start);
522 }
523 
524 /*
525  * Fork off the child process and wait for it to complete.  We trap SIGINT
526  * so that the caller can use Ctrl-C to stop tracing early and still get
527  * useful results.
528  */
529 void
530 spawn(int argc, char **argv)
531 {
532 	pid_t pid;
533 
534 	switch (pid = fork()) {
535 	case 0:
536 		close(lsfd);
537 		if (execvp(argv[0], argv) == -1)
538 			err(EXIT_FAILURE, "cannot exec");
539 		break;
540 	case -1:
541 		err(EXIT_FAILURE, "cannot fork to exec");
542 		break;
543 	default:
544 		signal(SIGINT, nullsig);
545 		wait(NULL);
546 		signal(SIGINT, SIG_DFL);
547 		break;
548 	}
549 }
550 
551 /*
552  * Allocate a new block of lock_t structures.
553  */
554 lock_t *
555 morelocks(void)
556 {
557 	const static int batch = 32;
558 	lock_t *l, *lp, *max;
559 
560 	l = (lock_t *)malloc(sizeof(*l) * batch);
561 
562 	for (lp = l, max = l + batch; lp < max; lp++)
563 		TAILQ_INSERT_TAIL(&freelist, lp, chain);
564 
565 	return l;
566 }
567 
568 /*
569  * Collapse addresses from unique objects.
570  */
571 void
572 collapse(bool func, bool lock)
573 {
574 	lsbuf_t *lb, *max;
575 
576 	for (lb = bufs, max = bufs + nbufs; lb < max; lb++) {
577 		if (func && lb->lb_callsite != 0) {
578 			findsym(FUNC_BYADDR, NULL, &lb->lb_callsite, NULL,
579 			    true);
580 		}
581 		if (lock && lb->lb_lock != 0) {
582 			findsym(LOCK_BYADDR, NULL, &lb->lb_lock, NULL,
583 			    true);
584 		}
585 	}
586 }
587 
588 /*
589  * From the kernel supplied data, construct two dimensional lists of locks
590  * and event buffers, indexed by lock type and sorted by event type.
591  */
592 void
593 makelists(int mask, int event)
594 {
595 	lsbuf_t *lb, *lb2, *max;
596 	lock_t *l, *l2;
597 	int type;
598 
599 	/*
600 	 * Recycle lock_t structures from the last run.
601 	 */
602 	while ((l = TAILQ_FIRST(&locklist)) != NULL) {
603 		TAILQ_REMOVE(&locklist, l, chain);
604 		TAILQ_INSERT_HEAD(&freelist, l, chain);
605 	}
606 
607 	type = mask & LB_LOCK_MASK;
608 
609 	for (lb = bufs, max = bufs + nbufs; lb < max; lb++) {
610 		if ((lb->lb_flags & LB_LOCK_MASK) != type ||
611 		    lb->lb_counts[event] == 0)
612 			continue;
613 
614 		/*
615 		 * Look for a record descibing this lock, and allocate a
616 		 * new one if needed.
617 		 */
618 		TAILQ_FOREACH(l, &sortlist, chain) {
619 			if (l->lock == lb->lb_lock)
620 				break;
621 		}
622 		if (l == NULL) {
623 			if ((l = TAILQ_FIRST(&freelist)) == NULL)
624 				l = morelocks();
625 			TAILQ_REMOVE(&freelist, l, chain);
626 			l->flags = lb->lb_flags;
627 			l->lock = lb->lb_lock;
628 			l->nbufs = 0;
629 			l->name[0] = '\0';
630 			l->count = 0;
631 			l->time = 0;
632 			TAILQ_INIT(&l->tosort);
633 			TAILQ_INIT(&l->bufs);
634 			TAILQ_INSERT_TAIL(&sortlist, l, chain);
635 		}
636 
637 		/*
638 		 * Scale the time values per buffer and summarise
639 		 * times+counts per lock.
640 		 */
641 		lb->lb_times[event] *= cpuscale[lb->lb_cpu];
642 		l->count += lb->lb_counts[event];
643 		l->time += lb->lb_times[event];
644 
645 		/*
646 		 * Merge same lock+callsite pairs from multiple CPUs
647 		 * together.
648 		 */
649 		TAILQ_FOREACH(lb2, &l->tosort, lb_chain.tailq) {
650 			if (lb->lb_callsite == lb2->lb_callsite)
651 				break;
652 		}
653 		if (lb2 != NULL) {
654 			lb2->lb_counts[event] += lb->lb_counts[event];
655 			lb2->lb_times[event] += lb->lb_times[event];
656 		} else {
657 			TAILQ_INSERT_HEAD(&l->tosort, lb, lb_chain.tailq);
658 			l->nbufs++;
659 		}
660 	}
661 
662 	/*
663 	 * Now sort the lists.
664 	 */
665 	while ((l = TAILQ_FIRST(&sortlist)) != NULL) {
666 		TAILQ_REMOVE(&sortlist, l, chain);
667 
668 		/*
669 		 * Sort the buffers into the per-lock list.
670 		 */
671 		while ((lb = TAILQ_FIRST(&l->tosort)) != NULL) {
672 			TAILQ_REMOVE(&l->tosort, lb, lb_chain.tailq);
673 
674 			lb2 = TAILQ_FIRST(&l->bufs);
675 			while (lb2 != NULL) {
676 				if (cflag) {
677 					if (lb->lb_counts[event] >
678 					    lb2->lb_counts[event])
679 						break;
680 				} else if (lb->lb_times[event] >
681 				    lb2->lb_times[event])
682 					break;
683 				lb2 = TAILQ_NEXT(lb2, lb_chain.tailq);
684 			}
685 			if (lb2 == NULL)
686 				TAILQ_INSERT_TAIL(&l->bufs, lb,
687 				    lb_chain.tailq);
688 			else
689 				TAILQ_INSERT_BEFORE(lb2, lb, lb_chain.tailq);
690 		}
691 
692 		/*
693 		 * Sort this lock into the per-type list, based on the
694 		 * totals per lock.
695 		 */
696 		l2 = TAILQ_FIRST(&locklist);
697 		while (l2 != NULL) {
698 			if (cflag) {
699 				if (l->count > l2->count)
700 					break;
701 			} else if (l->time > l2->time)
702 				break;
703 			l2 = TAILQ_NEXT(l2, chain);
704 		}
705 		if (l2 == NULL)
706 			TAILQ_INSERT_TAIL(&locklist, l, chain);
707 		else
708 			TAILQ_INSERT_BEFORE(l2, l, chain);
709 	}
710 }
711 
712 /*
713  * Display a summary table for one lock type / event type pair.
714  */
715 void
716 display(int mask, const char *name)
717 {
718 	lock_t *l;
719 	lsbuf_t *lb;
720 	double pcscale, metric;
721 	char fname[NAME_SIZE];
722 	int event;
723 
724 	event = (mask & LB_EVENT_MASK) - 1;
725 	makelists(mask, event);
726 
727 	if (TAILQ_EMPTY(&locklist))
728 		return;
729 
730 	fprintf(outfp, "\n-- %s\n\n"
731 	    "Total%%  Count   Time/ms          Lock                       Caller\n"
732 	    "------ ------- --------- ---------------------- ------------------------------\n",
733 	    name);
734 
735 	/*
736 	 * Sum up all events for this type of lock + event.
737 	 */
738 	pcscale = 0;
739 	TAILQ_FOREACH(l, &locklist, chain) {
740 		if (cflag)
741 			pcscale += l->count;
742 		else
743 			pcscale += l->time;
744 		displayed++;
745 	}
746 	if (pcscale == 0)
747 		pcscale = 100;
748 	else
749 		pcscale = (100.0 / pcscale);
750 
751 	/*
752 	 * For each lock, print a summary total, followed by a breakdown by
753 	 * caller.
754 	 */
755 	TAILQ_FOREACH(l, &locklist, chain) {
756 		if (cflag)
757 			metric = l->count;
758 		else
759 			metric = l->time;
760 		metric *= pcscale;
761 
762 		if (l->name[0] == '\0')
763 			findsym(LOCK_BYADDR, l->name, &l->lock, NULL, false);
764 
765 		if (lflag || l->nbufs > 1)
766 			fprintf(outfp, "%6.2f %7d %9.2f %-22s <all>\n",
767 			    metric, (int)(l->count * cscale),
768 			    l->time * tscale, l->name);
769 
770 		if (lflag)
771 			continue;
772 
773 		TAILQ_FOREACH(lb, &l->bufs, lb_chain.tailq) {
774 			if (cflag)
775 				metric = lb->lb_counts[event];
776 			else
777 				metric = lb->lb_times[event];
778 			metric *= pcscale;
779 
780 			findsym(FUNC_BYADDR, fname, &lb->lb_callsite, NULL,
781 			    false);
782 			fprintf(outfp, "%6.2f %7d %9.2f %-22s %s\n",
783 			    metric, (int)(lb->lb_counts[event] * cscale),
784 			    lb->lb_times[event] * tscale, l->name, fname);
785 		}
786 	}
787 }
788