summaryrefslogtreecommitdiffstats
path: root/usr.sbin/pmcstat
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/pmcstat')
-rw-r--r--usr.sbin/pmcstat/Makefile14
-rw-r--r--usr.sbin/pmcstat/Makefile.depend23
-rw-r--r--usr.sbin/pmcstat/pmcpl_annotate.c111
-rw-r--r--usr.sbin/pmcstat/pmcpl_annotate.h41
-rw-r--r--usr.sbin/pmcstat/pmcpl_annotate_cg.c127
-rw-r--r--usr.sbin/pmcstat/pmcpl_annotate_cg.h42
-rw-r--r--usr.sbin/pmcstat/pmcpl_callgraph.c687
-rw-r--r--usr.sbin/pmcstat/pmcpl_callgraph.h67
-rw-r--r--usr.sbin/pmcstat/pmcpl_calltree.c1197
-rw-r--r--usr.sbin/pmcstat/pmcpl_calltree.h42
-rw-r--r--usr.sbin/pmcstat/pmcpl_gprof.c566
-rw-r--r--usr.sbin/pmcstat/pmcpl_gprof.h47
-rw-r--r--usr.sbin/pmcstat/pmcstat.8500
-rw-r--r--usr.sbin/pmcstat/pmcstat.c1537
-rw-r--r--usr.sbin/pmcstat/pmcstat.h187
-rw-r--r--usr.sbin/pmcstat/pmcstat_log.c2238
-rw-r--r--usr.sbin/pmcstat/pmcstat_log.h199
-rw-r--r--usr.sbin/pmcstat/pmcstat_top.h75
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(&reg, spec, REG_EXTENDED|REG_NOSUB)) != 0) {
+ regerror(rv, &reg, 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(&reg, kp->ki_comm, 1, &regmatch, 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, &reg, errbuf, sizeof(errbuf));
+ errx(EX_SOFTWARE, "ERROR: Regex evalation failed: %s",
+ errbuf);
+ }
+ }
+
+ regfree(&reg);
+
+ 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_ */
OpenPOWER on IntegriCloud