1dc3444cjkoshy/*-
27551d83pfg * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
37551d83pfg *
4ef261a0jkoshy * Copyright (c) 2003-2008, Joseph Koshy
52aef795jkoshy * Copyright (c) 2007 The FreeBSD Foundation
6dc3444cjkoshy * All rights reserved.
7dc3444cjkoshy *
82aef795jkoshy * Portions of this software were developed by A. Joseph Koshy under
92aef795jkoshy * sponsorship from the FreeBSD Foundation and Google, Inc.
102aef795jkoshy *
11dc3444cjkoshy * Redistribution and use in source and binary forms, with or without
12dc3444cjkoshy * modification, are permitted provided that the following conditions
13dc3444cjkoshy * are met:
14dc3444cjkoshy * 1. Redistributions of source code must retain the above copyright
15dc3444cjkoshy *    notice, this list of conditions and the following disclaimer.
16dc3444cjkoshy * 2. Redistributions in binary form must reproduce the above copyright
17dc3444cjkoshy *    notice, this list of conditions and the following disclaimer in the
18dc3444cjkoshy *    documentation and/or other materials provided with the distribution.
19dc3444cjkoshy *
20dc3444cjkoshy * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21dc3444cjkoshy * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22dc3444cjkoshy * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23dc3444cjkoshy * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
24dc3444cjkoshy * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25dc3444cjkoshy * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26dc3444cjkoshy * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27dc3444cjkoshy * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28dc3444cjkoshy * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29dc3444cjkoshy * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30dc3444cjkoshy * SUCH DAMAGE.
31dc3444cjkoshy */
32dc3444cjkoshy
33dc3444cjkoshy#include <sys/cdefs.h>
34dc3444cjkoshy__FBSDID("$FreeBSD$");
35dc3444cjkoshy
360dfc773jkoshy#include <sys/param.h>
3718728bbattilio#include <sys/cpuset.h>
3818728bbattilio#include <sys/event.h>
39dc3444cjkoshy#include <sys/queue.h>
403cade8djkoshy#include <sys/socket.h>
413cade8djkoshy#include <sys/stat.h>
42bf9c7e5jkoshy#include <sys/sysctl.h>
43dc3444cjkoshy#include <sys/time.h>
44dc3444cjkoshy#include <sys/ttycom.h>
450dfc773jkoshy#include <sys/user.h>
46dc3444cjkoshy#include <sys/wait.h>
47dc3444cjkoshy
48dc3444cjkoshy#include <assert.h>
492d898effabient#include <curses.h>
50dc3444cjkoshy#include <err.h>
51dc3444cjkoshy#include <errno.h>
52dc3444cjkoshy#include <fcntl.h>
530dfc773jkoshy#include <kvm.h>
5448e5e47jkoshy#include <libgen.h>
55dc3444cjkoshy#include <limits.h>
56dc3444cjkoshy#include <math.h>
57dc3444cjkoshy#include <pmc.h>
581d3209ajkoshy#include <pmclog.h>
590dfc773jkoshy#include <regex.h>
60dc3444cjkoshy#include <signal.h>
61dc3444cjkoshy#include <stdarg.h>
62dc3444cjkoshy#include <stdint.h>
633cade8djkoshy#include <stdio.h>
64dc3444cjkoshy#include <stdlib.h>
65dc3444cjkoshy#include <string.h>
66dc3444cjkoshy#include <sysexits.h>
67dc3444cjkoshy#include <unistd.h>
68dc3444cjkoshy
698f6b3abbr#include <libpmcstat.h>
708f6b3abbr
713cade8djkoshy#include "pmcstat.h"
723cade8djkoshy
731d3209ajkoshy/*
741d3209ajkoshy * A given invocation of pmcstat(8) can manage multiple PMCs of both
751d3209ajkoshy * the system-wide and per-process variety.  Each of these could be in
761d3209ajkoshy * 'counting mode' or in 'sampling mode'.
771d3209ajkoshy *
781d3209ajkoshy * For 'counting mode' PMCs, pmcstat(8) will periodically issue a
791d3209ajkoshy * pmc_read() at the configured time interval and print out the value
801d3209ajkoshy * of the requested PMCs.
811d3209ajkoshy *
821d3209ajkoshy * For 'sampling mode' PMCs it can log to a file for offline analysis,
831d3209ajkoshy * or can analyse sampling data "on the fly", either by converting
841d3209ajkoshy * samples to printed textual form or by creating gprof(1) compatible
851d3209ajkoshy * profiles, one per program executed.  When creating gprof(1)
861d3209ajkoshy * profiles it can optionally merge entries from multiple processes
871d3209ajkoshy * for a given executable into a single profile file.
88bf9c7e5jkoshy *
89bf9c7e5jkoshy * pmcstat(8) can also execute a command line and attach PMCs to the
90bf9c7e5jkoshy * resulting child process.  The protocol used is as follows:
91bf9c7e5jkoshy *
92bf9c7e5jkoshy * - parent creates a socketpair for two way communication and
93bf9c7e5jkoshy *   fork()s.
94bf9c7e5jkoshy * - subsequently:
95bf9c7e5jkoshy *
96bf9c7e5jkoshy *   /Parent/				/Child/
97bf9c7e5jkoshy *
98bf9c7e5jkoshy *   - Wait for childs token.
99bf9c7e5jkoshy *					- Sends token.
100bf9c7e5jkoshy *					- Awaits signal to start.
101bf9c7e5jkoshy *  - Attaches PMCs to the child's pid
102bf9c7e5jkoshy *    and starts them. Sets up
103bf9c7e5jkoshy *    monitoring for the child.
104bf9c7e5jkoshy *  - Signals child to start.
105415a3a9uqs *					- Receives signal, attempts exec().
106bf9c7e5jkoshy *
107bf9c7e5jkoshy * After this point normal processing can happen.
1081d3209ajkoshy */
1091d3209ajkoshy
1103cade8djkoshy/* Globals */
111dc3444cjkoshy
112af31e88edint		pmcstat_displayheight = DEFAULT_DISPLAY_HEIGHT;
113af31e88edint		pmcstat_displaywidth  = DEFAULT_DISPLAY_WIDTH;
114af31e88edstatic int	pmcstat_sockpair[NSOCKPAIRFD];
115af31e88edstatic int	pmcstat_kq;
116af31e88edstatic kvm_t	*pmcstat_kvm;
117af31e88edstatic struct kinfo_proc *pmcstat_plist;
1182d898effabientstruct pmcstat_args args;
119dc3444cjkoshy
12018728bbattiliostatic void
12118728bbattiliopmcstat_get_cpumask(const char *cpuspec, cpuset_t *cpumask)
12218728bbattilio{
12318728bbattilio	int cpu;
12418728bbattilio	const char *s;
12518728bbattilio	char *end;
12618728bbattilio
12718728bbattilio	CPU_ZERO(cpumask);
12818728bbattilio	s = cpuspec;
12918728bbattilio
13018728bbattilio	do {
13118728bbattilio		cpu = strtol(s, &end, 0);
13218728bbattilio		if (cpu < 0 || end == s)
133645c159obrien			errx(EX_USAGE,
134645c159obrien			    "ERROR: Illegal CPU specification \"%s\".",
135645c159obrien			    cpuspec);
13618728bbattilio		CPU_SET(cpu, cpumask);
13718728bbattilio		s = end + strspn(end, ", \t");
13818728bbattilio	} while (*s);
139f9f0a47jhb	assert(!CPU_EMPTY(cpumask));
14018728bbattilio}
14118728bbattilio
142bf9c7e5jkoshyvoid
1432d898effabientpmcstat_cleanup(void)
144dc3444cjkoshy{
145a904c8dstevek	struct pmcstat_ev *ev;
146dc3444cjkoshy
147dc3444cjkoshy	/* release allocated PMCs. */
148a904c8dstevek	STAILQ_FOREACH(ev, &args.pa_events, ev_next)
149a904c8dstevek		if (ev->ev_pmcid != PMC_ID_INVALID) {
150a904c8dstevek			if (pmc_stop(ev->ev_pmcid) < 0)
151a904c8dstevek				err(EX_OSERR,
152a904c8dstevek				    "ERROR: cannot stop pmc 0x%x \"%s\"",
153a904c8dstevek				    ev->ev_pmcid, ev->ev_name);
154a904c8dstevek			if (pmc_release(ev->ev_pmcid) < 0)
155a904c8dstevek				err(EX_OSERR,
156a904c8dstevek				    "ERROR: cannot release pmc 0x%x \"%s\"",
157a904c8dstevek				    ev->ev_pmcid, ev->ev_name);
158a904c8dstevek		}
1591d3209ajkoshy
16093ae007jkoshy	/* de-configure the log file if present. */
1612d898effabient	if (args.pa_flags & (FLAG_HAS_PIPE | FLAG_HAS_OUTPUT_LOGFILE))
16293ae007jkoshy		(void) pmc_configure_logfile(-1);
16393ae007jkoshy
1642d898effabient	if (args.pa_logparser) {
1652d898effabient		pmclog_close(args.pa_logparser);
1662d898effabient		args.pa_logparser = NULL;
1671d3209ajkoshy	}
1683cade8djkoshy
1698f6b3abbr	pmcstat_log_shutdown_logging();
170bf9c7e5jkoshy}
171bf9c7e5jkoshy
1720dfc773jkoshyvoid
1732d898effabientpmcstat_find_targets(const char *spec)
1740dfc773jkoshy{
1750dfc773jkoshy	int n, nproc, pid, rv;
1760dfc773jkoshy	struct pmcstat_target *pt;
1770dfc773jkoshy	char errbuf[_POSIX2_LINE_MAX], *end;
1780dfc773jkoshy	static struct kinfo_proc *kp;
1790dfc773jkoshy	regex_t reg;
1800dfc773jkoshy	regmatch_t regmatch;
1810dfc773jkoshy
1820dfc773jkoshy	/* First check if we've been given a process id. */
1830dfc773jkoshy      	pid = strtol(spec, &end, 0);
1840dfc773jkoshy	if (end != spec && pid >= 0) {
1850dfc773jkoshy		if ((pt = malloc(sizeof(*pt))) == NULL)
1860dfc773jkoshy			goto outofmemory;
1870dfc773jkoshy		pt->pt_pid = pid;
1882d898effabient		SLIST_INSERT_HEAD(&args.pa_targets, pt, pt_next);
1890dfc773jkoshy		return;
1900dfc773jkoshy	}
1910dfc773jkoshy
1920dfc773jkoshy	/* Otherwise treat arg as a regular expression naming processes. */
1930dfc773jkoshy	if (pmcstat_kvm == NULL) {
1940dfc773jkoshy		if ((pmcstat_kvm = kvm_openfiles(NULL, "/dev/null", NULL, 0,
1950dfc773jkoshy		    errbuf)) == NULL)
1960dfc773jkoshy			err(EX_OSERR, "ERROR: Cannot open kernel \"%s\"",
1970dfc773jkoshy			    errbuf);
1980dfc773jkoshy		if ((pmcstat_plist = kvm_getprocs(pmcstat_kvm, KERN_PROC_PROC,
1990dfc773jkoshy		    0, &nproc)) == NULL)
2000dfc773jkoshy			err(EX_OSERR, "ERROR: Cannot get process list: %s",
2010dfc773jkoshy			    kvm_geterr(pmcstat_kvm));
2020ecf60cfabient	} else
2030ecf60cfabient		nproc = 0;
2040dfc773jkoshy
2050dfc773jkoshy	if ((rv = regcomp(&reg, spec, REG_EXTENDED|REG_NOSUB)) != 0) {
2060dfc773jkoshy		regerror(rv, &reg, errbuf, sizeof(errbuf));
2070dfc773jkoshy		err(EX_DATAERR, "ERROR: Failed to compile regex \"%s\": %s",
2080dfc773jkoshy		    spec, errbuf);
2090dfc773jkoshy	}
2100dfc773jkoshy
2110dfc773jkoshy	for (n = 0, kp = pmcstat_plist; n < nproc; n++, kp++) {
2120dfc773jkoshy		if ((rv = regexec(&reg, kp->ki_comm, 1, &regmatch, 0)) == 0) {
2130dfc773jkoshy			if ((pt = malloc(sizeof(*pt))) == NULL)
2140dfc773jkoshy				goto outofmemory;
2150dfc773jkoshy			pt->pt_pid = kp->ki_pid;
2162d898effabient			SLIST_INSERT_HEAD(&args.pa_targets, pt, pt_next);
2170dfc773jkoshy		} else if (rv != REG_NOMATCH) {
2180dfc773jkoshy			regerror(rv, &reg, errbuf, sizeof(errbuf));
2190dfc773jkoshy			errx(EX_SOFTWARE, "ERROR: Regex evalation failed: %s",
2200dfc773jkoshy			    errbuf);
2210dfc773jkoshy		}
2220dfc773jkoshy	}
2230dfc773jkoshy
2240dfc773jkoshy	regfree(&reg);
2250dfc773jkoshy
2260dfc773jkoshy	return;
2270dfc773jkoshy
2280dfc773jkoshy outofmemory:
2290dfc773jkoshy	errx(EX_SOFTWARE, "Out of memory.");
2300dfc773jkoshy	/*NOTREACHED*/
2310dfc773jkoshy}
2320dfc773jkoshy
233bf9c7e5jkoshyvoid
2342d898effabientpmcstat_kill_process(void)
2350dfc773jkoshy{
2360dfc773jkoshy	struct pmcstat_target *pt;
2370dfc773jkoshy
2382d898effabient	assert(args.pa_flags & FLAG_HAS_COMMANDLINE);
2390dfc773jkoshy
2400dfc773jkoshy	/*
2410dfc773jkoshy	 * If a command line was specified, it would be the very first
2420dfc773jkoshy	 * in the list, before any other processes specified by -t.
2430dfc773jkoshy	 */
2442d898effabient	pt = SLIST_FIRST(&args.pa_targets);
2450dfc773jkoshy	assert(pt != NULL);
2460dfc773jkoshy
2470dfc773jkoshy	if (kill(pt->pt_pid, SIGINT) != 0)
2480dfc773jkoshy		err(EX_OSERR, "ERROR: cannot signal child process");
2490dfc773jkoshy}
2500dfc773jkoshy
2510dfc773jkoshyvoid
2522d898effabientpmcstat_start_pmcs(void)
253dc3444cjkoshy{
254dc3444cjkoshy	struct pmcstat_ev *ev;
255dc3444cjkoshy
2560dfc773jkoshy	STAILQ_FOREACH(ev, &args.pa_events, ev_next) {
257dc3444cjkoshy
258dc3444cjkoshy	    assert(ev->ev_pmcid != PMC_ID_INVALID);
259dc3444cjkoshy
260dc3444cjkoshy	    if (pmc_start(ev->ev_pmcid) < 0) {
2611d3209ajkoshy	        warn("ERROR: Cannot start pmc 0x%x \"%s\"",
262dc3444cjkoshy		    ev->ev_pmcid, ev->ev_name);
2632d898effabient		pmcstat_cleanup();
2641d3209ajkoshy		exit(EX_OSERR);
265dc3444cjkoshy	    }
266dc3444cjkoshy	}
267dc3444cjkoshy}
268dc3444cjkoshy
269dc3444cjkoshyvoid
2702d898effabientpmcstat_print_headers(void)
271dc3444cjkoshy{
272dc3444cjkoshy	struct pmcstat_ev *ev;
273bf9c7e5jkoshy	int c, w;
274dc3444cjkoshy
2752d898effabient	(void) fprintf(args.pa_printfile, PRINT_HEADER_PREFIX);
276dc3444cjkoshy
2772d898effabient	STAILQ_FOREACH(ev, &args.pa_events, ev_next) {
278dc3444cjkoshy		if (PMC_IS_SAMPLING_MODE(ev->ev_mode))
279dc3444cjkoshy			continue;
280dc3444cjkoshy
281dc3444cjkoshy		c = PMC_IS_SYSTEM_MODE(ev->ev_mode) ? 's' : 'p';
282dc3444cjkoshy
283bf9c7e5jkoshy		if (ev->ev_fieldskip != 0)
2842d898effabient			(void) fprintf(args.pa_printfile, "%*s",
285bf9c7e5jkoshy			    ev->ev_fieldskip, "");
286bf9c7e5jkoshy		w = ev->ev_fieldwidth - ev->ev_fieldskip - 2;
287bf9c7e5jkoshy
288bf9c7e5jkoshy		if (c == 's')
2892d898effabient			(void) fprintf(args.pa_printfile, "s/%02d/%-*s ",
290bf9c7e5jkoshy			    ev->ev_cpu, w-3, ev->ev_name);
291bf9c7e5jkoshy		else
2922d898effabient			(void) fprintf(args.pa_printfile, "p/%*s ", w,
293dc3444cjkoshy			    ev->ev_name);
294dc3444cjkoshy	}
295dc3444cjkoshy
2962d898effabient	(void) fflush(args.pa_printfile);
297dc3444cjkoshy}
298dc3444cjkoshy
299dc3444cjkoshyvoid
3002d898effabientpmcstat_print_counters(void)
301dc3444cjkoshy{
302dc3444cjkoshy	int extra_width;
303dc3444cjkoshy	struct pmcstat_ev *ev;
304dc3444cjkoshy	pmc_value_t value;
305dc3444cjkoshy
306dc3444cjkoshy	extra_width = sizeof(PRINT_HEADER_PREFIX) - 1;
307dc3444cjkoshy
3082d898effabient	STAILQ_FOREACH(ev, &args.pa_events, ev_next) {
309dc3444cjkoshy
310dc3444cjkoshy		/* skip sampling mode counters */
311dc3444cjkoshy		if (PMC_IS_SAMPLING_MODE(ev->ev_mode))
312dc3444cjkoshy			continue;
313dc3444cjkoshy
314dc3444cjkoshy		if (pmc_read(ev->ev_pmcid, &value) < 0)
315645c159obrien			err(EX_OSERR, "ERROR: Cannot read pmc \"%s\"",
316645c159obrien			    ev->ev_name);
317dc3444cjkoshy
3182d898effabient		(void) fprintf(args.pa_printfile, "%*ju ",
3193cade8djkoshy		    ev->ev_fieldwidth + extra_width,
3203cade8djkoshy		    (uintmax_t) ev->ev_cumulative ? value :
3213cade8djkoshy		    (value - ev->ev_saved));
3223cade8djkoshy
323dc3444cjkoshy		if (ev->ev_cumulative == 0)
324dc3444cjkoshy			ev->ev_saved = value;
325dc3444cjkoshy		extra_width = 0;
326dc3444cjkoshy	}
327dc3444cjkoshy
3282d898effabient	(void) fflush(args.pa_printfile);
329dc3444cjkoshy}
330dc3444cjkoshy
331dc3444cjkoshy/*
332dc3444cjkoshy * Print output
333dc3444cjkoshy */
334dc3444cjkoshy
335dc3444cjkoshyvoid
3362d898effabientpmcstat_print_pmcs(void)
337dc3444cjkoshy{
338dc3444cjkoshy	static int linecount = 0;
339dc3444cjkoshy
3403cade8djkoshy	/* check if we need to print a header line */
341dc3444cjkoshy	if (++linecount > pmcstat_displayheight) {
3422d898effabient		(void) fprintf(args.pa_printfile, "\n");
343dc3444cjkoshy		linecount = 1;
344dc3444cjkoshy	}
345dc3444cjkoshy	if (linecount == 1)
3462d898effabient		pmcstat_print_headers();
3472d898effabient	(void) fprintf(args.pa_printfile, "\n");
348dc3444cjkoshy
3492d898effabient	pmcstat_print_counters();
350dc3444cjkoshy
351dc3444cjkoshy	return;
352dc3444cjkoshy}
353dc3444cjkoshy
354dc3444cjkoshyvoid
355dc3444cjkoshypmcstat_show_usage(void)
356dc3444cjkoshy{
357dc3444cjkoshy	errx(EX_USAGE,
358dc3444cjkoshy	    "[options] [commandline]\n"
359dc3444cjkoshy	    "\t Measure process and/or system performance using hardware\n"
360dc3444cjkoshy	    "\t performance monitoring counters.\n"
361dc3444cjkoshy	    "\t Options include:\n"
3621d3209ajkoshy	    "\t -C\t\t (toggle) show cumulative counts\n"
3631d3209ajkoshy	    "\t -D path\t create profiles in directory \"path\"\n"
3641d3209ajkoshy	    "\t -E\t\t (toggle) show counts at process exit\n"
3652d898effabient	    "\t -F file\t write a system-wide callgraph (Kcachegrind format)"
3662d898effabient		" to \"file\"\n"
3672aef795jkoshy	    "\t -G file\t write a system-wide callgraph to \"file\"\n"
3680224710mmacy	    "\t -I\t\t don't resolve leaf function name, show address instead\n"
3690224710mmacy	    "\t -L\t\t list all counters available on this host\n"
37048e5e47jkoshy	    "\t -M file\t print executable/gmon file map to \"file\"\n"
3712aef795jkoshy	    "\t -N\t\t (toggle) capture callchains\n"
3721d3209ajkoshy	    "\t -O file\t send log output to \"file\"\n"
3731d3209ajkoshy	    "\t -P spec\t allocate a process-private sampling PMC\n"
3741d3209ajkoshy	    "\t -R file\t read events from \"file\"\n"
3751d3209ajkoshy	    "\t -S spec\t allocate a system-wide sampling PMC\n"
3762d898effabient	    "\t -T\t\t start in top mode\n"
37773041f2mmacy	    "\t -U \t\n merged user kernel stack capture\n"
3781d3209ajkoshy	    "\t -W\t\t (toggle) show counts per context switch\n"
379508d93ascottl	    "\t -a file\t print sampled PCs and callgraph to \"file\"\n"
380bf9c7e5jkoshy	    "\t -c cpu-list\t set cpus for subsequent system-wide PMCs\n"
3811d3209ajkoshy	    "\t -d\t\t (toggle) track descendants\n"
382e193259jtl	    "\t -e\t\t use wide history counter for gprof(1) output\n"
3832d898effabient	    "\t -f spec\t pass \"spec\" to as plugin option\n"
3841d3209ajkoshy	    "\t -g\t\t produce gprof(1) compatible profiles\n"
3850224710mmacy	    "\t -i lwp\t\t filter on thread id \"lwp\" in post-processing\n"
386822ecb3ru	    "\t -k dir\t\t set the path to the kernel\n"
387f767e17gnn	    "\t -l secs\t set duration time\n"
3881fa84dfjimharris	    "\t -m file\t print sampled PCs to \"file\"\n"
389dc3444cjkoshy	    "\t -n rate\t set sampling rate\n"
390dc3444cjkoshy	    "\t -o file\t send print output to \"file\"\n"
3911d3209ajkoshy	    "\t -p spec\t allocate a process-private counting PMC\n"
39248e5e47jkoshy	    "\t -q\t\t suppress verbosity\n"
39348e5e47jkoshy	    "\t -r fsroot\t specify FS root directory\n"
3941d3209ajkoshy	    "\t -s spec\t allocate a system-wide counting PMC\n"
39599d8d99jkoshy	    "\t -t process-spec attach to running processes matching "
39699d8d99jkoshy		"\"process-spec\"\n"
3970224710mmacy	    "\t -u spec \t provide short description of counters matching spec\n"
39848e5e47jkoshy	    "\t -v\t\t increase verbosity\n"
3992aef795jkoshy	    "\t -w secs\t set printing time interval\n"
4002aef795jkoshy	    "\t -z depth\t limit callchain display depth"
401dc3444cjkoshy	);
402dc3444cjkoshy}
403dc3444cjkoshy
404dc3444cjkoshy/*
4052d898effabient * At exit handler for top mode
4062d898effabient */
4072d898effabient
4082d898effabientvoid
4092d898effabientpmcstat_topexit(void)
4102d898effabient{
4112d898effabient	if (!args.pa_toptty)
4122d898effabient		return;
4132d898effabient
4142d898effabient	/*
4152d898effabient	 * Shutdown ncurses.
4162d898effabient	 */
4172d898effabient	clrtoeol();
4182d898effabient	refresh();
4192d898effabient	endwin();
4202d898effabient}
4212d898effabient
4222d898effabient/*
423dc3444cjkoshy * Main
424dc3444cjkoshy */
425dc3444cjkoshy
426dc3444cjkoshyint
427dc3444cjkoshymain(int argc, char **argv)
428dc3444cjkoshy{
429f9f0a47jhb	cpuset_t cpumask, rootmask;
430dc3444cjkoshy	double interval;
431f767e17gnn	double duration;
432f9f0a47jhb	int option, npmc;
433e4c45b8mmacy	int c, check_driver_stats;
4342aef795jkoshy	int do_callchain, do_descendants, do_logproccsw, do_logprocexit;
43573041f2mmacy	int do_print, do_read, do_listcounters, do_descr;
43673041f2mmacy	int do_userspace;
4375764574jhb	size_t len;
4382aef795jkoshy	int graphdepth;
43994897cafabient	int pipefd[2], rfd;
4401d3209ajkoshy	int use_cumulative_counts;
4412d898effabient	short cf, cb;
442e4c45b8mmacy	uint64_t current_sampling_count;
4430224710mmacy	char *end, *tmp, *event;
4442aef795jkoshy	const char *errmsg, *graphfilename;
4451d3209ajkoshy	enum pmcstat_state runstate;
446f2b9dccjkoshy	struct pmc_driverstats ds_start, ds_end;
447dc3444cjkoshy	struct pmcstat_ev *ev;
448dc3444cjkoshy	struct sigaction sa;
449dc3444cjkoshy	struct kevent kev;
450dc3444cjkoshy	struct winsize ws;
4513cade8djkoshy	struct stat sb;
45248e5e47jkoshy	char buffer[PATH_MAX];
453dc3444cjkoshy
454f2b9dccjkoshy	check_driver_stats      = 0;
455da844acmmacy	current_sampling_count  = 0;
4562aef795jkoshy	do_callchain		= 1;
4570224710mmacy	do_descr                = 0;
458dc3444cjkoshy	do_descendants          = 0;
45973041f2mmacy	do_userspace            = 0;
4601d3209ajkoshy	do_logproccsw           = 0;
4611d3209ajkoshy	do_logprocexit          = 0;
4620224710mmacy	do_listcounters         = 0;
463dc3444cjkoshy	use_cumulative_counts   = 0;
4642aef795jkoshy	graphfilename		= "-";
4651d3209ajkoshy	args.pa_required	= 0;
466dc3444cjkoshy	args.pa_flags		= 0;
46748e5e47jkoshy	args.pa_verbosity	= 1;
4683cade8djkoshy	args.pa_logfd		= -1;
46948e5e47jkoshy	args.pa_fsroot		= "";
4703cade8djkoshy	args.pa_samplesdir	= ".";
4713cade8djkoshy	args.pa_printfile	= stderr;
4722aef795jkoshy	args.pa_graphdepth	= DEFAULT_CALLGRAPH_DEPTH;
4732aef795jkoshy	args.pa_graphfile	= NULL;
474dc3444cjkoshy	args.pa_interval	= DEFAULT_WAIT_INTERVAL;
47548e5e47jkoshy	args.pa_mapfilename	= NULL;
4762aef795jkoshy	args.pa_inputpath	= NULL;
4772aef795jkoshy	args.pa_outputpath	= NULL;
4782d898effabient	args.pa_pplugin		= PMCSTAT_PL_NONE;
4792d898effabient	args.pa_plugin		= PMCSTAT_PL_NONE;
4802d898effabient	args.pa_ctdumpinstr	= 1;
4812d898effabient	args.pa_topmode		= PMCSTAT_TOP_DELTA;
4822d898effabient	args.pa_toptty		= 0;
4832d898effabient	args.pa_topcolor	= 0;
4842d898effabient	args.pa_mergepmc	= 0;
485f767e17gnn	args.pa_duration	= 0.0;
4860dfc773jkoshy	STAILQ_INIT(&args.pa_events);
4870dfc773jkoshy	SLIST_INIT(&args.pa_targets);
488d308f96jkoshy	bzero(&ds_start, sizeof(ds_start));
489d308f96jkoshy	bzero(&ds_end, sizeof(ds_end));
490dc3444cjkoshy	ev = NULL;
4913fe632ammacy	event = NULL;
49218728bbattilio	CPU_ZERO(&cpumask);
493dc3444cjkoshy
4945764574jhb	/* Default to using the running system kernel. */
4955764574jhb	len = 0;
4965764574jhb	if (sysctlbyname("kern.bootfile", NULL, &len, NULL, 0) == -1)
4975764574jhb		err(EX_OSERR, "ERROR: Cannot determine path of running kernel");
498a904c8dstevek	args.pa_kernel = malloc(len);
499a904c8dstevek	if (args.pa_kernel == NULL)
500a904c8dstevek		errx(EX_SOFTWARE, "ERROR: Out of memory.");
5015764574jhb	if (sysctlbyname("kern.bootfile", args.pa_kernel, &len, NULL, 0) == -1)
5025764574jhb		err(EX_OSERR, "ERROR: Cannot determine path of running kernel");
5035764574jhb
5042aef795jkoshy	/*
505f9f0a47jhb	 * The initial CPU mask specifies the root mask of this process
506f9f0a47jhb	 * which is usually all CPUs in the system.
5072aef795jkoshy	 */
508f9f0a47jhb	if (cpuset_getaffinity(CPU_LEVEL_ROOT, CPU_WHICH_PID, -1,
509f9f0a47jhb	    sizeof(rootmask), &rootmask) == -1)
510f9f0a47jhb		err(EX_OSERR, "ERROR: Cannot determine the root set of CPUs");
511f9f0a47jhb	CPU_COPY(&rootmask, &cpumask);
512bf9c7e5jkoshy
51348e5e47jkoshy	while ((option = getopt(argc, argv,
51473041f2mmacy	    "CD:EF:G:ILM:NO:P:R:S:TUWZa:c:def:gi:k:l:m:n:o:p:qr:s:t:u:vw:z:")) != -1)
515dc3444cjkoshy		switch (option) {
516c4a6baeadrian		case 'a':	/* Annotate + callgraph */
517c4a6baeadrian			args.pa_flags |= FLAG_DO_ANNOTATE;
518c4a6baeadrian			args.pa_plugin = PMCSTAT_PL_ANNOTATE_CG;
519c4a6baeadrian			graphfilename  = optarg;
520c4a6baeadrian			break;
521c4a6baeadrian
522dc3444cjkoshy		case 'C':	/* cumulative values */
523dc3444cjkoshy			use_cumulative_counts = !use_cumulative_counts;
5241d3209ajkoshy			args.pa_required |= FLAG_HAS_COUNTING_PMCS;
525dc3444cjkoshy			break;
526dc3444cjkoshy
527dc3444cjkoshy		case 'c':	/* CPU */
528f9f0a47jhb			if (optarg[0] == '*' && optarg[1] == '\0')
529f9f0a47jhb				CPU_COPY(&rootmask, &cpumask);
530f9f0a47jhb			else
53118728bbattilio				pmcstat_get_cpumask(optarg, &cpumask);
532bf9c7e5jkoshy
5333327e87fabient			args.pa_flags	 |= FLAGS_HAS_CPUMASK;
5341d3209ajkoshy			args.pa_required |= FLAG_HAS_SYSTEM_PMCS;
535dc3444cjkoshy			break;
536dc3444cjkoshy
5373cade8djkoshy		case 'D':
5383cade8djkoshy			if (stat(optarg, &sb) < 0)
5393cade8djkoshy				err(EX_OSERR, "ERROR: Cannot stat \"%s\"",
5403cade8djkoshy				    optarg);
5413cade8djkoshy			if (!S_ISDIR(sb.st_mode))
542645c159obrien				errx(EX_USAGE,
543645c159obrien				    "ERROR: \"%s\" is not a directory.",
544645c159obrien				    optarg);
5453cade8djkoshy			args.pa_samplesdir = optarg;
5463cade8djkoshy			args.pa_flags     |= FLAG_HAS_SAMPLESDIR;
5473cade8djkoshy			args.pa_required  |= FLAG_DO_GPROF;
5483cade8djkoshy			break;
5493cade8djkoshy
550dc3444cjkoshy		case 'd':	/* toggle descendents */
551dc3444cjkoshy			do_descendants = !do_descendants;
5521d3209ajkoshy			args.pa_required |= FLAG_HAS_PROCESS_PMCS;
5531d3209ajkoshy			break;
5541d3209ajkoshy
5550224710mmacy		case 'E':	/* log process exit */
5560224710mmacy			do_logprocexit = !do_logprocexit;
5570224710mmacy			args.pa_required |= (FLAG_HAS_PROCESS_PMCS |
5580224710mmacy			    FLAG_HAS_COUNTING_PMCS | FLAG_HAS_OUTPUT_LOGFILE);
5590224710mmacy			break;
5600224710mmacy
561e193259jtl		case 'e':	/* wide gprof metrics */
562e193259jtl			args.pa_flags |= FLAG_DO_WIDE_GPROF_HC;
563e193259jtl			break;
564e193259jtl
5652d898effabient		case 'F':	/* produce a system-wide calltree */
5662d898effabient			args.pa_flags |= FLAG_DO_CALLGRAPHS;
5672d898effabient			args.pa_plugin = PMCSTAT_PL_CALLTREE;
5682d898effabient			graphfilename = optarg;
5692d898effabient			break;
5702d898effabient
5712d898effabient		case 'f':	/* plugins options */
5722d898effabient			if (args.pa_plugin == PMCSTAT_PL_NONE)
5732d898effabient				err(EX_USAGE, "ERROR: Need -g/-G/-m/-T.");
5742d898effabient			pmcstat_pluginconfigure_log(optarg);
5752d898effabient			break;
5762d898effabient
5772aef795jkoshy		case 'G':	/* produce a system-wide callgraph */
5782aef795jkoshy			args.pa_flags |= FLAG_DO_CALLGRAPHS;
5792d898effabient			args.pa_plugin = PMCSTAT_PL_CALLGRAPH;
5802aef795jkoshy			graphfilename = optarg;
5812aef795jkoshy			break;
5822aef795jkoshy
5831d3209ajkoshy		case 'g':	/* produce gprof compatible profiles */
5841d3209ajkoshy			args.pa_flags |= FLAG_DO_GPROF;
5852d898effabient			args.pa_pplugin = PMCSTAT_PL_CALLGRAPH;
5862d898effabient			args.pa_plugin	= PMCSTAT_PL_GPROF;
5871d3209ajkoshy			break;
5881d3209ajkoshy
5891f9699ammacy		case 'I':
5901f9699ammacy			args.pa_flags |= FLAG_SKIP_TOP_FN_RES;
5911f9699ammacy			break;
5920224710mmacy		case 'i':
5930224710mmacy			args.pa_flags |= FLAG_FILTER_THREAD_ID;
5940224710mmacy			args.pa_tid = strtol(optarg, &end, 0);
5950224710mmacy			break;
5960224710mmacy
5973cade8djkoshy		case 'k':	/* pathname to the kernel */
59848e5e47jkoshy			free(args.pa_kernel);
59948e5e47jkoshy			args.pa_kernel = strdup(optarg);
600a904c8dstevek			if (args.pa_kernel == NULL)
601a904c8dstevek				errx(EX_SOFTWARE, "ERROR: Out of memory");
6022aef795jkoshy			args.pa_required |= FLAG_DO_ANALYSIS;
6033cade8djkoshy			args.pa_flags    |= FLAG_HAS_KERNELPATH;
6041d3209ajkoshy			break;
6051d3209ajkoshy
606834f2e0mmacy		case 'L':
6070224710mmacy			do_listcounters = 1;
608834f2e0mmacy			break;
6090224710mmacy
610f767e17gnn		case 'l':	/* time duration in seconds */
611f767e17gnn			duration = strtod(optarg, &end);
612f767e17gnn			if (*end != '\0' || duration <= 0)
613f767e17gnn				errx(EX_USAGE, "ERROR: Illegal duration time "
614f767e17gnn				    "value \"%s\".", optarg);
615f767e17gnn			args.pa_flags |= FLAG_HAS_DURATION;
616f767e17gnn			args.pa_duration = duration;
617f767e17gnn			break;
618f767e17gnn
619eff98a0attilio		case 'm':
6202d898effabient			args.pa_flags |= FLAG_DO_ANNOTATE;
6212d898effabient			args.pa_plugin = PMCSTAT_PL_ANNOTATE;
6222d898effabient			graphfilename  = optarg;
623eff98a0attilio			break;
624eff98a0attilio
62548e5e47jkoshy		case 'M':	/* mapfile */
62648e5e47jkoshy			args.pa_mapfilename = optarg;
62748e5e47jkoshy			break;
62848e5e47jkoshy
6292aef795jkoshy		case 'N':
6302aef795jkoshy			do_callchain = !do_callchain;
6312aef795jkoshy			args.pa_required |= FLAG_HAS_SAMPLING_PMCS;
6322aef795jkoshy			break;
6332aef795jkoshy
634dc3444cjkoshy		case 'p':	/* process virtual counting PMC */
635dc3444cjkoshy		case 's':	/* system-wide counting PMC */
636dc3444cjkoshy		case 'P':	/* process virtual sampling PMC */
637dc3444cjkoshy		case 'S':	/* system-wide sampling PMC */
638dc3444cjkoshy			if ((ev = malloc(sizeof(*ev))) == NULL)
6391d3209ajkoshy				errx(EX_SOFTWARE, "ERROR: Out of memory.");
640dc3444cjkoshy
641dc3444cjkoshy			switch (option) {
642dc3444cjkoshy			case 'p': ev->ev_mode = PMC_MODE_TC; break;
643dc3444cjkoshy			case 's': ev->ev_mode = PMC_MODE_SC; break;
644dc3444cjkoshy			case 'P': ev->ev_mode = PMC_MODE_TS; break;
645dc3444cjkoshy			case 'S': ev->ev_mode = PMC_MODE_SS; break;
646dc3444cjkoshy			}
647dc3444cjkoshy
6481d3209ajkoshy			if (option == 'P' || option == 'p') {
6491d3209ajkoshy				args.pa_flags |= FLAG_HAS_PROCESS_PMCS;
6503cade8djkoshy				args.pa_required |= (FLAG_HAS_COMMANDLINE |
6510dfc773jkoshy				    FLAG_HAS_TARGET);
6521d3209ajkoshy			}
653dc3444cjkoshy
6541d3209ajkoshy			if (option == 'P' || option == 'S') {
6551d3209ajkoshy				args.pa_flags |= FLAG_HAS_SAMPLING_PMCS;
6563cade8djkoshy				args.pa_required |= (FLAG_HAS_PIPE |
6573cade8djkoshy				    FLAG_HAS_OUTPUT_LOGFILE);
6581d3209ajkoshy			}
659dc3444cjkoshy
660dc3444cjkoshy			if (option == 'p' || option == 's')
6611d3209ajkoshy				args.pa_flags |= FLAG_HAS_COUNTING_PMCS;
6621d3209ajkoshy
6631d3209ajkoshy			if (option == 's' || option == 'S')
6641d3209ajkoshy				args.pa_flags |= FLAG_HAS_SYSTEM_PMCS;
665dc3444cjkoshy
6668f6b3abbr			ev->ev_spec = strdup(optarg);
667