From 2d898efa0f4c98f1caff58bc68f8d94b3428295c Mon Sep 17 00:00:00 2001 From: fabient Date: Thu, 11 Feb 2010 22:51:44 +0000 Subject: - Reorganize code in 'plugin' to share log processing. - Kcachegrind (calltree) support with assembly/source code mapping and call count estimator (-F). - Top mode for calltree and callgraph plugin (-T). MFC after: 1 month --- usr.sbin/pmcstat/pmcpl_gprof.c | 533 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 533 insertions(+) create mode 100644 usr.sbin/pmcstat/pmcpl_gprof.c (limited to 'usr.sbin/pmcstat/pmcpl_gprof.c') diff --git a/usr.sbin/pmcstat/pmcpl_gprof.c b/usr.sbin/pmcstat/pmcpl_gprof.c new file mode 100644 index 0000000..9327eb9 --- /dev/null +++ b/usr.sbin/pmcstat/pmcpl_gprof.c @@ -0,0 +1,533 @@ +/*- + * Copyright (c) 2005-2007, Joseph Koshy + * Copyright (c) 2007 The FreeBSD Foundation + * Copyright (c) 2009, Fabien Thomas + * All rights reserved. + * + * Portions of this software were developed by A. Joseph Koshy under + * sponsorship from the FreeBSD Foundation and Google, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Transform a hwpmc(4) log into human readable form, and into + * gprof(1) compatible profiles. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pmcstat.h" +#include "pmcstat_log.h" +#include "pmcpl_callgraph.h" +#include "pmcpl_gprof.h" + +/* + * struct pmcstat_gmonfile tracks a given 'gmon.out' file. These + * files are mmap()'ed in as needed. + */ + +struct pmcstat_gmonfile { + LIST_ENTRY(pmcstat_gmonfile) pgf_next; /* list of entries */ + int pgf_overflow; /* whether a count overflowed */ + pmc_id_t pgf_pmcid; /* id of the associated pmc */ + size_t pgf_nbuckets; /* #buckets in this gmon.out */ + unsigned int pgf_nsamples; /* #samples in this gmon.out */ + pmcstat_interned_string pgf_name; /* pathname of gmon.out file */ + size_t pgf_ndatabytes; /* number of bytes mapped */ + void *pgf_gmondata; /* pointer to mmap'ed data */ + FILE *pgf_file; /* used when writing gmon arcs */ +}; + +/* + * Prototypes + */ + +static void pmcstat_gmon_create_file(struct pmcstat_gmonfile *_pgf, + struct pmcstat_image *_image); +static pmcstat_interned_string pmcstat_gmon_create_name(const char *_sd, + struct pmcstat_image *_img, pmc_id_t _pmcid); +static void pmcstat_gmon_map_file(struct pmcstat_gmonfile *_pgf); +static void pmcstat_gmon_unmap_file(struct pmcstat_gmonfile *_pgf); + +static struct pmcstat_gmonfile *pmcstat_image_find_gmonfile(struct + pmcstat_image *_i, pmc_id_t _id); + +/* + * Create a gmon.out file and size it. + */ + +static void +pmcstat_gmon_create_file(struct pmcstat_gmonfile *pgf, + struct pmcstat_image *image) +{ + int fd; + size_t count; + struct gmonhdr gm; + const char *pathname; + char buffer[DEFAULT_BUFFER_SIZE]; + + pathname = pmcstat_string_unintern(pgf->pgf_name); + if ((fd = open(pathname, O_RDWR|O_NOFOLLOW|O_CREAT, + S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) < 0) + err(EX_OSERR, "ERROR: Cannot open \"%s\"", pathname); + + gm.lpc = image->pi_start; + gm.hpc = image->pi_end; + gm.ncnt = (pgf->pgf_nbuckets * sizeof(HISTCOUNTER)) + + sizeof(struct gmonhdr); + gm.version = GMONVERSION; + gm.profrate = 0; /* use ticks */ + gm.histcounter_type = 0; /* compatibility with moncontrol() */ + gm.spare[0] = gm.spare[1] = 0; + + /* Write out the gmon header */ + if (write(fd, &gm, sizeof(gm)) < 0) + goto error; + + /* Zero fill the samples[] array */ + (void) memset(buffer, 0, sizeof(buffer)); + + count = pgf->pgf_ndatabytes - sizeof(struct gmonhdr); + while (count > sizeof(buffer)) { + if (write(fd, &buffer, sizeof(buffer)) < 0) + goto error; + count -= sizeof(buffer); + } + + if (write(fd, &buffer, count) < 0) + goto error; + + (void) close(fd); + + return; + + error: + err(EX_OSERR, "ERROR: Cannot write \"%s\"", pathname); +} + +/* + * Determine the full pathname of a gmon.out file for a given + * (image,pmcid) combination. Return the interned string. + */ + +pmcstat_interned_string +pmcstat_gmon_create_name(const char *samplesdir, struct pmcstat_image *image, + pmc_id_t pmcid) +{ + const char *pmcname; + char fullpath[PATH_MAX]; + + pmcname = pmcstat_pmcid_to_name(pmcid); + + (void) snprintf(fullpath, sizeof(fullpath), + "%s/%s/%s", samplesdir, pmcname, + pmcstat_string_unintern(image->pi_samplename)); + + return (pmcstat_string_intern(fullpath)); +} + + +/* + * Mmap in a gmon.out file for processing. + */ + +static void +pmcstat_gmon_map_file(struct pmcstat_gmonfile *pgf) +{ + int fd; + const char *pathname; + + pathname = pmcstat_string_unintern(pgf->pgf_name); + + /* the gmon.out file must already exist */ + if ((fd = open(pathname, O_RDWR | O_NOFOLLOW, 0)) < 0) + err(EX_OSERR, "ERROR: cannot open \"%s\"", pathname); + + pgf->pgf_gmondata = mmap(NULL, pgf->pgf_ndatabytes, + PROT_READ|PROT_WRITE, MAP_NOSYNC|MAP_SHARED, fd, 0); + + if (pgf->pgf_gmondata == MAP_FAILED) + err(EX_OSERR, "ERROR: cannot map \"%s\"", pathname); + + (void) close(fd); +} + +/* + * Unmap a gmon.out file after sync'ing its data to disk. + */ + +static void +pmcstat_gmon_unmap_file(struct pmcstat_gmonfile *pgf) +{ + (void) msync(pgf->pgf_gmondata, pgf->pgf_ndatabytes, + MS_SYNC); + (void) munmap(pgf->pgf_gmondata, pgf->pgf_ndatabytes); + pgf->pgf_gmondata = NULL; +} + +static void +pmcstat_gmon_append_arc(struct pmcstat_image *image, pmc_id_t pmcid, + uintptr_t rawfrom, uintptr_t rawto, uint32_t count) +{ + struct rawarc arc; /* from */ + const char *pathname; + struct pmcstat_gmonfile *pgf; + + if ((pgf = pmcstat_image_find_gmonfile(image, pmcid)) == NULL) + return; + + if (pgf->pgf_file == NULL) { + pathname = pmcstat_string_unintern(pgf->pgf_name); + if ((pgf->pgf_file = fopen(pathname, "a")) == NULL) + return; + } + + arc.raw_frompc = rawfrom + image->pi_vaddr; + arc.raw_selfpc = rawto + image->pi_vaddr; + arc.raw_count = count; + + (void) fwrite(&arc, sizeof(arc), 1, pgf->pgf_file); + +} + +static struct pmcstat_gmonfile * +pmcstat_image_find_gmonfile(struct pmcstat_image *image, pmc_id_t pmcid) +{ + struct pmcstat_gmonfile *pgf; + LIST_FOREACH(pgf, &image->pi_gmlist, pgf_next) + if (pgf->pgf_pmcid == pmcid) + return (pgf); + return (NULL); +} + +static void +pmcstat_cgnode_do_gmon_arcs(struct pmcstat_cgnode *cg, pmc_id_t pmcid) +{ + struct pmcstat_cgnode *cgc; + + /* + * Look for child nodes that belong to the same image. + */ + + LIST_FOREACH(cgc, &cg->pcg_children, pcg_sibling) { + if (cgc->pcg_image == cg->pcg_image) + pmcstat_gmon_append_arc(cg->pcg_image, pmcid, + cgc->pcg_func, cg->pcg_func, cgc->pcg_count); + if (cgc->pcg_nchildren > 0) + pmcstat_cgnode_do_gmon_arcs(cgc, pmcid); + } +} + +static void +pmcstat_callgraph_do_gmon_arcs_for_pmcid(pmc_id_t pmcid) +{ + int n; + struct pmcstat_cgnode_hash *pch; + + for (n = 0; n < PMCSTAT_NHASH; n++) + LIST_FOREACH(pch, &pmcstat_cgnode_hash[n], pch_next) + if (pch->pch_pmcid == pmcid && + pch->pch_cgnode->pcg_nchildren > 1) + pmcstat_cgnode_do_gmon_arcs(pch->pch_cgnode, + pmcid); +} + + +static void +pmcstat_callgraph_do_gmon_arcs(void) +{ + struct pmcstat_pmcrecord *pmcr; + + LIST_FOREACH(pmcr, &pmcstat_pmcs, pr_next) + pmcstat_callgraph_do_gmon_arcs_for_pmcid(pmcr->pr_pmcid); +} + +void +pmcpl_gmon_initimage(struct pmcstat_image *pi) +{ + int count, nlen; + char *sn; + char name[NAME_MAX]; + + /* + * Look for a suitable name for the sample files associated + * with this image: if `basename(path)`+".gmon" is available, + * we use that, otherwise we try iterating through + * `basename(path)`+ "~" + NNN + ".gmon" till we get a free + * entry. + */ + if ((sn = basename(pmcstat_string_unintern(pi->pi_execpath))) == NULL) + err(EX_OSERR, "ERROR: Cannot process \"%s\"", + pmcstat_string_unintern(pi->pi_execpath)); + + nlen = strlen(sn); + nlen = min(nlen, (int) (sizeof(name) - sizeof(".gmon"))); + + snprintf(name, sizeof(name), "%.*s.gmon", nlen, sn); + + /* try use the unabridged name first */ + if (pmcstat_string_lookup(name) == NULL) + pi->pi_samplename = pmcstat_string_intern(name); + else { + /* + * Otherwise use a prefix from the original name and + * upto 3 digits. + */ + nlen = strlen(sn); + nlen = min(nlen, (int) (sizeof(name)-sizeof("~NNN.gmon"))); + count = 0; + do { + if (++count > 999) + errx(EX_CANTCREAT, "ERROR: cannot create a " + "gmon file for \"%s\"", name); + snprintf(name, sizeof(name), "%.*s~%3.3d.gmon", + nlen, sn, count); + if (pmcstat_string_lookup(name) == NULL) { + pi->pi_samplename = + pmcstat_string_intern(name); + count = 0; + } + } while (count > 0); + } + + LIST_INIT(&pi->pi_gmlist); +} + +void +pmcpl_gmon_shutdownimage(struct pmcstat_image *pi) +{ + struct pmcstat_gmonfile *pgf, *pgftmp; + + LIST_FOREACH_SAFE(pgf, &pi->pi_gmlist, pgf_next, pgftmp) { + if (pgf->pgf_file) + (void) fclose(pgf->pgf_file); + LIST_REMOVE(pgf, pgf_next); + free(pgf); + } +} + +void +pmcpl_gmon_newpmc(pmcstat_interned_string ps, struct pmcstat_pmcrecord *pr) +{ + struct stat st; + char fullpath[PATH_MAX]; + + (void) pr; + + /* + * Create the appropriate directory to hold gmon.out files. + */ + + (void) snprintf(fullpath, sizeof(fullpath), "%s/%s", args.pa_samplesdir, + pmcstat_string_unintern(ps)); + + /* If the path name exists, it should be a directory */ + if (stat(fullpath, &st) == 0 && S_ISDIR(st.st_mode)) + return; + + if (mkdir(fullpath, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) < 0) + err(EX_OSERR, "ERROR: Cannot create directory \"%s\"", + fullpath); +} + +/* + * Increment the bucket in the gmon.out file corresponding to 'pmcid' + * and 'pc'. + */ + +void +pmcpl_gmon_process(struct pmcstat_process *pp, struct pmcstat_pmcrecord *pmcr, + uint32_t nsamples, uintfptr_t *cc, int usermode, uint32_t cpu) +{ + struct pmcstat_pcmap *map; + struct pmcstat_image *image; + struct pmcstat_gmonfile *pgf; + uintfptr_t bucket; + HISTCOUNTER *hc; + pmc_id_t pmcid; + + (void) nsamples; (void) usermode; (void) cpu; + + map = pmcstat_process_find_map(usermode ? pp : pmcstat_kernproc, cc[0]); + if (map == NULL) { + /* Unknown offset. */ + pmcstat_stats.ps_samples_unknown_offset++; + return; + } + + assert(cc[0] >= map->ppm_lowpc && cc[0] < map->ppm_highpc); + + image = map->ppm_image; + pmcid = pmcr->pr_pmcid; + + /* + * If this is the first time we are seeing a sample for + * this executable image, try determine its parameters. + */ + if (image->pi_type == PMCSTAT_IMAGE_UNKNOWN) + pmcstat_image_determine_type(image); + + assert(image->pi_type != PMCSTAT_IMAGE_UNKNOWN); + + /* Ignore samples in images that we know nothing about. */ + if (image->pi_type == PMCSTAT_IMAGE_INDETERMINABLE) { + pmcstat_stats.ps_samples_indeterminable++; + return; + } + + /* + * Find the gmon file corresponding to 'pmcid', creating it if + * needed. + */ + pgf = pmcstat_image_find_gmonfile(image, pmcid); + if (pgf == NULL) { + if ((pgf = calloc(1, sizeof(*pgf))) == NULL) + err(EX_OSERR, "ERROR:"); + + pgf->pgf_gmondata = NULL; /* mark as unmapped */ + pgf->pgf_name = pmcstat_gmon_create_name(args.pa_samplesdir, + image, pmcid); + pgf->pgf_pmcid = pmcid; + assert(image->pi_end > image->pi_start); + pgf->pgf_nbuckets = (image->pi_end - image->pi_start) / + FUNCTION_ALIGNMENT; /* see */ + pgf->pgf_ndatabytes = sizeof(struct gmonhdr) + + pgf->pgf_nbuckets * sizeof(HISTCOUNTER); + pgf->pgf_nsamples = 0; + pgf->pgf_file = NULL; + + pmcstat_gmon_create_file(pgf, image); + + LIST_INSERT_HEAD(&image->pi_gmlist, pgf, pgf_next); + } + + /* + * Map the gmon file in if needed. It may have been mapped + * out under memory pressure. + */ + if (pgf->pgf_gmondata == NULL) + pmcstat_gmon_map_file(pgf); + + assert(pgf->pgf_gmondata != NULL); + + /* + * + */ + + bucket = (cc[0] - map->ppm_lowpc) / FUNCTION_ALIGNMENT; + + assert(bucket < pgf->pgf_nbuckets); + + hc = (HISTCOUNTER *) ((uintptr_t) pgf->pgf_gmondata + + sizeof(struct gmonhdr)); + + /* saturating add */ + if (hc[bucket] < 0xFFFFU) /* XXX tie this to sizeof(HISTCOUNTER) */ + hc[bucket]++; + else /* mark that an overflow occurred */ + pgf->pgf_overflow = 1; + + pgf->pgf_nsamples++; +} + +/* + * Shutdown module. + */ + +void +pmcpl_gmon_shutdown(FILE *mf) +{ + int i; + struct pmcstat_gmonfile *pgf; + struct pmcstat_image *pi; + + /* + * Sync back all gprof flat profile data. + */ + for (i = 0; i < PMCSTAT_NHASH; i++) { + LIST_FOREACH(pi, &pmcstat_image_hash[i], pi_next) { + if (mf) + (void) fprintf(mf, " \"%s\" => \"%s\"", + pmcstat_string_unintern(pi->pi_execpath), + pmcstat_string_unintern( + pi->pi_samplename)); + + /* flush gmon.out data to disk */ + LIST_FOREACH(pgf, &pi->pi_gmlist, pgf_next) { + pmcstat_gmon_unmap_file(pgf); + if (mf) + (void) fprintf(mf, " %s/%d", + pmcstat_pmcid_to_name( + pgf->pgf_pmcid), + pgf->pgf_nsamples); + if (pgf->pgf_overflow && args.pa_verbosity >= 1) + warnx("WARNING: profile \"%s\" " + "overflowed.", + pmcstat_string_unintern( + pgf->pgf_name)); + } + + if (mf) + (void) fprintf(mf, "\n"); + } + } + + /* + * Compute arcs and add these to the gprof files. + */ + if (args.pa_flags & FLAG_DO_GPROF && args.pa_graphdepth > 1) + pmcstat_callgraph_do_gmon_arcs(); +} -- cgit v1.1