[Midnightbsd-cvs] src: usr.sbin/pmcstat: merge

laffer1 at midnightbsd.org laffer1 at midnightbsd.org
Mon Nov 24 08:46:51 EST 2008


Log Message:
-----------
merge

Modified Files:
--------------
    src/usr.sbin/pmcstat:
        Makefile (r1.1.1.1 -> r1.2)
        pmcstat.8 (r1.1.1.1 -> r1.2)
        pmcstat.c (r1.1.1.1 -> r1.2)
        pmcstat.h (r1.1.1.1 -> r1.2)
        pmcstat_log.c (r1.1.1.1 -> r1.2)

-------------- next part --------------
Index: pmcstat.c
===================================================================
RCS file: /home/cvs/src/usr.sbin/pmcstat/pmcstat.c,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -L usr.sbin/pmcstat/pmcstat.c -L usr.sbin/pmcstat/pmcstat.c -u -r1.1.1.1 -r1.2
--- usr.sbin/pmcstat/pmcstat.c
+++ usr.sbin/pmcstat/pmcstat.c
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2003-2005, Joseph Koshy
+ * Copyright (c) 2003-2007, Joseph Koshy
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -25,25 +25,31 @@
  */
 
 #include <sys/cdefs.h>
-__FBSDID("$FreeBSD: src/usr.sbin/pmcstat/pmcstat.c,v 1.6.2.3 2005/09/13 15:23:26 jkoshy Exp $");
+__FBSDID("$FreeBSD: src/usr.sbin/pmcstat/pmcstat.c,v 1.17 2007/04/27 12:09:31 jkoshy Exp $");
 
 #include <sys/types.h>
 #include <sys/event.h>
+#include <sys/param.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 <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>
@@ -70,18 +76,62 @@
  * 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.
+ *					- Recieves signal, attempts exec().
+ *
+ * After this point normal processing can happen.
  */
 
 /* Globals */
 
 int	pmcstat_interrupt = 0;
 int	pmcstat_displayheight = DEFAULT_DISPLAY_HEIGHT;
-int	pmcstat_pipefd[NPIPEFD];
+int	pmcstat_sockpair[NSOCKPAIRFD];
 int	pmcstat_kq;
+kvm_t	*pmcstat_kvm;
+struct kinfo_proc *pmcstat_plist;
+
+void
+pmcstat_attach_pmcs(struct pmcstat_args *a)
+{
+	struct pmcstat_ev *ev;
+	struct pmcstat_target *pt;
+	int count;
+
+	/* Attach all process PMCs to target processes. */
+	count = 0;
+	STAILQ_FOREACH(ev, &a->pa_events, ev_next) {
+		if (PMC_IS_SYSTEM_MODE(ev->ev_mode))
+			continue;
+		SLIST_FOREACH(pt, &a->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.");
+}
 
-/*
- * cleanup
- */
 
 void
 pmcstat_cleanup(struct pmcstat_args *a)
@@ -89,14 +139,14 @@
 	struct pmcstat_ev *ev, *tmp;
 
 	/* release allocated PMCs. */
-	STAILQ_FOREACH_SAFE(ev, &a->pa_head, ev_next, tmp)
+	STAILQ_FOREACH_SAFE(ev, &a->pa_events, ev_next, tmp)
 	    if (ev->ev_pmcid != PMC_ID_INVALID) {
 		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(&a->pa_head, ev, pmcstat_ev, ev_next);
+		STAILQ_REMOVE(&a->pa_events, ev, pmcstat_ev, ev_next);
 		free(ev);
 	    }
 
@@ -110,7 +160,197 @@
 	}
 
 	if (a->pa_flags & (FLAG_HAS_PIPE | FLAG_HAS_OUTPUT_LOGFILE))
-		pmcstat_shutdown_logging();
+		pmcstat_shutdown_logging(a);
+}
+
+void
+pmcstat_clone_event_descriptor(struct pmcstat_args *a, struct pmcstat_ev *ev,
+    uint32_t cpumask)
+{
+	int cpu;
+	struct pmcstat_ev *ev_clone;
+
+	while ((cpu = ffs(cpumask)) > 0) {
+		cpu--;
+
+		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(&a->pa_events, ev_clone, ev_next);
+
+		cpumask &= ~(1 << cpu);
+	}
+}
+
+void
+pmcstat_create_process(struct pmcstat_args *a)
+{
+	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(*a->pa_argv, a->pa_argv);
+		/* and if that fails, notify the parent */
+		kill(getppid(), SIGCHLD);
+		err(EX_OSERR, "ERROR: execvp \"%s\" failed", *a->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(&a->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(struct pmcstat_args *a, 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(&a->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));
+	}
+
+	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(&a->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*/
+}
+
+uint32_t
+pmcstat_get_cpumask(const char *cpuspec)
+{
+	uint32_t cpumask;
+	int cpu;
+	const char *s;
+	char *end;
+
+	s = cpuspec;
+	cpumask = 0ULL;
+
+	do {
+		cpu = strtol(s, &end, 0);
+		if (cpu < 0 || end == s)
+			errx(EX_USAGE, "ERROR: Illegal CPU specification "
+			    "\"%s\".", cpuspec);
+		cpumask |= (1 << cpu);
+		s = end + strspn(end, ", \t");
+	} while (*s);
+
+	return (cpumask);
+}
+
+void
+pmcstat_kill_process(struct pmcstat_args *a)
+{
+	struct pmcstat_target *pt;
+
+	assert(a->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(&a->pa_targets);
+	assert(pt != NULL);
+
+	if (kill(pt->pt_pid, SIGINT) != 0)
+		err(EX_OSERR, "ERROR: cannot signal child process");
 }
 
 void
@@ -118,7 +358,7 @@
 {
 	struct pmcstat_ev *ev;
 
-	STAILQ_FOREACH(ev, &args.pa_head, ev_next) {
+	STAILQ_FOREACH(ev, &args.pa_events, ev_next) {
 
 	    assert(ev->ev_pmcid != PMC_ID_INVALID);
 
@@ -136,24 +376,27 @@
 pmcstat_print_headers(struct pmcstat_args *a)
 {
 	struct pmcstat_ev *ev;
-	int c;
+	int c, w;
 
 	(void) fprintf(a->pa_printfile, PRINT_HEADER_PREFIX);
 
-	STAILQ_FOREACH(ev, &a->pa_head, ev_next) {
+	STAILQ_FOREACH(ev, &a->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(a->pa_printfile, "%*s%c/%*s ",
-			    ev->ev_fieldskip, "", c,
-			    ev->ev_fieldwidth - ev->ev_fieldskip - 2,
+		if (ev->ev_fieldskip != 0)
+			(void) fprintf(a->pa_printfile, "%*s",
+			    ev->ev_fieldskip, "");
+		w = ev->ev_fieldwidth - ev->ev_fieldskip - 2;
+
+		if (c == 's')
+			(void) fprintf(a->pa_printfile, "s/%02d/%-*s ",
+			    ev->ev_cpu, w-3, ev->ev_name);
+		else
+			(void) fprintf(a->pa_printfile, "p/%*s ", w,
 			    ev->ev_name);
-		} else
-			(void) fprintf(a->pa_printfile, "%c/%*s ",
-			    c, ev->ev_fieldwidth - 2, ev->ev_name);
 	}
 
 	(void) fflush(a->pa_printfile);
@@ -168,7 +411,7 @@
 
 	extra_width = sizeof(PRINT_HEADER_PREFIX) - 1;
 
-	STAILQ_FOREACH(ev, &a->pa_head, ev_next) {
+	STAILQ_FOREACH(ev, &a->pa_events, ev_next) {
 
 		/* skip sampling mode counters */
 		if (PMC_IS_SAMPLING_MODE(ev->ev_mode))
@@ -223,87 +466,13 @@
  */
 
 void
-pmcstat_setup_process(struct pmcstat_args *a)
+pmcstat_start_process(void)
 {
-	char token;
-	struct pmcstat_ev *ev;
-	struct kevent kev;
-
-	if (a->pa_flags & FLAG_HAS_PID) {
-		STAILQ_FOREACH(ev, &a->pa_head, ev_next)
-		    if (pmc_attach(ev->ev_pmcid, a->pa_pid) != 0)
-			    err(EX_OSERR, "ERROR: cannot attach pmc \"%s\" to "
-				"process %d", ev->ev_name, (int) a->pa_pid);
-	} else {
-
-		/*
-		 * We need to fork a new process and startup the child
-		 * using execvp().  Before doing the exec() the child
-		 * process reads its pipe for a token so that the parent
-		 * can finish doing its pmc_attach() calls.
-		 */
-		if (pipe(pmcstat_pipefd) < 0)
-			err(EX_OSERR, "ERROR: cannot create pipe");
-
-		switch (a->pa_pid = fork()) {
-		case -1:
-			err(EX_OSERR, "ERROR: cannot fork");
-			/*NOTREACHED*/
-
-		case 0:		/* child */
-
-			/* wait for our parent to signal us */
-			(void) close(pmcstat_pipefd[WRITEPIPEFD]);
-			if (read(pmcstat_pipefd[READPIPEFD], &token, 1) < 0)
-				err(EX_OSERR, "ERROR (child): cannot read "
-				    "token");
-			(void) close(pmcstat_pipefd[READPIPEFD]);
-
-			/* exec() the program requested */
-			execvp(*a->pa_argv, a->pa_argv);
-			/* and if that fails, notify the parent */
-			kill(getppid(), SIGCHLD);
-			err(EX_OSERR, "ERROR: execvp \"%s\" failed",
-			    *a->pa_argv);
-			/*NOTREACHED*/
-
-		default:	/* parent */
-
-			(void) close(pmcstat_pipefd[READPIPEFD]);
-
-			/* attach all our PMCs to the child */
-			STAILQ_FOREACH(ev, &args.pa_head, ev_next)
-			    if (PMC_IS_VIRTUAL_MODE(ev->ev_mode) &&
-				pmc_attach(ev->ev_pmcid, a->pa_pid) != 0)
-				    err(EX_OSERR, "ERROR: cannot attach pmc "
-					"\"%s\" to process %d", ev->ev_name,
-					(int) a->pa_pid);
-
-		}
-	}
+	/* Signal the child to proceed. */
+	if (write(pmcstat_sockpair[PARENTSOCKET], "!", 1) != 1)
+		err(EX_OSERR, "ERROR (parent): write of token failed");
 
-	/* Ask to be notified via a kevent when the target process exits */
-	EV_SET(&kev, a->pa_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",
-		    a->pa_pid);
-	return;
-}
-
-void
-pmcstat_start_process(struct pmcstat_args *a)
-{
-
-	/* nothing to do: target is already running */
-	if (a->pa_flags & FLAG_HAS_PID)
-		return;
-
-	/* write token to child to state that we are ready */
-	if (write(pmcstat_pipefd[WRITEPIPEFD], "+", 1) != 1)
-		err(EX_OSERR, "ERROR: write failed");
-
-	(void) close(pmcstat_pipefd[WRITEPIPEFD]);
+	(void) close(pmcstat_sockpair[PARENTSOCKET]);
 }
 
 void
@@ -317,20 +486,24 @@
 	    "\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 -M file\t print executable/gmon file map to \"file\"\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 -W\t\t (toggle) show counts per context switch\n"
-	    "\t -c cpu\t\t set cpu for subsequent system-wide PMCs\n"
+	    "\t -c cpu-list\t set cpus for subsequent system-wide PMCs\n"
 	    "\t -d\t\t (toggle) track descendants\n"
 	    "\t -g\t\t produce gprof(1) compatible profiles\n"
-	    "\t -k file\t set the path to the kernel\n"
+	    "\t -k dir\t\t set the path to the kernel\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 pid\t\t attach to running process with pid \"pid\"\n"
+	    "\t -v\t\t increase verbosity\n"
 	    "\t -w secs\t set printing time interval"
 	);
 }
@@ -344,21 +517,25 @@
 {
 	double interval;
 	int option, npmc, ncpu;
-	int c, current_cpu, current_sampling_count;
+	int c, check_driver_stats, current_cpu, current_sampling_count;
 	int do_print, do_descendants;
 	int do_logproccsw, do_logprocexit;
+	size_t dummy;
 	int pipefd[2];
 	int use_cumulative_counts;
-	pid_t pid;
-	char *end;
+	uint32_t cpumask;
+	char *end, *tmp;
 	const char *errmsg;
 	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_cpu 		= 0;
 	current_sampling_count  = DEFAULT_SAMPLE_COUNT;
 	do_descendants          = 0;
@@ -367,18 +544,27 @@
 	use_cumulative_counts   = 0;
 	args.pa_required	= 0;
 	args.pa_flags		= 0;
-	args.pa_pid		= (pid_t) -1;
+	args.pa_verbosity	= 1;
 	args.pa_logfd		= -1;
+	args.pa_fsroot		= "";
+	args.pa_kernel		= strdup("/boot/kernel");
 	args.pa_samplesdir	= ".";
-	args.pa_kernel		= "/boot/kernel/kernel";
 	args.pa_printfile	= stderr;
 	args.pa_interval	= DEFAULT_WAIT_INTERVAL;
-	STAILQ_INIT(&args.pa_head);
-
+	args.pa_mapfilename	= NULL;
+	STAILQ_INIT(&args.pa_events);
+	SLIST_INIT(&args.pa_targets);
+	bzero(&ds_start, sizeof(ds_start));
+	bzero(&ds_end, sizeof(ds_end));
 	ev = NULL;
 
-	while ((option = getopt(argc, argv, "CD:EO:P:R:S:Wc:dgk:n:o:p:s:t:w:"))
-	    != -1)
+	dummy = sizeof(ncpu);
+	if (sysctlbyname("hw.ncpu", &ncpu, &dummy, NULL, 0) < 0)
+		err(EX_OSERR, "ERROR: Cannot determine #cpus");
+	cpumask = (1 << ncpu) - 1;
+
+	while ((option = getopt(argc, argv,
+	    "CD:EM:O:P:R:S:Wc:dgk:n:o:p:qr:s:t:vw:")) != -1)
 		switch (option) {
 		case 'C':	/* cumulative values */
 			use_cumulative_counts = !use_cumulative_counts;
@@ -386,11 +572,12 @@
 			break;
 
 		case 'c':	/* CPU */
-			current_cpu = strtol(optarg, &end, 0);
-			if (*end != '\0' || current_cpu < 0)
-				errx(EX_USAGE,
-				    "ERROR: Illegal CPU number \"%s\".",
-				    optarg);
+
+			if (optarg[0] == '*' && optarg[1] == '\0')
+				cpumask = (1 << ncpu) - 1;
+			else
+				cpumask = pmcstat_get_cpumask(optarg);
+
 			args.pa_required |= FLAG_HAS_SYSTEM_PMCS;
 			break;
 
@@ -400,7 +587,7 @@
 				    optarg);
 			if (!S_ISDIR(sb.st_mode))
 				errx(EX_USAGE, "ERROR: \"%s\" is not a "
-				    "directory", optarg);
+				    "directory.", optarg);
 			args.pa_samplesdir = optarg;
 			args.pa_flags     |= FLAG_HAS_SAMPLESDIR;
 			args.pa_required  |= FLAG_DO_GPROF;
@@ -416,7 +603,8 @@
 			break;
 
 		case 'k':	/* pathname to the kernel */
-			args.pa_kernel = optarg;
+			free(args.pa_kernel);
+			args.pa_kernel = strdup(optarg);
 			args.pa_required |= FLAG_DO_GPROF;
 			args.pa_flags    |= FLAG_HAS_KERNELPATH;
 			break;
@@ -427,6 +615,10 @@
 			    FLAG_HAS_COUNTING_PMCS | FLAG_HAS_OUTPUT_LOGFILE);
 			break;
 
+		case 'M':	/* mapfile */
+			args.pa_mapfilename = optarg;
+			break;
+
 		case 'p':	/* process virtual counting PMC */
 		case 's':	/* system-wide counting PMC */
 		case 'P':	/* process virtual sampling PMC */
@@ -444,7 +636,7 @@
 			if (option == 'P' || option == 'p') {
 				args.pa_flags |= FLAG_HAS_PROCESS_PMCS;
 				args.pa_required |= (FLAG_HAS_COMMANDLINE |
-				    FLAG_HAS_PID);
+				    FLAG_HAS_TARGET);
 			}
 
 			if (option == 'P' || option == 'S') {
@@ -467,7 +659,7 @@
 				ev->ev_count = -1;
 
 			if (option == 'S' || option == 's')
-				ev->ev_cpu = current_cpu;
+				ev->ev_cpu = ffs(cpumask) - 1;
 			else
 				ev->ev_cpu = PMC_CPU_ANY;
 
@@ -490,7 +682,11 @@
 			(void) strncpy(ev->ev_name, optarg, c);
 			*(ev->ev_name + c) = '\0';
 
-			STAILQ_INSERT_TAIL(&args.pa_head, ev, ev_next);
+			STAILQ_INSERT_TAIL(&args.pa_events, ev, ev_next);
+
+			if (option == 's' || option == 'S')
+				pmcstat_clone_event_descriptor(&args, ev,
+				    cpumask & ~(1 << ev->ev_cpu));
 
 			break;
 
@@ -520,6 +716,14 @@
 			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_logparser != NULL)
 				errx(EX_USAGE, "ERROR: option -R may only be "
@@ -530,15 +734,15 @@
 			args.pa_flags |= FLAG_READ_LOGFILE;
 			break;
 
-		case 't':	/* target pid */
-			pid = strtol(optarg, &end, 0);
-			if (*end != '\0' || pid <= 0)
-				errx(EX_USAGE, "ERROR: Illegal pid value "
-				    "\"%s\".", optarg);
+		case 't':	/* target pid or process name */
+			pmcstat_find_targets(&args, optarg);
 
-			args.pa_flags |= FLAG_HAS_PID;
+			args.pa_flags |= FLAG_HAS_TARGET;
 			args.pa_required |= FLAG_HAS_PROCESS_PMCS;
-			args.pa_pid = pid;
+			break;
+
+		case 'v':	/* verbose */
+			args.pa_verbosity++;
 			break;
 
 		case 'w':	/* wait interval */
@@ -583,32 +787,32 @@
 		errmsg = NULL;
 		if (args.pa_flags & FLAG_HAS_COMMANDLINE)
 			errmsg = "a command line specification";
-		else if (args.pa_flags & FLAG_HAS_PID)
+		else if (args.pa_flags & FLAG_HAS_TARGET)
 			errmsg = "option -t";
-		else if (!STAILQ_EMPTY(&args.pa_head))
+		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_head))
+	} 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_PID) &&
+	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_PID)) == 0)
+	    (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_PID)) &&
-	    (args.pa_flags & (FLAG_HAS_COMMANDLINE | FLAG_HAS_PID)) == 0)
+	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.");
 
