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