1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2005-2007, Joseph Koshy 5 * Copyright (c) 2007 The FreeBSD Foundation 6 * All rights reserved. 7 * 8 * Portions of this software were developed by A. Joseph Koshy under 9 * sponsorship from the FreeBSD Foundation and Google, Inc. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 * SUCH DAMAGE. 31 */ 32 33 /* 34 * Transform a hwpmc(4) log into human readable form, and into 35 * gprof(1) compatible profiles. 36 */ 37 38 #include <sys/cdefs.h> 39 #include <sys/param.h> 40 #include <sys/endian.h> 41 #include <sys/gmon.h> 42 #include <sys/imgact_aout.h> 43 #include <sys/imgact_elf.h> 44 #include <sys/mman.h> 45 #include <sys/pmc.h> 46 #include <sys/queue.h> 47 #include <sys/socket.h> 48 #include <sys/stat.h> 49 #include <sys/wait.h> 50 51 #include <netinet/in.h> 52 53 #include <assert.h> 54 #include <curses.h> 55 #include <err.h> 56 #include <errno.h> 57 #include <fcntl.h> 58 #include <gelf.h> 59 #include <inttypes.h> 60 #include <libgen.h> 61 #include <limits.h> 62 #include <netdb.h> 63 #include <pmc.h> 64 #include <pmclog.h> 65 #include <sysexits.h> 66 #include <stdint.h> 67 #include <stdio.h> 68 #include <stdlib.h> 69 #include <string.h> 70 #include <unistd.h> 71 72 #include "pmcstat.h" 73 #include "pmcstat_log.h" 74 #include "pmcstat_top.h" 75 #include "pmcpl_callgraph.h" 76 77 #define min(A,B) ((A) < (B) ? (A) : (B)) 78 #define max(A,B) ((A) > (B) ? (A) : (B)) 79 80 /* Get the sample value in percent related to nsamples. */ 81 #define PMCPL_CG_COUNTP(a) \ 82 ((a)->pcg_count * 100.0 / nsamples) 83 84 /* 85 * The toplevel CG nodes (i.e., with rank == 0) are placed in a hash table. 86 */ 87 88 struct pmcstat_cgnode_hash_list pmcstat_cgnode_hash[PMCSTAT_NHASH]; 89 int pmcstat_cgnode_hash_count; 90 91 static pmcstat_interned_string pmcstat_previous_filename_printed; 92 93 static struct pmcstat_cgnode * 94 pmcstat_cgnode_allocate(struct pmcstat_image *image, uintfptr_t pc) 95 { 96 struct pmcstat_cgnode *cg; 97 98 if ((cg = malloc(sizeof(*cg))) == NULL) 99 err(EX_OSERR, "ERROR: Cannot allocate callgraph node"); 100 101 cg->pcg_image = image; 102 cg->pcg_func = pc; 103 104 cg->pcg_count = 0; 105 cg->pcg_nchildren = 0; 106 LIST_INIT(&cg->pcg_children); 107 108 return (cg); 109 } 110 111 /* 112 * Free a node and its children. 113 */ 114 static void 115 pmcstat_cgnode_free(struct pmcstat_cgnode *cg) 116 { 117 struct pmcstat_cgnode *cgc, *cgtmp; 118 119 LIST_FOREACH_SAFE(cgc, &cg->pcg_children, pcg_sibling, cgtmp) 120 pmcstat_cgnode_free(cgc); 121 free(cg); 122 } 123 124 /* 125 * Look for a callgraph node associated with pmc `pmcid' in the global 126 * hash table that corresponds to the given `pc' value in the process 127 * `pp'. 128 */ 129 static struct pmcstat_cgnode * 130 pmcstat_cgnode_hash_lookup_pc(struct pmcstat_process *pp, pmc_id_t pmcid, 131 uintfptr_t pc, int usermode) 132 { 133 struct pmcstat_pcmap *ppm; 134 struct pmcstat_symbol *sym; 135 struct pmcstat_image *image; 136 struct pmcstat_cgnode *cg; 137 struct pmcstat_cgnode_hash *h; 138 uintfptr_t loadaddress; 139 unsigned int i, hash; 140 141 ppm = pmcstat_process_find_map(usermode ? pp : pmcstat_kernproc, pc); 142 if (ppm == NULL) 143 return (NULL); 144 145 image = ppm->ppm_image; 146 147 loadaddress = ppm->ppm_lowpc + image->pi_vaddr - image->pi_start; 148 pc -= loadaddress; /* Convert to an offset in the image. */ 149 150 /* 151 * Try determine the function at this offset. If we can't 152 * find a function round leave the `pc' value alone. 153 */ 154 if (!(args.pa_flags & (FLAG_SKIP_TOP_FN_RES | FLAG_SHOW_OFFSET))) { 155 if ((sym = pmcstat_symbol_search(image, pc)) != NULL) 156 pc = sym->ps_start; 157 else 158 pmcstat_stats.ps_samples_unknown_function++; 159 } 160 161 for (hash = i = 0; i < sizeof(uintfptr_t); i++) 162 hash += (pc >> i) & 0xFF; 163 164 hash &= PMCSTAT_HASH_MASK; 165 166 cg = NULL; 167 LIST_FOREACH(h, &pmcstat_cgnode_hash[hash], pch_next) 168 { 169 if (h->pch_pmcid != pmcid) 170 continue; 171 172 cg = h->pch_cgnode; 173 174 assert(cg != NULL); 175 176 if (cg->pcg_image == image && cg->pcg_func == pc) 177 return (cg); 178 } 179 180 /* 181 * We haven't seen this (pmcid, pc) tuple yet, so allocate a 182 * new callgraph node and a new hash table entry for it. 183 */ 184 cg = pmcstat_cgnode_allocate(image, pc); 185 if ((h = malloc(sizeof(*h))) == NULL) 186 err(EX_OSERR, "ERROR: Could not allocate callgraph node"); 187 188 h->pch_pmcid = pmcid; 189 h->pch_cgnode = cg; 190 LIST_INSERT_HEAD(&pmcstat_cgnode_hash[hash], h, pch_next); 191 192 pmcstat_cgnode_hash_count++; 193 194 return (cg); 195 } 196 197 /* 198 * Compare two callgraph nodes for sorting. 199 */ 200 static int 201 pmcstat_cgnode_compare(const void *a, const void *b) 202 { 203 const struct pmcstat_cgnode *const *pcg1, *const *pcg2, *cg1, *cg2; 204 205 pcg1 = (const struct pmcstat_cgnode *const *) a; 206 cg1 = *pcg1; 207 pcg2 = (const struct pmcstat_cgnode *const *) b; 208 cg2 = *pcg2; 209 210 /* Sort in reverse order */ 211 if (cg1->pcg_count < cg2->pcg_count) 212 return (1); 213 if (cg1->pcg_count > cg2->pcg_count) 214 return (-1); 215 return (0); 216 } 217 218 /* 219 * Find (allocating if a needed) a callgraph node in the given 220 * parent with the same (image, pcoffset) pair. 221 */ 222 223 static struct pmcstat_cgnode * 224 pmcstat_cgnode_find(struct pmcstat_cgnode *parent, struct pmcstat_image *image, 225 uintfptr_t pcoffset) 226 { 227 struct pmcstat_cgnode *child; 228 229 LIST_FOREACH(child, &parent->pcg_children, pcg_sibling) { 230 if (child->pcg_image == image && 231 child->pcg_func == pcoffset) 232 return (child); 233 } 234 235 /* 236 * Allocate a new structure. 237 */ 238 239 child = pmcstat_cgnode_allocate(image, pcoffset); 240 241 /* 242 * Link it into the parent. 243 */ 244 LIST_INSERT_HEAD(&parent->pcg_children, child, pcg_sibling); 245 parent->pcg_nchildren++; 246 247 return (child); 248 } 249 250 /* 251 * Print one callgraph node. The output format is: 252 * 253 * indentation %(parent's samples) #nsamples function@object 254 */ 255 static void 256 pmcstat_cgnode_print(struct pmcstat_cgnode *cg, int depth, uint32_t total) 257 { 258 uint32_t n; 259 const char *space; 260 struct pmcstat_symbol *sym; 261 struct pmcstat_cgnode **sortbuffer, **cgn, *pcg; 262 263 space = " "; 264 265 if (depth > 0) 266 (void) fprintf(args.pa_graphfile, "%*s", depth, space); 267 268 if (cg->pcg_count == total) 269 (void) fprintf(args.pa_graphfile, "100.0%% "); 270 else 271 (void) fprintf(args.pa_graphfile, "%05.2f%% ", 272 100.0 * cg->pcg_count / total); 273 274 n = fprintf(args.pa_graphfile, " [%u] ", cg->pcg_count); 275 276 /* #samples is a 12 character wide field. */ 277 if (n < 12) 278 (void) fprintf(args.pa_graphfile, "%*s", 12 - n, space); 279 280 if (depth > 0) 281 (void) fprintf(args.pa_graphfile, "%*s", depth, space); 282 283 sym = pmcstat_symbol_search(cg->pcg_image, cg->pcg_func); 284 if (sym) 285 (void) fprintf(args.pa_graphfile, "%s", 286 pmcstat_string_unintern(sym->ps_name)); 287 else 288 (void) fprintf(args.pa_graphfile, "%p", 289 (void *) (cg->pcg_image->pi_vaddr + cg->pcg_func)); 290 291 if (pmcstat_previous_filename_printed != 292 cg->pcg_image->pi_fullpath) { 293 pmcstat_previous_filename_printed = cg->pcg_image->pi_fullpath; 294 (void) fprintf(args.pa_graphfile, " @ %s\n", 295 pmcstat_string_unintern( 296 pmcstat_previous_filename_printed)); 297 } else 298 (void) fprintf(args.pa_graphfile, "\n"); 299 300 if (cg->pcg_nchildren == 0) 301 return; 302 303 if ((sortbuffer = (struct pmcstat_cgnode **) 304 malloc(sizeof(struct pmcstat_cgnode *) * 305 cg->pcg_nchildren)) == NULL) 306 err(EX_OSERR, "ERROR: Cannot print callgraph"); 307 cgn = sortbuffer; 308 309 LIST_FOREACH(pcg, &cg->pcg_children, pcg_sibling) 310 *cgn++ = pcg; 311 312 assert(cgn - sortbuffer == (int) cg->pcg_nchildren); 313 314 qsort(sortbuffer, cg->pcg_nchildren, sizeof(struct pmcstat_cgnode *), 315 pmcstat_cgnode_compare); 316 317 for (cgn = sortbuffer, n = 0; n < cg->pcg_nchildren; n++, cgn++) 318 pmcstat_cgnode_print(*cgn, depth+1, cg->pcg_count); 319 320 free(sortbuffer); 321 } 322 323 /* 324 * Record a callchain. 325 */ 326 327 void 328 pmcpl_cg_process(struct pmcstat_process *pp, struct pmcstat_pmcrecord *pmcr, 329 uint32_t nsamples, uintfptr_t *cc, int usermode, uint32_t cpu) 330 { 331 uintfptr_t pc, loadaddress; 332 uint32_t n; 333 struct pmcstat_image *image; 334 struct pmcstat_pcmap *ppm; 335 struct pmcstat_symbol *sym; 336 struct pmcstat_cgnode *parent, *child; 337 struct pmcstat_process *km; 338 pmc_id_t pmcid; 339 340 (void) cpu; 341 342 /* 343 * Find the callgraph node recorded in the global hash table 344 * for this (pmcid, pc). 345 */ 346 347 pc = cc[0]; 348 pmcid = pmcr->pr_pmcid; 349 child = parent = pmcstat_cgnode_hash_lookup_pc(pp, pmcid, pc, usermode); 350 if (parent == NULL) { 351 pmcstat_stats.ps_callchain_dubious_frames++; 352 pmcr->pr_dubious_frames++; 353 return; 354 } 355 356 parent->pcg_count++; 357 358 /* 359 * For each return address in the call chain record, subject 360 * to the maximum depth desired. 361 * - Find the image associated with the sample. Stop if there 362 * there is no valid image at that address. 363 * - Find the function that overlaps the return address. 364 * - If found: use the start address of the function. 365 * If not found (say an object's symbol table is not present or 366 * is incomplete), round down to th gprof bucket granularity. 367 * - Convert return virtual address to an offset in the image. 368 * - Look for a child with the same {offset,image} tuple, 369 * inserting one if needed. 370 * - Increment the count of occurrences of the child. 371 */ 372 km = pmcstat_kernproc; 373 374 for (n = 1; n < (uint32_t) args.pa_graphdepth && n < nsamples; n++, 375 parent = child) { 376 pc = cc[n]; 377 378 ppm = pmcstat_process_find_map(usermode ? pp : km, pc); 379 if (ppm == NULL) { 380 /* Detect full frame capture (kernel + user). */ 381 if (!usermode) { 382 ppm = pmcstat_process_find_map(pp, pc); 383 if (ppm != NULL) 384 km = pp; 385 } 386 } 387 if (ppm == NULL) 388 continue; 389 390 image = ppm->ppm_image; 391 loadaddress = ppm->ppm_lowpc + image->pi_vaddr - 392 image->pi_start; 393 pc -= loadaddress; 394 395 if ((sym = pmcstat_symbol_search(image, pc)) != NULL) 396 pc = sym->ps_start; 397 398 child = pmcstat_cgnode_find(parent, image, pc); 399 child->pcg_count++; 400 } 401 } 402 403 /* 404 * Printing a callgraph for a PMC. 405 */ 406 static void 407 pmcstat_callgraph_print_for_pmcid(struct pmcstat_pmcrecord *pmcr) 408 { 409 int n, nentries; 410 uint32_t nsamples; 411 pmc_id_t pmcid; 412 struct pmcstat_cgnode **sortbuffer, **cgn; 413 struct pmcstat_cgnode_hash *pch; 414 415 /* 416 * We pull out all callgraph nodes in the top-level hash table 417 * with a matching PMC id. We then sort these based on the 418 * frequency of occurrence. Each callgraph node is then 419 * printed. 420 */ 421 422 nsamples = 0; 423 pmcid = pmcr->pr_pmcid; 424 if ((sortbuffer = (struct pmcstat_cgnode **) 425 malloc(sizeof(struct pmcstat_cgnode *) * 426 pmcstat_cgnode_hash_count)) == NULL) 427 err(EX_OSERR, "ERROR: Cannot sort callgraph"); 428 cgn = sortbuffer; 429 430 for (n = 0; n < PMCSTAT_NHASH; n++) 431 LIST_FOREACH(pch, &pmcstat_cgnode_hash[n], pch_next) 432 if (pch->pch_pmcid == pmcid) { 433 nsamples += pch->pch_cgnode->pcg_count; 434 *cgn++ = pch->pch_cgnode; 435 } 436 437 nentries = cgn - sortbuffer; 438 assert(nentries <= pmcstat_cgnode_hash_count); 439 440 if (nentries == 0) { 441 free(sortbuffer); 442 return; 443 } 444 445 qsort(sortbuffer, nentries, sizeof(struct pmcstat_cgnode *), 446 pmcstat_cgnode_compare); 447 448 (void) fprintf(args.pa_graphfile, 449 "@ %s [%u samples]\n\n", 450 pmcstat_string_unintern(pmcr->pr_pmcname), 451 nsamples); 452 453 for (cgn = sortbuffer, n = 0; n < nentries; n++, cgn++) { 454 pmcstat_previous_filename_printed = NULL; 455 pmcstat_cgnode_print(*cgn, 0, nsamples); 456 (void) fprintf(args.pa_graphfile, "\n"); 457 } 458 459 free(sortbuffer); 460 } 461 462 /* 463 * Print out callgraphs. 464 */ 465 466 static void 467 pmcstat_callgraph_print(void) 468 { 469 struct pmcstat_pmcrecord *pmcr; 470 471 LIST_FOREACH(pmcr, &pmcstat_pmcs, pr_next) 472 pmcstat_callgraph_print_for_pmcid(pmcr); 473 } 474 475 static void 476 pmcstat_cgnode_topprint(struct pmcstat_cgnode *cg, 477 int depth __unused, uint32_t nsamples) 478 { 479 int v_attrs, vs_len, ns_len, width, len, n, nchildren; 480 float v; 481 char ns[30], vs[10]; 482 struct pmcstat_symbol *sym; 483 struct pmcstat_cgnode **sortbuffer, **cgn, *pcg; 484 485 /* Format value. */ 486 v = PMCPL_CG_COUNTP(cg); 487 snprintf(vs, sizeof(vs), "%.1f", v); 488 v_attrs = PMCSTAT_ATTRPERCENT(v); 489 490 /* Format name. */ 491 sym = pmcstat_symbol_search(cg->pcg_image, cg->pcg_func); 492 if (sym == NULL) { 493 snprintf(ns, sizeof(ns), "%p", 494 (void *)(cg->pcg_image->pi_vaddr + cg->pcg_func)); 495 } else { 496 switch (args.pa_flags & (FLAG_SKIP_TOP_FN_RES | FLAG_SHOW_OFFSET)) { 497 case FLAG_SKIP_TOP_FN_RES | FLAG_SHOW_OFFSET: 498 case FLAG_SKIP_TOP_FN_RES: 499 snprintf(ns, sizeof(ns), "%p", 500 (void *)(cg->pcg_image->pi_vaddr + cg->pcg_func)); 501 break; 502 case FLAG_SHOW_OFFSET: 503 snprintf(ns, sizeof(ns), "%s+%#0" PRIx64, 504 pmcstat_string_unintern(sym->ps_name), 505 cg->pcg_func - sym->ps_start); 506 break; 507 default: 508 snprintf(ns, sizeof(ns), "%s", 509 pmcstat_string_unintern(sym->ps_name)); 510 break; 511 } 512 } 513 514 PMCSTAT_ATTRON(v_attrs); 515 PMCSTAT_PRINTW("%5.5s", vs); 516 PMCSTAT_ATTROFF(v_attrs); 517 PMCSTAT_PRINTW(" %-10.10s %-30.30s", 518 pmcstat_string_unintern(cg->pcg_image->pi_name), 519 ns); 520 521 nchildren = cg->pcg_nchildren; 522 if (nchildren == 0) { 523 PMCSTAT_PRINTW("\n"); 524 return; 525 } 526 527 width = pmcstat_displaywidth - 40; 528 529 if ((sortbuffer = (struct pmcstat_cgnode **) 530 malloc(sizeof(struct pmcstat_cgnode *) * 531 nchildren)) == NULL) 532 err(EX_OSERR, "ERROR: Cannot print callgraph"); 533 cgn = sortbuffer; 534 535 LIST_FOREACH(pcg, &cg->pcg_children, pcg_sibling) 536 *cgn++ = pcg; 537 538 assert(cgn - sortbuffer == (int)nchildren); 539 540 qsort(sortbuffer, nchildren, sizeof(struct pmcstat_cgnode *), 541 pmcstat_cgnode_compare); 542 543 /* Count how many callers. */ 544 for (cgn = sortbuffer, n = 0; n < nchildren; n++, cgn++) { 545 pcg = *cgn; 546 547 v = PMCPL_CG_COUNTP(pcg); 548 if (v < pmcstat_threshold) 549 break; 550 } 551 nchildren = n; 552 553 for (cgn = sortbuffer, n = 0; n < nchildren; n++, cgn++) { 554 pcg = *cgn; 555 556 /* Format value. */ 557 if (nchildren > 1) { 558 v = PMCPL_CG_COUNTP(pcg); 559 vs_len = snprintf(vs, sizeof(vs), ":%.1f", v); 560 v_attrs = PMCSTAT_ATTRPERCENT(v); 561 } else 562 vs_len = 0; 563 564 /* Format name. */ 565 sym = pmcstat_symbol_search(pcg->pcg_image, pcg->pcg_func); 566 if (sym != NULL) { 567 ns_len = snprintf(ns, sizeof(ns), "%s", 568 pmcstat_string_unintern(sym->ps_name)); 569 } else 570 ns_len = snprintf(ns, sizeof(ns), "%p", 571 (void *)pcg->pcg_func); 572 573 len = ns_len + vs_len + 1; 574 if (width - len < 0) { 575 PMCSTAT_PRINTW(" ..."); 576 break; 577 } 578 width -= len; 579 580 PMCSTAT_PRINTW(" %s", ns); 581 if (nchildren > 1) { 582 PMCSTAT_ATTRON(v_attrs); 583 PMCSTAT_PRINTW("%s", vs); 584 PMCSTAT_ATTROFF(v_attrs); 585 } 586 } 587 PMCSTAT_PRINTW("\n"); 588 free(sortbuffer); 589 } 590 591 /* 592 * Top mode display. 593 */ 594 595 void 596 pmcpl_cg_topdisplay(void) 597 { 598 int n, nentries; 599 uint32_t nsamples; 600 struct pmcstat_cgnode **sortbuffer, **cgn; 601 struct pmcstat_cgnode_hash *pch; 602 struct pmcstat_pmcrecord *pmcr; 603 604 pmcr = pmcstat_pmcindex_to_pmcr(pmcstat_pmcinfilter); 605 if (!pmcr) 606 err(EX_SOFTWARE, "ERROR: invalid pmcindex"); 607 608 /* 609 * We pull out all callgraph nodes in the top-level hash table 610 * with a matching PMC index. We then sort these based on the 611 * frequency of occurrence. Each callgraph node is then 612 * printed. 613 */ 614 615 nsamples = 0; 616 617 if ((sortbuffer = (struct pmcstat_cgnode **) 618 malloc(sizeof(struct pmcstat_cgnode *) * 619 pmcstat_cgnode_hash_count)) == NULL) 620 err(EX_OSERR, "ERROR: Cannot sort callgraph"); 621 cgn = sortbuffer; 622 623 for (n = 0; n < PMCSTAT_NHASH; n++) 624 LIST_FOREACH(pch, &pmcstat_cgnode_hash[n], pch_next) 625 if (pmcr == NULL || pch->pch_pmcid == pmcr->pr_pmcid) { 626 nsamples += pch->pch_cgnode->pcg_count; 627 *cgn++ = pch->pch_cgnode; 628 } 629 630 nentries = cgn - sortbuffer; 631 assert(nentries <= pmcstat_cgnode_hash_count); 632 633 if (nentries == 0) { 634 free(sortbuffer); 635 return; 636 } 637 638 qsort(sortbuffer, nentries, sizeof(struct pmcstat_cgnode *), 639 pmcstat_cgnode_compare); 640 641 PMCSTAT_PRINTW("%5.5s %-10.10s %-30.30s %s\n", 642 "%SAMP", "IMAGE", "FUNCTION", "CALLERS"); 643 644 nentries = min(pmcstat_displayheight - 2, nentries); 645 646 for (cgn = sortbuffer, n = 0; n < nentries; n++, cgn++) { 647 if (PMCPL_CG_COUNTP(*cgn) < pmcstat_threshold) 648 break; 649 pmcstat_cgnode_topprint(*cgn, 0, nsamples); 650 } 651 652 free(sortbuffer); 653 } 654 655 /* 656 * Handle top mode keypress. 657 */ 658 659 int 660 pmcpl_cg_topkeypress(int c, void *arg) 661 { 662 WINDOW *w; 663 664 w = (WINDOW *)arg; 665 666 (void) c; (void) w; 667 668 return 0; 669 } 670 671 int 672 pmcpl_cg_init(void) 673 { 674 int i; 675 676 pmcstat_cgnode_hash_count = 0; 677 pmcstat_previous_filename_printed = NULL; 678 679 for (i = 0; i < PMCSTAT_NHASH; i++) { 680 LIST_INIT(&pmcstat_cgnode_hash[i]); 681 } 682 683 return (0); 684 } 685 686 void 687 pmcpl_cg_shutdown(FILE *mf) 688 { 689 int i; 690 struct pmcstat_cgnode_hash *pch, *pchtmp; 691 692 (void) mf; 693 694 if (args.pa_flags & FLAG_DO_CALLGRAPHS) 695 pmcstat_callgraph_print(); 696 697 /* 698 * Free memory. 699 */ 700 for (i = 0; i < PMCSTAT_NHASH; i++) { 701 LIST_FOREACH_SAFE(pch, &pmcstat_cgnode_hash[i], pch_next, 702 pchtmp) { 703 pmcstat_cgnode_free(pch->pch_cgnode); 704 LIST_REMOVE(pch, pch_next); 705 free(pch); 706 } 707 } 708 } 709 710