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