pmcstat.c revision 145796
1235783Skib/*- 2235783Skib * Copyright (c) 2003,2004 Joseph Koshy 3235783Skib * All rights reserved. 4235783Skib * 5235783Skib * Redistribution and use in source and binary forms, with or without 6235783Skib * modification, are permitted provided that the following conditions 7235783Skib * are met: 8235783Skib * 1. Redistributions of source code must retain the above copyright 9235783Skib * notice, this list of conditions and the following disclaimer. 10235783Skib * 2. Redistributions in binary form must reproduce the above copyright 11235783Skib * notice, this list of conditions and the following disclaimer in the 12235783Skib * documentation and/or other materials provided with the distribution. 13235783Skib * 14235783Skib * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15235783Skib * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16235783Skib * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17235783Skib * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18235783Skib * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19235783Skib * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20235783Skib * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21235783Skib * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22235783Skib * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23235783Skib * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24235783Skib * SUCH DAMAGE. 25235783Skib * 26235783Skib */ 27235783Skib 28235783Skib#include <sys/cdefs.h> 29235783Skib__FBSDID("$FreeBSD: head/usr.sbin/pmcstat/pmcstat.c 145796 2005-05-02 10:07:11Z jkoshy $"); 30235783Skib 31235783Skib#include <sys/types.h> 32235783Skib#include <sys/event.h> 33235783Skib#include <sys/queue.h> 34235783Skib#include <sys/time.h> 35235783Skib#include <sys/ttycom.h> 36235783Skib#include <sys/wait.h> 37235783Skib 38235783Skib#include <assert.h> 39235783Skib#include <err.h> 40235783Skib#include <errno.h> 41235783Skib#include <fcntl.h> 42235783Skib#include <limits.h> 43235783Skib#include <math.h> 44235783Skib#include <pmc.h> 45235783Skib#include <signal.h> 46235783Skib#include <stdarg.h> 47235783Skib#include <stdio.h> 48235783Skib#include <stdint.h> 49235783Skib#include <stdlib.h> 50235783Skib#include <string.h> 51235783Skib#include <sysexits.h> 52235783Skib#include <unistd.h> 53235783Skib 54235783Skib/* Operation modes */ 55235783Skib 56235783Skib#define FLAG_HAS_PID 0x00000001 57235783Skib#define FLAG_HAS_WAIT_INTERVAL 0x00000002 58235783Skib#define FLAG_HAS_LOG_FILE 0x00000004 59235783Skib#define FLAG_HAS_PROCESS 0x00000008 60235783Skib#define FLAG_USING_SAMPLING 0x00000010 61235783Skib#define FLAG_USING_COUNTING 0x00000020 62235783Skib#define FLAG_USING_PROCESS_PMC 0x00000040 63235783Skib 64235783Skib#define DEFAULT_SAMPLE_COUNT 65536 65235783Skib#define DEFAULT_WAIT_INTERVAL 5.0 66235783Skib#define DEFAULT_DISPLAY_HEIGHT 23 67235783Skib#define DEFAULT_LOGFILE_NAME "pmcstat.out" 68235783Skib 69235783Skib#define PRINT_HEADER_PREFIX "# " 70235783Skib#define READPIPEFD 0 71235783Skib#define WRITEPIPEFD 1 72235783Skib#define NPIPEFD 2 73235783Skib 74235783Skibstruct pmcstat_ev { 75235783Skib STAILQ_ENTRY(pmcstat_ev) ev_next; 76235783Skib char *ev_spec; /* event specification */ 77235783Skib char *ev_name; /* (derived) event name */ 78235783Skib enum pmc_mode ev_mode; /* desired mode */ 79235783Skib int ev_count; /* associated count if in sampling mode */ 80235783Skib int ev_cpu; /* specific cpu if requested */ 81235783Skib int ev_descendants; /* attach to descendants */ 82235783Skib int ev_cumulative; /* show cumulative counts */ 83235783Skib int ev_fieldwidth; /* print width */ 84235783Skib int ev_fieldskip; /* #leading spaces */ 85235783Skib pmc_value_t ev_saved; /* saved value for incremental counts */ 86235783Skib pmc_id_t ev_pmcid; /* allocated ID */ 87235783Skib}; 88235783Skib 89235783Skibstruct pmcstat_args { 90235783Skib int pa_flags; 91235783Skib pid_t pa_pid; 92235783Skib FILE *pa_outputfile; 93235783Skib FILE *pa_logfile; 94235783Skib double pa_interval; 95235783Skib int pa_argc; 96235783Skib char **pa_argv; 97235783Skib STAILQ_HEAD(, pmcstat_ev) pa_head; 98235783Skib} args; 99235783Skib 100235783Skibint pmcstat_interrupt = 0; 101235783Skibint pmcstat_displayheight = DEFAULT_DISPLAY_HEIGHT; 102235783Skibint pmcstat_pipefd[NPIPEFD]; 103235783Skibint pmcstat_kq; 104235783Skib 105235783Skib/* Function prototypes */ 106235783Skibvoid pmcstat_cleanup(struct pmcstat_args *_a); 107235783Skibvoid pmcstat_print_counters(struct pmcstat_args *_a); 108235783Skibvoid pmcstat_print_headers(struct pmcstat_args *_a); 109235783Skibvoid pmcstat_print_pmcs(struct pmcstat_args *_a); 110235783Skibvoid pmcstat_setup_process(struct pmcstat_args *_a); 111235783Skibvoid pmcstat_show_usage(void); 112235783Skibvoid pmcstat_start_pmcs(struct pmcstat_args *_a); 113235783Skibvoid pmcstat_start_process(struct pmcstat_args *_a); 114235783Skib 115235783Skib 116235783Skib/* 117235783Skib * cleanup 118235783Skib */ 119235783Skib 120235783Skibvoid 121235783Skibpmcstat_cleanup(struct pmcstat_args *a) 122235783Skib{ 123235783Skib struct pmcstat_ev *ev, *tmp; 124235783Skib 125235783Skib /* de-configure the log file if present. */ 126235783Skib if (a->pa_flags & FLAG_USING_SAMPLING) { 127235783Skib (void) pmc_configure_logfile(-1); 128235783Skib (void) fclose(a->pa_logfile); 129235783Skib } 130235783Skib 131235783Skib /* release allocated PMCs. */ 132235783Skib STAILQ_FOREACH_SAFE(ev, &a->pa_head, ev_next, tmp) 133235783Skib if (ev->ev_pmcid != PMC_ID_INVALID) { 134235783Skib if (pmc_release(ev->ev_pmcid) < 0) 135235783Skib err(EX_OSERR, "ERROR: cannot release pmc " 136235783Skib "%d \"%s\"", ev->ev_pmcid, ev->ev_name); 137235783Skib free(ev->ev_name); 138235783Skib free(ev->ev_spec); 139235783Skib STAILQ_REMOVE(&a->pa_head, ev, pmcstat_ev, ev_next); 140235783Skib free(ev); 141235783Skib } 142235783Skib} 143235783Skib 144235783Skibvoid 145235783Skibpmcstat_start_pmcs(struct pmcstat_args *a) 146235783Skib{ 147235783Skib struct pmcstat_ev *ev; 148235783Skib 149235783Skib STAILQ_FOREACH(ev, &args.pa_head, ev_next) { 150235783Skib 151235783Skib assert(ev->ev_pmcid != PMC_ID_INVALID); 152235783Skib 153235783Skib if (pmc_start(ev->ev_pmcid) < 0) { 154235783Skib warn("ERROR: Cannot start pmc %d \"%s\"", 155235783Skib ev->ev_pmcid, ev->ev_name); 156235783Skib pmcstat_cleanup(a); 157235783Skib } 158235783Skib } 159235783Skib 160235783Skib} 161235783Skib 162235783Skibvoid 163235783Skibpmcstat_print_headers(struct pmcstat_args *a) 164235783Skib{ 165235783Skib struct pmcstat_ev *ev; 166235783Skib int c; 167235783Skib 168235783Skib (void) fprintf(a->pa_outputfile, PRINT_HEADER_PREFIX); 169235783Skib 170235783Skib STAILQ_FOREACH(ev, &a->pa_head, ev_next) { 171235783Skib if (PMC_IS_SAMPLING_MODE(ev->ev_mode)) 172235783Skib continue; 173235783Skib 174235783Skib c = PMC_IS_SYSTEM_MODE(ev->ev_mode) ? 's' : 'p'; 175235783Skib 176235783Skib if (ev->ev_fieldskip != 0) { 177235783Skib (void) fprintf(a->pa_outputfile, "%*s%c/%*s ", 178235783Skib ev->ev_fieldskip, "", c, 179235783Skib ev->ev_fieldwidth - ev->ev_fieldskip - 2, 180235783Skib ev->ev_name); 181235783Skib } else 182235783Skib (void) fprintf(a->pa_outputfile, "%c/%*s ", 183235783Skib c, ev->ev_fieldwidth - 2, ev->ev_name); 184235783Skib } 185235783Skib 186235783Skib (void) fflush(a->pa_outputfile); 187235783Skib} 188235783Skib 189235783Skibvoid 190235783Skibpmcstat_print_counters(struct pmcstat_args *a) 191235783Skib{ 192235783Skib int extra_width; 193235783Skib struct pmcstat_ev *ev; 194235783Skib pmc_value_t value; 195235783Skib 196235783Skib extra_width = sizeof(PRINT_HEADER_PREFIX) - 1; 197235783Skib 198235783Skib STAILQ_FOREACH(ev, &a->pa_head, ev_next) { 199235783Skib 200235783Skib /* skip sampling mode counters */ 201235783Skib if (PMC_IS_SAMPLING_MODE(ev->ev_mode)) 202235783Skib continue; 203235783Skib 204235783Skib if (pmc_read(ev->ev_pmcid, &value) < 0) 205235783Skib err(EX_OSERR, "ERROR: Cannot read pmc " 206235783Skib "\"%s\"", ev->ev_name); 207235783Skib 208235783Skib (void) fprintf(a->pa_outputfile, "%*ju ", 209235783Skib ev->ev_fieldwidth + extra_width, (uintmax_t) 210235783Skib ev->ev_cumulative ? value : (value - ev->ev_saved)); 211235783Skib if (ev->ev_cumulative == 0) 212235783Skib ev->ev_saved = value; 213235783Skib extra_width = 0; 214235783Skib } 215235783Skib 216235783Skib (void) fflush(a->pa_outputfile); 217235783Skib} 218235783Skib 219235783Skib/* 220235783Skib * Print output 221235783Skib */ 222235783Skib 223235783Skibvoid 224235783Skibpmcstat_print_pmcs(struct pmcstat_args *a) 225235783Skib{ 226235783Skib static int linecount = 0; 227235783Skib 228235783Skib if (++linecount > pmcstat_displayheight) { 229235783Skib (void) fprintf(a->pa_outputfile, "\n"); 230235783Skib linecount = 1; 231235783Skib } 232235783Skib 233235783Skib if (linecount == 1) 234235783Skib pmcstat_print_headers(a); 235235783Skib 236235783Skib (void) fprintf(a->pa_outputfile, "\n"); 237235783Skib pmcstat_print_counters(a); 238235783Skib 239235783Skib return; 240235783Skib} 241235783Skib 242235783Skib/* 243235783Skib * Do process profiling 244235783Skib * 245235783Skib * If a pid was specified, attach each allocated PMC to the target 246235783Skib * process. Otherwise, fork a child and attach the PMCs to the child, 247235783Skib * and have the child exec() the target program. 248235783Skib */ 249235783Skib 250235783Skibvoid 251235783Skibpmcstat_setup_process(struct pmcstat_args *a) 252235783Skib{ 253235783Skib char token; 254235783Skib struct pmcstat_ev *ev; 255235783Skib struct kevent kev; 256235783Skib 257235783Skib if (a->pa_flags & FLAG_HAS_PID) { 258235783Skib 259235783Skib STAILQ_FOREACH(ev, &args.pa_head, ev_next) 260235783Skib if (pmc_attach(ev->ev_pmcid, a->pa_pid) != 0) 261235783Skib err(EX_OSERR, "ERROR: cannot attach pmc \"%s\" to " 262235783Skib "process %d", ev->ev_name, (int) a->pa_pid); 263235783Skib 264235783Skib } else { 265235783Skib 266235783Skib /* 267235783Skib * We need to fork a new process and startup the child 268235783Skib * using execvp(). Before doing the exec() the child 269235783Skib * process reads its pipe for a token so that the parent 270235783Skib * can finish doing its pmc_attach() calls. 271235783Skib */ 272235783Skib 273235783Skib if (pipe(pmcstat_pipefd) < 0) 274235783Skib err(EX_OSERR, "ERROR: cannot create pipe"); 275235783Skib 276235783Skib switch (a->pa_pid = fork()) { 277235783Skib case -1: 278235783Skib err(EX_OSERR, "ERROR: cannot fork"); 279235783Skib /*NOTREACHED*/ 280235783Skib 281235783Skib case 0: /* child */ 282235783Skib 283235783Skib /* wait for our parent to signal us */ 284235783Skib (void) close(pmcstat_pipefd[WRITEPIPEFD]); 285235783Skib if (read(pmcstat_pipefd[READPIPEFD], &token, 1) < 0) 286235783Skib err(EX_OSERR, "ERROR (child): cannot read " 287235783Skib "token"); 288235783Skib (void) close(pmcstat_pipefd[READPIPEFD]); 289235783Skib 290235783Skib /* exec() the program requested */ 291235783Skib execvp(*args.pa_argv, args.pa_argv); 292235783Skib err(EX_OSERR, "ERROR (child): execvp failed"); 293235783Skib /*NOTREACHED*/ 294235783Skib 295235783Skib default: /* parent */ 296235783Skib 297235783Skib (void) close(pmcstat_pipefd[READPIPEFD]); 298235783Skib 299235783Skib /* attach all our PMCs to the child */ 300235783Skib STAILQ_FOREACH(ev, &args.pa_head, ev_next) 301235783Skib if (PMC_IS_VIRTUAL_MODE(ev->ev_mode) && 302235783Skib pmc_attach(ev->ev_pmcid, a->pa_pid) != 0) 303235783Skib err(EX_OSERR, "ERROR: cannot attach pmc " 304235783Skib "\"%s\" to process %d", ev->ev_name, 305235783Skib (int) a->pa_pid); 306235783Skib 307235783Skib } 308235783Skib } 309235783Skib 310235783Skib /* Ask to be notified via a kevent when the child exits */ 311235783Skib EV_SET(&kev, a->pa_pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, 0); 312235783Skib 313235783Skib if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) 314235783Skib err(EX_OSERR, "ERROR: cannot monitor process %d", 315235783Skib a->pa_pid); 316235783Skib 317235783Skib return; 318235783Skib} 319235783Skib 320235783Skibvoid 321235783Skibpmcstat_start_process(struct pmcstat_args *a) 322235783Skib{ 323235783Skib 324235783Skib /* nothing to do: target is already running */ 325235783Skib if (a->pa_flags & FLAG_HAS_PID) 326235783Skib return; 327235783Skib 328235783Skib /* write token to child to state that we are ready */ 329235783Skib if (write(pmcstat_pipefd[WRITEPIPEFD], "+", 1) != 1) 330235783Skib err(EX_OSERR, "ERROR: write failed"); 331235783Skib 332235783Skib (void) close(pmcstat_pipefd[WRITEPIPEFD]); 333235783Skib} 334235783Skib 335235783Skibvoid 336235783Skibpmcstat_show_usage(void) 337235783Skib{ 338235783Skib errx(EX_USAGE, 339235783Skib "[options] [commandline]\n" 340235783Skib "\t Measure process and/or system performance using hardware\n" 341235783Skib "\t performance monitoring counters.\n" 342235783Skib "\t Options include:\n" 343235783Skib "\t -C\t\t toggle showing cumulative counts\n" 344235783Skib "\t -O file\t set sampling log file to \"file\"\n" 345235783Skib "\t -P spec\t allocate process-private sampling PMC\n" 346235783Skib "\t -S spec\t allocate system-wide sampling PMC\n" 347235783Skib "\t -c cpu\t\t set default cpu\n" 348235783Skib "\t -d\t\t toggle tracking descendants\n" 349235783Skib "\t -n rate\t set sampling rate\n" 350235783Skib "\t -o file\t send print output to \"file\"\n" 351235783Skib "\t -p spec\t allocate process-private counting PMC\n" 352235783Skib "\t -s spec\t allocate system-wide counting PMC\n" 353235783Skib "\t -t pid\t\t attach to running process with pid \"pid\"\n" 354235783Skib "\t -w secs\t set printing time interval" 355235783Skib ); 356235783Skib} 357235783Skib 358235783Skib/* 359235783Skib * Main 360235783Skib */ 361235783Skib 362235783Skibint 363235783Skibmain(int argc, char **argv) 364235783Skib{ 365235783Skib double interval; 366235783Skib int option, npmc, ncpu; 367235783Skib int c, current_cpu, current_sampling_count; 368235783Skib int running; 369235783Skib int do_descendants, use_cumulative_counts; 370235783Skib pid_t pid; 371235783Skib char *end; 372235783Skib struct pmcstat_ev *ev; 373235783Skib struct pmc_op_getpmcinfo *ppmci; 374235783Skib struct sigaction sa; 375235783Skib struct kevent kev; 376235783Skib struct winsize ws; 377235783Skib 378235783Skib current_cpu = 0; 379235783Skib current_sampling_count = DEFAULT_SAMPLE_COUNT; 380235783Skib do_descendants = 0; 381235783Skib use_cumulative_counts = 0; 382235783Skib args.pa_flags = 0; 383235783Skib args.pa_pid = (pid_t) -1; 384235783Skib args.pa_logfile = NULL; 385235783Skib args.pa_outputfile = stderr; 386235783Skib args.pa_interval = DEFAULT_WAIT_INTERVAL; 387235783Skib STAILQ_INIT(&args.pa_head); 388235783Skib 389235783Skib ev = NULL; 390235783Skib 391235783Skib while ((option = getopt(argc, argv, "CO:P:S:c:dn:o:p:s:t:w:")) != -1) 392235783Skib switch (option) { 393235783Skib case 'C': /* cumulative values */ 394235783Skib use_cumulative_counts = !use_cumulative_counts; 395235783Skib break; 396235783Skib 397235783Skib case 'c': /* CPU */ 398235783Skib current_cpu = strtol(optarg, &end, 0); 399235783Skib if (*end != '\0' || current_cpu < 0) 400235783Skib errx(EX_USAGE, 401235783Skib "ERROR: Illegal CPU number \"%s\"", 402235783Skib optarg); 403235783Skib 404235783Skib break; 405235783Skib 406235783Skib case 'd': /* toggle descendents */ 407235783Skib do_descendants = !do_descendants; 408235783Skib break; 409235783Skib 410235783Skib case 'p': /* process virtual counting PMC */ 411235783Skib case 's': /* system-wide counting PMC */ 412235783Skib case 'P': /* process virtual sampling PMC */ 413235783Skib case 'S': /* system-wide sampling PMC */ 414235783Skib if ((ev = malloc(sizeof(*ev))) == NULL) 415235783Skib errx(EX_SOFTWARE, "ERROR: Out of memory"); 416235783Skib 417235783Skib switch (option) { 418235783Skib case 'p': ev->ev_mode = PMC_MODE_TC; break; 419235783Skib case 's': ev->ev_mode = PMC_MODE_SC; break; 420235783Skib case 'P': ev->ev_mode = PMC_MODE_TS; break; 421235783Skib case 'S': ev->ev_mode = PMC_MODE_SS; break; 422235783Skib } 423235783Skib 424235783Skib if (option == 'P' || option == 'p') 425235783Skib args.pa_flags |= FLAG_USING_PROCESS_PMC; 426235783Skib 427235783Skib if (option == 'P' || option == 'S') 428235783Skib args.pa_flags |= FLAG_USING_SAMPLING; 429235783Skib 430235783Skib if (option == 'p' || option == 's') 431235783Skib args.pa_flags |= FLAG_USING_COUNTING; 432235783Skib 433235783Skib ev->ev_spec = strdup(optarg); 434235783Skib 435235783Skib if (option == 'S' || option == 'P') 436235783Skib ev->ev_count = current_sampling_count; 437235783Skib else 438235783Skib ev->ev_count = -1; 439235783Skib 440235783Skib if (option == 'S' || option == 's') 441235783Skib ev->ev_cpu = current_cpu; 442235783Skib else 443235783Skib ev->ev_cpu = PMC_CPU_ANY; 444235783Skib 445235783Skib ev->ev_descendants = do_descendants; 446235783Skib ev->ev_cumulative = use_cumulative_counts; 447235783Skib 448235783Skib ev->ev_saved = 0LL; 449235783Skib ev->ev_pmcid = PMC_ID_INVALID; 450235783Skib 451235783Skib /* extract event name */ 452235783Skib c = strcspn(optarg, ", \t"); 453235783Skib ev->ev_name = malloc(c + 1); 454235783Skib (void) strncpy(ev->ev_name, optarg, c); 455235783Skib *(ev->ev_name + c) = '\0'; 456235783Skib 457235783Skib STAILQ_INSERT_TAIL(&args.pa_head, ev, ev_next); 458235783Skib 459235783Skib break; 460235783Skib 461235783Skib case 'n': /* sampling count */ 462235783Skib current_sampling_count = strtol(optarg, &end, 0); 463235783Skib if (*end != '\0' || current_sampling_count <= 0) 464235783Skib errx(EX_USAGE, 465235783Skib "ERROR: Illegal count value \"%s\"", 466235783Skib optarg); 467235783Skib break; 468235783Skib 469235783Skib case 'o': /* outputfile */ 470235783Skib if (args.pa_outputfile != NULL) 471235783Skib (void) fclose(args.pa_outputfile); 472235783Skib 473235783Skib if ((args.pa_outputfile = fopen(optarg, "w")) == NULL) 474235783Skib errx(EX_OSERR, "ERROR: cannot open \"%s\" for " 475235783Skib "writing", optarg); 476235783Skib 477235783Skib case 'O': /* sampling output */ 478235783Skib if (args.pa_logfile != NULL) 479235783Skib (void) fclose(args.pa_logfile); 480235783Skib 481235783Skib if ((args.pa_logfile = fopen(optarg, "w")) == NULL) 482235783Skib errx(EX_OSERR, "ERROR: cannot open \"%s\" for " 483235783Skib "writing", optarg); 484235783Skib break; 485235783Skib 486235783Skib case 't': /* target pid */ 487235783Skib pid = strtol(optarg, &end, 0); 488235783Skib if (*end != '\0' || pid <= 0) 489235783Skib errx(EX_USAGE, "ERROR: Illegal pid value " 490235783Skib "\"%s\"", optarg); 491235783Skib 492235783Skib args.pa_flags |= FLAG_HAS_PID; 493235783Skib args.pa_pid = pid; 494235783Skib 495235783Skib break; 496235783Skib 497235783Skib case 'w': /* wait interval */ 498235783Skib interval = strtod(optarg, &end); 499235783Skib if (*end != '\0' || interval <= 0) 500235783Skib errx(EX_USAGE, "ERROR: Illegal wait interval " 501235783Skib "value \"%s\"", optarg); 502235783Skib args.pa_flags |= FLAG_HAS_WAIT_INTERVAL; 503235783Skib args.pa_interval = interval; 504235783Skib 505235783Skib break; 506235783Skib 507235783Skib case '?': 508235783Skib default: 509235783Skib pmcstat_show_usage(); 510235783Skib break; 511235783Skib 512235783Skib } 513235783Skib 514235783Skib args.pa_argc = (argc -= optind); 515235783Skib args.pa_argv = (argv += optind); 516235783Skib 517235783Skib if (argc) 518235783Skib args.pa_flags |= FLAG_HAS_PROCESS; 519235783Skib 520235783Skib /* 521235783Skib * Check invocation syntax. 522235783Skib */ 523235783Skib 524235783Skib if (STAILQ_EMPTY(&args.pa_head)) { 525235783Skib warnx("ERROR: At least one PMC event must be specified"); 526235783Skib pmcstat_show_usage(); 527235783Skib } 528235783Skib 529235783Skib if (argc == 0) { 530235783Skib if (args.pa_pid == -1) { 531235783Skib if (args.pa_flags & FLAG_USING_PROCESS_PMC) 532235783Skib errx(EX_USAGE, "ERROR: the -P or -p options " 533235783Skib "require a target process"); 534235783Skib } else if ((args.pa_flags & FLAG_USING_PROCESS_PMC) == 0) 535235783Skib errx(EX_USAGE, 536235783Skib "ERROR: option -t requires a process-mode pmc " 537235783Skib "specification"); 538235783Skib } else if (args.pa_pid != -1) 539235783Skib errx(EX_USAGE, 540235783Skib "ERROR: option -t cannot be specified with a command " 541235783Skib "name"); 542235783Skib 543235783Skib if (pmc_init() < 0) 544235783Skib err(EX_UNAVAILABLE, 545235783Skib "ERROR: Initialization of the pmc(3) library failed"); 546235783Skib 547235783Skib if ((ncpu = pmc_ncpu()) < 0) 548235783Skib err(EX_OSERR, "ERROR: Cannot determine the number CPUs " 549235783Skib "on the system"); 550235783Skib 551235783Skib if ((npmc = pmc_npmc(0)) < 0) /* assume all CPUs are identical */ 552235783Skib err(EX_OSERR, "ERROR: Cannot determine the number of PMCs " 553235783Skib "on CPU %d", 0); 554235783Skib 555235783Skib /* 556235783Skib * Allocate PMCs. 557235783Skib */ 558235783Skib 559235783Skib if (pmc_pmcinfo(0, &ppmci) < 0) 560235783Skib err(EX_OSERR, "ERROR: cannot retrieve pmc information"); 561235783Skib 562235783Skib assert(ppmci != NULL); 563235783Skib 564235783Skib STAILQ_FOREACH(ev, &args.pa_head, ev_next) 565235783Skib if (pmc_allocate(ev->ev_spec, ev->ev_mode, 566235783Skib (ev->ev_descendants ? PMC_F_DESCENDANTS : 0), 567235783Skib ev->ev_cpu, &ev->ev_pmcid) < 0) 568235783Skib err(EX_OSERR, "ERROR: Cannot allocate %s-mode pmc with " 569235783Skib "specification \"%s\"", 570235783Skib PMC_IS_SYSTEM_MODE(ev->ev_mode) ? "system" : "process", 571235783Skib ev->ev_spec); 572235783Skib 573235783Skib /* compute printout widths */ 574235783Skib STAILQ_FOREACH(ev, &args.pa_head, ev_next) { 575235783Skib int counter_width; 576235783Skib int display_width; 577235783Skib int header_width; 578235783Skib 579235783Skib (void) pmc_width(ev->ev_pmcid, &counter_width); 580235783Skib header_width = strlen(ev->ev_name) + 2; /* prefix '%c|' */ 581235783Skib display_width = (int) floor(counter_width / 3.32193) + 1; 582235783Skib 583235783Skib if (header_width > display_width) { 584235783Skib ev->ev_fieldskip = 0; 585235783Skib ev->ev_fieldwidth = header_width; 586235783Skib } else { 587235783Skib ev->ev_fieldskip = display_width - 588235783Skib header_width; 589235783Skib ev->ev_fieldwidth = display_width; 590235783Skib } 591235783Skib } 592235783Skib 593235783Skib /* Allocate a kqueue */ 594235783Skib if ((pmcstat_kq = kqueue()) < 0) 595235783Skib err(EX_OSERR, "ERROR: Cannot allocate kqueue"); 596235783Skib 597235783Skib /* 598235783Skib * If our output is being set to a terminal, register a handler 599235783Skib * for window size changes. 600235783Skib */ 601235783Skib 602235783Skib if (isatty(fileno(args.pa_outputfile))) { 603235783Skib 604235783Skib if (ioctl(fileno(args.pa_outputfile), TIOCGWINSZ, &ws) < 0) 605235783Skib err(EX_OSERR, "ERROR: Cannot determine window size"); 606235783Skib 607235783Skib pmcstat_displayheight = ws.ws_row - 1; 608235783Skib 609235783Skib EV_SET(&kev, SIGWINCH, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); 610235783Skib 611235783Skib if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) 612235783Skib err(EX_OSERR, "ERROR: Cannot register kevent for " 613235783Skib "SIGWINCH"); 614235783Skib } 615235783Skib 616235783Skib EV_SET(&kev, SIGINT, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); 617235783Skib 618235783Skib if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) 619235783Skib err(EX_OSERR, "ERROR: Cannot register kevent for SIGINT"); 620235783Skib 621235783Skib if (args.pa_flags & FLAG_USING_SAMPLING) { 622235783Skib 623235783Skib /* 624235783Skib * configure log file 625235783Skib */ 626235783Skib 627235783Skib if (args.pa_logfile == NULL) 628235783Skib if ((args.pa_logfile = 629235783Skib fopen(DEFAULT_LOGFILE_NAME, "w")) == NULL) 630235783Skib err(EX_CANTCREAT, "ERROR: Cannot open sampling " 631235783Skib "log file \"%s\"", DEFAULT_LOGFILE_NAME); 632235783Skib 633235783Skib if (pmc_configure_logfile(fileno(args.pa_logfile)) < 0) 634235783Skib err(EX_OSERR, "ERROR: Cannot configure sampling " 635235783Skib "log"); 636235783Skib 637235783Skib STAILQ_FOREACH(ev, &args.pa_head, ev_next) 638235783Skib if (PMC_IS_SAMPLING_MODE(ev->ev_mode) && 639235783Skib pmc_set(ev->ev_pmcid, ev->ev_count) < 0) 640235783Skib err(EX_OSERR, "ERROR: Cannot set sampling count " 641235783Skib "for PMC \"%s\"", ev->ev_name); 642235783Skib } 643235783Skib 644235783Skib /* setup a timer for any counting mode PMCs */ 645235783Skib if (args.pa_flags & FLAG_USING_COUNTING) { 646235783Skib EV_SET(&kev, 0, EVFILT_TIMER, EV_ADD, 0, 647235783Skib args.pa_interval * 1000, NULL); 648235783Skib 649235783Skib if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) 650235783Skib err(EX_OSERR, "ERROR: Cannot register kevent for " 651235783Skib "timer"); 652235783Skib } 653235783Skib 654235783Skib /* attach PMCs to the target process, starting it if specified */ 655235783Skib if (args.pa_flags & FLAG_HAS_PROCESS) 656235783Skib pmcstat_setup_process(&args); 657235783Skib 658235783Skib /* start the pmcs */ 659235783Skib pmcstat_start_pmcs(&args); 660235783Skib 661235783Skib /* start the (commandline) process if needed */ 662235783Skib if (args.pa_flags & FLAG_HAS_PROCESS) 663235783Skib pmcstat_start_process(&args); 664235783Skib 665235783Skib /* Handle SIGINT using the kqueue loop */ 666235783Skib sa.sa_handler = SIG_IGN; 667235783Skib sa.sa_flags = 0; 668235783Skib (void) sigemptyset(&sa.sa_mask); 669235783Skib 670 if (sigaction(SIGINT, &sa, NULL) < 0) 671 err(EX_OSERR, "ERROR: Cannot install signal handler"); 672 673 /* 674 * loop till either the target process (if any) exits, or we 675 * are killed by a SIGINT. 676 */ 677 678 running = 1; 679 do { 680 if ((c = kevent(pmcstat_kq, NULL, 0, &kev, 1, NULL)) <= 0) { 681 if (errno != EINTR) 682 err(EX_OSERR, "ERROR: kevent failed"); 683 else 684 continue; 685 } 686 687 if (kev.flags & EV_ERROR) 688 errc(EX_OSERR, kev.data, "ERROR: kevent failed"); 689 690 switch (kev.filter) { 691 case EVFILT_PROC: /* target process exited */ 692 running = 0; 693 /* FALLTHROUGH */ 694 695 case EVFILT_TIMER: /* print out counting PMCs */ 696 pmcstat_print_pmcs(&args); 697 698 if (running == 0) /* final newline */ 699 (void) fprintf(args.pa_outputfile, "\n"); 700 break; 701 702 case EVFILT_SIGNAL: 703 if (kev.ident == SIGINT) { 704 /* pass the signal on to the child process */ 705 if ((args.pa_flags & FLAG_HAS_PROCESS) && 706 (args.pa_flags & FLAG_HAS_PID) == 0) 707 if (kill(args.pa_pid, SIGINT) != 0) 708 err(EX_OSERR, "cannot kill " 709 "child"); 710 running = 0; 711 } else if (kev.ident == SIGWINCH) { 712 if (ioctl(fileno(args.pa_outputfile), 713 TIOCGWINSZ, &ws) < 0) 714 err(EX_OSERR, "ERROR: Cannot determine " 715 "window size"); 716 pmcstat_displayheight = ws.ws_row - 1; 717 } else 718 assert(0); 719 720 break; 721 } 722 723 } while (running); 724 725 pmcstat_cleanup(&args); 726 727 return 0; 728} 729