1 /* $NetBSD: main.c,v 1.11 2008/01/26 14:29:31 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 * 3. All advertising materials mentioning features or use of this software 19 * must display the following acknowledgement: 20 * This product includes software developed by the NetBSD 21 * Foundation, Inc. and its contributors. 22 * 4. Neither the name of The NetBSD Foundation nor the names of its 23 * contributors may be used to endorse or promote products derived 24 * from this software without specific prior written permission. 25 * 26 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 27 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 28 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 29 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 30 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 33 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 36 * POSSIBILITY OF SUCH DAMAGE. 37 */ 38 39 /* 40 * TODO: 41 * 42 * - Tracking of times for sleep locks is broken. 43 * - Need better analysis and tracking of events. 44 * - Shouldn't have to parse the namelist here. We should use something like 45 * FreeBSD's libelf. 46 * - The way the namelist is searched sucks, is it worth doing something 47 * better? 48 */ 49 50 #include <sys/cdefs.h> 51 #ifndef lint 52 __RCSID("$NetBSD: main.c,v 1.11 2008/01/26 14:29:31 ad Exp $"); 53 #endif /* not lint */ 54 55 #include <sys/types.h> 56 #include <sys/param.h> 57 #include <sys/time.h> 58 #include <sys/fcntl.h> 59 #include <sys/ioctl.h> 60 #include <sys/wait.h> 61 #include <sys/signal.h> 62 #include <sys/sysctl.h> 63 64 #include <dev/lockstat.h> 65 66 #include <stdio.h> 67 #include <stdlib.h> 68 #include <string.h> 69 #include <limits.h> 70 #include <unistd.h> 71 #include <err.h> 72 #include <paths.h> 73 #include <util.h> 74 #include <ctype.h> 75 #include <errno.h> 76 #include <stdbool.h> 77 78 #include "extern.h" 79 80 #define _PATH_DEV_LOCKSTAT "/dev/lockstat" 81 82 #define MILLI 1000.0 83 #define MICRO 1000000.0 84 #define NANO 1000000000.0 85 #define PICO 1000000000000.0 86 87 TAILQ_HEAD(lock_head, lockstruct); 88 typedef struct lock_head locklist_t; 89 TAILQ_HEAD(buf_head, lsbuf); 90 typedef struct buf_head buflist_t; 91 92 typedef struct lockstruct { 93 TAILQ_ENTRY(lockstruct) chain; 94 buflist_t bufs; 95 buflist_t tosort; 96 uintptr_t lock; 97 double time; 98 uint32_t count; 99 u_int flags; 100 u_int nbufs; 101 char name[NAME_SIZE]; 102 } lock_t; 103 104 typedef struct name { 105 const char *name; 106 int mask; 107 } name_t; 108 109 const name_t locknames[] = { 110 { "adaptive_mutex", LB_ADAPTIVE_MUTEX }, 111 { "spin_mutex", LB_SPIN_MUTEX }, 112 { "rwlock", LB_RWLOCK }, 113 { "kernel_lock", LB_KERNEL_LOCK }, 114 { NULL, 0 } 115 }; 116 117 const name_t eventnames[] = { 118 { "spin", LB_SPIN }, 119 { "sleep_exclusive", LB_SLEEP1 }, 120 { "sleep_shared", LB_SLEEP2 }, 121 { NULL, 0 }, 122 }; 123 124 const name_t alltypes[] = { 125 { "Adaptive mutex spin", LB_ADAPTIVE_MUTEX | LB_SPIN }, 126 { "Adaptive mutex sleep", LB_ADAPTIVE_MUTEX | LB_SLEEP1 }, 127 { "Spin mutex spin", LB_SPIN_MUTEX | LB_SPIN }, 128 { "RW lock sleep (writer)", LB_RWLOCK | LB_SLEEP1 }, 129 { "RW lock sleep (reader)", LB_RWLOCK | LB_SLEEP2 }, 130 { "Kernel lock spin", LB_KERNEL_LOCK | LB_SPIN }, 131 { NULL, 0 } 132 }; 133 134 locklist_t locklist; 135 locklist_t freelist; 136 locklist_t sortlist; 137 138 lsbuf_t *bufs; 139 lsdisable_t ld; 140 bool lflag; 141 bool fflag; 142 int nbufs; 143 bool cflag; 144 int lsfd; 145 int displayed; 146 int bin64; 147 double tscale; 148 double cscale; 149 double cpuscale[sizeof(ld.ld_freq) / sizeof(ld.ld_freq[0])]; 150 FILE *outfp; 151 152 void findsym(findsym_t, char *, uintptr_t *, uintptr_t *, bool); 153 void spawn(int, char **); 154 void display(int, const char *name); 155 void listnames(const name_t *); 156 void collapse(bool, bool); 157 int matchname(const name_t *, char *); 158 void makelists(int, int); 159 void nullsig(int); 160 void usage(void); 161 int ncpu(void); 162 lock_t *morelocks(void); 163 164 int 165 main(int argc, char **argv) 166 { 167 int eventtype, locktype, ch, nlfd, fd, i; 168 bool sflag, pflag, mflag, Mflag; 169 const char *nlistf, *outf; 170 char *lockname, *funcname; 171 const name_t *name; 172 lsenable_t le; 173 double ms; 174 char *p; 175 176 nlistf = NULL; 177 outf = NULL; 178 lockname = NULL; 179 funcname = NULL; 180 eventtype = -1; 181 locktype = -1; 182 nbufs = 0; 183 sflag = false; 184 pflag = false; 185 mflag = false; 186 Mflag = false; 187 188 while ((ch = getopt(argc, argv, "E:F:L:MN:T:b:ceflmo:pst")) != -1) 189 switch (ch) { 190 case 'E': 191 eventtype = matchname(eventnames, optarg); 192 break; 193 case 'F': 194 funcname = optarg; 195 break; 196 case 'L': 197 lockname = optarg; 198 break; 199 case 'N': 200 nlistf = optarg; 201 break; 202 case 'T': 203 locktype = matchname(locknames, optarg); 204 break; 205 case 'b': 206 nbufs = (int)strtol(optarg, &p, 0); 207 if (!isdigit((u_int)*optarg) || *p != '\0') 208 usage(); 209 break; 210 case 'c': 211 cflag = true; 212 break; 213 case 'e': 214 listnames(eventnames); 215 break; 216 case 'f': 217 fflag = true; 218 break; 219 case 'l': 220 lflag = true; 221 break; 222 case 'm': 223 mflag = true; 224 break; 225 case 'M': 226 Mflag = true; 227 break; 228 case 'o': 229 outf = optarg; 230 break; 231 case 'p': 232 pflag = true; 233 break; 234 case 's': 235 sflag = true; 236 break; 237 case 't': 238 listnames(locknames); 239 break; 240 default: 241 usage(); 242 } 243 argc -= optind; 244 argv += optind; 245 246 if (*argv == NULL) 247 usage(); 248 249 if (outf) { 250 fd = open(outf, O_WRONLY | O_CREAT | O_TRUNC, 0600); 251 if (fd == -1) 252 err(EXIT_FAILURE, "opening %s", outf); 253 outfp = fdopen(fd, "w"); 254 } else 255 outfp = stdout; 256 257 /* 258 * Find the name list for resolving symbol names, and load it into 259 * memory. 260 */ 261 if (nlistf == NULL) { 262 nlfd = open(_PATH_KSYMS, O_RDONLY); 263 nlistf = getbootfile(); 264 } else 265 nlfd = -1; 266 if (nlfd == -1) { 267 if ((nlfd = open(nlistf, O_RDONLY)) < 0) 268 err(EXIT_FAILURE, "cannot open " _PATH_KSYMS " or %s", 269 nlistf); 270 } 271 if (loadsym32(nlfd) != 0) { 272 if (loadsym64(nlfd) != 0) 273 errx(EXIT_FAILURE, "unable to load symbol table"); 274 bin64 = 1; 275 } 276 close(nlfd); 277 278 memset(&le, 0, sizeof(le)); 279 le.le_nbufs = nbufs; 280 281 /* 282 * Set up initial filtering. 283 */ 284 if (lockname != NULL) { 285 findsym(LOCK_BYNAME, lockname, &le.le_lockstart, 286 &le.le_lockend, true); 287 le.le_flags |= LE_ONE_LOCK; 288 } 289 if (!lflag) 290 le.le_flags |= LE_CALLSITE; 291 if (!fflag) 292 le.le_flags |= LE_LOCK; 293 if (funcname != NULL) { 294 if (lflag) 295 usage(); 296 findsym(FUNC_BYNAME, funcname, &le.le_csstart, &le.le_csend, true); 297 le.le_flags |= LE_ONE_CALLSITE; 298 } 299 le.le_mask = (eventtype & LB_EVENT_MASK) | (locktype & LB_LOCK_MASK); 300 301 /* 302 * Start tracing. 303 */ 304 if ((lsfd = open(_PATH_DEV_LOCKSTAT, O_RDONLY)) < 0) 305 err(EXIT_FAILURE, "cannot open " _PATH_DEV_LOCKSTAT); 306 if (ioctl(lsfd, IOC_LOCKSTAT_GVERSION, &ch) < 0) 307 err(EXIT_FAILURE, "ioctl"); 308 if (ch != LS_VERSION) 309 errx(EXIT_FAILURE, 310 "incompatible lockstat interface version (%d, kernel %d)", 311 LS_VERSION, ch); 312 if (ioctl(lsfd, IOC_LOCKSTAT_ENABLE, &le)) 313 err(EXIT_FAILURE, "cannot enable tracing"); 314 315 /* 316 * Execute the traced program. 317 */ 318 spawn(argc, argv); 319 320 /* 321 * Stop tracing, and read the trace buffers from the kernel. 322 */ 323 if (ioctl(lsfd, IOC_LOCKSTAT_DISABLE, &ld) == -1) { 324 if (errno == EOVERFLOW) { 325 warnx("overflowed available kernel trace buffers"); 326 exit(EXIT_FAILURE); 327 } 328 err(EXIT_FAILURE, "cannot disable tracing"); 329 } 330 if ((bufs = malloc(ld.ld_size)) == NULL) 331 err(EXIT_FAILURE, "cannot allocate memory for user buffers"); 332 if (read(lsfd, bufs, ld.ld_size) != ld.ld_size) 333 err(EXIT_FAILURE, "reading from " _PATH_DEV_LOCKSTAT); 334 if (close(lsfd)) 335 err(EXIT_FAILURE, "close(" _PATH_DEV_LOCKSTAT ")"); 336 337 /* 338 * Figure out how to scale the results. For internal use we convert 339 * all times from CPU frequency based to picoseconds, and values are 340 * eventually displayed in ms. 341 */ 342 for (i = 0; i < sizeof(ld.ld_freq) / sizeof(ld.ld_freq[0]); i++) 343 if (ld.ld_freq[i] != 0) 344 cpuscale[i] = PICO / ld.ld_freq[i]; 345 ms = ld.ld_time.tv_sec * MILLI + ld.ld_time.tv_nsec / MICRO; 346 if (pflag) 347 cscale = 1.0 / ncpu(); 348 else 349 cscale = 1.0; 350 cscale *= (sflag ? MILLI / ms : 1.0); 351 tscale = cscale / NANO; 352 nbufs = (int)(ld.ld_size / sizeof(lsbuf_t)); 353 354 TAILQ_INIT(&locklist); 355 TAILQ_INIT(&sortlist); 356 TAILQ_INIT(&freelist); 357 358 if ((mflag | Mflag) != 0) 359 collapse(mflag, Mflag); 360 361 /* 362 * Display the results. 363 */ 364 fprintf(outfp, "Elapsed time: %.2f seconds.", ms / MILLI); 365 if (sflag || pflag) { 366 fprintf(outfp, " Displaying "); 367 if (pflag) 368 fprintf(outfp, "per-CPU "); 369 if (sflag) 370 fprintf(outfp, "per-second "); 371 fprintf(outfp, "averages."); 372 } 373 putc('\n', outfp); 374 375 for (name = alltypes; name->name != NULL; name++) { 376 if (eventtype != -1 && 377 (name->mask & LB_EVENT_MASK) != eventtype) 378 continue; 379 if (locktype != -1 && 380 (name->mask & LB_LOCK_MASK) != locktype) 381 continue; 382 383 display(name->mask, name->name); 384 } 385 386 if (displayed == 0) 387 fprintf(outfp, "None of the selected events were recorded.\n"); 388 exit(EXIT_SUCCESS); 389 } 390 391 void 392 usage(void) 393 { 394 395 fprintf(stderr, 396 "%s: usage:\n" 397 "%s [options] <command>\n\n" 398 "-b nbuf\t\tset number of event buffers to allocate\n" 399 "-c\t\treport percentage of total events by count, not time\n" 400 "-E event\t\tdisplay only one type of event\n" 401 "-e\t\tlist event types\n" 402 "-F func\t\tlimit trace to one function\n" 403 "-f\t\ttrace only by function\n" 404 "-L lock\t\tlimit trace to one lock (name, or address)\n" 405 "-l\t\ttrace only by lock\n" 406 "-M\t\tmerge lock addresses within unique objects\n" 407 "-m\t\tmerge call sites within unique functions\n" 408 "-N nlist\tspecify name list file\n" 409 "-o file\t\tsend output to named file, not stdout\n" 410 "-p\t\tshow average count/time per CPU, not total\n" 411 "-s\t\tshow average count/time per second, not total\n" 412 "-T type\t\tdisplay only one type of lock\n" 413 "-t\t\tlist lock types\n", 414 getprogname(), getprogname()); 415 416 exit(EXIT_FAILURE); 417 } 418 419 void 420 nullsig(int junk) 421 { 422 423 (void)junk; 424 } 425 426 void 427 listnames(const name_t *name) 428 { 429 430 for (; name->name != NULL; name++) 431 printf("%s\n", name->name); 432 433 exit(EXIT_SUCCESS); 434 } 435 436 int 437 matchname(const name_t *name, char *string) 438 { 439 int empty, mask; 440 char *sp; 441 442 empty = 1; 443 mask = 0; 444 445 while ((sp = strsep(&string, ",")) != NULL) { 446 if (*sp == '\0') 447 usage(); 448 449 for (; name->name != NULL; name++) { 450 if (strcasecmp(name->name, sp) == 0) { 451 mask |= name->mask; 452 break; 453 } 454 } 455 if (name->name == NULL) 456 errx(EXIT_FAILURE, "unknown identifier `%s'", sp); 457 empty = 0; 458 } 459 460 if (empty) 461 usage(); 462 463 return mask; 464 } 465 466 /* 467 * Return the number of CPUs in the running system. 468 */ 469 int 470 ncpu(void) 471 { 472 int rv, mib[2]; 473 size_t varlen; 474 475 mib[0] = CTL_HW; 476 mib[1] = HW_NCPU; 477 varlen = sizeof(rv); 478 if (sysctl(mib, 2, &rv, &varlen, NULL, (size_t)0) < 0) 479 rv = 1; 480 481 return (rv); 482 } 483 484 /* 485 * Call into the ELF parser and look up a symbol by name or by address. 486 */ 487 void 488 findsym(findsym_t find, char *name, uintptr_t *start, uintptr_t *end, bool chg) 489 { 490 uintptr_t tend, sa, ea; 491 char *p; 492 int rv; 493 494 if (!chg) { 495 sa = *start; 496 start = &sa; 497 end = &ea; 498 } 499 500 if (end == NULL) 501 end = &tend; 502 503 if (find == LOCK_BYNAME) { 504 if (isdigit((u_int)name[0])) { 505 *start = (uintptr_t)strtoul(name, &p, 0); 506 if (*p == '\0') 507 return; 508 } 509 } 510 511 if (bin64) 512 rv = findsym64(find, name, start, end); 513 else 514 rv = findsym32(find, name, start, end); 515 516 if (find == FUNC_BYNAME || find == LOCK_BYNAME) { 517 if (rv == -1) 518 errx(EXIT_FAILURE, "unable to find symbol `%s'", name); 519 return; 520 } 521 522 if (rv == -1) 523 snprintf(name, NAME_SIZE, "%016lx", (long)*start); 524 } 525 526 /* 527 * Fork off the child process and wait for it to complete. We trap SIGINT 528 * so that the caller can use Ctrl-C to stop tracing early and still get 529 * useful results. 530 */ 531 void 532 spawn(int argc, char **argv) 533 { 534 pid_t pid; 535 536 switch (pid = fork()) { 537 case 0: 538 close(lsfd); 539 if (execvp(argv[0], argv) == -1) 540 err(EXIT_FAILURE, "cannot exec"); 541 break; 542 case -1: 543 err(EXIT_FAILURE, "cannot fork to exec"); 544 break; 545 default: 546 signal(SIGINT, nullsig); 547 wait(NULL); 548 signal(SIGINT, SIG_DFL); 549 break; 550 } 551 } 552 553 /* 554 * Allocate a new block of lock_t structures. 555 */ 556 lock_t * 557 morelocks(void) 558 { 559 const static int batch = 32; 560 lock_t *l, *lp, *max; 561 562 l = (lock_t *)malloc(sizeof(*l) * batch); 563 564 for (lp = l, max = l + batch; lp < max; lp++) 565 TAILQ_INSERT_TAIL(&freelist, lp, chain); 566 567 return l; 568 } 569 570 /* 571 * Collapse addresses from unique objects. 572 */ 573 void 574 collapse(bool func, bool lock) 575 { 576 lsbuf_t *lb, *max; 577 578 for (lb = bufs, max = bufs + nbufs; lb < max; lb++) { 579 if (func && lb->lb_callsite != 0) { 580 findsym(FUNC_BYADDR, NULL, &lb->lb_callsite, NULL, 581 true); 582 } 583 if (lock && lb->lb_lock != 0) { 584 findsym(LOCK_BYADDR, NULL, &lb->lb_lock, NULL, 585 true); 586 } 587 } 588 } 589 590 /* 591 * From the kernel supplied data, construct two dimensional lists of locks 592 * and event buffers, indexed by lock type and sorted by event type. 593 */ 594 void 595 makelists(int mask, int event) 596 { 597 lsbuf_t *lb, *lb2, *max; 598 lock_t *l, *l2; 599 int type; 600 601 /* 602 * Recycle lock_t structures from the last run. 603 */ 604 while ((l = TAILQ_FIRST(&locklist)) != NULL) { 605 TAILQ_REMOVE(&locklist, l, chain); 606 TAILQ_INSERT_HEAD(&freelist, l, chain); 607 } 608 609 type = mask & LB_LOCK_MASK; 610 611 for (lb = bufs, max = bufs + nbufs; lb < max; lb++) { 612 if ((lb->lb_flags & LB_LOCK_MASK) != type || 613 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 */ 717 void 718 display(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