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