@@ -636,12 +840,6 @@
 		errx(EX_USAGE, "ERROR: options -n and -O require at least "
 		    "one sampling mode PMC to be specified.");
 
-	if ((args.pa_flags & (FLAG_HAS_PID | FLAG_HAS_COMMANDLINE)) ==
-	    (FLAG_HAS_PID | FLAG_HAS_COMMANDLINE))
-		errx(EX_USAGE,
-		    "ERROR: option -t cannot be specified with a command "
-		    "line.");
-
 	/* check if -g is being used correctly */
 	if ((args.pa_flags & FLAG_DO_GPROF) &&
 	    !(args.pa_flags & (FLAG_HAS_SAMPLING_PMCS|FLAG_READ_LOGFILE)))
@@ -655,14 +853,22 @@
 		    "ERROR: option -O is used only with options "
 		    "-E, -P, -S and -W.");
 
-	/* -D dir and -k kernel path require -g */
+	/* -D dir and -k kernel path require -g or -R */
 	if ((args.pa_flags & FLAG_HAS_KERNELPATH) &&
-	    ((args.pa_flags & FLAG_DO_GPROF) == 0))
-	    errx(EX_USAGE, "ERROR: option -k is only used with -g.");
+	    (args.pa_flags & FLAG_DO_GPROF) == 0 &&
+	    (args.pa_flags & FLAG_READ_LOGFILE) == 0)
+	    errx(EX_USAGE, "ERROR: option -k is only used with -g/-R.");
 
 	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.");
