pmcstat.c revision 168927
1145256Sjkoshy/*- 2157144Sjkoshy * Copyright (c) 2003-2006, Joseph Koshy 3145256Sjkoshy * All rights reserved. 4145256Sjkoshy * 5145256Sjkoshy * Redistribution and use in source and binary forms, with or without 6145256Sjkoshy * modification, are permitted provided that the following conditions 7145256Sjkoshy * are met: 8145256Sjkoshy * 1. Redistributions of source code must retain the above copyright 9145256Sjkoshy * notice, this list of conditions and the following disclaimer. 10145256Sjkoshy * 2. Redistributions in binary form must reproduce the above copyright 11145256Sjkoshy * notice, this list of conditions and the following disclaimer in the 12145256Sjkoshy * documentation and/or other materials provided with the distribution. 13145256Sjkoshy * 14145256Sjkoshy * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15145256Sjkoshy * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16145256Sjkoshy * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17145256Sjkoshy * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18145256Sjkoshy * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19145256Sjkoshy * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20145256Sjkoshy * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21145256Sjkoshy * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22145256Sjkoshy * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23145256Sjkoshy * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24145256Sjkoshy * SUCH DAMAGE. 25145256Sjkoshy */ 26145256Sjkoshy 27145256Sjkoshy#include <sys/cdefs.h> 28145256Sjkoshy__FBSDID("$FreeBSD: head/usr.sbin/pmcstat/pmcstat.c 168927 2007-04-21 12:04:03Z jkoshy $"); 29145256Sjkoshy 30145256Sjkoshy#include <sys/types.h> 31145256Sjkoshy#include <sys/event.h> 32145256Sjkoshy#include <sys/queue.h> 33147708Sjkoshy#include <sys/socket.h> 34147708Sjkoshy#include <sys/stat.h> 35145256Sjkoshy#include <sys/time.h> 36145256Sjkoshy#include <sys/ttycom.h> 37145256Sjkoshy#include <sys/wait.h> 38145256Sjkoshy 39145256Sjkoshy#include <assert.h> 40145256Sjkoshy#include <err.h> 41145256Sjkoshy#include <errno.h> 42145256Sjkoshy#include <fcntl.h> 43157144Sjkoshy#include <libgen.h> 44145256Sjkoshy#include <limits.h> 45145256Sjkoshy#include <math.h> 46145256Sjkoshy#include <pmc.h> 47147191Sjkoshy#include <pmclog.h> 48145256Sjkoshy#include <signal.h> 49145256Sjkoshy#include <stdarg.h> 50147708Sjkoshy#include <stdint.h> 51145256Sjkoshy#include <stdio.h> 52145256Sjkoshy#include <stdlib.h> 53145256Sjkoshy#include <string.h> 54145256Sjkoshy#include <sysexits.h> 55145256Sjkoshy#include <unistd.h> 56145256Sjkoshy 57147708Sjkoshy#include "pmcstat.h" 58147708Sjkoshy 59147191Sjkoshy/* 60147191Sjkoshy * A given invocation of pmcstat(8) can manage multiple PMCs of both 61147191Sjkoshy * the system-wide and per-process variety. Each of these could be in 62147191Sjkoshy * 'counting mode' or in 'sampling mode'. 63147191Sjkoshy * 64147191Sjkoshy * For 'counting mode' PMCs, pmcstat(8) will periodically issue a 65147191Sjkoshy * pmc_read() at the configured time interval and print out the value 66147191Sjkoshy * of the requested PMCs. 67147191Sjkoshy * 68147191Sjkoshy * For 'sampling mode' PMCs it can log to a file for offline analysis, 69147191Sjkoshy * or can analyse sampling data "on the fly", either by converting 70147191Sjkoshy * samples to printed textual form or by creating gprof(1) compatible 71147191Sjkoshy * profiles, one per program executed. When creating gprof(1) 72147191Sjkoshy * profiles it can optionally merge entries from multiple processes 73147191Sjkoshy * for a given executable into a single profile file. 74147191Sjkoshy */ 75147191Sjkoshy 76147708Sjkoshy/* Globals */ 77145256Sjkoshy 78145256Sjkoshyint pmcstat_interrupt = 0; 79145256Sjkoshyint pmcstat_displayheight = DEFAULT_DISPLAY_HEIGHT; 80145256Sjkoshyint pmcstat_pipefd[NPIPEFD]; 81145256Sjkoshyint pmcstat_kq; 82145256Sjkoshy 83145256Sjkoshy/* 84145256Sjkoshy * cleanup 85145256Sjkoshy */ 86145256Sjkoshy 87145256Sjkoshyvoid 88145256Sjkoshypmcstat_cleanup(struct pmcstat_args *a) 89145256Sjkoshy{ 90145256Sjkoshy struct pmcstat_ev *ev, *tmp; 91145256Sjkoshy 92145256Sjkoshy /* release allocated PMCs. */ 93145256Sjkoshy STAILQ_FOREACH_SAFE(ev, &a->pa_head, ev_next, tmp) 94145256Sjkoshy if (ev->ev_pmcid != PMC_ID_INVALID) { 95145256Sjkoshy if (pmc_release(ev->ev_pmcid) < 0) 96145256Sjkoshy err(EX_OSERR, "ERROR: cannot release pmc " 97147191Sjkoshy "0x%x \"%s\"", ev->ev_pmcid, ev->ev_name); 98145256Sjkoshy free(ev->ev_name); 99145256Sjkoshy free(ev->ev_spec); 100145256Sjkoshy STAILQ_REMOVE(&a->pa_head, ev, pmcstat_ev, ev_next); 101145256Sjkoshy free(ev); 102145256Sjkoshy } 103147191Sjkoshy 104147863Sjkoshy /* de-configure the log file if present. */ 105147863Sjkoshy if (a->pa_flags & (FLAG_HAS_PIPE | FLAG_HAS_OUTPUT_LOGFILE)) 106147863Sjkoshy (void) pmc_configure_logfile(-1); 107147863Sjkoshy 108147191Sjkoshy if (a->pa_logparser) { 109147191Sjkoshy pmclog_close(a->pa_logparser); 110147191Sjkoshy a->pa_logparser = NULL; 111147191Sjkoshy } 112147708Sjkoshy 113147708Sjkoshy if (a->pa_flags & (FLAG_HAS_PIPE | FLAG_HAS_OUTPUT_LOGFILE)) 114157144Sjkoshy pmcstat_shutdown_logging(a); 115145256Sjkoshy} 116145256Sjkoshy 117145256Sjkoshyvoid 118145256Sjkoshypmcstat_start_pmcs(struct pmcstat_args *a) 119145256Sjkoshy{ 120145256Sjkoshy struct pmcstat_ev *ev; 121145256Sjkoshy 122145256Sjkoshy STAILQ_FOREACH(ev, &args.pa_head, ev_next) { 123145256Sjkoshy 124145256Sjkoshy assert(ev->ev_pmcid != PMC_ID_INVALID); 125145256Sjkoshy 126145256Sjkoshy if (pmc_start(ev->ev_pmcid) < 0) { 127147191Sjkoshy warn("ERROR: Cannot start pmc 0x%x \"%s\"", 128145256Sjkoshy ev->ev_pmcid, ev->ev_name); 129145256Sjkoshy pmcstat_cleanup(a); 130147191Sjkoshy exit(EX_OSERR); 131145256Sjkoshy } 132145256Sjkoshy } 133145256Sjkoshy 134145256Sjkoshy} 135145256Sjkoshy 136145256Sjkoshyvoid 137145256Sjkoshypmcstat_print_headers(struct pmcstat_args *a) 138145256Sjkoshy{ 139145256Sjkoshy struct pmcstat_ev *ev; 140145256Sjkoshy int c; 141145256Sjkoshy 142147708Sjkoshy (void) fprintf(a->pa_printfile, PRINT_HEADER_PREFIX); 143145256Sjkoshy 144145256Sjkoshy STAILQ_FOREACH(ev, &a->pa_head, ev_next) { 145145256Sjkoshy if (PMC_IS_SAMPLING_MODE(ev->ev_mode)) 146145256Sjkoshy continue; 147145256Sjkoshy 148145256Sjkoshy c = PMC_IS_SYSTEM_MODE(ev->ev_mode) ? 's' : 'p'; 149145256Sjkoshy 150145256Sjkoshy if (ev->ev_fieldskip != 0) { 151147708Sjkoshy (void) fprintf(a->pa_printfile, "%*s%c/%*s ", 152145256Sjkoshy ev->ev_fieldskip, "", c, 153145256Sjkoshy ev->ev_fieldwidth - ev->ev_fieldskip - 2, 154145256Sjkoshy ev->ev_name); 155145256Sjkoshy } else 156147708Sjkoshy (void) fprintf(a->pa_printfile, "%c/%*s ", 157145256Sjkoshy c, ev->ev_fieldwidth - 2, ev->ev_name); 158145256Sjkoshy } 159145256Sjkoshy 160147708Sjkoshy (void) fflush(a->pa_printfile); 161145256Sjkoshy} 162145256Sjkoshy 163145256Sjkoshyvoid 164145256Sjkoshypmcstat_print_counters(struct pmcstat_args *a) 165145256Sjkoshy{ 166145256Sjkoshy int extra_width; 167145256Sjkoshy struct pmcstat_ev *ev; 168145256Sjkoshy pmc_value_t value; 169145256Sjkoshy 170145256Sjkoshy extra_width = sizeof(PRINT_HEADER_PREFIX) - 1; 171145256Sjkoshy 172145256Sjkoshy STAILQ_FOREACH(ev, &a->pa_head, ev_next) { 173145256Sjkoshy 174145256Sjkoshy /* skip sampling mode counters */ 175145256Sjkoshy if (PMC_IS_SAMPLING_MODE(ev->ev_mode)) 176145256Sjkoshy continue; 177145256Sjkoshy 178145256Sjkoshy if (pmc_read(ev->ev_pmcid, &value) < 0) 179145256Sjkoshy err(EX_OSERR, "ERROR: Cannot read pmc " 180145256Sjkoshy "\"%s\"", ev->ev_name); 181145256Sjkoshy 182147708Sjkoshy (void) fprintf(a->pa_printfile, "%*ju ", 183147708Sjkoshy ev->ev_fieldwidth + extra_width, 184147708Sjkoshy (uintmax_t) ev->ev_cumulative ? value : 185147708Sjkoshy (value - ev->ev_saved)); 186147708Sjkoshy 187145256Sjkoshy if (ev->ev_cumulative == 0) 188145256Sjkoshy ev->ev_saved = value; 189145256Sjkoshy extra_width = 0; 190145256Sjkoshy } 191145256Sjkoshy 192147708Sjkoshy (void) fflush(a->pa_printfile); 193145256Sjkoshy} 194145256Sjkoshy 195145256Sjkoshy/* 196145256Sjkoshy * Print output 197145256Sjkoshy */ 198145256Sjkoshy 199145256Sjkoshyvoid 200145256Sjkoshypmcstat_print_pmcs(struct pmcstat_args *a) 201145256Sjkoshy{ 202145256Sjkoshy static int linecount = 0; 203145256Sjkoshy 204147708Sjkoshy /* check if we need to print a header line */ 205145256Sjkoshy if (++linecount > pmcstat_displayheight) { 206147708Sjkoshy (void) fprintf(a->pa_printfile, "\n"); 207145256Sjkoshy linecount = 1; 208145256Sjkoshy } 209145256Sjkoshy if (linecount == 1) 210145256Sjkoshy pmcstat_print_headers(a); 211147708Sjkoshy (void) fprintf(a->pa_printfile, "\n"); 212145256Sjkoshy 213145256Sjkoshy pmcstat_print_counters(a); 214145256Sjkoshy 215145256Sjkoshy return; 216145256Sjkoshy} 217145256Sjkoshy 218145256Sjkoshy/* 219145256Sjkoshy * Do process profiling 220145256Sjkoshy * 221145256Sjkoshy * If a pid was specified, attach each allocated PMC to the target 222145256Sjkoshy * process. Otherwise, fork a child and attach the PMCs to the child, 223145256Sjkoshy * and have the child exec() the target program. 224145256Sjkoshy */ 225145256Sjkoshy 226145256Sjkoshyvoid 227145256Sjkoshypmcstat_setup_process(struct pmcstat_args *a) 228145256Sjkoshy{ 229145256Sjkoshy char token; 230145256Sjkoshy struct pmcstat_ev *ev; 231145256Sjkoshy struct kevent kev; 232145256Sjkoshy 233145256Sjkoshy if (a->pa_flags & FLAG_HAS_PID) { 234147191Sjkoshy STAILQ_FOREACH(ev, &a->pa_head, ev_next) 235145256Sjkoshy if (pmc_attach(ev->ev_pmcid, a->pa_pid) != 0) 236145256Sjkoshy err(EX_OSERR, "ERROR: cannot attach pmc \"%s\" to " 237145256Sjkoshy "process %d", ev->ev_name, (int) a->pa_pid); 238145256Sjkoshy } else { 239145256Sjkoshy 240145256Sjkoshy /* 241145256Sjkoshy * We need to fork a new process and startup the child 242145256Sjkoshy * using execvp(). Before doing the exec() the child 243145256Sjkoshy * process reads its pipe for a token so that the parent 244145256Sjkoshy * can finish doing its pmc_attach() calls. 245145256Sjkoshy */ 246145256Sjkoshy if (pipe(pmcstat_pipefd) < 0) 247145256Sjkoshy err(EX_OSERR, "ERROR: cannot create pipe"); 248145256Sjkoshy 249145256Sjkoshy switch (a->pa_pid = fork()) { 250145256Sjkoshy case -1: 251145256Sjkoshy err(EX_OSERR, "ERROR: cannot fork"); 252145256Sjkoshy /*NOTREACHED*/ 253145256Sjkoshy 254145256Sjkoshy case 0: /* child */ 255145256Sjkoshy 256145256Sjkoshy /* wait for our parent to signal us */ 257145256Sjkoshy (void) close(pmcstat_pipefd[WRITEPIPEFD]); 258145256Sjkoshy if (read(pmcstat_pipefd[READPIPEFD], &token, 1) < 0) 259145256Sjkoshy err(EX_OSERR, "ERROR (child): cannot read " 260145256Sjkoshy "token"); 261145256Sjkoshy (void) close(pmcstat_pipefd[READPIPEFD]); 262145256Sjkoshy 263145256Sjkoshy /* exec() the program requested */ 264147191Sjkoshy execvp(*a->pa_argv, a->pa_argv); 265147191Sjkoshy /* and if that fails, notify the parent */ 266147191Sjkoshy kill(getppid(), SIGCHLD); 267147191Sjkoshy err(EX_OSERR, "ERROR: execvp \"%s\" failed", 268147191Sjkoshy *a->pa_argv); 269145256Sjkoshy /*NOTREACHED*/ 270145256Sjkoshy 271145256Sjkoshy default: /* parent */ 272145256Sjkoshy 273145256Sjkoshy (void) close(pmcstat_pipefd[READPIPEFD]); 274145256Sjkoshy 275145256Sjkoshy /* attach all our PMCs to the child */ 276145256Sjkoshy STAILQ_FOREACH(ev, &args.pa_head, ev_next) 277145256Sjkoshy if (PMC_IS_VIRTUAL_MODE(ev->ev_mode) && 278145256Sjkoshy pmc_attach(ev->ev_pmcid, a->pa_pid) != 0) 279145256Sjkoshy err(EX_OSERR, "ERROR: cannot attach pmc " 280145256Sjkoshy "\"%s\" to process %d", ev->ev_name, 281145256Sjkoshy (int) a->pa_pid); 282145256Sjkoshy 283145256Sjkoshy } 284145256Sjkoshy } 285145256Sjkoshy 286147191Sjkoshy /* Ask to be notified via a kevent when the target process exits */ 287147191Sjkoshy EV_SET(&kev, a->pa_pid, EVFILT_PROC, EV_ADD|EV_ONESHOT, NOTE_EXIT, 0, 288147191Sjkoshy NULL); 289145256Sjkoshy if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) 290147191Sjkoshy err(EX_OSERR, "ERROR: cannot monitor child process %d", 291145256Sjkoshy a->pa_pid); 292145256Sjkoshy return; 293145256Sjkoshy} 294145256Sjkoshy 295145256Sjkoshyvoid 296145256Sjkoshypmcstat_start_process(struct pmcstat_args *a) 297145256Sjkoshy{ 298145256Sjkoshy 299145256Sjkoshy /* nothing to do: target is already running */ 300145256Sjkoshy if (a->pa_flags & FLAG_HAS_PID) 301145256Sjkoshy return; 302145256Sjkoshy 303145256Sjkoshy /* write token to child to state that we are ready */ 304145256Sjkoshy if (write(pmcstat_pipefd[WRITEPIPEFD], "+", 1) != 1) 305145256Sjkoshy err(EX_OSERR, "ERROR: write failed"); 306145256Sjkoshy 307145256Sjkoshy (void) close(pmcstat_pipefd[WRITEPIPEFD]); 308145256Sjkoshy} 309145256Sjkoshy 310145256Sjkoshyvoid 311145256Sjkoshypmcstat_show_usage(void) 312145256Sjkoshy{ 313145256Sjkoshy errx(EX_USAGE, 314145256Sjkoshy "[options] [commandline]\n" 315145256Sjkoshy "\t Measure process and/or system performance using hardware\n" 316145256Sjkoshy "\t performance monitoring counters.\n" 317145256Sjkoshy "\t Options include:\n" 318147191Sjkoshy "\t -C\t\t (toggle) show cumulative counts\n" 319147191Sjkoshy "\t -D path\t create profiles in directory \"path\"\n" 320147191Sjkoshy "\t -E\t\t (toggle) show counts at process exit\n" 321157144Sjkoshy "\t -M file\t print executable/gmon file map to \"file\"\n" 322147191Sjkoshy "\t -O file\t send log output to \"file\"\n" 323147191Sjkoshy "\t -P spec\t allocate a process-private sampling PMC\n" 324147191Sjkoshy "\t -R file\t read events from \"file\"\n" 325147191Sjkoshy "\t -S spec\t allocate a system-wide sampling PMC\n" 326147191Sjkoshy "\t -W\t\t (toggle) show counts per context switch\n" 327147191Sjkoshy "\t -c cpu\t\t set cpu for subsequent system-wide PMCs\n" 328147191Sjkoshy "\t -d\t\t (toggle) track descendants\n" 329147191Sjkoshy "\t -g\t\t produce gprof(1) compatible profiles\n" 330162804Sru "\t -k dir\t\t set the path to the kernel\n" 331145256Sjkoshy "\t -n rate\t set sampling rate\n" 332145256Sjkoshy "\t -o file\t send print output to \"file\"\n" 333147191Sjkoshy "\t -p spec\t allocate a process-private counting PMC\n" 334157144Sjkoshy "\t -q\t\t suppress verbosity\n" 335157144Sjkoshy "\t -r fsroot\t specify FS root directory\n" 336147191Sjkoshy "\t -s spec\t allocate a system-wide counting PMC\n" 337145796Sjkoshy "\t -t pid\t\t attach to running process with pid \"pid\"\n" 338157144Sjkoshy "\t -v\t\t increase verbosity\n" 339145256Sjkoshy "\t -w secs\t set printing time interval" 340145256Sjkoshy ); 341145256Sjkoshy} 342145256Sjkoshy 343145256Sjkoshy/* 344145256Sjkoshy * Main 345145256Sjkoshy */ 346145256Sjkoshy 347145256Sjkoshyint 348145256Sjkoshymain(int argc, char **argv) 349145256Sjkoshy{ 350145256Sjkoshy double interval; 351145256Sjkoshy int option, npmc, ncpu; 352151542Sjkoshy int c, check_driver_stats, current_cpu, current_sampling_count; 353147191Sjkoshy int do_print, do_descendants; 354147191Sjkoshy int do_logproccsw, do_logprocexit; 355147191Sjkoshy int pipefd[2]; 356147191Sjkoshy int use_cumulative_counts; 357145256Sjkoshy pid_t pid; 358157144Sjkoshy char *end, *tmp; 359147191Sjkoshy const char *errmsg; 360147191Sjkoshy enum pmcstat_state runstate; 361151542Sjkoshy struct pmc_driverstats ds_start, ds_end; 362145256Sjkoshy struct pmcstat_ev *ev; 363145256Sjkoshy struct sigaction sa; 364145256Sjkoshy struct kevent kev; 365145256Sjkoshy struct winsize ws; 366147708Sjkoshy struct stat sb; 367157144Sjkoshy char buffer[PATH_MAX]; 368145256Sjkoshy 369151542Sjkoshy check_driver_stats = 0; 370145256Sjkoshy current_cpu = 0; 371145256Sjkoshy current_sampling_count = DEFAULT_SAMPLE_COUNT; 372145256Sjkoshy do_descendants = 0; 373147191Sjkoshy do_logproccsw = 0; 374147191Sjkoshy do_logprocexit = 0; 375145256Sjkoshy use_cumulative_counts = 0; 376147191Sjkoshy args.pa_required = 0; 377145256Sjkoshy args.pa_flags = 0; 378157144Sjkoshy args.pa_verbosity = 1; 379145256Sjkoshy args.pa_pid = (pid_t) -1; 380147708Sjkoshy args.pa_logfd = -1; 381157144Sjkoshy args.pa_fsroot = ""; 382157144Sjkoshy args.pa_kernel = strdup("/boot/kernel"); 383147708Sjkoshy args.pa_samplesdir = "."; 384147708Sjkoshy args.pa_printfile = stderr; 385145256Sjkoshy args.pa_interval = DEFAULT_WAIT_INTERVAL; 386157144Sjkoshy args.pa_mapfilename = NULL; 387145256Sjkoshy STAILQ_INIT(&args.pa_head); 388153704Sjkoshy bzero(&ds_start, sizeof(ds_start)); 389153704Sjkoshy bzero(&ds_end, sizeof(ds_end)); 390145256Sjkoshy ev = NULL; 391145256Sjkoshy 392157144Sjkoshy while ((option = getopt(argc, argv, 393157144Sjkoshy "CD:EM:O:P:R:S:Wc:dgk:n:o:p:qr:s:t:vw:")) != -1) 394145256Sjkoshy switch (option) { 395145256Sjkoshy case 'C': /* cumulative values */ 396145256Sjkoshy use_cumulative_counts = !use_cumulative_counts; 397147191Sjkoshy args.pa_required |= FLAG_HAS_COUNTING_PMCS; 398145256Sjkoshy break; 399145256Sjkoshy 400145256Sjkoshy case 'c': /* CPU */ 401145256Sjkoshy current_cpu = strtol(optarg, &end, 0); 402145256Sjkoshy if (*end != '\0' || current_cpu < 0) 403145256Sjkoshy errx(EX_USAGE, 404147191Sjkoshy "ERROR: Illegal CPU number \"%s\".", 405145256Sjkoshy optarg); 406147191Sjkoshy args.pa_required |= FLAG_HAS_SYSTEM_PMCS; 407145256Sjkoshy break; 408145256Sjkoshy 409147708Sjkoshy case 'D': 410147708Sjkoshy if (stat(optarg, &sb) < 0) 411147708Sjkoshy err(EX_OSERR, "ERROR: Cannot stat \"%s\"", 412147708Sjkoshy optarg); 413147708Sjkoshy if (!S_ISDIR(sb.st_mode)) 414147708Sjkoshy errx(EX_USAGE, "ERROR: \"%s\" is not a " 415147708Sjkoshy "directory", optarg); 416147708Sjkoshy args.pa_samplesdir = optarg; 417147708Sjkoshy args.pa_flags |= FLAG_HAS_SAMPLESDIR; 418147708Sjkoshy args.pa_required |= FLAG_DO_GPROF; 419147708Sjkoshy break; 420147708Sjkoshy 421145256Sjkoshy case 'd': /* toggle descendents */ 422145256Sjkoshy do_descendants = !do_descendants; 423147191Sjkoshy args.pa_required |= FLAG_HAS_PROCESS_PMCS; 424145256Sjkoshy break; 425145256Sjkoshy 426147191Sjkoshy case 'g': /* produce gprof compatible profiles */ 427147191Sjkoshy args.pa_flags |= FLAG_DO_GPROF; 428147191Sjkoshy break; 429147191Sjkoshy 430147708Sjkoshy case 'k': /* pathname to the kernel */ 431157144Sjkoshy free(args.pa_kernel); 432157144Sjkoshy args.pa_kernel = strdup(optarg); 433147708Sjkoshy args.pa_required |= FLAG_DO_GPROF; 434147708Sjkoshy args.pa_flags |= FLAG_HAS_KERNELPATH; 435147191Sjkoshy break; 436147191Sjkoshy 437147191Sjkoshy case 'E': /* log process exit */ 438147191Sjkoshy do_logprocexit = !do_logprocexit; 439147191Sjkoshy args.pa_required |= (FLAG_HAS_PROCESS_PMCS | 440147708Sjkoshy FLAG_HAS_COUNTING_PMCS | FLAG_HAS_OUTPUT_LOGFILE); 441147191Sjkoshy break; 442147191Sjkoshy 443157144Sjkoshy case 'M': /* mapfile */ 444157144Sjkoshy args.pa_mapfilename = optarg; 445157144Sjkoshy break; 446157144Sjkoshy 447145256Sjkoshy case 'p': /* process virtual counting PMC */ 448145256Sjkoshy case 's': /* system-wide counting PMC */ 449145256Sjkoshy case 'P': /* process virtual sampling PMC */ 450145256Sjkoshy case 'S': /* system-wide sampling PMC */ 451145256Sjkoshy if ((ev = malloc(sizeof(*ev))) == NULL) 452147191Sjkoshy errx(EX_SOFTWARE, "ERROR: Out of memory."); 453145256Sjkoshy 454145256Sjkoshy switch (option) { 455145256Sjkoshy case 'p': ev->ev_mode = PMC_MODE_TC; break; 456145256Sjkoshy case 's': ev->ev_mode = PMC_MODE_SC; break; 457145256Sjkoshy case 'P': ev->ev_mode = PMC_MODE_TS; break; 458145256Sjkoshy case 'S': ev->ev_mode = PMC_MODE_SS; break; 459145256Sjkoshy } 460145256Sjkoshy 461147191Sjkoshy if (option == 'P' || option == 'p') { 462147191Sjkoshy args.pa_flags |= FLAG_HAS_PROCESS_PMCS; 463147708Sjkoshy args.pa_required |= (FLAG_HAS_COMMANDLINE | 464147191Sjkoshy FLAG_HAS_PID); 465147191Sjkoshy } 466145256Sjkoshy 467147191Sjkoshy if (option == 'P' || option == 'S') { 468147191Sjkoshy args.pa_flags |= FLAG_HAS_SAMPLING_PMCS; 469147708Sjkoshy args.pa_required |= (FLAG_HAS_PIPE | 470147708Sjkoshy FLAG_HAS_OUTPUT_LOGFILE); 471147191Sjkoshy } 472145256Sjkoshy 473145256Sjkoshy if (option == 'p' || option == 's') 474147191Sjkoshy args.pa_flags |= FLAG_HAS_COUNTING_PMCS; 475145256Sjkoshy 476147191Sjkoshy if (option == 's' || option == 'S') 477147191Sjkoshy args.pa_flags |= FLAG_HAS_SYSTEM_PMCS; 478147191Sjkoshy 479145256Sjkoshy ev->ev_spec = strdup(optarg); 480145256Sjkoshy 481145256Sjkoshy if (option == 'S' || option == 'P') 482145256Sjkoshy ev->ev_count = current_sampling_count; 483145256Sjkoshy else 484145256Sjkoshy ev->ev_count = -1; 485145256Sjkoshy 486145256Sjkoshy if (option == 'S' || option == 's') 487145256Sjkoshy ev->ev_cpu = current_cpu; 488145256Sjkoshy else 489145256Sjkoshy ev->ev_cpu = PMC_CPU_ANY; 490145256Sjkoshy 491147191Sjkoshy ev->ev_flags = 0; 492147191Sjkoshy if (do_descendants) 493147191Sjkoshy ev->ev_flags |= PMC_F_DESCENDANTS; 494147191Sjkoshy if (do_logprocexit) 495147191Sjkoshy ev->ev_flags |= PMC_F_LOG_PROCEXIT; 496147191Sjkoshy if (do_logproccsw) 497147191Sjkoshy ev->ev_flags |= PMC_F_LOG_PROCCSW; 498147191Sjkoshy 499145256Sjkoshy ev->ev_cumulative = use_cumulative_counts; 500145256Sjkoshy 501145256Sjkoshy ev->ev_saved = 0LL; 502145256Sjkoshy ev->ev_pmcid = PMC_ID_INVALID; 503145256Sjkoshy 504145256Sjkoshy /* extract event name */ 505145256Sjkoshy c = strcspn(optarg, ", \t"); 506145256Sjkoshy ev->ev_name = malloc(c + 1); 507145256Sjkoshy (void) strncpy(ev->ev_name, optarg, c); 508145256Sjkoshy *(ev->ev_name + c) = '\0'; 509145256Sjkoshy 510145256Sjkoshy STAILQ_INSERT_TAIL(&args.pa_head, ev, ev_next); 511145256Sjkoshy 512145256Sjkoshy break; 513145256Sjkoshy 514145256Sjkoshy case 'n': /* sampling count */ 515145256Sjkoshy current_sampling_count = strtol(optarg, &end, 0); 516145256Sjkoshy if (*end != '\0' || current_sampling_count <= 0) 517145256Sjkoshy errx(EX_USAGE, 518147191Sjkoshy "ERROR: Illegal count value \"%s\".", 519145256Sjkoshy optarg); 520147191Sjkoshy args.pa_required |= FLAG_HAS_SAMPLING_PMCS; 521145256Sjkoshy break; 522145256Sjkoshy 523145256Sjkoshy case 'o': /* outputfile */ 524147708Sjkoshy if (args.pa_printfile != NULL) 525147708Sjkoshy (void) fclose(args.pa_printfile); 526147708Sjkoshy if ((args.pa_printfile = fopen(optarg, "w")) == NULL) 527145256Sjkoshy errx(EX_OSERR, "ERROR: cannot open \"%s\" for " 528147191Sjkoshy "writing.", optarg); 529147708Sjkoshy args.pa_flags |= FLAG_DO_PRINT; 530147191Sjkoshy break; 531145256Sjkoshy 532145256Sjkoshy case 'O': /* sampling output */ 533147708Sjkoshy if (args.pa_outputpath) 534147708Sjkoshy errx(EX_USAGE, "ERROR: option -O may only be " 535147191Sjkoshy "specified once."); 536147708Sjkoshy args.pa_outputpath = optarg; 537147708Sjkoshy args.pa_flags |= FLAG_HAS_OUTPUT_LOGFILE; 538145256Sjkoshy break; 539145256Sjkoshy 540157144Sjkoshy case 'q': /* quiet mode */ 541157144Sjkoshy args.pa_verbosity = 0; 542157144Sjkoshy break; 543157144Sjkoshy 544157144Sjkoshy case 'r': /* root FS path */ 545157144Sjkoshy args.pa_fsroot = optarg; 546157144Sjkoshy break; 547157144Sjkoshy 548147708Sjkoshy case 'R': /* read an existing log file */ 549147708Sjkoshy if (args.pa_logparser != NULL) 550147708Sjkoshy errx(EX_USAGE, "ERROR: option -R may only be " 551147708Sjkoshy "specified once."); 552147708Sjkoshy args.pa_inputpath = optarg; 553147708Sjkoshy if (args.pa_printfile == stderr) 554147708Sjkoshy args.pa_printfile = stdout; 555147708Sjkoshy args.pa_flags |= FLAG_READ_LOGFILE; 556147708Sjkoshy break; 557147708Sjkoshy 558145256Sjkoshy case 't': /* target pid */ 559145256Sjkoshy pid = strtol(optarg, &end, 0); 560145256Sjkoshy if (*end != '\0' || pid <= 0) 561145256Sjkoshy errx(EX_USAGE, "ERROR: Illegal pid value " 562147191Sjkoshy "\"%s\".", optarg); 563145256Sjkoshy 564145256Sjkoshy args.pa_flags |= FLAG_HAS_PID; 565147191Sjkoshy args.pa_required |= FLAG_HAS_PROCESS_PMCS; 566145256Sjkoshy args.pa_pid = pid; 567145256Sjkoshy break; 568145256Sjkoshy 569157144Sjkoshy case 'v': /* verbose */ 570157144Sjkoshy args.pa_verbosity++; 571157144Sjkoshy break; 572157144Sjkoshy 573145256Sjkoshy case 'w': /* wait interval */ 574145256Sjkoshy interval = strtod(optarg, &end); 575145256Sjkoshy if (*end != '\0' || interval <= 0) 576145256Sjkoshy errx(EX_USAGE, "ERROR: Illegal wait interval " 577147191Sjkoshy "value \"%s\".", optarg); 578145256Sjkoshy args.pa_flags |= FLAG_HAS_WAIT_INTERVAL; 579147708Sjkoshy args.pa_required |= FLAG_HAS_COUNTING_PMCS; 580145256Sjkoshy args.pa_interval = interval; 581147191Sjkoshy break; 582145256Sjkoshy 583147191Sjkoshy case 'W': /* toggle LOG_CSW */ 584147191Sjkoshy do_logproccsw = !do_logproccsw; 585147191Sjkoshy args.pa_required |= (FLAG_HAS_PROCESS_PMCS | 586147708Sjkoshy FLAG_HAS_COUNTING_PMCS | FLAG_HAS_OUTPUT_LOGFILE); 587145256Sjkoshy break; 588145256Sjkoshy 589145256Sjkoshy case '?': 590145256Sjkoshy default: 591145256Sjkoshy pmcstat_show_usage(); 592145256Sjkoshy break; 593145256Sjkoshy 594145256Sjkoshy } 595145256Sjkoshy 596145256Sjkoshy args.pa_argc = (argc -= optind); 597145256Sjkoshy args.pa_argv = (argv += optind); 598145256Sjkoshy 599147708Sjkoshy if (argc) /* command line present */ 600147708Sjkoshy args.pa_flags |= FLAG_HAS_COMMANDLINE; 601145256Sjkoshy 602145256Sjkoshy /* 603145256Sjkoshy * Check invocation syntax. 604145256Sjkoshy */ 605145256Sjkoshy 606147708Sjkoshy /* disallow -O and -R together */ 607147708Sjkoshy if (args.pa_outputpath && args.pa_inputpath) 608147708Sjkoshy errx(EX_USAGE, "ERROR: options -O and -R are mutually " 609147708Sjkoshy "exclusive."); 610147708Sjkoshy 611147708Sjkoshy if (args.pa_flags & FLAG_READ_LOGFILE) { 612147191Sjkoshy errmsg = NULL; 613147708Sjkoshy if (args.pa_flags & FLAG_HAS_COMMANDLINE) 614147191Sjkoshy errmsg = "a command line specification"; 615147191Sjkoshy else if (args.pa_flags & FLAG_HAS_PID) 616147191Sjkoshy errmsg = "option -t"; 617147191Sjkoshy else if (!STAILQ_EMPTY(&args.pa_head)) 618147191Sjkoshy errmsg = "a PMC event specification"; 619147191Sjkoshy if (errmsg) 620147191Sjkoshy errx(EX_USAGE, "ERROR: option -R may not be used with " 621147191Sjkoshy "%s.", errmsg); 622147708Sjkoshy } else if (STAILQ_EMPTY(&args.pa_head)) 623147708Sjkoshy /* All other uses require a PMC spec. */ 624145256Sjkoshy pmcstat_show_usage(); 625145256Sjkoshy 626147191Sjkoshy /* check for -t pid without a process PMC spec */ 627147191Sjkoshy if ((args.pa_required & FLAG_HAS_PID) && 628147191Sjkoshy (args.pa_flags & FLAG_HAS_PROCESS_PMCS) == 0) 629147191Sjkoshy errx(EX_USAGE, "ERROR: option -t requires a process mode PMC " 630147191Sjkoshy "to be specified."); 631147191Sjkoshy 632147191Sjkoshy /* check for process-mode options without a command or -t pid */ 633147191Sjkoshy if ((args.pa_required & FLAG_HAS_PROCESS_PMCS) && 634147708Sjkoshy (args.pa_flags & (FLAG_HAS_COMMANDLINE | FLAG_HAS_PID)) == 0) 635147708Sjkoshy errx(EX_USAGE, "ERROR: options -d, -E, -p, -P, and -W require " 636147708Sjkoshy "a command line or target process."); 637147191Sjkoshy 638147191Sjkoshy /* check for -p | -P without a target process of some sort */ 639147708Sjkoshy if ((args.pa_required & (FLAG_HAS_COMMANDLINE | FLAG_HAS_PID)) && 640147708Sjkoshy (args.pa_flags & (FLAG_HAS_COMMANDLINE | FLAG_HAS_PID)) == 0) 641147708Sjkoshy errx(EX_USAGE, "ERROR: options -P and -p require a " 642147191Sjkoshy "target process or a command line."); 643147191Sjkoshy 644147191Sjkoshy /* check for process-mode options without a process-mode PMC */ 645147191Sjkoshy if ((args.pa_required & FLAG_HAS_PROCESS_PMCS) && 646147191Sjkoshy (args.pa_flags & FLAG_HAS_PROCESS_PMCS) == 0) 647147708Sjkoshy errx(EX_USAGE, "ERROR: options -d, -E, and -W require a " 648147191Sjkoshy "process mode PMC to be specified."); 649147191Sjkoshy 650147191Sjkoshy /* check for -c cpu and not system mode PMCs */ 651147191Sjkoshy if ((args.pa_required & FLAG_HAS_SYSTEM_PMCS) && 652147191Sjkoshy (args.pa_flags & FLAG_HAS_SYSTEM_PMCS) == 0) 653147191Sjkoshy errx(EX_USAGE, "ERROR: option -c requires at least one " 654147191Sjkoshy "system mode PMC to be specified."); 655147191Sjkoshy 656147191Sjkoshy /* check for counting mode options without a counting PMC */ 657147191Sjkoshy if ((args.pa_required & FLAG_HAS_COUNTING_PMCS) && 658147191Sjkoshy (args.pa_flags & FLAG_HAS_COUNTING_PMCS) == 0) 659149805Sjkoshy errx(EX_USAGE, "ERROR: options -C, -W, -o and -w require at " 660149805Sjkoshy "least one counting mode PMC to be specified."); 661147191Sjkoshy 662147191Sjkoshy /* check for sampling mode options without a sampling PMC spec */ 663147191Sjkoshy if ((args.pa_required & FLAG_HAS_SAMPLING_PMCS) && 664147191Sjkoshy (args.pa_flags & FLAG_HAS_SAMPLING_PMCS) == 0) 665147708Sjkoshy errx(EX_USAGE, "ERROR: options -n and -O require at least " 666147708Sjkoshy "one sampling mode PMC to be specified."); 667147191Sjkoshy 668147708Sjkoshy if ((args.pa_flags & (FLAG_HAS_PID | FLAG_HAS_COMMANDLINE)) == 669147708Sjkoshy (FLAG_HAS_PID | FLAG_HAS_COMMANDLINE)) 670145256Sjkoshy errx(EX_USAGE, 671145256Sjkoshy "ERROR: option -t cannot be specified with a command " 672147191Sjkoshy "line."); 673145256Sjkoshy 674147708Sjkoshy /* check if -g is being used correctly */ 675147708Sjkoshy if ((args.pa_flags & FLAG_DO_GPROF) && 676147708Sjkoshy !(args.pa_flags & (FLAG_HAS_SAMPLING_PMCS|FLAG_READ_LOGFILE))) 677147708Sjkoshy errx(EX_USAGE, "ERROR: option -g requires sampling PMCs or -R " 678147708Sjkoshy "to be specified."); 679147708Sjkoshy 680147191Sjkoshy /* check if -O was spuriously specified */ 681147708Sjkoshy if ((args.pa_flags & FLAG_HAS_OUTPUT_LOGFILE) && 682147708Sjkoshy (args.pa_required & FLAG_HAS_OUTPUT_LOGFILE) == 0) 683147191Sjkoshy errx(EX_USAGE, 684147191Sjkoshy "ERROR: option -O is used only with options " 685147708Sjkoshy "-E, -P, -S and -W."); 686147191Sjkoshy 687157144Sjkoshy /* -D dir and -k kernel path require -g or -R */ 688147708Sjkoshy if ((args.pa_flags & FLAG_HAS_KERNELPATH) && 689157144Sjkoshy (args.pa_flags & FLAG_DO_GPROF) == 0 && 690157144Sjkoshy (args.pa_flags & FLAG_READ_LOGFILE) == 0) 691157144Sjkoshy errx(EX_USAGE, "ERROR: option -k is only used with -g/-R."); 692147708Sjkoshy 693147708Sjkoshy if ((args.pa_flags & FLAG_HAS_SAMPLESDIR) && 694157144Sjkoshy (args.pa_flags & FLAG_DO_GPROF) == 0 && 695157144Sjkoshy (args.pa_flags & FLAG_READ_LOGFILE) == 0) 696157144Sjkoshy errx(EX_USAGE, "ERROR: option -D is only used with -g/-R."); 697147708Sjkoshy 698157144Sjkoshy /* -M mapfile requires -g or -R */ 699157144Sjkoshy if (args.pa_mapfilename != NULL && 700157144Sjkoshy (args.pa_flags & FLAG_DO_GPROF) == 0 && 701157144Sjkoshy (args.pa_flags & FLAG_READ_LOGFILE) == 0) 702157144Sjkoshy errx(EX_USAGE, "ERROR: option -M is only used with -g/-R."); 703157144Sjkoshy 704147708Sjkoshy /* 705147708Sjkoshy * Disallow textual output of sampling PMCs if counting PMCs 706147708Sjkoshy * have also been asked for, mostly because the combined output 707147708Sjkoshy * is difficult to make sense of. 708147708Sjkoshy */ 709147708Sjkoshy if ((args.pa_flags & FLAG_HAS_COUNTING_PMCS) && 710147708Sjkoshy (args.pa_flags & FLAG_HAS_SAMPLING_PMCS) && 711168927Sjkoshy ((args.pa_flags & FLAG_HAS_OUTPUT_LOGFILE) == 0)) 712147708Sjkoshy errx(EX_USAGE, "ERROR: option -O is required if counting and " 713147708Sjkoshy "sampling PMCs are specified together."); 714147708Sjkoshy 715157144Sjkoshy /* 716157144Sjkoshy * Check if "-k kerneldir" was specified, and if whether 'kerneldir' 717157144Sjkoshy * actually refers to a a file. If so, use `dirname path` to determine 718157144Sjkoshy * the kernel directory. 719157144Sjkoshy */ 720157144Sjkoshy if (args.pa_flags & FLAG_HAS_KERNELPATH) { 721157144Sjkoshy (void) snprintf(buffer, sizeof(buffer), "%s%s", args.pa_fsroot, 722157144Sjkoshy args.pa_kernel); 723157144Sjkoshy if (stat(buffer, &sb) < 0) 724157144Sjkoshy err(EX_OSERR, "ERROR: Cannot locate kernel \"%s\"", 725157144Sjkoshy buffer); 726157144Sjkoshy if (!S_ISREG(sb.st_mode) && !S_ISDIR(sb.st_mode)) 727157144Sjkoshy errx(EX_USAGE, "ERROR: \"%s\": Unsupported file type.", 728157144Sjkoshy buffer); 729157144Sjkoshy if (!S_ISDIR(sb.st_mode)) { 730157144Sjkoshy tmp = args.pa_kernel; 731157144Sjkoshy args.pa_kernel = strdup(dirname(args.pa_kernel)); 732157144Sjkoshy free(tmp); 733157144Sjkoshy (void) snprintf(buffer, sizeof(buffer), "%s%s", 734157144Sjkoshy args.pa_fsroot, args.pa_kernel); 735157144Sjkoshy if (stat(buffer, &sb) < 0) 736157144Sjkoshy err(EX_OSERR, "ERROR: Cannot stat \"%s\"", 737157144Sjkoshy buffer); 738157144Sjkoshy if (!S_ISDIR(sb.st_mode)) 739157144Sjkoshy errx(EX_USAGE, "ERROR: \"%s\" is not a " 740157144Sjkoshy "directory.", buffer); 741157144Sjkoshy } 742157144Sjkoshy } 743157144Sjkoshy 744147191Sjkoshy /* if we've been asked to process a log file, do that and exit */ 745147708Sjkoshy if (args.pa_flags & FLAG_READ_LOGFILE) { 746147708Sjkoshy /* 747147708Sjkoshy * Print the log in textual form if we haven't been 748147708Sjkoshy * asked to generate gmon.out files. 749147708Sjkoshy */ 750147708Sjkoshy if ((args.pa_flags & FLAG_DO_GPROF) == 0) 751147708Sjkoshy args.pa_flags |= FLAG_DO_PRINT; 752147708Sjkoshy 753147708Sjkoshy pmcstat_initialize_logging(&args); 754157406Sjkoshy args.pa_logfd = pmcstat_open_log(args.pa_inputpath, 755157406Sjkoshy PMCSTAT_OPEN_FOR_READ); 756147708Sjkoshy if ((args.pa_logparser = pmclog_open(args.pa_logfd)) == NULL) 757147708Sjkoshy err(EX_OSERR, "ERROR: Cannot create parser"); 758147191Sjkoshy pmcstat_process_log(&args); 759157144Sjkoshy pmcstat_shutdown_logging(&args); 760147191Sjkoshy exit(EX_OK); 761147191Sjkoshy } 762147191Sjkoshy 763147191Sjkoshy /* otherwise, we've been asked to collect data */ 764145256Sjkoshy if (pmc_init() < 0) 765145256Sjkoshy err(EX_UNAVAILABLE, 766145256Sjkoshy "ERROR: Initialization of the pmc(3) library failed"); 767145256Sjkoshy 768145256Sjkoshy if ((ncpu = pmc_ncpu()) < 0) 769145256Sjkoshy err(EX_OSERR, "ERROR: Cannot determine the number CPUs " 770145256Sjkoshy "on the system"); 771145256Sjkoshy 772145256Sjkoshy if ((npmc = pmc_npmc(0)) < 0) /* assume all CPUs are identical */ 773145256Sjkoshy err(EX_OSERR, "ERROR: Cannot determine the number of PMCs " 774145256Sjkoshy "on CPU %d", 0); 775145256Sjkoshy 776147708Sjkoshy /* Allocate a kqueue */ 777147708Sjkoshy if ((pmcstat_kq = kqueue()) < 0) 778147708Sjkoshy err(EX_OSERR, "ERROR: Cannot allocate kqueue"); 779147708Sjkoshy 780145256Sjkoshy /* 781147708Sjkoshy * Configure the specified log file or setup a default log 782147708Sjkoshy * consumer via a pipe. 783147708Sjkoshy */ 784147708Sjkoshy if (args.pa_required & FLAG_HAS_OUTPUT_LOGFILE) { 785157406Sjkoshy if (args.pa_outputpath) 786157406Sjkoshy args.pa_logfd = pmcstat_open_log(args.pa_outputpath, 787157406Sjkoshy PMCSTAT_OPEN_FOR_WRITE); 788157406Sjkoshy else { 789147708Sjkoshy /* 790147708Sjkoshy * process the log on the fly by reading it in 791147708Sjkoshy * through a pipe. 792147708Sjkoshy */ 793147708Sjkoshy if (pipe(pipefd) < 0) 794147708Sjkoshy err(EX_OSERR, "ERROR: pipe(2) failed"); 795147708Sjkoshy 796147708Sjkoshy if (fcntl(pipefd[READPIPEFD], F_SETFL, O_NONBLOCK) < 0) 797147708Sjkoshy err(EX_OSERR, "ERROR: fcntl(2) failed"); 798147708Sjkoshy 799147708Sjkoshy EV_SET(&kev, pipefd[READPIPEFD], EVFILT_READ, EV_ADD, 800147708Sjkoshy 0, 0, NULL); 801147708Sjkoshy 802147708Sjkoshy if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) 803147708Sjkoshy err(EX_OSERR, "ERROR: Cannot register kevent"); 804147708Sjkoshy 805147708Sjkoshy args.pa_logfd = pipefd[WRITEPIPEFD]; 806147708Sjkoshy 807147708Sjkoshy args.pa_flags |= (FLAG_HAS_PIPE | FLAG_DO_PRINT); 808147708Sjkoshy args.pa_logparser = pmclog_open(pipefd[READPIPEFD]); 809147708Sjkoshy } 810147708Sjkoshy 811147708Sjkoshy if (pmc_configure_logfile(args.pa_logfd) < 0) 812147708Sjkoshy err(EX_OSERR, "ERROR: Cannot configure log file"); 813147708Sjkoshy } 814147708Sjkoshy 815151542Sjkoshy /* remember to check for driver errors if we are sampling or logging */ 816151542Sjkoshy check_driver_stats = (args.pa_flags & FLAG_HAS_SAMPLING_PMCS) || 817151542Sjkoshy (args.pa_flags & FLAG_HAS_OUTPUT_LOGFILE); 818151542Sjkoshy 819147708Sjkoshy /* 820145256Sjkoshy * Allocate PMCs. 821145256Sjkoshy */ 822145256Sjkoshy 823147708Sjkoshy STAILQ_FOREACH(ev, &args.pa_head, ev_next) { 824145256Sjkoshy if (pmc_allocate(ev->ev_spec, ev->ev_mode, 825147191Sjkoshy ev->ev_flags, ev->ev_cpu, &ev->ev_pmcid) < 0) 826145256Sjkoshy err(EX_OSERR, "ERROR: Cannot allocate %s-mode pmc with " 827145256Sjkoshy "specification \"%s\"", 828145256Sjkoshy PMC_IS_SYSTEM_MODE(ev->ev_mode) ? "system" : "process", 829145256Sjkoshy ev->ev_spec); 830145256Sjkoshy 831147708Sjkoshy if (PMC_IS_SAMPLING_MODE(ev->ev_mode) && 832147708Sjkoshy pmc_set(ev->ev_pmcid, ev->ev_count) < 0) 833147708Sjkoshy err(EX_OSERR, "ERROR: Cannot set sampling count " 834147708Sjkoshy "for PMC \"%s\"", ev->ev_name); 835147708Sjkoshy } 836147708Sjkoshy 837145256Sjkoshy /* compute printout widths */ 838145256Sjkoshy STAILQ_FOREACH(ev, &args.pa_head, ev_next) { 839145774Sjkoshy int counter_width; 840145774Sjkoshy int display_width; 841145774Sjkoshy int header_width; 842145256Sjkoshy 843145774Sjkoshy (void) pmc_width(ev->ev_pmcid, &counter_width); 844145774Sjkoshy header_width = strlen(ev->ev_name) + 2; /* prefix '%c|' */ 845145774Sjkoshy display_width = (int) floor(counter_width / 3.32193) + 1; 846145256Sjkoshy 847145774Sjkoshy if (header_width > display_width) { 848145256Sjkoshy ev->ev_fieldskip = 0; 849145774Sjkoshy ev->ev_fieldwidth = header_width; 850145256Sjkoshy } else { 851145774Sjkoshy ev->ev_fieldskip = display_width - 852145774Sjkoshy header_width; 853145774Sjkoshy ev->ev_fieldwidth = display_width; 854145256Sjkoshy } 855145256Sjkoshy } 856145256Sjkoshy 857145256Sjkoshy /* 858145256Sjkoshy * If our output is being set to a terminal, register a handler 859145256Sjkoshy * for window size changes. 860145256Sjkoshy */ 861145256Sjkoshy 862147708Sjkoshy if (isatty(fileno(args.pa_printfile))) { 863145256Sjkoshy 864147708Sjkoshy if (ioctl(fileno(args.pa_printfile), TIOCGWINSZ, &ws) < 0) 865145256Sjkoshy err(EX_OSERR, "ERROR: Cannot determine window size"); 866145256Sjkoshy 867145256Sjkoshy pmcstat_displayheight = ws.ws_row - 1; 868145256Sjkoshy 869145256Sjkoshy EV_SET(&kev, SIGWINCH, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); 870145256Sjkoshy 871145256Sjkoshy if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) 872145256Sjkoshy err(EX_OSERR, "ERROR: Cannot register kevent for " 873145256Sjkoshy "SIGWINCH"); 874145256Sjkoshy } 875145256Sjkoshy 876145256Sjkoshy EV_SET(&kev, SIGINT, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); 877145256Sjkoshy if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) 878145256Sjkoshy err(EX_OSERR, "ERROR: Cannot register kevent for SIGINT"); 879145256Sjkoshy 880147191Sjkoshy EV_SET(&kev, SIGIO, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); 881147191Sjkoshy if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) 882147191Sjkoshy err(EX_OSERR, "ERROR: Cannot register kevent for SIGIO"); 883145256Sjkoshy 884147191Sjkoshy /* 885147191Sjkoshy * An exec() failure of a forked child is signalled by the 886147191Sjkoshy * child sending the parent a SIGCHLD. We don't register an 887147191Sjkoshy * actual signal handler for SIGCHLD, but instead use our 888147191Sjkoshy * kqueue to pick up the signal. 889147191Sjkoshy */ 890147191Sjkoshy EV_SET(&kev, SIGCHLD, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); 891147191Sjkoshy if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) 892147191Sjkoshy err(EX_OSERR, "ERROR: Cannot register kevent for SIGCHLD"); 893145256Sjkoshy 894147708Sjkoshy /* setup a timer if we have counting mode PMCs needing to be printed */ 895147708Sjkoshy if ((args.pa_flags & FLAG_HAS_COUNTING_PMCS) && 896147708Sjkoshy (args.pa_required & FLAG_HAS_OUTPUT_LOGFILE) == 0) { 897145256Sjkoshy EV_SET(&kev, 0, EVFILT_TIMER, EV_ADD, 0, 898145256Sjkoshy args.pa_interval * 1000, NULL); 899145256Sjkoshy 900145256Sjkoshy if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) 901145256Sjkoshy err(EX_OSERR, "ERROR: Cannot register kevent for " 902145256Sjkoshy "timer"); 903145256Sjkoshy } 904145256Sjkoshy 905145256Sjkoshy /* attach PMCs to the target process, starting it if specified */ 906147708Sjkoshy if (args.pa_flags & (FLAG_HAS_PID | FLAG_HAS_COMMANDLINE)) 907145256Sjkoshy pmcstat_setup_process(&args); 908145256Sjkoshy 909151542Sjkoshy if (check_driver_stats && pmc_get_driver_stats(&ds_start) < 0) 910151542Sjkoshy err(EX_OSERR, "ERROR: Cannot retrieve driver statistics"); 911151542Sjkoshy 912145256Sjkoshy /* start the pmcs */ 913145256Sjkoshy pmcstat_start_pmcs(&args); 914145256Sjkoshy 915145256Sjkoshy /* start the (commandline) process if needed */ 916147708Sjkoshy if (args.pa_flags & FLAG_HAS_COMMANDLINE) 917145256Sjkoshy pmcstat_start_process(&args); 918145256Sjkoshy 919147708Sjkoshy /* initialize logging if printing the configured log */ 920147708Sjkoshy if ((args.pa_flags & FLAG_DO_PRINT) && 921147708Sjkoshy (args.pa_flags & (FLAG_HAS_PIPE | FLAG_HAS_OUTPUT_LOGFILE))) 922147708Sjkoshy pmcstat_initialize_logging(&args); 923147708Sjkoshy 924145256Sjkoshy /* Handle SIGINT using the kqueue loop */ 925145256Sjkoshy sa.sa_handler = SIG_IGN; 926145256Sjkoshy sa.sa_flags = 0; 927145256Sjkoshy (void) sigemptyset(&sa.sa_mask); 928145256Sjkoshy 929145256Sjkoshy if (sigaction(SIGINT, &sa, NULL) < 0) 930145256Sjkoshy err(EX_OSERR, "ERROR: Cannot install signal handler"); 931145256Sjkoshy 932145256Sjkoshy /* 933145256Sjkoshy * loop till either the target process (if any) exits, or we 934145256Sjkoshy * are killed by a SIGINT. 935145256Sjkoshy */ 936147191Sjkoshy runstate = PMCSTAT_RUNNING; 937147191Sjkoshy do_print = 0; 938145256Sjkoshy do { 939145256Sjkoshy if ((c = kevent(pmcstat_kq, NULL, 0, &kev, 1, NULL)) <= 0) { 940145256Sjkoshy if (errno != EINTR) 941145256Sjkoshy err(EX_OSERR, "ERROR: kevent failed"); 942145256Sjkoshy else 943145256Sjkoshy continue; 944145256Sjkoshy } 945145256Sjkoshy 946145256Sjkoshy if (kev.flags & EV_ERROR) 947145256Sjkoshy errc(EX_OSERR, kev.data, "ERROR: kevent failed"); 948145256Sjkoshy 949145256Sjkoshy switch (kev.filter) { 950147191Sjkoshy case EVFILT_PROC: /* target has exited */ 951147708Sjkoshy if (args.pa_flags & (FLAG_HAS_OUTPUT_LOGFILE | 952147708Sjkoshy FLAG_HAS_PIPE)) 953147191Sjkoshy runstate = pmcstat_close_log(&args); 954148688Sjkoshy else 955148688Sjkoshy runstate = PMCSTAT_FINISHED; 956148688Sjkoshy do_print = 1; 957147191Sjkoshy break; 958145256Sjkoshy 959147191Sjkoshy case EVFILT_READ: /* log file data is present */ 960147863Sjkoshy runstate = pmcstat_process_log(&args); 961145256Sjkoshy break; 962145256Sjkoshy 963145256Sjkoshy case EVFILT_SIGNAL: 964147191Sjkoshy if (kev.ident == SIGCHLD) { 965147191Sjkoshy /* 966147191Sjkoshy * The child process sends us a 967147191Sjkoshy * SIGCHLD if its exec() failed. We 968147191Sjkoshy * wait for it to exit and then exit 969147191Sjkoshy * ourselves. 970147191Sjkoshy */ 971147191Sjkoshy (void) wait(&c); 972147191Sjkoshy runstate = PMCSTAT_FINISHED; 973147191Sjkoshy } else if (kev.ident == SIGIO) { 974147191Sjkoshy /* 975147191Sjkoshy * We get a SIGIO if a PMC loses all 976147191Sjkoshy * of its targets, or if logfile 977147191Sjkoshy * writes encounter an error. 978147191Sjkoshy */ 979147708Sjkoshy if (args.pa_flags & (FLAG_HAS_OUTPUT_LOGFILE | 980147708Sjkoshy FLAG_HAS_PIPE)) { 981147191Sjkoshy runstate = pmcstat_close_log(&args); 982147708Sjkoshy if (args.pa_flags & 983147708Sjkoshy (FLAG_DO_PRINT|FLAG_DO_GPROF)) 984147708Sjkoshy pmcstat_process_log(&args); 985147708Sjkoshy } 986147191Sjkoshy do_print = 1; /* print PMCs at exit */ 987147191Sjkoshy runstate = PMCSTAT_FINISHED; 988147191Sjkoshy } else if (kev.ident == SIGINT) { 989147708Sjkoshy /* Kill the child process if we started it */ 990147708Sjkoshy if (args.pa_flags & FLAG_HAS_COMMANDLINE) 991145256Sjkoshy if (kill(args.pa_pid, SIGINT) != 0) 992147191Sjkoshy err(EX_OSERR, "ERROR: cannot " 993147191Sjkoshy "signal child process"); 994147191Sjkoshy runstate = PMCSTAT_FINISHED; 995145256Sjkoshy } else if (kev.ident == SIGWINCH) { 996147708Sjkoshy if (ioctl(fileno(args.pa_printfile), 997145256Sjkoshy TIOCGWINSZ, &ws) < 0) 998145256Sjkoshy err(EX_OSERR, "ERROR: Cannot determine " 999145256Sjkoshy "window size"); 1000145256Sjkoshy pmcstat_displayheight = ws.ws_row - 1; 1001145256Sjkoshy } else 1002145256Sjkoshy assert(0); 1003145256Sjkoshy 1004145256Sjkoshy break; 1005147191Sjkoshy 1006147191Sjkoshy case EVFILT_TIMER: /* print out counting PMCs */ 1007147191Sjkoshy do_print = 1; 1008147191Sjkoshy break; 1009147191Sjkoshy 1010145256Sjkoshy } 1011145256Sjkoshy 1012147708Sjkoshy if (do_print && 1013147708Sjkoshy (args.pa_required & FLAG_HAS_OUTPUT_LOGFILE) == 0) { 1014147191Sjkoshy pmcstat_print_pmcs(&args); 1015147708Sjkoshy if (runstate == PMCSTAT_FINISHED && /* final newline */ 1016147708Sjkoshy (args.pa_flags & FLAG_DO_PRINT) == 0) 1017147708Sjkoshy (void) fprintf(args.pa_printfile, "\n"); 1018147191Sjkoshy do_print = 0; 1019147191Sjkoshy } 1020145256Sjkoshy 1021147191Sjkoshy } while (runstate != PMCSTAT_FINISHED); 1022147191Sjkoshy 1023147191Sjkoshy /* flush any pending log entries */ 1024147708Sjkoshy if (args.pa_flags & (FLAG_HAS_OUTPUT_LOGFILE | FLAG_HAS_PIPE)) 1025147191Sjkoshy pmc_flush_logfile(); 1026147191Sjkoshy 1027145256Sjkoshy pmcstat_cleanup(&args); 1028145256Sjkoshy 1029157144Sjkoshy free(args.pa_kernel); 1030157144Sjkoshy 1031151542Sjkoshy /* check if the driver lost any samples or events */ 1032151542Sjkoshy if (check_driver_stats) { 1033151542Sjkoshy if (pmc_get_driver_stats(&ds_end) < 0) 1034151542Sjkoshy err(EX_OSERR, "ERROR: Cannot retrieve driver " 1035151542Sjkoshy "statistics"); 1036157144Sjkoshy if (ds_start.pm_intr_bufferfull != ds_end.pm_intr_bufferfull && 1037157144Sjkoshy args.pa_verbosity > 0) 1038153704Sjkoshy warnx("WARNING: some samples were dropped. Please " 1039151542Sjkoshy "consider tuning the \"kern.hwpmc.nsamples\" " 1040151542Sjkoshy "tunable."); 1041151542Sjkoshy if (ds_start.pm_buffer_requests_failed != 1042157144Sjkoshy ds_end.pm_buffer_requests_failed && 1043157144Sjkoshy args.pa_verbosity > 0) 1044153704Sjkoshy warnx("WARNING: some events were discarded. Please " 1045151542Sjkoshy "consider tuning the \"kern.hwpmc.nbuffers\" " 1046151542Sjkoshy "tunable."); 1047151542Sjkoshy } 1048151542Sjkoshy 1049151542Sjkoshy exit(EX_OK); 1050145256Sjkoshy} 1051