pmcstat.c revision 145256
1145256Sjkoshy/*- 2145256Sjkoshy * Copyright (c) 2003,2004 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 28145256Sjkoshy#include <sys/cdefs.h> 29145256Sjkoshy__FBSDID("$FreeBSD: head/usr.sbin/pmcstat/pmcstat.c 145256 2005-04-19 04:01:25Z jkoshy $"); 30145256Sjkoshy 31145256Sjkoshy#include <sys/types.h> 32145256Sjkoshy#include <sys/event.h> 33145256Sjkoshy#include <sys/queue.h> 34145256Sjkoshy#include <sys/time.h> 35145256Sjkoshy#include <sys/ttycom.h> 36145256Sjkoshy#include <sys/wait.h> 37145256Sjkoshy 38145256Sjkoshy#include <assert.h> 39145256Sjkoshy#include <err.h> 40145256Sjkoshy#include <errno.h> 41145256Sjkoshy#include <fcntl.h> 42145256Sjkoshy#include <limits.h> 43145256Sjkoshy#include <math.h> 44145256Sjkoshy#include <pmc.h> 45145256Sjkoshy#include <signal.h> 46145256Sjkoshy#include <stdarg.h> 47145256Sjkoshy#include <stdio.h> 48145256Sjkoshy#include <stdint.h> 49145256Sjkoshy#include <stdlib.h> 50145256Sjkoshy#include <string.h> 51145256Sjkoshy#include <sysexits.h> 52145256Sjkoshy#include <unistd.h> 53145256Sjkoshy 54145256Sjkoshy/* Operation modes */ 55145256Sjkoshy 56145256Sjkoshy#define FLAG_HAS_PID 0x00000001 57145256Sjkoshy#define FLAG_HAS_WAIT_INTERVAL 0x00000002 58145256Sjkoshy#define FLAG_HAS_LOG_FILE 0x00000004 59145256Sjkoshy#define FLAG_HAS_PROCESS 0x00000008 60145256Sjkoshy#define FLAG_USING_SAMPLING 0x00000010 61145256Sjkoshy#define FLAG_USING_COUNTING 0x00000020 62145256Sjkoshy#define FLAG_USING_PROCESS_PMC 0x00000040 63145256Sjkoshy 64145256Sjkoshy#define DEFAULT_SAMPLE_COUNT 65536 65145256Sjkoshy#define DEFAULT_WAIT_INTERVAL 5.0 66145256Sjkoshy#define DEFAULT_DISPLAY_HEIGHT 23 67145256Sjkoshy#define DEFAULT_LOGFILE_NAME "pmcstat.out" 68145256Sjkoshy 69145256Sjkoshy#define PRINT_HEADER_PREFIX "# " 70145256Sjkoshy#define READPIPEFD 0 71145256Sjkoshy#define WRITEPIPEFD 1 72145256Sjkoshy#define NPIPEFD 2 73145256Sjkoshy 74145256Sjkoshystruct pmcstat_ev { 75145256Sjkoshy STAILQ_ENTRY(pmcstat_ev) ev_next; 76145256Sjkoshy char *ev_spec; /* event specification */ 77145256Sjkoshy char *ev_name; /* (derived) event name */ 78145256Sjkoshy enum pmc_mode ev_mode; /* desired mode */ 79145256Sjkoshy int ev_count; /* associated count if in sampling mode */ 80145256Sjkoshy int ev_cpu; /* specific cpu if requested */ 81145256Sjkoshy int ev_descendants; /* attach to descendants */ 82145256Sjkoshy int ev_cumulative; /* show cumulative counts */ 83145256Sjkoshy int ev_fieldwidth; /* print width */ 84145256Sjkoshy int ev_fieldskip; /* #leading spaces */ 85145256Sjkoshy pmc_value_t ev_saved; /* saved value for incremental counts */ 86145256Sjkoshy pmc_id_t ev_pmcid; /* allocated ID */ 87145256Sjkoshy}; 88145256Sjkoshy 89145256Sjkoshystruct pmcstat_args { 90145256Sjkoshy int pa_flags; 91145256Sjkoshy pid_t pa_pid; 92145256Sjkoshy FILE *pa_outputfile; 93145256Sjkoshy FILE *pa_logfile; 94145256Sjkoshy double pa_interval; 95145256Sjkoshy int pa_argc; 96145256Sjkoshy char **pa_argv; 97145256Sjkoshy STAILQ_HEAD(, pmcstat_ev) pa_head; 98145256Sjkoshy} args; 99145256Sjkoshy 100145256Sjkoshyint pmcstat_interrupt = 0; 101145256Sjkoshyint pmcstat_displayheight = DEFAULT_DISPLAY_HEIGHT; 102145256Sjkoshyint pmcstat_pipefd[NPIPEFD]; 103145256Sjkoshyint pmcstat_kq; 104145256Sjkoshy 105145256Sjkoshy/* Function prototypes */ 106145256Sjkoshyvoid pmcstat_cleanup(struct pmcstat_args *_a); 107145256Sjkoshyvoid pmcstat_print_counters(struct pmcstat_args *_a); 108145256Sjkoshyvoid pmcstat_print_headers(struct pmcstat_args *_a); 109145256Sjkoshyvoid pmcstat_print_pmcs(struct pmcstat_args *_a); 110145256Sjkoshyvoid pmcstat_setup_process(struct pmcstat_args *_a); 111145256Sjkoshyvoid pmcstat_show_usage(void); 112145256Sjkoshyvoid pmcstat_start_pmcs(struct pmcstat_args *_a); 113145256Sjkoshyvoid pmcstat_start_process(struct pmcstat_args *_a); 114145256Sjkoshy 115145256Sjkoshy 116145256Sjkoshy/* 117145256Sjkoshy * cleanup 118145256Sjkoshy */ 119145256Sjkoshy 120145256Sjkoshyvoid 121145256Sjkoshypmcstat_cleanup(struct pmcstat_args *a) 122145256Sjkoshy{ 123145256Sjkoshy struct pmcstat_ev *ev, *tmp; 124145256Sjkoshy 125145256Sjkoshy /* de-configure the log file if present. */ 126145256Sjkoshy if (a->pa_flags & FLAG_USING_SAMPLING) { 127145256Sjkoshy (void) pmc_configure_logfile(-1); 128145256Sjkoshy (void) fclose(a->pa_logfile); 129145256Sjkoshy } 130145256Sjkoshy 131145256Sjkoshy /* release allocated PMCs. */ 132145256Sjkoshy STAILQ_FOREACH_SAFE(ev, &a->pa_head, ev_next, tmp) 133145256Sjkoshy if (ev->ev_pmcid != PMC_ID_INVALID) { 134145256Sjkoshy if (pmc_release(ev->ev_pmcid) < 0) 135145256Sjkoshy err(EX_OSERR, "ERROR: cannot release pmc " 136145256Sjkoshy "%d \"%s\"", ev->ev_pmcid, ev->ev_name); 137145256Sjkoshy free(ev->ev_name); 138145256Sjkoshy free(ev->ev_spec); 139145256Sjkoshy STAILQ_REMOVE(&a->pa_head, ev, pmcstat_ev, ev_next); 140145256Sjkoshy free(ev); 141145256Sjkoshy } 142145256Sjkoshy} 143145256Sjkoshy 144145256Sjkoshyvoid 145145256Sjkoshypmcstat_start_pmcs(struct pmcstat_args *a) 146145256Sjkoshy{ 147145256Sjkoshy struct pmcstat_ev *ev; 148145256Sjkoshy 149145256Sjkoshy STAILQ_FOREACH(ev, &args.pa_head, ev_next) { 150145256Sjkoshy 151145256Sjkoshy assert(ev->ev_pmcid != PMC_ID_INVALID); 152145256Sjkoshy 153145256Sjkoshy if (pmc_start(ev->ev_pmcid) < 0) { 154145256Sjkoshy warn("ERROR: Cannot start pmc %d \"%s\"", 155145256Sjkoshy ev->ev_pmcid, ev->ev_name); 156145256Sjkoshy pmcstat_cleanup(a); 157145256Sjkoshy } 158145256Sjkoshy } 159145256Sjkoshy 160145256Sjkoshy} 161145256Sjkoshy 162145256Sjkoshyvoid 163145256Sjkoshypmcstat_print_headers(struct pmcstat_args *a) 164145256Sjkoshy{ 165145256Sjkoshy struct pmcstat_ev *ev; 166145256Sjkoshy int c; 167145256Sjkoshy 168145256Sjkoshy (void) fprintf(a->pa_outputfile, PRINT_HEADER_PREFIX); 169145256Sjkoshy 170145256Sjkoshy STAILQ_FOREACH(ev, &a->pa_head, ev_next) { 171145256Sjkoshy if (PMC_IS_SAMPLING_MODE(ev->ev_mode)) 172145256Sjkoshy continue; 173145256Sjkoshy 174145256Sjkoshy c = PMC_IS_SYSTEM_MODE(ev->ev_mode) ? 's' : 'p'; 175145256Sjkoshy 176145256Sjkoshy if (ev->ev_fieldskip != 0) { 177145256Sjkoshy (void) fprintf(a->pa_outputfile, "%*s%c/%*s ", 178145256Sjkoshy ev->ev_fieldskip, "", c, 179145256Sjkoshy ev->ev_fieldwidth - ev->ev_fieldskip - 2, 180145256Sjkoshy ev->ev_name); 181145256Sjkoshy } else 182145256Sjkoshy (void) fprintf(a->pa_outputfile, "%c/%*s ", 183145256Sjkoshy c, ev->ev_fieldwidth - 2, ev->ev_name); 184145256Sjkoshy } 185145256Sjkoshy 186145256Sjkoshy (void) fflush(a->pa_outputfile); 187145256Sjkoshy} 188145256Sjkoshy 189145256Sjkoshyvoid 190145256Sjkoshypmcstat_print_counters(struct pmcstat_args *a) 191145256Sjkoshy{ 192145256Sjkoshy int extra_width; 193145256Sjkoshy struct pmcstat_ev *ev; 194145256Sjkoshy pmc_value_t value; 195145256Sjkoshy 196145256Sjkoshy extra_width = sizeof(PRINT_HEADER_PREFIX) - 1; 197145256Sjkoshy 198145256Sjkoshy STAILQ_FOREACH(ev, &a->pa_head, ev_next) { 199145256Sjkoshy 200145256Sjkoshy /* skip sampling mode counters */ 201145256Sjkoshy if (PMC_IS_SAMPLING_MODE(ev->ev_mode)) 202145256Sjkoshy continue; 203145256Sjkoshy 204145256Sjkoshy if (pmc_read(ev->ev_pmcid, &value) < 0) 205145256Sjkoshy err(EX_OSERR, "ERROR: Cannot read pmc " 206145256Sjkoshy "\"%s\"", ev->ev_name); 207145256Sjkoshy 208145256Sjkoshy (void) fprintf(a->pa_outputfile, "%*ju ", 209145256Sjkoshy ev->ev_fieldwidth + extra_width, (uintmax_t) 210145256Sjkoshy ev->ev_cumulative ? value : (value - ev->ev_saved)); 211145256Sjkoshy if (ev->ev_cumulative == 0) 212145256Sjkoshy ev->ev_saved = value; 213145256Sjkoshy extra_width = 0; 214145256Sjkoshy } 215145256Sjkoshy 216145256Sjkoshy (void) fflush(a->pa_outputfile); 217145256Sjkoshy} 218145256Sjkoshy 219145256Sjkoshy/* 220145256Sjkoshy * Print output 221145256Sjkoshy */ 222145256Sjkoshy 223145256Sjkoshyvoid 224145256Sjkoshypmcstat_print_pmcs(struct pmcstat_args *a) 225145256Sjkoshy{ 226145256Sjkoshy static int linecount = 0; 227145256Sjkoshy 228145256Sjkoshy if (++linecount > pmcstat_displayheight) { 229145256Sjkoshy (void) fprintf(a->pa_outputfile, "\n"); 230145256Sjkoshy linecount = 1; 231145256Sjkoshy } 232145256Sjkoshy 233145256Sjkoshy if (linecount == 1) 234145256Sjkoshy pmcstat_print_headers(a); 235145256Sjkoshy 236145256Sjkoshy (void) fprintf(a->pa_outputfile, "\n"); 237145256Sjkoshy pmcstat_print_counters(a); 238145256Sjkoshy 239145256Sjkoshy return; 240145256Sjkoshy} 241145256Sjkoshy 242145256Sjkoshy/* 243145256Sjkoshy * Do process profiling 244145256Sjkoshy * 245145256Sjkoshy * If a pid was specified, attach each allocated PMC to the target 246145256Sjkoshy * process. Otherwise, fork a child and attach the PMCs to the child, 247145256Sjkoshy * and have the child exec() the target program. 248145256Sjkoshy */ 249145256Sjkoshy 250145256Sjkoshyvoid 251145256Sjkoshypmcstat_setup_process(struct pmcstat_args *a) 252145256Sjkoshy{ 253145256Sjkoshy char token; 254145256Sjkoshy struct pmcstat_ev *ev; 255145256Sjkoshy struct kevent kev; 256145256Sjkoshy 257145256Sjkoshy if (a->pa_flags & FLAG_HAS_PID) { 258145256Sjkoshy 259145256Sjkoshy STAILQ_FOREACH(ev, &args.pa_head, ev_next) 260145256Sjkoshy if (pmc_attach(ev->ev_pmcid, a->pa_pid) != 0) 261145256Sjkoshy err(EX_OSERR, "ERROR: cannot attach pmc \"%s\" to " 262145256Sjkoshy "process %d", ev->ev_name, (int) a->pa_pid); 263145256Sjkoshy 264145256Sjkoshy } else { 265145256Sjkoshy 266145256Sjkoshy /* 267145256Sjkoshy * We need to fork a new process and startup the child 268145256Sjkoshy * using execvp(). Before doing the exec() the child 269145256Sjkoshy * process reads its pipe for a token so that the parent 270145256Sjkoshy * can finish doing its pmc_attach() calls. 271145256Sjkoshy */ 272145256Sjkoshy 273145256Sjkoshy if (pipe(pmcstat_pipefd) < 0) 274145256Sjkoshy err(EX_OSERR, "ERROR: cannot create pipe"); 275145256Sjkoshy 276145256Sjkoshy switch (a->pa_pid = fork()) { 277145256Sjkoshy case -1: 278145256Sjkoshy err(EX_OSERR, "ERROR: cannot fork"); 279145256Sjkoshy /*NOTREACHED*/ 280145256Sjkoshy 281145256Sjkoshy case 0: /* child */ 282145256Sjkoshy 283145256Sjkoshy /* wait for our parent to signal us */ 284145256Sjkoshy (void) close(pmcstat_pipefd[WRITEPIPEFD]); 285145256Sjkoshy if (read(pmcstat_pipefd[READPIPEFD], &token, 1) < 0) 286145256Sjkoshy err(EX_OSERR, "ERROR (child): cannot read " 287145256Sjkoshy "token"); 288145256Sjkoshy (void) close(pmcstat_pipefd[READPIPEFD]); 289145256Sjkoshy 290145256Sjkoshy /* exec() the program requested */ 291145256Sjkoshy execvp(*args.pa_argv, args.pa_argv); 292145256Sjkoshy err(EX_OSERR, "ERROR (child): execvp failed"); 293145256Sjkoshy /*NOTREACHED*/ 294145256Sjkoshy 295145256Sjkoshy default: /* parent */ 296145256Sjkoshy 297145256Sjkoshy (void) close(pmcstat_pipefd[READPIPEFD]); 298145256Sjkoshy 299145256Sjkoshy /* attach all our PMCs to the child */ 300145256Sjkoshy STAILQ_FOREACH(ev, &args.pa_head, ev_next) 301145256Sjkoshy if (PMC_IS_VIRTUAL_MODE(ev->ev_mode) && 302145256Sjkoshy pmc_attach(ev->ev_pmcid, a->pa_pid) != 0) 303145256Sjkoshy err(EX_OSERR, "ERROR: cannot attach pmc " 304145256Sjkoshy "\"%s\" to process %d", ev->ev_name, 305145256Sjkoshy (int) a->pa_pid); 306145256Sjkoshy 307145256Sjkoshy } 308145256Sjkoshy } 309145256Sjkoshy 310145256Sjkoshy /* Ask to be notified via a kevent when the child exits */ 311145256Sjkoshy EV_SET(&kev, a->pa_pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, 0); 312145256Sjkoshy 313145256Sjkoshy if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) 314145256Sjkoshy err(EX_OSERR, "ERROR: cannot monitor process %d", 315145256Sjkoshy a->pa_pid); 316145256Sjkoshy 317145256Sjkoshy return; 318145256Sjkoshy} 319145256Sjkoshy 320145256Sjkoshyvoid 321145256Sjkoshypmcstat_start_process(struct pmcstat_args *a) 322145256Sjkoshy{ 323145256Sjkoshy 324145256Sjkoshy /* nothing to do: target is already running */ 325145256Sjkoshy if (a->pa_flags & FLAG_HAS_PID) 326145256Sjkoshy return; 327145256Sjkoshy 328145256Sjkoshy /* write token to child to state that we are ready */ 329145256Sjkoshy if (write(pmcstat_pipefd[WRITEPIPEFD], "+", 1) != 1) 330145256Sjkoshy err(EX_OSERR, "ERROR: write failed"); 331145256Sjkoshy 332145256Sjkoshy (void) close(pmcstat_pipefd[WRITEPIPEFD]); 333145256Sjkoshy} 334145256Sjkoshy 335145256Sjkoshyvoid 336145256Sjkoshypmcstat_show_usage(void) 337145256Sjkoshy{ 338145256Sjkoshy errx(EX_USAGE, 339145256Sjkoshy "[options] [commandline]\n" 340145256Sjkoshy "\t Measure process and/or system performance using hardware\n" 341145256Sjkoshy "\t performance monitoring counters.\n" 342145256Sjkoshy "\t Options include:\n" 343145256Sjkoshy "\t -C\t\t toggle showing cumulative counts\n" 344145256Sjkoshy "\t -O file\t set sampling log file to \"file\"\n" 345145256Sjkoshy "\t -P spec\t allocate process-private sampling PMC\n" 346145256Sjkoshy "\t -S spec\t allocate system-wide sampling PMC\n" 347145256Sjkoshy "\t -c cpu\t\t set default cpu\n" 348145256Sjkoshy "\t -d\t\t toggle tracking descendants\n" 349145256Sjkoshy "\t -n rate\t set sampling rate\n" 350145256Sjkoshy "\t -o file\t send print output to \"file\"\n" 351145256Sjkoshy "\t -p spec\t allocate process-private counting PMC\n" 352145256Sjkoshy "\t -s spec\t allocate system-wide counting PMC\n" 353145256Sjkoshy "\t -t pid\t attach to running process with pid \"pid\"\n" 354145256Sjkoshy "\t -w secs\t set printing time interval" 355145256Sjkoshy ); 356145256Sjkoshy} 357145256Sjkoshy 358145256Sjkoshy/* 359145256Sjkoshy * Main 360145256Sjkoshy */ 361145256Sjkoshy 362145256Sjkoshyint 363145256Sjkoshymain(int argc, char **argv) 364145256Sjkoshy{ 365145256Sjkoshy double interval; 366145256Sjkoshy int option, npmc, ncpu; 367145256Sjkoshy int c, current_cpu, current_sampling_count; 368145256Sjkoshy int running; 369145256Sjkoshy int do_descendants, use_cumulative_counts; 370145256Sjkoshy pid_t pid; 371145256Sjkoshy char *end; 372145256Sjkoshy struct pmcstat_ev *ev; 373145256Sjkoshy struct pmc_op_getpmcinfo *ppmci; 374145256Sjkoshy struct sigaction sa; 375145256Sjkoshy struct kevent kev; 376145256Sjkoshy struct winsize ws; 377145256Sjkoshy 378145256Sjkoshy current_cpu = 0; 379145256Sjkoshy current_sampling_count = DEFAULT_SAMPLE_COUNT; 380145256Sjkoshy do_descendants = 0; 381145256Sjkoshy use_cumulative_counts = 0; 382145256Sjkoshy args.pa_flags = 0; 383145256Sjkoshy args.pa_pid = (pid_t) -1; 384145256Sjkoshy args.pa_logfile = NULL; 385145256Sjkoshy args.pa_outputfile = stderr; 386145256Sjkoshy args.pa_interval = DEFAULT_WAIT_INTERVAL; 387145256Sjkoshy STAILQ_INIT(&args.pa_head); 388145256Sjkoshy 389145256Sjkoshy ev = NULL; 390145256Sjkoshy 391145256Sjkoshy while ((option = getopt(argc, argv, "CO:P:S:c:dn:o:p:s:t:w:")) != -1) 392145256Sjkoshy switch (option) { 393145256Sjkoshy case 'C': /* cumulative values */ 394145256Sjkoshy use_cumulative_counts = !use_cumulative_counts; 395145256Sjkoshy break; 396145256Sjkoshy 397145256Sjkoshy case 'c': /* CPU */ 398145256Sjkoshy current_cpu = strtol(optarg, &end, 0); 399145256Sjkoshy if (*end != '\0' || current_cpu < 0) 400145256Sjkoshy errx(EX_USAGE, 401145256Sjkoshy "ERROR: Illegal CPU number \"%s\"", 402145256Sjkoshy optarg); 403145256Sjkoshy 404145256Sjkoshy break; 405145256Sjkoshy 406145256Sjkoshy case 'd': /* toggle descendents */ 407145256Sjkoshy do_descendants = !do_descendants; 408145256Sjkoshy break; 409145256Sjkoshy 410145256Sjkoshy case 'p': /* process virtual counting PMC */ 411145256Sjkoshy case 's': /* system-wide counting PMC */ 412145256Sjkoshy case 'P': /* process virtual sampling PMC */ 413145256Sjkoshy case 'S': /* system-wide sampling PMC */ 414145256Sjkoshy if ((ev = malloc(sizeof(*ev))) == NULL) 415145256Sjkoshy errx(EX_SOFTWARE, "ERROR: Out of memory"); 416145256Sjkoshy 417145256Sjkoshy switch (option) { 418145256Sjkoshy case 'p': ev->ev_mode = PMC_MODE_TC; break; 419145256Sjkoshy case 's': ev->ev_mode = PMC_MODE_SC; break; 420145256Sjkoshy case 'P': ev->ev_mode = PMC_MODE_TS; break; 421145256Sjkoshy case 'S': ev->ev_mode = PMC_MODE_SS; break; 422145256Sjkoshy } 423145256Sjkoshy 424145256Sjkoshy if (option == 'P' || option == 'p') 425145256Sjkoshy args.pa_flags |= FLAG_USING_PROCESS_PMC; 426145256Sjkoshy 427145256Sjkoshy if (option == 'P' || option == 'S') 428145256Sjkoshy args.pa_flags |= FLAG_USING_SAMPLING; 429145256Sjkoshy 430145256Sjkoshy if (option == 'p' || option == 's') 431145256Sjkoshy args.pa_flags |= FLAG_USING_COUNTING; 432145256Sjkoshy 433145256Sjkoshy ev->ev_spec = strdup(optarg); 434145256Sjkoshy 435145256Sjkoshy if (option == 'S' || option == 'P') 436145256Sjkoshy ev->ev_count = current_sampling_count; 437145256Sjkoshy else 438145256Sjkoshy ev->ev_count = -1; 439145256Sjkoshy 440145256Sjkoshy if (option == 'S' || option == 's') 441145256Sjkoshy ev->ev_cpu = current_cpu; 442145256Sjkoshy else 443145256Sjkoshy ev->ev_cpu = PMC_CPU_ANY; 444145256Sjkoshy 445145256Sjkoshy ev->ev_descendants = do_descendants; 446145256Sjkoshy ev->ev_cumulative = use_cumulative_counts; 447145256Sjkoshy 448145256Sjkoshy ev->ev_saved = 0LL; 449145256Sjkoshy ev->ev_pmcid = PMC_ID_INVALID; 450145256Sjkoshy 451145256Sjkoshy /* extract event name */ 452145256Sjkoshy c = strcspn(optarg, ", \t"); 453145256Sjkoshy ev->ev_name = malloc(c + 1); 454145256Sjkoshy (void) strncpy(ev->ev_name, optarg, c); 455145256Sjkoshy *(ev->ev_name + c) = '\0'; 456145256Sjkoshy 457145256Sjkoshy STAILQ_INSERT_TAIL(&args.pa_head, ev, ev_next); 458145256Sjkoshy 459145256Sjkoshy break; 460145256Sjkoshy 461145256Sjkoshy case 'n': /* sampling count */ 462145256Sjkoshy current_sampling_count = strtol(optarg, &end, 0); 463145256Sjkoshy if (*end != '\0' || current_sampling_count <= 0) 464145256Sjkoshy errx(EX_USAGE, 465145256Sjkoshy "ERROR: Illegal count value \"%s\"", 466145256Sjkoshy optarg); 467145256Sjkoshy break; 468145256Sjkoshy 469145256Sjkoshy case 'o': /* outputfile */ 470145256Sjkoshy if (args.pa_outputfile != NULL) 471145256Sjkoshy (void) fclose(args.pa_outputfile); 472145256Sjkoshy 473145256Sjkoshy if ((args.pa_outputfile = fopen(optarg, "w")) == NULL) 474145256Sjkoshy errx(EX_OSERR, "ERROR: cannot open \"%s\" for " 475145256Sjkoshy "writing", optarg); 476145256Sjkoshy 477145256Sjkoshy case 'O': /* sampling output */ 478145256Sjkoshy if (args.pa_logfile != NULL) 479145256Sjkoshy (void) fclose(args.pa_logfile); 480145256Sjkoshy 481145256Sjkoshy if ((args.pa_logfile = fopen(optarg, "w")) == NULL) 482145256Sjkoshy errx(EX_OSERR, "ERROR: cannot open \"%s\" for " 483145256Sjkoshy "writing", optarg); 484145256Sjkoshy break; 485145256Sjkoshy 486145256Sjkoshy case 't': /* target pid */ 487145256Sjkoshy pid = strtol(optarg, &end, 0); 488145256Sjkoshy if (*end != '\0' || pid <= 0) 489145256Sjkoshy errx(EX_USAGE, "ERROR: Illegal pid value " 490145256Sjkoshy "\"%s\"", optarg); 491145256Sjkoshy 492145256Sjkoshy args.pa_flags |= FLAG_HAS_PID; 493145256Sjkoshy args.pa_pid = pid; 494145256Sjkoshy 495145256Sjkoshy break; 496145256Sjkoshy 497145256Sjkoshy case 'w': /* wait interval */ 498145256Sjkoshy interval = strtod(optarg, &end); 499145256Sjkoshy if (*end != '\0' || interval <= 0) 500145256Sjkoshy errx(EX_USAGE, "ERROR: Illegal wait interval " 501145256Sjkoshy "value \"%s\"", optarg); 502145256Sjkoshy args.pa_flags |= FLAG_HAS_WAIT_INTERVAL; 503145256Sjkoshy args.pa_interval = interval; 504145256Sjkoshy 505145256Sjkoshy break; 506145256Sjkoshy 507145256Sjkoshy case '?': 508145256Sjkoshy default: 509145256Sjkoshy pmcstat_show_usage(); 510145256Sjkoshy break; 511145256Sjkoshy 512145256Sjkoshy } 513145256Sjkoshy 514145256Sjkoshy args.pa_argc = (argc -= optind); 515145256Sjkoshy args.pa_argv = (argv += optind); 516145256Sjkoshy 517145256Sjkoshy if (argc) 518145256Sjkoshy args.pa_flags |= FLAG_HAS_PROCESS; 519145256Sjkoshy 520145256Sjkoshy /* 521145256Sjkoshy * Check invocation syntax. 522145256Sjkoshy */ 523145256Sjkoshy 524145256Sjkoshy if (STAILQ_EMPTY(&args.pa_head)) { 525145256Sjkoshy warnx("ERROR: At least one PMC event must be specified"); 526145256Sjkoshy pmcstat_show_usage(); 527145256Sjkoshy } 528145256Sjkoshy 529145256Sjkoshy if (argc == 0) { 530145256Sjkoshy if (args.pa_pid == -1) { 531145256Sjkoshy if (args.pa_flags & FLAG_USING_PROCESS_PMC) 532145256Sjkoshy errx(EX_USAGE, "ERROR: the -P or -p options " 533145256Sjkoshy "require a target process"); 534145256Sjkoshy } else if ((args.pa_flags & FLAG_USING_PROCESS_PMC) == 0) 535145256Sjkoshy errx(EX_USAGE, 536145256Sjkoshy "ERROR: option -t requires a process-mode pmc " 537145256Sjkoshy "specification"); 538145256Sjkoshy } else if (args.pa_pid != -1) 539145256Sjkoshy errx(EX_USAGE, 540145256Sjkoshy "ERROR: option -t cannot be specified with a command " 541145256Sjkoshy "name"); 542145256Sjkoshy 543145256Sjkoshy if (pmc_init() < 0) 544145256Sjkoshy err(EX_UNAVAILABLE, 545145256Sjkoshy "ERROR: Initialization of the pmc(3) library failed"); 546145256Sjkoshy 547145256Sjkoshy if ((ncpu = pmc_ncpu()) < 0) 548145256Sjkoshy err(EX_OSERR, "ERROR: Cannot determine the number CPUs " 549145256Sjkoshy "on the system"); 550145256Sjkoshy 551145256Sjkoshy if ((npmc = pmc_npmc(0)) < 0) /* assume all CPUs are identical */ 552145256Sjkoshy err(EX_OSERR, "ERROR: Cannot determine the number of PMCs " 553145256Sjkoshy "on CPU %d", 0); 554145256Sjkoshy 555145256Sjkoshy /* 556145256Sjkoshy * Allocate PMCs. 557145256Sjkoshy */ 558145256Sjkoshy 559145256Sjkoshy if (pmc_pmcinfo(0, &ppmci) < 0) 560145256Sjkoshy err(EX_OSERR, "ERROR: cannot retrieve pmc information"); 561145256Sjkoshy 562145256Sjkoshy assert(ppmci != NULL); 563145256Sjkoshy 564145256Sjkoshy STAILQ_FOREACH(ev, &args.pa_head, ev_next) 565145256Sjkoshy if (pmc_allocate(ev->ev_spec, ev->ev_mode, 566145256Sjkoshy (ev->ev_descendants ? PMC_F_DESCENDANTS : 0), 567145256Sjkoshy ev->ev_cpu, &ev->ev_pmcid) < 0) 568145256Sjkoshy err(EX_OSERR, "ERROR: Cannot allocate %s-mode pmc with " 569145256Sjkoshy "specification \"%s\"", 570145256Sjkoshy PMC_IS_SYSTEM_MODE(ev->ev_mode) ? "system" : "process", 571145256Sjkoshy ev->ev_spec); 572145256Sjkoshy 573145256Sjkoshy /* compute printout widths */ 574145256Sjkoshy STAILQ_FOREACH(ev, &args.pa_head, ev_next) { 575145256Sjkoshy int pmc_width; 576145256Sjkoshy int pmc_display_width; 577145256Sjkoshy int pmc_header_width; 578145256Sjkoshy 579145256Sjkoshy pmc_width = ppmci->pm_pmcs[ev->ev_pmcid].pm_width; 580145256Sjkoshy pmc_header_width = strlen(ev->ev_name) + 2; /* prefix '%c|' */ 581145256Sjkoshy pmc_display_width = (int) floor(pmc_width / 3.32193) + 1; 582145256Sjkoshy 583145256Sjkoshy if (pmc_header_width > pmc_display_width) { 584145256Sjkoshy ev->ev_fieldskip = 0; 585145256Sjkoshy ev->ev_fieldwidth = pmc_header_width; 586145256Sjkoshy } else { 587145256Sjkoshy ev->ev_fieldskip = pmc_display_width - 588145256Sjkoshy pmc_header_width; 589145256Sjkoshy ev->ev_fieldwidth = pmc_display_width; 590145256Sjkoshy } 591145256Sjkoshy } 592145256Sjkoshy 593145256Sjkoshy /* Allocate a kqueue */ 594145256Sjkoshy if ((pmcstat_kq = kqueue()) < 0) 595145256Sjkoshy err(EX_OSERR, "ERROR: Cannot allocate kqueue"); 596145256Sjkoshy 597145256Sjkoshy /* 598145256Sjkoshy * If our output is being set to a terminal, register a handler 599145256Sjkoshy * for window size changes. 600145256Sjkoshy */ 601145256Sjkoshy 602145256Sjkoshy if (isatty(fileno(args.pa_outputfile))) { 603145256Sjkoshy 604145256Sjkoshy if (ioctl(fileno(args.pa_outputfile), TIOCGWINSZ, &ws) < 0) 605145256Sjkoshy err(EX_OSERR, "ERROR: Cannot determine window size"); 606145256Sjkoshy 607145256Sjkoshy pmcstat_displayheight = ws.ws_row - 1; 608145256Sjkoshy 609145256Sjkoshy EV_SET(&kev, SIGWINCH, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); 610145256Sjkoshy 611145256Sjkoshy if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) 612145256Sjkoshy err(EX_OSERR, "ERROR: Cannot register kevent for " 613145256Sjkoshy "SIGWINCH"); 614145256Sjkoshy } 615145256Sjkoshy 616145256Sjkoshy EV_SET(&kev, SIGINT, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); 617145256Sjkoshy 618145256Sjkoshy if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) 619145256Sjkoshy err(EX_OSERR, "ERROR: Cannot register kevent for SIGINT"); 620145256Sjkoshy 621145256Sjkoshy if (args.pa_flags & FLAG_USING_SAMPLING) { 622145256Sjkoshy 623145256Sjkoshy /* 624145256Sjkoshy * configure log file 625145256Sjkoshy */ 626145256Sjkoshy 627145256Sjkoshy if (args.pa_logfile == NULL) 628145256Sjkoshy if ((args.pa_logfile = 629145256Sjkoshy fopen(DEFAULT_LOGFILE_NAME, "w")) == NULL) 630145256Sjkoshy err(EX_CANTCREAT, "ERROR: Cannot open sampling " 631145256Sjkoshy "log file \"%s\"", DEFAULT_LOGFILE_NAME); 632145256Sjkoshy 633145256Sjkoshy if (pmc_configure_logfile(fileno(args.pa_logfile)) < 0) 634145256Sjkoshy err(EX_OSERR, "ERROR: Cannot configure sampling " 635145256Sjkoshy "log"); 636145256Sjkoshy 637145256Sjkoshy STAILQ_FOREACH(ev, &args.pa_head, ev_next) 638145256Sjkoshy if (PMC_IS_SAMPLING_MODE(ev->ev_mode) && 639145256Sjkoshy pmc_set(ev->ev_pmcid, ev->ev_count) < 0) 640145256Sjkoshy err(EX_OSERR, "ERROR: Cannot set sampling count " 641145256Sjkoshy "for PMC \"%s\"", ev->ev_name); 642145256Sjkoshy } 643145256Sjkoshy 644145256Sjkoshy /* setup a timer for any counting mode PMCs */ 645145256Sjkoshy if (args.pa_flags & FLAG_USING_COUNTING) { 646145256Sjkoshy EV_SET(&kev, 0, EVFILT_TIMER, EV_ADD, 0, 647145256Sjkoshy args.pa_interval * 1000, NULL); 648145256Sjkoshy 649145256Sjkoshy if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) 650145256Sjkoshy err(EX_OSERR, "ERROR: Cannot register kevent for " 651145256Sjkoshy "timer"); 652145256Sjkoshy } 653145256Sjkoshy 654145256Sjkoshy /* attach PMCs to the target process, starting it if specified */ 655145256Sjkoshy if (args.pa_flags & FLAG_HAS_PROCESS) 656145256Sjkoshy pmcstat_setup_process(&args); 657145256Sjkoshy 658145256Sjkoshy /* start the pmcs */ 659145256Sjkoshy pmcstat_start_pmcs(&args); 660145256Sjkoshy 661145256Sjkoshy /* start the (commandline) process if needed */ 662145256Sjkoshy if (args.pa_flags & FLAG_HAS_PROCESS) 663145256Sjkoshy pmcstat_start_process(&args); 664145256Sjkoshy 665145256Sjkoshy /* Handle SIGINT using the kqueue loop */ 666145256Sjkoshy sa.sa_handler = SIG_IGN; 667145256Sjkoshy sa.sa_flags = 0; 668145256Sjkoshy (void) sigemptyset(&sa.sa_mask); 669145256Sjkoshy 670145256Sjkoshy if (sigaction(SIGINT, &sa, NULL) < 0) 671145256Sjkoshy err(EX_OSERR, "ERROR: Cannot install signal handler"); 672145256Sjkoshy 673145256Sjkoshy /* 674145256Sjkoshy * loop till either the target process (if any) exits, or we 675145256Sjkoshy * are killed by a SIGINT. 676145256Sjkoshy */ 677145256Sjkoshy 678145256Sjkoshy running = 1; 679145256Sjkoshy do { 680145256Sjkoshy if ((c = kevent(pmcstat_kq, NULL, 0, &kev, 1, NULL)) <= 0) { 681145256Sjkoshy if (errno != EINTR) 682145256Sjkoshy err(EX_OSERR, "ERROR: kevent failed"); 683145256Sjkoshy else 684145256Sjkoshy continue; 685145256Sjkoshy } 686145256Sjkoshy 687145256Sjkoshy if (kev.flags & EV_ERROR) 688145256Sjkoshy errc(EX_OSERR, kev.data, "ERROR: kevent failed"); 689145256Sjkoshy 690145256Sjkoshy switch (kev.filter) { 691145256Sjkoshy case EVFILT_PROC: /* target process exited */ 692145256Sjkoshy running = 0; 693145256Sjkoshy /* FALLTHROUGH */ 694145256Sjkoshy 695145256Sjkoshy case EVFILT_TIMER: /* print out counting PMCs */ 696145256Sjkoshy pmcstat_print_pmcs(&args); 697145256Sjkoshy 698145256Sjkoshy if (running == 0) /* final newline */ 699145256Sjkoshy (void) fprintf(args.pa_outputfile, "\n"); 700145256Sjkoshy break; 701145256Sjkoshy 702145256Sjkoshy case EVFILT_SIGNAL: 703145256Sjkoshy if (kev.ident == SIGINT) { 704145256Sjkoshy /* pass the signal on to the child process */ 705145256Sjkoshy if ((args.pa_flags & FLAG_HAS_PROCESS) && 706145256Sjkoshy (args.pa_flags & FLAG_HAS_PID) == 0) 707145256Sjkoshy if (kill(args.pa_pid, SIGINT) != 0) 708145256Sjkoshy err(EX_OSERR, "cannot kill " 709145256Sjkoshy "child"); 710145256Sjkoshy running = 0; 711145256Sjkoshy } else if (kev.ident == SIGWINCH) { 712145256Sjkoshy if (ioctl(fileno(args.pa_outputfile), 713145256Sjkoshy TIOCGWINSZ, &ws) < 0) 714145256Sjkoshy err(EX_OSERR, "ERROR: Cannot determine " 715145256Sjkoshy "window size"); 716145256Sjkoshy pmcstat_displayheight = ws.ws_row - 1; 717145256Sjkoshy } else 718145256Sjkoshy assert(0); 719145256Sjkoshy 720145256Sjkoshy break; 721145256Sjkoshy } 722145256Sjkoshy 723145256Sjkoshy } while (running); 724145256Sjkoshy 725145256Sjkoshy pmcstat_cleanup(&args); 726145256Sjkoshy 727145256Sjkoshy return 0; 728145256Sjkoshy} 729