xref: /freebsd/usr.sbin/pmcstat/pmcstat.c (revision ebccf1e3a6b11b97cbf5f813dd76636e892a9035)
1 /*-
2  * Copyright (c) 2003,2004 Joseph Koshy
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  */
27 
28 #include <sys/cdefs.h>
29 __FBSDID("$FreeBSD$");
30 
31 #include <sys/types.h>
32 #include <sys/event.h>
33 #include <sys/queue.h>
34 #include <sys/time.h>
35 #include <sys/ttycom.h>
36 #include <sys/wait.h>
37 
38 #include <assert.h>
39 #include <err.h>
40 #include <errno.h>
41 #include <fcntl.h>
42 #include <limits.h>
43 #include <math.h>
44 #include <pmc.h>
45 #include <signal.h>
46 #include <stdarg.h>
47 #include <stdio.h>
48 #include <stdint.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <sysexits.h>
52 #include <unistd.h>
53 
54 /* Operation modes */
55 
56 #define	FLAG_HAS_PID			0x00000001
57 #define	FLAG_HAS_WAIT_INTERVAL		0x00000002
58 #define	FLAG_HAS_LOG_FILE		0x00000004
59 #define	FLAG_HAS_PROCESS		0x00000008
60 #define	FLAG_USING_SAMPLING		0x00000010
61 #define	FLAG_USING_COUNTING		0x00000020
62 #define	FLAG_USING_PROCESS_PMC		0x00000040
63 
64 #define	DEFAULT_SAMPLE_COUNT		65536
65 #define	DEFAULT_WAIT_INTERVAL		5.0
66 #define	DEFAULT_DISPLAY_HEIGHT		23
67 #define	DEFAULT_LOGFILE_NAME		"pmcstat.out"
68 
69 #define	PRINT_HEADER_PREFIX		"# "
70 #define	READPIPEFD			0
71 #define	WRITEPIPEFD			1
72 #define	NPIPEFD				2
73 
74 struct pmcstat_ev {
75 	STAILQ_ENTRY(pmcstat_ev) ev_next;
76 	char	       *ev_spec;  /* event specification */
77 	char	       *ev_name;  /* (derived) event name */
78 	enum pmc_mode	ev_mode;  /* desired mode */
79 	int		ev_count; /* associated count if in sampling mode */
80 	int		ev_cpu;	  /* specific cpu if requested */
81 	int		ev_descendants; /* attach to descendants */
82 	int		ev_cumulative;  /* show cumulative counts */
83 	int		ev_fieldwidth;  /* print width */
84 	int		ev_fieldskip;   /* #leading spaces */
85 	pmc_value_t	ev_saved; /* saved value for incremental counts */
86 	pmc_id_t	ev_pmcid; /* allocated ID */
87 };
88 
89 struct pmcstat_args {
90 	int	pa_flags;
91 	pid_t	pa_pid;
92 	FILE   *pa_outputfile;
93 	FILE   *pa_logfile;
94 	double  pa_interval;
95 	int	pa_argc;
96 	char  **pa_argv;
97 	STAILQ_HEAD(, pmcstat_ev) pa_head;
98 } args;
99 
100 int	pmcstat_interrupt = 0;
101 int	pmcstat_displayheight = DEFAULT_DISPLAY_HEIGHT;
102 int	pmcstat_pipefd[NPIPEFD];
103 int	pmcstat_kq;
104 
105 /* Function prototypes */
106 void pmcstat_cleanup(struct pmcstat_args *_a);
107 void pmcstat_print_counters(struct pmcstat_args *_a);
108 void pmcstat_print_headers(struct pmcstat_args *_a);
109 void pmcstat_print_pmcs(struct pmcstat_args *_a);
110 void pmcstat_setup_process(struct pmcstat_args *_a);
111 void pmcstat_show_usage(void);
112 void pmcstat_start_pmcs(struct pmcstat_args *_a);
113 void pmcstat_start_process(struct pmcstat_args *_a);
114 
115 
116 /*
117  * cleanup
118  */
119 
120 void
121 pmcstat_cleanup(struct pmcstat_args *a)
122 {
123 	struct pmcstat_ev *ev, *tmp;
124 
125 	/* de-configure the log file if present. */
126 	if (a->pa_flags & FLAG_USING_SAMPLING) {
127 		(void) pmc_configure_logfile(-1);
128 		(void) fclose(a->pa_logfile);
129 	}
130 
131 	/* release allocated PMCs. */
132 	STAILQ_FOREACH_SAFE(ev, &a->pa_head, ev_next, tmp)
133 	    if (ev->ev_pmcid != PMC_ID_INVALID) {
134 		if (pmc_release(ev->ev_pmcid) < 0)
135 			err(EX_OSERR, "ERROR: cannot release pmc "
136 			    "%d \"%s\"", ev->ev_pmcid, ev->ev_name);
137 		free(ev->ev_name);
138 		free(ev->ev_spec);
139 		STAILQ_REMOVE(&a->pa_head, ev, pmcstat_ev, ev_next);
140 		free(ev);
141 	    }
142 }
143 
144 void
145 pmcstat_start_pmcs(struct pmcstat_args *a)
146 {
147 	struct pmcstat_ev *ev;
148 
149 	STAILQ_FOREACH(ev, &args.pa_head, ev_next) {
150 
151 	    assert(ev->ev_pmcid != PMC_ID_INVALID);
152 
153 	    if (pmc_start(ev->ev_pmcid) < 0) {
154 	        warn("ERROR: Cannot start pmc %d \"%s\"",
155 		    ev->ev_pmcid, ev->ev_name);
156 		pmcstat_cleanup(a);
157 	    }
158 	}
159 
160 }
161 
162 void
163 pmcstat_print_headers(struct pmcstat_args *a)
164 {
165 	struct pmcstat_ev *ev;
166 	int c;
167 
168 	(void) fprintf(a->pa_outputfile, PRINT_HEADER_PREFIX);
169 
170 	STAILQ_FOREACH(ev, &a->pa_head, ev_next) {
171 		if (PMC_IS_SAMPLING_MODE(ev->ev_mode))
172 			continue;
173 
174 		c = PMC_IS_SYSTEM_MODE(ev->ev_mode) ? 's' : 'p';
175 
176 		if (ev->ev_fieldskip != 0) {
177 			(void) fprintf(a->pa_outputfile, "%*s%c/%*s ",
178 			    ev->ev_fieldskip, "", c,
179 			    ev->ev_fieldwidth - ev->ev_fieldskip - 2,
180 			    ev->ev_name);
181 		} else
182 			(void) fprintf(a->pa_outputfile, "%c/%*s ",
183 			    c, ev->ev_fieldwidth - 2, ev->ev_name);
184 	}
185 
186 	(void) fflush(a->pa_outputfile);
187 }
188 
189 void
190 pmcstat_print_counters(struct pmcstat_args *a)
191 {
192 	int extra_width;
193 	struct pmcstat_ev *ev;
194 	pmc_value_t value;
195 
196 	extra_width = sizeof(PRINT_HEADER_PREFIX) - 1;
197 
198 	STAILQ_FOREACH(ev, &a->pa_head, ev_next) {
199 
200 		/* skip sampling mode counters */
201 		if (PMC_IS_SAMPLING_MODE(ev->ev_mode))
202 			continue;
203 
204 		if (pmc_read(ev->ev_pmcid, &value) < 0)
205 			err(EX_OSERR, "ERROR: Cannot read pmc "
206 			    "\"%s\"", ev->ev_name);
207 
208 		(void) fprintf(a->pa_outputfile, "%*ju ",
209 		    ev->ev_fieldwidth + extra_width, (uintmax_t)
210 		    ev->ev_cumulative ? value : (value - ev->ev_saved));
211 		if (ev->ev_cumulative == 0)
212 			ev->ev_saved = value;
213 		extra_width = 0;
214 	}
215 
216 	(void) fflush(a->pa_outputfile);
217 }
218 
219 /*
220  * Print output
221  */
222 
223 void
224 pmcstat_print_pmcs(struct pmcstat_args *a)
225 {
226 	static int linecount = 0;
227 
228 	if (++linecount > pmcstat_displayheight) {
229 		(void) fprintf(a->pa_outputfile, "\n");
230 		linecount = 1;
231 	}
232 
233 	if (linecount == 1)
234 		pmcstat_print_headers(a);
235 
236 	(void) fprintf(a->pa_outputfile, "\n");
237 	pmcstat_print_counters(a);
238 
239 	return;
240 }
241 
242 /*
243  * Do process profiling
244  *
245  * If a pid was specified, attach each allocated PMC to the target
246  * process.  Otherwise, fork a child and attach the PMCs to the child,
247  * and have the child exec() the target program.
248  */
249 
250 void
251 pmcstat_setup_process(struct pmcstat_args *a)
252 {
253 	char token;
254 	struct pmcstat_ev *ev;
255 	struct kevent kev;
256 
257 	if (a->pa_flags & FLAG_HAS_PID) {
258 
259 		STAILQ_FOREACH(ev, &args.pa_head, ev_next)
260 		    if (pmc_attach(ev->ev_pmcid, a->pa_pid) != 0)
261 			    err(EX_OSERR, "ERROR: cannot attach pmc \"%s\" to "
262 				"process %d", ev->ev_name, (int) a->pa_pid);
263 
264 	} else {
265 
266 		/*
267 		 * We need to fork a new process and startup the child
268 		 * using execvp().  Before doing the exec() the child
269 		 * process reads its pipe for a token so that the parent
270 		 * can finish doing its pmc_attach() calls.
271 		 */
272 
273 		if (pipe(pmcstat_pipefd) < 0)
274 			err(EX_OSERR, "ERROR: cannot create pipe");
275 
276 		switch (a->pa_pid = fork()) {
277 		case -1:
278 			err(EX_OSERR, "ERROR: cannot fork");
279 			/*NOTREACHED*/
280 
281 		case 0:		/* child */
282 
283 			/* wait for our parent to signal us */
284 			(void) close(pmcstat_pipefd[WRITEPIPEFD]);
285 			if (read(pmcstat_pipefd[READPIPEFD], &token, 1) < 0)
286 				err(EX_OSERR, "ERROR (child): cannot read "
287 				    "token");
288 			(void) close(pmcstat_pipefd[READPIPEFD]);
289 
290 			/* exec() the program requested */
291 			execvp(*args.pa_argv, args.pa_argv);
292 			err(EX_OSERR, "ERROR (child): execvp failed");
293 			/*NOTREACHED*/
294 
295 		default:	/* parent */
296 
297 			(void) close(pmcstat_pipefd[READPIPEFD]);
298 
299 			/* attach all our PMCs to the child */
300 			STAILQ_FOREACH(ev, &args.pa_head, ev_next)
301 			    if (PMC_IS_VIRTUAL_MODE(ev->ev_mode) &&
302 				pmc_attach(ev->ev_pmcid, a->pa_pid) != 0)
303 				    err(EX_OSERR, "ERROR: cannot attach pmc "
304 					"\"%s\" to process %d", ev->ev_name,
305 					(int) a->pa_pid);
306 
307 		}
308 	}
309 
310 	/* Ask to be notified via a kevent when the child exits */
311 	EV_SET(&kev, a->pa_pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, 0);
312 
313 	if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0)
314 		err(EX_OSERR, "ERROR: cannot monitor process %d",
315 		    a->pa_pid);
316 
317 	return;
318 }
319 
320 void
321 pmcstat_start_process(struct pmcstat_args *a)
322 {
323 
324 	/* nothing to do: target is already running */
325 	if (a->pa_flags & FLAG_HAS_PID)
326 		return;
327 
328 	/* write token to child to state that we are ready */
329 	if (write(pmcstat_pipefd[WRITEPIPEFD], "+", 1) != 1)
330 		err(EX_OSERR, "ERROR: write failed");
331 
332 	(void) close(pmcstat_pipefd[WRITEPIPEFD]);
333 }
334 
335 void
336 pmcstat_show_usage(void)
337 {
338 	errx(EX_USAGE,
339 	    "[options] [commandline]\n"
340 	    "\t Measure process and/or system performance using hardware\n"
341 	    "\t performance monitoring counters.\n"
342 	    "\t Options include:\n"
343 	    "\t -C\t\t toggle showing cumulative counts\n"
344 	    "\t -O file\t set sampling log file to \"file\"\n"
345 	    "\t -P spec\t allocate process-private sampling PMC\n"
346 	    "\t -S spec\t allocate system-wide sampling PMC\n"
347 	    "\t -c cpu\t\t set default cpu\n"
348 	    "\t -d\t\t toggle tracking descendants\n"
349 	    "\t -n rate\t set sampling rate\n"
350 	    "\t -o file\t send print output to \"file\"\n"
351 	    "\t -p spec\t allocate process-private counting PMC\n"
352 	    "\t -s spec\t allocate system-wide counting PMC\n"
353 	    "\t -t pid\t attach to running process with pid \"pid\"\n"
354 	    "\t -w secs\t set printing time interval"
355 	);
356 }
357 
358 /*
359  * Main
360  */
361 
362 int
363 main(int argc, char **argv)
364 {
365 	double interval;
366 	int option, npmc, ncpu;
367 	int c, current_cpu, current_sampling_count;
368 	int running;
369 	int do_descendants, use_cumulative_counts;
370 	pid_t pid;
371 	char *end;
372 	struct pmcstat_ev *ev;
373 	struct pmc_op_getpmcinfo *ppmci;
374 	struct sigaction sa;
375 	struct kevent kev;
376 	struct winsize ws;
377 
378 	current_cpu 		= 0;
379 	current_sampling_count  = DEFAULT_SAMPLE_COUNT;
380 	do_descendants          = 0;
381 	use_cumulative_counts   = 0;
382 	args.pa_flags		= 0;
383 	args.pa_pid		= (pid_t) -1;
384 	args.pa_logfile		= NULL;
385 	args.pa_outputfile	= stderr;
386 	args.pa_interval	= DEFAULT_WAIT_INTERVAL;
387 	STAILQ_INIT(&args.pa_head);
388 
389 	ev = NULL;
390 
391 	while ((option = getopt(argc, argv, "CO:P:S:c:dn:o:p:s:t:w:")) != -1)
392 		switch (option) {
393 		case 'C':	/* cumulative values */
394 			use_cumulative_counts = !use_cumulative_counts;
395 			break;
396 
397 		case 'c':	/* CPU */
398 			current_cpu = strtol(optarg, &end, 0);
399 			if (*end != '\0' || current_cpu < 0)
400 				errx(EX_USAGE,
401 				    "ERROR: Illegal CPU number \"%s\"",
402 				    optarg);
403 
404 			break;
405 
406 		case 'd':	/* toggle descendents */
407 			do_descendants = !do_descendants;
408 			break;
409 
410 		case 'p':	/* process virtual counting PMC */
411 		case 's':	/* system-wide counting PMC */
412 		case 'P':	/* process virtual sampling PMC */
413 		case 'S':	/* system-wide sampling PMC */
414 			if ((ev = malloc(sizeof(*ev))) == NULL)
415 				errx(EX_SOFTWARE, "ERROR: Out of memory");
416 
417 			switch (option) {
418 			case 'p': ev->ev_mode = PMC_MODE_TC; break;
419 			case 's': ev->ev_mode = PMC_MODE_SC; break;
420 			case 'P': ev->ev_mode = PMC_MODE_TS; break;
421 			case 'S': ev->ev_mode = PMC_MODE_SS; break;
422 			}
423 
424 			if (option == 'P' || option == 'p')
425 				args.pa_flags |= FLAG_USING_PROCESS_PMC;
426 
427 			if (option == 'P' || option == 'S')
428 				args.pa_flags |= FLAG_USING_SAMPLING;
429 
430 			if (option == 'p' || option == 's')
431 				args.pa_flags |= FLAG_USING_COUNTING;
432 
433 			ev->ev_spec  = strdup(optarg);
434 
435 			if (option == 'S' || option == 'P')
436 				ev->ev_count = current_sampling_count;
437 			else
438 				ev->ev_count = -1;
439 
440 			if (option == 'S' || option == 's')
441 				ev->ev_cpu = current_cpu;
442 			else
443 				ev->ev_cpu = PMC_CPU_ANY;
444 
445 			ev->ev_descendants = do_descendants;
446 			ev->ev_cumulative  = use_cumulative_counts;
447 
448 			ev->ev_saved = 0LL;
449 			ev->ev_pmcid = PMC_ID_INVALID;
450 
451 			/* extract event name */
452 			c = strcspn(optarg, ", \t");
453 			ev->ev_name = malloc(c + 1);
454 			(void) strncpy(ev->ev_name, optarg, c);
455 			*(ev->ev_name + c) = '\0';
456 
457 			STAILQ_INSERT_TAIL(&args.pa_head, ev, ev_next);
458 
459 			break;
460 
461 		case 'n':	/* sampling count */
462 			current_sampling_count = strtol(optarg, &end, 0);
463 			if (*end != '\0' || current_sampling_count <= 0)
464 				errx(EX_USAGE,
465 				    "ERROR: Illegal count value \"%s\"",
466 				    optarg);
467 			break;
468 
469 		case 'o':	/* outputfile */
470 			if (args.pa_outputfile != NULL)
471 				(void) fclose(args.pa_outputfile);
472 
473 			if ((args.pa_outputfile = fopen(optarg, "w")) == NULL)
474 				errx(EX_OSERR, "ERROR: cannot open \"%s\" for "
475 				    "writing", optarg);
476 
477 		case 'O':	/* sampling output */
478 			if (args.pa_logfile != NULL)
479 				(void) fclose(args.pa_logfile);
480 
481 			if ((args.pa_logfile = fopen(optarg, "w")) == NULL)
482 				errx(EX_OSERR, "ERROR: cannot open \"%s\" for "
483 				    "writing", optarg);
484 			break;
485 
486 		case 't':	/* target pid */
487 			pid = strtol(optarg, &end, 0);
488 			if (*end != '\0' || pid <= 0)
489 				errx(EX_USAGE, "ERROR: Illegal pid value "
490 				    "\"%s\"", optarg);
491 
492 			args.pa_flags |= FLAG_HAS_PID;
493 			args.pa_pid = pid;
494 
495 			break;
496 
497 		case 'w':	/* wait interval */
498 			interval = strtod(optarg, &end);
499 			if (*end != '\0' || interval <= 0)
500 				errx(EX_USAGE, "ERROR: Illegal wait interval "
501 				    "value \"%s\"", optarg);
502 			args.pa_flags |= FLAG_HAS_WAIT_INTERVAL;
503 			args.pa_interval = interval;
504 
505 			break;
506 
507 		case '?':
508 		default:
509 			pmcstat_show_usage();
510 			break;
511 
512 		}
513 
514 	args.pa_argc = (argc -= optind);
515 	args.pa_argv = (argv += optind);
516 
517 	if (argc)
518 		args.pa_flags |= FLAG_HAS_PROCESS;
519 
520 	/*
521 	 * Check invocation syntax.
522 	 */
523 
524 	if (STAILQ_EMPTY(&args.pa_head)) {
525 		warnx("ERROR: At least one PMC event must be specified");
526 		pmcstat_show_usage();
527 	}
528 
529 	if (argc == 0) {
530 		if (args.pa_pid == -1) {
531 			if (args.pa_flags & FLAG_USING_PROCESS_PMC)
532 				errx(EX_USAGE, "ERROR: the -P or -p options "
533 				    "require a target process");
534 		} else if ((args.pa_flags & FLAG_USING_PROCESS_PMC) == 0)
535 			errx(EX_USAGE,
536 			    "ERROR: option -t requires a process-mode pmc "
537 			    "specification");
538 	} else if (args.pa_pid != -1)
539 		errx(EX_USAGE,
540 		    "ERROR: option -t cannot be specified with a command "
541 		    "name");
542 
543 	if (pmc_init() < 0)
544 		err(EX_UNAVAILABLE,
545 		    "ERROR: Initialization of the pmc(3) library failed");
546 
547 	if ((ncpu = pmc_ncpu()) < 0)
548 		err(EX_OSERR, "ERROR: Cannot determine the number CPUs "
549 		    "on the system");
550 
551 	if ((npmc = pmc_npmc(0)) < 0) /* assume all CPUs are identical */
552 		err(EX_OSERR, "ERROR: Cannot determine the number of PMCs "
553 		    "on CPU %d", 0);
554 
555 	/*
556 	 * Allocate PMCs.
557 	 */
558 
559 	if (pmc_pmcinfo(0, &ppmci) < 0)
560 		err(EX_OSERR, "ERROR: cannot retrieve pmc information");
561 
562 	assert(ppmci != NULL);
563 
564 	STAILQ_FOREACH(ev, &args.pa_head, ev_next)
565 	    if (pmc_allocate(ev->ev_spec, ev->ev_mode,
566 		    (ev->ev_descendants ? PMC_F_DESCENDANTS : 0),
567 		    ev->ev_cpu, &ev->ev_pmcid) < 0)
568 		    err(EX_OSERR, "ERROR: Cannot allocate %s-mode pmc with "
569 			"specification \"%s\"",
570 			PMC_IS_SYSTEM_MODE(ev->ev_mode) ? "system" : "process",
571 			ev->ev_spec);
572 
573 	/* compute printout widths */
574 	STAILQ_FOREACH(ev, &args.pa_head, ev_next) {
575 		int pmc_width;
576 		int pmc_display_width;
577 		int pmc_header_width;
578 
579 		pmc_width = ppmci->pm_pmcs[ev->ev_pmcid].pm_width;
580 		pmc_header_width = strlen(ev->ev_name) + 2; /* prefix '%c|' */
581 		pmc_display_width = (int) floor(pmc_width / 3.32193) + 1;
582 
583 		if (pmc_header_width > pmc_display_width) {
584 			ev->ev_fieldskip = 0;
585 			ev->ev_fieldwidth = pmc_header_width;
586 		} else {
587 			ev->ev_fieldskip = pmc_display_width -
588 			    pmc_header_width;
589 			ev->ev_fieldwidth = pmc_display_width;
590 		}
591 	}
592 
593 	/* Allocate a kqueue */
594 	if ((pmcstat_kq = kqueue()) < 0)
595 		err(EX_OSERR, "ERROR: Cannot allocate kqueue");
596 
597 	/*
598 	 * If our output is being set to a terminal, register a handler
599 	 * for window size changes.
600 	 */
601 
602 	if (isatty(fileno(args.pa_outputfile))) {
603 
604 		if (ioctl(fileno(args.pa_outputfile), TIOCGWINSZ, &ws) < 0)
605 			err(EX_OSERR, "ERROR: Cannot determine window size");
606 
607 		pmcstat_displayheight = ws.ws_row - 1;
608 
609 		EV_SET(&kev, SIGWINCH, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
610 
611 		if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0)
612 			err(EX_OSERR, "ERROR: Cannot register kevent for "
613 			    "SIGWINCH");
614 	}
615 
616 	EV_SET(&kev, SIGINT, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
617 
618 	if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0)
619 		err(EX_OSERR, "ERROR: Cannot register kevent for SIGINT");
620 
621 	if (args.pa_flags & FLAG_USING_SAMPLING) {
622 
623 		/*
624 		 * configure log file
625 		 */
626 
627 		if (args.pa_logfile == NULL)
628 			if ((args.pa_logfile =
629 				fopen(DEFAULT_LOGFILE_NAME, "w")) == NULL)
630 				err(EX_CANTCREAT, "ERROR: Cannot open sampling "
631 				    "log file \"%s\"", DEFAULT_LOGFILE_NAME);
632 
633 		if (pmc_configure_logfile(fileno(args.pa_logfile)) < 0)
634 			err(EX_OSERR, "ERROR: Cannot configure sampling "
635 			    "log");
636 
637 		STAILQ_FOREACH(ev, &args.pa_head, ev_next)
638 		    if (PMC_IS_SAMPLING_MODE(ev->ev_mode) &&
639 			pmc_set(ev->ev_pmcid, ev->ev_count) < 0)
640 			    err(EX_OSERR, "ERROR: Cannot set sampling count "
641 				"for PMC \"%s\"", ev->ev_name);
642 	}
643 
644 	/* setup a timer for any counting mode PMCs */
645 	if (args.pa_flags & FLAG_USING_COUNTING) {
646 		EV_SET(&kev, 0, EVFILT_TIMER, EV_ADD, 0,
647 		    args.pa_interval * 1000, NULL);
648 
649 		if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0)
650 			err(EX_OSERR, "ERROR: Cannot register kevent for "
651 			    "timer");
652 	}
653 
654 	/* attach PMCs to the target process, starting it if specified */
655 	if (args.pa_flags & FLAG_HAS_PROCESS)
656 		pmcstat_setup_process(&args);
657 
658 	/* start the pmcs */
659 	pmcstat_start_pmcs(&args);
660 
661 	/* start the (commandline) process if needed */
662 	if (args.pa_flags & FLAG_HAS_PROCESS)
663 		pmcstat_start_process(&args);
664 
665 	/* Handle SIGINT using the kqueue loop */
666 	sa.sa_handler = SIG_IGN;
667 	sa.sa_flags   = 0;
668 	(void) sigemptyset(&sa.sa_mask);
669 
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