+	    (args.pa_flags & FLAG_DO_GPROF) == 0 &&
+	    (args.pa_flags & FLAG_READ_LOGFILE) == 0)
+	    errx(EX_USAGE, "ERROR: option -D is only used with -g/-R.");
+
+	/* -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
@@ -671,10 +877,39 @@
 	 */
 	if ((args.pa_flags & FLAG_HAS_COUNTING_PMCS) &&
 	    (args.pa_flags & FLAG_HAS_SAMPLING_PMCS) &&
-	    ((args.pa_required & FLAG_HAS_OUTPUT_LOGFILE) == 0))
+	    ((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 "-k kerneldir" was specified, and if whether 'kerneldir'
+	 * actually refers to a a file.  If so, use `dirname path` to determine
+	 * the kernel directory.
+	 */
+	if (args.pa_flags & FLAG_HAS_KERNELPATH) {
+		(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've been asked to process a log file, do that and exit */
 	if (args.pa_flags & FLAG_READ_LOGFILE) {
 		/*
@@ -685,13 +920,12 @@
 			args.pa_flags |= FLAG_DO_PRINT;
 
 		pmcstat_initialize_logging(&args);
-		if ((args.pa_logfd = pmcstat_open(args.pa_inputpath,
-		    PMCSTAT_OPEN_FOR_READ)) < 0)
-			err(EX_OSERR, "ERROR: Cannot open \"%s\" for "
-			    "reading", args.pa_inputpath);
+		args.pa_logfd = pmcstat_open_log(args.pa_inputpath,
+		    PMCSTAT_OPEN_FOR_READ);
 		if ((args.pa_logparser = pmclog_open(args.pa_logfd)) == NULL)
 			err(EX_OSERR, "ERROR: Cannot create parser");
 		pmcstat_process_log(&args);
+		pmcstat_shutdown_logging(&args);
 		exit(EX_OK);
 	}
 
@@ -700,10 +934,6 @@
 		err(EX_UNAVAILABLE,
 		    "ERROR: Initialization of the pmc(3) library failed");
 
-	if ((ncpu = pmc_ncpu()) < 0)
-		err(EX_OSERR, "ERROR: Cannot determine the number CPUs "
-		    "on the system");
-
 	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);
@@ -717,12 +947,10 @@
 	 * consumer via a pipe.
 	 */
 	if (args.pa_required & FLAG_HAS_OUTPUT_LOGFILE) {
-		if (args.pa_outputpath) {
-			if ((args.pa_logfd = pmcstat_open(args.pa_outputpath,
-			    PMCSTAT_OPEN_FOR_WRITE)) < 0)
-				err(EX_OSERR, "ERROR: Cannot open \"%s\" for "
-				    "writing", args.pa_outputpath);
-		} else {
+		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.
@@ -749,11 +977,15 @@
 			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);
+
 	/*
 	 * Allocate PMCs.
 	 */
 
-	STAILQ_FOREACH(ev, &args.pa_head, ev_next) {
+	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 "
@@ -768,15 +1000,18 @@
 	}
 
 	/* compute printout widths */
-	STAILQ_FOREACH(ev, &args.pa_head, ev_next) {
+	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|' */
+		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;
@@ -836,15 +1071,32 @@
 	}
 
 	/* attach PMCs to the target process, starting it if specified */
-	if (args.pa_flags & (FLAG_HAS_PID | FLAG_HAS_COMMANDLINE))
-		pmcstat_setup_process(&args);
+	if (args.pa_flags & FLAG_HAS_COMMANDLINE)
+		pmcstat_create_process(&args);
+
+	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) {
+		if (SLIST_EMPTY(&args.pa_targets))
+			errx(EX_DATAERR, "ERROR: No matching target "
+			    "processes.");
+		else
+			pmcstat_attach_pmcs(&args);
+
+		if (pmcstat_kvm) {
+			kvm_close(pmcstat_kvm);
+			pmcstat_kvm = NULL;
+		}
+	}
 
 	/* start the pmcs */
 	pmcstat_start_pmcs(&args);
 
 	/* start the (commandline) process if needed */
 	if (args.pa_flags & FLAG_HAS_COMMANDLINE)
-		pmcstat_start_process(&args);
+		pmcstat_start_process();
 
 	/* initialize logging if printing the configured log */
 	if ((args.pa_flags & FLAG_DO_PRINT) &&
@@ -918,9 +1170,7 @@
 			} else if (kev.ident == SIGINT) {
 				/* Kill the child process if we started it */
 				if (args.pa_flags & FLAG_HAS_COMMANDLINE)
-					if (kill(args.pa_pid, SIGINT) != 0)
-						err(EX_OSERR, "ERROR: cannot "
-						    "signal child process");
+					pmcstat_kill_process(&args);
 				runstate = PMCSTAT_FINISHED;
 			} else if (kev.ident == SIGWINCH) {
 				if (ioctl(fileno(args.pa_printfile),
@@ -956,5 +1206,25 @@
 
 	pmcstat_cleanup(&args);
 
-	return 0;
+	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: some samples were dropped.  Please "
+			    "consider tuning the \"kern.hwpmc.nsamples\" "
+			    "tunable.");
+		if (ds_start.pm_buffer_requests_failed !=
+		    ds_end.pm_buffer_requests_failed &&
+		    args.pa_verbosity > 0)
+			warnx("WARNING: some events were discarded.  Please "
+			    "consider tuning the \"kern.hwpmc.nbuffers\" "
+			    "tunable.");
+	}
+
+	exit(EX_OK);
 }
Index: pmcstat_log.c
===================================================================
RCS file: /home/cvs/src/usr.sbin/pmcstat/pmcstat_log.c,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -L usr.sbin/pmcstat/pmcstat_log.c -L usr.sbin/pmcstat/pmcstat_log.c -u -r1.1.1.1 -r1.2
--- usr.sbin/pmcstat/pmcstat_log.c
+++ usr.sbin/pmcstat/pmcstat_log.c
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2005, Joseph Koshy
+ * Copyright (c) 2005-2006, Joseph Koshy
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -24,27 +24,14 @@
  * SUCH DAMAGE.
  */
 
-#include <sys/cdefs.h>
-__FBSDID("$FreeBSD: src/usr.sbin/pmcstat/pmcstat_log.c,v 1.3.2.1 2005/09/17 13:04:10 jkoshy Exp $");
-
 /*
- * Transform a hwpmc(4) log into human readable form and into gprof(1)
- * compatible profiles.
- *
- * Each executable object encountered in the log gets one 'gmon.out'
- * profile per PMC.  We currently track:
- * 	- program executables
- *	- shared libraries loaded by the runtime loader
- *	- the runtime loader itself
- *	- the kernel.
- * We do not track shared objects mapped in by dlopen() yet (this
- * needs additional support from hwpmc(4)).
- *
- * 'gmon.out' profiles generated for a given sampling PMC are
- * aggregates of all the samples for that particular executable
- * object.
+ * Transform a hwpmc(4) log into human readable form, and into
+ * gprof(1) compatible profiles.
  */
 
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.sbin/pmcstat/pmcstat_log.c,v 1.10 2006/04/05 15:12:25 jkoshy Exp $");
+
 #include <sys/param.h>
 #include <sys/endian.h>
 #include <sys/gmon.h>
@@ -53,6 +40,7 @@
 #include <sys/mman.h>
 #include <sys/pmc.h>
 #include <sys/queue.h>
+#include <sys/socket.h>
 #include <sys/stat.h>
 #include <sys/wait.h>
 
@@ -60,9 +48,11 @@
 
 #include <assert.h>
 #include <err.h>
+#include <errno.h>
 #include <fcntl.h>
 #include <libgen.h>
 #include <limits.h>
+#include <netdb.h>
 #include <pmc.h>
 #include <pmclog.h>
 #include <sysexits.h>
@@ -78,19 +68,47 @@
 #define	max(A,B)		((A) > (B) ? (A) : (B))
 
 /*
- * 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 comparision instead of with
- * strcmp().
+ * 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_close_log()			finish processing an event log
+ *
+ * IMPLEMENTATION OF GMON OUTPUT
+ *
+ * We correlate each 'sample' 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 such 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.
+ *
+ * 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.
  */
-struct pmcstat_string {
-	LIST_ENTRY(pmcstat_string)	ps_next;	/* hash link */
-	int		ps_len;
-	int		ps_hash;
-	const char	*ps_string;
-};
 
-static LIST_HEAD(,pmcstat_string)	pmcstat_string_hash[PMCSTAT_NHASH];
+typedef const void *pmcstat_interned_string;
 
 /*
  * 'pmcstat_pmcrecord' is a mapping from PMC ids to human-readable
@@ -99,8 +117,8 @@
 
 struct pmcstat_pmcrecord {
 	LIST_ENTRY(pmcstat_pmcrecord)	pr_next;
-	pmc_id_t	pr_pmcid;
-	const char 	*pr_pmcname;
+	pmc_id_t			pr_pmcid;
+	pmcstat_interned_string	pr_pmcname;
 };
 
 static LIST_HEAD(,pmcstat_pmcrecord)	pmcstat_pmcs =
@@ -114,19 +132,18 @@
 
 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 */
-	const char	*pgf_name;	/* pathname of gmon.out file */
+	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 */
 };
 
-static TAILQ_HEAD(,pmcstat_gmonfile)	pmcstat_gmonfiles =
-	TAILQ_HEAD_INITIALIZER(pmcstat_gmonfiles);
-
 /*
  * A 'pmcstat_image' structure describes an executable program on
- * disk.  'pi_internedpath' is a cookie representing the pathname of
+ * 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
@@ -134,31 +151,52 @@
  */
 
 enum pmcstat_image_type {
-	PMCSTAT_IMAGE_UNKNOWN = 0,
-	PMCSTAT_IMAGE_ELF,
-	PMCSTAT_IMAGE_AOUT
+	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 */
-	const char	*pi_internedpath;	/* cookie */
-	const char	*pi_samplename;		/* sample path name */
+	pmcstat_interned_string	pi_execpath;/* cookie */
+	pmcstat_interned_string pi_samplename;  /* sample path 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 */
-	int		pi_isdynamic;		/* whether a dynamic object */
-	const char	*pi_dynlinkerpath;	/* path in .interp section */
+	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 */
 
+	/*
+	 * An image can be associated with one or more gmon.out files;
+	 * one per PMC.
+	 */
 	LIST_HEAD(,pmcstat_gmonfile) pi_gmlist;
 };
 
+/*
+ * All image descriptors are kept in a hash table.
+ */
 static LIST_HEAD(,pmcstat_image)	pmcstat_image_hash[PMCSTAT_NHASH];
 static TAILQ_HEAD(,pmcstat_image)	pmcstat_image_lru =
 	TAILQ_HEAD_INITIALIZER(pmcstat_image_lru);
 
+/*
+ * 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;
@@ -167,7 +205,15 @@
 };
 
 /*
- * A 'pmcstat_process' structure tracks processes.
+ * 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 {
@@ -178,45 +224,200 @@
 	TAILQ_HEAD(,pmcstat_pcmap) pp_map;	/* address range map */
 };
 
+#define	PMCSTAT_ALLOCATE		1
+
+/*
+ * All process descriptors are kept in a hash table.
+ */
 static LIST_HEAD(,pmcstat_process) pmcstat_process_hash[PMCSTAT_NHASH];
 
 static struct pmcstat_process *pmcstat_kernproc; /* kernel 'process' */
 
+/* Misc. statistics */
+static 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_unknown_offset;	/* #samples not in any map */
+	int ps_samples_indeterminable;	/* #samples in indeterminable images */
+} pmcstat_stats;
+
 /*
  * Prototypes
  */
 
 static void	pmcstat_gmon_create_file(struct pmcstat_gmonfile *_pgf,
     struct pmcstat_image *_image);
-static const char *pmcstat_gmon_create_name(const char *_sd,
+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_image *pmcstat_image_from_path(const char *_path);
-static enum pmcstat_image_type pmcstat_image_get_type(const char *_p);
-static void pmcstat_image_get_elf_params(struct pmcstat_image *_image);
+static void pmcstat_image_determine_type(struct pmcstat_image *_image,
+    struct pmcstat_args *_a);
+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,
+    struct pmcstat_args *_a);
+static void pmcstat_image_get_elf_params(struct pmcstat_image *_image,
+    struct pmcstat_args *_a);
 static void	pmcstat_image_increment_bucket(struct pmcstat_pcmap *_pcm,
     uintfptr_t _pc, pmc_id_t _pmcid, struct pmcstat_args *_a);
 static void	pmcstat_image_link(struct pmcstat_process *_pp,
-    struct pmcstat_image *_i, uintfptr_t _lpc, uintfptr_t _hpc);
+    struct pmcstat_image *_i, uintfptr_t _lpc);
 
-static void	pmcstat_pmcid_add(pmc_id_t _pmcid, const char *_name,
-    struct pmcstat_args *_a);
+static void	pmcstat_pmcid_add(pmc_id_t _pmcid,
+    pmcstat_interned_string _name, struct pmcstat_args *_a);
 static const char *pmcstat_pmcid_to_name(pmc_id_t _pmcid);
 
-static void	pmcstat_process_add_elf_image(struct pmcstat_process *_pp,
-    const char *_path, uintfptr_t _entryaddr);
+static void	pmcstat_process_aout_exec(struct pmcstat_process *_pp,
+    struct pmcstat_image *_image, uintfptr_t _entryaddr,
+    struct pmcstat_args *_a);
+static void	pmcstat_process_elf_exec(struct pmcstat_process *_pp,
+    struct pmcstat_image *_image, uintfptr_t _entryaddr,
+    struct pmcstat_args *_a);
 static void	pmcstat_process_exec(struct pmcstat_process *_pp,
-    const char *_path, uintfptr_t _entryaddr);
-static struct pmcstat_process *pmcstat_process_lookup(pid_t _pid, int _allocate);
+    pmcstat_interned_string _path, uintfptr_t _entryaddr,
+    struct pmcstat_args *_ao);
+static struct pmcstat_process *pmcstat_process_lookup(pid_t _pid,
+    int _allocate);
 static struct pmcstat_pcmap *pmcstat_process_find_map(
     struct pmcstat_process *_p, uintfptr_t _pc);
 
 static int	pmcstat_string_compute_hash(const char *_string);
