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