diff options
Diffstat (limited to 'usr.sbin/pmcstat')
-rw-r--r-- | usr.sbin/pmcstat/Makefile | 14 | ||||
-rw-r--r-- | usr.sbin/pmcstat/Makefile.depend | 23 | ||||
-rw-r--r-- | usr.sbin/pmcstat/pmcpl_annotate.c | 111 | ||||
-rw-r--r-- | usr.sbin/pmcstat/pmcpl_annotate.h | 41 | ||||
-rw-r--r-- | usr.sbin/pmcstat/pmcpl_annotate_cg.c | 127 | ||||
-rw-r--r-- | usr.sbin/pmcstat/pmcpl_annotate_cg.h | 42 | ||||
-rw-r--r-- | usr.sbin/pmcstat/pmcpl_callgraph.c | 687 | ||||
-rw-r--r-- | usr.sbin/pmcstat/pmcpl_callgraph.h | 67 | ||||
-rw-r--r-- | usr.sbin/pmcstat/pmcpl_calltree.c | 1197 | ||||
-rw-r--r-- | usr.sbin/pmcstat/pmcpl_calltree.h | 42 | ||||
-rw-r--r-- | usr.sbin/pmcstat/pmcpl_gprof.c | 566 | ||||
-rw-r--r-- | usr.sbin/pmcstat/pmcpl_gprof.h | 47 | ||||
-rw-r--r-- | usr.sbin/pmcstat/pmcstat.8 | 500 | ||||
-rw-r--r-- | usr.sbin/pmcstat/pmcstat.c | 1537 | ||||
-rw-r--r-- | usr.sbin/pmcstat/pmcstat.h | 187 | ||||
-rw-r--r-- | usr.sbin/pmcstat/pmcstat_log.c | 2238 | ||||
-rw-r--r-- | usr.sbin/pmcstat/pmcstat_log.h | 199 | ||||
-rw-r--r-- | usr.sbin/pmcstat/pmcstat_top.h | 75 |
18 files changed, 7700 insertions, 0 deletions
diff --git a/usr.sbin/pmcstat/Makefile b/usr.sbin/pmcstat/Makefile new file mode 100644 index 0000000..dc5a30a --- /dev/null +++ b/usr.sbin/pmcstat/Makefile @@ -0,0 +1,14 @@ +# +# $FreeBSD$ +# + +PROG= pmcstat +MAN= pmcstat.8 + +LIBADD= elf kvm pmc m ncursesw + +SRCS= pmcstat.c pmcstat.h pmcstat_log.c \ +pmcpl_callgraph.c pmcpl_gprof.c pmcpl_annotate.c \ +pmcpl_annotate_cg.c pmcpl_calltree.c + +.include <bsd.prog.mk> diff --git a/usr.sbin/pmcstat/Makefile.depend b/usr.sbin/pmcstat/Makefile.depend new file mode 100644 index 0000000..a3f7225 --- /dev/null +++ b/usr.sbin/pmcstat/Makefile.depend @@ -0,0 +1,23 @@ +# $FreeBSD$ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + gnu/lib/csu \ + gnu/lib/libgcc \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcompiler_rt \ + lib/libelf \ + lib/libkvm \ + lib/libpmc \ + lib/msun \ + lib/ncurses/ncursesw \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/usr.sbin/pmcstat/pmcpl_annotate.c b/usr.sbin/pmcstat/pmcpl_annotate.c new file mode 100644 index 0000000..802983c --- /dev/null +++ b/usr.sbin/pmcstat/pmcpl_annotate.c @@ -0,0 +1,111 @@ +/*- + * 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. + */ + +/* + * Transform a hwpmc(4) log into human readable form, and into + * gprof(1) compatible profiles. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/endian.h> +#include <sys/gmon.h> +#include <sys/imgact_aout.h> +#include <sys/imgact_elf.h> +#include <sys/mman.h> +#include <sys/pmc.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/wait.h> + +#include <netinet/in.h> + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <gelf.h> +#include <libgen.h> +#include <limits.h> +#include <netdb.h> +#include <pmc.h> +#include <pmclog.h> +#include <sysexits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "pmcstat.h" +#include "pmcstat_log.h" +#include "pmcpl_annotate.h" + +/* + * Record a callchain. + */ + +void +pmcpl_annotate_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_symbol *sym; + uintfptr_t newpc; + struct pmcstat_image *image; + + (void) pmcr; (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; + newpc = cc[0] - (map->ppm_lowpc + + (image->pi_vaddr - image->pi_start)); + sym = pmcstat_symbol_search(image, newpc); + if (sym == NULL) + return; + + fprintf(args.pa_graphfile, "%p %s 0x%jx 0x%jx\n", + (void *)cc[0], + pmcstat_string_unintern(sym->ps_name), + (uintmax_t)(sym->ps_start + + image->pi_vaddr), (uintmax_t)(sym->ps_end + + image->pi_vaddr)); +} diff --git a/usr.sbin/pmcstat/pmcpl_annotate.h b/usr.sbin/pmcstat/pmcpl_annotate.h new file mode 100644 index 0000000..482bcd4 --- /dev/null +++ b/usr.sbin/pmcstat/pmcpl_annotate.h @@ -0,0 +1,41 @@ +/*- + * 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_PL_ANNOTATE_H_ +#define _PMCSTAT_PL_ANNOTATE_H_ + +/* Function prototypes */ +void pmcpl_annotate_process( + struct pmcstat_process *pp, struct pmcstat_pmcrecord *pmcr, + uint32_t nsamples, uintfptr_t *cc, int usermode, uint32_t cpu); + +#endif /* _PMCSTAT_PL_ANNOTATE_H_ */ diff --git a/usr.sbin/pmcstat/pmcpl_annotate_cg.c b/usr.sbin/pmcstat/pmcpl_annotate_cg.c new file mode 100644 index 0000000..e90bda1 --- /dev/null +++ b/usr.sbin/pmcstat/pmcpl_annotate_cg.c @@ -0,0 +1,127 @@ +/*- + * Copyright (c) 2005-2007, Joseph Koshy + * Copyright (c) 2007 The FreeBSD Foundation + * Copyright (c) 2014, Adrian Chadd, Netflix Inc. + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/endian.h> +#include <sys/gmon.h> +#include <sys/imgact_aout.h> +#include <sys/imgact_elf.h> +#include <sys/mman.h> +#include <sys/pmc.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/wait.h> + +#include <netinet/in.h> + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <gelf.h> +#include <libgen.h> +#include <limits.h> +#include <netdb.h> +#include <pmc.h> +#include <pmclog.h> +#include <sysexits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "pmcstat.h" +#include "pmcstat_log.h" +#include "pmcpl_annotate_cg.h" + +/* + * Record a callchain. + */ + +void +pmcpl_annotate_cg_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_symbol *sym; + uintfptr_t newpc; + struct pmcstat_image *image; + int i; + char filename[PATH_MAX], funcname[PATH_MAX]; + unsigned sline; + + (void) pmcr; (void) nsamples; (void) usermode; (void) cpu; + + for (i = 0; i < (int) nsamples; i++) { + map = NULL; + sym = NULL; + image = NULL; + filename[0] = '\0'; + funcname[0] = '\0'; + sline = 0; + + map = pmcstat_process_find_map(usermode ? pp : pmcstat_kernproc, cc[i]); + if (map != NULL) { + assert(cc[i] >= map->ppm_lowpc && cc[i] < map->ppm_highpc); + image = map->ppm_image; + newpc = cc[i] - (map->ppm_lowpc + + (image->pi_vaddr - image->pi_start)); + sym = pmcstat_symbol_search(image, newpc); + } + + if (map != NULL && image != NULL && sym != NULL) { + (void) pmcstat_image_addr2line(image, cc[i], + filename, sizeof(filename), &sline, funcname, sizeof(funcname)); + } + + if (map != NULL && sym != NULL) { + fprintf(args.pa_graphfile, "%p %s %s:%d\n", + (void *)cc[i], + funcname, + filename, + sline); + } else { + fprintf(args.pa_graphfile, "%p <unknown> ??:0\n", + (void *) cc[i]); + } + } + fprintf(args.pa_graphfile, "--\n"); +} diff --git a/usr.sbin/pmcstat/pmcpl_annotate_cg.h b/usr.sbin/pmcstat/pmcpl_annotate_cg.h new file mode 100644 index 0000000..bd655f7 --- /dev/null +++ b/usr.sbin/pmcstat/pmcpl_annotate_cg.h @@ -0,0 +1,42 @@ +/*- + * Copyright (c) 2005-2007, Joseph Koshy + * Copyright (c) 2007 The FreeBSD Foundation + * Copyright (c) 2014, Adrian Chadd, Netflix Inc. + * 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_PL_ANNOTATE_CG_H_ +#define _PMCSTAT_PL_ANNOTATE_CG_H_ + +/* Function prototypes */ +void pmcpl_annotate_cg_process( + struct pmcstat_process *pp, struct pmcstat_pmcrecord *pmcr, + uint32_t nsamples, uintfptr_t *cc, int usermode, uint32_t cpu); + +#endif /* _PMCSTAT_PL_ANNOTATE_CG_H_ */ diff --git a/usr.sbin/pmcstat/pmcpl_callgraph.c b/usr.sbin/pmcstat/pmcpl_callgraph.c new file mode 100644 index 0000000..33998b5 --- /dev/null +++ b/usr.sbin/pmcstat/pmcpl_callgraph.c @@ -0,0 +1,687 @@ +/*- + * 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. + */ + +/* + * Transform a hwpmc(4) log into human readable form, and into + * gprof(1) compatible profiles. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/endian.h> +#include <sys/gmon.h> +#include <sys/imgact_aout.h> +#include <sys/imgact_elf.h> +#include <sys/mman.h> +#include <sys/pmc.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/wait.h> + +#include <netinet/in.h> + +#include <assert.h> +#include <curses.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <gelf.h> +#include <libgen.h> +#include <limits.h> +#include <netdb.h> +#include <pmc.h> +#include <pmclog.h> +#include <sysexits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "pmcstat.h" +#include "pmcstat_log.h" +#include "pmcstat_top.h" +#include "pmcpl_callgraph.h" + +/* Get the sample value in percent related to nsamples. */ +#define PMCPL_CG_COUNTP(a) \ + ((a)->pcg_count * 100.0 / nsamples) + +/* + * The toplevel CG nodes (i.e., with rank == 0) are placed in a hash table. + */ + +struct pmcstat_cgnode_hash_list pmcstat_cgnode_hash[PMCSTAT_NHASH]; +int pmcstat_cgnode_hash_count; + +static pmcstat_interned_string pmcstat_previous_filename_printed; + +static struct pmcstat_cgnode * +pmcstat_cgnode_allocate(struct pmcstat_image *image, uintfptr_t pc) +{ + struct pmcstat_cgnode *cg; + + if ((cg = malloc(sizeof(*cg))) == NULL) + err(EX_OSERR, "ERROR: Cannot allocate callgraph node"); + + cg->pcg_image = image; + cg->pcg_func = pc; + + cg->pcg_count = 0; + cg->pcg_nchildren = 0; + LIST_INIT(&cg->pcg_children); + + return (cg); +} + +/* + * Free a node and its children. + */ +static void +pmcstat_cgnode_free(struct pmcstat_cgnode *cg) +{ + struct pmcstat_cgnode *cgc, *cgtmp; + + LIST_FOREACH_SAFE(cgc, &cg->pcg_children, pcg_sibling, cgtmp) + pmcstat_cgnode_free(cgc); + free(cg); +} + +/* + * Look for a callgraph node associated with pmc `pmcid' in the global + * hash table that corresponds to the given `pc' value in the process + * `pp'. + */ +static struct pmcstat_cgnode * +pmcstat_cgnode_hash_lookup_pc(struct pmcstat_process *pp, pmc_id_t pmcid, + uintfptr_t pc, int usermode) +{ + struct pmcstat_pcmap *ppm; + struct pmcstat_symbol *sym; + struct pmcstat_image *image; + struct pmcstat_cgnode *cg; + struct pmcstat_cgnode_hash *h; + uintfptr_t loadaddress; + unsigned int i, hash; + + ppm = pmcstat_process_find_map(usermode ? pp : pmcstat_kernproc, pc); + if (ppm == NULL) + return (NULL); + + image = ppm->ppm_image; + + loadaddress = ppm->ppm_lowpc + image->pi_vaddr - image->pi_start; + pc -= loadaddress; /* Convert to an offset in the image. */ + + /* + * Try determine the function at this offset. If we can't + * find a function round leave the `pc' value alone. + */ + if ((sym = pmcstat_symbol_search(image, pc)) != NULL) + pc = sym->ps_start; + else + pmcstat_stats.ps_samples_unknown_function++; + + for (hash = i = 0; i < sizeof(uintfptr_t); i++) + hash += (pc >> i) & 0xFF; + + hash &= PMCSTAT_HASH_MASK; + + cg = NULL; + LIST_FOREACH(h, &pmcstat_cgnode_hash[hash], pch_next) + { + if (h->pch_pmcid != pmcid) + continue; + + cg = h->pch_cgnode; + + assert(cg != NULL); + + if (cg->pcg_image == image && cg->pcg_func == pc) + return (cg); + } + + /* + * We haven't seen this (pmcid, pc) tuple yet, so allocate a + * new callgraph node and a new hash table entry for it. + */ + cg = pmcstat_cgnode_allocate(image, pc); + if ((h = malloc(sizeof(*h))) == NULL) + err(EX_OSERR, "ERROR: Could not allocate callgraph node"); + + h->pch_pmcid = pmcid; + h->pch_cgnode = cg; + LIST_INSERT_HEAD(&pmcstat_cgnode_hash[hash], h, pch_next); + + pmcstat_cgnode_hash_count++; + + return (cg); +} + +/* + * Compare two callgraph nodes for sorting. + */ +static int +pmcstat_cgnode_compare(const void *a, const void *b) +{ + const struct pmcstat_cgnode *const *pcg1, *const *pcg2, *cg1, *cg2; + + pcg1 = (const struct pmcstat_cgnode *const *) a; + cg1 = *pcg1; + pcg2 = (const struct pmcstat_cgnode *const *) b; + cg2 = *pcg2; + + /* Sort in reverse order */ + if (cg1->pcg_count < cg2->pcg_count) + return (1); + if (cg1->pcg_count > cg2->pcg_count) + return (-1); + return (0); +} + +/* + * Find (allocating if a needed) a callgraph node in the given + * parent with the same (image, pcoffset) pair. + */ + +static struct pmcstat_cgnode * +pmcstat_cgnode_find(struct pmcstat_cgnode *parent, struct pmcstat_image *image, + uintfptr_t pcoffset) +{ + struct pmcstat_cgnode *child; + + LIST_FOREACH(child, &parent->pcg_children, pcg_sibling) { + if (child->pcg_image == image && + child->pcg_func == pcoffset) + return (child); + } + + /* + * Allocate a new structure. + */ + + child = pmcstat_cgnode_allocate(image, pcoffset); + + /* + * Link it into the parent. + */ + LIST_INSERT_HEAD(&parent->pcg_children, child, pcg_sibling); + parent->pcg_nchildren++; + + return (child); +} + +/* + * Print one callgraph node. The output format is: + * + * indentation %(parent's samples) #nsamples function@object + */ +static void +pmcstat_cgnode_print(struct pmcstat_cgnode *cg, int depth, uint32_t total) +{ + uint32_t n; + const char *space; + struct pmcstat_symbol *sym; + struct pmcstat_cgnode **sortbuffer, **cgn, *pcg; + + space = " "; + + if (depth > 0) + (void) fprintf(args.pa_graphfile, "%*s", depth, space); + + if (cg->pcg_count == total) + (void) fprintf(args.pa_graphfile, "100.0%% "); + else + (void) fprintf(args.pa_graphfile, "%05.2f%% ", + 100.0 * cg->pcg_count / total); + + n = fprintf(args.pa_graphfile, " [%u] ", cg->pcg_count); + + /* #samples is a 12 character wide field. */ + if (n < 12) + (void) fprintf(args.pa_graphfile, "%*s", 12 - n, space); + + if (depth > 0) + (void) fprintf(args.pa_graphfile, "%*s", depth, space); + + sym = pmcstat_symbol_search(cg->pcg_image, cg->pcg_func); + if (sym) + (void) fprintf(args.pa_graphfile, "%s", + pmcstat_string_unintern(sym->ps_name)); + else + (void) fprintf(args.pa_graphfile, "%p", + (void *) (cg->pcg_image->pi_vaddr + cg->pcg_func)); + + if (pmcstat_previous_filename_printed != + cg->pcg_image->pi_fullpath) { + pmcstat_previous_filename_printed = cg->pcg_image->pi_fullpath; + (void) fprintf(args.pa_graphfile, " @ %s\n", + pmcstat_string_unintern( + pmcstat_previous_filename_printed)); + } else + (void) fprintf(args.pa_graphfile, "\n"); + + if (cg->pcg_nchildren == 0) + return; + + if ((sortbuffer = (struct pmcstat_cgnode **) + malloc(sizeof(struct pmcstat_cgnode *) * + cg->pcg_nchildren)) == NULL) + err(EX_OSERR, "ERROR: Cannot print callgraph"); + cgn = sortbuffer; + + LIST_FOREACH(pcg, &cg->pcg_children, pcg_sibling) + *cgn++ = pcg; + + assert(cgn - sortbuffer == (int) cg->pcg_nchildren); + + qsort(sortbuffer, cg->pcg_nchildren, sizeof(struct pmcstat_cgnode *), + pmcstat_cgnode_compare); + + for (cgn = sortbuffer, n = 0; n < cg->pcg_nchildren; n++, cgn++) + pmcstat_cgnode_print(*cgn, depth+1, cg->pcg_count); + + free(sortbuffer); +} + +/* + * Record a callchain. + */ + +void +pmcpl_cg_process(struct pmcstat_process *pp, struct pmcstat_pmcrecord *pmcr, + uint32_t nsamples, uintfptr_t *cc, int usermode, uint32_t cpu) +{ + uintfptr_t pc, loadaddress; + uint32_t n; + struct pmcstat_image *image; + struct pmcstat_pcmap *ppm; + struct pmcstat_symbol *sym; + struct pmcstat_cgnode *parent, *child; + struct pmcstat_process *km; + pmc_id_t pmcid; + + (void) cpu; + + /* + * Find the callgraph node recorded in the global hash table + * for this (pmcid, pc). + */ + + pc = cc[0]; + pmcid = pmcr->pr_pmcid; + parent = pmcstat_cgnode_hash_lookup_pc(pp, pmcid, pc, usermode); + if (parent == NULL) { + pmcstat_stats.ps_callchain_dubious_frames++; + pmcr->pr_dubious_frames++; + return; + } + + parent->pcg_count++; + + /* + * For each return address in the call chain record, subject + * to the maximum depth desired. + * - Find the image associated with the sample. Stop if there + * there is no valid image at that address. + * - Find the function that overlaps the return address. + * - If found: use the start address of the function. + * If not found (say an object's symbol table is not present or + * is incomplete), round down to th gprof bucket granularity. + * - Convert return virtual address to an offset in the image. + * - Look for a child with the same {offset,image} tuple, + * inserting one if needed. + * - Increment the count of occurrences of the child. + */ + km = pmcstat_kernproc; + + for (n = 1; n < (uint32_t) args.pa_graphdepth && n < nsamples; n++, + parent = child) { + pc = cc[n]; + + ppm = pmcstat_process_find_map(usermode ? pp : km, pc); + if (ppm == NULL) { + /* Detect full frame capture (kernel + user). */ + if (!usermode) { + ppm = pmcstat_process_find_map(pp, pc); + if (ppm != NULL) + km = pp; + } + } + if (ppm == NULL) + return; + + image = ppm->ppm_image; + loadaddress = ppm->ppm_lowpc + image->pi_vaddr - + image->pi_start; + pc -= loadaddress; + + if ((sym = pmcstat_symbol_search(image, pc)) != NULL) + pc = sym->ps_start; + + child = pmcstat_cgnode_find(parent, image, pc); + child->pcg_count++; + } +} + +/* + * Printing a callgraph for a PMC. + */ +static void +pmcstat_callgraph_print_for_pmcid(struct pmcstat_pmcrecord *pmcr) +{ + int n, nentries; + uint32_t nsamples; + pmc_id_t pmcid; + struct pmcstat_cgnode **sortbuffer, **cgn; + struct pmcstat_cgnode_hash *pch; + + /* + * We pull out all callgraph nodes in the top-level hash table + * with a matching PMC id. We then sort these based on the + * frequency of occurrence. Each callgraph node is then + * printed. + */ + + nsamples = 0; + pmcid = pmcr->pr_pmcid; + if ((sortbuffer = (struct pmcstat_cgnode **) + malloc(sizeof(struct pmcstat_cgnode *) * + pmcstat_cgnode_hash_count)) == NULL) + err(EX_OSERR, "ERROR: Cannot sort callgraph"); + cgn = sortbuffer; + + for (n = 0; n < PMCSTAT_NHASH; n++) + LIST_FOREACH(pch, &pmcstat_cgnode_hash[n], pch_next) + if (pch->pch_pmcid == pmcid) { + nsamples += pch->pch_cgnode->pcg_count; + *cgn++ = pch->pch_cgnode; + } + + nentries = cgn - sortbuffer; + assert(nentries <= pmcstat_cgnode_hash_count); + + if (nentries == 0) { + free(sortbuffer); + return; + } + + qsort(sortbuffer, nentries, sizeof(struct pmcstat_cgnode *), + pmcstat_cgnode_compare); + + (void) fprintf(args.pa_graphfile, + "@ %s [%u samples]\n\n", + pmcstat_string_unintern(pmcr->pr_pmcname), + nsamples); + + for (cgn = sortbuffer, n = 0; n < nentries; n++, cgn++) { + pmcstat_previous_filename_printed = NULL; + pmcstat_cgnode_print(*cgn, 0, nsamples); + (void) fprintf(args.pa_graphfile, "\n"); + } + + free(sortbuffer); +} + +/* + * Print out callgraphs. + */ + +static void +pmcstat_callgraph_print(void) +{ + struct pmcstat_pmcrecord *pmcr; + + LIST_FOREACH(pmcr, &pmcstat_pmcs, pr_next) + pmcstat_callgraph_print_for_pmcid(pmcr); +} + +static void +pmcstat_cgnode_topprint(struct pmcstat_cgnode *cg, + int depth, uint32_t nsamples) +{ + int v_attrs, vs_len, ns_len, width, len, n, nchildren; + float v; + char ns[30], vs[10]; + struct pmcstat_symbol *sym; + struct pmcstat_cgnode **sortbuffer, **cgn, *pcg; + + (void) depth; + + /* Format value. */ + v = PMCPL_CG_COUNTP(cg); + snprintf(vs, sizeof(vs), "%.1f", v); + v_attrs = PMCSTAT_ATTRPERCENT(v); + + /* Format name. */ + sym = pmcstat_symbol_search(cg->pcg_image, cg->pcg_func); + if (sym != NULL) { + snprintf(ns, sizeof(ns), "%s", + pmcstat_string_unintern(sym->ps_name)); + } else + snprintf(ns, sizeof(ns), "%p", + (void *)cg->pcg_func); + + PMCSTAT_ATTRON(v_attrs); + PMCSTAT_PRINTW("%5.5s", vs); + PMCSTAT_ATTROFF(v_attrs); + PMCSTAT_PRINTW(" %-10.10s %-20.20s", + pmcstat_string_unintern(cg->pcg_image->pi_name), + ns); + + nchildren = cg->pcg_nchildren; + if (nchildren == 0) { + PMCSTAT_PRINTW("\n"); + return; + } + + width = pmcstat_displaywidth - 40; + + if ((sortbuffer = (struct pmcstat_cgnode **) + malloc(sizeof(struct pmcstat_cgnode *) * + nchildren)) == NULL) + err(EX_OSERR, "ERROR: Cannot print callgraph"); + cgn = sortbuffer; + + LIST_FOREACH(pcg, &cg->pcg_children, pcg_sibling) + *cgn++ = pcg; + + assert(cgn - sortbuffer == (int)nchildren); + + qsort(sortbuffer, nchildren, sizeof(struct pmcstat_cgnode *), + pmcstat_cgnode_compare); + + /* Count how many callers. */ + for (cgn = sortbuffer, n = 0; n < nchildren; n++, cgn++) { + pcg = *cgn; + + v = PMCPL_CG_COUNTP(pcg); + if (v < pmcstat_threshold) + break; + } + nchildren = n; + + for (cgn = sortbuffer, n = 0; n < nchildren; n++, cgn++) { + pcg = *cgn; + + /* Format value. */ + if (nchildren > 1) { + v = PMCPL_CG_COUNTP(pcg); + vs_len = snprintf(vs, sizeof(vs), ":%.1f", v); + v_attrs = PMCSTAT_ATTRPERCENT(v); + } else + vs_len = 0; + + /* Format name. */ + sym = pmcstat_symbol_search(pcg->pcg_image, pcg->pcg_func); + if (sym != NULL) { + ns_len = snprintf(ns, sizeof(ns), "%s", + pmcstat_string_unintern(sym->ps_name)); + } else + ns_len = snprintf(ns, sizeof(ns), "%p", + (void *)pcg->pcg_func); + + len = ns_len + vs_len + 1; + if (width - len < 0) { + PMCSTAT_PRINTW(" ..."); + break; + } + width -= len; + + PMCSTAT_PRINTW(" %s", ns); + if (nchildren > 1) { + PMCSTAT_ATTRON(v_attrs); + PMCSTAT_PRINTW("%s", vs); + PMCSTAT_ATTROFF(v_attrs); + } + } + PMCSTAT_PRINTW("\n"); + free(sortbuffer); +} + +/* + * Top mode display. + */ + +void +pmcpl_cg_topdisplay(void) +{ + int n, nentries; + uint32_t nsamples; + struct pmcstat_cgnode **sortbuffer, **cgn; + struct pmcstat_cgnode_hash *pch; + struct pmcstat_pmcrecord *pmcr; + + pmcr = pmcstat_pmcindex_to_pmcr(pmcstat_pmcinfilter); + if (!pmcr) + err(EX_SOFTWARE, "ERROR: invalid pmcindex"); + + /* + * We pull out all callgraph nodes in the top-level hash table + * with a matching PMC index. We then sort these based on the + * frequency of occurrence. Each callgraph node is then + * printed. + */ + + nsamples = 0; + + if ((sortbuffer = (struct pmcstat_cgnode **) + malloc(sizeof(struct pmcstat_cgnode *) * + pmcstat_cgnode_hash_count)) == NULL) + err(EX_OSERR, "ERROR: Cannot sort callgraph"); + cgn = sortbuffer; + + for (n = 0; n < PMCSTAT_NHASH; n++) + LIST_FOREACH(pch, &pmcstat_cgnode_hash[n], pch_next) + if (pmcr == NULL || pch->pch_pmcid == pmcr->pr_pmcid) { + nsamples += pch->pch_cgnode->pcg_count; + *cgn++ = pch->pch_cgnode; + } + + nentries = cgn - sortbuffer; + assert(nentries <= pmcstat_cgnode_hash_count); + + if (nentries == 0) { + free(sortbuffer); + return; + } + + qsort(sortbuffer, nentries, sizeof(struct pmcstat_cgnode *), + pmcstat_cgnode_compare); + + PMCSTAT_PRINTW("%5.5s %-10.10s %-20.20s %s\n", + "%SAMP", "IMAGE", "FUNCTION", "CALLERS"); + + nentries = min(pmcstat_displayheight - 2, nentries); + + for (cgn = sortbuffer, n = 0; n < nentries; n++, cgn++) { + if (PMCPL_CG_COUNTP(*cgn) < pmcstat_threshold) + break; + pmcstat_cgnode_topprint(*cgn, 0, nsamples); + } + + free(sortbuffer); +} + +/* + * Handle top mode keypress. + */ + +int +pmcpl_cg_topkeypress(int c, WINDOW *w) +{ + + (void) c; (void) w; + + return 0; +} + +int +pmcpl_cg_init(void) +{ + int i; + + pmcstat_cgnode_hash_count = 0; + pmcstat_previous_filename_printed = NULL; + + for (i = 0; i < PMCSTAT_NHASH; i++) { + LIST_INIT(&pmcstat_cgnode_hash[i]); + } + + return (0); +} + +void +pmcpl_cg_shutdown(FILE *mf) +{ + int i; + struct pmcstat_cgnode_hash *pch, *pchtmp; + + (void) mf; + + if (args.pa_flags & FLAG_DO_CALLGRAPHS) + pmcstat_callgraph_print(); + + /* + * Free memory. + */ + for (i = 0; i < PMCSTAT_NHASH; i++) { + LIST_FOREACH_SAFE(pch, &pmcstat_cgnode_hash[i], pch_next, + pchtmp) { + pmcstat_cgnode_free(pch->pch_cgnode); + LIST_REMOVE(pch, pch_next); + free(pch); + } + } +} + diff --git a/usr.sbin/pmcstat/pmcpl_callgraph.h b/usr.sbin/pmcstat/pmcpl_callgraph.h new file mode 100644 index 0000000..aaf0e1b --- /dev/null +++ b/usr.sbin/pmcstat/pmcpl_callgraph.h @@ -0,0 +1,67 @@ +/*- + * 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_PL_CALLGRAPH_H_ +#define _PMCSTAT_PL_CALLGRAPH_H_ + +/* + * Each call graph node is tracked by a pmcstat_cgnode struct. + */ + +struct pmcstat_cgnode { + struct pmcstat_image *pcg_image; + uintfptr_t pcg_func; + uint32_t pcg_count; + uint32_t pcg_nchildren; + LIST_ENTRY(pmcstat_cgnode) pcg_sibling; + LIST_HEAD(,pmcstat_cgnode) pcg_children; +}; + +struct pmcstat_cgnode_hash { + struct pmcstat_cgnode *pch_cgnode; + pmc_id_t pch_pmcid; + LIST_ENTRY(pmcstat_cgnode_hash) pch_next; +}; +extern LIST_HEAD(pmcstat_cgnode_hash_list, pmcstat_cgnode_hash) pmcstat_cgnode_hash[PMCSTAT_NHASH]; +extern int pmcstat_cgnode_hash_count; + +/* Function prototypes */ +int pmcpl_cg_init(void); +void pmcpl_cg_shutdown(FILE *mf); +void pmcpl_cg_process( + struct pmcstat_process *pp, struct pmcstat_pmcrecord *pmcr, + uint32_t nsamples, uintfptr_t *cc, int usermode, uint32_t cpu); +int pmcpl_cg_topkeypress(int c, WINDOW *w); +void pmcpl_cg_topdisplay(void); +void pmcpl_cg_configure(char *opt); + +#endif /* _PMCSTAT_PL_CALLGRAPH_H_ */ diff --git a/usr.sbin/pmcstat/pmcpl_calltree.c b/usr.sbin/pmcstat/pmcpl_calltree.c new file mode 100644 index 0000000..3d0127d --- /dev/null +++ b/usr.sbin/pmcstat/pmcpl_calltree.c @@ -0,0 +1,1197 @@ +/*- + * Copyright (c) 2012, Fabien Thomas + * 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 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. + */ + +/* + * Process hwpmc(4) samples as calltree. + * + * Output file format compatible with Kcachegrind (kdesdk). + * Handle top mode with a sorted tree display. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/endian.h> +#include <sys/queue.h> + +#include <assert.h> +#include <curses.h> +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <pmc.h> +#include <pmclog.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sysexits.h> + +#include "pmcstat.h" +#include "pmcstat_log.h" +#include "pmcstat_top.h" +#include "pmcpl_calltree.h" + +#define PMCPL_CT_GROWSIZE 4 + +static int pmcstat_skiplink = 0; + +struct pmcpl_ct_node; + +/* Get the sample value for PMC a. */ +#define PMCPL_CT_SAMPLE(a, b) \ + ((a) < (b)->npmcs ? (b)->sb[a] : 0) + +/* Get the sample value in percent related to rsamples. */ +#define PMCPL_CT_SAMPLEP(a, b) \ + (PMCPL_CT_SAMPLE(a, b) * 100.0 / rsamples->sb[a]) + +struct pmcpl_ct_sample { + int npmcs; /* Max pmc index available. */ + unsigned *sb; /* Sample buffer for 0..npmcs. */ +}; + +struct pmcpl_ct_arc { + struct pmcpl_ct_sample pcta_samples; + struct pmcpl_ct_sample pcta_callid; + unsigned pcta_call; + struct pmcpl_ct_node *pcta_child; +}; + +struct pmcpl_ct_instr { + uintfptr_t pctf_func; + struct pmcpl_ct_sample pctf_samples; +}; + +/* + * Each calltree node is tracked by a pmcpl_ct_node struct. + */ +struct pmcpl_ct_node { + struct pmcstat_image *pct_image; + uintfptr_t pct_func; + + struct pmcstat_symbol *pct_sym; + pmcstat_interned_string pct_ifl; + pmcstat_interned_string pct_ifn; + + struct pmcpl_ct_sample pct_samples; + + int pct_narc; + int pct_arc_c; + struct pmcpl_ct_arc *pct_arc; + + /* TODO: optimize for large number of items. */ + int pct_ninstr; + int pct_instr_c; + struct pmcpl_ct_instr *pct_instr; + +#define PMCPL_PCT_ADDR 0 +#define PMCPL_PCT_NAME 1 + char pct_type; +#define PMCPL_PCT_WHITE 0 +#define PMCPL_PCT_GREY 1 +#define PMCPL_PCT_BLACK 2 + char pct_color; +}; + +struct pmcpl_ct_node_hash { + struct pmcpl_ct_node *pch_ctnode; + STAILQ_ENTRY(pmcpl_ct_node_hash) pch_next; +}; + +static struct pmcpl_ct_sample pmcpl_ct_callid; + +#define PMCPL_CT_MAXCOL PMC_CALLCHAIN_DEPTH_MAX +#define PMCPL_CT_MAXLINE 1024 /* TODO: dynamic. */ + +struct pmcpl_ct_line { + unsigned ln_sum; + unsigned ln_index; +}; + +static struct pmcpl_ct_line pmcpl_ct_topmax[PMCPL_CT_MAXLINE+1]; +static struct pmcpl_ct_node + *pmcpl_ct_topscreen[PMCPL_CT_MAXCOL+1][PMCPL_CT_MAXLINE+1]; + +/* + * All nodes indexed by function/image name are placed in a hash table. + */ +static STAILQ_HEAD(,pmcpl_ct_node_hash) pmcpl_ct_node_hash[PMCSTAT_NHASH]; + +/* + * Root node for the graph. + */ +static struct pmcpl_ct_node *pmcpl_ct_root; + +/* + * Prototypes + */ + +/* + * Initialize a samples. + */ + +static void +pmcpl_ct_samples_init(struct pmcpl_ct_sample *samples) +{ + + samples->npmcs = 0; + samples->sb = NULL; +} + +/* + * Free a samples. + */ + +static void +pmcpl_ct_samples_free(struct pmcpl_ct_sample *samples) +{ + + samples->npmcs = 0; + free(samples->sb); + samples->sb = NULL; +} + +/* + * Grow a sample block to store pmcstat_npmcs PMCs. + */ + +static void +pmcpl_ct_samples_grow(struct pmcpl_ct_sample *samples) +{ + int npmcs; + + /* Enough storage. */ + if (pmcstat_npmcs <= samples->npmcs) + return; + + npmcs = samples->npmcs + + max(pmcstat_npmcs - samples->npmcs, PMCPL_CT_GROWSIZE); + samples->sb = realloc(samples->sb, npmcs * sizeof(unsigned)); + if (samples->sb == NULL) + errx(EX_SOFTWARE, "ERROR: out of memory"); + bzero((char *)samples->sb + samples->npmcs * sizeof(unsigned), + (npmcs - samples->npmcs) * sizeof(unsigned)); + samples->npmcs = npmcs; +} + +/* + * Compute the sum of all root arcs. + */ + +static void +pmcpl_ct_samples_root(struct pmcpl_ct_sample *samples) +{ + int i, pmcin; + + pmcpl_ct_samples_init(samples); + pmcpl_ct_samples_grow(samples); + + for (i = 0; i < pmcpl_ct_root->pct_narc; i++) + for (pmcin = 0; pmcin < pmcstat_npmcs; pmcin++) + samples->sb[pmcin] += PMCPL_CT_SAMPLE(pmcin, + &pmcpl_ct_root->pct_arc[i].pcta_samples); +} + +/* + * Grow the arc table. + */ + +static void +pmcpl_ct_arc_grow(int cursize, int *maxsize, struct pmcpl_ct_arc **items) +{ + int nmaxsize; + + if (cursize < *maxsize) + return; + + nmaxsize = *maxsize + max(cursize + 1 - *maxsize, PMCPL_CT_GROWSIZE); + *items = realloc(*items, nmaxsize * sizeof(struct pmcpl_ct_arc)); + if (*items == NULL) + errx(EX_SOFTWARE, "ERROR: out of memory"); + bzero((char *)*items + *maxsize * sizeof(struct pmcpl_ct_arc), + (nmaxsize - *maxsize) * sizeof(struct pmcpl_ct_arc)); + *maxsize = nmaxsize; +} + +/* + * Grow the instr table. + */ + +static void +pmcpl_ct_instr_grow(int cursize, int *maxsize, struct pmcpl_ct_instr **items) +{ + int nmaxsize; + + if (cursize < *maxsize) + return; + + nmaxsize = *maxsize + max(cursize + 1 - *maxsize, PMCPL_CT_GROWSIZE); + *items = realloc(*items, nmaxsize * sizeof(struct pmcpl_ct_instr)); + if (*items == NULL) + errx(EX_SOFTWARE, "ERROR: out of memory"); + bzero((char *)*items + *maxsize * sizeof(struct pmcpl_ct_instr), + (nmaxsize - *maxsize) * sizeof(struct pmcpl_ct_instr)); + *maxsize = nmaxsize; +} + +/* + * Add a new instruction sample to given node. + */ + +static void +pmcpl_ct_instr_add(struct pmcpl_ct_node *ct, int pmcin, + uintfptr_t pc, unsigned v) +{ + int i; + struct pmcpl_ct_instr *in; + + for (i = 0; i<ct->pct_ninstr; i++) { + if (ct->pct_instr[i].pctf_func == pc) { + in = &ct->pct_instr[i]; + pmcpl_ct_samples_grow(&in->pctf_samples); + in->pctf_samples.sb[pmcin] += v; + return; + } + } + + pmcpl_ct_instr_grow(ct->pct_ninstr, &ct->pct_instr_c, &ct->pct_instr); + in = &ct->pct_instr[ct->pct_ninstr]; + in->pctf_func = pc; + pmcpl_ct_samples_init(&in->pctf_samples); + pmcpl_ct_samples_grow(&in->pctf_samples); + in->pctf_samples.sb[pmcin] = v; + ct->pct_ninstr++; +} + +/* + * Allocate a new node. + */ + +static struct pmcpl_ct_node * +pmcpl_ct_node_allocate(void) +{ + struct pmcpl_ct_node *ct; + + if ((ct = malloc(sizeof(*ct))) == NULL) + err(EX_OSERR, "ERROR: Cannot allocate callgraph node"); + + pmcpl_ct_samples_init(&ct->pct_samples); + + ct->pct_sym = NULL; + ct->pct_image = NULL; + ct->pct_func = 0; + + ct->pct_narc = 0; + ct->pct_arc_c = 0; + ct->pct_arc = NULL; + + ct->pct_ninstr = 0; + ct->pct_instr_c = 0; + ct->pct_instr = NULL; + + ct->pct_color = PMCPL_PCT_WHITE; + + return (ct); +} + +/* + * Free a node. + */ + +static void +pmcpl_ct_node_free(struct pmcpl_ct_node *ct) +{ + int i; + + for (i = 0; i < ct->pct_narc; i++) { + pmcpl_ct_samples_free(&ct->pct_arc[i].pcta_samples); + pmcpl_ct_samples_free(&ct->pct_arc[i].pcta_callid); + } + + pmcpl_ct_samples_free(&ct->pct_samples); + free(ct->pct_arc); + free(ct->pct_instr); + free(ct); +} + +/* + * Clear the graph tag on each node. + */ +static void +pmcpl_ct_node_cleartag(void) +{ + int i; + struct pmcpl_ct_node_hash *pch; + + for (i = 0; i < PMCSTAT_NHASH; i++) + STAILQ_FOREACH(pch, &pmcpl_ct_node_hash[i], pch_next) + pch->pch_ctnode->pct_color = PMCPL_PCT_WHITE; + + pmcpl_ct_root->pct_color = PMCPL_PCT_WHITE; +} + +/* + * Print the callchain line by line with maximum cost at top. + */ + +static int +pmcpl_ct_node_dumptop(int pmcin, struct pmcpl_ct_node *ct, + struct pmcpl_ct_sample *rsamples, int x, int *y) +{ + int i, terminal; + struct pmcpl_ct_arc *arc; + + if (ct->pct_color == PMCPL_PCT_GREY) + return 0; + + if (x >= PMCPL_CT_MAXCOL) { + pmcpl_ct_topscreen[x][*y] = NULL; + return 1; + } + pmcpl_ct_topscreen[x][*y] = ct; + + /* + * Check if this is a terminal node. + * We need to check that some samples exist + * for at least one arc for that PMC. + */ + terminal = 1; + for (i = 0; i < ct->pct_narc; i++) { + arc = &ct->pct_arc[i]; + if (arc->pcta_child->pct_color != PMCPL_PCT_GREY && + PMCPL_CT_SAMPLE(pmcin, + &arc->pcta_samples) != 0 && + PMCPL_CT_SAMPLEP(pmcin, + &arc->pcta_samples) > pmcstat_threshold) { + terminal = 0; + break; + } + } + + if (ct->pct_narc == 0 || terminal) { + pmcpl_ct_topscreen[x+1][*y] = NULL; + if (*y >= PMCPL_CT_MAXLINE) + return 1; + *y = *y + 1; + for (i=0; i < x; i++) + pmcpl_ct_topscreen[i][*y] = + pmcpl_ct_topscreen[i][*y - 1]; + return 0; + } + + ct->pct_color = PMCPL_PCT_GREY; + for (i = 0; i < ct->pct_narc; i++) { + if (PMCPL_CT_SAMPLE(pmcin, + &ct->pct_arc[i].pcta_samples) == 0) + continue; + if (PMCPL_CT_SAMPLEP(pmcin, + &ct->pct_arc[i].pcta_samples) > pmcstat_threshold) { + if (pmcpl_ct_node_dumptop(pmcin, + ct->pct_arc[i].pcta_child, + rsamples, x+1, y)) { + ct->pct_color = PMCPL_PCT_BLACK; + return 1; + } + } + } + ct->pct_color = PMCPL_PCT_BLACK; + + return 0; +} + +/* + * Compare two top line by sum. + */ +static int +pmcpl_ct_line_compare(const void *a, const void *b) +{ + const struct pmcpl_ct_line *ct1, *ct2; + + ct1 = (const struct pmcpl_ct_line *) a; + ct2 = (const struct pmcpl_ct_line *) b; + + /* Sort in reverse order */ + if (ct1->ln_sum < ct2->ln_sum) + return (1); + if (ct1->ln_sum > ct2->ln_sum) + return (-1); + return (0); +} + +/* + * Format and display given PMC index. + */ + +static void +pmcpl_ct_node_printtop(struct pmcpl_ct_sample *rsamples, int pmcin, int maxy) +{ +#undef TS +#undef TSI +#define TS(x, y) (pmcpl_ct_topscreen[x][y]) +#define TSI(x, y) (pmcpl_ct_topscreen[x][pmcpl_ct_topmax[y].ln_index]) + + int v_attrs, ns_len, vs_len, is_len, width, indentwidth, x, y; + float v; + char ns[30], vs[10], is[20]; + struct pmcpl_ct_node *ct; + const char *space = " "; + + /* + * Sort by line cost. + */ + for (y = 0; ; y++) { + ct = TS(1, y); + if (ct == NULL) + break; + + pmcpl_ct_topmax[y].ln_sum = 0; + pmcpl_ct_topmax[y].ln_index = y; + for (x = 1; TS(x, y) != NULL; x++) { + pmcpl_ct_topmax[y].ln_sum += + PMCPL_CT_SAMPLE(pmcin, &TS(x, y)->pct_samples); + } + } + qsort(pmcpl_ct_topmax, y, sizeof(pmcpl_ct_topmax[0]), + pmcpl_ct_line_compare); + pmcpl_ct_topmax[y].ln_index = y; + + for (y = 0; y < maxy; y++) { + ct = TSI(1, y); + if (ct == NULL) + break; + + if (y > 0) + PMCSTAT_PRINTW("\n"); + + /* Output sum. */ + v = pmcpl_ct_topmax[y].ln_sum * 100.0 / + rsamples->sb[pmcin]; + snprintf(vs, sizeof(vs), "%.1f", v); + v_attrs = PMCSTAT_ATTRPERCENT(v); + PMCSTAT_ATTRON(v_attrs); + PMCSTAT_PRINTW("%5.5s ", vs); + PMCSTAT_ATTROFF(v_attrs); + + width = indentwidth = 5 + 1; + + for (x = 1; (ct = TSI(x, y)) != NULL; x++) { + + vs[0] = '\0'; vs_len = 0; + is[0] = '\0'; is_len = 0; + + /* Format value. */ + v = PMCPL_CT_SAMPLEP(pmcin, &ct->pct_samples); + if (v > pmcstat_threshold) + vs_len = snprintf(vs, sizeof(vs), + "(%.1f%%)", v); + v_attrs = PMCSTAT_ATTRPERCENT(v); + + if (pmcstat_skiplink && v <= pmcstat_threshold) { + strlcpy(ns, ".", sizeof(ns)); + ns_len = 1; + } else { + if (ct->pct_sym != NULL) { + ns_len = snprintf(ns, sizeof(ns), "%s", + pmcstat_string_unintern(ct->pct_sym->ps_name)); + } else + ns_len = snprintf(ns, sizeof(ns), "%p", + (void *)ct->pct_func); + + /* Format image. */ + if (x == 1 || + TSI(x-1, y)->pct_image != ct->pct_image) + is_len = snprintf(is, sizeof(is), "@%s", + pmcstat_string_unintern(ct->pct_image->pi_name)); + + /* Check for line wrap. */ + width += ns_len + is_len + vs_len + 1; + } + if (width >= pmcstat_displaywidth) { + maxy--; + if (y >= maxy) + break; + PMCSTAT_PRINTW("\n%*s", indentwidth, space); + width = indentwidth + ns_len + is_len + vs_len; + } + + PMCSTAT_ATTRON(v_attrs); + PMCSTAT_PRINTW("%s%s%s ", ns, is, vs); + PMCSTAT_ATTROFF(v_attrs); + } + } +} + +/* + * Output top mode snapshot. + */ + +void +pmcpl_ct_topdisplay(void) +{ + int y; + struct pmcpl_ct_sample r, *rsamples; + + rsamples = &r; + pmcpl_ct_samples_root(rsamples); + pmcpl_ct_node_cleartag(); + + PMCSTAT_PRINTW("%5.5s %s\n", "%SAMP", "CALLTREE"); + + y = 0; + if (pmcpl_ct_node_dumptop(pmcstat_pmcinfilter, + pmcpl_ct_root, rsamples, 0, &y)) + PMCSTAT_PRINTW("...\n"); + pmcpl_ct_topscreen[1][y] = NULL; + + pmcpl_ct_node_printtop(rsamples, + pmcstat_pmcinfilter, pmcstat_displayheight - 2); + + pmcpl_ct_samples_free(rsamples); +} + +/* + * Handle top mode keypress. + */ + +int +pmcpl_ct_topkeypress(int c, WINDOW *w) +{ + + switch (c) { + case 'f': + pmcstat_skiplink = !pmcstat_skiplink; + wprintw(w, "skip empty link %s", + pmcstat_skiplink ? "on" : "off"); + break; + } + + return 0; +} + +/* + * Look for a callgraph node associated with pmc `pmcid' in the global + * hash table that corresponds to the given `pc' value in the process map + * `ppm'. + */ + +static void +pmcpl_ct_node_update(struct pmcpl_ct_node *parent, + struct pmcpl_ct_node *child, int pmcin, unsigned v, int cd) +{ + struct pmcpl_ct_arc *arc; + int i; + + assert(parent != NULL); + + /* + * Find related arc in parent node and + * increment the sample count. + */ + for (i = 0; i < parent->pct_narc; i++) { + if (parent->pct_arc[i].pcta_child == child) { + arc = &parent->pct_arc[i]; + pmcpl_ct_samples_grow(&arc->pcta_samples); + arc->pcta_samples.sb[pmcin] += v; + /* Estimate call count. */ + if (cd) { + pmcpl_ct_samples_grow(&arc->pcta_callid); + if (pmcpl_ct_callid.sb[pmcin] - + arc->pcta_callid.sb[pmcin] > 1) + arc->pcta_call++; + arc->pcta_callid.sb[pmcin] = + pmcpl_ct_callid.sb[pmcin]; + } + return; + } + } + + /* + * No arc found for us, add ourself to the parent. + */ + pmcpl_ct_arc_grow(parent->pct_narc, + &parent->pct_arc_c, &parent->pct_arc); + arc = &parent->pct_arc[parent->pct_narc]; + pmcpl_ct_samples_grow(&arc->pcta_samples); + arc->pcta_samples.sb[pmcin] = v; + arc->pcta_call = 1; + if (cd) { + pmcpl_ct_samples_grow(&arc->pcta_callid); + arc->pcta_callid.sb[pmcin] = pmcpl_ct_callid.sb[pmcin]; + } + arc->pcta_child = child; + parent->pct_narc++; +} + +/* + * Lookup by image/pc. + */ + +static struct pmcpl_ct_node * +pmcpl_ct_node_hash_lookup(struct pmcstat_image *image, uintfptr_t pc, + struct pmcstat_symbol *sym, char *fl, char *fn) +{ + int i; + unsigned int hash; + struct pmcpl_ct_node *ct; + struct pmcpl_ct_node_hash *h; + pmcstat_interned_string ifl, ifn; + + if (fn != NULL) { + ifl = pmcstat_string_intern(fl); + ifn = pmcstat_string_intern(fn); + } else { + ifl = 0; + ifn = 0; + } + + for (hash = i = 0; i < (int)sizeof(uintfptr_t); i++) + hash += (pc >> i) & 0xFF; + + hash &= PMCSTAT_HASH_MASK; + + STAILQ_FOREACH(h, &pmcpl_ct_node_hash[hash], pch_next) { + ct = h->pch_ctnode; + + assert(ct != NULL); + + if (ct->pct_image == image && ct->pct_func == pc) { + if (fn == NULL) + return (ct); + if (ct->pct_type == PMCPL_PCT_NAME && + ct->pct_ifl == ifl && ct->pct_ifn == ifn) + return (ct); + } + } + + /* + * We haven't seen this (pmcid, pc) tuple yet, so allocate a + * new callgraph node and a new hash table entry for it. + */ + ct = pmcpl_ct_node_allocate(); + if ((h = malloc(sizeof(*h))) == NULL) + err(EX_OSERR, "ERROR: Could not allocate callgraph node"); + + if (fn != NULL) { + ct->pct_type = PMCPL_PCT_NAME; + ct->pct_ifl = ifl; + ct->pct_ifn = ifn; + } else + ct->pct_type = PMCPL_PCT_ADDR; + ct->pct_image = image; + ct->pct_func = pc; + ct->pct_sym = sym; + + h->pch_ctnode = ct; + STAILQ_INSERT_HEAD(&pmcpl_ct_node_hash[hash], h, pch_next); + return (ct); +} + +/* + * Record a callchain. + */ + +void +pmcpl_ct_process(struct pmcstat_process *pp, struct pmcstat_pmcrecord *pmcr, + uint32_t nsamples, uintfptr_t *cc, int usermode, uint32_t cpu) +{ + int i, n, pmcin; + uintfptr_t pc, loadaddress; + struct pmcstat_image *image; + struct pmcstat_symbol *sym; + struct pmcstat_pcmap *ppm[PMC_CALLCHAIN_DEPTH_MAX]; + struct pmcstat_process *km; + struct pmcpl_ct_node *ct; + struct pmcpl_ct_node *ctl[PMC_CALLCHAIN_DEPTH_MAX+1]; + + (void) cpu; + + assert(nsamples>0 && nsamples<=PMC_CALLCHAIN_DEPTH_MAX); + + /* Get the PMC index. */ + pmcin = pmcr->pr_pmcin; + + /* + * Validate mapping for the callchain. + * Go from bottom to first invalid entry. + */ + km = pmcstat_kernproc; + for (n = 0; n < (int)nsamples; n++) { + ppm[n] = pmcstat_process_find_map(usermode ? + pp : km, cc[n]); + if (ppm[n] == NULL) { + /* Detect full frame capture (kernel + user). */ + if (!usermode) { + ppm[n] = pmcstat_process_find_map(pp, cc[n]); + if (ppm[n] != NULL) + km = pp; + } + } + if (ppm[n] == NULL) + break; + } + if (n-- == 0) { + pmcstat_stats.ps_callchain_dubious_frames++; + pmcr->pr_dubious_frames++; + return; + } + + /* Increase the call generation counter. */ + pmcpl_ct_samples_grow(&pmcpl_ct_callid); + pmcpl_ct_callid.sb[pmcin]++; + + /* + * Build node list. + */ + ctl[0] = pmcpl_ct_root; + for (i = 1; n >= 0; n--) { + image = ppm[n]->ppm_image; + loadaddress = ppm[n]->ppm_lowpc + + image->pi_vaddr - image->pi_start; + /* Convert to an offset in the image. */ + pc = cc[n] - loadaddress; + /* + * Try determine the function at this offset. If we can't + * find a function round leave the `pc' value alone. + */ + if ((sym = pmcstat_symbol_search(image, pc)) != NULL) + pc = sym->ps_start; + else + pmcstat_stats.ps_samples_unknown_function++; + + ct = pmcpl_ct_node_hash_lookup(image, pc, sym, NULL, NULL); + if (ct == NULL) { + pmcstat_stats.ps_callchain_dubious_frames++; + continue; + } + ctl[i++] = ct; + } + /* No valid node found. */ + if (i == 1) + return; + n = i; + + ct = ctl[0]; + for (i = 1; i < n; i++) + pmcpl_ct_node_update(ctl[i-1], ctl[i], pmcin, 1, 1); + + /* + * Increment the sample count for this PMC. + */ + pmcpl_ct_samples_grow(&ctl[n-1]->pct_samples); + ctl[n-1]->pct_samples.sb[pmcin]++; + + /* Update per instruction sample if required. */ + if (args.pa_ctdumpinstr) + pmcpl_ct_instr_add(ctl[n-1], pmcin, cc[0] - + (ppm[0]->ppm_lowpc + ppm[0]->ppm_image->pi_vaddr - + ppm[0]->ppm_image->pi_start), 1); +} + +/* + * Print node child cost. + */ + +static void +pmcpl_ct_node_printchild(struct pmcpl_ct_node *ct, uintfptr_t paddr, + int pline) +{ + int i, j, line; + uintfptr_t addr; + struct pmcpl_ct_node *child; + char sourcefile[PATH_MAX]; + char funcname[PATH_MAX]; + + /* + * Child cost. + * TODO: attach child cost to the real position in the funtion. + * TODO: cfn=<fn> / call <ncall> addr(<fn>) / addr(call <fn>) <arccost> + */ + for (i=0 ; i<ct->pct_narc; i++) { + child = ct->pct_arc[i].pcta_child; + /* Object binary. */ + fprintf(args.pa_graphfile, "cob=%s\n", + pmcstat_string_unintern(child->pct_image->pi_fullpath)); + /* Child function name. */ + addr = child->pct_image->pi_vaddr + child->pct_func; + line = 0; + /* Child function source file. */ + if (child->pct_type == PMCPL_PCT_NAME) { + fprintf(args.pa_graphfile, "cfi=%s\ncfn=%s\n", + pmcstat_string_unintern(child->pct_ifl), + pmcstat_string_unintern(child->pct_ifn)); + } else if (pmcstat_image_addr2line(child->pct_image, addr, + sourcefile, sizeof(sourcefile), &line, + funcname, sizeof(funcname))) { + fprintf(args.pa_graphfile, "cfi=%s\ncfn=%s\n", + sourcefile, funcname); + } else { + if (child->pct_sym != NULL) + fprintf(args.pa_graphfile, + "cfi=???\ncfn=%s\n", + pmcstat_string_unintern( + child->pct_sym->ps_name)); + else + fprintf(args.pa_graphfile, + "cfi=???\ncfn=%p\n", (void *)addr); + } + + /* Child function address, line and call count. */ + fprintf(args.pa_graphfile, "calls=%u %p %u\n", + ct->pct_arc[i].pcta_call, (void *)addr, line); + + /* + * Call address, line, sample. + * TODO: Associate call address to the right location. + */ + fprintf(args.pa_graphfile, "%p %u", (void *)paddr, pline); + for (j = 0; j<pmcstat_npmcs; j++) + fprintf(args.pa_graphfile, " %u", + PMCPL_CT_SAMPLE(j, &ct->pct_arc[i].pcta_samples)); + fprintf(args.pa_graphfile, "\n"); + } +} + +/* + * Print node self cost. + */ + +static void +pmcpl_ct_node_printself(struct pmcpl_ct_node *ct) +{ + int i, j, fline, line; + uintfptr_t faddr, addr; + char sourcefile[PATH_MAX]; + char funcname[PATH_MAX]; + + /* + * Object binary. + */ + fprintf(args.pa_graphfile, "ob=%s\n", + pmcstat_string_unintern(ct->pct_image->pi_fullpath)); + + /* + * Function name. + */ + faddr = ct->pct_image->pi_vaddr + ct->pct_func; + fline = 0; + if (ct->pct_type == PMCPL_PCT_NAME) { + fprintf(args.pa_graphfile, "fl=%s\nfn=%s\n", + pmcstat_string_unintern(ct->pct_ifl), + pmcstat_string_unintern(ct->pct_ifn)); + } else if (pmcstat_image_addr2line(ct->pct_image, faddr, + sourcefile, sizeof(sourcefile), &fline, + funcname, sizeof(funcname))) { + fprintf(args.pa_graphfile, "fl=%s\nfn=%s\n", + sourcefile, funcname); + } else { + if (ct->pct_sym != NULL) + fprintf(args.pa_graphfile, "fl=???\nfn=%s\n", + pmcstat_string_unintern(ct->pct_sym->ps_name)); + else + fprintf(args.pa_graphfile, "fl=???\nfn=%p\n", + (void *)(ct->pct_image->pi_vaddr + ct->pct_func)); + } + + /* + * Self cost. + */ + if (ct->pct_ninstr > 0) { + /* + * Per location cost. + */ + for (i = 0; i < ct->pct_ninstr; i++) { + addr = ct->pct_image->pi_vaddr + + ct->pct_instr[i].pctf_func; + line = 0; + pmcstat_image_addr2line(ct->pct_image, addr, + sourcefile, sizeof(sourcefile), &line, + funcname, sizeof(funcname)); + fprintf(args.pa_graphfile, "%p %u", + (void *)addr, line); + for (j = 0; j<pmcstat_npmcs; j++) + fprintf(args.pa_graphfile, " %u", + PMCPL_CT_SAMPLE(j, + &ct->pct_instr[i].pctf_samples)); + fprintf(args.pa_graphfile, "\n"); + } + } else { + /* Global cost function cost. */ + fprintf(args.pa_graphfile, "%p %u", (void *)faddr, fline); + for (i = 0; i<pmcstat_npmcs ; i++) + fprintf(args.pa_graphfile, " %u", + PMCPL_CT_SAMPLE(i, &ct->pct_samples)); + fprintf(args.pa_graphfile, "\n"); + } + + pmcpl_ct_node_printchild(ct, faddr, fline); +} + +static void +pmcpl_ct_printnode(struct pmcpl_ct_node *ct) +{ + int i; + + if (ct == pmcpl_ct_root) { + fprintf(args.pa_graphfile, "fn=root\n"); + fprintf(args.pa_graphfile, "0x0 1"); + for (i = 0; i<pmcstat_npmcs ; i++) + fprintf(args.pa_graphfile, " 0"); + fprintf(args.pa_graphfile, "\n"); + pmcpl_ct_node_printchild(ct, 0, 0); + } else + pmcpl_ct_node_printself(ct); +} + +/* + * Breadth first traversal. + */ + +static void +pmcpl_ct_bfs(struct pmcpl_ct_node *ct) +{ + int i; + struct pmcpl_ct_node_hash *pch, *pchc; + struct pmcpl_ct_node *child; + STAILQ_HEAD(,pmcpl_ct_node_hash) q; + + STAILQ_INIT(&q); + if ((pch = malloc(sizeof(*pch))) == NULL) + err(EX_OSERR, "ERROR: Cannot allocate queue"); + pch->pch_ctnode = ct; + STAILQ_INSERT_TAIL(&q, pch, pch_next); + ct->pct_color = PMCPL_PCT_BLACK; + + while (!STAILQ_EMPTY(&q)) { + pch = STAILQ_FIRST(&q); + STAILQ_REMOVE_HEAD(&q, pch_next); + pmcpl_ct_printnode(pch->pch_ctnode); + for (i = 0; i<pch->pch_ctnode->pct_narc; i++) { + child = pch->pch_ctnode->pct_arc[i].pcta_child; + if (child->pct_color == PMCPL_PCT_WHITE) { + child->pct_color = PMCPL_PCT_BLACK; + if ((pchc = malloc(sizeof(*pchc))) == NULL) + err(EX_OSERR, + "ERROR: Cannot allocate queue"); + pchc->pch_ctnode = child; + STAILQ_INSERT_TAIL(&q, pchc, pch_next); + } + } + free(pch); + } +} + +/* + * Detect and fix inlined location. + */ + +static void +_pmcpl_ct_expand_inline(struct pmcpl_ct_node *ct) +{ + int i, j; + unsigned fline, line, v; + uintfptr_t faddr, addr, pc; + char sourcefile[PATH_MAX]; + char ffuncname[PATH_MAX], funcname[PATH_MAX]; + char buffer[PATH_MAX]; + struct pmcpl_ct_node *child; + + /* + * Resolve parent and compare to each instr location. + */ + faddr = ct->pct_image->pi_vaddr + ct->pct_func; + fline = 0; + if (!pmcstat_image_addr2line(ct->pct_image, faddr, + sourcefile, sizeof(sourcefile), &fline, + ffuncname, sizeof(ffuncname))) + return; + + for (i = 0; i < ct->pct_ninstr; i++) { + addr = ct->pct_image->pi_vaddr + + ct->pct_instr[i].pctf_func; + line = 0; + if (!pmcstat_image_addr2line(ct->pct_image, addr, + sourcefile, sizeof(sourcefile), &line, + funcname, sizeof(funcname))) + continue; + + if (strcmp(funcname, ffuncname) == 0) + continue; + + /* + * - Lookup/create inline node by function name. + * - Move instr PMCs to the inline node. + * - Link nodes. + * The lookup create a specific node per image/pc. + */ + if (args.pa_verbosity >= 2) + fprintf(args.pa_printfile, + "WARNING: inlined function at %p %s in %s\n", + (void *)addr, funcname, ffuncname); + + snprintf(buffer, sizeof(buffer), "%s@%s", + funcname, ffuncname); + child = pmcpl_ct_node_hash_lookup(ct->pct_image, + ct->pct_func, ct->pct_sym, sourcefile, buffer); + assert(child != NULL); + pc = ct->pct_instr[i].pctf_func; + for (j = 0; j<pmcstat_npmcs; j++) { + v = PMCPL_CT_SAMPLE(j, + &ct->pct_instr[i].pctf_samples); + if (v == 0) + continue; + pmcpl_ct_instr_add(child, j, pc, v); + pmcpl_ct_node_update(ct, child, j, v, 0); + if (j < ct->pct_samples.npmcs) + ct->pct_samples.sb[j] -= + ct->pct_instr[i].pctf_samples.sb[j]; + ct->pct_instr[i].pctf_samples.sb[j] = 0; + } + } +} + +static void +pmcpl_ct_expand_inline(void) +{ + int i; + struct pmcpl_ct_node_hash *pch; + + if (!args.pa_ctdumpinstr) + return; + + for (i = 0; i < PMCSTAT_NHASH; i++) + STAILQ_FOREACH(pch, &pmcpl_ct_node_hash[i], pch_next) + if (pch->pch_ctnode->pct_type == PMCPL_PCT_ADDR) + _pmcpl_ct_expand_inline(pch->pch_ctnode); +} + +/* + * Clean the PMC name for Kcachegrind formula + */ + +static void +pmcpl_ct_fixup_pmcname(char *s) +{ + char *p; + + for (p = s; *p; p++) + if (!isalnum(*p)) + *p = '_'; +} + +/* + * Print a calltree (KCachegrind) for all PMCs. + */ + +static void +pmcpl_ct_print(void) +{ + int i; + char name[40]; + struct pmcpl_ct_sample rsamples; + + pmcpl_ct_samples_root(&rsamples); + pmcpl_ct_expand_inline(); + + fprintf(args.pa_graphfile, + "version: 1\n" + "creator: pmcstat\n" + "positions: instr line\n" + "events:"); + for (i=0; i<pmcstat_npmcs; i++) { + snprintf(name, sizeof(name), "%s_%d", + pmcstat_pmcindex_to_name(i), i); + pmcpl_ct_fixup_pmcname(name); + fprintf(args.pa_graphfile, " %s", name); + } + fprintf(args.pa_graphfile, "\nsummary:"); + for (i=0; i<pmcstat_npmcs ; i++) + fprintf(args.pa_graphfile, " %u", + PMCPL_CT_SAMPLE(i, &rsamples)); + fprintf(args.pa_graphfile, "\n"); + pmcpl_ct_bfs(pmcpl_ct_root); + pmcpl_ct_samples_free(&rsamples); +} + +int +pmcpl_ct_configure(char *opt) +{ + + if (strncmp(opt, "skiplink=", 9) == 0) { + pmcstat_skiplink = atoi(opt+9); + } else + return (0); + + return (1); +} + +int +pmcpl_ct_init(void) +{ + int i; + + pmcpl_ct_root = pmcpl_ct_node_allocate(); + + for (i = 0; i < PMCSTAT_NHASH; i++) + STAILQ_INIT(&pmcpl_ct_node_hash[i]); + + pmcpl_ct_samples_init(&pmcpl_ct_callid); + + return (0); +} + +void +pmcpl_ct_shutdown(FILE *mf) +{ + int i; + struct pmcpl_ct_node_hash *pch, *pchtmp; + + (void) mf; + + if (args.pa_flags & FLAG_DO_CALLGRAPHS) + pmcpl_ct_print(); + + /* + * Free memory. + */ + + for (i = 0; i < PMCSTAT_NHASH; i++) { + STAILQ_FOREACH_SAFE(pch, &pmcpl_ct_node_hash[i], pch_next, + pchtmp) { + pmcpl_ct_node_free(pch->pch_ctnode); + free(pch); + } + } + + pmcpl_ct_node_free(pmcpl_ct_root); + pmcpl_ct_root = NULL; + + pmcpl_ct_samples_free(&pmcpl_ct_callid); +} + diff --git a/usr.sbin/pmcstat/pmcpl_calltree.h b/usr.sbin/pmcstat/pmcpl_calltree.h new file mode 100644 index 0000000..f54957f --- /dev/null +++ b/usr.sbin/pmcstat/pmcpl_calltree.h @@ -0,0 +1,42 @@ +/*- + * Copyright (c) 2009, Fabien Thomas + * 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 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_PL_CALLTREE_H_ +#define _PMCSTAT_PL_CALLTREE_H_ + +/* Function prototypes */ +int pmcpl_ct_init(void); +void pmcpl_ct_shutdown(FILE *mf); +void pmcpl_ct_process( + struct pmcstat_process *pp, struct pmcstat_pmcrecord *pmcr, + uint32_t nsamples, uintfptr_t *cc, int usermode, uint32_t cpu); +int pmcpl_ct_topkeypress(int c, WINDOW *w); +void pmcpl_ct_topdisplay(void); +int pmcpl_ct_configure(char *opt); + +#endif /* _PMCSTAT_PL_CALLTREE_H_ */ diff --git a/usr.sbin/pmcstat/pmcpl_gprof.c b/usr.sbin/pmcstat/pmcpl_gprof.c new file mode 100644 index 0000000..5fc9b41 --- /dev/null +++ b/usr.sbin/pmcstat/pmcpl_gprof.c @@ -0,0 +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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/endian.h> +#include <sys/gmon.h> +#include <sys/imgact_aout.h> +#include <sys/imgact_elf.h> +#include <sys/mman.h> +#include <sys/pmc.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/wait.h> + +#include <netinet/in.h> + +#include <assert.h> +#include <curses.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <gelf.h> +#include <libgen.h> +#include <limits.h> +#include <netdb.h> +#include <pmc.h> +#include <pmclog.h> +#include <sysexits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#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 * hc_sz) + sizeof(struct gmonhdr); + gm.version = GMONVERSION; + gm.profrate = 0; /* use ticks */ + 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 <sys/gmon.h> */ + 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 <machine/profile.h> */ + pgf->pgf_ndatabytes = sizeof(struct gmonhdr) + + 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); + + if (args.pa_flags & FLAG_DO_WIDE_GPROF_HC) { + whc = (WIDEHISTCOUNTER *) ((uintptr_t) pgf->pgf_gmondata + + sizeof(struct gmonhdr)); + + /* 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(); +} diff --git a/usr.sbin/pmcstat/pmcpl_gprof.h b/usr.sbin/pmcstat/pmcpl_gprof.h new file mode 100644 index 0000000..069082f --- /dev/null +++ b/usr.sbin/pmcstat/pmcpl_gprof.h @@ -0,0 +1,47 @@ +/*- + * 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. + * + * $FreeBSD$ + */ + +#ifndef _PMCSTAT_PL_GPROF_H_ +#define _PMCSTAT_PL_GPROF_H_ + +/* Function prototypes */ +void pmcpl_gmon_shutdown(FILE *mf); +void pmcpl_gmon_process( + struct pmcstat_process *pp, struct pmcstat_pmcrecord *pmcr, + uint32_t nsamples, uintfptr_t *cc, int usermode, uint32_t cpu); +void pmcpl_gmon_initimage(struct pmcstat_image *pi); +void pmcpl_gmon_shutdownimage(struct pmcstat_image *pi); +void pmcpl_gmon_newpmc(pmcstat_interned_string ps, + struct pmcstat_pmcrecord *pr); + +#endif /* _PMCSTAT_PL_GPROF_H_ */ diff --git a/usr.sbin/pmcstat/pmcstat.8 b/usr.sbin/pmcstat/pmcstat.8 new file mode 100644 index 0000000..bc4bb74 --- /dev/null +++ b/usr.sbin/pmcstat/pmcstat.8 @@ -0,0 +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 November 18, 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=<float> 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. diff --git a/usr.sbin/pmcstat/pmcstat.c b/usr.sbin/pmcstat/pmcstat.c new file mode 100644 index 0000000..81b0cd0 --- /dev/null +++ b/usr.sbin/pmcstat/pmcstat.c @@ -0,0 +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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/cpuset.h> +#include <sys/event.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/sysctl.h> +#include <sys/time.h> +#include <sys/ttycom.h> +#include <sys/user.h> +#include <sys/wait.h> + +#include <assert.h> +#include <curses.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <kvm.h> +#include <libgen.h> +#include <limits.h> +#include <math.h> +#include <pmc.h> +#include <pmclog.h> +#include <regex.h> +#include <signal.h> +#include <stdarg.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> + +#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: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); +} diff --git a/usr.sbin/pmcstat/pmcstat.h b/usr.sbin/pmcstat/pmcstat.h new file mode 100644 index 0000000..5b1d3d9 --- /dev/null +++ b/usr.sbin/pmcstat/pmcstat.h @@ -0,0 +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 <sys/_cpuset.h> + +#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_ */ diff --git a/usr.sbin/pmcstat/pmcstat_log.c b/usr.sbin/pmcstat/pmcstat_log.c new file mode 100644 index 0000000..ea9b547 --- /dev/null +++ b/usr.sbin/pmcstat/pmcstat_log.c @@ -0,0 +1,2238 @@ +/*- + * 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. + */ + +/* + * Transform a hwpmc(4) log into human readable form, and into + * gprof(1) compatible profiles. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/endian.h> +#include <sys/cpuset.h> +#include <sys/gmon.h> +#include <sys/imgact_aout.h> +#include <sys/imgact_elf.h> +#include <sys/mman.h> +#include <sys/pmc.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/wait.h> + +#include <netinet/in.h> + +#include <assert.h> +#include <curses.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <gelf.h> +#include <libgen.h> +#include <limits.h> +#include <netdb.h> +#include <pmc.h> +#include <pmclog.h> +#include <sysexits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "pmcstat.h" +#include "pmcstat_log.h" +#include "pmcstat_top.h" + +#define PMCSTAT_ALLOCATE 1 + +/* + * PUBLIC INTERFACES + * + * pmcstat_initialize_logging() initialize this module, called first + * pmcstat_shutdown_logging() orderly shutdown, called last + * pmcstat_open_log() open an eventlog for processing + * pmcstat_process_log() print/convert an event log + * pmcstat_display_log() top mode display for the log + * pmcstat_close_log() finish processing an event log + * + * IMPLEMENTATION NOTES + * + * We correlate each 'callchain' or 'sample' entry seen in the event + * log back to an executable object in the system. Executable objects + * include: + * - program executables, + * - shared libraries loaded by the runtime loader, + * - dlopen()'ed objects loaded by the program, + * - the runtime loader itself, + * - the kernel and kernel modules. + * + * Each process that we know about is treated as a set of regions that + * map to executable objects. Processes are described by + * 'pmcstat_process' structures. Executable objects are tracked by + * 'pmcstat_image' structures. The kernel and kernel modules are + * common to all processes (they reside at the same virtual addresses + * for all processes). Individual processes can have their text + * segments and shared libraries loaded at process-specific locations. + * + * A given executable object can be in use by multiple processes + * (e.g., libc.so) and loaded at a different address in each. + * pmcstat_pcmap structures track per-image mappings. + * + * The sample log could have samples from multiple PMCs; we + * generate one 'gmon.out' profile per PMC. + * + * IMPLEMENTATION OF GMON OUTPUT + * + * Each executable object gets one 'gmon.out' profile, per PMC in + * use. Creation of 'gmon.out' profiles is done lazily. The + * 'gmon.out' profiles generated for a given sampling PMC are + * aggregates of all the samples for that particular executable + * object. + * + * IMPLEMENTATION OF SYSTEM-WIDE CALLGRAPH OUTPUT + * + * Each active pmcid has its own callgraph structure, described by a + * 'struct pmcstat_callgraph'. Given a process id and a list of pc + * values, we map each pc value to a tuple (image, symbol), where + * 'image' denotes an executable object and 'symbol' is the closest + * symbol that precedes the pc value. Each pc value in the list is + * also given a 'rank' that reflects its depth in the call stack. + */ + +struct pmcstat_pmcs pmcstat_pmcs = LIST_HEAD_INITIALIZER(pmcstat_pmcs); + +/* + * All image descriptors are kept in a hash table. + */ +struct pmcstat_image_hash_list pmcstat_image_hash[PMCSTAT_NHASH]; + +/* + * All process descriptors are kept in a hash table. + */ +struct pmcstat_process_hash_list pmcstat_process_hash[PMCSTAT_NHASH]; + +struct pmcstat_stats pmcstat_stats; /* statistics */ +static int ps_samples_period; /* samples count between top refresh. */ + +struct pmcstat_process *pmcstat_kernproc; /* kernel 'process' */ + +#include "pmcpl_gprof.h" +#include "pmcpl_callgraph.h" +#include "pmcpl_annotate.h" +#include "pmcpl_annotate_cg.h" +#include "pmcpl_calltree.h" + +static struct pmc_plugins { + const char *pl_name; /* name */ + + /* configure */ + int (*pl_configure)(char *opt); + + /* init and shutdown */ + int (*pl_init)(void); + void (*pl_shutdown)(FILE *mf); + + /* sample processing */ + void (*pl_process)(struct pmcstat_process *pp, + struct pmcstat_pmcrecord *pmcr, uint32_t nsamples, + uintfptr_t *cc, int usermode, uint32_t cpu); + + /* image */ + void (*pl_initimage)(struct pmcstat_image *pi); + void (*pl_shutdownimage)(struct pmcstat_image *pi); + + /* pmc */ + void (*pl_newpmc)(pmcstat_interned_string ps, + struct pmcstat_pmcrecord *pr); + + /* top display */ + void (*pl_topdisplay)(void); + + /* top keypress */ + int (*pl_topkeypress)(int c, WINDOW *w); + +} plugins[] = { + { + .pl_name = "none", + }, + { + .pl_name = "callgraph", + .pl_init = pmcpl_cg_init, + .pl_shutdown = pmcpl_cg_shutdown, + .pl_process = pmcpl_cg_process, + .pl_topkeypress = pmcpl_cg_topkeypress, + .pl_topdisplay = pmcpl_cg_topdisplay + }, + { + .pl_name = "gprof", + .pl_shutdown = pmcpl_gmon_shutdown, + .pl_process = pmcpl_gmon_process, + .pl_initimage = pmcpl_gmon_initimage, + .pl_shutdownimage = pmcpl_gmon_shutdownimage, + .pl_newpmc = pmcpl_gmon_newpmc + }, + { + .pl_name = "annotate", + .pl_process = pmcpl_annotate_process + }, + { + .pl_name = "calltree", + .pl_configure = pmcpl_ct_configure, + .pl_init = pmcpl_ct_init, + .pl_shutdown = pmcpl_ct_shutdown, + .pl_process = pmcpl_ct_process, + .pl_topkeypress = pmcpl_ct_topkeypress, + .pl_topdisplay = pmcpl_ct_topdisplay + }, + { + .pl_name = "annotate_cg", + .pl_process = pmcpl_annotate_cg_process + }, + + { + .pl_name = NULL + } +}; + +static int pmcstat_mergepmc; + +int pmcstat_pmcinfilter = 0; /* PMC filter for top mode. */ +float pmcstat_threshold = 0.5; /* Cost filter for top mode. */ + +/* + * Prototypes + */ + +static struct pmcstat_image *pmcstat_image_from_path(pmcstat_interned_string + _path, int _iskernelmodule); +static void pmcstat_image_get_aout_params(struct pmcstat_image *_image); +static void pmcstat_image_get_elf_params(struct pmcstat_image *_image); +static void pmcstat_image_link(struct pmcstat_process *_pp, + struct pmcstat_image *_i, uintfptr_t _lpc); + +static void pmcstat_pmcid_add(pmc_id_t _pmcid, + pmcstat_interned_string _name); + +static void pmcstat_process_aout_exec(struct pmcstat_process *_pp, + struct pmcstat_image *_image, uintfptr_t _entryaddr); +static void pmcstat_process_elf_exec(struct pmcstat_process *_pp, + struct pmcstat_image *_image, uintfptr_t _entryaddr); +static void pmcstat_process_exec(struct pmcstat_process *_pp, + pmcstat_interned_string _path, uintfptr_t _entryaddr); +static struct pmcstat_process *pmcstat_process_lookup(pid_t _pid, + int _allocate); +static int pmcstat_string_compute_hash(const char *_string); +static void pmcstat_string_initialize(void); +static int pmcstat_string_lookup_hash(pmcstat_interned_string _is); +static void pmcstat_string_shutdown(void); +static void pmcstat_stats_reset(int _reset_global); + +/* + * A simple implementation of interned strings. Each interned string + * is assigned a unique address, so that subsequent string compares + * can be done by a simple pointer comparison instead of using + * strcmp(). This speeds up hash table lookups and saves memory if + * duplicate strings are the norm. + */ +struct pmcstat_string { + LIST_ENTRY(pmcstat_string) ps_next; /* hash link */ + int ps_len; + int ps_hash; + char *ps_string; +}; + +static LIST_HEAD(,pmcstat_string) pmcstat_string_hash[PMCSTAT_NHASH]; + +/* + * PMC count. + */ +int pmcstat_npmcs; + +/* + * PMC Top mode pause state. + */ +static int pmcstat_pause; + +static void +pmcstat_stats_reset(int reset_global) +{ + struct pmcstat_pmcrecord *pr; + + /* Flush PMCs stats. */ + LIST_FOREACH(pr, &pmcstat_pmcs, pr_next) { + pr->pr_samples = 0; + pr->pr_dubious_frames = 0; + } + ps_samples_period = 0; + + /* Flush global stats. */ + if (reset_global) + bzero(&pmcstat_stats, sizeof(struct pmcstat_stats)); +} + +/* + * Compute a 'hash' value for a string. + */ + +static int +pmcstat_string_compute_hash(const char *s) +{ + unsigned hash; + + for (hash = 2166136261; *s; s++) + hash = (hash ^ *s) * 16777619; + + return (hash & PMCSTAT_HASH_MASK); +} + +/* + * Intern a copy of string 's', and return a pointer to the + * interned structure. + */ + +pmcstat_interned_string +pmcstat_string_intern(const char *s) +{ + struct pmcstat_string *ps; + const struct pmcstat_string *cps; + int hash, len; + + if ((cps = pmcstat_string_lookup(s)) != NULL) + return (cps); + + hash = pmcstat_string_compute_hash(s); + len = strlen(s); + + if ((ps = malloc(sizeof(*ps))) == NULL) + err(EX_OSERR, "ERROR: Could not intern string"); + ps->ps_len = len; + ps->ps_hash = hash; + ps->ps_string = strdup(s); + LIST_INSERT_HEAD(&pmcstat_string_hash[hash], ps, ps_next); + return ((pmcstat_interned_string) ps); +} + +const char * +pmcstat_string_unintern(pmcstat_interned_string str) +{ + const char *s; + + s = ((const struct pmcstat_string *) str)->ps_string; + return (s); +} + +pmcstat_interned_string +pmcstat_string_lookup(const char *s) +{ + struct pmcstat_string *ps; + int hash, len; + + hash = pmcstat_string_compute_hash(s); + len = strlen(s); + + LIST_FOREACH(ps, &pmcstat_string_hash[hash], ps_next) + if (ps->ps_len == len && ps->ps_hash == hash && + strcmp(ps->ps_string, s) == 0) + return (ps); + return (NULL); +} + +static int +pmcstat_string_lookup_hash(pmcstat_interned_string s) +{ + const struct pmcstat_string *ps; + + ps = (const struct pmcstat_string *) s; + return (ps->ps_hash); +} + +/* + * Initialize the string interning facility. + */ + +static void +pmcstat_string_initialize(void) +{ + int i; + + for (i = 0; i < PMCSTAT_NHASH; i++) + LIST_INIT(&pmcstat_string_hash[i]); +} + +/* + * Destroy the string table, free'ing up space. + */ + +static void +pmcstat_string_shutdown(void) +{ + int i; + struct pmcstat_string *ps, *pstmp; + + for (i = 0; i < PMCSTAT_NHASH; i++) + LIST_FOREACH_SAFE(ps, &pmcstat_string_hash[i], ps_next, + pstmp) { + LIST_REMOVE(ps, ps_next); + free(ps->ps_string); + free(ps); + } +} + +/* + * Determine whether a given executable image is an A.OUT object, and + * if so, fill in its parameters from the text file. + * Sets image->pi_type. + */ + +static void +pmcstat_image_get_aout_params(struct pmcstat_image *image) +{ + int fd; + ssize_t nbytes; + struct exec ex; + const char *path; + char buffer[PATH_MAX]; + + path = pmcstat_string_unintern(image->pi_execpath); + assert(path != NULL); + + if (image->pi_iskernelmodule) + errx(EX_SOFTWARE, + "ERROR: a.out kernel modules are unsupported \"%s\"", path); + + (void) snprintf(buffer, sizeof(buffer), "%s%s", + args.pa_fsroot, path); + + if ((fd = open(buffer, O_RDONLY, 0)) < 0 || + (nbytes = read(fd, &ex, sizeof(ex))) < 0) { + if (args.pa_verbosity >= 2) + warn("WARNING: Cannot determine type of \"%s\"", + path); + image->pi_type = PMCSTAT_IMAGE_INDETERMINABLE; + if (fd != -1) + (void) close(fd); + return; + } + + (void) close(fd); + + if ((unsigned) nbytes != sizeof(ex) || + N_BADMAG(ex)) + return; + + image->pi_type = PMCSTAT_IMAGE_AOUT; + + /* TODO: the rest of a.out processing */ + + return; +} + +/* + * Helper function. + */ + +static int +pmcstat_symbol_compare(const void *a, const void *b) +{ + const struct pmcstat_symbol *sym1, *sym2; + + sym1 = (const struct pmcstat_symbol *) a; + sym2 = (const struct pmcstat_symbol *) b; + + if (sym1->ps_end <= sym2->ps_start) + return (-1); + if (sym1->ps_start >= sym2->ps_end) + return (1); + return (0); +} + +/* + * Map an address to a symbol in an image. + */ + +struct pmcstat_symbol * +pmcstat_symbol_search(struct pmcstat_image *image, uintfptr_t addr) +{ + struct pmcstat_symbol sym; + + if (image->pi_symbols == NULL) + return (NULL); + + sym.ps_name = NULL; + sym.ps_start = addr; + sym.ps_end = addr + 1; + + return (bsearch((void *) &sym, image->pi_symbols, + image->pi_symcount, sizeof(struct pmcstat_symbol), + pmcstat_symbol_compare)); +} + +/* + * Add the list of symbols in the given section to the list associated + * with the object. + */ +static void +pmcstat_image_add_symbols(struct pmcstat_image *image, Elf *e, + Elf_Scn *scn, GElf_Shdr *sh) +{ + int firsttime; + size_t n, newsyms, nshsyms, nfuncsyms; + struct pmcstat_symbol *symptr; + char *fnname; + GElf_Sym sym; + Elf_Data *data; + + if ((data = elf_getdata(scn, NULL)) == NULL) + return; + + /* + * Determine the number of functions named in this + * section. + */ + + nshsyms = sh->sh_size / sh->sh_entsize; + for (n = nfuncsyms = 0; n < nshsyms; n++) { + if (gelf_getsym(data, (int) n, &sym) != &sym) + return; + if (GELF_ST_TYPE(sym.st_info) == STT_FUNC) + nfuncsyms++; + } + + if (nfuncsyms == 0) + return; + + /* + * Allocate space for the new entries. + */ + firsttime = image->pi_symbols == NULL; + symptr = realloc(image->pi_symbols, + sizeof(*symptr) * (image->pi_symcount + nfuncsyms)); + if (symptr == image->pi_symbols) /* realloc() failed. */ + return; + image->pi_symbols = symptr; + + /* + * Append new symbols to the end of the current table. + */ + symptr += image->pi_symcount; + + for (n = newsyms = 0; n < nshsyms; n++) { + if (gelf_getsym(data, (int) n, &sym) != &sym) + return; + if (GELF_ST_TYPE(sym.st_info) != STT_FUNC) + continue; + if (sym.st_shndx == STN_UNDEF) + continue; + + if (!firsttime && pmcstat_symbol_search(image, sym.st_value)) + continue; /* We've seen this symbol already. */ + + if ((fnname = elf_strptr(e, sh->sh_link, sym.st_name)) + == NULL) + continue; +#ifdef __arm__ + /* Remove spurious ARM function name. */ + if (fnname[0] == '$' && + (fnname[1] == 'a' || fnname[1] == 't' || + fnname[1] == 'd') && + fnname[2] == '\0') + continue; +#endif + + symptr->ps_name = pmcstat_string_intern(fnname); + symptr->ps_start = sym.st_value - image->pi_vaddr; + symptr->ps_end = symptr->ps_start + sym.st_size; + symptr++; + + newsyms++; + } + + image->pi_symcount += newsyms; + if (image->pi_symcount == 0) + return; + + assert(newsyms <= nfuncsyms); + + /* + * Return space to the system if there were duplicates. + */ + if (newsyms < nfuncsyms) + image->pi_symbols = realloc(image->pi_symbols, + sizeof(*symptr) * image->pi_symcount); + + /* + * Keep the list of symbols sorted. + */ + qsort(image->pi_symbols, image->pi_symcount, sizeof(*symptr), + pmcstat_symbol_compare); + + /* + * Deal with function symbols that have a size of 'zero' by + * making them extend to the next higher address. These + * symbols are usually defined in assembly code. + */ + for (symptr = image->pi_symbols; + symptr < image->pi_symbols + (image->pi_symcount - 1); + symptr++) + if (symptr->ps_start == symptr->ps_end) + symptr->ps_end = (symptr+1)->ps_start; +} + +/* + * Examine an ELF file to determine the size of its text segment. + * Sets image->pi_type if anything conclusive can be determined about + * this image. + */ + +static void +pmcstat_image_get_elf_params(struct pmcstat_image *image) +{ + int fd; + size_t i, nph, nsh; + const char *path, *elfbase; + char *p, *endp; + uintfptr_t minva, maxva; + Elf *e; + Elf_Scn *scn; + GElf_Ehdr eh; + GElf_Phdr ph; + GElf_Shdr sh; + enum pmcstat_image_type image_type; + char buffer[PATH_MAX]; + + assert(image->pi_type == PMCSTAT_IMAGE_UNKNOWN); + + image->pi_start = minva = ~(uintfptr_t) 0; + image->pi_end = maxva = (uintfptr_t) 0; + image->pi_type = image_type = PMCSTAT_IMAGE_INDETERMINABLE; + image->pi_isdynamic = 0; + image->pi_dynlinkerpath = NULL; + image->pi_vaddr = 0; + + path = pmcstat_string_unintern(image->pi_execpath); + assert(path != NULL); + + /* + * Look for kernel modules under FSROOT/KERNELPATH/NAME, + * and user mode executable objects under FSROOT/PATHNAME. + */ + if (image->pi_iskernelmodule) + (void) snprintf(buffer, sizeof(buffer), "%s%s/%s", + args.pa_fsroot, args.pa_kernel, path); + else + (void) snprintf(buffer, sizeof(buffer), "%s%s", + args.pa_fsroot, path); + + e = NULL; + if ((fd = open(buffer, O_RDONLY, 0)) < 0 || + (e = elf_begin(fd, ELF_C_READ, NULL)) == NULL || + (elf_kind(e) != ELF_K_ELF)) { + if (args.pa_verbosity >= 2) + warnx("WARNING: Cannot determine the type of \"%s\".", + buffer); + goto done; + } + + if (gelf_getehdr(e, &eh) != &eh) { + warnx( + "WARNING: Cannot retrieve the ELF Header for \"%s\": %s.", + buffer, elf_errmsg(-1)); + goto done; + } + + if (eh.e_type != ET_EXEC && eh.e_type != ET_DYN && + !(image->pi_iskernelmodule && eh.e_type == ET_REL)) { + warnx("WARNING: \"%s\" is of an unsupported ELF type.", + buffer); + goto done; + } + + image_type = eh.e_ident[EI_CLASS] == ELFCLASS32 ? + PMCSTAT_IMAGE_ELF32 : PMCSTAT_IMAGE_ELF64; + + /* + * Determine the virtual address where an executable would be + * loaded. Additionally, for dynamically linked executables, + * save the pathname to the runtime linker. + */ + if (eh.e_type == ET_EXEC) { + if (elf_getphnum(e, &nph) == 0) { + warnx( +"WARNING: Could not determine the number of program headers in \"%s\": %s.", + buffer, + elf_errmsg(-1)); + goto done; + } + for (i = 0; i < eh.e_phnum; i++) { + if (gelf_getphdr(e, i, &ph) != &ph) { + warnx( +"WARNING: Retrieval of PHDR entry #%ju in \"%s\" failed: %s.", + (uintmax_t) i, buffer, elf_errmsg(-1)); + goto done; + } + switch (ph.p_type) { + case PT_DYNAMIC: + image->pi_isdynamic = 1; + break; + case PT_INTERP: + if ((elfbase = elf_rawfile(e, NULL)) == NULL) { + warnx( +"WARNING: Cannot retrieve the interpreter for \"%s\": %s.", + buffer, elf_errmsg(-1)); + goto done; + } + image->pi_dynlinkerpath = + pmcstat_string_intern(elfbase + + ph.p_offset); + break; + case PT_LOAD: + if ((ph.p_flags & PF_X) != 0 && + (ph.p_offset & (-ph.p_align)) == 0) + image->pi_vaddr = ph.p_vaddr & (-ph.p_align); + break; + } + } + } + + /* + * Get the min and max VA associated with this ELF object. + */ + if (elf_getshnum(e, &nsh) == 0) { + warnx( +"WARNING: Could not determine the number of sections for \"%s\": %s.", + buffer, elf_errmsg(-1)); + goto done; + } + + for (i = 0; i < nsh; i++) { + if ((scn = elf_getscn(e, i)) == NULL || + gelf_getshdr(scn, &sh) != &sh) { + warnx( +"WARNING: Could not retrieve section header #%ju in \"%s\": %s.", + (uintmax_t) i, buffer, elf_errmsg(-1)); + goto done; + } + if (sh.sh_flags & SHF_EXECINSTR) { + minva = min(minva, sh.sh_addr); + maxva = max(maxva, sh.sh_addr + sh.sh_size); + } + if (sh.sh_type == SHT_SYMTAB || sh.sh_type == SHT_DYNSYM) + pmcstat_image_add_symbols(image, e, scn, &sh); + } + + image->pi_start = minva; + image->pi_end = maxva; + image->pi_type = image_type; + image->pi_fullpath = pmcstat_string_intern(buffer); + + /* Build display name + */ + endp = buffer; + for (p = buffer; *p; p++) + if (*p == '/') + endp = p+1; + image->pi_name = pmcstat_string_intern(endp); + + done: + (void) elf_end(e); + if (fd >= 0) + (void) close(fd); + return; +} + +/* + * Given an image descriptor, determine whether it is an ELF, or AOUT. + * If no handler claims the image, set its type to 'INDETERMINABLE'. + */ + +void +pmcstat_image_determine_type(struct pmcstat_image *image) +{ + assert(image->pi_type == PMCSTAT_IMAGE_UNKNOWN); + + /* Try each kind of handler in turn */ + if (image->pi_type == PMCSTAT_IMAGE_UNKNOWN) + pmcstat_image_get_elf_params(image); + if (image->pi_type == PMCSTAT_IMAGE_UNKNOWN) + pmcstat_image_get_aout_params(image); + + /* + * Otherwise, remember that we tried to determine + * the object's type and had failed. + */ + if (image->pi_type == PMCSTAT_IMAGE_UNKNOWN) + image->pi_type = PMCSTAT_IMAGE_INDETERMINABLE; +} + +/* + * Locate an image descriptor given an interned path, adding a fresh + * descriptor to the cache if necessary. This function also finds a + * suitable name for this image's sample file. + * + * We defer filling in the file format specific parts of the image + * structure till the time we actually see a sample that would fall + * into this image. + */ + +static struct pmcstat_image * +pmcstat_image_from_path(pmcstat_interned_string internedpath, + int iskernelmodule) +{ + int hash; + struct pmcstat_image *pi; + + hash = pmcstat_string_lookup_hash(internedpath); + + /* First, look for an existing entry. */ + LIST_FOREACH(pi, &pmcstat_image_hash[hash], pi_next) + if (pi->pi_execpath == internedpath && + pi->pi_iskernelmodule == iskernelmodule) + return (pi); + + /* + * Allocate a new entry and place it at the head of the hash + * and LRU lists. + */ + pi = malloc(sizeof(*pi)); + if (pi == NULL) + return (NULL); + + pi->pi_type = PMCSTAT_IMAGE_UNKNOWN; + pi->pi_execpath = internedpath; + pi->pi_start = ~0; + pi->pi_end = 0; + pi->pi_entry = 0; + pi->pi_vaddr = 0; + pi->pi_isdynamic = 0; + pi->pi_iskernelmodule = iskernelmodule; + pi->pi_dynlinkerpath = NULL; + pi->pi_symbols = NULL; + pi->pi_symcount = 0; + pi->pi_addr2line = NULL; + + if (plugins[args.pa_pplugin].pl_initimage != NULL) + plugins[args.pa_pplugin].pl_initimage(pi); + if (plugins[args.pa_plugin].pl_initimage != NULL) + plugins[args.pa_plugin].pl_initimage(pi); + + LIST_INSERT_HEAD(&pmcstat_image_hash[hash], pi, pi_next); + + return (pi); +} + +/* + * Record the fact that PC values from 'start' to 'end' come from + * image 'image'. + */ + +static void +pmcstat_image_link(struct pmcstat_process *pp, struct pmcstat_image *image, + uintfptr_t start) +{ + struct pmcstat_pcmap *pcm, *pcmnew; + uintfptr_t offset; + + assert(image->pi_type != PMCSTAT_IMAGE_UNKNOWN && + image->pi_type != PMCSTAT_IMAGE_INDETERMINABLE); + + if ((pcmnew = malloc(sizeof(*pcmnew))) == NULL) + err(EX_OSERR, "ERROR: Cannot create a map entry"); + + /* + * Adjust the map entry to only cover the text portion + * of the object. + */ + + offset = start - image->pi_vaddr; + pcmnew->ppm_lowpc = image->pi_start + offset; + pcmnew->ppm_highpc = image->pi_end + offset; + pcmnew->ppm_image = image; + + assert(pcmnew->ppm_lowpc < pcmnew->ppm_highpc); + + /* Overlapped mmap()'s are assumed to never occur. */ + TAILQ_FOREACH(pcm, &pp->pp_map, ppm_next) + if (pcm->ppm_lowpc >= pcmnew->ppm_highpc) + break; + + if (pcm == NULL) + TAILQ_INSERT_TAIL(&pp->pp_map, pcmnew, ppm_next); + else + TAILQ_INSERT_BEFORE(pcm, pcmnew, ppm_next); +} + +/* + * Unmap images in the range [start..end) associated with process + * 'pp'. + */ + +static void +pmcstat_image_unmap(struct pmcstat_process *pp, uintfptr_t start, + uintfptr_t end) +{ + struct pmcstat_pcmap *pcm, *pcmtmp, *pcmnew; + + assert(pp != NULL); + assert(start < end); + + /* + * Cases: + * - we could have the range completely in the middle of an + * existing pcmap; in this case we have to split the pcmap + * structure into two (i.e., generate a 'hole'). + * - we could have the range covering multiple pcmaps; these + * will have to be removed. + * - we could have either 'start' or 'end' falling in the + * middle of a pcmap; in this case shorten the entry. + */ + TAILQ_FOREACH_SAFE(pcm, &pp->pp_map, ppm_next, pcmtmp) { + assert(pcm->ppm_lowpc < pcm->ppm_highpc); + if (pcm->ppm_highpc <= start) + continue; + if (pcm->ppm_lowpc >= end) + return; + if (pcm->ppm_lowpc >= start && pcm->ppm_highpc <= end) { + /* + * The current pcmap is completely inside the + * unmapped range: remove it entirely. + */ + TAILQ_REMOVE(&pp->pp_map, pcm, ppm_next); + free(pcm); + } else if (pcm->ppm_lowpc < start && pcm->ppm_highpc > end) { + /* + * Split this pcmap into two; curtail the + * current map to end at [start-1], and start + * the new one at [end]. + */ + if ((pcmnew = malloc(sizeof(*pcmnew))) == NULL) + err(EX_OSERR, + "ERROR: Cannot split a map entry"); + + pcmnew->ppm_image = pcm->ppm_image; + + pcmnew->ppm_lowpc = end; + pcmnew->ppm_highpc = pcm->ppm_highpc; + + pcm->ppm_highpc = start; + + TAILQ_INSERT_AFTER(&pp->pp_map, pcm, pcmnew, ppm_next); + + return; + } else if (pcm->ppm_lowpc < start && pcm->ppm_highpc <= end) + pcm->ppm_highpc = start; + else if (pcm->ppm_lowpc >= start && pcm->ppm_highpc > end) + pcm->ppm_lowpc = end; + else + assert(0); + } +} + +/* + * Resolve file name and line number for the given address. + */ +int +pmcstat_image_addr2line(struct pmcstat_image *image, uintfptr_t addr, + char *sourcefile, size_t sourcefile_len, unsigned *sourceline, + char *funcname, size_t funcname_len) +{ + static int addr2line_warn = 0; + + char *sep, cmdline[PATH_MAX], imagepath[PATH_MAX]; + unsigned l; + int fd; + + if (image->pi_addr2line == NULL) { + /* Try default debug file location. */ + snprintf(imagepath, sizeof(imagepath), + "/usr/lib/debug/%s%s.debug", + args.pa_fsroot, + pmcstat_string_unintern(image->pi_fullpath)); + fd = open(imagepath, O_RDONLY); + if (fd < 0) { + /* Old kernel symbol path. */ + snprintf(imagepath, sizeof(imagepath), "%s%s.symbols", + args.pa_fsroot, + pmcstat_string_unintern(image->pi_fullpath)); + fd = open(imagepath, O_RDONLY); + if (fd < 0) { + snprintf(imagepath, sizeof(imagepath), "%s%s", + args.pa_fsroot, + pmcstat_string_unintern( + image->pi_fullpath)); + } + } + if (fd >= 0) + close(fd); + /* + * New addr2line support recursive inline function with -i + * but the format does not add a marker when no more entries + * are available. + */ + snprintf(cmdline, sizeof(cmdline), "addr2line -Cfe \"%s\"", + imagepath); + image->pi_addr2line = popen(cmdline, "r+"); + if (image->pi_addr2line == NULL) { + if (!addr2line_warn) { + addr2line_warn = 1; + warnx( +"WARNING: addr2line is needed for source code information." + ); + } + return (0); + } + } + + if (feof(image->pi_addr2line) || ferror(image->pi_addr2line)) { + warnx("WARNING: addr2line pipe error"); + pclose(image->pi_addr2line); + image->pi_addr2line = NULL; + return (0); + } + + fprintf(image->pi_addr2line, "%p\n", (void *)addr); + + if (fgets(funcname, funcname_len, image->pi_addr2line) == NULL) { + warnx("WARNING: addr2line function name read error"); + return (0); + } + sep = strchr(funcname, '\n'); + if (sep != NULL) + *sep = '\0'; + + if (fgets(sourcefile, sourcefile_len, image->pi_addr2line) == NULL) { + warnx("WARNING: addr2line source file read error"); + return (0); + } + sep = strchr(sourcefile, ':'); + if (sep == NULL) { + warnx("WARNING: addr2line source line separator missing"); + return (0); + } + *sep = '\0'; + l = atoi(sep+1); + if (l == 0) + return (0); + *sourceline = l; + return (1); +} + +/* + * Add a {pmcid,name} mapping. + */ + +static void +pmcstat_pmcid_add(pmc_id_t pmcid, pmcstat_interned_string ps) +{ + struct pmcstat_pmcrecord *pr, *prm; + + /* Replace an existing name for the PMC. */ + prm = NULL; + LIST_FOREACH(pr, &pmcstat_pmcs, pr_next) + if (pr->pr_pmcid == pmcid) { + pr->pr_pmcname = ps; + return; + } else if (pr->pr_pmcname == ps) + prm = pr; + + /* + * Otherwise, allocate a new descriptor and call the + * plugins hook. + */ + if ((pr = malloc(sizeof(*pr))) == NULL) + err(EX_OSERR, "ERROR: Cannot allocate pmc record"); + + pr->pr_pmcid = pmcid; + pr->pr_pmcname = ps; + pr->pr_pmcin = pmcstat_npmcs++; + pr->pr_samples = 0; + pr->pr_dubious_frames = 0; + pr->pr_merge = prm == NULL ? pr : prm; + + LIST_INSERT_HEAD(&pmcstat_pmcs, pr, pr_next); + + if (plugins[args.pa_pplugin].pl_newpmc != NULL) + plugins[args.pa_pplugin].pl_newpmc(ps, pr); + if (plugins[args.pa_plugin].pl_newpmc != NULL) + plugins[args.pa_plugin].pl_newpmc(ps, pr); +} + +/* + * Given a pmcid in use, find its human-readable name. + */ + +const char * +pmcstat_pmcid_to_name(pmc_id_t pmcid) +{ + struct pmcstat_pmcrecord *pr; + + LIST_FOREACH(pr, &pmcstat_pmcs, pr_next) + if (pr->pr_pmcid == pmcid) + return (pmcstat_string_unintern(pr->pr_pmcname)); + + return NULL; +} + +/* + * Convert PMC index to name. + */ + +const char * +pmcstat_pmcindex_to_name(int pmcin) +{ + struct pmcstat_pmcrecord *pr; + + LIST_FOREACH(pr, &pmcstat_pmcs, pr_next) + if (pr->pr_pmcin == pmcin) + return pmcstat_string_unintern(pr->pr_pmcname); + + return NULL; +} + +/* + * Return PMC record with given index. + */ + +struct pmcstat_pmcrecord * +pmcstat_pmcindex_to_pmcr(int pmcin) +{ + struct pmcstat_pmcrecord *pr; + + LIST_FOREACH(pr, &pmcstat_pmcs, pr_next) + if (pr->pr_pmcin == pmcin) + return pr; + + return NULL; +} + +/* + * Get PMC record by id, apply merge policy. + */ + +static struct pmcstat_pmcrecord * +pmcstat_lookup_pmcid(pmc_id_t pmcid) +{ + struct pmcstat_pmcrecord *pr; + + LIST_FOREACH(pr, &pmcstat_pmcs, pr_next) { + if (pr->pr_pmcid == pmcid) { + if (pmcstat_mergepmc) + return pr->pr_merge; + return pr; + } + } + + return NULL; +} + +/* + * Associate an AOUT image with a process. + */ + +static void +pmcstat_process_aout_exec(struct pmcstat_process *pp, + struct pmcstat_image *image, uintfptr_t entryaddr) +{ + (void) pp; + (void) image; + (void) entryaddr; + /* TODO Implement a.out handling */ +} + +/* + * Associate an ELF image with a process. + */ + +static void +pmcstat_process_elf_exec(struct pmcstat_process *pp, + struct pmcstat_image *image, uintfptr_t entryaddr) +{ + uintmax_t libstart; + struct pmcstat_image *rtldimage; + + assert(image->pi_type == PMCSTAT_IMAGE_ELF32 || + image->pi_type == PMCSTAT_IMAGE_ELF64); + + /* Create a map entry for the base executable. */ + pmcstat_image_link(pp, image, image->pi_vaddr); + + /* + * For dynamically linked executables we need to determine + * where the dynamic linker was mapped to for this process, + * Subsequent executable objects that are mapped in by the + * dynamic linker will be tracked by log events of type + * PMCLOG_TYPE_MAP_IN. + */ + + if (image->pi_isdynamic) { + + /* + * The runtime loader gets loaded just after the maximum + * possible heap address. Like so: + * + * [ TEXT DATA BSS HEAP -->*RTLD SHLIBS <--STACK] + * ^ ^ + * 0 VM_MAXUSER_ADDRESS + + * + * The exact address where the loader gets mapped in + * will vary according to the size of the executable + * and the limits on the size of the process'es data + * segment at the time of exec(). The entry address + * recorded at process exec time corresponds to the + * 'start' address inside the dynamic linker. From + * this we can figure out the address where the + * runtime loader's file object had been mapped to. + */ + rtldimage = pmcstat_image_from_path(image->pi_dynlinkerpath, 0); + if (rtldimage == NULL) { + warnx("WARNING: Cannot find image for \"%s\".", + pmcstat_string_unintern(image->pi_dynlinkerpath)); + pmcstat_stats.ps_exec_errors++; + return; + } + + if (rtldimage->pi_type == PMCSTAT_IMAGE_UNKNOWN) + pmcstat_image_get_elf_params(rtldimage); + + if (rtldimage->pi_type != PMCSTAT_IMAGE_ELF32 && + rtldimage->pi_type != PMCSTAT_IMAGE_ELF64) { + warnx("WARNING: rtld not an ELF object \"%s\".", + pmcstat_string_unintern(image->pi_dynlinkerpath)); + return; + } + + libstart = entryaddr - rtldimage->pi_entry; + pmcstat_image_link(pp, rtldimage, libstart); + } +} + +/* + * Find the process descriptor corresponding to a PID. If 'allocate' + * is zero, we return a NULL if a pid descriptor could not be found or + * a process descriptor process. If 'allocate' is non-zero, then we + * will attempt to allocate a fresh process descriptor. Zombie + * process descriptors are only removed if a fresh allocation for the + * same PID is requested. + */ + +static struct pmcstat_process * +pmcstat_process_lookup(pid_t pid, int allocate) +{ + uint32_t hash; + struct pmcstat_pcmap *ppm, *ppmtmp; + struct pmcstat_process *pp, *pptmp; + + hash = (uint32_t) pid & PMCSTAT_HASH_MASK; /* simplicity wins */ + + LIST_FOREACH_SAFE(pp, &pmcstat_process_hash[hash], pp_next, pptmp) + if (pp->pp_pid == pid) { + /* Found a descriptor, check and process zombies */ + if (allocate && pp->pp_isactive == 0) { + /* remove maps */ + TAILQ_FOREACH_SAFE(ppm, &pp->pp_map, ppm_next, + ppmtmp) { + TAILQ_REMOVE(&pp->pp_map, ppm, + ppm_next); + free(ppm); + } + /* remove process entry */ + LIST_REMOVE(pp, pp_next); + free(pp); + break; + } + return (pp); + } + + if (!allocate) + return (NULL); + + if ((pp = malloc(sizeof(*pp))) == NULL) + err(EX_OSERR, "ERROR: Cannot allocate pid descriptor"); + + pp->pp_pid = pid; + pp->pp_isactive = 1; + + TAILQ_INIT(&pp->pp_map); + + LIST_INSERT_HEAD(&pmcstat_process_hash[hash], pp, pp_next); + return (pp); +} + +/* + * Associate an image and a process. + */ + +static void +pmcstat_process_exec(struct pmcstat_process *pp, + pmcstat_interned_string path, uintfptr_t entryaddr) +{ + struct pmcstat_image *image; + + if ((image = pmcstat_image_from_path(path, 0)) == NULL) { + pmcstat_stats.ps_exec_errors++; + return; + } + + if (image->pi_type == PMCSTAT_IMAGE_UNKNOWN) + pmcstat_image_determine_type(image); + + assert(image->pi_type != PMCSTAT_IMAGE_UNKNOWN); + + switch (image->pi_type) { + case PMCSTAT_IMAGE_ELF32: + case PMCSTAT_IMAGE_ELF64: + pmcstat_stats.ps_exec_elf++; + pmcstat_process_elf_exec(pp, image, entryaddr); + break; + + case PMCSTAT_IMAGE_AOUT: + pmcstat_stats.ps_exec_aout++; + pmcstat_process_aout_exec(pp, image, entryaddr); + break; + + case PMCSTAT_IMAGE_INDETERMINABLE: + pmcstat_stats.ps_exec_indeterminable++; + break; + + default: + err(EX_SOFTWARE, + "ERROR: Unsupported executable type for \"%s\"", + pmcstat_string_unintern(path)); + } +} + + +/* + * Find the map entry associated with process 'p' at PC value 'pc'. + */ + +struct pmcstat_pcmap * +pmcstat_process_find_map(struct pmcstat_process *p, uintfptr_t pc) +{ + struct pmcstat_pcmap *ppm; + + TAILQ_FOREACH(ppm, &p->pp_map, ppm_next) { + if (pc >= ppm->ppm_lowpc && pc < ppm->ppm_highpc) + return (ppm); + if (pc < ppm->ppm_lowpc) + return (NULL); + } + + return (NULL); +} + +/* + * Convert a hwpmc(4) log to profile information. A system-wide + * callgraph is generated if FLAG_DO_CALLGRAPHS is set. gmon.out + * files usable by gprof(1) are created if FLAG_DO_GPROF is set. + */ +static int +pmcstat_analyze_log(void) +{ + uint32_t cpu, cpuflags; + uintfptr_t pc; + pid_t pid; + struct pmcstat_image *image; + struct pmcstat_process *pp, *ppnew; + struct pmcstat_pcmap *ppm, *ppmtmp; + struct pmclog_ev ev; + struct pmcstat_pmcrecord *pmcr; + pmcstat_interned_string image_path; + + assert(args.pa_flags & FLAG_DO_ANALYSIS); + + if (elf_version(EV_CURRENT) == EV_NONE) + err(EX_UNAVAILABLE, "Elf library intialization failed"); + + while (pmclog_read(args.pa_logparser, &ev) == 0) { + assert(ev.pl_state == PMCLOG_OK); + + switch (ev.pl_type) { + case PMCLOG_TYPE_INITIALIZE: + if ((ev.pl_u.pl_i.pl_version & 0xFF000000) != + PMC_VERSION_MAJOR << 24 && args.pa_verbosity > 0) + warnx( +"WARNING: Log version 0x%x does not match compiled version 0x%x.", + ev.pl_u.pl_i.pl_version, PMC_VERSION_MAJOR); + break; + + case PMCLOG_TYPE_MAP_IN: + /* + * Introduce an address range mapping for a + * userland process or the kernel (pid == -1). + * + * We always allocate a process descriptor so + * that subsequent samples seen for this + * address range are mapped to the current + * object being mapped in. + */ + pid = ev.pl_u.pl_mi.pl_pid; + if (pid == -1) + pp = pmcstat_kernproc; + else + pp = pmcstat_process_lookup(pid, + PMCSTAT_ALLOCATE); + + assert(pp != NULL); + + image_path = pmcstat_string_intern(ev.pl_u.pl_mi. + pl_pathname); + image = pmcstat_image_from_path(image_path, pid == -1); + if (image->pi_type == PMCSTAT_IMAGE_UNKNOWN) + pmcstat_image_determine_type(image); + if (image->pi_type != PMCSTAT_IMAGE_INDETERMINABLE) + pmcstat_image_link(pp, image, + ev.pl_u.pl_mi.pl_start); + break; + + case PMCLOG_TYPE_MAP_OUT: + /* + * Remove an address map. + */ + pid = ev.pl_u.pl_mo.pl_pid; + if (pid == -1) + pp = pmcstat_kernproc; + else + pp = pmcstat_process_lookup(pid, 0); + + if (pp == NULL) /* unknown process */ + break; + + pmcstat_image_unmap(pp, ev.pl_u.pl_mo.pl_start, + ev.pl_u.pl_mo.pl_end); + break; + + case PMCLOG_TYPE_PCSAMPLE: + /* + * Note: the `PCSAMPLE' log entry is not + * generated by hpwmc(4) after version 2. + */ + + /* + * We bring in the gmon file for the image + * currently associated with the PMC & pid + * pair and increment the appropriate entry + * bin inside this. + */ + pmcstat_stats.ps_samples_total++; + ps_samples_period++; + + pc = ev.pl_u.pl_s.pl_pc; + pp = pmcstat_process_lookup(ev.pl_u.pl_s.pl_pid, + PMCSTAT_ALLOCATE); + + /* Get PMC record. */ + pmcr = pmcstat_lookup_pmcid(ev.pl_u.pl_s.pl_pmcid); + assert(pmcr != NULL); + pmcr->pr_samples++; + + /* + * Call the plugins processing + * TODO: move pmcstat_process_find_map inside plugins + */ + + if (plugins[args.pa_pplugin].pl_process != NULL) + plugins[args.pa_pplugin].pl_process( + pp, pmcr, 1, &pc, + pmcstat_process_find_map(pp, pc) != NULL, 0); + plugins[args.pa_plugin].pl_process( + pp, pmcr, 1, &pc, + pmcstat_process_find_map(pp, pc) != NULL, 0); + break; + + case PMCLOG_TYPE_CALLCHAIN: + pmcstat_stats.ps_samples_total++; + ps_samples_period++; + + cpuflags = ev.pl_u.pl_cc.pl_cpuflags; + cpu = PMC_CALLCHAIN_CPUFLAGS_TO_CPU(cpuflags); + + /* Filter on the CPU id. */ + if (!CPU_ISSET(cpu, &(args.pa_cpumask))) { + pmcstat_stats.ps_samples_skipped++; + break; + } + + pp = pmcstat_process_lookup(ev.pl_u.pl_cc.pl_pid, + PMCSTAT_ALLOCATE); + + /* Get PMC record. */ + pmcr = pmcstat_lookup_pmcid(ev.pl_u.pl_cc.pl_pmcid); + assert(pmcr != NULL); + pmcr->pr_samples++; + + /* + * Call the plugins processing + */ + + if (plugins[args.pa_pplugin].pl_process != NULL) + plugins[args.pa_pplugin].pl_process( + pp, pmcr, + ev.pl_u.pl_cc.pl_npc, + ev.pl_u.pl_cc.pl_pc, + PMC_CALLCHAIN_CPUFLAGS_TO_USERMODE(cpuflags), + cpu); + plugins[args.pa_plugin].pl_process( + pp, pmcr, + ev.pl_u.pl_cc.pl_npc, + ev.pl_u.pl_cc.pl_pc, + PMC_CALLCHAIN_CPUFLAGS_TO_USERMODE(cpuflags), + cpu); + break; + + case PMCLOG_TYPE_PMCALLOCATE: + /* + * Record the association pmc id between this + * PMC and its name. + */ + pmcstat_pmcid_add(ev.pl_u.pl_a.pl_pmcid, + pmcstat_string_intern(ev.pl_u.pl_a.pl_evname)); + break; + + case PMCLOG_TYPE_PMCALLOCATEDYN: + /* + * Record the association pmc id between this + * PMC and its name. + */ + pmcstat_pmcid_add(ev.pl_u.pl_ad.pl_pmcid, + pmcstat_string_intern(ev.pl_u.pl_ad.pl_evname)); + break; + + case PMCLOG_TYPE_PROCEXEC: + + /* + * Change the executable image associated with + * a process. + */ + pp = pmcstat_process_lookup(ev.pl_u.pl_x.pl_pid, + PMCSTAT_ALLOCATE); + + /* delete the current process map */ + TAILQ_FOREACH_SAFE(ppm, &pp->pp_map, ppm_next, ppmtmp) { + TAILQ_REMOVE(&pp->pp_map, ppm, ppm_next); + free(ppm); + } + + /* + * Associate this process image. + */ + image_path = pmcstat_string_intern( + ev.pl_u.pl_x.pl_pathname); + assert(image_path != NULL); + pmcstat_process_exec(pp, image_path, + ev.pl_u.pl_x.pl_entryaddr); + break; + + case PMCLOG_TYPE_PROCEXIT: + + /* + * Due to the way the log is generated, the + * last few samples corresponding to a process + * may appear in the log after the process + * exit event is recorded. Thus we keep the + * process' descriptor and associated data + * structures around, but mark the process as + * having exited. + */ + pp = pmcstat_process_lookup(ev.pl_u.pl_e.pl_pid, 0); + if (pp == NULL) + break; + pp->pp_isactive = 0; /* mark as a zombie */ + break; + + case PMCLOG_TYPE_SYSEXIT: + pp = pmcstat_process_lookup(ev.pl_u.pl_se.pl_pid, 0); + if (pp == NULL) + break; + pp->pp_isactive = 0; /* make a zombie */ + break; + + case PMCLOG_TYPE_PROCFORK: + + /* + * Allocate a process descriptor for the new + * (child) process. + */ + ppnew = + pmcstat_process_lookup(ev.pl_u.pl_f.pl_newpid, + PMCSTAT_ALLOCATE); + + /* + * If we had been tracking the parent, clone + * its address maps. + */ + pp = pmcstat_process_lookup(ev.pl_u.pl_f.pl_oldpid, 0); + if (pp == NULL) + break; + TAILQ_FOREACH(ppm, &pp->pp_map, ppm_next) + pmcstat_image_link(ppnew, ppm->ppm_image, + ppm->ppm_lowpc); + break; + + default: /* other types of entries are not relevant */ + break; + } + } + + if (ev.pl_state == PMCLOG_EOF) + return (PMCSTAT_FINISHED); + else if (ev.pl_state == PMCLOG_REQUIRE_DATA) + return (PMCSTAT_RUNNING); + + err(EX_DATAERR, + "ERROR: event parsing failed (record %jd, offset 0x%jx)", + (uintmax_t) ev.pl_count + 1, ev.pl_offset); +} + +/* + * Print log entries as text. + */ + +static int +pmcstat_print_log(void) +{ + struct pmclog_ev ev; + uint32_t npc; + + while (pmclog_read(args.pa_logparser, &ev) == 0) { + assert(ev.pl_state == PMCLOG_OK); + switch (ev.pl_type) { + case PMCLOG_TYPE_CALLCHAIN: + PMCSTAT_PRINT_ENTRY("callchain", + "%d 0x%x %d %d %c", ev.pl_u.pl_cc.pl_pid, + ev.pl_u.pl_cc.pl_pmcid, + PMC_CALLCHAIN_CPUFLAGS_TO_CPU(ev.pl_u.pl_cc. \ + pl_cpuflags), ev.pl_u.pl_cc.pl_npc, + PMC_CALLCHAIN_CPUFLAGS_TO_USERMODE(ev.pl_u.pl_cc.\ + pl_cpuflags) ? 'u' : 's'); + for (npc = 0; npc < ev.pl_u.pl_cc.pl_npc; npc++) + PMCSTAT_PRINT_ENTRY("...", "%p", + (void *) ev.pl_u.pl_cc.pl_pc[npc]); + break; + case PMCLOG_TYPE_CLOSELOG: + PMCSTAT_PRINT_ENTRY("closelog",); + break; + case PMCLOG_TYPE_DROPNOTIFY: + PMCSTAT_PRINT_ENTRY("drop",); + break; + case PMCLOG_TYPE_INITIALIZE: + PMCSTAT_PRINT_ENTRY("initlog","0x%x \"%s\"", + ev.pl_u.pl_i.pl_version, + pmc_name_of_cputype(ev.pl_u.pl_i.pl_arch)); + if ((ev.pl_u.pl_i.pl_version & 0xFF000000) != + PMC_VERSION_MAJOR << 24 && args.pa_verbosity > 0) + warnx( +"WARNING: Log version 0x%x != expected version 0x%x.", + ev.pl_u.pl_i.pl_version, PMC_VERSION); + break; + case PMCLOG_TYPE_MAP_IN: + PMCSTAT_PRINT_ENTRY("map-in","%d %p \"%s\"", + ev.pl_u.pl_mi.pl_pid, + (void *) ev.pl_u.pl_mi.pl_start, + ev.pl_u.pl_mi.pl_pathname); + break; + case PMCLOG_TYPE_MAP_OUT: + PMCSTAT_PRINT_ENTRY("map-out","%d %p %p", + ev.pl_u.pl_mo.pl_pid, + (void *) ev.pl_u.pl_mo.pl_start, + (void *) ev.pl_u.pl_mo.pl_end); + break; + case PMCLOG_TYPE_PCSAMPLE: + PMCSTAT_PRINT_ENTRY("sample","0x%x %d %p %c", + ev.pl_u.pl_s.pl_pmcid, + ev.pl_u.pl_s.pl_pid, + (void *) ev.pl_u.pl_s.pl_pc, + ev.pl_u.pl_s.pl_usermode ? 'u' : 's'); + break; + case PMCLOG_TYPE_PMCALLOCATE: + PMCSTAT_PRINT_ENTRY("allocate","0x%x \"%s\" 0x%x", + ev.pl_u.pl_a.pl_pmcid, + ev.pl_u.pl_a.pl_evname, + ev.pl_u.pl_a.pl_flags); + break; + case PMCLOG_TYPE_PMCALLOCATEDYN: + PMCSTAT_PRINT_ENTRY("allocatedyn","0x%x \"%s\" 0x%x", + ev.pl_u.pl_ad.pl_pmcid, + ev.pl_u.pl_ad.pl_evname, + ev.pl_u.pl_ad.pl_flags); + break; + case PMCLOG_TYPE_PMCATTACH: + PMCSTAT_PRINT_ENTRY("attach","0x%x %d \"%s\"", + ev.pl_u.pl_t.pl_pmcid, + ev.pl_u.pl_t.pl_pid, + ev.pl_u.pl_t.pl_pathname); + break; + case PMCLOG_TYPE_PMCDETACH: + PMCSTAT_PRINT_ENTRY("detach","0x%x %d", + ev.pl_u.pl_d.pl_pmcid, + ev.pl_u.pl_d.pl_pid); + break; + case PMCLOG_TYPE_PROCCSW: + PMCSTAT_PRINT_ENTRY("cswval","0x%x %d %jd", + ev.pl_u.pl_c.pl_pmcid, + ev.pl_u.pl_c.pl_pid, + ev.pl_u.pl_c.pl_value); + break; + case PMCLOG_TYPE_PROCEXEC: + PMCSTAT_PRINT_ENTRY("exec","0x%x %d %p \"%s\"", + ev.pl_u.pl_x.pl_pmcid, + ev.pl_u.pl_x.pl_pid, + (void *) ev.pl_u.pl_x.pl_entryaddr, + ev.pl_u.pl_x.pl_pathname); + break; + case PMCLOG_TYPE_PROCEXIT: + PMCSTAT_PRINT_ENTRY("exitval","0x%x %d %jd", + ev.pl_u.pl_e.pl_pmcid, + ev.pl_u.pl_e.pl_pid, + ev.pl_u.pl_e.pl_value); + break; + case PMCLOG_TYPE_PROCFORK: + PMCSTAT_PRINT_ENTRY("fork","%d %d", + ev.pl_u.pl_f.pl_oldpid, + ev.pl_u.pl_f.pl_newpid); + break; + case PMCLOG_TYPE_USERDATA: + PMCSTAT_PRINT_ENTRY("userdata","0x%x", + ev.pl_u.pl_u.pl_userdata); + break; + case PMCLOG_TYPE_SYSEXIT: + PMCSTAT_PRINT_ENTRY("exit","%d", + ev.pl_u.pl_se.pl_pid); + break; + default: + fprintf(args.pa_printfile, "unknown event (type %d).\n", + ev.pl_type); + } + } + + if (ev.pl_state == PMCLOG_EOF) + return (PMCSTAT_FINISHED); + else if (ev.pl_state == PMCLOG_REQUIRE_DATA) + return (PMCSTAT_RUNNING); + + errx(EX_DATAERR, + "ERROR: event parsing failed (record %jd, offset 0x%jx).", + (uintmax_t) ev.pl_count + 1, ev.pl_offset); + /*NOTREACHED*/ +} + +/* + * Public Interfaces. + */ + +/* + * Close a logfile, after first flushing all in-module queued data. + */ + +int +pmcstat_close_log(void) +{ + /* If a local logfile is configured ask the kernel to stop + * and flush data. Kernel will close the file when data is flushed + * so keep the status to EXITING. + */ + if (args.pa_logfd != -1) { + if (pmc_close_logfile() < 0) + err(EX_OSERR, "ERROR: logging failed"); + } + + return (args.pa_flags & FLAG_HAS_PIPE ? PMCSTAT_EXITING : + PMCSTAT_FINISHED); +} + + + +/* + * Open a log file, for reading or writing. + * + * The function returns the fd of a successfully opened log or -1 in + * case of failure. + */ + +int +pmcstat_open_log(const char *path, int mode) +{ + int error, fd, cfd; + size_t hlen; + const char *p, *errstr; + struct addrinfo hints, *res, *res0; + char hostname[MAXHOSTNAMELEN]; + + errstr = NULL; + fd = -1; + + /* + * If 'path' is "-" then open one of stdin or stdout depending + * on the value of 'mode'. + * + * If 'path' contains a ':' and does not start with a '/' or '.', + * and is being opened for writing, treat it as a "host:port" + * specification and open a network socket. + * + * Otherwise, treat 'path' as a file name and open that. + */ + if (path[0] == '-' && path[1] == '\0') + fd = (mode == PMCSTAT_OPEN_FOR_READ) ? 0 : 1; + else if (path[0] != '/' && + path[0] != '.' && strchr(path, ':') != NULL) { + + p = strrchr(path, ':'); + hlen = p - path; + if (p == path || hlen >= sizeof(hostname)) { + errstr = strerror(EINVAL); + goto done; + } + + assert(hlen < sizeof(hostname)); + (void) strncpy(hostname, path, hlen); + hostname[hlen] = '\0'; + + (void) memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + if ((error = getaddrinfo(hostname, p+1, &hints, &res0)) != 0) { + errstr = gai_strerror(error); + goto done; + } + + fd = -1; + for (res = res0; res; res = res->ai_next) { + if ((fd = socket(res->ai_family, res->ai_socktype, + res->ai_protocol)) < 0) { + errstr = strerror(errno); + continue; + } + if (mode == PMCSTAT_OPEN_FOR_READ) { + if (bind(fd, res->ai_addr, res->ai_addrlen) < 0) { + errstr = strerror(errno); + (void) close(fd); + fd = -1; + continue; + } + listen(fd, 1); + cfd = accept(fd, NULL, NULL); + (void) close(fd); + if (cfd < 0) { + errstr = strerror(errno); + fd = -1; + break; + } + fd = cfd; + } else { + if (connect(fd, res->ai_addr, res->ai_addrlen) < 0) { + errstr = strerror(errno); + (void) close(fd); + fd = -1; + continue; + } + } + errstr = NULL; + break; + } + freeaddrinfo(res0); + + } else if ((fd = open(path, mode == PMCSTAT_OPEN_FOR_READ ? + O_RDONLY : (O_WRONLY|O_CREAT|O_TRUNC), + S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) < 0) + errstr = strerror(errno); + + done: + if (errstr) + errx(EX_OSERR, "ERROR: Cannot open \"%s\" for %s: %s.", path, + (mode == PMCSTAT_OPEN_FOR_READ ? "reading" : "writing"), + errstr); + + return (fd); +} + +/* + * Process a log file in offline analysis mode. + */ + +int +pmcstat_process_log(void) +{ + + /* + * If analysis has not been asked for, just print the log to + * the current output file. + */ + if (args.pa_flags & FLAG_DO_PRINT) + return (pmcstat_print_log()); + else + return (pmcstat_analyze_log()); +} + +/* + * Refresh top display. + */ + +static void +pmcstat_refresh_top(void) +{ + int v_attrs; + float v; + char pmcname[40]; + struct pmcstat_pmcrecord *pmcpr; + + /* If in pause mode do not refresh display. */ + if (pmcstat_pause) + return; + + /* Wait until PMC pop in the log. */ + pmcpr = pmcstat_pmcindex_to_pmcr(pmcstat_pmcinfilter); + if (pmcpr == NULL) + return; + + /* Format PMC name. */ + if (pmcstat_mergepmc) + snprintf(pmcname, sizeof(pmcname), "[%s]", + pmcstat_string_unintern(pmcpr->pr_pmcname)); + else + snprintf(pmcname, sizeof(pmcname), "%s.%d", + pmcstat_string_unintern(pmcpr->pr_pmcname), + pmcstat_pmcinfilter); + + /* Format samples count. */ + if (ps_samples_period > 0) + v = (pmcpr->pr_samples * 100.0) / ps_samples_period; + else + v = 0.; + v_attrs = PMCSTAT_ATTRPERCENT(v); + + PMCSTAT_PRINTBEGIN(); + PMCSTAT_PRINTW("PMC: %s Samples: %u ", + pmcname, + pmcpr->pr_samples); + PMCSTAT_ATTRON(v_attrs); + PMCSTAT_PRINTW("(%.1f%%) ", v); + PMCSTAT_ATTROFF(v_attrs); + PMCSTAT_PRINTW(", %u unresolved\n\n", + pmcpr->pr_dubious_frames); + if (plugins[args.pa_plugin].pl_topdisplay != NULL) + plugins[args.pa_plugin].pl_topdisplay(); + PMCSTAT_PRINTEND(); +} + +/* + * Find the next pmc index to display. + */ + +static void +pmcstat_changefilter(void) +{ + int pmcin; + struct pmcstat_pmcrecord *pmcr; + + /* + * Find the next merge target. + */ + if (pmcstat_mergepmc) { + pmcin = pmcstat_pmcinfilter; + + do { + pmcr = pmcstat_pmcindex_to_pmcr(pmcstat_pmcinfilter); + if (pmcr == NULL || pmcr == pmcr->pr_merge) + break; + + pmcstat_pmcinfilter++; + if (pmcstat_pmcinfilter >= pmcstat_npmcs) + pmcstat_pmcinfilter = 0; + + } while (pmcstat_pmcinfilter != pmcin); + } +} + +/* + * Top mode keypress. + */ + +int +pmcstat_keypress_log(void) +{ + int c, ret = 0; + WINDOW *w; + + w = newwin(1, 0, 1, 0); + c = wgetch(w); + wprintw(w, "Key: %c => ", c); + switch (c) { + case 'c': + wprintw(w, "enter mode 'd' or 'a' => "); + c = wgetch(w); + if (c == 'd') { + args.pa_topmode = PMCSTAT_TOP_DELTA; + wprintw(w, "switching to delta mode"); + } else { + args.pa_topmode = PMCSTAT_TOP_ACCUM; + wprintw(w, "switching to accumulation mode"); + } + break; + case 'm': + pmcstat_mergepmc = !pmcstat_mergepmc; + /* + * Changing merge state require data reset. + */ + if (plugins[args.pa_plugin].pl_shutdown != NULL) + plugins[args.pa_plugin].pl_shutdown(NULL); + pmcstat_stats_reset(0); + if (plugins[args.pa_plugin].pl_init != NULL) + plugins[args.pa_plugin].pl_init(); + + /* Update filter to be on a merge target. */ + pmcstat_changefilter(); + wprintw(w, "merge PMC %s", pmcstat_mergepmc ? "on" : "off"); + break; + case 'n': + /* Close current plugin. */ + if (plugins[args.pa_plugin].pl_shutdown != NULL) + plugins[args.pa_plugin].pl_shutdown(NULL); + + /* Find next top display available. */ + do { + args.pa_plugin++; + if (plugins[args.pa_plugin].pl_name == NULL) + args.pa_plugin = 0; + } while (plugins[args.pa_plugin].pl_topdisplay == NULL); + + /* Open new plugin. */ + pmcstat_stats_reset(0); + if (plugins[args.pa_plugin].pl_init != NULL) + plugins[args.pa_plugin].pl_init(); + wprintw(w, "switching to plugin %s", + plugins[args.pa_plugin].pl_name); + break; + case 'p': + pmcstat_pmcinfilter++; + if (pmcstat_pmcinfilter >= pmcstat_npmcs) + pmcstat_pmcinfilter = 0; + pmcstat_changefilter(); + wprintw(w, "switching to PMC %s.%d", + pmcstat_pmcindex_to_name(pmcstat_pmcinfilter), + pmcstat_pmcinfilter); + break; + case ' ': + pmcstat_pause = !pmcstat_pause; + if (pmcstat_pause) + wprintw(w, "pause => press space again to continue"); + break; + case 'q': + wprintw(w, "exiting..."); + ret = 1; + break; + default: + if (plugins[args.pa_plugin].pl_topkeypress != NULL) + if (plugins[args.pa_plugin].pl_topkeypress(c, w)) + ret = 1; + } + + wrefresh(w); + delwin(w); + return ret; +} + + +/* + * Top mode display. + */ + +void +pmcstat_display_log(void) +{ + + pmcstat_refresh_top(); + + /* Reset everythings if delta mode. */ + if (args.pa_topmode == PMCSTAT_TOP_DELTA) { + if (plugins[args.pa_plugin].pl_shutdown != NULL) + plugins[args.pa_plugin].pl_shutdown(NULL); + pmcstat_stats_reset(0); + if (plugins[args.pa_plugin].pl_init != NULL) + plugins[args.pa_plugin].pl_init(); + } + +} + +/* + * Configure a plugins. + */ + +void +pmcstat_pluginconfigure_log(char *opt) +{ + + if (strncmp(opt, "threshold=", 10) == 0) { + pmcstat_threshold = atof(opt+10); + } else { + if (plugins[args.pa_plugin].pl_configure != NULL) { + if (!plugins[args.pa_plugin].pl_configure(opt)) + err(EX_USAGE, + "ERROR: unknown option <%s>.", opt); + } + } +} + +/* + * Initialize module. + */ + +void +pmcstat_initialize_logging(void) +{ + int i; + + /* use a convenient format for 'ldd' output */ + if (setenv("LD_TRACE_LOADED_OBJECTS_FMT1","%o \"%p\" %x\n",1) != 0) + err(EX_OSERR, "ERROR: Cannot setenv"); + + /* Initialize hash tables */ + pmcstat_string_initialize(); + for (i = 0; i < PMCSTAT_NHASH; i++) { + LIST_INIT(&pmcstat_image_hash[i]); + LIST_INIT(&pmcstat_process_hash[i]); + } + + /* + * Create a fake 'process' entry for the kernel with pid -1. + * hwpmc(4) will subsequently inform us about where the kernel + * and any loaded kernel modules are mapped. + */ + if ((pmcstat_kernproc = pmcstat_process_lookup((pid_t) -1, + PMCSTAT_ALLOCATE)) == NULL) + err(EX_OSERR, "ERROR: Cannot initialize logging"); + + /* PMC count. */ + pmcstat_npmcs = 0; + + /* Merge PMC with same name. */ + pmcstat_mergepmc = args.pa_mergepmc; + + /* + * Initialize plugins + */ + + if (plugins[args.pa_pplugin].pl_init != NULL) + plugins[args.pa_pplugin].pl_init(); + if (plugins[args.pa_plugin].pl_init != NULL) + plugins[args.pa_plugin].pl_init(); +} + +/* + * Shutdown module. + */ + +void +pmcstat_shutdown_logging(void) +{ + int i; + FILE *mf; + struct pmcstat_image *pi, *pitmp; + struct pmcstat_process *pp, *pptmp; + struct pmcstat_pcmap *ppm, *ppmtmp; + + /* determine where to send the map file */ + mf = NULL; + if (args.pa_mapfilename != NULL) + mf = (strcmp(args.pa_mapfilename, "-") == 0) ? + args.pa_printfile : fopen(args.pa_mapfilename, "w"); + + if (mf == NULL && args.pa_flags & FLAG_DO_GPROF && + args.pa_verbosity >= 2) + mf = args.pa_printfile; + + if (mf) + (void) fprintf(mf, "MAP:\n"); + + /* + * Shutdown the plugins + */ + + if (plugins[args.pa_plugin].pl_shutdown != NULL) + plugins[args.pa_plugin].pl_shutdown(mf); + if (plugins[args.pa_pplugin].pl_shutdown != NULL) + plugins[args.pa_pplugin].pl_shutdown(mf); + + for (i = 0; i < PMCSTAT_NHASH; i++) { + LIST_FOREACH_SAFE(pi, &pmcstat_image_hash[i], pi_next, + pitmp) { + if (plugins[args.pa_plugin].pl_shutdownimage != NULL) + plugins[args.pa_plugin].pl_shutdownimage(pi); + if (plugins[args.pa_pplugin].pl_shutdownimage != NULL) + plugins[args.pa_pplugin].pl_shutdownimage(pi); + + free(pi->pi_symbols); + if (pi->pi_addr2line != NULL) + pclose(pi->pi_addr2line); + LIST_REMOVE(pi, pi_next); + free(pi); + } + + LIST_FOREACH_SAFE(pp, &pmcstat_process_hash[i], pp_next, + pptmp) { + TAILQ_FOREACH_SAFE(ppm, &pp->pp_map, ppm_next, ppmtmp) { + TAILQ_REMOVE(&pp->pp_map, ppm, ppm_next); + free(ppm); + } + LIST_REMOVE(pp, pp_next); + free(pp); + } + } + + pmcstat_string_shutdown(); + + /* + * Print errors unless -q was specified. Print all statistics + * if verbosity > 1. + */ +#define PRINT(N,V) do { \ + if (pmcstat_stats.ps_##V || args.pa_verbosity >= 2) \ + (void) fprintf(args.pa_printfile, " %-40s %d\n",\ + N, pmcstat_stats.ps_##V); \ + } while (0) + + if (args.pa_verbosity >= 1 && (args.pa_flags & FLAG_DO_ANALYSIS)) { + (void) fprintf(args.pa_printfile, "CONVERSION STATISTICS:\n"); + PRINT("#exec/a.out", exec_aout); + PRINT("#exec/elf", exec_elf); + PRINT("#exec/unknown", exec_indeterminable); + PRINT("#exec handling errors", exec_errors); + PRINT("#samples/total", samples_total); + PRINT("#samples/unclaimed", samples_unknown_offset); + PRINT("#samples/unknown-object", samples_indeterminable); + PRINT("#samples/unknown-function", samples_unknown_function); + PRINT("#callchain/dubious-frames", callchain_dubious_frames); + } + + if (mf) + (void) fclose(mf); +} diff --git a/usr.sbin/pmcstat/pmcstat_log.h b/usr.sbin/pmcstat/pmcstat_log.h new file mode 100644 index 0000000..b4ced4d --- /dev/null +++ b/usr.sbin/pmcstat/pmcstat_log.h @@ -0,0 +1,199 @@ +/*- + * 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. + * + * $FreeBSD$ + */ + +#ifndef _PMCSTAT_LOG_H_ +#define _PMCSTAT_LOG_H_ + +typedef const void *pmcstat_interned_string; + +/* + * A 'pmcstat_process' structure models processes. Each process is + * associated with a set of pmcstat_pcmap structures that map + * addresses inside it to executable objects. This set is implemented + * as a list, kept sorted in ascending order of mapped addresses. + * + * 'pp_pid' holds the pid of the process. When a process exits, the + * 'pp_isactive' field is set to zero, but the process structure is + * not immediately reclaimed because there may still be samples in the + * log for this process. + */ + +struct pmcstat_process { + LIST_ENTRY(pmcstat_process) pp_next; /* hash-next */ + pid_t pp_pid; /* associated pid */ + int pp_isactive; /* whether active */ + uintfptr_t pp_entryaddr; /* entry address */ + TAILQ_HEAD(,pmcstat_pcmap) pp_map; /* address range map */ +}; +extern LIST_HEAD(pmcstat_process_hash_list, pmcstat_process) pmcstat_process_hash[PMCSTAT_NHASH]; + +/* + * A 'pmcstat_image' structure describes an executable program on + * disk. 'pi_execpath' is a cookie representing the pathname of + * the executable. 'pi_start' and 'pi_end' are the least and greatest + * virtual addresses for the text segments in the executable. + * 'pi_gmonlist' contains a linked list of gmon.out files associated + * with this image. + */ + +enum pmcstat_image_type { + PMCSTAT_IMAGE_UNKNOWN = 0, /* never looked at the image */ + PMCSTAT_IMAGE_INDETERMINABLE, /* can't tell what the image is */ + PMCSTAT_IMAGE_ELF32, /* ELF 32 bit object */ + PMCSTAT_IMAGE_ELF64, /* ELF 64 bit object */ + PMCSTAT_IMAGE_AOUT /* AOUT object */ +}; + +struct pmcstat_image { + LIST_ENTRY(pmcstat_image) pi_next; /* hash link */ + TAILQ_ENTRY(pmcstat_image) pi_lru; /* LRU list */ + pmcstat_interned_string pi_execpath; /* cookie */ + pmcstat_interned_string pi_samplename; /* sample path name */ + pmcstat_interned_string pi_fullpath; /* path to FS object */ + pmcstat_interned_string pi_name; /* display name */ + + enum pmcstat_image_type pi_type; /* executable type */ + + /* + * Executables have pi_start and pi_end; these are zero + * for shared libraries. + */ + uintfptr_t pi_start; /* start address (inclusive) */ + uintfptr_t pi_end; /* end address (exclusive) */ + uintfptr_t pi_entry; /* entry address */ + uintfptr_t pi_vaddr; /* virtual address where loaded */ + int pi_isdynamic; /* whether a dynamic object */ + int pi_iskernelmodule; + pmcstat_interned_string pi_dynlinkerpath; /* path in .interp */ + + /* All symbols associated with this object. */ + struct pmcstat_symbol *pi_symbols; + size_t pi_symcount; + + /* Handle to addr2line for this image. */ + FILE *pi_addr2line; + + /* + * Plugins private data + */ + + /* gprof: + * An image can be associated with one or more gmon.out files; + * one per PMC. + */ + LIST_HEAD(,pmcstat_gmonfile) pi_gmlist; +}; +extern LIST_HEAD(pmcstat_image_hash_list, pmcstat_image) pmcstat_image_hash[PMCSTAT_NHASH]; + +/* + * A 'pmcstat_pcmap' structure maps a virtual address range to an + * underlying 'pmcstat_image' descriptor. + */ +struct pmcstat_pcmap { + TAILQ_ENTRY(pmcstat_pcmap) ppm_next; + uintfptr_t ppm_lowpc; + uintfptr_t ppm_highpc; + struct pmcstat_image *ppm_image; +}; + +/* + * Each function symbol tracked by pmcstat(8). + */ + +struct pmcstat_symbol { + pmcstat_interned_string ps_name; + uint64_t ps_start; + uint64_t ps_end; +}; + +/* + * 'pmcstat_pmcrecord' is a mapping from PMC ids to human-readable + * names. + */ + +struct pmcstat_pmcrecord { + LIST_ENTRY(pmcstat_pmcrecord) pr_next; + pmc_id_t pr_pmcid; + int pr_pmcin; + pmcstat_interned_string pr_pmcname; + int pr_samples; + int pr_dubious_frames; + struct pmcstat_pmcrecord *pr_merge; +}; +extern LIST_HEAD(pmcstat_pmcs, pmcstat_pmcrecord) pmcstat_pmcs; /* PMC list */ + +/* + * Misc. statistics + */ +struct pmcstat_stats { + int ps_exec_aout; /* # a.out executables seen */ + int ps_exec_elf; /* # elf executables seen */ + int ps_exec_errors; /* # errors processing executables */ + int ps_exec_indeterminable; /* # unknown executables seen */ + int ps_samples_total; /* total number of samples processed */ + int ps_samples_skipped; /* #samples filtered out for any reason */ + int ps_samples_unknown_offset; /* #samples of rank 0 not in a map */ + int ps_samples_indeterminable; /* #samples in indeterminable images */ + int ps_samples_unknown_function;/* #samples with unknown function at offset */ + int ps_callchain_dubious_frames;/* #dubious frame pointers seen */ +}; +extern struct pmcstat_stats pmcstat_stats; /* statistics */ + +extern struct pmcstat_process *pmcstat_kernproc; /* kernel 'process' */ + +extern int pmcstat_npmcs; /* PMC count. */ + +/* + * Top mode global options. + */ +extern float pmcstat_threshold; /* Threshold to filter node. */ +extern int pmcstat_pmcinfilter; /* PMC index displayed. */ + +/* Function prototypes */ +const char *pmcstat_pmcid_to_name(pmc_id_t _pmcid); +const char *pmcstat_pmcindex_to_name(int pmcin); +struct pmcstat_pmcrecord *pmcstat_pmcindex_to_pmcr(int pmcin); +struct pmcstat_pcmap *pmcstat_process_find_map(struct pmcstat_process *_p, + uintfptr_t _pc); +struct pmcstat_symbol *pmcstat_symbol_search(struct pmcstat_image *image, + uintfptr_t addr); +const char *pmcstat_string_unintern(pmcstat_interned_string _is); +pmcstat_interned_string pmcstat_string_intern(const char *_s); +void pmcstat_image_determine_type(struct pmcstat_image *_image); +pmcstat_interned_string pmcstat_string_lookup(const char *_s); +int pmcstat_image_addr2line(struct pmcstat_image *image, uintfptr_t addr, + char *sourcefile, size_t sourcefile_len, unsigned *sourceline, + char *funcname, size_t funcname_len); + +#endif /* _PMCSTAT_LOG_H_ */ + diff --git a/usr.sbin/pmcstat/pmcstat_top.h b/usr.sbin/pmcstat/pmcstat_top.h new file mode 100644 index 0000000..281410b --- /dev/null +++ b/usr.sbin/pmcstat/pmcstat_top.h @@ -0,0 +1,75 @@ +/*- + * Copyright (c) 2009, Fabien Thomas + * 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 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_TOP_H_ +#define _PMCSTAT_TOP_H_ + +/* Return the ncurses attributes for the given value. */ +#define PMCSTAT_ATTRPERCENT(b) \ + ((b) > 10.0 ? (args.pa_topcolor ? COLOR_PAIR(1) : A_BOLD) : \ + ((b) > 5.0 ? (args.pa_topcolor ? COLOR_PAIR(2) : 0) : \ + ((b) > 2.5 ? (args.pa_topcolor ? COLOR_PAIR(3) : 0) : 0))) + +/* Print to the default ncurse windows if on a terminal or to the file. */ +#define PMCSTAT_PRINTW(...) do { \ + if (args.pa_toptty) \ + printw(__VA_ARGS__); \ + else \ + fprintf(args.pa_printfile, __VA_ARGS__);\ +} while (0) + +/* If ncurses mode active set attributes. */ +#define PMCSTAT_ATTRON(b) do { \ + if (args.pa_toptty) \ + attron(b); \ +} while (0) + +/* If ncurses mode active unset attributes. */ +#define PMCSTAT_ATTROFF(b) do { \ + if (args.pa_toptty) \ + attroff(b); \ +} while (0) + +/* Erase screen and set cursor to top left. */ +#define PMCSTAT_PRINTBEGIN() do { \ + if (args.pa_toptty) \ + clear(); \ +} while (0) + +/* Flush buffer to backend. */ +#define PMCSTAT_PRINTEND() do { \ + if (!args.pa_toptty) { \ + PMCSTAT_PRINTW("---\n"); \ + fflush(args.pa_printfile); \ + } else \ + refresh(); \ +} while (0) + +/* Function prototypes */ + +#endif /* _PMCSTAT_TOP_H_ */ |