-static const char *pmcstat_string_intern(const char *_s);
-static struct pmcstat_string *pmcstat_string_lookup(const char *_s);
+static void pmcstat_string_initialize(void);
+static pmcstat_interned_string pmcstat_string_intern(const char *_s);
+static pmcstat_interned_string pmcstat_string_lookup(const char *_s);
+static int	pmcstat_string_lookup_hash(pmcstat_interned_string _is);
+static void pmcstat_string_shutdown(void);
+static const char *pmcstat_string_unintern(pmcstat_interned_string _is);
+
+
+/*
+ * 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 comparision 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];
+
+/*
+ * Compute a 'hash' value for a string.
+ */
+
+static int
+pmcstat_string_compute_hash(const char *s)
+{
+	int hash;
+
+	for (hash = 0; *s; s++)
+		hash ^= *s;
+
+	return (hash & PMCSTAT_HASH_MASK);
+}
+
+/*
+ * Intern a copy of string 's', and return a pointer to the
+ * interned structure.
+ */
+
+static 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);
+}
+
+static const char *
+pmcstat_string_unintern(pmcstat_interned_string str)
+{
+	const char *s;
+
+	s = ((const struct pmcstat_string *) str)->ps_string;
+	return (s);
+}
+
+static 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);
+		}
+}
 
 /*
  * Create a gmon.out file and size it.
@@ -229,11 +430,13 @@
 	int fd;
 	size_t count;
 	struct gmonhdr gm;
+	const char *pathname;
 	char buffer[DEFAULT_BUFFER_SIZE];
 
-	if ((fd = open(pgf->pgf_name, O_RDWR|O_NOFOLLOW|O_CREAT,
+	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\"", pgf->pgf_name);
+		err(EX_OSERR, "ERROR: Cannot open \"%s\"", pathname);
 
 	gm.lpc = image->pi_start;
 	gm.hpc = image->pi_end;
@@ -261,15 +464,22 @@
 	if (write(fd, &buffer, count) < 0)
 		goto error;
 
+	/* TODO size the arc table */
+
 	(void) close(fd);
 
 	return;
 
  error:
-	err(EX_OSERR, "ERROR: Cannot write \"%s\"", pgf->pgf_name);
+	err(EX_OSERR, "ERROR: Cannot write \"%s\"", pathname);
 }
 
-const char *
+/*
+ * 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)
 {
@@ -279,34 +489,40 @@
 	pmcname = pmcstat_pmcid_to_name(pmcid);
 
 	(void) snprintf(fullpath, sizeof(fullpath),
-	    "%s/%s/%s", samplesdir, pmcname, image->pi_samplename);
+	    "%s/%s/%s", samplesdir, pmcname,
+	    pmcstat_string_unintern(image->pi_samplename));
 
-	return pmcstat_string_intern(fullpath);
+	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(pgf->pgf_name, O_RDWR | O_NOFOLLOW, 0)) < 0)
-		err(EX_OSERR, "ERROR: cannot open \"%s\"",
-		    pgf->pgf_name);
+	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)
-		/* XXX unmap a few files and try again? */
-		err(EX_OSERR, "ERROR: cannot map \"%s\"", pgf->pgf_name);
+		err(EX_OSERR, "ERROR: cannot map \"%s\"", pathname);
 
 	(void) close(fd);
 }
 
 /*
- * Unmap the data mapped from a gmon.out file.
+ * Unmap a gmon.out file after sync'ing its data to disk.
  */
 
 static void
@@ -318,109 +534,262 @@
 	pgf->pgf_gmondata = NULL;
 }
 
+/*
+ * 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,
+    struct pmcstat_args *a)
+{
+	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",
+	    a->pa_fsroot, path);
+
+	if ((fd = open(buffer, O_RDONLY, 0)) < 0 ||
+	    (nbytes = read(fd, &ex, sizeof(ex))) < 0) {
+		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;
+}
+
+/*
+ * 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)
+pmcstat_image_get_elf_params(struct pmcstat_image *image,
+    struct pmcstat_args *a)
 {
 	int fd, i;
-	struct stat st;
+	const char *path;
 	void *mapbase;
 	uintfptr_t minva, maxva;
 	const Elf_Ehdr *h;
 	const Elf_Phdr *ph;
 	const Elf_Shdr *sh;
-	const char *path;
+#if	defined(__amd64__)
+	const Elf32_Ehdr *h32;
+	const Elf32_Phdr *ph32;
+	const Elf32_Shdr *sh32;
+#endif
+	enum pmcstat_image_type image_type;
+	struct stat st;
+	char buffer[PATH_MAX];
 
 	assert(image->pi_type == PMCSTAT_IMAGE_UNKNOWN);
 
 	minva = ~(uintfptr_t) 0;
 	maxva = (uintfptr_t) 0;
-	path = image->pi_internedpath;
+	path = pmcstat_string_unintern(image->pi_execpath);
 
-	if ((fd = open(path, O_RDONLY, 0)) < 0)
-		err(EX_OSERR, "ERROR: Cannot open \"%s\"", path);
+	assert(path != NULL);
 
-	if (fstat(fd, &st) < 0)
-		err(EX_OSERR, "ERROR: Cannot stat \"%s\"", path);
+	/*
+	 * 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",
+		    a->pa_fsroot, a->pa_kernel, path);
+	else
+		(void) snprintf(buffer, sizeof(buffer), "%s%s",
+		    a->pa_fsroot, path);
 
-	if ((mapbase = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) ==
-	    MAP_FAILED)
-		err(EX_OSERR, "ERROR: Cannot mmap \"%s\"", path);
+	if ((fd = open(buffer, O_RDONLY, 0)) < 0 ||
+	    fstat(fd, &st) < 0 ||
+	    (mapbase = mmap(0, st.st_size, PROT_READ, MAP_SHARED,
+		fd, 0)) == MAP_FAILED) {
+		warn("WARNING: Cannot determine type of \"%s\"", buffer);
+		image->pi_type = PMCSTAT_IMAGE_INDETERMINABLE;
+		if (fd != -1)
+			(void) close(fd);
+		return;
+	}
 
 	(void) close(fd);
 
+	/* Punt on non-ELF objects */
 	h = (const Elf_Ehdr *) mapbase;
 	if (!IS_ELF(*h))
-		err(EX_SOFTWARE, "ERROR: \"%s\" not an ELF file", path);
-
-	sh = (const Elf_Shdr *)((uintptr_t) mapbase + h->e_shoff);
+		return;
 
-	if (h->e_type == ET_EXEC || h->e_type == ET_DYN) {
-		/*
-		 * Some kind of executable object: find the min,max va
-		 * for its executable sections.
-		 */
-		for (i = 0; i < h->e_shnum; i++)
-			if (sh[i].sh_flags & SHF_EXECINSTR) { /* code */
-				minva = min(minva, sh[i].sh_addr);
-				maxva = max(maxva, sh[i].sh_addr +
-				    sh[i].sh_size);
-			}
-	} else
-		err(EX_DATAERR, "ERROR: Unknown file type for \"%s\"",
-		    image->pi_internedpath);
+	/*
+	 * We only handle executable ELF objects and kernel
+	 * modules.
+	 */
+	if (h->e_type != ET_EXEC && h->e_type != ET_DYN &&
+	    !(image->pi_iskernelmodule && h->e_type == ET_REL))
+		return;
 
-	image->pi_type = PMCSTAT_IMAGE_ELF;
-	image->pi_start = minva;
-	image->pi_entry = h->e_entry;
-	image->pi_end = maxva;
 	image->pi_isdynamic = 0;
 	image->pi_dynlinkerpath = NULL;
+	image->pi_vaddr = 0;
 
