1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2005-2007, Joseph Koshy 5 * Copyright (c) 2007 The FreeBSD Foundation 6 * Copyright (c) 2009, Fabien Thomas 7 * All rights reserved. 8 * 9 * Portions of this software were developed by A. Joseph Koshy under 10 * sponsorship from the FreeBSD Foundation and Google, Inc. 11 * 12 * Redistribution and use in source and binary forms, with or without 13 * modification, are permitted provided that the following conditions 14 * are met: 15 * 1. Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * 2. Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 */ 33 34 /* 35 * Transform a hwpmc(4) log into human readable form, and into 36 * gprof(1) compatible profiles. 37 */ 38 39 #include <sys/cdefs.h> 40 __FBSDID("$FreeBSD$"); 41 42 #include <sys/param.h> 43 #include <sys/endian.h> 44 #include <sys/gmon.h> 45 #include <sys/imgact_aout.h> 46 #include <sys/imgact_elf.h> 47 #include <sys/mman.h> 48 #include <sys/pmc.h> 49 #include <sys/queue.h> 50 #include <sys/socket.h> 51 #include <sys/stat.h> 52 #include <sys/wait.h> 53 54 #include <netinet/in.h> 55 56 #include <assert.h> 57 #include <curses.h> 58 #include <err.h> 59 #include <errno.h> 60 #include <fcntl.h> 61 #include <gelf.h> 62 #include <libgen.h> 63 #include <limits.h> 64 #include <netdb.h> 65 #include <pmc.h> 66 #include <pmclog.h> 67 #include <sysexits.h> 68 #include <stdint.h> 69 #include <stdio.h> 70 #include <stdlib.h> 71 #include <string.h> 72 #include <unistd.h> 73 74 #include "pmcstat.h" 75 #include "pmcstat_log.h" 76 #include "pmcpl_callgraph.h" 77 #include "pmcpl_gprof.h" 78 79 typedef uint64_t WIDEHISTCOUNTER; 80 81 #define min(A,B) ((A) < (B) ? (A) : (B)) 82 #define max(A,B) ((A) > (B) ? (A) : (B)) 83 84 #define WIDEHISTCOUNTER_MAX UINT64_MAX 85 #define HISTCOUNTER_MAX USHRT_MAX 86 #define WIDEHISTCOUNTER_GMONTYPE ((int) 64) 87 #define HISTCOUNTER_GMONTYPE ((int) 0) 88 static int hc_sz=0; 89 90 /* 91 * struct pmcstat_gmonfile tracks a given 'gmon.out' file. These 92 * files are mmap()'ed in as needed. 93 */ 94 95 struct pmcstat_gmonfile { 96 LIST_ENTRY(pmcstat_gmonfile) pgf_next; /* list of entries */ 97 int pgf_overflow; /* whether a count overflowed */ 98 pmc_id_t pgf_pmcid; /* id of the associated pmc */ 99 size_t pgf_nbuckets; /* #buckets in this gmon.out */ 100 unsigned int pgf_nsamples; /* #samples in this gmon.out */ 101 pmcstat_interned_string pgf_name; /* pathname of gmon.out file */ 102 size_t pgf_ndatabytes; /* number of bytes mapped */ 103 void *pgf_gmondata; /* pointer to mmap'ed data */ 104 FILE *pgf_file; /* used when writing gmon arcs */ 105 }; 106 107 /* 108 * Prototypes 109 */ 110 111 static void pmcstat_gmon_create_file(struct pmcstat_gmonfile *_pgf, 112 struct pmcstat_image *_image); 113 static pmcstat_interned_string pmcstat_gmon_create_name(const char *_sd, 114 struct pmcstat_image *_img, pmc_id_t _pmcid); 115 static void pmcstat_gmon_map_file(struct pmcstat_gmonfile *_pgf); 116 static void pmcstat_gmon_unmap_file(struct pmcstat_gmonfile *_pgf); 117 118 static struct pmcstat_gmonfile *pmcstat_image_find_gmonfile(struct 119 pmcstat_image *_i, pmc_id_t _id); 120 121 /* 122 * Create a gmon.out file and size it. 123 */ 124 125 static void 126 pmcstat_gmon_create_file(struct pmcstat_gmonfile *pgf, 127 struct pmcstat_image *image) 128 { 129 int fd; 130 size_t count; 131 struct gmonhdr gm; 132 const char *pathname; 133 char buffer[DEFAULT_BUFFER_SIZE]; 134 135 pathname = pmcstat_string_unintern(pgf->pgf_name); 136 if ((fd = open(pathname, O_RDWR|O_NOFOLLOW|O_CREAT, 137 S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) < 0) 138 err(EX_OSERR, "ERROR: Cannot open \"%s\"", pathname); 139 140 gm.lpc = image->pi_start; 141 gm.hpc = image->pi_end; 142 gm.ncnt = (pgf->pgf_nbuckets * hc_sz) + sizeof(struct gmonhdr); 143 gm.version = GMONVERSION; 144 gm.profrate = 0; /* use ticks */ 145 if (args.pa_flags & FLAG_DO_WIDE_GPROF_HC) 146 gm.histcounter_type = WIDEHISTCOUNTER_GMONTYPE; 147 else 148 gm.histcounter_type = HISTCOUNTER_GMONTYPE; 149 gm.spare[0] = gm.spare[1] = 0; 150 151 /* Write out the gmon header */ 152 if (write(fd, &gm, sizeof(gm)) < 0) 153 goto error; 154 155 /* Zero fill the samples[] array */ 156 (void) memset(buffer, 0, sizeof(buffer)); 157 158 count = pgf->pgf_ndatabytes - sizeof(struct gmonhdr); 159 while (count > sizeof(buffer)) { 160 if (write(fd, &buffer, sizeof(buffer)) < 0) 161 goto error; 162 count -= sizeof(buffer); 163 } 164 165 if (write(fd, &buffer, count) < 0) 166 goto error; 167 168 (void) close(fd); 169 170 return; 171 172 error: 173 err(EX_OSERR, "ERROR: Cannot write \"%s\"", pathname); 174 } 175 176 /* 177 * Determine the full pathname of a gmon.out file for a given 178 * (image,pmcid) combination. Return the interned string. 179 */ 180 181 pmcstat_interned_string 182 pmcstat_gmon_create_name(const char *samplesdir, struct pmcstat_image *image, 183 pmc_id_t pmcid) 184 { 185 const char *pmcname; 186 char fullpath[PATH_MAX]; 187 188 pmcname = pmcstat_pmcid_to_name(pmcid); 189 if (!pmcname) 190 err(EX_SOFTWARE, "ERROR: cannot find pmcid"); 191 192 (void) snprintf(fullpath, sizeof(fullpath), 193 "%s/%s/%s", samplesdir, pmcname, 194 pmcstat_string_unintern(image->pi_samplename)); 195 196 return (pmcstat_string_intern(fullpath)); 197 } 198 199 200 /* 201 * Mmap in a gmon.out file for processing. 202 */ 203 204 static void 205 pmcstat_gmon_map_file(struct pmcstat_gmonfile *pgf) 206 { 207 int fd; 208 const char *pathname; 209 210 pathname = pmcstat_string_unintern(pgf->pgf_name); 211 212 /* the gmon.out file must already exist */ 213 if ((fd = open(pathname, O_RDWR | O_NOFOLLOW, 0)) < 0) 214 err(EX_OSERR, "ERROR: cannot open \"%s\"", pathname); 215 216 pgf->pgf_gmondata = mmap(NULL, pgf->pgf_ndatabytes, 217 PROT_READ|PROT_WRITE, MAP_NOSYNC|MAP_SHARED, fd, 0); 218 219 if (pgf->pgf_gmondata == MAP_FAILED) 220 err(EX_OSERR, "ERROR: cannot map \"%s\"", pathname); 221 222 (void) close(fd); 223 } 224 225 /* 226 * Unmap a gmon.out file after sync'ing its data to disk. 227 */ 228 229 static void 230 pmcstat_gmon_unmap_file(struct pmcstat_gmonfile *pgf) 231 { 232 (void) msync(pgf->pgf_gmondata, pgf->pgf_ndatabytes, 233 MS_SYNC); 234 (void) munmap(pgf->pgf_gmondata, pgf->pgf_ndatabytes); 235 pgf->pgf_gmondata = NULL; 236 } 237 238 static void 239 pmcstat_gmon_append_arc(struct pmcstat_image *image, pmc_id_t pmcid, 240 uintptr_t rawfrom, uintptr_t rawto, uint32_t count) 241 { 242 struct rawarc arc; /* from <sys/gmon.h> */ 243 const char *pathname; 244 struct pmcstat_gmonfile *pgf; 245 246 if ((pgf = pmcstat_image_find_gmonfile(image, pmcid)) == NULL) 247 return; 248 249 if (pgf->pgf_file == NULL) { 250 pathname = pmcstat_string_unintern(pgf->pgf_name); 251 if ((pgf->pgf_file = fopen(pathname, "a")) == NULL) 252 return; 253 } 254 255 arc.raw_frompc = rawfrom + image->pi_vaddr; 256 arc.raw_selfpc = rawto + image->pi_vaddr; 257 arc.raw_count = count; 258 259 (void) fwrite(&arc, sizeof(arc), 1, pgf->pgf_file); 260 261 } 262 263 static struct pmcstat_gmonfile * 264 pmcstat_image_find_gmonfile(struct pmcstat_image *image, pmc_id_t pmcid) 265 { 266 struct pmcstat_gmonfile *pgf; 267 LIST_FOREACH(pgf, &image->pi_gmlist, pgf_next) 268 if (pgf->pgf_pmcid == pmcid) 269 return (pgf); 270 return (NULL); 271 } 272 273 static void 274 pmcstat_cgnode_do_gmon_arcs(struct pmcstat_cgnode *cg, pmc_id_t pmcid) 275 { 276 struct pmcstat_cgnode *cgc; 277 278 /* 279 * Look for child nodes that belong to the same image. 280 */ 281 282 LIST_FOREACH(cgc, &cg->pcg_children, pcg_sibling) { 283 if (cgc->pcg_image == cg->pcg_image) 284 pmcstat_gmon_append_arc(cg->pcg_image, pmcid, 285 cgc->pcg_func, cg->pcg_func, cgc->pcg_count); 286 if (cgc->pcg_nchildren > 0) 287 pmcstat_cgnode_do_gmon_arcs(cgc, pmcid); 288 } 289 } 290 291 static void 292 pmcstat_callgraph_do_gmon_arcs_for_pmcid(pmc_id_t pmcid) 293 { 294 int n; 295 struct pmcstat_cgnode_hash *pch; 296 297 for (n = 0; n < PMCSTAT_NHASH; n++) 298 LIST_FOREACH(pch, &pmcstat_cgnode_hash[n], pch_next) 299 if (pch->pch_pmcid == pmcid && 300 pch->pch_cgnode->pcg_nchildren > 1) 301 pmcstat_cgnode_do_gmon_arcs(pch->pch_cgnode, 302 pmcid); 303 } 304 305 306 static void 307 pmcstat_callgraph_do_gmon_arcs(void) 308 { 309 struct pmcstat_pmcrecord *pmcr; 310 311 LIST_FOREACH(pmcr, &pmcstat_pmcs, pr_next) 312 pmcstat_callgraph_do_gmon_arcs_for_pmcid(pmcr->pr_pmcid); 313 } 314 315 void 316 pmcpl_gmon_initimage(struct pmcstat_image *pi) 317 { 318 const char *execpath; 319 int count, nlen; 320 char *sn, *snbuf; 321 char name[NAME_MAX]; 322 323 /* 324 * Look for a suitable name for the sample files associated 325 * with this image: if `basename(path)`+".gmon" is available, 326 * we use that, otherwise we try iterating through 327 * `basename(path)`+ "~" + NNN + ".gmon" till we get a free 328 * entry. 329 */ 330 execpath = pmcstat_string_unintern(pi->pi_execpath); 331 if ((snbuf = strdup(execpath)) == NULL) 332 err(EX_OSERR, "ERROR: Cannot copy \"%s\"", execpath); 333 if ((sn = basename(snbuf)) == NULL) 334 err(EX_OSERR, "ERROR: Cannot process \"%s\"", execpath); 335 336 nlen = strlen(sn); 337 nlen = min(nlen, (int) (sizeof(name) - sizeof(".gmon"))); 338 339 snprintf(name, sizeof(name), "%.*s.gmon", nlen, sn); 340 341 /* try use the unabridged name first */ 342 if (pmcstat_string_lookup(name) == NULL) 343 pi->pi_samplename = pmcstat_string_intern(name); 344 else { 345 /* 346 * Otherwise use a prefix from the original name and 347 * up to 3 digits. 348 */ 349 nlen = strlen(sn); 350 nlen = min(nlen, (int) (sizeof(name)-sizeof("~NNN.gmon"))); 351 count = 0; 352 do { 353 if (++count > 999) 354 errx(EX_CANTCREAT, 355 "ERROR: cannot create a gmon file for" 356 " \"%s\"", name); 357 snprintf(name, sizeof(name), "%.*s~%3.3d.gmon", 358 nlen, sn, count); 359 if (pmcstat_string_lookup(name) == NULL) { 360 pi->pi_samplename = 361 pmcstat_string_intern(name); 362 count = 0; 363 } 364 } while (count > 0); 365 } 366 free(snbuf); 367 368 LIST_INIT(&pi->pi_gmlist); 369 } 370 371 void 372 pmcpl_gmon_shutdownimage(struct pmcstat_image *pi) 373 { 374 struct pmcstat_gmonfile *pgf, *pgftmp; 375 376 LIST_FOREACH_SAFE(pgf, &pi->pi_gmlist, pgf_next, pgftmp) { 377 if (pgf->pgf_file) 378 (void) fclose(pgf->pgf_file); 379 LIST_REMOVE(pgf, pgf_next); 380 free(pgf); 381 } 382 } 383 384 void 385 pmcpl_gmon_newpmc(pmcstat_interned_string ps, struct pmcstat_pmcrecord *pr) 386 { 387 struct stat st; 388 char fullpath[PATH_MAX]; 389 390 (void) pr; 391 392 /* 393 * Create the appropriate directory to hold gmon.out files. 394 */ 395 396 (void) snprintf(fullpath, sizeof(fullpath), "%s/%s", args.pa_samplesdir, 397 pmcstat_string_unintern(ps)); 398 399 /* If the path name exists, it should be a directory */ 400 if (stat(fullpath, &st) == 0 && S_ISDIR(st.st_mode)) 401 return; 402 403 if (mkdir(fullpath, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) < 0) 404 err(EX_OSERR, "ERROR: Cannot create directory \"%s\"", 405 fullpath); 406 } 407 408 /* 409 * Increment the bucket in the gmon.out file corresponding to 'pmcid' 410 * and 'pc'. 411 */ 412 413 void 414 pmcpl_gmon_process(struct pmcstat_process *pp, struct pmcstat_pmcrecord *pmcr, 415 uint32_t nsamples, uintfptr_t *cc, int usermode, uint32_t cpu) 416 { 417 struct pmcstat_pcmap *map; 418 struct pmcstat_image *image; 419 struct pmcstat_gmonfile *pgf; 420 uintfptr_t bucket; 421 HISTCOUNTER *hc; 422 WIDEHISTCOUNTER *whc; 423 pmc_id_t pmcid; 424 425 (void) nsamples; (void) usermode; (void) cpu; 426 427 map = pmcstat_process_find_map(usermode ? pp : pmcstat_kernproc, cc[0]); 428 if (map == NULL) { 429 /* Unknown offset. */ 430 pmcstat_stats.ps_samples_unknown_offset++; 431 return; 432 } 433 434 assert(cc[0] >= map->ppm_lowpc && cc[0] < map->ppm_highpc); 435 436 image = map->ppm_image; 437 pmcid = pmcr->pr_pmcid; 438 439 /* 440 * If this is the first time we are seeing a sample for 441 * this executable image, try determine its parameters. 442 */ 443 if (image->pi_type == PMCSTAT_IMAGE_UNKNOWN) 444 pmcstat_image_determine_type(image, &args); 445 446 assert(image->pi_type != PMCSTAT_IMAGE_UNKNOWN); 447 448 /* Ignore samples in images that we know nothing about. */ 449 if (image->pi_type == PMCSTAT_IMAGE_INDETERMINABLE) { 450 pmcstat_stats.ps_samples_indeterminable++; 451 return; 452 } 453 454 /* 455 * Find the gmon file corresponding to 'pmcid', creating it if 456 * needed. 457 */ 458 pgf = pmcstat_image_find_gmonfile(image, pmcid); 459 if (pgf == NULL) { 460 if (hc_sz == 0) { 461 /* Determine the correct histcounter size. */ 462 if (args.pa_flags & FLAG_DO_WIDE_GPROF_HC) 463 hc_sz = sizeof(WIDEHISTCOUNTER); 464 else 465 hc_sz = sizeof(HISTCOUNTER); 466 } 467 468 if ((pgf = calloc(1, sizeof(*pgf))) == NULL) 469 err(EX_OSERR, "ERROR:"); 470 471 pgf->pgf_gmondata = NULL; /* mark as unmapped */ 472 pgf->pgf_name = pmcstat_gmon_create_name(args.pa_samplesdir, 473 image, pmcid); 474 pgf->pgf_pmcid = pmcid; 475 assert(image->pi_end > image->pi_start); 476 pgf->pgf_nbuckets = howmany(image->pi_end - image->pi_start, 477 FUNCTION_ALIGNMENT); /* see <machine/profile.h> */ 478 pgf->pgf_ndatabytes = sizeof(struct gmonhdr) + 479 pgf->pgf_nbuckets * hc_sz; 480 pgf->pgf_nsamples = 0; 481 pgf->pgf_file = NULL; 482 483 pmcstat_gmon_create_file(pgf, image); 484 485 LIST_INSERT_HEAD(&image->pi_gmlist, pgf, pgf_next); 486 } 487 488 /* 489 * Map the gmon file in if needed. It may have been mapped 490 * out under memory pressure. 491 */ 492 if (pgf->pgf_gmondata == NULL) 493 pmcstat_gmon_map_file(pgf); 494 495 assert(pgf->pgf_gmondata != NULL); 496 497 /* 498 * 499 */ 500 501 bucket = (cc[0] - map->ppm_lowpc) / FUNCTION_ALIGNMENT; 502 503 assert(bucket < pgf->pgf_nbuckets); 504 505 if (args.pa_flags & FLAG_DO_WIDE_GPROF_HC) { 506 whc = (WIDEHISTCOUNTER *) ((uintptr_t) pgf->pgf_gmondata + 507 sizeof(struct gmonhdr)); 508 509 /* saturating add */ 510 if (whc[bucket] < WIDEHISTCOUNTER_MAX) 511 whc[bucket]++; 512 else /* mark that an overflow occurred */ 513 pgf->pgf_overflow = 1; 514 } else { 515 hc = (HISTCOUNTER *) ((uintptr_t) pgf->pgf_gmondata + 516 sizeof(struct gmonhdr)); 517 518 /* saturating add */ 519 if (hc[bucket] < HISTCOUNTER_MAX) 520 hc[bucket]++; 521 else /* mark that an overflow occurred */ 522 pgf->pgf_overflow = 1; 523 } 524 525 pgf->pgf_nsamples++; 526 } 527 528 /* 529 * Shutdown module. 530 */ 531 532 void 533 pmcpl_gmon_shutdown(FILE *mf) 534 { 535 int i; 536 struct pmcstat_gmonfile *pgf; 537 struct pmcstat_image *pi; 538 539 /* 540 * Sync back all gprof flat profile data. 541 */ 542 for (i = 0; i < PMCSTAT_NHASH; i++) { 543 LIST_FOREACH(pi, &pmcstat_image_hash[i], pi_next) { 544 if (mf) 545 (void) fprintf(mf, " \"%s\" => \"%s\"", 546 pmcstat_string_unintern(pi->pi_execpath), 547 pmcstat_string_unintern( 548 pi->pi_samplename)); 549 550 /* flush gmon.out data to disk */ 551 LIST_FOREACH(pgf, &pi->pi_gmlist, pgf_next) { 552 pmcstat_gmon_unmap_file(pgf); 553 if (mf) 554 (void) fprintf(mf, " %s/%d", 555 pmcstat_pmcid_to_name( 556 pgf->pgf_pmcid), 557 pgf->pgf_nsamples); 558 if (pgf->pgf_overflow && args.pa_verbosity >= 1) 559 warnx( 560 "WARNING: profile \"%s\" overflowed.", 561 pmcstat_string_unintern( 562 pgf->pgf_name)); 563 } 564 565 if (mf) 566 (void) fprintf(mf, "\n"); 567 } 568 } 569 570 /* 571 * Compute arcs and add these to the gprof files. 572 */ 573 if (args.pa_flags & FLAG_DO_GPROF && args.pa_graphdepth > 1) 574 pmcstat_callgraph_do_gmon_arcs(); 575 } 576