Index: head/usr.sbin/pmcstat/pmcpl_gprof.c =================================================================== --- head/usr.sbin/pmcstat/pmcpl_gprof.c (revision 291015) +++ head/usr.sbin/pmcstat/pmcpl_gprof.c (revision 291016) @@ -1,536 +1,566 @@ /*- * 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" +typedef uint64_t WIDEHISTCOUNTER; + +#define WIDEHISTCOUNTER_MAX UINT64_MAX +#define HISTCOUNTER_MAX USHRT_MAX +#define WIDEHISTCOUNTER_GMONTYPE ((int) 64) +#define HISTCOUNTER_GMONTYPE ((int) 0) +static int hc_sz=0; + /* * 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.ncnt = (pgf->pgf_nbuckets * hc_sz) + sizeof(struct gmonhdr); gm.version = GMONVERSION; gm.profrate = 0; /* use ticks */ - gm.histcounter_type = 0; /* compatibility with moncontrol() */ + if (args.pa_flags & FLAG_DO_WIDE_GPROF_HC) + gm.histcounter_type = WIDEHISTCOUNTER_GMONTYPE; + else + gm.histcounter_type = HISTCOUNTER_GMONTYPE; 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); if (!pmcname) err(EX_SOFTWARE, "ERROR: cannot find 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 * up to 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; + WIDEHISTCOUNTER *whc; 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 (hc_sz == 0) { + /* Determine the correct histcounter size. */ + if (args.pa_flags & FLAG_DO_WIDE_GPROF_HC) + hc_sz = sizeof(WIDEHISTCOUNTER); + else + hc_sz = sizeof(HISTCOUNTER); + } + 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_nbuckets * hc_sz; 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)); + if (args.pa_flags & FLAG_DO_WIDE_GPROF_HC) { + whc = (WIDEHISTCOUNTER *) ((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; + /* saturating add */ + if (whc[bucket] < WIDEHISTCOUNTER_MAX) + whc[bucket]++; + else /* mark that an overflow occurred */ + pgf->pgf_overflow = 1; + } else { + hc = (HISTCOUNTER *) ((uintptr_t) pgf->pgf_gmondata + + sizeof(struct gmonhdr)); + + /* saturating add */ + if (hc[bucket] < HISTCOUNTER_MAX) + 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(); } Index: head/usr.sbin/pmcstat/pmcstat.8 =================================================================== --- head/usr.sbin/pmcstat/pmcstat.8 (revision 291015) +++ head/usr.sbin/pmcstat/pmcstat.8 (revision 291016) @@ -1,493 +1,500 @@ .\" Copyright (c) 2003-2008 Joseph Koshy .\" Copyright (c) 2007 The FreeBSD Foundation .\" All rights reserved. .\" .\" 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 Joseph Koshy ``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 Joseph Koshy 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. .\" .\" $FreeBSD$ .\" -.Dd May 27, 2015 +.Dd November 17, 2015 .Dt PMCSTAT 8 .Os .Sh NAME .Nm pmcstat .Nd "performance measurement with performance monitoring hardware" .Sh SYNOPSIS .Nm .Op Fl C .Op Fl D Ar pathname .Op Fl E .Op Fl F Ar pathname .Op Fl G Ar pathname .Op Fl M Ar mapfilename .Op Fl N .Op Fl O Ar logfilename .Op Fl P Ar event-spec .Op Fl R Ar logfilename .Op Fl S Ar event-spec .Op Fl T .Op Fl W .Op Fl a Ar pathname .Op Fl c Ar cpu-spec .Op Fl d +.Op Fl e .Op Fl f Ar pluginopt .Op Fl g .Op Fl k Ar kerneldir .Op Fl l Ar secs .Op Fl m Ar pathname .Op Fl n Ar rate .Op Fl o Ar outputfile .Op Fl p Ar event-spec .Op Fl q .Op Fl r Ar fsroot .Op Fl s Ar event-spec .Op Fl t Ar process-spec .Op Fl v .Op Fl w Ar secs .Op Fl z Ar graphdepth .Op Ar command Op Ar args .Sh DESCRIPTION The .Nm utility measures system performance using the facilities provided by .Xr hwpmc 4 . .Pp The .Nm utility can measure both hardware events seen by the system as a whole, and those seen when a specified set of processes are executing on the system's CPUs. If a specific set of processes is being targeted (for example, if the .Fl t Ar process-spec option is specified, or if a command line is specified using .Ar command ) , then measurement occurs till .Ar command exits, or till all target processes specified by the .Fl t Ar process-spec options exit, or till the .Nm utility is interrupted by the user. If a specific set of processes is not targeted for measurement, then .Nm will perform system-wide measurements till interrupted by the user. .Pp A given invocation of .Nm can mix allocations of system-mode and process-mode PMCs, of both counting and sampling flavors. The values of all counting PMCs are printed in human readable form at regular intervals by .Nm . The output of sampling PMCs may be configured to go to a log file for subsequent offline analysis, or, at the expense of greater overhead, may be configured to be printed in text form on the fly. .Pp Hardware events to measure are specified to .Nm using event specifier strings .Ar event-spec . The syntax of these event specifiers is machine dependent and is documented in .Xr pmc 3 . .Pp A process-mode PMC may be configured to be inheritable by the target process' current and future children. .Sh OPTIONS The following options are available: .Bl -tag -width indent .It Fl C Toggle between showing cumulative or incremental counts for subsequent counting mode PMCs specified on the command line. The default is to show incremental counts. .It Fl D Ar pathname Create files with per-program samples in the directory named by .Ar pathname . The default is to create these files in the current directory. .It Fl E Toggle showing per-process counts at the time a tracked process exits for subsequent process-mode PMCs specified on the command line. This option is useful for mapping the performance characteristics of a complex pipeline of processes when used in conjunction with the .Fl d option. The default is to not to enable per-process tracking. .It Fl F Ar pathname Print calltree (Kcachegrind) information to file .Ar pathname . If argument .Ar pathname is a .Dq Li - this information is sent to the output file specified by the .Fl o option. .It Fl G Ar pathname Print callchain information to file .Ar pathname . If argument .Ar pathname is a .Dq Li - this information is sent to the output file specified by the .Fl o option. .It Fl M Ar mapfilename Write the mapping between executable objects encountered in the event log and the abbreviated pathnames used for .Xr gprof 1 profiles to file .Ar mapfilename . If this option is not specified, mapping information is not written. Argument .Ar mapfilename may be a .Dq Li - in which case this mapping information is sent to the output file configured by the .Fl o option. .It Fl N Toggle capturing callchain information for subsequent sampling PMCs. The default is for sampling PMCs to capture callchain information. .It Fl O Ar logfilename Send logging output to file .Ar logfilename . If .Ar logfilename is of the form .Ar hostname Ns : Ns Ar port , where .Ar hostname does not start with a .Ql \&. or a .Ql / , then .Nm will open a network socket to host .Ar hostname on port .Ar port . .Pp If the .Fl O option is not specified and one of the logging options is requested, then .Nm will print a textual form of the logged events to the configured output file. .It Fl P Ar event-spec Allocate a process mode sampling PMC measuring hardware events specified in .Ar event-spec . .It Fl R Ar logfilename Perform offline analysis using sampling data in file .Ar logfilename . .It Fl S Ar event-spec Allocate a system mode sampling PMC measuring hardware events specified in .Ar event-spec . .It Fl T Use a top like mode for sampling PMCs. The following hotkeys can be used: 'c+a' switch to accumulative mode, 'c+d' switch to delta mode, 'm' merge PMCs, 'n' change view, 'p' show next PMC, ' ' pause, 'q' quit. calltree only: 'f' cost under threshold is seen as a dot. .It Fl W Toggle logging the incremental counts seen by the threads of a tracked process each time they are scheduled on a CPU. This is an experimental feature intended to help analyse the dynamic behaviour of processes in the system. It may incur substantial overhead if enabled. The default is for this feature to be disabled. .It Fl a Ar pathname Perform a symbol and file:line lookup for each address in each callgraph and save the output to .Ar pathname . Unlike .Fl m that only resolves the first symbol in the graph, this resolves every node in the callgraph, or prints out addresses if no lookup information is available. This option requires the .Fl R option to read in samples that were previously collected and saved with the .Fl O option. .It Fl c Ar cpu-spec Set the cpus for subsequent system mode PMCs specified on the command line to .Ar cpu-spec . Argument .Ar cpu-spec is a comma separated list of CPU numbers, or the literal .Sq * denoting all available CPUs. The default is to allocate system mode PMCs on all available CPUs. .It Fl d Toggle between process mode PMCs measuring events for the target process' current and future children or only measuring events for the target process. The default is to measure events for the target process alone. (it has to be passed in the command line prior to .Fl p , .Fl s , .Fl P , or .Fl S ) . +.It Fl e +Specify that the gprof profile files will use a wide history counter. +These files are produced in a format compatible with +.Xr gprof 1 . +However, other tools that cannot fully parse a BSD-style +gmon header might be unable to correctly parse these files. .It Fl f Ar pluginopt Pass option string to the active plugin. .br threshold= do not display cost under specified value (Top). .br skiplink=0|1 replace node with cost under threshold by a dot (Top). .It Fl g Produce profiles in a format compatible with .Xr gprof 1 . A separate profile file is generated for each executable object encountered. Profile files are placed in sub-directories named by their PMC event name. .It Fl k Ar kerneldir Set the pathname of the kernel directory to argument .Ar kerneldir . This directory specifies where .Nm should look for the kernel and its modules. The default is to use the path of the running kernel obtained from the .Va kern.bootfile sysctl. .It Fl l Ar secs Set system-wide performance measurement duration for .Ar secs seconds. The argument .Ar secs may be a fractional value. .It Fl m Ar pathname Print the sampled PCs with the name, the start and ending addresses of the function within they live. The .Ar pathname argument is mandatory and indicates where the information will be stored. If argument .Ar pathname is a .Dq Li - this information is sent to the output file specified by the .Fl o option. This option requires the .Fl R option to read in samples that were previously collected and saved with the .Fl O option. .It Fl n Ar rate Set the default sampling rate for subsequent sampling mode PMCs specified on the command line. The default is to configure PMCs to sample the CPU's instruction pointer every 65536 events. .It Fl o Ar outputfile Send counter readings and textual representations of logged data to file .Ar outputfile . The default is to send output to .Pa stderr when collecting live data and to .Pa stdout when processing a pre-existing logfile. .It Fl p Ar event-spec Allocate a process mode counting PMC measuring hardware events specified in .Ar event-spec . .It Fl q Decrease verbosity. .It Fl r Ar fsroot Set the top of the filesystem hierarchy under which executables are located to argument .Ar fsroot . The default is .Pa / . .It Fl s Ar event-spec Allocate a system mode counting PMC measuring hardware events specified in .Ar event-spec . .It Fl t Ar process-spec Attach process mode PMCs to the processes named by argument .Ar process-spec . Argument .Ar process-spec may be a non-negative integer denoting a specific process id, or a regular expression for selecting processes based on their command names. .It Fl v Increase verbosity. .It Fl w Ar secs Print the values of all counting mode PMCs or sampling mode PMCs for top mode every .Ar secs seconds. The argument .Ar secs may be a fractional value. The default interval is 5 seconds. .It Fl z Ar graphdepth When printing system-wide callgraphs, limit callgraphs to the depth specified by argument .Ar graphdepth . .El .Pp If .Ar command is specified, it is executed using .Xr execvp 3 . .Sh EXAMPLES To perform system-wide statistical sampling on an AMD Athlon CPU with samples taken every 32768 instruction retirals and data being sampled to file .Pa sample.stat , use: .Dl "pmcstat -O sample.stat -n 32768 -S k7-retired-instructions" .Pp To execute .Nm firefox and measure the number of data cache misses suffered by it and its children every 12 seconds on an AMD Athlon, use: .Dl "pmcstat -d -w 12 -p k7-dc-misses firefox" .Pp To measure instructions retired for all processes named .Dq emacs use: .Dl "pmcstat -t '^emacs$' -p instructions" .Pp To measure instructions retired for processes named .Dq emacs for a period of 10 seconds use: .Dl "pmcstat -t '^emacs$' -p instructions sleep 10" .Pp To count instruction tlb-misses on CPUs 0 and 2 on a Intel Pentium Pro/Pentium III SMP system use: .Dl "pmcstat -c 0,2 -s p6-itlb-miss" .Pp To collect profiling information for a specific process with pid 1234 based on instruction cache misses seen by it use: .Dl "pmcstat -P ic-misses -t 1234 -O /tmp/sample.out" .Pp To perform system-wide sampling on all configured processors based on processor instructions retired use: .Dl "pmcstat -S instructions -O /tmp/sample.out" If callgraph capture is not desired use: .Dl "pmcstat -N -S instructions -O /tmp/sample.out" .Pp To send the generated event log to a remote machine use: .Dl "pmcstat -S instructions -O remotehost:port" On the remote machine, the sample log can be collected using .Xr nc 1 : .Dl "nc -l remotehost port > /tmp/sample.out" .Pp To generate .Xr gprof 1 compatible profiles from a sample file use: .Dl "pmcstat -R /tmp/sample.out -g" .Pp To print a system-wide profile with callgraphs to file .Pa "foo.graph" use: .Dl "pmcstat -R /tmp/sample.out -G foo.graph" .Sh DIAGNOSTICS If option .Fl v is specified, .Nm may issue the following diagnostic messages: .Bl -diag .It "#callchain/dubious-frames" The number of callchain records that had an .Dq impossible value for a return address. .It "#exec handling errors" The number of .Xr exec 2 events in the log file that named executables that could not be analyzed. .It "#exec/elf" The number of .Xr exec 2 events that named ELF executables. .It "#exec/unknown" The number of .Xr exec 2 events that named executables with unrecognized formats. .It "#samples/total" The total number of samples in the log file. .It "#samples/unclaimed" The number of samples that could not be correlated to a known executable object (i.e., to an executable, shared library, the kernel or the runtime loader). .It "#samples/unknown-object" The number of samples that were associated with an executable with an unrecognized object format. .El .Pp .Ex -std .Sh COMPATIBILITY Due to the limitations of the .Pa gmon.out file format, .Xr gprof 1 compatible profiles generated by the .Fl g option do not contain information about calls that cross executable boundaries. The generated .Pa gmon.out files are also only meaningful for native executables. .Sh SEE ALSO .Xr gprof 1 , .Xr nc 1 , .Xr execvp 3 , .Xr pmc 3 , .Xr pmclog 3 , .Xr hwpmc 4 , .Xr pmccontrol 8 , .Xr sysctl 8 .Sh HISTORY The .Nm utility first appeared in .Fx 6.0 . It is .Ud .Sh AUTHORS .An Joseph Koshy Aq Mt jkoshy@FreeBSD.org .Sh BUGS The .Nm utility cannot yet analyse .Xr hwpmc 4 logs generated by non-native architectures. Index: head/usr.sbin/pmcstat/pmcstat.c =================================================================== --- head/usr.sbin/pmcstat/pmcstat.c (revision 291015) +++ head/usr.sbin/pmcstat/pmcstat.c (revision 291016) @@ -1,1525 +1,1537 @@ /*- * Copyright (c) 2003-2008, Joseph Koshy * Copyright (c) 2007 The FreeBSD Foundation * 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. */ #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 #include #include "pmcstat.h" /* * A given invocation of pmcstat(8) can manage multiple PMCs of both * the system-wide and per-process variety. Each of these could be in * 'counting mode' or in 'sampling mode'. * * For 'counting mode' PMCs, pmcstat(8) will periodically issue a * pmc_read() at the configured time interval and print out the value * of the requested PMCs. * * For 'sampling mode' PMCs it can log to a file for offline analysis, * or can analyse sampling data "on the fly", either by converting * samples to printed textual form or by creating gprof(1) compatible * profiles, one per program executed. When creating gprof(1) * profiles it can optionally merge entries from multiple processes * for a given executable into a single profile file. * * pmcstat(8) can also execute a command line and attach PMCs to the * resulting child process. The protocol used is as follows: * * - parent creates a socketpair for two way communication and * fork()s. * - subsequently: * * /Parent/ /Child/ * * - Wait for childs token. * - Sends token. * - Awaits signal to start. * - Attaches PMCs to the child's pid * and starts them. Sets up * monitoring for the child. * - Signals child to start. * - Receives signal, attempts exec(). * * After this point normal processing can happen. */ /* Globals */ int pmcstat_displayheight = DEFAULT_DISPLAY_HEIGHT; int pmcstat_displaywidth = DEFAULT_DISPLAY_WIDTH; static int pmcstat_sockpair[NSOCKPAIRFD]; static int pmcstat_kq; static kvm_t *pmcstat_kvm; static struct kinfo_proc *pmcstat_plist; struct pmcstat_args args; static void pmcstat_clone_event_descriptor(struct pmcstat_ev *ev, const cpuset_t *cpumask) { int cpu; struct pmcstat_ev *ev_clone; for (cpu = 0; cpu < CPU_SETSIZE; cpu++) { if (!CPU_ISSET(cpu, cpumask)) continue; if ((ev_clone = malloc(sizeof(*ev_clone))) == NULL) errx(EX_SOFTWARE, "ERROR: Out of memory"); (void) memset(ev_clone, 0, sizeof(*ev_clone)); ev_clone->ev_count = ev->ev_count; ev_clone->ev_cpu = cpu; ev_clone->ev_cumulative = ev->ev_cumulative; ev_clone->ev_flags = ev->ev_flags; ev_clone->ev_mode = ev->ev_mode; ev_clone->ev_name = strdup(ev->ev_name); ev_clone->ev_pmcid = ev->ev_pmcid; ev_clone->ev_saved = ev->ev_saved; ev_clone->ev_spec = strdup(ev->ev_spec); STAILQ_INSERT_TAIL(&args.pa_events, ev_clone, ev_next); } } static void pmcstat_get_cpumask(const char *cpuspec, cpuset_t *cpumask) { int cpu; const char *s; char *end; CPU_ZERO(cpumask); s = cpuspec; do { cpu = strtol(s, &end, 0); if (cpu < 0 || end == s) errx(EX_USAGE, "ERROR: Illegal CPU specification \"%s\".", cpuspec); CPU_SET(cpu, cpumask); s = end + strspn(end, ", \t"); } while (*s); assert(!CPU_EMPTY(cpumask)); } void pmcstat_attach_pmcs(void) { struct pmcstat_ev *ev; struct pmcstat_target *pt; int count; /* Attach all process PMCs to target processes. */ count = 0; STAILQ_FOREACH(ev, &args.pa_events, ev_next) { if (PMC_IS_SYSTEM_MODE(ev->ev_mode)) continue; SLIST_FOREACH(pt, &args.pa_targets, pt_next) if (pmc_attach(ev->ev_pmcid, pt->pt_pid) == 0) count++; else if (errno != ESRCH) err(EX_OSERR, "ERROR: cannot attach pmc \"%s\" to process %d", ev->ev_name, (int)pt->pt_pid); } if (count == 0) errx(EX_DATAERR, "ERROR: No processes were attached to."); } void pmcstat_cleanup(void) { struct pmcstat_ev *ev, *tmp; /* release allocated PMCs. */ STAILQ_FOREACH_SAFE(ev, &args.pa_events, ev_next, tmp) if (ev->ev_pmcid != PMC_ID_INVALID) { if (pmc_stop(ev->ev_pmcid) < 0) err(EX_OSERR, "ERROR: cannot stop pmc 0x%x \"%s\"", ev->ev_pmcid, ev->ev_name); if (pmc_release(ev->ev_pmcid) < 0) err(EX_OSERR, "ERROR: cannot release pmc 0x%x \"%s\"", ev->ev_pmcid, ev->ev_name); free(ev->ev_name); free(ev->ev_spec); STAILQ_REMOVE(&args.pa_events, ev, pmcstat_ev, ev_next); free(ev); } /* de-configure the log file if present. */ if (args.pa_flags & (FLAG_HAS_PIPE | FLAG_HAS_OUTPUT_LOGFILE)) (void) pmc_configure_logfile(-1); if (args.pa_logparser) { pmclog_close(args.pa_logparser); args.pa_logparser = NULL; } pmcstat_shutdown_logging(); } void pmcstat_create_process(void) { char token; pid_t pid; struct kevent kev; struct pmcstat_target *pt; if (socketpair(AF_UNIX, SOCK_STREAM, 0, pmcstat_sockpair) < 0) err(EX_OSERR, "ERROR: cannot create socket pair"); switch (pid = fork()) { case -1: err(EX_OSERR, "ERROR: cannot fork"); /*NOTREACHED*/ case 0: /* child */ (void) close(pmcstat_sockpair[PARENTSOCKET]); /* Write a token to tell our parent we've started executing. */ if (write(pmcstat_sockpair[CHILDSOCKET], "+", 1) != 1) err(EX_OSERR, "ERROR (child): cannot write token"); /* Wait for our parent to signal us to start. */ if (read(pmcstat_sockpair[CHILDSOCKET], &token, 1) < 0) err(EX_OSERR, "ERROR (child): cannot read token"); (void) close(pmcstat_sockpair[CHILDSOCKET]); /* exec() the program requested */ execvp(*args.pa_argv, args.pa_argv); /* and if that fails, notify the parent */ kill(getppid(), SIGCHLD); err(EX_OSERR, "ERROR: execvp \"%s\" failed", *args.pa_argv); /*NOTREACHED*/ default: /* parent */ (void) close(pmcstat_sockpair[CHILDSOCKET]); break; } /* Ask to be notified via a kevent when the target process exits. */ EV_SET(&kev, pid, EVFILT_PROC, EV_ADD|EV_ONESHOT, NOTE_EXIT, 0, NULL); if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) err(EX_OSERR, "ERROR: cannot monitor child process %d", pid); if ((pt = malloc(sizeof(*pt))) == NULL) errx(EX_SOFTWARE, "ERROR: Out of memory."); pt->pt_pid = pid; SLIST_INSERT_HEAD(&args.pa_targets, pt, pt_next); /* Wait for the child to signal that its ready to go. */ if (read(pmcstat_sockpair[PARENTSOCKET], &token, 1) < 0) err(EX_OSERR, "ERROR (parent): cannot read token"); return; } void pmcstat_find_targets(const char *spec) { int n, nproc, pid, rv; struct pmcstat_target *pt; char errbuf[_POSIX2_LINE_MAX], *end; static struct kinfo_proc *kp; regex_t reg; regmatch_t regmatch; /* First check if we've been given a process id. */ pid = strtol(spec, &end, 0); if (end != spec && pid >= 0) { if ((pt = malloc(sizeof(*pt))) == NULL) goto outofmemory; pt->pt_pid = pid; SLIST_INSERT_HEAD(&args.pa_targets, pt, pt_next); return; } /* Otherwise treat arg as a regular expression naming processes. */ if (pmcstat_kvm == NULL) { if ((pmcstat_kvm = kvm_openfiles(NULL, "/dev/null", NULL, 0, errbuf)) == NULL) err(EX_OSERR, "ERROR: Cannot open kernel \"%s\"", errbuf); if ((pmcstat_plist = kvm_getprocs(pmcstat_kvm, KERN_PROC_PROC, 0, &nproc)) == NULL) err(EX_OSERR, "ERROR: Cannot get process list: %s", kvm_geterr(pmcstat_kvm)); } else nproc = 0; if ((rv = regcomp(®, spec, REG_EXTENDED|REG_NOSUB)) != 0) { regerror(rv, ®, errbuf, sizeof(errbuf)); err(EX_DATAERR, "ERROR: Failed to compile regex \"%s\": %s", spec, errbuf); } for (n = 0, kp = pmcstat_plist; n < nproc; n++, kp++) { if ((rv = regexec(®, kp->ki_comm, 1, ®match, 0)) == 0) { if ((pt = malloc(sizeof(*pt))) == NULL) goto outofmemory; pt->pt_pid = kp->ki_pid; SLIST_INSERT_HEAD(&args.pa_targets, pt, pt_next); } else if (rv != REG_NOMATCH) { regerror(rv, ®, errbuf, sizeof(errbuf)); errx(EX_SOFTWARE, "ERROR: Regex evalation failed: %s", errbuf); } } regfree(®); return; outofmemory: errx(EX_SOFTWARE, "Out of memory."); /*NOTREACHED*/ } void pmcstat_kill_process(void) { struct pmcstat_target *pt; assert(args.pa_flags & FLAG_HAS_COMMANDLINE); /* * If a command line was specified, it would be the very first * in the list, before any other processes specified by -t. */ pt = SLIST_FIRST(&args.pa_targets); assert(pt != NULL); if (kill(pt->pt_pid, SIGINT) != 0) err(EX_OSERR, "ERROR: cannot signal child process"); } void pmcstat_start_pmcs(void) { struct pmcstat_ev *ev; STAILQ_FOREACH(ev, &args.pa_events, ev_next) { assert(ev->ev_pmcid != PMC_ID_INVALID); if (pmc_start(ev->ev_pmcid) < 0) { warn("ERROR: Cannot start pmc 0x%x \"%s\"", ev->ev_pmcid, ev->ev_name); pmcstat_cleanup(); exit(EX_OSERR); } } } void pmcstat_print_headers(void) { struct pmcstat_ev *ev; int c, w; (void) fprintf(args.pa_printfile, PRINT_HEADER_PREFIX); STAILQ_FOREACH(ev, &args.pa_events, ev_next) { if (PMC_IS_SAMPLING_MODE(ev->ev_mode)) continue; c = PMC_IS_SYSTEM_MODE(ev->ev_mode) ? 's' : 'p'; if (ev->ev_fieldskip != 0) (void) fprintf(args.pa_printfile, "%*s", ev->ev_fieldskip, ""); w = ev->ev_fieldwidth - ev->ev_fieldskip - 2; if (c == 's') (void) fprintf(args.pa_printfile, "s/%02d/%-*s ", ev->ev_cpu, w-3, ev->ev_name); else (void) fprintf(args.pa_printfile, "p/%*s ", w, ev->ev_name); } (void) fflush(args.pa_printfile); } void pmcstat_print_counters(void) { int extra_width; struct pmcstat_ev *ev; pmc_value_t value; extra_width = sizeof(PRINT_HEADER_PREFIX) - 1; STAILQ_FOREACH(ev, &args.pa_events, ev_next) { /* skip sampling mode counters */ if (PMC_IS_SAMPLING_MODE(ev->ev_mode)) continue; if (pmc_read(ev->ev_pmcid, &value) < 0) err(EX_OSERR, "ERROR: Cannot read pmc \"%s\"", ev->ev_name); (void) fprintf(args.pa_printfile, "%*ju ", ev->ev_fieldwidth + extra_width, (uintmax_t) ev->ev_cumulative ? value : (value - ev->ev_saved)); if (ev->ev_cumulative == 0) ev->ev_saved = value; extra_width = 0; } (void) fflush(args.pa_printfile); } /* * Print output */ void pmcstat_print_pmcs(void) { static int linecount = 0; /* check if we need to print a header line */ if (++linecount > pmcstat_displayheight) { (void) fprintf(args.pa_printfile, "\n"); linecount = 1; } if (linecount == 1) pmcstat_print_headers(); (void) fprintf(args.pa_printfile, "\n"); pmcstat_print_counters(); return; } /* * Do process profiling * * If a pid was specified, attach each allocated PMC to the target * process. Otherwise, fork a child and attach the PMCs to the child, * and have the child exec() the target program. */ void pmcstat_start_process(void) { /* Signal the child to proceed. */ if (write(pmcstat_sockpair[PARENTSOCKET], "!", 1) != 1) err(EX_OSERR, "ERROR (parent): write of token failed"); (void) close(pmcstat_sockpair[PARENTSOCKET]); } void pmcstat_show_usage(void) { errx(EX_USAGE, "[options] [commandline]\n" "\t Measure process and/or system performance using hardware\n" "\t performance monitoring counters.\n" "\t Options include:\n" "\t -C\t\t (toggle) show cumulative counts\n" "\t -D path\t create profiles in directory \"path\"\n" "\t -E\t\t (toggle) show counts at process exit\n" "\t -F file\t write a system-wide callgraph (Kcachegrind format)" " to \"file\"\n" "\t -G file\t write a system-wide callgraph to \"file\"\n" "\t -M file\t print executable/gmon file map to \"file\"\n" "\t -N\t\t (toggle) capture callchains\n" "\t -O file\t send log output to \"file\"\n" "\t -P spec\t allocate a process-private sampling PMC\n" "\t -R file\t read events from \"file\"\n" "\t -S spec\t allocate a system-wide sampling PMC\n" "\t -T\t\t start in top mode\n" "\t -W\t\t (toggle) show counts per context switch\n" "\t -a file\t print sampled PCs and callgraph to \"file\"\n" "\t -c cpu-list\t set cpus for subsequent system-wide PMCs\n" "\t -d\t\t (toggle) track descendants\n" + "\t -e\t\t use wide history counter for gprof(1) output\n" "\t -f spec\t pass \"spec\" to as plugin option\n" "\t -g\t\t produce gprof(1) compatible profiles\n" "\t -k dir\t\t set the path to the kernel\n" "\t -l secs\t set duration time\n" "\t -m file\t print sampled PCs to \"file\"\n" "\t -n rate\t set sampling rate\n" "\t -o file\t send print output to \"file\"\n" "\t -p spec\t allocate a process-private counting PMC\n" "\t -q\t\t suppress verbosity\n" "\t -r fsroot\t specify FS root directory\n" "\t -s spec\t allocate a system-wide counting PMC\n" "\t -t process-spec attach to running processes matching " "\"process-spec\"\n" "\t -v\t\t increase verbosity\n" "\t -w secs\t set printing time interval\n" "\t -z depth\t limit callchain display depth" ); } /* * At exit handler for top mode */ void pmcstat_topexit(void) { if (!args.pa_toptty) return; /* * Shutdown ncurses. */ clrtoeol(); refresh(); endwin(); } /* * Main */ int main(int argc, char **argv) { cpuset_t cpumask, rootmask; double interval; double duration; int option, npmc; int c, check_driver_stats, current_sampling_count; int do_callchain, do_descendants, do_logproccsw, do_logprocexit; int do_print, do_read; size_t len; int graphdepth; int pipefd[2], rfd; int use_cumulative_counts; short cf, cb; char *end, *tmp; const char *errmsg, *graphfilename; enum pmcstat_state runstate; struct pmc_driverstats ds_start, ds_end; struct pmcstat_ev *ev; struct sigaction sa; struct kevent kev; struct winsize ws; struct stat sb; char buffer[PATH_MAX]; check_driver_stats = 0; current_sampling_count = DEFAULT_SAMPLE_COUNT; do_callchain = 1; do_descendants = 0; do_logproccsw = 0; do_logprocexit = 0; use_cumulative_counts = 0; graphfilename = "-"; args.pa_required = 0; args.pa_flags = 0; args.pa_verbosity = 1; args.pa_logfd = -1; args.pa_fsroot = ""; args.pa_samplesdir = "."; args.pa_printfile = stderr; args.pa_graphdepth = DEFAULT_CALLGRAPH_DEPTH; args.pa_graphfile = NULL; args.pa_interval = DEFAULT_WAIT_INTERVAL; args.pa_mapfilename = NULL; args.pa_inputpath = NULL; args.pa_outputpath = NULL; args.pa_pplugin = PMCSTAT_PL_NONE; args.pa_plugin = PMCSTAT_PL_NONE; args.pa_ctdumpinstr = 1; args.pa_topmode = PMCSTAT_TOP_DELTA; args.pa_toptty = 0; args.pa_topcolor = 0; args.pa_mergepmc = 0; args.pa_duration = 0.0; STAILQ_INIT(&args.pa_events); SLIST_INIT(&args.pa_targets); bzero(&ds_start, sizeof(ds_start)); bzero(&ds_end, sizeof(ds_end)); ev = NULL; CPU_ZERO(&cpumask); /* Default to using the running system kernel. */ len = 0; if (sysctlbyname("kern.bootfile", NULL, &len, NULL, 0) == -1) err(EX_OSERR, "ERROR: Cannot determine path of running kernel"); args.pa_kernel = malloc(len + 1); if (sysctlbyname("kern.bootfile", args.pa_kernel, &len, NULL, 0) == -1) err(EX_OSERR, "ERROR: Cannot determine path of running kernel"); /* * The initial CPU mask specifies the root mask of this process * which is usually all CPUs in the system. */ if (cpuset_getaffinity(CPU_LEVEL_ROOT, CPU_WHICH_PID, -1, sizeof(rootmask), &rootmask) == -1) err(EX_OSERR, "ERROR: Cannot determine the root set of CPUs"); CPU_COPY(&rootmask, &cpumask); while ((option = getopt(argc, argv, - "CD:EF:G:M:NO:P:R:S:TWa:c:df:gk:l:m:n:o:p:qr:s:t:vw:z:")) != -1) + "CD:EF:G:M:NO:P:R:S:TWa:c:def:gk:l:m:n:o:p:qr:s:t:vw:z:")) != -1) switch (option) { case 'a': /* Annotate + callgraph */ args.pa_flags |= FLAG_DO_ANNOTATE; args.pa_plugin = PMCSTAT_PL_ANNOTATE_CG; graphfilename = optarg; break; case 'C': /* cumulative values */ use_cumulative_counts = !use_cumulative_counts; args.pa_required |= FLAG_HAS_COUNTING_PMCS; break; case 'c': /* CPU */ if (optarg[0] == '*' && optarg[1] == '\0') CPU_COPY(&rootmask, &cpumask); else pmcstat_get_cpumask(optarg, &cpumask); args.pa_flags |= FLAGS_HAS_CPUMASK; args.pa_required |= FLAG_HAS_SYSTEM_PMCS; break; case 'D': if (stat(optarg, &sb) < 0) err(EX_OSERR, "ERROR: Cannot stat \"%s\"", optarg); if (!S_ISDIR(sb.st_mode)) errx(EX_USAGE, "ERROR: \"%s\" is not a directory.", optarg); args.pa_samplesdir = optarg; args.pa_flags |= FLAG_HAS_SAMPLESDIR; args.pa_required |= FLAG_DO_GPROF; break; case 'd': /* toggle descendents */ do_descendants = !do_descendants; args.pa_required |= FLAG_HAS_PROCESS_PMCS; break; + case 'e': /* wide gprof metrics */ + args.pa_flags |= FLAG_DO_WIDE_GPROF_HC; + break; + case 'F': /* produce a system-wide calltree */ args.pa_flags |= FLAG_DO_CALLGRAPHS; args.pa_plugin = PMCSTAT_PL_CALLTREE; graphfilename = optarg; break; case 'f': /* plugins options */ if (args.pa_plugin == PMCSTAT_PL_NONE) err(EX_USAGE, "ERROR: Need -g/-G/-m/-T."); pmcstat_pluginconfigure_log(optarg); break; case 'G': /* produce a system-wide callgraph */ args.pa_flags |= FLAG_DO_CALLGRAPHS; args.pa_plugin = PMCSTAT_PL_CALLGRAPH; graphfilename = optarg; break; case 'g': /* produce gprof compatible profiles */ args.pa_flags |= FLAG_DO_GPROF; args.pa_pplugin = PMCSTAT_PL_CALLGRAPH; args.pa_plugin = PMCSTAT_PL_GPROF; break; case 'k': /* pathname to the kernel */ free(args.pa_kernel); args.pa_kernel = strdup(optarg); args.pa_required |= FLAG_DO_ANALYSIS; args.pa_flags |= FLAG_HAS_KERNELPATH; break; case 'l': /* time duration in seconds */ duration = strtod(optarg, &end); if (*end != '\0' || duration <= 0) errx(EX_USAGE, "ERROR: Illegal duration time " "value \"%s\".", optarg); args.pa_flags |= FLAG_HAS_DURATION; args.pa_duration = duration; break; case 'm': args.pa_flags |= FLAG_DO_ANNOTATE; args.pa_plugin = PMCSTAT_PL_ANNOTATE; graphfilename = optarg; break; case 'E': /* log process exit */ do_logprocexit = !do_logprocexit; args.pa_required |= (FLAG_HAS_PROCESS_PMCS | FLAG_HAS_COUNTING_PMCS | FLAG_HAS_OUTPUT_LOGFILE); break; case 'M': /* mapfile */ args.pa_mapfilename = optarg; break; case 'N': do_callchain = !do_callchain; args.pa_required |= FLAG_HAS_SAMPLING_PMCS; break; case 'p': /* process virtual counting PMC */ case 's': /* system-wide counting PMC */ case 'P': /* process virtual sampling PMC */ case 'S': /* system-wide sampling PMC */ if ((ev = malloc(sizeof(*ev))) == NULL) errx(EX_SOFTWARE, "ERROR: Out of memory."); switch (option) { case 'p': ev->ev_mode = PMC_MODE_TC; break; case 's': ev->ev_mode = PMC_MODE_SC; break; case 'P': ev->ev_mode = PMC_MODE_TS; break; case 'S': ev->ev_mode = PMC_MODE_SS; break; } if (option == 'P' || option == 'p') { args.pa_flags |= FLAG_HAS_PROCESS_PMCS; args.pa_required |= (FLAG_HAS_COMMANDLINE | FLAG_HAS_TARGET); } if (option == 'P' || option == 'S') { args.pa_flags |= FLAG_HAS_SAMPLING_PMCS; args.pa_required |= (FLAG_HAS_PIPE | FLAG_HAS_OUTPUT_LOGFILE); } if (option == 'p' || option == 's') args.pa_flags |= FLAG_HAS_COUNTING_PMCS; if (option == 's' || option == 'S') args.pa_flags |= FLAG_HAS_SYSTEM_PMCS; ev->ev_spec = strdup(optarg); if (option == 'S' || option == 'P') ev->ev_count = current_sampling_count; else ev->ev_count = -1; if (option == 'S' || option == 's') ev->ev_cpu = CPU_FFS(&cpumask) - 1; else ev->ev_cpu = PMC_CPU_ANY; ev->ev_flags = 0; if (do_callchain) ev->ev_flags |= PMC_F_CALLCHAIN; if (do_descendants) ev->ev_flags |= PMC_F_DESCENDANTS; if (do_logprocexit) ev->ev_flags |= PMC_F_LOG_PROCEXIT; if (do_logproccsw) ev->ev_flags |= PMC_F_LOG_PROCCSW; ev->ev_cumulative = use_cumulative_counts; ev->ev_saved = 0LL; ev->ev_pmcid = PMC_ID_INVALID; /* extract event name */ c = strcspn(optarg, ", \t"); ev->ev_name = malloc(c + 1); (void) strncpy(ev->ev_name, optarg, c); *(ev->ev_name + c) = '\0'; STAILQ_INSERT_TAIL(&args.pa_events, ev, ev_next); if (option == 's' || option == 'S') { CPU_CLR(ev->ev_cpu, &cpumask); pmcstat_clone_event_descriptor(ev, &cpumask); CPU_SET(ev->ev_cpu, &cpumask); } break; case 'n': /* sampling count */ current_sampling_count = strtol(optarg, &end, 0); if (*end != '\0' || current_sampling_count <= 0) errx(EX_USAGE, "ERROR: Illegal count value \"%s\".", optarg); args.pa_required |= FLAG_HAS_SAMPLING_PMCS; break; case 'o': /* outputfile */ if (args.pa_printfile != NULL && args.pa_printfile != stdout && args.pa_printfile != stderr) (void) fclose(args.pa_printfile); if ((args.pa_printfile = fopen(optarg, "w")) == NULL) errx(EX_OSERR, "ERROR: cannot open \"%s\" for writing.", optarg); args.pa_flags |= FLAG_DO_PRINT; break; case 'O': /* sampling output */ if (args.pa_outputpath) errx(EX_USAGE, "ERROR: option -O may only be specified once."); args.pa_outputpath = optarg; args.pa_flags |= FLAG_HAS_OUTPUT_LOGFILE; break; case 'q': /* quiet mode */ args.pa_verbosity = 0; break; case 'r': /* root FS path */ args.pa_fsroot = optarg; break; case 'R': /* read an existing log file */ if (args.pa_inputpath != NULL) errx(EX_USAGE, "ERROR: option -R may only be specified once."); args.pa_inputpath = optarg; if (args.pa_printfile == stderr) args.pa_printfile = stdout; args.pa_flags |= FLAG_READ_LOGFILE; break; case 't': /* target pid or process name */ pmcstat_find_targets(optarg); args.pa_flags |= FLAG_HAS_TARGET; args.pa_required |= FLAG_HAS_PROCESS_PMCS; break; case 'T': /* top mode */ args.pa_flags |= FLAG_DO_TOP; args.pa_plugin = PMCSTAT_PL_CALLGRAPH; args.pa_ctdumpinstr = 0; args.pa_mergepmc = 1; if (args.pa_printfile == stderr) args.pa_printfile = stdout; break; case 'v': /* verbose */ args.pa_verbosity++; break; case 'w': /* wait interval */ interval = strtod(optarg, &end); if (*end != '\0' || interval <= 0) errx(EX_USAGE, "ERROR: Illegal wait interval value \"%s\".", optarg); args.pa_flags |= FLAG_HAS_WAIT_INTERVAL; args.pa_interval = interval; break; case 'W': /* toggle LOG_CSW */ do_logproccsw = !do_logproccsw; args.pa_required |= (FLAG_HAS_PROCESS_PMCS | FLAG_HAS_COUNTING_PMCS | FLAG_HAS_OUTPUT_LOGFILE); break; case 'z': graphdepth = strtod(optarg, &end); if (*end != '\0' || graphdepth <= 0) errx(EX_USAGE, "ERROR: Illegal callchain depth \"%s\".", optarg); args.pa_graphdepth = graphdepth; args.pa_required |= FLAG_DO_CALLGRAPHS; break; case '?': default: pmcstat_show_usage(); break; } args.pa_argc = (argc -= optind); args.pa_argv = (argv += optind); /* If we read from logfile and no specified CPU mask use * the maximum CPU count. */ if ((args.pa_flags & FLAG_READ_LOGFILE) && (args.pa_flags & FLAGS_HAS_CPUMASK) == 0) CPU_FILL(&cpumask); args.pa_cpumask = cpumask; /* For selecting CPUs using -R. */ if (argc) /* command line present */ args.pa_flags |= FLAG_HAS_COMMANDLINE; if (args.pa_flags & (FLAG_DO_GPROF | FLAG_DO_CALLGRAPHS | FLAG_DO_ANNOTATE | FLAG_DO_TOP)) args.pa_flags |= FLAG_DO_ANALYSIS; /* * Check invocation syntax. */ /* disallow -O and -R together */ if (args.pa_outputpath && args.pa_inputpath) errx(EX_USAGE, "ERROR: options -O and -R are mutually exclusive."); /* disallow -T and -l together */ if ((args.pa_flags & FLAG_HAS_DURATION) && (args.pa_flags & FLAG_DO_TOP)) errx(EX_USAGE, "ERROR: options -T and -l are mutually " "exclusive."); /* -a and -m require -R */ if (args.pa_flags & FLAG_DO_ANNOTATE && args.pa_inputpath == NULL) errx(EX_USAGE, "ERROR: option %s requires an input file", args.pa_plugin == PMCSTAT_PL_ANNOTATE ? "-m" : "-a"); /* -m option is not allowed combined with -g or -G. */ if (args.pa_flags & FLAG_DO_ANNOTATE && args.pa_flags & (FLAG_DO_GPROF | FLAG_DO_CALLGRAPHS)) errx(EX_USAGE, "ERROR: option -m and -g | -G are mutually exclusive"); if (args.pa_flags & FLAG_READ_LOGFILE) { errmsg = NULL; if (args.pa_flags & FLAG_HAS_COMMANDLINE) errmsg = "a command line specification"; else if (args.pa_flags & FLAG_HAS_TARGET) errmsg = "option -t"; else if (!STAILQ_EMPTY(&args.pa_events)) errmsg = "a PMC event specification"; if (errmsg) errx(EX_USAGE, "ERROR: option -R may not be used with %s.", errmsg); } else if (STAILQ_EMPTY(&args.pa_events)) /* All other uses require a PMC spec. */ pmcstat_show_usage(); /* check for -t pid without a process PMC spec */ if ((args.pa_required & FLAG_HAS_TARGET) && (args.pa_flags & FLAG_HAS_PROCESS_PMCS) == 0) errx(EX_USAGE, "ERROR: option -t requires a process mode PMC to be specified." ); /* check for process-mode options without a command or -t pid */ if ((args.pa_required & FLAG_HAS_PROCESS_PMCS) && (args.pa_flags & (FLAG_HAS_COMMANDLINE | FLAG_HAS_TARGET)) == 0) errx(EX_USAGE, "ERROR: options -d, -E, -p, -P, and -W require a command line or target process." ); /* check for -p | -P without a target process of some sort */ if ((args.pa_required & (FLAG_HAS_COMMANDLINE | FLAG_HAS_TARGET)) && (args.pa_flags & (FLAG_HAS_COMMANDLINE | FLAG_HAS_TARGET)) == 0) errx(EX_USAGE, "ERROR: options -P and -p require a target process or a command line." ); /* check for process-mode options without a process-mode PMC */ if ((args.pa_required & FLAG_HAS_PROCESS_PMCS) && (args.pa_flags & FLAG_HAS_PROCESS_PMCS) == 0) errx(EX_USAGE, "ERROR: options -d, -E, and -W require a process mode PMC to be specified." ); /* check for -c cpu with no system mode PMCs or logfile. */ if ((args.pa_required & FLAG_HAS_SYSTEM_PMCS) && (args.pa_flags & FLAG_HAS_SYSTEM_PMCS) == 0 && (args.pa_flags & FLAG_READ_LOGFILE) == 0) errx(EX_USAGE, "ERROR: option -c requires at least one system mode PMC to be specified." ); /* check for counting mode options without a counting PMC */ if ((args.pa_required & FLAG_HAS_COUNTING_PMCS) && (args.pa_flags & FLAG_HAS_COUNTING_PMCS) == 0) errx(EX_USAGE, "ERROR: options -C, -W and -o require at least one counting mode PMC to be specified." ); /* check for sampling mode options without a sampling PMC spec */ if ((args.pa_required & FLAG_HAS_SAMPLING_PMCS) && (args.pa_flags & FLAG_HAS_SAMPLING_PMCS) == 0) errx(EX_USAGE, "ERROR: options -N, -n and -O require at least one sampling mode PMC to be specified." ); /* check if -g/-G/-m/-T are being used correctly */ if ((args.pa_flags & FLAG_DO_ANALYSIS) && !(args.pa_flags & (FLAG_HAS_SAMPLING_PMCS|FLAG_READ_LOGFILE))) errx(EX_USAGE, "ERROR: options -g/-G/-m/-T require sampling PMCs or -R to be specified." + ); + + /* check if -e was specified without -g */ + if ((args.pa_flags & FLAG_DO_WIDE_GPROF_HC) && + !(args.pa_flags & FLAG_DO_GPROF)) + errx(EX_USAGE, +"ERROR: option -e requires gprof mode to be specified." ); /* check if -O was spuriously specified */ if ((args.pa_flags & FLAG_HAS_OUTPUT_LOGFILE) && (args.pa_required & FLAG_HAS_OUTPUT_LOGFILE) == 0) errx(EX_USAGE, "ERROR: option -O is used only with options -E, -P, -S and -W." ); /* -k kernel path require -g/-G/-m/-T or -R */ if ((args.pa_flags & FLAG_HAS_KERNELPATH) && (args.pa_flags & FLAG_DO_ANALYSIS) == 0 && (args.pa_flags & FLAG_READ_LOGFILE) == 0) errx(EX_USAGE, "ERROR: option -k is only used with -g/-R/-m/-T."); /* -D only applies to gprof output mode (-g) */ if ((args.pa_flags & FLAG_HAS_SAMPLESDIR) && (args.pa_flags & FLAG_DO_GPROF) == 0) errx(EX_USAGE, "ERROR: option -D is only used with -g."); /* -M mapfile requires -g or -R */ if (args.pa_mapfilename != NULL && (args.pa_flags & FLAG_DO_GPROF) == 0 && (args.pa_flags & FLAG_READ_LOGFILE) == 0) errx(EX_USAGE, "ERROR: option -M is only used with -g/-R."); /* * Disallow textual output of sampling PMCs if counting PMCs * have also been asked for, mostly because the combined output * is difficult to make sense of. */ if ((args.pa_flags & FLAG_HAS_COUNTING_PMCS) && (args.pa_flags & FLAG_HAS_SAMPLING_PMCS) && ((args.pa_flags & FLAG_HAS_OUTPUT_LOGFILE) == 0)) errx(EX_USAGE, "ERROR: option -O is required if counting and sampling PMCs are specified together." ); /* * Check if 'kerneldir' refers to a file rather than a * directory. If so, use `dirname path` to determine the * kernel directory. */ (void) snprintf(buffer, sizeof(buffer), "%s%s", args.pa_fsroot, args.pa_kernel); if (stat(buffer, &sb) < 0) err(EX_OSERR, "ERROR: Cannot locate kernel \"%s\"", buffer); if (!S_ISREG(sb.st_mode) && !S_ISDIR(sb.st_mode)) errx(EX_USAGE, "ERROR: \"%s\": Unsupported file type.", buffer); if (!S_ISDIR(sb.st_mode)) { tmp = args.pa_kernel; args.pa_kernel = strdup(dirname(args.pa_kernel)); free(tmp); (void) snprintf(buffer, sizeof(buffer), "%s%s", args.pa_fsroot, args.pa_kernel); if (stat(buffer, &sb) < 0) err(EX_OSERR, "ERROR: Cannot stat \"%s\"", buffer); if (!S_ISDIR(sb.st_mode)) errx(EX_USAGE, "ERROR: \"%s\" is not a directory.", buffer); } /* * If we have a callgraph be created, select the outputfile. */ if (args.pa_flags & FLAG_DO_CALLGRAPHS) { if (strcmp(graphfilename, "-") == 0) args.pa_graphfile = args.pa_printfile; else { args.pa_graphfile = fopen(graphfilename, "w"); if (args.pa_graphfile == NULL) err(EX_OSERR, "ERROR: cannot open \"%s\" for writing", graphfilename); } } if (args.pa_flags & FLAG_DO_ANNOTATE) { args.pa_graphfile = fopen(graphfilename, "w"); if (args.pa_graphfile == NULL) err(EX_OSERR, "ERROR: cannot open \"%s\" for writing", graphfilename); } /* if we've been asked to process a log file, skip init */ if ((args.pa_flags & FLAG_READ_LOGFILE) == 0) { if (pmc_init() < 0) err(EX_UNAVAILABLE, "ERROR: Initialization of the pmc(3) library failed" ); if ((npmc = pmc_npmc(0)) < 0) /* assume all CPUs are identical */ err(EX_OSERR, "ERROR: Cannot determine the number of PMCs on CPU %d", 0); } /* Allocate a kqueue */ if ((pmcstat_kq = kqueue()) < 0) err(EX_OSERR, "ERROR: Cannot allocate kqueue"); /* Setup the logfile as the source. */ if (args.pa_flags & FLAG_READ_LOGFILE) { /* * Print the log in textual form if we haven't been * asked to generate profiling information. */ if ((args.pa_flags & FLAG_DO_ANALYSIS) == 0) args.pa_flags |= FLAG_DO_PRINT; pmcstat_initialize_logging(); rfd = pmcstat_open_log(args.pa_inputpath, PMCSTAT_OPEN_FOR_READ); if ((args.pa_logparser = pmclog_open(rfd)) == NULL) err(EX_OSERR, "ERROR: Cannot create parser"); if (fcntl(rfd, F_SETFL, O_NONBLOCK) < 0) err(EX_OSERR, "ERROR: fcntl(2) failed"); EV_SET(&kev, rfd, EVFILT_READ, EV_ADD, 0, 0, NULL); if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) err(EX_OSERR, "ERROR: Cannot register kevent"); } /* * Configure the specified log file or setup a default log * consumer via a pipe. */ if (args.pa_required & FLAG_HAS_OUTPUT_LOGFILE) { if (args.pa_outputpath) args.pa_logfd = pmcstat_open_log(args.pa_outputpath, PMCSTAT_OPEN_FOR_WRITE); else { /* * process the log on the fly by reading it in * through a pipe. */ if (pipe(pipefd) < 0) err(EX_OSERR, "ERROR: pipe(2) failed"); if (fcntl(pipefd[READPIPEFD], F_SETFL, O_NONBLOCK) < 0) err(EX_OSERR, "ERROR: fcntl(2) failed"); EV_SET(&kev, pipefd[READPIPEFD], EVFILT_READ, EV_ADD, 0, 0, NULL); if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) err(EX_OSERR, "ERROR: Cannot register kevent"); args.pa_logfd = pipefd[WRITEPIPEFD]; args.pa_flags |= FLAG_HAS_PIPE; if ((args.pa_flags & FLAG_DO_TOP) == 0) args.pa_flags |= FLAG_DO_PRINT; args.pa_logparser = pmclog_open(pipefd[READPIPEFD]); } if (pmc_configure_logfile(args.pa_logfd) < 0) err(EX_OSERR, "ERROR: Cannot configure log file"); } /* remember to check for driver errors if we are sampling or logging */ check_driver_stats = (args.pa_flags & FLAG_HAS_SAMPLING_PMCS) || (args.pa_flags & FLAG_HAS_OUTPUT_LOGFILE); /* if (args.pa_flags & FLAG_READ_LOGFILE) { * Allocate PMCs. */ STAILQ_FOREACH(ev, &args.pa_events, ev_next) { if (pmc_allocate(ev->ev_spec, ev->ev_mode, ev->ev_flags, ev->ev_cpu, &ev->ev_pmcid) < 0) err(EX_OSERR, "ERROR: Cannot allocate %s-mode pmc with specification \"%s\"", PMC_IS_SYSTEM_MODE(ev->ev_mode) ? "system" : "process", ev->ev_spec); if (PMC_IS_SAMPLING_MODE(ev->ev_mode) && pmc_set(ev->ev_pmcid, ev->ev_count) < 0) err(EX_OSERR, "ERROR: Cannot set sampling count for PMC \"%s\"", ev->ev_name); } /* compute printout widths */ STAILQ_FOREACH(ev, &args.pa_events, ev_next) { int counter_width; int display_width; int header_width; (void) pmc_width(ev->ev_pmcid, &counter_width); header_width = strlen(ev->ev_name) + 2; /* prefix '%c/' */ display_width = (int) floor(counter_width / 3.32193) + 1; if (PMC_IS_SYSTEM_MODE(ev->ev_mode)) header_width += 3; /* 2 digit CPU number + '/' */ if (header_width > display_width) { ev->ev_fieldskip = 0; ev->ev_fieldwidth = header_width; } else { ev->ev_fieldskip = display_width - header_width; ev->ev_fieldwidth = display_width; } } /* * If our output is being set to a terminal, register a handler * for window size changes. */ if (isatty(fileno(args.pa_printfile))) { if (ioctl(fileno(args.pa_printfile), TIOCGWINSZ, &ws) < 0) err(EX_OSERR, "ERROR: Cannot determine window size"); pmcstat_displayheight = ws.ws_row - 1; pmcstat_displaywidth = ws.ws_col - 1; EV_SET(&kev, SIGWINCH, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) err(EX_OSERR, "ERROR: Cannot register kevent for SIGWINCH"); args.pa_toptty = 1; } /* * Listen to key input in top mode. */ if (args.pa_flags & FLAG_DO_TOP) { EV_SET(&kev, fileno(stdin), EVFILT_READ, EV_ADD, 0, 0, NULL); if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) err(EX_OSERR, "ERROR: Cannot register kevent"); } EV_SET(&kev, SIGINT, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) err(EX_OSERR, "ERROR: Cannot register kevent for SIGINT"); EV_SET(&kev, SIGIO, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) err(EX_OSERR, "ERROR: Cannot register kevent for SIGIO"); /* * An exec() failure of a forked child is signalled by the * child sending the parent a SIGCHLD. We don't register an * actual signal handler for SIGCHLD, but instead use our * kqueue to pick up the signal. */ EV_SET(&kev, SIGCHLD, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) err(EX_OSERR, "ERROR: Cannot register kevent for SIGCHLD"); /* * Setup a timer if we have counting mode PMCs needing to be printed or * top mode plugin is active. */ if (((args.pa_flags & FLAG_HAS_COUNTING_PMCS) && (args.pa_required & FLAG_HAS_OUTPUT_LOGFILE) == 0) || (args.pa_flags & FLAG_DO_TOP)) { EV_SET(&kev, 0, EVFILT_TIMER, EV_ADD, 0, args.pa_interval * 1000, NULL); if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) err(EX_OSERR, "ERROR: Cannot register kevent for timer"); } /* * Setup a duration timer if we have sampling mode PMCs and * a duration time is set */ if ((args.pa_flags & FLAG_HAS_SAMPLING_PMCS) && (args.pa_flags & FLAG_HAS_DURATION)) { EV_SET(&kev, 0, EVFILT_TIMER, EV_ADD, 0, args.pa_duration * 1000, NULL); if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) err(EX_OSERR, "ERROR: Cannot register kevent for " "time duration"); } /* attach PMCs to the target process, starting it if specified */ if (args.pa_flags & FLAG_HAS_COMMANDLINE) pmcstat_create_process(); if (check_driver_stats && pmc_get_driver_stats(&ds_start) < 0) err(EX_OSERR, "ERROR: Cannot retrieve driver statistics"); /* Attach process pmcs to the target process. */ if (args.pa_flags & (FLAG_HAS_TARGET | FLAG_HAS_COMMANDLINE)) { if (SLIST_EMPTY(&args.pa_targets)) errx(EX_DATAERR, "ERROR: No matching target processes."); if (args.pa_flags & FLAG_HAS_PROCESS_PMCS) pmcstat_attach_pmcs(); if (pmcstat_kvm) { kvm_close(pmcstat_kvm); pmcstat_kvm = NULL; } } /* start the pmcs */ pmcstat_start_pmcs(); /* start the (commandline) process if needed */ if (args.pa_flags & FLAG_HAS_COMMANDLINE) pmcstat_start_process(); /* initialize logging */ pmcstat_initialize_logging(); /* Handle SIGINT using the kqueue loop */ sa.sa_handler = SIG_IGN; sa.sa_flags = 0; (void) sigemptyset(&sa.sa_mask); if (sigaction(SIGINT, &sa, NULL) < 0) err(EX_OSERR, "ERROR: Cannot install signal handler"); /* * Setup the top mode display. */ if (args.pa_flags & FLAG_DO_TOP) { args.pa_flags &= ~FLAG_DO_PRINT; if (args.pa_toptty) { /* * Init ncurses. */ initscr(); if(has_colors() == TRUE) { args.pa_topcolor = 1; start_color(); use_default_colors(); pair_content(0, &cf, &cb); init_pair(1, COLOR_RED, cb); init_pair(2, COLOR_YELLOW, cb); init_pair(3, COLOR_GREEN, cb); } cbreak(); noecho(); nonl(); nodelay(stdscr, 1); intrflush(stdscr, FALSE); keypad(stdscr, TRUE); clear(); /* Get terminal width / height with ncurses. */ getmaxyx(stdscr, pmcstat_displayheight, pmcstat_displaywidth); pmcstat_displayheight--; pmcstat_displaywidth--; atexit(pmcstat_topexit); } } /* * loop till either the target process (if any) exits, or we * are killed by a SIGINT or we reached the time duration. */ runstate = PMCSTAT_RUNNING; do_print = do_read = 0; do { if ((c = kevent(pmcstat_kq, NULL, 0, &kev, 1, NULL)) <= 0) { if (errno != EINTR) err(EX_OSERR, "ERROR: kevent failed"); else continue; } if (kev.flags & EV_ERROR) errc(EX_OSERR, kev.data, "ERROR: kevent failed"); switch (kev.filter) { case EVFILT_PROC: /* target has exited */ runstate = pmcstat_close_log(); do_print = 1; break; case EVFILT_READ: /* log file data is present */ if (kev.ident == (unsigned)fileno(stdin) && (args.pa_flags & FLAG_DO_TOP)) { if (pmcstat_keypress_log()) runstate = pmcstat_close_log(); } else { do_read = 0; runstate = pmcstat_process_log(); } break; case EVFILT_SIGNAL: if (kev.ident == SIGCHLD) { /* * The child process sends us a * SIGCHLD if its exec() failed. We * wait for it to exit and then exit * ourselves. */ (void) wait(&c); runstate = PMCSTAT_FINISHED; } else if (kev.ident == SIGIO) { /* * We get a SIGIO if a PMC loses all * of its targets, or if logfile * writes encounter an error. */ runstate = pmcstat_close_log(); do_print = 1; /* print PMCs at exit */ } else if (kev.ident == SIGINT) { /* Kill the child process if we started it */ if (args.pa_flags & FLAG_HAS_COMMANDLINE) pmcstat_kill_process(); runstate = pmcstat_close_log(); } else if (kev.ident == SIGWINCH) { if (ioctl(fileno(args.pa_printfile), TIOCGWINSZ, &ws) < 0) err(EX_OSERR, "ERROR: Cannot determine window size"); pmcstat_displayheight = ws.ws_row - 1; pmcstat_displaywidth = ws.ws_col - 1; } else assert(0); break; case EVFILT_TIMER: /* time duration reached, exit */ if (args.pa_flags & FLAG_HAS_DURATION) { runstate = PMCSTAT_FINISHED; break; } /* print out counting PMCs */ if ((args.pa_flags & FLAG_DO_TOP) && pmc_flush_logfile() == 0) do_read = 1; do_print = 1; break; } if (do_print && !do_read) { if ((args.pa_required & FLAG_HAS_OUTPUT_LOGFILE) == 0) { pmcstat_print_pmcs(); if (runstate == PMCSTAT_FINISHED && /* final newline */ (args.pa_flags & FLAG_DO_PRINT) == 0) (void) fprintf(args.pa_printfile, "\n"); } if (args.pa_flags & FLAG_DO_TOP) pmcstat_display_log(); do_print = 0; } } while (runstate != PMCSTAT_FINISHED); if ((args.pa_flags & FLAG_DO_TOP) && args.pa_toptty) { pmcstat_topexit(); args.pa_toptty = 0; } /* flush any pending log entries */ if (args.pa_flags & (FLAG_HAS_OUTPUT_LOGFILE | FLAG_HAS_PIPE)) pmc_close_logfile(); pmcstat_cleanup(); free(args.pa_kernel); /* check if the driver lost any samples or events */ if (check_driver_stats) { if (pmc_get_driver_stats(&ds_end) < 0) err(EX_OSERR, "ERROR: Cannot retrieve driver statistics"); if (ds_start.pm_intr_bufferfull != ds_end.pm_intr_bufferfull && args.pa_verbosity > 0) warnx( "WARNING: sampling was paused at least %u time%s.\n" "Please consider tuning the \"kern.hwpmc.nsamples\" tunable.", ds_end.pm_intr_bufferfull - ds_start.pm_intr_bufferfull, ((ds_end.pm_intr_bufferfull - ds_start.pm_intr_bufferfull) != 1) ? "s" : "" ); if (ds_start.pm_buffer_requests_failed != ds_end.pm_buffer_requests_failed && args.pa_verbosity > 0) warnx( "WARNING: at least %u event%s were discarded while running.\n" "Please consider tuning the \"kern.hwpmc.nbuffers\" tunable.", ds_end.pm_buffer_requests_failed - ds_start.pm_buffer_requests_failed, ((ds_end.pm_buffer_requests_failed - ds_start.pm_buffer_requests_failed) != 1) ? "s" : "" ); } exit(EX_OK); } Index: head/usr.sbin/pmcstat/pmcstat.h =================================================================== --- head/usr.sbin/pmcstat/pmcstat.h (revision 291015) +++ head/usr.sbin/pmcstat/pmcstat.h (revision 291016) @@ -1,186 +1,187 @@ /*- * Copyright (c) 2005-2007, Joseph Koshy * Copyright (c) 2007 The FreeBSD Foundation * 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. * * $FreeBSD$ */ #ifndef _PMCSTAT_H_ #define _PMCSTAT_H_ #include #define FLAG_HAS_TARGET 0x00000001 /* process target */ #define FLAG_HAS_WAIT_INTERVAL 0x00000002 /* -w secs */ #define FLAG_HAS_OUTPUT_LOGFILE 0x00000004 /* -O file or pipe */ #define FLAG_HAS_COMMANDLINE 0x00000008 /* command */ #define FLAG_HAS_SAMPLING_PMCS 0x00000010 /* -S or -P */ #define FLAG_HAS_COUNTING_PMCS 0x00000020 /* -s or -p */ #define FLAG_HAS_PROCESS_PMCS 0x00000040 /* -P or -p */ #define FLAG_HAS_SYSTEM_PMCS 0x00000080 /* -S or -s */ #define FLAG_HAS_PIPE 0x00000100 /* implicit log */ #define FLAG_READ_LOGFILE 0x00000200 /* -R file */ #define FLAG_DO_GPROF 0x00000400 /* -g */ #define FLAG_HAS_SAMPLESDIR 0x00000800 /* -D dir */ #define FLAG_HAS_KERNELPATH 0x00001000 /* -k kernel */ #define FLAG_DO_PRINT 0x00002000 /* -o */ #define FLAG_DO_CALLGRAPHS 0x00004000 /* -G or -F */ #define FLAG_DO_ANNOTATE 0x00008000 /* -m */ #define FLAG_DO_TOP 0x00010000 /* -T */ #define FLAG_DO_ANALYSIS 0x00020000 /* -g or -G or -m or -T */ #define FLAGS_HAS_CPUMASK 0x00040000 /* -c */ #define FLAG_HAS_DURATION 0x00080000 /* -l secs */ +#define FLAG_DO_WIDE_GPROF_HC 0x00100000 /* -e */ #define DEFAULT_SAMPLE_COUNT 65536 #define DEFAULT_WAIT_INTERVAL 5.0 #define DEFAULT_DISPLAY_HEIGHT 256 /* file virtual height */ #define DEFAULT_DISPLAY_WIDTH 1024 /* file virtual width */ #define DEFAULT_BUFFER_SIZE 4096 #define DEFAULT_CALLGRAPH_DEPTH 16 #define PRINT_HEADER_PREFIX "# " #define READPIPEFD 0 #define WRITEPIPEFD 1 #define NPIPEFD 2 #define NSOCKPAIRFD 2 #define PARENTSOCKET 0 #define CHILDSOCKET 1 #define PMCSTAT_OPEN_FOR_READ 0 #define PMCSTAT_OPEN_FOR_WRITE 1 #define PMCSTAT_DEFAULT_NW_HOST "localhost" #define PMCSTAT_DEFAULT_NW_PORT "9000" #define PMCSTAT_NHASH 256 #define PMCSTAT_HASH_MASK 0xFF #define PMCSTAT_LDD_COMMAND "/usr/bin/ldd" #define PMCSTAT_PRINT_ENTRY(T,...) do { \ (void) fprintf(args.pa_printfile, "%-9s", T); \ (void) fprintf(args.pa_printfile, " " __VA_ARGS__); \ (void) fprintf(args.pa_printfile, "\n"); \ } while (0) #define PMCSTAT_PL_NONE 0 #define PMCSTAT_PL_CALLGRAPH 1 #define PMCSTAT_PL_GPROF 2 #define PMCSTAT_PL_ANNOTATE 3 #define PMCSTAT_PL_CALLTREE 4 #define PMCSTAT_PL_ANNOTATE_CG 5 #define PMCSTAT_TOP_DELTA 0 #define PMCSTAT_TOP_ACCUM 1 #define min(A,B) ((A) < (B) ? (A) : (B)) #define max(A,B) ((A) > (B) ? (A) : (B)) enum pmcstat_state { PMCSTAT_FINISHED = 0, PMCSTAT_EXITING = 1, PMCSTAT_RUNNING = 2 }; struct pmcstat_ev { STAILQ_ENTRY(pmcstat_ev) ev_next; int ev_count; /* associated count if in sampling mode */ uint32_t ev_cpu; /* cpus for this event */ int ev_cumulative; /* show cumulative counts */ int ev_flags; /* PMC_F_* */ int ev_fieldskip; /* #leading spaces */ int ev_fieldwidth; /* print width */ enum pmc_mode ev_mode; /* desired mode */ char *ev_name; /* (derived) event name */ pmc_id_t ev_pmcid; /* allocated ID */ pmc_value_t ev_saved; /* for incremental counts */ char *ev_spec; /* event specification */ }; struct pmcstat_target { SLIST_ENTRY(pmcstat_target) pt_next; pid_t pt_pid; }; struct pmcstat_args { int pa_flags; /* argument flags */ int pa_required; /* required features */ int pa_pplugin; /* pre-processing plugin */ int pa_plugin; /* analysis plugin */ int pa_verbosity; /* verbosity level */ FILE *pa_printfile; /* where to send printed output */ int pa_logfd; /* output log file */ char *pa_inputpath; /* path to input log */ char *pa_outputpath; /* path to output log */ void *pa_logparser; /* log file parser */ const char *pa_fsroot; /* FS root where executables reside */ char *pa_kernel; /* pathname of the kernel */ const char *pa_samplesdir; /* directory for profile files */ const char *pa_mapfilename;/* mapfile name */ FILE *pa_graphfile; /* where to send the callgraph */ int pa_graphdepth; /* print depth for callgraphs */ double pa_interval; /* printing interval in seconds */ cpuset_t pa_cpumask; /* filter for CPUs analysed */ int pa_ctdumpinstr; /* dump instructions with calltree */ int pa_topmode; /* delta or accumulative */ int pa_toptty; /* output to tty or file */ int pa_topcolor; /* terminal support color */ int pa_mergepmc; /* merge PMC with same name */ double pa_duration; /* time duration */ int pa_argc; char **pa_argv; STAILQ_HEAD(, pmcstat_ev) pa_events; SLIST_HEAD(, pmcstat_target) pa_targets; }; extern int pmcstat_displayheight; /* current terminal height */ extern int pmcstat_displaywidth; /* current terminal width */ extern struct pmcstat_args args; /* command line args */ /* Function prototypes */ void pmcstat_attach_pmcs(void); void pmcstat_cleanup(void); int pmcstat_close_log(void); void pmcstat_create_process(void); void pmcstat_find_targets(const char *_arg); void pmcstat_initialize_logging(void); void pmcstat_kill_process(void); int pmcstat_open_log(const char *_p, int _mode); void pmcstat_print_counters(void); void pmcstat_print_headers(void); void pmcstat_print_pmcs(void); void pmcstat_show_usage(void); void pmcstat_shutdown_logging(void); void pmcstat_start_pmcs(void); void pmcstat_start_process(void); int pmcstat_process_log(void); int pmcstat_keypress_log(void); void pmcstat_display_log(void); void pmcstat_pluginconfigure_log(char *_opt); void pmcstat_topexit(void); #endif /* _PMCSTAT_H_ */