+#define	GET_VA(H, SH, MINVA, MAXVA) do {				\
+		for (i = 0; i < (H)->e_shnum; i++)			\
+			if ((SH)[i].sh_flags & SHF_EXECINSTR) {		\
+				(MINVA) = min((MINVA),(SH)[i].sh_addr);	\
+				(MAXVA) = max((MAXVA),(SH)[i].sh_addr +	\
+				    (SH)[i].sh_size);			\
+			}						\
+	} while (0)
+
+
+#define	GET_PHDR_INFO(H, PH, IMAGE) do {				\
+		for (i = 0; i < (H)->e_phnum; i++) {			\
+			switch ((PH)[i].p_type) {			\
+			case PT_DYNAMIC:				\
+				image->pi_isdynamic = 1;		\
+				break;					\
+			case PT_INTERP:					\
+				image->pi_dynlinkerpath =		\
+				    pmcstat_string_intern(		\
+					(char *) mapbase +		\
+					(PH)[i].p_offset);		\
+				break;					\
+			case PT_LOAD:					\
+				if ((PH)[i].p_offset == 0)		\
+				    image->pi_vaddr = 			\
+					(PH)[i].p_vaddr;		\
+				break;					\
+			}						\
+		}							\
+	} while (0)
+
+	switch (h->e_machine) {
+	case EM_386:
+	case EM_486:
+#if	defined(__amd64__)
+		/* a 32 bit executable */
+		h32 = (const Elf32_Ehdr *) h;
+		sh32 = (const Elf32_Shdr *)((uintptr_t) mapbase + h32->e_shoff);
+
+		GET_VA(h32, sh32, minva, maxva);
+
+		image->pi_entry = h32->e_entry;
+
+		if (h32->e_type == ET_EXEC) {
+			ph32 = (const Elf32_Phdr *)((uintptr_t) mapbase +
+			    h32->e_phoff);
+			GET_PHDR_INFO(h32, ph32, image);
+		}
+		image_type = PMCSTAT_IMAGE_ELF32;
+		break;
+#endif
+	default:
+		sh = (const Elf_Shdr *)((uintptr_t) mapbase + h->e_shoff);
 
-	if (h->e_type == ET_EXEC) {
-		ph = (const Elf_Phdr *)((uintptr_t) mapbase + h->e_phoff);
-		for (i = 0; i < h->e_phnum; i++) {
-			switch (ph[i].p_type) {
-			case PT_DYNAMIC:
-				image->pi_isdynamic = 1;
-				break;
-			case PT_INTERP:
-				image->pi_dynlinkerpath =
-				    pmcstat_string_intern((char *) mapbase +
-							      ph[i].p_offset);
-				break;
-			}
+		GET_VA(h, sh, minva, maxva);
+
+		image->pi_entry = h->e_entry;
+
+		if (h->e_type == ET_EXEC) {
+			ph = (const Elf_Phdr *)((uintptr_t) mapbase +
+			    h->e_phoff);
+			GET_PHDR_INFO(h, ph, image);
 		}
+		image_type = PMCSTAT_IMAGE_ELF64;
+		break;
 	}
 
+#undef	GET_PHDR_INFO
+#undef	GET_VA
+
+	image->pi_start = minva;
+	image->pi_end   = maxva;
+	image->pi_type  = image_type;
+
 	if (munmap(mapbase, st.st_size) < 0)
 		err(EX_OSERR, "ERROR: Cannot unmap \"%s\"", path);
+	return;
+}
+
+/*
+ * Given an image descriptor, determine whether it is an ELF, or AOUT.
+ * If no handler claims the image, set its type to 'INDETERMINABLE'.
+ */
+
+static void
+pmcstat_image_determine_type(struct pmcstat_image *image,
+    struct pmcstat_args *a)
+{
+	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, a);
+	if (image->pi_type == PMCSTAT_IMAGE_UNKNOWN)
+		pmcstat_image_get_aout_params(image, a);
+
+	/*
+	 * 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(const char *internedpath)
+pmcstat_image_from_path(pmcstat_interned_string internedpath,
+    int iskernelmodule)
 {
 	int count, hash, nlen;
 	struct pmcstat_image *pi;
 	char *sn;
 	char name[NAME_MAX];
 
-	hash = pmcstat_string_compute_hash(internedpath);
+	hash = pmcstat_string_lookup_hash(internedpath);
 
-	/* Look for an existing entry. */
+	/* First, look for an existing entry. */
 	LIST_FOREACH(pi, &pmcstat_image_hash[hash], pi_next)
-	    if (pi->pi_internedpath == internedpath) {
+	    if (pi->pi_execpath == internedpath &&
+		  pi->pi_iskernelmodule == iskernelmodule) {
 		    /* move descriptor to the head of the lru list */
 		    TAILQ_REMOVE(&pmcstat_image_lru, pi, pi_lru);
 		    TAILQ_INSERT_HEAD(&pmcstat_image_lru, pi, pi_lru);
-		    return pi;
+		    return (pi);
 	    }
 
 	/*
@@ -429,13 +798,14 @@
 	 */
 	pi = malloc(sizeof(*pi));
 	if (pi == NULL)
-		return NULL;
+		return (NULL);
 
 	pi->pi_type = PMCSTAT_IMAGE_UNKNOWN;
-	pi->pi_internedpath = internedpath;
+	pi->pi_execpath = internedpath;
 	pi->pi_start = ~0;
 	pi->pi_entry = ~0;
 	pi->pi_end = 0;
+	pi->pi_iskernelmodule = iskernelmodule;
 
 	/*
 	 * Look for a suitable name for the sample files associated
@@ -444,23 +814,31 @@
 	 * `basename(path)`+ "~" + NNN + ".gmon" till we get a free
 	 * entry.
 	 */
-	if ((sn = basename(internedpath)) == NULL)
-		err(EX_OSERR, "ERROR: Cannot process \"%s\"", internedpath);
+	if ((sn = basename(pmcstat_string_unintern(internedpath))) == NULL)
+		err(EX_OSERR, "ERROR: Cannot process \"%s\"",
+		    pmcstat_string_unintern(internedpath));
 
 	nlen = strlen(sn);
-	nlen = min(nlen, (int) sizeof(name) - 6);	/* ".gmon\0" */
+	nlen = min(nlen, (int) (sizeof(name) - sizeof(".gmon")));
 
 	snprintf(name, sizeof(name), "%.*s.gmon", nlen, sn);
 
+	/* try use the unabridged name first */
 	if (pmcstat_string_lookup(name) == NULL)
 		pi->pi_samplename = pmcstat_string_intern(name);
 	else {
+		/*
+		 * Otherwise use a prefix from the original name and
+		 * upto 3 digits.
+		 */
 		nlen = strlen(sn);
-		nlen = min(nlen, (int) sizeof(name)-10); /* "~ddd.gmon\0" */
+		nlen = min(nlen, (int) (sizeof(name)-sizeof("~NNN.gmon")));
 		count = 0;
 		do {
-			count++;
-			snprintf(name, sizeof(name), "%.*s~%3.3d",
+			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);
@@ -469,51 +847,13 @@
 		} while (count > 0);
 	}
 
+
 	LIST_INIT(&pi->pi_gmlist);
 
 	LIST_INSERT_HEAD(&pmcstat_image_hash[hash], pi, pi_next);
 	TAILQ_INSERT_HEAD(&pmcstat_image_lru, pi, pi_lru);
 
-	return pi;
-}
-
-/*
- * Given an open file, determine its file type.
- */
-
-static enum pmcstat_image_type
-pmcstat_image_get_type(const char *path)
-{
-	int fd;
-	Elf_Ehdr eh;
-	struct exec ex;
-	ssize_t nbytes;
-	char buffer[DEFAULT_BUFFER_SIZE];
-
-	if ((fd = open(path, O_RDONLY)) < 0)
-		err(EX_OSERR, "ERROR: Cannot open \"%s\"", path);
-
-	nbytes = max(sizeof(eh), sizeof(ex));
-	if ((nbytes = pread(fd, buffer, nbytes, 0)) < 0)
-		err(EX_OSERR, "ERROR: Cannot read \"%s\"", path);
-
-	(void) close(fd);
-
-	/* check if its an ELF file */
-	if ((unsigned) nbytes >= sizeof(Elf_Ehdr)) {
-		bcopy(buffer, &eh, sizeof(eh));
-		if (IS_ELF(eh))
-			return PMCSTAT_IMAGE_ELF;
-	}
-
-	/* Look for an A.OUT header */
-	if ((unsigned) nbytes >= sizeof(struct exec)) {
-		bcopy(buffer, &ex, sizeof(ex));
-		if (!N_BADMAG(ex))
-			return PMCSTAT_IMAGE_AOUT;
-	}
-
-	return PMCSTAT_IMAGE_UNKNOWN;
+	return (pi);
 }
 
 /*
@@ -532,13 +872,27 @@
 
 	assert(pc >= map->ppm_lowpc && pc < map->ppm_highpc);
 
+	image = map->ppm_image;
+
 	/*
-	 * Find the gmon file corresponding to 'pmcid', creating it if
-	 * needed.
+	 * 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, a);
 
-	image = map->ppm_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.
+	 */
 	LIST_FOREACH(pgf, &image->pi_gmlist, pgf_next)
 	    if (pgf->pgf_pmcid == pmcid)
 		    break;
@@ -557,6 +911,7 @@
 		    FUNCTION_ALIGNMENT;	/* see <machine/profile.h> */
 		pgf->pgf_ndatabytes = sizeof(struct gmonhdr) +
 		    pgf->pgf_nbuckets * sizeof(HISTCOUNTER);
+		pgf->pgf_nsamples = 0;
 
 		pmcstat_gmon_create_file(pgf, image);
 
@@ -570,6 +925,12 @@
 	if (pgf->pgf_gmondata == NULL)
 		pmcstat_gmon_map_file(pgf);
 
+	assert(pgf->pgf_gmondata != NULL);
+
+	/*
+	 *
+	 */
+
 	bucket = (pc - map->ppm_lowpc) / FUNCTION_ALIGNMENT;
 
 	assert(bucket < pgf->pgf_nbuckets);
@@ -578,31 +939,47 @@
 	    sizeof(struct gmonhdr));
 
 	/* saturating add */
-	if (hc[bucket] < 0xFFFF)
+	if (hc[bucket] < 0xFFFFU)  /* XXX tie this to sizeof(HISTCOUNTER) */
 		hc[bucket]++;
+	else /* mark that an overflow occurred */
+		pgf->pgf_overflow = 1;
 
+	pgf->pgf_nsamples++;
 }
 
 /*
- * Record the fact that PC values from 'lowpc' to 'highpc' come from
+ * 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 lowpc, uintfptr_t highpc)
+    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: ");
+		err(EX_OSERR, "ERROR: Cannot create a map entry");
+
+	/*
+	 * Adjust the map entry to only cover the text portion
+	 * of the object.
+	 */
 
-	pcmnew->ppm_lowpc  = lowpc;
-	pcmnew->ppm_highpc = highpc;
+	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 < lowpc)
+	    if (pcm->ppm_lowpc >= pcmnew->ppm_highpc)
 		    break;
 
 	if (pcm == NULL)
@@ -612,11 +989,79 @@
 }
 
 /*
+ * 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_lowpc = start;
+		else if (pcm->ppm_highpc > end)
+			pcm->ppm_highpc = end;
+		else
+			assert(0);
+	}
+}
+
+/*
  * Add a {pmcid,name} mapping.
  */
 
 static void
-pmcstat_pmcid_add(pmc_id_t pmcid, const char *name, struct pmcstat_args *a)
+pmcstat_pmcid_add(pmc_id_t pmcid, pmcstat_interned_string ps,
+    struct pmcstat_args *a)
 {
 	struct pmcstat_pmcrecord *pr;
 	struct stat st;
@@ -624,7 +1069,7 @@
 
 	LIST_FOREACH(pr, &pmcstat_pmcs, pr_next)
 	    if (pr->pr_pmcid == pmcid) {
-		    pr->pr_pmcname = name;
+		    pr->pr_pmcname = ps;
 		    return;
 	    }
 
@@ -632,11 +1077,11 @@
 		err(EX_OSERR, "ERROR: Cannot allocate pmc record");
 
 	pr->pr_pmcid = pmcid;
-	pr->pr_pmcname = name;
+	pr->pr_pmcname = ps;
 	LIST_INSERT_HEAD(&pmcstat_pmcs, pr, pr_next);
 
 	(void) snprintf(fullpath, sizeof(fullpath), "%s/%s", a->pa_samplesdir,
-	    name);
+	    pmcstat_string_unintern(ps));
 
 	/* If the path name exists, it should be a directory */
 	if (stat(fullpath, &st) == 0 && S_ISDIR(st.st_mode))
