diff options
Diffstat (limited to 'usr.sbin/pmcstat/pmcstat.c')
-rw-r--r-- | usr.sbin/pmcstat/pmcstat.c | 1537 |
1 files changed, 1537 insertions, 0 deletions
diff --git a/usr.sbin/pmcstat/pmcstat.c b/usr.sbin/pmcstat/pmcstat.c new file mode 100644 index 0000000..81b0cd0 --- /dev/null +++ b/usr.sbin/pmcstat/pmcstat.c @@ -0,0 +1,1537 @@ +/*- + * Copyright (c) 2003-2008, Joseph Koshy + * Copyright (c) 2007 The FreeBSD Foundation + * All rights reserved. + * + * Portions of this software were developed by A. Joseph Koshy under + * sponsorship from the FreeBSD Foundation and Google, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/cpuset.h> +#include <sys/event.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/sysctl.h> +#include <sys/time.h> +#include <sys/ttycom.h> +#include <sys/user.h> +#include <sys/wait.h> + +#include <assert.h> +#include <curses.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <kvm.h> +#include <libgen.h> +#include <limits.h> +#include <math.h> +#include <pmc.h> +#include <pmclog.h> +#include <regex.h> +#include <signal.h> +#include <stdarg.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> + +#include "pmcstat.h" + +/* + * A given invocation of pmcstat(8) can manage multiple PMCs of both + * the system-wide and per-process variety. Each of these could be in + * 'counting mode' or in 'sampling mode'. + * + * For 'counting mode' PMCs, pmcstat(8) will periodically issue a + * pmc_read() at the configured time interval and print out the value + * of the requested PMCs. + * + * For 'sampling mode' PMCs it can log to a file for offline analysis, + * or can analyse sampling data "on the fly", either by converting + * samples to printed textual form or by creating gprof(1) compatible + * profiles, one per program executed. When creating gprof(1) + * profiles it can optionally merge entries from multiple processes + * for a given executable into a single profile file. + * + * pmcstat(8) can also execute a command line and attach PMCs to the + * resulting child process. The protocol used is as follows: + * + * - parent creates a socketpair for two way communication and + * fork()s. + * - subsequently: + * + * /Parent/ /Child/ + * + * - Wait for childs token. + * - Sends token. + * - Awaits signal to start. + * - Attaches PMCs to the child's pid + * and starts them. Sets up + * monitoring for the child. + * - Signals child to start. + * - Receives signal, attempts exec(). + * + * After this point normal processing can happen. + */ + +/* Globals */ + +int pmcstat_displayheight = DEFAULT_DISPLAY_HEIGHT; +int pmcstat_displaywidth = DEFAULT_DISPLAY_WIDTH; +static int pmcstat_sockpair[NSOCKPAIRFD]; +static int pmcstat_kq; +static kvm_t *pmcstat_kvm; +static struct kinfo_proc *pmcstat_plist; +struct pmcstat_args args; + +static void +pmcstat_clone_event_descriptor(struct pmcstat_ev *ev, const cpuset_t *cpumask) +{ + int cpu; + struct pmcstat_ev *ev_clone; + + for (cpu = 0; cpu < CPU_SETSIZE; cpu++) { + if (!CPU_ISSET(cpu, cpumask)) + continue; + + if ((ev_clone = malloc(sizeof(*ev_clone))) == NULL) + errx(EX_SOFTWARE, "ERROR: Out of memory"); + (void) memset(ev_clone, 0, sizeof(*ev_clone)); + + ev_clone->ev_count = ev->ev_count; + ev_clone->ev_cpu = cpu; + ev_clone->ev_cumulative = ev->ev_cumulative; + ev_clone->ev_flags = ev->ev_flags; + ev_clone->ev_mode = ev->ev_mode; + ev_clone->ev_name = strdup(ev->ev_name); + ev_clone->ev_pmcid = ev->ev_pmcid; + ev_clone->ev_saved = ev->ev_saved; + ev_clone->ev_spec = strdup(ev->ev_spec); + + STAILQ_INSERT_TAIL(&args.pa_events, ev_clone, ev_next); + } +} + +static void +pmcstat_get_cpumask(const char *cpuspec, cpuset_t *cpumask) +{ + int cpu; + const char *s; + char *end; + + CPU_ZERO(cpumask); + s = cpuspec; + + do { + cpu = strtol(s, &end, 0); + if (cpu < 0 || end == s) + errx(EX_USAGE, + "ERROR: Illegal CPU specification \"%s\".", + cpuspec); + CPU_SET(cpu, cpumask); + s = end + strspn(end, ", \t"); + } while (*s); + assert(!CPU_EMPTY(cpumask)); +} + +void +pmcstat_attach_pmcs(void) +{ + struct pmcstat_ev *ev; + struct pmcstat_target *pt; + int count; + + /* Attach all process PMCs to target processes. */ + count = 0; + STAILQ_FOREACH(ev, &args.pa_events, ev_next) { + if (PMC_IS_SYSTEM_MODE(ev->ev_mode)) + continue; + SLIST_FOREACH(pt, &args.pa_targets, pt_next) + if (pmc_attach(ev->ev_pmcid, pt->pt_pid) == 0) + count++; + else if (errno != ESRCH) + err(EX_OSERR, +"ERROR: cannot attach pmc \"%s\" to process %d", + ev->ev_name, (int)pt->pt_pid); + } + + if (count == 0) + errx(EX_DATAERR, "ERROR: No processes were attached to."); +} + + +void +pmcstat_cleanup(void) +{ + struct pmcstat_ev *ev, *tmp; + + /* release allocated PMCs. */ + STAILQ_FOREACH_SAFE(ev, &args.pa_events, ev_next, tmp) + if (ev->ev_pmcid != PMC_ID_INVALID) { + if (pmc_stop(ev->ev_pmcid) < 0) + err(EX_OSERR, "ERROR: cannot stop pmc 0x%x \"%s\"", + ev->ev_pmcid, ev->ev_name); + if (pmc_release(ev->ev_pmcid) < 0) + err(EX_OSERR, "ERROR: cannot release pmc 0x%x \"%s\"", + ev->ev_pmcid, ev->ev_name); + free(ev->ev_name); + free(ev->ev_spec); + STAILQ_REMOVE(&args.pa_events, ev, pmcstat_ev, ev_next); + free(ev); + } + + /* de-configure the log file if present. */ + if (args.pa_flags & (FLAG_HAS_PIPE | FLAG_HAS_OUTPUT_LOGFILE)) + (void) pmc_configure_logfile(-1); + + if (args.pa_logparser) { + pmclog_close(args.pa_logparser); + args.pa_logparser = NULL; + } + + pmcstat_shutdown_logging(); +} + +void +pmcstat_create_process(void) +{ + char token; + pid_t pid; + struct kevent kev; + struct pmcstat_target *pt; + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, pmcstat_sockpair) < 0) + err(EX_OSERR, "ERROR: cannot create socket pair"); + + switch (pid = fork()) { + case -1: + err(EX_OSERR, "ERROR: cannot fork"); + /*NOTREACHED*/ + + case 0: /* child */ + (void) close(pmcstat_sockpair[PARENTSOCKET]); + + /* Write a token to tell our parent we've started executing. */ + if (write(pmcstat_sockpair[CHILDSOCKET], "+", 1) != 1) + err(EX_OSERR, "ERROR (child): cannot write token"); + + /* Wait for our parent to signal us to start. */ + if (read(pmcstat_sockpair[CHILDSOCKET], &token, 1) < 0) + err(EX_OSERR, "ERROR (child): cannot read token"); + (void) close(pmcstat_sockpair[CHILDSOCKET]); + + /* exec() the program requested */ + execvp(*args.pa_argv, args.pa_argv); + /* and if that fails, notify the parent */ + kill(getppid(), SIGCHLD); + err(EX_OSERR, "ERROR: execvp \"%s\" failed", *args.pa_argv); + /*NOTREACHED*/ + + default: /* parent */ + (void) close(pmcstat_sockpair[CHILDSOCKET]); + break; + } + + /* Ask to be notified via a kevent when the target process exits. */ + EV_SET(&kev, pid, EVFILT_PROC, EV_ADD|EV_ONESHOT, NOTE_EXIT, 0, + NULL); + if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) + err(EX_OSERR, "ERROR: cannot monitor child process %d", pid); + + if ((pt = malloc(sizeof(*pt))) == NULL) + errx(EX_SOFTWARE, "ERROR: Out of memory."); + + pt->pt_pid = pid; + SLIST_INSERT_HEAD(&args.pa_targets, pt, pt_next); + + /* Wait for the child to signal that its ready to go. */ + if (read(pmcstat_sockpair[PARENTSOCKET], &token, 1) < 0) + err(EX_OSERR, "ERROR (parent): cannot read token"); + + return; +} + +void +pmcstat_find_targets(const char *spec) +{ + int n, nproc, pid, rv; + struct pmcstat_target *pt; + char errbuf[_POSIX2_LINE_MAX], *end; + static struct kinfo_proc *kp; + regex_t reg; + regmatch_t regmatch; + + /* First check if we've been given a process id. */ + pid = strtol(spec, &end, 0); + if (end != spec && pid >= 0) { + if ((pt = malloc(sizeof(*pt))) == NULL) + goto outofmemory; + pt->pt_pid = pid; + SLIST_INSERT_HEAD(&args.pa_targets, pt, pt_next); + return; + } + + /* Otherwise treat arg as a regular expression naming processes. */ + if (pmcstat_kvm == NULL) { + if ((pmcstat_kvm = kvm_openfiles(NULL, "/dev/null", NULL, 0, + errbuf)) == NULL) + err(EX_OSERR, "ERROR: Cannot open kernel \"%s\"", + errbuf); + if ((pmcstat_plist = kvm_getprocs(pmcstat_kvm, KERN_PROC_PROC, + 0, &nproc)) == NULL) + err(EX_OSERR, "ERROR: Cannot get process list: %s", + kvm_geterr(pmcstat_kvm)); + } else + nproc = 0; + + if ((rv = regcomp(®, spec, REG_EXTENDED|REG_NOSUB)) != 0) { + regerror(rv, ®, errbuf, sizeof(errbuf)); + err(EX_DATAERR, "ERROR: Failed to compile regex \"%s\": %s", + spec, errbuf); + } + + for (n = 0, kp = pmcstat_plist; n < nproc; n++, kp++) { + if ((rv = regexec(®, kp->ki_comm, 1, ®match, 0)) == 0) { + if ((pt = malloc(sizeof(*pt))) == NULL) + goto outofmemory; + pt->pt_pid = kp->ki_pid; + SLIST_INSERT_HEAD(&args.pa_targets, pt, pt_next); + } else if (rv != REG_NOMATCH) { + regerror(rv, ®, errbuf, sizeof(errbuf)); + errx(EX_SOFTWARE, "ERROR: Regex evalation failed: %s", + errbuf); + } + } + + regfree(®); + + return; + + outofmemory: + errx(EX_SOFTWARE, "Out of memory."); + /*NOTREACHED*/ +} + +void +pmcstat_kill_process(void) +{ + struct pmcstat_target *pt; + + assert(args.pa_flags & FLAG_HAS_COMMANDLINE); + + /* + * If a command line was specified, it would be the very first + * in the list, before any other processes specified by -t. + */ + pt = SLIST_FIRST(&args.pa_targets); + assert(pt != NULL); + + if (kill(pt->pt_pid, SIGINT) != 0) + err(EX_OSERR, "ERROR: cannot signal child process"); +} + +void +pmcstat_start_pmcs(void) +{ + struct pmcstat_ev *ev; + + STAILQ_FOREACH(ev, &args.pa_events, ev_next) { + + assert(ev->ev_pmcid != PMC_ID_INVALID); + + if (pmc_start(ev->ev_pmcid) < 0) { + warn("ERROR: Cannot start pmc 0x%x \"%s\"", + ev->ev_pmcid, ev->ev_name); + pmcstat_cleanup(); + exit(EX_OSERR); + } + } + +} + +void +pmcstat_print_headers(void) +{ + struct pmcstat_ev *ev; + int c, w; + + (void) fprintf(args.pa_printfile, PRINT_HEADER_PREFIX); + + STAILQ_FOREACH(ev, &args.pa_events, ev_next) { + if (PMC_IS_SAMPLING_MODE(ev->ev_mode)) + continue; + + c = PMC_IS_SYSTEM_MODE(ev->ev_mode) ? 's' : 'p'; + + if (ev->ev_fieldskip != 0) + (void) fprintf(args.pa_printfile, "%*s", + ev->ev_fieldskip, ""); + w = ev->ev_fieldwidth - ev->ev_fieldskip - 2; + + if (c == 's') + (void) fprintf(args.pa_printfile, "s/%02d/%-*s ", + ev->ev_cpu, w-3, ev->ev_name); + else + (void) fprintf(args.pa_printfile, "p/%*s ", w, + ev->ev_name); + } + + (void) fflush(args.pa_printfile); +} + +void +pmcstat_print_counters(void) +{ + int extra_width; + struct pmcstat_ev *ev; + pmc_value_t value; + + extra_width = sizeof(PRINT_HEADER_PREFIX) - 1; + + STAILQ_FOREACH(ev, &args.pa_events, ev_next) { + + /* skip sampling mode counters */ + if (PMC_IS_SAMPLING_MODE(ev->ev_mode)) + continue; + + if (pmc_read(ev->ev_pmcid, &value) < 0) + err(EX_OSERR, "ERROR: Cannot read pmc \"%s\"", + ev->ev_name); + + (void) fprintf(args.pa_printfile, "%*ju ", + ev->ev_fieldwidth + extra_width, + (uintmax_t) ev->ev_cumulative ? value : + (value - ev->ev_saved)); + + if (ev->ev_cumulative == 0) + ev->ev_saved = value; + extra_width = 0; + } + + (void) fflush(args.pa_printfile); +} + +/* + * Print output + */ + +void +pmcstat_print_pmcs(void) +{ + static int linecount = 0; + + /* check if we need to print a header line */ + if (++linecount > pmcstat_displayheight) { + (void) fprintf(args.pa_printfile, "\n"); + linecount = 1; + } + if (linecount == 1) + pmcstat_print_headers(); + (void) fprintf(args.pa_printfile, "\n"); + + pmcstat_print_counters(); + + return; +} + +/* + * Do process profiling + * + * If a pid was specified, attach each allocated PMC to the target + * process. Otherwise, fork a child and attach the PMCs to the child, + * and have the child exec() the target program. + */ + +void +pmcstat_start_process(void) +{ + /* Signal the child to proceed. */ + if (write(pmcstat_sockpair[PARENTSOCKET], "!", 1) != 1) + err(EX_OSERR, "ERROR (parent): write of token failed"); + + (void) close(pmcstat_sockpair[PARENTSOCKET]); +} + +void +pmcstat_show_usage(void) +{ + errx(EX_USAGE, + "[options] [commandline]\n" + "\t Measure process and/or system performance using hardware\n" + "\t performance monitoring counters.\n" + "\t Options include:\n" + "\t -C\t\t (toggle) show cumulative counts\n" + "\t -D path\t create profiles in directory \"path\"\n" + "\t -E\t\t (toggle) show counts at process exit\n" + "\t -F file\t write a system-wide callgraph (Kcachegrind format)" + " to \"file\"\n" + "\t -G file\t write a system-wide callgraph to \"file\"\n" + "\t -M file\t print executable/gmon file map to \"file\"\n" + "\t -N\t\t (toggle) capture callchains\n" + "\t -O file\t send log output to \"file\"\n" + "\t -P spec\t allocate a process-private sampling PMC\n" + "\t -R file\t read events from \"file\"\n" + "\t -S spec\t allocate a system-wide sampling PMC\n" + "\t -T\t\t start in top mode\n" + "\t -W\t\t (toggle) show counts per context switch\n" + "\t -a file\t print sampled PCs and callgraph to \"file\"\n" + "\t -c cpu-list\t set cpus for subsequent system-wide PMCs\n" + "\t -d\t\t (toggle) track descendants\n" + "\t -e\t\t use wide history counter for gprof(1) output\n" + "\t -f spec\t pass \"spec\" to as plugin option\n" + "\t -g\t\t produce gprof(1) compatible profiles\n" + "\t -k dir\t\t set the path to the kernel\n" + "\t -l secs\t set duration time\n" + "\t -m file\t print sampled PCs to \"file\"\n" + "\t -n rate\t set sampling rate\n" + "\t -o file\t send print output to \"file\"\n" + "\t -p spec\t allocate a process-private counting PMC\n" + "\t -q\t\t suppress verbosity\n" + "\t -r fsroot\t specify FS root directory\n" + "\t -s spec\t allocate a system-wide counting PMC\n" + "\t -t process-spec attach to running processes matching " + "\"process-spec\"\n" + "\t -v\t\t increase verbosity\n" + "\t -w secs\t set printing time interval\n" + "\t -z depth\t limit callchain display depth" + ); +} + +/* + * At exit handler for top mode + */ + +void +pmcstat_topexit(void) +{ + if (!args.pa_toptty) + return; + + /* + * Shutdown ncurses. + */ + clrtoeol(); + refresh(); + endwin(); +} + +/* + * Main + */ + +int +main(int argc, char **argv) +{ + cpuset_t cpumask, rootmask; + double interval; + double duration; + int option, npmc; + int c, check_driver_stats, current_sampling_count; + int do_callchain, do_descendants, do_logproccsw, do_logprocexit; + int do_print, do_read; + size_t len; + int graphdepth; + int pipefd[2], rfd; + int use_cumulative_counts; + short cf, cb; + char *end, *tmp; + const char *errmsg, *graphfilename; + enum pmcstat_state runstate; + struct pmc_driverstats ds_start, ds_end; + struct pmcstat_ev *ev; + struct sigaction sa; + struct kevent kev; + struct winsize ws; + struct stat sb; + char buffer[PATH_MAX]; + + check_driver_stats = 0; + current_sampling_count = DEFAULT_SAMPLE_COUNT; + do_callchain = 1; + do_descendants = 0; + do_logproccsw = 0; + do_logprocexit = 0; + use_cumulative_counts = 0; + graphfilename = "-"; + args.pa_required = 0; + args.pa_flags = 0; + args.pa_verbosity = 1; + args.pa_logfd = -1; + args.pa_fsroot = ""; + args.pa_samplesdir = "."; + args.pa_printfile = stderr; + args.pa_graphdepth = DEFAULT_CALLGRAPH_DEPTH; + args.pa_graphfile = NULL; + args.pa_interval = DEFAULT_WAIT_INTERVAL; + args.pa_mapfilename = NULL; + args.pa_inputpath = NULL; + args.pa_outputpath = NULL; + args.pa_pplugin = PMCSTAT_PL_NONE; + args.pa_plugin = PMCSTAT_PL_NONE; + args.pa_ctdumpinstr = 1; + args.pa_topmode = PMCSTAT_TOP_DELTA; + args.pa_toptty = 0; + args.pa_topcolor = 0; + args.pa_mergepmc = 0; + args.pa_duration = 0.0; + STAILQ_INIT(&args.pa_events); + SLIST_INIT(&args.pa_targets); + bzero(&ds_start, sizeof(ds_start)); + bzero(&ds_end, sizeof(ds_end)); + ev = NULL; + CPU_ZERO(&cpumask); + + /* Default to using the running system kernel. */ + len = 0; + if (sysctlbyname("kern.bootfile", NULL, &len, NULL, 0) == -1) + err(EX_OSERR, "ERROR: Cannot determine path of running kernel"); + args.pa_kernel = malloc(len + 1); + if (sysctlbyname("kern.bootfile", args.pa_kernel, &len, NULL, 0) == -1) + err(EX_OSERR, "ERROR: Cannot determine path of running kernel"); + + /* + * The initial CPU mask specifies the root mask of this process + * which is usually all CPUs in the system. + */ + if (cpuset_getaffinity(CPU_LEVEL_ROOT, CPU_WHICH_PID, -1, + sizeof(rootmask), &rootmask) == -1) + err(EX_OSERR, "ERROR: Cannot determine the root set of CPUs"); + CPU_COPY(&rootmask, &cpumask); + + while ((option = getopt(argc, argv, + "CD:EF:G:M:NO:P:R:S:TWa:c:def:gk:l:m:n:o:p:qr:s:t:vw:z:")) != -1) + switch (option) { + case 'a': /* Annotate + callgraph */ + args.pa_flags |= FLAG_DO_ANNOTATE; + args.pa_plugin = PMCSTAT_PL_ANNOTATE_CG; + graphfilename = optarg; + break; + + case 'C': /* cumulative values */ + use_cumulative_counts = !use_cumulative_counts; + args.pa_required |= FLAG_HAS_COUNTING_PMCS; + break; + + case 'c': /* CPU */ + if (optarg[0] == '*' && optarg[1] == '\0') + CPU_COPY(&rootmask, &cpumask); + else + pmcstat_get_cpumask(optarg, &cpumask); + + args.pa_flags |= FLAGS_HAS_CPUMASK; + args.pa_required |= FLAG_HAS_SYSTEM_PMCS; + break; + + case 'D': + if (stat(optarg, &sb) < 0) + err(EX_OSERR, "ERROR: Cannot stat \"%s\"", + optarg); + if (!S_ISDIR(sb.st_mode)) + errx(EX_USAGE, + "ERROR: \"%s\" is not a directory.", + optarg); + args.pa_samplesdir = optarg; + args.pa_flags |= FLAG_HAS_SAMPLESDIR; + args.pa_required |= FLAG_DO_GPROF; + break; + + case 'd': /* toggle descendents */ + do_descendants = !do_descendants; + args.pa_required |= FLAG_HAS_PROCESS_PMCS; + break; + + case 'e': /* wide gprof metrics */ + args.pa_flags |= FLAG_DO_WIDE_GPROF_HC; + break; + + case 'F': /* produce a system-wide calltree */ + args.pa_flags |= FLAG_DO_CALLGRAPHS; + args.pa_plugin = PMCSTAT_PL_CALLTREE; + graphfilename = optarg; + break; + + case 'f': /* plugins options */ + if (args.pa_plugin == PMCSTAT_PL_NONE) + err(EX_USAGE, "ERROR: Need -g/-G/-m/-T."); + pmcstat_pluginconfigure_log(optarg); + break; + + case 'G': /* produce a system-wide callgraph */ + args.pa_flags |= FLAG_DO_CALLGRAPHS; + args.pa_plugin = PMCSTAT_PL_CALLGRAPH; + graphfilename = optarg; + break; + + case 'g': /* produce gprof compatible profiles */ + args.pa_flags |= FLAG_DO_GPROF; + args.pa_pplugin = PMCSTAT_PL_CALLGRAPH; + args.pa_plugin = PMCSTAT_PL_GPROF; + break; + + case 'k': /* pathname to the kernel */ + free(args.pa_kernel); + args.pa_kernel = strdup(optarg); + args.pa_required |= FLAG_DO_ANALYSIS; + args.pa_flags |= FLAG_HAS_KERNELPATH; + break; + + case 'l': /* time duration in seconds */ + duration = strtod(optarg, &end); + if (*end != '\0' || duration <= 0) + errx(EX_USAGE, "ERROR: Illegal duration time " + "value \"%s\".", optarg); + args.pa_flags |= FLAG_HAS_DURATION; + args.pa_duration = duration; + break; + + case 'm': + args.pa_flags |= FLAG_DO_ANNOTATE; + args.pa_plugin = PMCSTAT_PL_ANNOTATE; + graphfilename = optarg; + break; + + case 'E': /* log process exit */ + do_logprocexit = !do_logprocexit; + args.pa_required |= (FLAG_HAS_PROCESS_PMCS | + FLAG_HAS_COUNTING_PMCS | FLAG_HAS_OUTPUT_LOGFILE); + break; + + case 'M': /* mapfile */ + args.pa_mapfilename = optarg; + break; + + case 'N': + do_callchain = !do_callchain; + args.pa_required |= FLAG_HAS_SAMPLING_PMCS; + break; + + case 'p': /* process virtual counting PMC */ + case 's': /* system-wide counting PMC */ + case 'P': /* process virtual sampling PMC */ + case 'S': /* system-wide sampling PMC */ + if ((ev = malloc(sizeof(*ev))) == NULL) + errx(EX_SOFTWARE, "ERROR: Out of memory."); + + switch (option) { + case 'p': ev->ev_mode = PMC_MODE_TC; break; + case 's': ev->ev_mode = PMC_MODE_SC; break; + case 'P': ev->ev_mode = PMC_MODE_TS; break; + case 'S': ev->ev_mode = PMC_MODE_SS; break; + } + + if (option == 'P' || option == 'p') { + args.pa_flags |= FLAG_HAS_PROCESS_PMCS; + args.pa_required |= (FLAG_HAS_COMMANDLINE | + FLAG_HAS_TARGET); + } + + if (option == 'P' || option == 'S') { + args.pa_flags |= FLAG_HAS_SAMPLING_PMCS; + args.pa_required |= (FLAG_HAS_PIPE | + FLAG_HAS_OUTPUT_LOGFILE); + } + + if (option == 'p' || option == 's') + args.pa_flags |= FLAG_HAS_COUNTING_PMCS; + + if (option == 's' || option == 'S') + args.pa_flags |= FLAG_HAS_SYSTEM_PMCS; + + ev->ev_spec = strdup(optarg); + + if (option == 'S' || option == 'P') + ev->ev_count = current_sampling_count; + else + ev->ev_count = -1; + + if (option == 'S' || option == 's') + ev->ev_cpu = CPU_FFS(&cpumask) - 1; + else + ev->ev_cpu = PMC_CPU_ANY; + + ev->ev_flags = 0; + if (do_callchain) + ev->ev_flags |= PMC_F_CALLCHAIN; + if (do_descendants) + ev->ev_flags |= PMC_F_DESCENDANTS; + if (do_logprocexit) + ev->ev_flags |= PMC_F_LOG_PROCEXIT; + if (do_logproccsw) + ev->ev_flags |= PMC_F_LOG_PROCCSW; + + ev->ev_cumulative = use_cumulative_counts; + + ev->ev_saved = 0LL; + ev->ev_pmcid = PMC_ID_INVALID; + + /* extract event name */ + c = strcspn(optarg, ", \t"); + ev->ev_name = malloc(c + 1); + (void) strncpy(ev->ev_name, optarg, c); + *(ev->ev_name + c) = '\0'; + + STAILQ_INSERT_TAIL(&args.pa_events, ev, ev_next); + + if (option == 's' || option == 'S') { + CPU_CLR(ev->ev_cpu, &cpumask); + pmcstat_clone_event_descriptor(ev, &cpumask); + CPU_SET(ev->ev_cpu, &cpumask); + } + + break; + + case 'n': /* sampling count */ + current_sampling_count = strtol(optarg, &end, 0); + if (*end != '\0' || current_sampling_count <= 0) + errx(EX_USAGE, + "ERROR: Illegal count value \"%s\".", + optarg); + args.pa_required |= FLAG_HAS_SAMPLING_PMCS; + break; + + case 'o': /* outputfile */ + if (args.pa_printfile != NULL && + args.pa_printfile != stdout && + args.pa_printfile != stderr) + (void) fclose(args.pa_printfile); + if ((args.pa_printfile = fopen(optarg, "w")) == NULL) + errx(EX_OSERR, + "ERROR: cannot open \"%s\" for writing.", + optarg); + args.pa_flags |= FLAG_DO_PRINT; + break; + + case 'O': /* sampling output */ + if (args.pa_outputpath) + errx(EX_USAGE, +"ERROR: option -O may only be specified once."); + args.pa_outputpath = optarg; + args.pa_flags |= FLAG_HAS_OUTPUT_LOGFILE; + break; + + case 'q': /* quiet mode */ + args.pa_verbosity = 0; + break; + + case 'r': /* root FS path */ + args.pa_fsroot = optarg; + break; + + case 'R': /* read an existing log file */ + if (args.pa_inputpath != NULL) + errx(EX_USAGE, +"ERROR: option -R may only be specified once."); + args.pa_inputpath = optarg; + if (args.pa_printfile == stderr) + args.pa_printfile = stdout; + args.pa_flags |= FLAG_READ_LOGFILE; + break; + + case 't': /* target pid or process name */ + pmcstat_find_targets(optarg); + + args.pa_flags |= FLAG_HAS_TARGET; + args.pa_required |= FLAG_HAS_PROCESS_PMCS; + break; + + case 'T': /* top mode */ + args.pa_flags |= FLAG_DO_TOP; + args.pa_plugin = PMCSTAT_PL_CALLGRAPH; + args.pa_ctdumpinstr = 0; + args.pa_mergepmc = 1; + if (args.pa_printfile == stderr) + args.pa_printfile = stdout; + break; + + case 'v': /* verbose */ + args.pa_verbosity++; + break; + + case 'w': /* wait interval */ + interval = strtod(optarg, &end); + if (*end != '\0' || interval <= 0) + errx(EX_USAGE, +"ERROR: Illegal wait interval value \"%s\".", + optarg); + args.pa_flags |= FLAG_HAS_WAIT_INTERVAL; + args.pa_interval = interval; + break; + + case 'W': /* toggle LOG_CSW */ + do_logproccsw = !do_logproccsw; + args.pa_required |= (FLAG_HAS_PROCESS_PMCS | + FLAG_HAS_COUNTING_PMCS | FLAG_HAS_OUTPUT_LOGFILE); + break; + + case 'z': + graphdepth = strtod(optarg, &end); + if (*end != '\0' || graphdepth <= 0) + errx(EX_USAGE, + "ERROR: Illegal callchain depth \"%s\".", + optarg); + args.pa_graphdepth = graphdepth; + args.pa_required |= FLAG_DO_CALLGRAPHS; + break; + + case '?': + default: + pmcstat_show_usage(); + break; + + } + + args.pa_argc = (argc -= optind); + args.pa_argv = (argv += optind); + + /* If we read from logfile and no specified CPU mask use + * the maximum CPU count. + */ + if ((args.pa_flags & FLAG_READ_LOGFILE) && + (args.pa_flags & FLAGS_HAS_CPUMASK) == 0) + CPU_FILL(&cpumask); + + args.pa_cpumask = cpumask; /* For selecting CPUs using -R. */ + + if (argc) /* command line present */ + args.pa_flags |= FLAG_HAS_COMMANDLINE; + + if (args.pa_flags & (FLAG_DO_GPROF | FLAG_DO_CALLGRAPHS | + FLAG_DO_ANNOTATE | FLAG_DO_TOP)) + args.pa_flags |= FLAG_DO_ANALYSIS; + + /* + * Check invocation syntax. + */ + + /* disallow -O and -R together */ + if (args.pa_outputpath && args.pa_inputpath) + errx(EX_USAGE, + "ERROR: options -O and -R are mutually exclusive."); + + /* disallow -T and -l together */ + if ((args.pa_flags & FLAG_HAS_DURATION) && + (args.pa_flags & FLAG_DO_TOP)) + errx(EX_USAGE, "ERROR: options -T and -l are mutually " + "exclusive."); + + /* -a and -m require -R */ + if (args.pa_flags & FLAG_DO_ANNOTATE && args.pa_inputpath == NULL) + errx(EX_USAGE, "ERROR: option %s requires an input file", + args.pa_plugin == PMCSTAT_PL_ANNOTATE ? "-m" : "-a"); + + /* -m option is not allowed combined with -g or -G. */ + if (args.pa_flags & FLAG_DO_ANNOTATE && + args.pa_flags & (FLAG_DO_GPROF | FLAG_DO_CALLGRAPHS)) + errx(EX_USAGE, + "ERROR: option -m and -g | -G are mutually exclusive"); + + if (args.pa_flags & FLAG_READ_LOGFILE) { + errmsg = NULL; + if (args.pa_flags & FLAG_HAS_COMMANDLINE) + errmsg = "a command line specification"; + else if (args.pa_flags & FLAG_HAS_TARGET) + errmsg = "option -t"; + else if (!STAILQ_EMPTY(&args.pa_events)) + errmsg = "a PMC event specification"; + if (errmsg) + errx(EX_USAGE, + "ERROR: option -R may not be used with %s.", + errmsg); + } else if (STAILQ_EMPTY(&args.pa_events)) + /* All other uses require a PMC spec. */ + pmcstat_show_usage(); + + /* check for -t pid without a process PMC spec */ + if ((args.pa_required & FLAG_HAS_TARGET) && + (args.pa_flags & FLAG_HAS_PROCESS_PMCS) == 0) + errx(EX_USAGE, +"ERROR: option -t requires a process mode PMC to be specified." + ); + + /* check for process-mode options without a command or -t pid */ + if ((args.pa_required & FLAG_HAS_PROCESS_PMCS) && + (args.pa_flags & (FLAG_HAS_COMMANDLINE | FLAG_HAS_TARGET)) == 0) + errx(EX_USAGE, +"ERROR: options -d, -E, -p, -P, and -W require a command line or target process." + ); + + /* check for -p | -P without a target process of some sort */ + if ((args.pa_required & (FLAG_HAS_COMMANDLINE | FLAG_HAS_TARGET)) && + (args.pa_flags & (FLAG_HAS_COMMANDLINE | FLAG_HAS_TARGET)) == 0) + errx(EX_USAGE, +"ERROR: options -P and -p require a target process or a command line." + ); + + /* check for process-mode options without a process-mode PMC */ + if ((args.pa_required & FLAG_HAS_PROCESS_PMCS) && + (args.pa_flags & FLAG_HAS_PROCESS_PMCS) == 0) + errx(EX_USAGE, +"ERROR: options -d, -E, and -W require a process mode PMC to be specified." + ); + + /* check for -c cpu with no system mode PMCs or logfile. */ + if ((args.pa_required & FLAG_HAS_SYSTEM_PMCS) && + (args.pa_flags & FLAG_HAS_SYSTEM_PMCS) == 0 && + (args.pa_flags & FLAG_READ_LOGFILE) == 0) + errx(EX_USAGE, +"ERROR: option -c requires at least one system mode PMC to be specified." + ); + + /* check for counting mode options without a counting PMC */ + if ((args.pa_required & FLAG_HAS_COUNTING_PMCS) && + (args.pa_flags & FLAG_HAS_COUNTING_PMCS) == 0) + errx(EX_USAGE, +"ERROR: options -C, -W and -o require at least one counting mode PMC to be specified." + ); + + /* check for sampling mode options without a sampling PMC spec */ + if ((args.pa_required & FLAG_HAS_SAMPLING_PMCS) && + (args.pa_flags & FLAG_HAS_SAMPLING_PMCS) == 0) + errx(EX_USAGE, +"ERROR: options -N, -n and -O require at least one sampling mode PMC to be specified." + ); + + /* check if -g/-G/-m/-T are being used correctly */ + if ((args.pa_flags & FLAG_DO_ANALYSIS) && + !(args.pa_flags & (FLAG_HAS_SAMPLING_PMCS|FLAG_READ_LOGFILE))) + errx(EX_USAGE, +"ERROR: options -g/-G/-m/-T require sampling PMCs or -R to be specified." + ); + + /* check if -e was specified without -g */ + if ((args.pa_flags & FLAG_DO_WIDE_GPROF_HC) && + !(args.pa_flags & FLAG_DO_GPROF)) + errx(EX_USAGE, +"ERROR: option -e requires gprof mode to be specified." + ); + + /* check if -O was spuriously specified */ + if ((args.pa_flags & FLAG_HAS_OUTPUT_LOGFILE) && + (args.pa_required & FLAG_HAS_OUTPUT_LOGFILE) == 0) + errx(EX_USAGE, +"ERROR: option -O is used only with options -E, -P, -S and -W." + ); + + /* -k kernel path require -g/-G/-m/-T or -R */ + if ((args.pa_flags & FLAG_HAS_KERNELPATH) && + (args.pa_flags & FLAG_DO_ANALYSIS) == 0 && + (args.pa_flags & FLAG_READ_LOGFILE) == 0) + errx(EX_USAGE, "ERROR: option -k is only used with -g/-R/-m/-T."); + + /* -D only applies to gprof output mode (-g) */ + if ((args.pa_flags & FLAG_HAS_SAMPLESDIR) && + (args.pa_flags & FLAG_DO_GPROF) == 0) + errx(EX_USAGE, "ERROR: option -D is only used with -g."); + + /* -M mapfile requires -g or -R */ + if (args.pa_mapfilename != NULL && + (args.pa_flags & FLAG_DO_GPROF) == 0 && + (args.pa_flags & FLAG_READ_LOGFILE) == 0) + errx(EX_USAGE, "ERROR: option -M is only used with -g/-R."); + + /* + * Disallow textual output of sampling PMCs if counting PMCs + * have also been asked for, mostly because the combined output + * is difficult to make sense of. + */ + if ((args.pa_flags & FLAG_HAS_COUNTING_PMCS) && + (args.pa_flags & FLAG_HAS_SAMPLING_PMCS) && + ((args.pa_flags & FLAG_HAS_OUTPUT_LOGFILE) == 0)) + errx(EX_USAGE, +"ERROR: option -O is required if counting and sampling PMCs are specified together." + ); + + /* + * Check if 'kerneldir' refers to a file rather than a + * directory. If so, use `dirname path` to determine the + * kernel directory. + */ + (void) snprintf(buffer, sizeof(buffer), "%s%s", args.pa_fsroot, + args.pa_kernel); + if (stat(buffer, &sb) < 0) + err(EX_OSERR, "ERROR: Cannot locate kernel \"%s\"", + buffer); + if (!S_ISREG(sb.st_mode) && !S_ISDIR(sb.st_mode)) + errx(EX_USAGE, "ERROR: \"%s\": Unsupported file type.", + buffer); + if (!S_ISDIR(sb.st_mode)) { + tmp = args.pa_kernel; + args.pa_kernel = strdup(dirname(args.pa_kernel)); + free(tmp); + (void) snprintf(buffer, sizeof(buffer), "%s%s", + args.pa_fsroot, args.pa_kernel); + if (stat(buffer, &sb) < 0) + err(EX_OSERR, "ERROR: Cannot stat \"%s\"", + buffer); + if (!S_ISDIR(sb.st_mode)) + errx(EX_USAGE, + "ERROR: \"%s\" is not a directory.", + buffer); + } + + /* + * If we have a callgraph be created, select the outputfile. + */ + if (args.pa_flags & FLAG_DO_CALLGRAPHS) { + if (strcmp(graphfilename, "-") == 0) + args.pa_graphfile = args.pa_printfile; + else { + args.pa_graphfile = fopen(graphfilename, "w"); + if (args.pa_graphfile == NULL) + err(EX_OSERR, + "ERROR: cannot open \"%s\" for writing", + graphfilename); + } + } + if (args.pa_flags & FLAG_DO_ANNOTATE) { + args.pa_graphfile = fopen(graphfilename, "w"); + if (args.pa_graphfile == NULL) + err(EX_OSERR, "ERROR: cannot open \"%s\" for writing", + graphfilename); + } + + /* if we've been asked to process a log file, skip init */ + if ((args.pa_flags & FLAG_READ_LOGFILE) == 0) { + if (pmc_init() < 0) + err(EX_UNAVAILABLE, + "ERROR: Initialization of the pmc(3) library failed" + ); + + if ((npmc = pmc_npmc(0)) < 0) /* assume all CPUs are identical */ + err(EX_OSERR, +"ERROR: Cannot determine the number of PMCs on CPU %d", + 0); + } + + /* Allocate a kqueue */ + if ((pmcstat_kq = kqueue()) < 0) + err(EX_OSERR, "ERROR: Cannot allocate kqueue"); + + /* Setup the logfile as the source. */ + if (args.pa_flags & FLAG_READ_LOGFILE) { + /* + * Print the log in textual form if we haven't been + * asked to generate profiling information. + */ + if ((args.pa_flags & FLAG_DO_ANALYSIS) == 0) + args.pa_flags |= FLAG_DO_PRINT; + + pmcstat_initialize_logging(); + rfd = pmcstat_open_log(args.pa_inputpath, + PMCSTAT_OPEN_FOR_READ); + if ((args.pa_logparser = pmclog_open(rfd)) == NULL) + err(EX_OSERR, "ERROR: Cannot create parser"); + if (fcntl(rfd, F_SETFL, O_NONBLOCK) < 0) + err(EX_OSERR, "ERROR: fcntl(2) failed"); + EV_SET(&kev, rfd, EVFILT_READ, EV_ADD, + 0, 0, NULL); + if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) + err(EX_OSERR, "ERROR: Cannot register kevent"); + } + /* + * Configure the specified log file or setup a default log + * consumer via a pipe. + */ + if (args.pa_required & FLAG_HAS_OUTPUT_LOGFILE) { + if (args.pa_outputpath) + args.pa_logfd = pmcstat_open_log(args.pa_outputpath, + PMCSTAT_OPEN_FOR_WRITE); + else { + /* + * process the log on the fly by reading it in + * through a pipe. + */ + if (pipe(pipefd) < 0) + err(EX_OSERR, "ERROR: pipe(2) failed"); + + if (fcntl(pipefd[READPIPEFD], F_SETFL, O_NONBLOCK) < 0) + err(EX_OSERR, "ERROR: fcntl(2) failed"); + + EV_SET(&kev, pipefd[READPIPEFD], EVFILT_READ, EV_ADD, + 0, 0, NULL); + + if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) + err(EX_OSERR, "ERROR: Cannot register kevent"); + + args.pa_logfd = pipefd[WRITEPIPEFD]; + + args.pa_flags |= FLAG_HAS_PIPE; + if ((args.pa_flags & FLAG_DO_TOP) == 0) + args.pa_flags |= FLAG_DO_PRINT; + args.pa_logparser = pmclog_open(pipefd[READPIPEFD]); + } + + if (pmc_configure_logfile(args.pa_logfd) < 0) + err(EX_OSERR, "ERROR: Cannot configure log file"); + } + + /* remember to check for driver errors if we are sampling or logging */ + check_driver_stats = (args.pa_flags & FLAG_HAS_SAMPLING_PMCS) || + (args.pa_flags & FLAG_HAS_OUTPUT_LOGFILE); + + /* + if (args.pa_flags & FLAG_READ_LOGFILE) { + * Allocate PMCs. + */ + + STAILQ_FOREACH(ev, &args.pa_events, ev_next) { + if (pmc_allocate(ev->ev_spec, ev->ev_mode, + ev->ev_flags, ev->ev_cpu, &ev->ev_pmcid) < 0) + err(EX_OSERR, +"ERROR: Cannot allocate %s-mode pmc with specification \"%s\"", + PMC_IS_SYSTEM_MODE(ev->ev_mode) ? + "system" : "process", ev->ev_spec); + + if (PMC_IS_SAMPLING_MODE(ev->ev_mode) && + pmc_set(ev->ev_pmcid, ev->ev_count) < 0) + err(EX_OSERR, + "ERROR: Cannot set sampling count for PMC \"%s\"", + ev->ev_name); + } + + /* compute printout widths */ + STAILQ_FOREACH(ev, &args.pa_events, ev_next) { + int counter_width; + int display_width; + int header_width; + + (void) pmc_width(ev->ev_pmcid, &counter_width); + header_width = strlen(ev->ev_name) + 2; /* prefix '%c/' */ + display_width = (int) floor(counter_width / 3.32193) + 1; + + if (PMC_IS_SYSTEM_MODE(ev->ev_mode)) + header_width += 3; /* 2 digit CPU number + '/' */ + + if (header_width > display_width) { + ev->ev_fieldskip = 0; + ev->ev_fieldwidth = header_width; + } else { + ev->ev_fieldskip = display_width - + header_width; + ev->ev_fieldwidth = display_width; + } + } + + /* + * If our output is being set to a terminal, register a handler + * for window size changes. + */ + + if (isatty(fileno(args.pa_printfile))) { + + if (ioctl(fileno(args.pa_printfile), TIOCGWINSZ, &ws) < 0) + err(EX_OSERR, "ERROR: Cannot determine window size"); + + pmcstat_displayheight = ws.ws_row - 1; + pmcstat_displaywidth = ws.ws_col - 1; + + EV_SET(&kev, SIGWINCH, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); + + if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) + err(EX_OSERR, + "ERROR: Cannot register kevent for SIGWINCH"); + + args.pa_toptty = 1; + } + + /* + * Listen to key input in top mode. + */ + if (args.pa_flags & FLAG_DO_TOP) { + EV_SET(&kev, fileno(stdin), EVFILT_READ, EV_ADD, 0, 0, NULL); + if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) + err(EX_OSERR, "ERROR: Cannot register kevent"); + } + + EV_SET(&kev, SIGINT, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); + if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) + err(EX_OSERR, "ERROR: Cannot register kevent for SIGINT"); + + EV_SET(&kev, SIGIO, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); + if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) + err(EX_OSERR, "ERROR: Cannot register kevent for SIGIO"); + + /* + * An exec() failure of a forked child is signalled by the + * child sending the parent a SIGCHLD. We don't register an + * actual signal handler for SIGCHLD, but instead use our + * kqueue to pick up the signal. + */ + EV_SET(&kev, SIGCHLD, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); + if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) + err(EX_OSERR, "ERROR: Cannot register kevent for SIGCHLD"); + + /* + * Setup a timer if we have counting mode PMCs needing to be printed or + * top mode plugin is active. + */ + if (((args.pa_flags & FLAG_HAS_COUNTING_PMCS) && + (args.pa_required & FLAG_HAS_OUTPUT_LOGFILE) == 0) || + (args.pa_flags & FLAG_DO_TOP)) { + EV_SET(&kev, 0, EVFILT_TIMER, EV_ADD, 0, + args.pa_interval * 1000, NULL); + + if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) + err(EX_OSERR, + "ERROR: Cannot register kevent for timer"); + } + + /* + * Setup a duration timer if we have sampling mode PMCs and + * a duration time is set + */ + if ((args.pa_flags & FLAG_HAS_SAMPLING_PMCS) && + (args.pa_flags & FLAG_HAS_DURATION)) { + EV_SET(&kev, 0, EVFILT_TIMER, EV_ADD, 0, + args.pa_duration * 1000, NULL); + + if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) + err(EX_OSERR, "ERROR: Cannot register kevent for " + "time duration"); + } + + /* attach PMCs to the target process, starting it if specified */ + if (args.pa_flags & FLAG_HAS_COMMANDLINE) + pmcstat_create_process(); + + if (check_driver_stats && pmc_get_driver_stats(&ds_start) < 0) + err(EX_OSERR, "ERROR: Cannot retrieve driver statistics"); + + /* Attach process pmcs to the target process. */ + if (args.pa_flags & (FLAG_HAS_TARGET | FLAG_HAS_COMMANDLINE)) { + if (SLIST_EMPTY(&args.pa_targets)) + errx(EX_DATAERR, + "ERROR: No matching target processes."); + if (args.pa_flags & FLAG_HAS_PROCESS_PMCS) + pmcstat_attach_pmcs(); + + if (pmcstat_kvm) { + kvm_close(pmcstat_kvm); + pmcstat_kvm = NULL; + } + } + + /* start the pmcs */ + pmcstat_start_pmcs(); + + /* start the (commandline) process if needed */ + if (args.pa_flags & FLAG_HAS_COMMANDLINE) + pmcstat_start_process(); + + /* initialize logging */ + pmcstat_initialize_logging(); + + /* Handle SIGINT using the kqueue loop */ + sa.sa_handler = SIG_IGN; + sa.sa_flags = 0; + (void) sigemptyset(&sa.sa_mask); + + if (sigaction(SIGINT, &sa, NULL) < 0) + err(EX_OSERR, "ERROR: Cannot install signal handler"); + + /* + * Setup the top mode display. + */ + if (args.pa_flags & FLAG_DO_TOP) { + args.pa_flags &= ~FLAG_DO_PRINT; + + if (args.pa_toptty) { + /* + * Init ncurses. + */ + initscr(); + if(has_colors() == TRUE) { + args.pa_topcolor = 1; + start_color(); + use_default_colors(); + pair_content(0, &cf, &cb); + init_pair(1, COLOR_RED, cb); + init_pair(2, COLOR_YELLOW, cb); + init_pair(3, COLOR_GREEN, cb); + } + cbreak(); + noecho(); + nonl(); + nodelay(stdscr, 1); + intrflush(stdscr, FALSE); + keypad(stdscr, TRUE); + clear(); + /* Get terminal width / height with ncurses. */ + getmaxyx(stdscr, + pmcstat_displayheight, pmcstat_displaywidth); + pmcstat_displayheight--; pmcstat_displaywidth--; + atexit(pmcstat_topexit); + } + } + + /* + * loop till either the target process (if any) exits, or we + * are killed by a SIGINT or we reached the time duration. + */ + runstate = PMCSTAT_RUNNING; + do_print = do_read = 0; + do { + if ((c = kevent(pmcstat_kq, NULL, 0, &kev, 1, NULL)) <= 0) { + if (errno != EINTR) + err(EX_OSERR, "ERROR: kevent failed"); + else + continue; + } + + if (kev.flags & EV_ERROR) + errc(EX_OSERR, kev.data, "ERROR: kevent failed"); + + switch (kev.filter) { + case EVFILT_PROC: /* target has exited */ + runstate = pmcstat_close_log(); + do_print = 1; + break; + + case EVFILT_READ: /* log file data is present */ + if (kev.ident == (unsigned)fileno(stdin) && + (args.pa_flags & FLAG_DO_TOP)) { + if (pmcstat_keypress_log()) + runstate = pmcstat_close_log(); + } else { + do_read = 0; + runstate = pmcstat_process_log(); + } + break; + + case EVFILT_SIGNAL: + if (kev.ident == SIGCHLD) { + /* + * The child process sends us a + * SIGCHLD if its exec() failed. We + * wait for it to exit and then exit + * ourselves. + */ + (void) wait(&c); + runstate = PMCSTAT_FINISHED; + } else if (kev.ident == SIGIO) { + /* + * We get a SIGIO if a PMC loses all + * of its targets, or if logfile + * writes encounter an error. + */ + runstate = pmcstat_close_log(); + do_print = 1; /* print PMCs at exit */ + } else if (kev.ident == SIGINT) { + /* Kill the child process if we started it */ + if (args.pa_flags & FLAG_HAS_COMMANDLINE) + pmcstat_kill_process(); + runstate = pmcstat_close_log(); + } else if (kev.ident == SIGWINCH) { + if (ioctl(fileno(args.pa_printfile), + TIOCGWINSZ, &ws) < 0) + err(EX_OSERR, + "ERROR: Cannot determine window size"); + pmcstat_displayheight = ws.ws_row - 1; + pmcstat_displaywidth = ws.ws_col - 1; + } else + assert(0); + + break; + + case EVFILT_TIMER: + /* time duration reached, exit */ + if (args.pa_flags & FLAG_HAS_DURATION) { + runstate = PMCSTAT_FINISHED; + break; + } + /* print out counting PMCs */ + if ((args.pa_flags & FLAG_DO_TOP) && + pmc_flush_logfile() == 0) + do_read = 1; + do_print = 1; + break; + + } + + if (do_print && !do_read) { + if ((args.pa_required & FLAG_HAS_OUTPUT_LOGFILE) == 0) { + pmcstat_print_pmcs(); + if (runstate == PMCSTAT_FINISHED && + /* final newline */ + (args.pa_flags & FLAG_DO_PRINT) == 0) + (void) fprintf(args.pa_printfile, "\n"); + } + if (args.pa_flags & FLAG_DO_TOP) + pmcstat_display_log(); + do_print = 0; + } + + } while (runstate != PMCSTAT_FINISHED); + + if ((args.pa_flags & FLAG_DO_TOP) && args.pa_toptty) { + pmcstat_topexit(); + args.pa_toptty = 0; + } + + /* flush any pending log entries */ + if (args.pa_flags & (FLAG_HAS_OUTPUT_LOGFILE | FLAG_HAS_PIPE)) + pmc_close_logfile(); + + pmcstat_cleanup(); + + free(args.pa_kernel); + + /* check if the driver lost any samples or events */ + if (check_driver_stats) { + if (pmc_get_driver_stats(&ds_end) < 0) + err(EX_OSERR, + "ERROR: Cannot retrieve driver statistics"); + if (ds_start.pm_intr_bufferfull != ds_end.pm_intr_bufferfull && + args.pa_verbosity > 0) + warnx( +"WARNING: sampling was paused at least %u time%s.\n" +"Please consider tuning the \"kern.hwpmc.nsamples\" tunable.", + ds_end.pm_intr_bufferfull - + ds_start.pm_intr_bufferfull, + ((ds_end.pm_intr_bufferfull - + ds_start.pm_intr_bufferfull) != 1) ? "s" : "" + ); + if (ds_start.pm_buffer_requests_failed != + ds_end.pm_buffer_requests_failed && + args.pa_verbosity > 0) + warnx( +"WARNING: at least %u event%s were discarded while running.\n" +"Please consider tuning the \"kern.hwpmc.nbuffers\" tunable.", + ds_end.pm_buffer_requests_failed - + ds_start.pm_buffer_requests_failed, + ((ds_end.pm_buffer_requests_failed - + ds_start.pm_buffer_requests_failed) != 1) ? "s" : "" + ); + } + + exit(EX_OK); +} |