@@ -648,7 +1093,7 @@
 }
 
 /*
- * Given a pmcid in use, find its human-readable name, or a 
+ * Given a pmcid in use, find its human-readable name.
  */
 
 static const char *
@@ -659,7 +1104,7 @@
 
 	LIST_FOREACH(pr, &pmcstat_pmcs, pr_next)
 	    if (pr->pr_pmcid == pmcid)
-		    return pr->pr_pmcname;
+		    return (pmcstat_string_unintern(pr->pr_pmcname));
 
 	/* create a default name and add this entry */
 	if ((pr = malloc(sizeof(*pr))) == NULL)
@@ -671,35 +1116,42 @@
 
 	LIST_INSERT_HEAD(&pmcstat_pmcs, pr, pr_next);
 
-	return pr->pr_pmcname;
+	return (pmcstat_string_unintern(pr->pr_pmcname));
 }
 
 /*
- * Associate an ELF image with a process.  Argument 'path' names the
- * executable while 'fd' is an already open descriptor to it.
+ * Associate an AOUT image with a process.
  */
 
 static void
-pmcstat_process_add_elf_image(struct pmcstat_process *pp, const char *path,
-    uintfptr_t entryaddr)
+pmcstat_process_aout_exec(struct pmcstat_process *pp,
+    struct pmcstat_image *image, uintfptr_t entryaddr,
+    struct pmcstat_args *a)
 {
-	size_t linelen;
-	FILE *rf;
-	char *line;
-	uintmax_t libstart;
-	struct pmcstat_image *image, *rtldimage;
-	char libpath[PATH_MAX];
-	char command[PATH_MAX + sizeof(PMCSTAT_LDD_COMMAND) + 1];
+	(void) pp;
+	(void) image;
+	(void) entryaddr;
+	(void) a;
+	/* TODO Implement a.out handling */
+}
 
-	/* Look up path in the cache. */
-	if ((image = pmcstat_image_from_path(path)) == NULL)
-		return;
+/*
+ * Associate an ELF image with a process.
+ */
 
-	if (image->pi_type == PMCSTAT_IMAGE_UNKNOWN)
-		pmcstat_image_get_elf_params(image);
+static void
+pmcstat_process_elf_exec(struct pmcstat_process *pp,
+    struct pmcstat_image *image, uintfptr_t entryaddr,
+    struct pmcstat_args *a)
+{
+	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_start, image->pi_end);
+	pmcstat_image_link(pp, image, image->pi_vaddr);
 
 	/*
 	 * For dynamically linked executables we need to:
@@ -708,6 +1160,7 @@
 	 * (b) find all the executable objects that the dynamic linker
 	 *     brought in.
 	 */
+
 	if (image->pi_isdynamic) {
 
 		/*
@@ -717,6 +1170,7 @@
 		 * [  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
@@ -727,51 +1181,27 @@
 		 * 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);
-		if (rtldimage == NULL)
-			err(EX_OSERR, "ERROR: Cannot find image for "
-			    "\"%s\"", image->pi_dynlinkerpath);
-		if (rtldimage->pi_type == PMCSTAT_IMAGE_UNKNOWN)
-			pmcstat_image_get_elf_params(rtldimage);
-
-		libstart = entryaddr - rtldimage->pi_entry;
-		pmcstat_image_link(pp, rtldimage, libstart,
-		    libstart + rtldimage->pi_end - rtldimage->pi_start);
-
-		/* Process all other objects loaded by this executable. */
-		(void) snprintf(command, sizeof(command), "%s %s",
-		    PMCSTAT_LDD_COMMAND, path);
-
-		if ((rf = popen(command, "r")) == NULL)
-			err(EX_OSERR, "ERROR: Cannot create pipe");
-
-		(void) fgetln(rf, &linelen);
-
-		while (!feof(rf) && !ferror(rf)) {
-
-			if ((line = fgetln(rf, &linelen)) == NULL)
-				continue;
-			line[linelen-1] = '\0';
-
-			if (sscanf(line, "%s %jx",
-				libpath, &libstart) != 2)
-				continue;
-
-			image = pmcstat_image_from_path(
-				pmcstat_string_intern(libpath));
-			if (image == NULL)
-				err(EX_OSERR, "ERROR: Cannot process "
-				    "\"%s\"", libpath);
+		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 (image->pi_type == PMCSTAT_IMAGE_UNKNOWN)
-				pmcstat_image_get_elf_params(image);
+		if (rtldimage->pi_type == PMCSTAT_IMAGE_UNKNOWN)
+			pmcstat_image_get_elf_params(rtldimage, a);
 
-			pmcstat_image_link(pp, image, libstart + image->pi_start,
-			    libstart + image->pi_end);
+		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;
 		}
 
-		(void) pclose(rf);
-
+		libstart = entryaddr - rtldimage->pi_entry;
+		pmcstat_image_link(pp, rtldimage, libstart);
 	}
 }
 
@@ -796,7 +1226,7 @@
 	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) {
+		    if (allocate && pp->pp_isactive == 0) {
 			    /* remove maps */
 			    TAILQ_FOREACH_SAFE(ppm, &pp->pp_map, ppm_next,
 				ppmtmp) {
@@ -808,11 +1238,11 @@
 			    free(pp);
 			    break;
 		    }
-		    return pp;
+		    return (pp);
 	    }
 
 	if (!allocate)
-		return NULL;
+		return (NULL);
 
 	if ((pp = malloc(sizeof(*pp))) == NULL)
 		err(EX_OSERR, "ERROR: Cannot allocate pid descriptor");
@@ -823,7 +1253,7 @@
 	TAILQ_INIT(&pp->pp_map);
 
 	LIST_INSERT_HEAD(&pmcstat_process_hash[hash], pp, pp_next);
-	return pp;
+	return (pp);
 }
 
 /*
@@ -831,31 +1261,41 @@
  */
 
 static void
-pmcstat_process_exec(struct pmcstat_process *pp, const char *path,
-    uintfptr_t entryaddr)
+pmcstat_process_exec(struct pmcstat_process *pp,
+    pmcstat_interned_string path, uintfptr_t entryaddr,
+    struct pmcstat_args *a)
 {
-	enum pmcstat_image_type filetype;
 	struct pmcstat_image *image;
 
-	if ((image = pmcstat_image_from_path(path)) == NULL)
+	if ((image = pmcstat_image_from_path(path, 0)) == NULL) {
+		pmcstat_stats.ps_exec_errors++;
 		return;
+	}
 
 	if (image->pi_type == PMCSTAT_IMAGE_UNKNOWN)
-		filetype = pmcstat_image_get_type(path);
-	else
-		filetype = image->pi_type;
+		pmcstat_image_determine_type(image, a);
+
+	assert(image->pi_type != PMCSTAT_IMAGE_UNKNOWN);
 
-	switch (filetype) {
-	case PMCSTAT_IMAGE_ELF:
-		pmcstat_process_add_elf_image(pp, path, entryaddr);
+	switch (image->pi_type) {
+	case PMCSTAT_IMAGE_ELF32:
+	case PMCSTAT_IMAGE_ELF64:
+		pmcstat_stats.ps_exec_elf++;
+		pmcstat_process_elf_exec(pp, image, entryaddr, a);
 		break;
 
 	case PMCSTAT_IMAGE_AOUT:
+		pmcstat_stats.ps_exec_aout++;
+		pmcstat_process_aout_exec(pp, image, entryaddr, a);
+		break;
+
+	case PMCSTAT_IMAGE_INDETERMINABLE:
+		pmcstat_stats.ps_exec_indeterminable++;
 		break;
 
 	default:
 		err(EX_SOFTWARE, "ERROR: Unsupported executable type for "
-		    "\"%s\"", path);
+		    "\"%s\"", pmcstat_string_unintern(path));
 	}
 }
 
@@ -869,108 +1309,85 @@
 {
 	struct pmcstat_pcmap *ppm;
 
-	TAILQ_FOREACH(ppm, &p->pp_map, ppm_next)
-	    if (pc >= ppm->ppm_lowpc && pc < ppm->ppm_highpc)
-		    return 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;
+	return (NULL);
 }
 
 
-/*
- * Compute a 'hash' value for a string.
- */
 
 static int
-pmcstat_string_compute_hash(const char *s)
-{
-	int hash;
-
-	for (hash = 0; *s; s++)
-		hash ^= *s;
-
-	return hash & PMCSTAT_HASH_MASK;
-}
-
-/*
- * Intern a copy of string 's', and return a pointer to it.
- */
-
-static const char *
-pmcstat_string_intern(const char *s)
-{
-	struct pmcstat_string *ps;
-	int hash, len;
-
-	hash = pmcstat_string_compute_hash(s);
-	len  = strlen(s);
-
-	if ((ps = pmcstat_string_lookup(s)) != NULL)
-		return ps->ps_string;
-
-	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 ps->ps_string;
-}
-
-static struct pmcstat_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;
-}
-
-/*
- * Public Interfaces.
- */
-
-/*
- * Close a logfile, after first flushing all in-module queued data.
- */
-
-int
-pmcstat_close_log(struct pmcstat_args *a)
-{
-	if (pmc_flush_logfile() < 0 ||
-	    pmc_configure_logfile(-1) < 0)
-		err(EX_OSERR, "ERROR: logging failed");
-	a->pa_flags &= ~(FLAG_HAS_OUTPUT_LOGFILE | FLAG_HAS_PIPE);
-	return a->pa_flags & FLAG_HAS_PIPE ? PMCSTAT_EXITING :
-	    PMCSTAT_FINISHED;
-}
-
-
-int
 pmcstat_convert_log(struct pmcstat_args *a)
 {
 	uintfptr_t pc;
+	pid_t pid;
+	struct pmcstat_image *image;
 	struct pmcstat_process *pp, *ppnew;
 	struct pmcstat_pcmap *ppm, *ppmtmp;
 	struct pmclog_ev ev;
-	const char *image_path;
+	pmcstat_interned_string image_path;
 
 	while (pmclog_read(a->pa_logparser, &ev) == 0) {
 		assert(ev.pl_state == PMCLOG_OK);
 
 		switch (ev.pl_type) {
-		case PMCLOG_TYPE_MAPPINGCHANGE:
+		case PMCLOG_TYPE_INITIALIZE:
+			if ((ev.pl_u.pl_i.pl_version & 0xFF000000) !=
+			    PMC_VERSION_MAJOR << 24 && a->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
-			 * process.
+			 * 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, a);
+			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:
@@ -981,12 +1398,17 @@
 			 * pair and increment the appropriate entry
 			 * bin inside this.
 			 */
+			pmcstat_stats.ps_samples_total++;
+
 			pc = ev.pl_u.pl_s.pl_pc;
-			pp = pmcstat_process_lookup(ev.pl_u.pl_s.pl_pid, 1);
+			pp = pmcstat_process_lookup(ev.pl_u.pl_s.pl_pid,
+			    PMCSTAT_ALLOCATE);
 			if ((ppm = pmcstat_process_find_map(pp, pc)) == NULL &&
 			    (ppm = pmcstat_process_find_map(pmcstat_kernproc,
-				pc)) == NULL)
-				break; /* unknown process,offset pair */
+				pc)) == NULL) {	/* unknown process,offset pair */
+				pmcstat_stats.ps_samples_unknown_offset++;
+				break;
+			}
 
 			pmcstat_image_increment_bucket(ppm, pc,
 			    ev.pl_u.pl_s.pl_pmcid, a);
@@ -1008,7 +1430,8 @@
 			 * Change the executable image associated with
 			 * a process.
 			 */
-			pp = pmcstat_process_lookup(ev.pl_u.pl_x.pl_pid, 1);
+			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) {
@@ -1016,13 +1439,12 @@
 				free(ppm);
 			}
 
-			/* locate the descriptor for the new 'base' image */
+			/* associate this process  image */
 			image_path = pmcstat_string_intern(
 				ev.pl_u.pl_x.pl_pathname);
-
-			/* link to the new image */
+			assert(image_path != NULL);
 			pmcstat_process_exec(pp, image_path,
-			    ev.pl_u.pl_x.pl_entryaddr);
+			    ev.pl_u.pl_x.pl_entryaddr, a);
 			break;
 
 		case PMCLOG_TYPE_PROCEXIT:
@@ -1039,7 +1461,7 @@
 			pp = pmcstat_process_lookup(ev.pl_u.pl_e.pl_pid, 0);
 			if (pp == NULL)
 				break;
-			pp->pp_isactive = 0;	/* make a zombie */
+			pp->pp_isactive = 0;	/* mark as a zombie */
 			break;
 
 		case PMCLOG_TYPE_SYSEXIT:
@@ -1052,20 +1474,23 @@
 		case PMCLOG_TYPE_PROCFORK:
 
 			/*
-			 * If we had been tracking 'oldpid', then clone
-			 * its pid descriptor.
+			 * 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;
-
-			ppnew =
-			    pmcstat_process_lookup(ev.pl_u.pl_f.pl_newpid, 1);
-
-			/* copy the old process' address maps */
 			TAILQ_FOREACH(ppm, &pp->pp_map, ppm_next)
 			    pmcstat_image_link(ppnew, ppm->ppm_image,
-				ppm->ppm_lowpc, ppm->ppm_highpc);
+				ppm->ppm_lowpc);
 			break;
 
 		default:	/* other types of entries are not relevant */
@@ -1074,47 +1499,19 @@
 	}
 
 	if (ev.pl_state == PMCLOG_EOF)
-		return PMCSTAT_FINISHED;
+		return (PMCSTAT_FINISHED);
 	else if (ev.pl_state == PMCLOG_REQUIRE_DATA)
-		return PMCSTAT_RUNNING;
+		return (PMCSTAT_RUNNING);
 
 	err(EX_DATAERR, "ERROR: event parsing failed (record %jd, "
 	    "offset 0x%jx)", (uintmax_t) ev.pl_count + 1, ev.pl_offset);
 }
 
-
-/*
- * 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(const char *path, int mode)
-{
-	int fd;
-
-	/*
-	 * If 'path' is "-" then open one of stdin or stdout depending
-	 * on the value of 'mode'.  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
-		fd = open(path, mode == PMCSTAT_OPEN_FOR_READ ?
-		    O_RDONLY : (O_WRONLY|O_CREAT|O_TRUNC),
-		    S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
-
-	return fd;
-}
-
 /*
  * Print log entries as text.
  */
 
-int
+static int
 pmcstat_print_log(struct pmcstat_args *a)
 {
 	struct pmclog_ev ev;
@@ -1133,14 +1530,17 @@
 			    ev.pl_u.pl_i.pl_version,
 			    pmc_name_of_cputype(ev.pl_u.pl_i.pl_arch));
 			break;
-		case PMCLOG_TYPE_MAPPINGCHANGE:
-			PMCSTAT_PRINT_ENTRY(a,"mapping","%s %d %p %p \"%s\"",
-			    ev.pl_u.pl_m.pl_type == PMCLOG_MAPPING_INSERT ?
-			    	"insert" : "delete",
-			    ev.pl_u.pl_m.pl_pid,
-			    (void *) ev.pl_u.pl_m.pl_start,
-			    (void *) ev.pl_u.pl_m.pl_end,
-			    ev.pl_u.pl_m.pl_pathname);
+		case PMCLOG_TYPE_MAP_IN:
+			PMCSTAT_PRINT_ENTRY(a,"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(a,"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(a,"sample","0x%x %d %p %c",
@@ -1205,9 +1605,9 @@
 	}
 
 	if (ev.pl_state == PMCLOG_EOF)
-		return PMCSTAT_FINISHED;
+		return (PMCSTAT_FINISHED);
 	else if (ev.pl_state ==  PMCLOG_REQUIRE_DATA)
-		return PMCSTAT_RUNNING;
+		return (PMCSTAT_RUNNING);
 
 	err(EX_DATAERR, "ERROR: event parsing failed "
 	    "(record %jd, offset 0x%jx)",
@@ -1216,6 +1616,112 @@
 }
 
 /*
+ * Public Interfaces.
+ */
+
+/*
+ * Close a logfile, after first flushing all in-module queued data.
+ */
+
+int
+pmcstat_close_log(struct pmcstat_args *a)
+{
+	if (pmc_flush_logfile() < 0 ||
+	    pmc_configure_logfile(-1) < 0)
+		err(EX_OSERR, "ERROR: logging failed");
+	a->pa_flags &= ~(FLAG_HAS_OUTPUT_LOGFILE | FLAG_HAS_PIPE);
+	return (a->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;
+	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 (mode == PMCSTAT_OPEN_FOR_WRITE && 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 (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.
  */
 
@@ -1228,67 +1734,98 @@
 	 * log to the current output file.
 	 */
 	if (a->pa_flags & FLAG_DO_PRINT)
-		return pmcstat_print_log(a);
+		return (pmcstat_print_log(a));
 	else
 		/* convert the log to gprof compatible profiles */
-		return pmcstat_convert_log(a);
+		return (pmcstat_convert_log(a));
 }
 
+/*
+ * Initialize module.
+ */
+
 void
 pmcstat_initialize_logging(struct pmcstat_args *a)
 {
 	int i;
-	const char *kernpath;
-	struct pmcstat_image *img;
+
+	(void) a;
 
 	/* use a convenient format for 'ldd' output */
-	if (setenv("LD_TRACE_LOADED_OBJECTS_FMT1","%p %x\n",1) != 0)
-		goto error;
+	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]);
-		LIST_INIT(&pmcstat_string_hash[i]);
 	}
 
-	/* create a fake 'process' entry for the kernel with pid == -1 */
-	if ((pmcstat_kernproc = pmcstat_process_lookup((pid_t) -1, 1)) == NULL)
-		goto error;
-
-	if ((kernpath = pmcstat_string_intern(a->pa_kernel)) == NULL)
-		goto error;
-
-	img = pmcstat_image_from_path(kernpath);
-
-	pmcstat_image_get_elf_params(img);
-	pmcstat_image_link(pmcstat_kernproc, img, img->pi_start, img->pi_end);
-
-	return;
-
- error:
-	err(EX_OSERR, "ERROR: Cannot initialize logging");
+	/*
+	 * 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");
 }
 
+/*
+ * Shutdown module.
+ */
+
 void
-pmcstat_shutdown_logging(void)
+pmcstat_shutdown_logging(struct pmcstat_args *a)
 {
 	int i;
+	FILE *mf;
 	struct pmcstat_gmonfile *pgf, *pgftmp;
 	struct pmcstat_image *pi, *pitmp;
 	struct pmcstat_process *pp, *pptmp;
-	struct pmcstat_string *ps, *pstmp;
+
+	/* determine where to send the map file */
+	mf = NULL;
+	if (a->pa_mapfilename != NULL)
+		mf = (strcmp(a->pa_mapfilename, "-") == 0) ?
+		    a->pa_printfile : fopen(a->pa_mapfilename, "w");
+
+	if (mf == NULL && a->pa_flags & FLAG_DO_GPROF &&
+	    a->pa_verbosity >= 2)
+		mf = a->pa_printfile;
+
+	if (mf)
+		(void) fprintf(mf, "MAP:\n");
 
 	for (i = 0; i < PMCSTAT_NHASH; i++) {
 		LIST_FOREACH_SAFE(pi, &pmcstat_image_hash[i], pi_next, pitmp) {
+
+			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_SAFE(pgf, &pi->pi_gmlist, pgf_next,
 			    pgftmp) {
-			    pmcstat_gmon_unmap_file(pgf);
-			    LIST_REMOVE(pgf, pgf_next);
-			    free(pgf);
+				pmcstat_gmon_unmap_file(pgf);
+			    	LIST_REMOVE(pgf, pgf_next);
+				if (mf)
+					(void) fprintf(mf, " %s/%d",
+					    pmcstat_pmcid_to_name(pgf->pgf_pmcid),
+					    pgf->pgf_nsamples);
+				if (pgf->pgf_overflow && a->pa_verbosity >= 1)
+					warnx("WARNING: profile \"%s\" "
+					    "overflowed.",
+					    pmcstat_string_unintern(
+					        pgf->pgf_name));
+			    	free(pgf);
 			}
 
+			if (mf)
+				(void) fprintf(mf, "\n");
+
 			LIST_REMOVE(pi, pi_next);
 			free(pi);
 		}
@@ -1297,10 +1834,31 @@
 			LIST_REMOVE(pp, pp_next);
 			free(pp);
 		}
-		LIST_FOREACH_SAFE(ps, &pmcstat_string_hash[i], ps_next,
-		    pstmp) {
-			LIST_REMOVE(ps, ps_next);
-			free(ps);
-		}
 	}
+
+	pmcstat_string_shutdown();
+
+	/*
+	 * Print errors unless -q was specified.  Print all statistics
+	 * if verbosity > 1.
+	 */
+#define	PRINT(N,V,A) do {						\
+		if (pmcstat_stats.ps_##V || (A)->pa_verbosity >= 2)	\
+			(void) fprintf((A)->pa_printfile, " %-40s %d\n",\
+			    N, pmcstat_stats.ps_##V);			\
+	} while (0)
+
+	if (a->pa_verbosity >= 1 && a->pa_flags & FLAG_DO_GPROF) {
+		(void) fprintf(a->pa_printfile, "CONVERSION STATISTICS:\n");
+		PRINT("#exec/a.out", exec_aout, a);
+		PRINT("#exec/elf", exec_elf, a);
+		PRINT("#exec/unknown", exec_indeterminable, a);
+		PRINT("#exec handling errors", exec_errors, a);
+		PRINT("#samples/total", samples_total, a);
+		PRINT("#samples/unclaimed", samples_unknown_offset, a);
+		PRINT("#samples/unknown-object", samples_indeterminable, a);
+	}
+
+	if (mf)
+		(void) fclose(mf);
 }
Index: pmcstat.h
===================================================================
RCS file: /home/cvs/src/usr.sbin/pmcstat/pmcstat.h,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -L usr.sbin/pmcstat/pmcstat.h -L usr.sbin/pmcstat/pmcstat.h -u -r1.1.1.1 -r1.2
--- usr.sbin/pmcstat/pmcstat.h
+++ usr.sbin/pmcstat/pmcstat.h
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2005, Joseph Koshy
+ * Copyright (c) 2005-2007, Joseph Koshy
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -23,13 +23,13 @@
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  *
- * $FreeBSD: src/usr.sbin/pmcstat/pmcstat.h,v 1.2 2005/07/09 17:08:46 jkoshy Exp $
+ * $FreeBSD: src/usr.sbin/pmcstat/pmcstat.h,v 1.5 2007/04/27 12:09:31 jkoshy Exp $
  */
 
 #ifndef	_PMCSTAT_H_
 #define	_PMCSTAT_H_
 
-#define	FLAG_HAS_PID			0x00000001	/* explicit pid */
+#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 */
@@ -54,6 +54,10 @@
 #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"
@@ -77,51 +81,63 @@
 
 struct pmcstat_ev {
 	STAILQ_ENTRY(pmcstat_ev) ev_next;
-	char	       *ev_spec;  /* event specification */
-	char	       *ev_name;  /* (derived) event name */
-	enum pmc_mode	ev_mode;  /* desired mode */
 	int		ev_count; /* associated count if in sampling mode */
-	int		ev_cpu;	  /* specific cpu if requested */
-	int		ev_flags; /* PMC_F_* */
+	uint32_t	ev_cpu;	  /* cpus for this event */
 	int		ev_cumulative;  /* show cumulative counts */
-	int		ev_fieldwidth;  /* print width */
+	int		ev_flags; /* PMC_F_* */
 	int		ev_fieldskip;   /* #leading spaces */
-	pmc_value_t	ev_saved; /* saved value for incremental counts */
+	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 */
-	pid_t	pa_pid;			/* attached to pid */
+	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_kernel;	/* pathname of the kernel */
+	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 */
 	double	pa_interval;		/* printing interval in seconds */
 	int	pa_argc;
 	char	**pa_argv;
-	STAILQ_HEAD(, pmcstat_ev) pa_head;
+	STAILQ_HEAD(, pmcstat_ev) pa_events;
+	SLIST_HEAD(, pmcstat_target) pa_targets;
 } args;
 
 /* Function prototypes */
+void	pmcstat_attach_pmcs(struct pmcstat_args *_a);
 void	pmcstat_cleanup(struct pmcstat_args *_a);
+void	pmcstat_clone_event_descriptor(struct pmcstat_args *_a,
+    struct pmcstat_ev *_ev, uint32_t _cpumask);
 int	pmcstat_close_log(struct pmcstat_args *_a);
+void	pmcstat_create_process(struct pmcstat_args *_a);
+void	pmcstat_find_targets(struct pmcstat_args *_a, const char *_arg);
 void	pmcstat_initialize_logging(struct pmcstat_args *_a);
-int	pmcstat_open(const char *_p, int _mode);
+void	pmcstat_kill_process(struct pmcstat_args *_a);
+int	pmcstat_open_log(const char *_p, int _mode);
 void	pmcstat_print_counters(struct pmcstat_args *_a);
 void	pmcstat_print_headers(struct pmcstat_args *_a);
 void	pmcstat_print_pmcs(struct pmcstat_args *_a);
-void	pmcstat_setup_process(struct pmcstat_args *_a);
 void	pmcstat_show_usage(void);
-void	pmcstat_shutdown_logging(void);
+void	pmcstat_shutdown_logging(struct pmcstat_args *_a);
 void	pmcstat_start_pmcs(struct pmcstat_args *_a);
-void	pmcstat_start_process(struct pmcstat_args *_a);
+void	pmcstat_start_process(void);
 int	pmcstat_process_log(struct pmcstat_args *_a);
-int	pmcstat_print_log(struct pmcstat_args *_a);
-int	pmcstat_convert_log(struct pmcstat_args *_a);
+uint32_t pmcstat_get_cpumask(const char *_a);
 
 #endif	/* _PMCSTAT_H_ */
Index: pmcstat.8
===================================================================
RCS file: /home/cvs/src/usr.sbin/pmcstat/pmcstat.8,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -L usr.sbin/pmcstat/pmcstat.8 -L usr.sbin/pmcstat/pmcstat.8 -u -r1.1.1.1 -r1.2
--- usr.sbin/pmcstat/pmcstat.8
+++ usr.sbin/pmcstat/pmcstat.8
@@ -1,4 +1,4 @@
-.\" Copyright (c) 2003 Joseph Koshy.  All rights reserved.
+.\" Copyright (c) 2003-2007 Joseph Koshy.  All rights reserved.
 .\"
 .\" Redistribution and use in source and binary forms, with or without
 .\" modification, are permitted provided that the following conditions
@@ -21,9 +21,9 @@
 .\" out of the use of this software, even if advised of the possibility of
 .\" such damage.
 .\"
-.\" $FreeBSD: src/usr.sbin/pmcstat/pmcstat.8,v 1.5.2.1 2005/07/17 19:32:27 rwatson Exp $
+.\" $FreeBSD: src/usr.sbin/pmcstat/pmcstat.8,v 1.12 2007/04/27 12:09:31 jkoshy Exp $
 .\"
-.Dd July 1, 2005
+.Dd April 23, 2007
 .Os
 .Dt PMCSTAT 8
 .Sh NAME
@@ -34,20 +34,24 @@
 .Op Fl C
 .Op Fl D Ar pathname
 .Op Fl E
+.Op Fl M Ar mapfilename
 .Op Fl O Ar logfilename
 .Op Fl P Ar event-spec
 .Op Fl R Ar logfilename
 .Op Fl S Ar event-spec
 .Op Fl W
-.Op Fl c Ar cpu
+.Op Fl c Ar cpu-spec
 .Op Fl d
 .Op Fl g
-.Op Fl k Ar kernelfile
+.Op Fl k Ar kerneldir
 .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 pid
+.Op Fl t Ar process-spec
+.Op Fl v
 .Op Fl w Ar secs
 .Op Ar command Op Ar args
 .Sh DESCRIPTION
@@ -59,18 +63,21 @@
 The
 .Nm
 utility can measure both hardware events seen by the system as a
-whole, and those seen when a specified process is executing on the
-system's CPUs.
-If a specific process is being targeted (for example,
+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 pid
+.Fl t Ar process-spec
 option is specified, or if a command line is specified using
 .Ar command ) ,
-then measurement occurs till the target process exits or
-the
+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 process is not targeted for measurement, then
+If a specific set of processes is not targeted for measurement, then
 .Nm
 will perform system-wide measurements till interrupted by the
 user.
@@ -116,11 +123,45 @@
 .Fl d
 option.
 The default is to not to enable per-process tracking.
+.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 O Ar logfilename
 Send logging output to file
 .Ar logfilename .
-If this option is not specified and one of the logging options
-is requested, then
+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.
@@ -142,11 +183,16 @@
 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 c Ar cpu
-Set the cpu for subsequent system mode PMCs specified on the
+.It Fl c Ar cpu-spec
+Set the cpus for subsequent system mode PMCs specified on the
 command line to
-.Ar cpu .
-The default is to allocate system mode PMCs on CPU zero.
+.Ar cpu-spec .
+Argument
+.Ar cpu-spec
+is a comma separated list of CPU numbers, or the literal
+.Sq *
+denoting all CPUs.
+The default is to allocate system mode PMCs on all CPUs.
 .It Fl d
 Toggle between process mode PMCs measuring events for the target
 process' current and future children or only measuring events for
@@ -159,11 +205,14 @@
 encountered.
 Profile files are placed in sub-directories named by their PMC
 event name.
-.It Fl k Ar kernelfile
-Set the pathname of the kernel to argument
-.Ar kernelfile .
+.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
-.Pa /boot/kernel/kernel .
+.Pa /boot/kernel .
 .It Fl n Ar rate
 Set the default sampling rate for subsequent sampling mode
 PMCs specified on the command line.
@@ -179,16 +228,27 @@
 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 pid
-Attach all process mode PMCs allocated to the process with PID
-.Ar pid .
-The option is not allowed in conjunction with specifying a
-command using
-.Ar command .
+.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 every
 .Ar secs
@@ -217,10 +277,25 @@
 by it and its children every 12 seconds on an AMD Athlon, use:
 .Dl "pmcstat -d -w 12 -p k7-dc-misses mozilla"
 .Pp
-To collect a system-wide samples driven by processor instructions executed
+To measure processor instructions retired for all processes named
+.Dq emacs
 use:
+.Dl "pmcstat -t '^emacs$' -p instructions"
+.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 perform system-wide sampling on all configured processors
+based on processor instructions retired use:
 .Dl "pmcstat -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 flat profiles from a sample file use:
@@ -229,6 +304,7 @@
 .Ex -std
 .Sh SEE ALSO
 .Xr gprof 1 ,
+.Xr nc 1 ,
 .Xr execvp 3 ,
 .Xr pmc 3 ,
 .Xr pmclog 3 ,
@@ -244,3 +320,9 @@
 .Ud
 .Sh AUTHORS
 .An Joseph Koshy Aq jkoshy at FreeBSD.org
+.Sh BUGS
+The
+.Nm
+utility cannot yet analyse
+.Xr hwpmc 4
+logs generated by non-native architectures.
Index: Makefile
===================================================================
RCS file: /home/cvs/src/usr.sbin/pmcstat/Makefile,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -L usr.sbin/pmcstat/Makefile -L usr.sbin/pmcstat/Makefile -u -r1.1.1.1 -r1.2
--- usr.sbin/pmcstat/Makefile
+++ usr.sbin/pmcstat/Makefile
@@ -1,12 +1,12 @@
 #
-# $FreeBSD: src/usr.sbin/pmcstat/Makefile,v 1.3 2005/06/30 19:01:26 jkoshy Exp $
+# $FreeBSD: src/usr.sbin/pmcstat/Makefile,v 1.5 2007/10/01 18:15:11 ru Exp $
 #
 
 PROG=	pmcstat
 MAN=	pmcstat.8
 
-DPADD=	${LIBPMC} ${LIBM}
-LDADD=	-lpmc -lm
+DPADD=	${LIBKVM} ${LIBPMC} ${LIBM}
+LDADD=	-lkvm -lpmc -lm
 
 WARNS?=	6
 


More information about the Midnightbsd-cvs mailing list