diff options
Diffstat (limited to 'usr.sbin')
-rw-r--r-- | usr.sbin/Makefile | 2 | ||||
-rw-r--r-- | usr.sbin/pmccontrol/Makefile | 17 | ||||
-rw-r--r-- | usr.sbin/pmccontrol/pmccontrol.8 | 132 | ||||
-rw-r--r-- | usr.sbin/pmccontrol/pmccontrol.c | 476 | ||||
-rw-r--r-- | usr.sbin/pmcstat/Makefile | 17 | ||||
-rw-r--r-- | usr.sbin/pmcstat/pmcstat.8 | 196 | ||||
-rw-r--r-- | usr.sbin/pmcstat/pmcstat.c | 728 |
7 files changed, 1568 insertions, 0 deletions
diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile index e8b528b..071435e 100644 --- a/usr.sbin/Makefile +++ b/usr.sbin/Makefile @@ -112,6 +112,8 @@ SUBDIR= ac \ ${_pcvt} \ periodic \ pkg_install \ + pmccontrol \ + pmcstat \ ${_pnpinfo} \ powerd \ ppp \ diff --git a/usr.sbin/pmccontrol/Makefile b/usr.sbin/pmccontrol/Makefile new file mode 100644 index 0000000..851b1c7 --- /dev/null +++ b/usr.sbin/pmccontrol/Makefile @@ -0,0 +1,17 @@ +# +# $FreeBSD$ +# + +PROG= pmccontrol +MAN= pmccontrol.8 + +DPADD= ${LIBPMC} +LDADD= -lpmc + +WARNS= 6 + +CFLAGS+= -I${.CURDIR}/../../sys -I${.CURDIR}/../../lib/libpmc + +SRCS= pmccontrol.c + +.include <bsd.prog.mk> diff --git a/usr.sbin/pmccontrol/pmccontrol.8 b/usr.sbin/pmccontrol/pmccontrol.8 new file mode 100644 index 0000000..61b61aa --- /dev/null +++ b/usr.sbin/pmccontrol/pmccontrol.8 @@ -0,0 +1,132 @@ +.\" Copyright (c) 2003 Joseph Koshy. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" This software is provided by Joseph Koshy ``as is'' and +.\" any express or implied warranties, including, but not limited to, the +.\" implied warranties of merchantability and fitness for a particular purpose +.\" are disclaimed. in no event shall Joseph Koshy be liable +.\" for any direct, indirect, incidental, special, exemplary, or consequential +.\" damages (including, but not limited to, procurement of substitute goods +.\" or services; loss of use, data, or profits; or business interruption) +.\" however caused and on any theory of liability, whether in contract, strict +.\" liability, or tort (including negligence or otherwise) arising in any way +.\" out of the use of this software, even if advised of the possibility of +.\" such damage. +.\" +.\" $FreeBSD$ +.\" +.Dd Dec 15, 2003 +.Os +.Dt PMCCONTROL 8 +.Sh NAME +.Nm pmccontrol +.Nd control hardware performance monitoring counters +.Sh SYNOPSIS +.Nm +.Oo +.Op Fl c Ar cpu +.Op Fl e Ar pmc +.Op Fl d Ar pmc +.Oc Ns ... +.Nm +.Op Fl lL +.Nm +.Op Fl s +.Sh DESCRIPTION +The +.Nm +utility controls the operation of the system's hardware performance +monitoring counters. +.Sh OPTIONS +The +.Nm +utility processes options in command line order, so later options modify +the effect of earlier ones. +The following options are available: +.Bl -tag -width indent +.It Fl c Ar cpu +Subsequent enable and disable options affect the CPU +denoted by +.Ar cpu . +The argument +.Ar cpu +is either a number denoting a CPU number in the system, or the string +.Dq Li \&* , +denoting all CPUs in the system. +.It Fl d Ar pmc +Disable PMC number +.Ar pmc +on the CPU specified by +.Fl c , +preventing it from being used till subsequently re-enabled. +The argument +.Ar pmc +is either a number denoting a specified PMC, or the string +.Dq Li \&* +denoting all the PMCs on the specified CPU. +.Pp +Only idle PMCs may be disabled. +.\" XXX this probably needs to be fixed. +.It Fl e Ar pmc +Enable PMC number +.Ar pmc , +on the CPU specified by +.Fl c , +allowing it to be used in the future. +The argument +.Ar pmc +is either a number denoting a specified PMC, or the string +.Dq Li \&* +denoting all the PMCs on the specified CPU. +If PMC +.Ar pmc +is already enabled, this option has no effect. +.It Fl l +List available hardware performance counters and their current +disposition. +.It Fl L +List available hardware performance counter classes and their +supported event names. +.It Fl s +Print driver statistics maintained by +.Xr hwpmc 4 . +.El +.Sh EXAMPLES +To disable all PMCs on all CPUs, use the command: +.Dl pmccontrol -d\&* +.Pp +To enable all PMCs on all CPUs, use: +.Dl pmccontrol -e\&* +.Pp +To disable PMCs 0 and 1 on CPU 2, use: +.Dl pmccontrol -c2 -d0 -d1 +.Pp +To disable PMC 0 of CPU 0 only, and enable all other PMCS on all other +CPUs, use: +.Dl pmccontrol -c\&* -e\&* -c0 -d0 +.Sh DIAGNOSTICS +.Ex -std pmccontrol +.Sh HISTORY +The +.Nm +utility is proposed to be integrated into +.Fx +sometime after +.Fx 5.2 . +.Nm +.Bt +.Sh AUTHORS +.An Joseph Koshy Aq jkoshy@FreeBSD.org +.Sh SEE ALSO +.Xr pmc 3 , +.Xr hwpmc 4 , +.Xr pmcstat 8 , +.Xr sysctl 8 diff --git a/usr.sbin/pmccontrol/pmccontrol.c b/usr.sbin/pmccontrol/pmccontrol.c new file mode 100644 index 0000000..a1ed2d5 --- /dev/null +++ b/usr.sbin/pmccontrol/pmccontrol.c @@ -0,0 +1,476 @@ +/*- + * Copyright (c) 2003,2004 Joseph Koshy + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/sysctl.h> + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <pmc.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> + +/* Compile time defaults */ + +#define PMCC_PRINT_USAGE 0 +#define PMCC_PRINT_EVENTS 1 +#define PMCC_LIST_STATE 2 +#define PMCC_ENABLE_DISABLE 3 +#define PMCC_SHOW_STATISTICS 4 + +#define PMCC_CPU_ALL -1 +#define PMCC_CPU_WILDCARD '*' + +#define PMCC_PMC_ALL -1 +#define PMCC_PMC_WILDCARD '*' + +#define PMCC_OP_IGNORE 0 +#define PMCC_OP_DISABLE 1 +#define PMCC_OP_ENABLE 2 + +#define PMCC_PROGRAM_NAME "pmccontrol" + +STAILQ_HEAD(pmcc_op_list, pmcc_op) head = STAILQ_HEAD_INITIALIZER(head); + +struct pmcc_op { + char op_cpu; + char op_pmc; + char op_op; + STAILQ_ENTRY(pmcc_op) op_next; +}; + +/* Function Prototypes */ +#if DEBUG +static void pmcc_init_debug(void); +#endif + +static int pmcc_do_list_state(void); +static int pmcc_do_enable_disable(struct pmcc_op_list *); +static int pmcc_do_list_events(void); + +/* Globals */ + +static char usage_message[] = + "Usage:\n" + " " PMCC_PROGRAM_NAME " -l\n" + " " PMCC_PROGRAM_NAME " -s\n" + " " PMCC_PROGRAM_NAME " [-e pmc | -d pmc | -c cpu] ..."; + +#if DEBUG +FILE *debug_stream = NULL; +#endif + +#if DEBUG +#define DEBUG_MSG(...) \ + (void) fprintf(debug_stream, "[pmccontrol] " __VA_ARGS__); +#else +#define DEBUG_MSG(m) /* */ +#endif /* !DEBUG */ + +int pmc_syscall = -1; + +#define PMC_CALL(cmd, params) \ +if ((error = syscall(pmc_syscall, PMC_OP_##cmd, (params))) != 0) \ +{ \ + DEBUG_MSG("ERROR: syscall [" #cmd "]"); \ + exit(EX_OSERR); \ +} + +#if DEBUG +/* log debug messages to a separate file */ +static void +pmcc_init_debug(void) +{ + char *fn; + + fn = getenv("PMCCONTROL_DEBUG"); + if (fn != NULL) + { + debug_stream = fopen(fn, "w"); + if (debug_stream == NULL) + debug_stream = stderr; + } else + debug_stream = stderr; +} +#endif + +static int +pmcc_do_enable_disable(struct pmcc_op_list *op_list) +{ + unsigned char op; + int c, error, i, j, ncpu, npmc, t; + int cpu, pmc; + struct pmcc_op *np; + unsigned char *map; + + if ((ncpu = pmc_ncpu()) < 0) + err(EX_OSERR, "Unable to determine the number of cpus"); + + /* determine the maximum number of PMCs in any CPU */ + npmc = 0; + for (c = 0; c < ncpu; c++) { + if ((t = pmc_npmc(c)) < 0) + err(EX_OSERR, "Unable to determine the number of PMCs in " + "CPU %d", c); + npmc = t > npmc ? t : npmc; + } + + if (npmc == 0) + errx(EX_CONFIG, "No PMCs found"); + + if ((map = malloc(npmc * ncpu)) == NULL) + err(EX_SOFTWARE, "Out of memory"); + + (void) memset(map, PMCC_OP_IGNORE, npmc*ncpu); + + error = 0; + STAILQ_FOREACH(np, op_list, op_next) { + + cpu = np->op_cpu; + pmc = np->op_pmc; + op = np->op_op; + + if (cpu >= ncpu) + errx(EX_DATAERR, "CPU id too large: \"%d\"", cpu); + + if (pmc >= npmc) + errx(EX_DATAERR, "PMC id too large: \"%d\"", pmc); + +#define MARKMAP(M,C,P,V) do { \ + *((M) + (C)*npmc + (P)) = (V); \ +} while (0) + +#define SET_PMCS(C,P,V) do { \ + if ((P) == PMCC_PMC_ALL) { \ + for (j = 0; j < npmc; j++) \ + MARKMAP(map, (C), j, (V)); \ + } else \ + MARKMAP(map, (C), (P), (V)); \ +} while (0) + +#define MAP(M,C,P) (*((M) + (C)*npmc + (P))) + + if (cpu == PMCC_CPU_ALL) + for (i = 0; i < ncpu; i++) + SET_PMCS(i, pmc, op); + else + SET_PMCS(cpu, pmc, op); + } + + /* Configure PMCS */ + for (i = 0; i < ncpu; i++) + for (j = 0; j < npmc; j++) { + unsigned char b; + + b = MAP(map, i, j); + + error = 0; + + if (b == PMCC_OP_ENABLE) + error = pmc_enable(i, j); + else if (b == PMCC_OP_DISABLE) + error = pmc_disable(i, j); + + if (error < 0) + err(EX_OSERR, "%s of PMC %d on CPU %d failed", + b == PMCC_OP_ENABLE ? "Enable" : + "Disable", j, i); + } + + return error; +} + +static int +pmcc_do_list_state(void) +{ + size_t dummy; + int c, cpu, n, npmc, ncpu; + unsigned int logical_cpus_mask; + struct pmc_info *pd; + struct pmc_op_getpmcinfo *pi; + const struct pmc_op_getcpuinfo *pc; + + if (pmc_cpuinfo(&pc) != 0) + err(EX_OSERR, "Unable to determine CPU information"); + + dummy = sizeof(logical_cpus_mask); + if (sysctlbyname("machdep.logical_cpus_mask", &logical_cpus_mask, + &dummy, NULL, 0) < 0) + logical_cpus_mask = 0; + + ncpu = pc->pm_ncpu; + + for (c = cpu = 0; cpu < ncpu; cpu++) { +#if i386 + if (pc->pm_cputype == PMC_CPU_INTEL_PIV && + (logical_cpus_mask & (1 << cpu))) + continue; /* skip P4-style 'logical' cpus */ +#endif + if (pmc_pmcinfo(cpu, &pi) < 0) + err(EX_OSERR, "Unable to get PMC status for CPU %d", + cpu); + + printf("#CPU %d:\n", c++); + npmc = pmc_npmc(cpu); + printf("#N NAME CLASS STATE ROW-DISP\n"); + + for (n = 0; n < npmc; n++) { + pd = &pi->pm_pmcs[n]; + + printf(" %-2d %-16s %-6s %-8s %-10s", + n, + pd->pm_name, + pmc_name_of_class(pd->pm_class), + pd->pm_enabled ? "ENABLED" : "DISABLED", + pmc_name_of_disposition(pd->pm_rowdisp)); + + if (pd->pm_ownerpid != -1) { + printf(" (pid %d)", pd->pm_ownerpid); + printf(" %-32s", + pmc_name_of_event(pd->pm_event)); + if (PMC_IS_SAMPLING_MODE(pd->pm_mode)) + printf(" (reload count %jd)", + pd->pm_reloadcount); + } + printf("\n"); + } + free(pi); + } + return 0; +} + +static int +pmcc_do_list_events(void) +{ + enum pmc_class c; + unsigned int i, j, nevents; + const char **eventnamelist; + const struct pmc_op_getcpuinfo *ci; + + if (pmc_cpuinfo(&ci) != 0) + err(EX_OSERR, "Unable to determine CPU information"); + + eventnamelist = NULL; + + for (i = 0; i < ci->pm_nclass; i++) { + c = ci->pm_classes[i]; + + printf("%s\n", pmc_name_of_class(c)); + if (pmc_event_names_of_class(c, &eventnamelist, &nevents) < 0) + err(EX_OSERR, "ERROR: Cannot find information for " + "event class \"%s\"", pmc_name_of_class(c)); + + for (j = 0; j < nevents; j++) + printf("\t%s\n", eventnamelist[j]); + + free(eventnamelist); + } + return 0; +} + +static int +pmcc_show_statistics(void) +{ + + struct pmc_op_getdriverstats gms; + + if (pmc_get_driver_stats(&gms) < 0) + err(EX_OSERR, "ERROR: cannot retrieve driver statistics"); + + /* + * Print statistics. + */ + +#define PRINT(N,V) (void) printf("%20s %d\n", (N), gms.pm_##V) + + PRINT("interrupts-processed", intr_processed); + PRINT("interrupts-ignored", intr_ignored); + PRINT("system-calls", syscalls); + PRINT("system-calls-with-errors", syscall_errors); + + return 0; +} + +/* + * Main + */ + +int +main(int argc, char **argv) +{ + int error, command, currentcpu, option, pmc; + char *dummy; + struct pmcc_op *p; + +#if DEBUG + pmcc_init_debug(); +#endif + + /* parse args */ + + currentcpu = PMCC_CPU_ALL; + command = PMCC_PRINT_USAGE; + error = 0; + + STAILQ_INIT(&head); + + while ((option = getopt(argc, argv, ":c:d:e:lLs")) != -1) + switch (option) { + case 'L': + if (command != PMCC_PRINT_USAGE) { + error = 1; + break; + } + command = PMCC_PRINT_EVENTS; + break; + + case 'c': + if (command != PMCC_PRINT_USAGE && + command != PMCC_ENABLE_DISABLE) { + error = 1; + break; + } + command = PMCC_ENABLE_DISABLE; + + if (*optarg == PMCC_CPU_WILDCARD) + currentcpu = PMCC_CPU_ALL; + else { + currentcpu = strtoul(optarg, &dummy, 0); + if (*dummy != '\0' || currentcpu < 0) + errx(EX_DATAERR, + "\"%s\" is not a valid CPU id", + optarg); + } + break; + + case 'd': + case 'e': + if (command != PMCC_PRINT_USAGE && + command != PMCC_ENABLE_DISABLE) { + error = 1; + break; + } + command = PMCC_ENABLE_DISABLE; + + if (*optarg == PMCC_PMC_WILDCARD) + pmc = PMCC_PMC_ALL; + else { + pmc = strtoul(optarg, &dummy, 0); + if (*dummy != '\0' || pmc < 0) + errx(EX_DATAERR, + "\"%s\" is not a valid PMC id", + optarg); + } + + if ((p = malloc(sizeof(*p))) == NULL) + err(EX_SOFTWARE, "Out of memory"); + + p->op_cpu = currentcpu; + p->op_pmc = pmc; + p->op_op = option == 'd' ? PMCC_OP_DISABLE : + PMCC_OP_ENABLE; + + STAILQ_INSERT_TAIL(&head, p, op_next); + break; + + case 'l': + if (command != PMCC_PRINT_USAGE) { + error = 1; + break; + } + command = PMCC_LIST_STATE; + break; + + case 's': + if (command != PMCC_PRINT_USAGE) { + error = 1; + break; + } + command = PMCC_SHOW_STATISTICS; + break; + + case ':': + errx(EX_USAGE, + "Missing argument to option '-%c'", optopt); + break; + + case '?': + warnx("Unrecognized option \"-%c\"", optopt); + errx(EX_USAGE, usage_message); + break; + + default: + error = 1; + break; + + } + + if (command == PMCC_PRINT_USAGE) + (void) errx(EX_USAGE, usage_message); + + if (error) + exit(EX_USAGE); + + if (pmc_init() < 0) + err(EX_UNAVAILABLE, + "Initialization of the pmc(3) library failed"); + + switch (command) { + case PMCC_LIST_STATE: + error = pmcc_do_list_state(); + break; + case PMCC_PRINT_EVENTS: + error = pmcc_do_list_events(); + break; + case PMCC_SHOW_STATISTICS: + error = pmcc_show_statistics(); + break; + case PMCC_ENABLE_DISABLE: + if (STAILQ_EMPTY(&head)) + errx(EX_USAGE, "No PMCs specified to enable or disable"); + error = pmcc_do_enable_disable(&head); + break; + default: + assert(0); + + } + + if (error != 0) + err(EX_OSERR, "Command failed"); + exit(0); +} diff --git a/usr.sbin/pmcstat/Makefile b/usr.sbin/pmcstat/Makefile new file mode 100644 index 0000000..350c024 --- /dev/null +++ b/usr.sbin/pmcstat/Makefile @@ -0,0 +1,17 @@ +# +# $FreeBSD$ +# + +PROG= pmcstat +MAN= pmcstat.8 + +DPADD= ${LIBPMC} +LDADD= -lpmc -lm + +WARNS= 6 + +CFLAGS+= -I${.CURDIR}/../../sys -I${.CURDIR}/../../lib/libpmc + +SRCS= pmcstat.c + +.include <bsd.prog.mk> diff --git a/usr.sbin/pmcstat/pmcstat.8 b/usr.sbin/pmcstat/pmcstat.8 new file mode 100644 index 0000000..75f132b --- /dev/null +++ b/usr.sbin/pmcstat/pmcstat.8 @@ -0,0 +1,196 @@ +.\" Copyright (c) 2003 Joseph Koshy. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" This software is provided by Joseph Koshy ``as is'' and +.\" any express or implied warranties, including, but not limited to, the +.\" implied warranties of merchantability and fitness for a particular purpose +.\" are disclaimed. in no event shall Joseph Koshy be liable +.\" for any direct, indirect, incidental, special, exemplary, or consequential +.\" damages (including, but not limited to, procurement of substitute goods +.\" or services; loss of use, data, or profits; or business interruption) +.\" however caused and on any theory of liability, whether in contract, strict +.\" liability, or tort (including negligence or otherwise) arising in any way +.\" out of the use of this software, even if advised of the possibility of +.\" such damage. +.\" +.\" $FreeBSD$ +.\" +.Dd Dec 15, 2003 +.Os +.Dt PMCSTAT 8 +.Sh NAME +.Nm pmcstat +.Nd performance measurement with performance monitoring hardware +.Sh SYNOPSIS +.Nm +.Op Fl C +.Op Fl O Ar logfilename +.Op Fl P Ar event-spec +.Op Fl S Ar event-spec +.Op Fl c Ar cpu +.Op Fl d +.Op Fl n Ar count +.Op Fl o Ar outputfile +.Op Fl p Ar event-spec +.Op Fl s Ar event-spec +.Op Fl t Ar pid +.Op Fl w Ar interval +.Op command Op args +.Sh DESCRIPTION +The +.Nm +utility measures system performance using the facilities provided by +.Xr hwpmc 4 . +.Pp +The +.Nm +utility can measure both hardware events seen by the system as a +whole, and those seen when a specified process is executing on the +system's CPUs. +If a specific process is being targeted (for example, +if the +.Fl t Ar pid +option is specified, or if a command line is specified using +.Ar command ) , +then measurement occurs till the target process exits or +the +.Nm +utility is interrupted by the user. +If a specific process is not targeted for measurement, then +.Nm +will perform system-wide measurements till interrupted by the +user. +.Pp +A given invocation of +.Nm +can mix allocations of system-mode and process-mode PMCs, of both +counting and sampling flavors. +The values of all counting PMCs are printed in human readable form +at regular intervals by +.Nm . +The output of sampling PMCs is configured to go to log file, for later +analysis by tools like +.Xr pmcreport 8 . +.Pp +Hardware events to measure are specified to +.Nm +using event specifier strings +.Ar event-spec . +The syntax of these event specifiers is machine dependent and is +documented in +.Xr pmc 3 . +.Pp +A process-mode PMC may be configured to be inheritable by the target +process' current and future children. +.Sh OPTIONS +The following options are available: +.Bl -tag -width indent +.It Fl C +Toggle between showing cumulative and incremental counts for +subsequent counting mode PMCs specified on the command line. +The default is to show incremental counts. +.It Fl O Ar logfilename +Send the output of sampling mode PMCs to +.Ar logfilename . +The default file name is +.Pa pmcstat.out , +in the current directory. +.It Fl P Ar event-spec +Allocate a process mode sampling PMC measuring hardware events +specified in +.Ar event-spec . +.It Fl S Ar event-spec +Allocate a system mode sampling PMC measuring hardware events +specified in +.Ar event-spec . +.It Fl c Ar cpu +Set the cpu for subsequent system mode PMCs specified on the +command line to +.Ar cpu . +The default is to allocate system mode PMCs on CPU zero. +.It Fl d +Toggle between process mode PMCs measuring events for the target +process' current and future children or only measuring events for +the attached process. +The default is to measure events for the target process alone. +.It Fl n Ar rate +Set the default sampling rate for subsequent sampling mode +PMCs specified on the command line. +The default is to configure PMCs to sample the CPU's instruction +pointer every 65536 events. +.It Fl o Ar outputfile +Send the periodic counter output of +.Nm +to file +.Ar outputfile . +The default is to send output to +.Pa stderr . +.It Fl p Ar event-spec +Allocate a process mode counting PMC measuring hardware events +specified in +.Ar event-spec . +.It Fl s Ar event-spec +Allocate a system mode counting PMC measuring hardware events +specified in +.Ar event-spec . +.It Fl t Ar pid +Attach all process mode PMCs allocated to the process with PID +.Ar pid . +The option is not allowed in conjunction with specifying a +command using +.Ar command . +.It Fl w Ar secs +Print the values of all counting mode PMCs every +.Ar secs +seconds. +The argument +.Ar secs +may be a fractional value. +The default interval is 5 seconds. +.El +.Pp +If +.Ar command +is specified, it is executed using +.Xr execvp 3 . +.Sh EXAMPLES +To perform system-wide statistical sampling on an AMD Athlon CPU with +samples taken every 32768 instruction retirals and data being sampled +to file +.Dq sample.stat , +use: +.Dl pmccstat -O sample.stat -n 32768 -S k7-retired-instructions +.Pp +To execute +.Dq mozilla +and measure the number of data cache misses suffered +by it and its children every 12 seconds on an AMD Athlon, use: +.Dl pmcstat -d -w 12 -p k7-dc-misses mozilla +.Sh DIAGNOSTICS +.Ex -std pmcstat +.Sh HISTORY +The +.Nm +utility is proposed to be integrated into +.Fx +sometime after +.Fx 5.2 . +.Nm +.Bt +.Sh AUTHORS +.An Joseph Koshy Aq jkoshy@FreeBSD.org +.Sh SEE ALSO +.Xr execvp 3 , +.Xr pmc 3 , +.Xr hwpmc 4 , +.Xr pmccontrol 8 , +.Xr pmcreport 8 , +.Xr sysctl 8 diff --git a/usr.sbin/pmcstat/pmcstat.c b/usr.sbin/pmcstat/pmcstat.c new file mode 100644 index 0000000..8dc09dc --- /dev/null +++ b/usr.sbin/pmcstat/pmcstat.c @@ -0,0 +1,728 @@ +/*- + * Copyright (c) 2003,2004 Joseph Koshy + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/types.h> +#include <sys/event.h> +#include <sys/queue.h> +#include <sys/time.h> +#include <sys/ttycom.h> +#include <sys/wait.h> + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <math.h> +#include <pmc.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> + +/* Operation modes */ + +#define FLAG_HAS_PID 0x00000001 +#define FLAG_HAS_WAIT_INTERVAL 0x00000002 +#define FLAG_HAS_LOG_FILE 0x00000004 +#define FLAG_HAS_PROCESS 0x00000008 +#define FLAG_USING_SAMPLING 0x00000010 +#define FLAG_USING_COUNTING 0x00000020 +#define FLAG_USING_PROCESS_PMC 0x00000040 + +#define DEFAULT_SAMPLE_COUNT 65536 +#define DEFAULT_WAIT_INTERVAL 5.0 +#define DEFAULT_DISPLAY_HEIGHT 23 +#define DEFAULT_LOGFILE_NAME "pmcstat.out" + +#define PRINT_HEADER_PREFIX "# " +#define READPIPEFD 0 +#define WRITEPIPEFD 1 +#define NPIPEFD 2 + +struct pmcstat_ev { + STAILQ_ENTRY(pmcstat_ev) ev_next; + char *ev_spec; /* event specification */ + char *ev_name; /* (derived) event name */ + enum pmc_mode ev_mode; /* desired mode */ + int ev_count; /* associated count if in sampling mode */ + int ev_cpu; /* specific cpu if requested */ + int ev_descendants; /* attach to descendants */ + int ev_cumulative; /* show cumulative counts */ + int ev_fieldwidth; /* print width */ + int ev_fieldskip; /* #leading spaces */ + pmc_value_t ev_saved; /* saved value for incremental counts */ + pmc_id_t ev_pmcid; /* allocated ID */ +}; + +struct pmcstat_args { + int pa_flags; + pid_t pa_pid; + FILE *pa_outputfile; + FILE *pa_logfile; + double pa_interval; + int pa_argc; + char **pa_argv; + STAILQ_HEAD(, pmcstat_ev) pa_head; +} args; + +int pmcstat_interrupt = 0; +int pmcstat_displayheight = DEFAULT_DISPLAY_HEIGHT; +int pmcstat_pipefd[NPIPEFD]; +int pmcstat_kq; + +/* Function prototypes */ +void pmcstat_cleanup(struct pmcstat_args *_a); +void pmcstat_print_counters(struct pmcstat_args *_a); +void pmcstat_print_headers(struct pmcstat_args *_a); +void pmcstat_print_pmcs(struct pmcstat_args *_a); +void pmcstat_setup_process(struct pmcstat_args *_a); +void pmcstat_show_usage(void); +void pmcstat_start_pmcs(struct pmcstat_args *_a); +void pmcstat_start_process(struct pmcstat_args *_a); + + +/* + * cleanup + */ + +void +pmcstat_cleanup(struct pmcstat_args *a) +{ + struct pmcstat_ev *ev, *tmp; + + /* de-configure the log file if present. */ + if (a->pa_flags & FLAG_USING_SAMPLING) { + (void) pmc_configure_logfile(-1); + (void) fclose(a->pa_logfile); + } + + /* release allocated PMCs. */ + STAILQ_FOREACH_SAFE(ev, &a->pa_head, ev_next, tmp) + if (ev->ev_pmcid != PMC_ID_INVALID) { + if (pmc_release(ev->ev_pmcid) < 0) + err(EX_OSERR, "ERROR: cannot release pmc " + "%d \"%s\"", ev->ev_pmcid, ev->ev_name); + free(ev->ev_name); + free(ev->ev_spec); + STAILQ_REMOVE(&a->pa_head, ev, pmcstat_ev, ev_next); + free(ev); + } +} + +void +pmcstat_start_pmcs(struct pmcstat_args *a) +{ + struct pmcstat_ev *ev; + + STAILQ_FOREACH(ev, &args.pa_head, ev_next) { + + assert(ev->ev_pmcid != PMC_ID_INVALID); + + if (pmc_start(ev->ev_pmcid) < 0) { + warn("ERROR: Cannot start pmc %d \"%s\"", + ev->ev_pmcid, ev->ev_name); + pmcstat_cleanup(a); + } + } + +} + +void +pmcstat_print_headers(struct pmcstat_args *a) +{ + struct pmcstat_ev *ev; + int c; + + (void) fprintf(a->pa_outputfile, PRINT_HEADER_PREFIX); + + STAILQ_FOREACH(ev, &a->pa_head, ev_next) { + if (PMC_IS_SAMPLING_MODE(ev->ev_mode)) + continue; + + c = PMC_IS_SYSTEM_MODE(ev->ev_mode) ? 's' : 'p'; + + if (ev->ev_fieldskip != 0) { + (void) fprintf(a->pa_outputfile, "%*s%c/%*s ", + ev->ev_fieldskip, "", c, + ev->ev_fieldwidth - ev->ev_fieldskip - 2, + ev->ev_name); + } else + (void) fprintf(a->pa_outputfile, "%c/%*s ", + c, ev->ev_fieldwidth - 2, ev->ev_name); + } + + (void) fflush(a->pa_outputfile); +} + +void +pmcstat_print_counters(struct pmcstat_args *a) +{ + int extra_width; + struct pmcstat_ev *ev; + pmc_value_t value; + + extra_width = sizeof(PRINT_HEADER_PREFIX) - 1; + + STAILQ_FOREACH(ev, &a->pa_head, ev_next) { + + /* skip sampling mode counters */ + if (PMC_IS_SAMPLING_MODE(ev->ev_mode)) + continue; + + if (pmc_read(ev->ev_pmcid, &value) < 0) + err(EX_OSERR, "ERROR: Cannot read pmc " + "\"%s\"", ev->ev_name); + + (void) fprintf(a->pa_outputfile, "%*ju ", + ev->ev_fieldwidth + extra_width, (uintmax_t) + ev->ev_cumulative ? value : (value - ev->ev_saved)); + if (ev->ev_cumulative == 0) + ev->ev_saved = value; + extra_width = 0; + } + + (void) fflush(a->pa_outputfile); +} + +/* + * Print output + */ + +void +pmcstat_print_pmcs(struct pmcstat_args *a) +{ + static int linecount = 0; + + if (++linecount > pmcstat_displayheight) { + (void) fprintf(a->pa_outputfile, "\n"); + linecount = 1; + } + + if (linecount == 1) + pmcstat_print_headers(a); + + (void) fprintf(a->pa_outputfile, "\n"); + pmcstat_print_counters(a); + + return; +} + +/* + * Do process profiling + * + * If a pid was specified, attach each allocated PMC to the target + * process. Otherwise, fork a child and attach the PMCs to the child, + * and have the child exec() the target program. + */ + +void +pmcstat_setup_process(struct pmcstat_args *a) +{ + char token; + struct pmcstat_ev *ev; + struct kevent kev; + + if (a->pa_flags & FLAG_HAS_PID) { + + STAILQ_FOREACH(ev, &args.pa_head, ev_next) + if (pmc_attach(ev->ev_pmcid, a->pa_pid) != 0) + err(EX_OSERR, "ERROR: cannot attach pmc \"%s\" to " + "process %d", ev->ev_name, (int) a->pa_pid); + + } else { + + /* + * We need to fork a new process and startup the child + * using execvp(). Before doing the exec() the child + * process reads its pipe for a token so that the parent + * can finish doing its pmc_attach() calls. + */ + + if (pipe(pmcstat_pipefd) < 0) + err(EX_OSERR, "ERROR: cannot create pipe"); + + switch (a->pa_pid = fork()) { + case -1: + err(EX_OSERR, "ERROR: cannot fork"); + /*NOTREACHED*/ + + case 0: /* child */ + + /* wait for our parent to signal us */ + (void) close(pmcstat_pipefd[WRITEPIPEFD]); + if (read(pmcstat_pipefd[READPIPEFD], &token, 1) < 0) + err(EX_OSERR, "ERROR (child): cannot read " + "token"); + (void) close(pmcstat_pipefd[READPIPEFD]); + + /* exec() the program requested */ + execvp(*args.pa_argv, args.pa_argv); + err(EX_OSERR, "ERROR (child): execvp failed"); + /*NOTREACHED*/ + + default: /* parent */ + + (void) close(pmcstat_pipefd[READPIPEFD]); + + /* attach all our PMCs to the child */ + STAILQ_FOREACH(ev, &args.pa_head, ev_next) + if (PMC_IS_VIRTUAL_MODE(ev->ev_mode) && + pmc_attach(ev->ev_pmcid, a->pa_pid) != 0) + err(EX_OSERR, "ERROR: cannot attach pmc " + "\"%s\" to process %d", ev->ev_name, + (int) a->pa_pid); + + } + } + + /* Ask to be notified via a kevent when the child exits */ + EV_SET(&kev, a->pa_pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, 0); + + if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) + err(EX_OSERR, "ERROR: cannot monitor process %d", + a->pa_pid); + + return; +} + +void +pmcstat_start_process(struct pmcstat_args *a) +{ + + /* nothing to do: target is already running */ + if (a->pa_flags & FLAG_HAS_PID) + return; + + /* write token to child to state that we are ready */ + if (write(pmcstat_pipefd[WRITEPIPEFD], "+", 1) != 1) + err(EX_OSERR, "ERROR: write failed"); + + (void) close(pmcstat_pipefd[WRITEPIPEFD]); +} + +void +pmcstat_show_usage(void) +{ + errx(EX_USAGE, + "[options] [commandline]\n" + "\t Measure process and/or system performance using hardware\n" + "\t performance monitoring counters.\n" + "\t Options include:\n" + "\t -C\t\t toggle showing cumulative counts\n" + "\t -O file\t set sampling log file to \"file\"\n" + "\t -P spec\t allocate process-private sampling PMC\n" + "\t -S spec\t allocate system-wide sampling PMC\n" + "\t -c cpu\t\t set default cpu\n" + "\t -d\t\t toggle tracking descendants\n" + "\t -n rate\t set sampling rate\n" + "\t -o file\t send print output to \"file\"\n" + "\t -p spec\t allocate process-private counting PMC\n" + "\t -s spec\t allocate system-wide counting PMC\n" + "\t -t pid\t attach to running process with pid \"pid\"\n" + "\t -w secs\t set printing time interval" + ); +} + +/* + * Main + */ + +int +main(int argc, char **argv) +{ + double interval; + int option, npmc, ncpu; + int c, current_cpu, current_sampling_count; + int running; + int do_descendants, use_cumulative_counts; + pid_t pid; + char *end; + struct pmcstat_ev *ev; + struct pmc_op_getpmcinfo *ppmci; + struct sigaction sa; + struct kevent kev; + struct winsize ws; + + current_cpu = 0; + current_sampling_count = DEFAULT_SAMPLE_COUNT; + do_descendants = 0; + use_cumulative_counts = 0; + args.pa_flags = 0; + args.pa_pid = (pid_t) -1; + args.pa_logfile = NULL; + args.pa_outputfile = stderr; + args.pa_interval = DEFAULT_WAIT_INTERVAL; + STAILQ_INIT(&args.pa_head); + + ev = NULL; + + while ((option = getopt(argc, argv, "CO:P:S:c:dn:o:p:s:t:w:")) != -1) + switch (option) { + case 'C': /* cumulative values */ + use_cumulative_counts = !use_cumulative_counts; + break; + + case 'c': /* CPU */ + current_cpu = strtol(optarg, &end, 0); + if (*end != '\0' || current_cpu < 0) + errx(EX_USAGE, + "ERROR: Illegal CPU number \"%s\"", + optarg); + + break; + + case 'd': /* toggle descendents */ + do_descendants = !do_descendants; + break; + + case 'p': /* process virtual counting PMC */ + case 's': /* system-wide counting PMC */ + case 'P': /* process virtual sampling PMC */ + case 'S': /* system-wide sampling PMC */ + if ((ev = malloc(sizeof(*ev))) == NULL) + errx(EX_SOFTWARE, "ERROR: Out of memory"); + + switch (option) { + case 'p': ev->ev_mode = PMC_MODE_TC; break; + case 's': ev->ev_mode = PMC_MODE_SC; break; + case 'P': ev->ev_mode = PMC_MODE_TS; break; + case 'S': ev->ev_mode = PMC_MODE_SS; break; + } + + if (option == 'P' || option == 'p') + args.pa_flags |= FLAG_USING_PROCESS_PMC; + + if (option == 'P' || option == 'S') + args.pa_flags |= FLAG_USING_SAMPLING; + + if (option == 'p' || option == 's') + args.pa_flags |= FLAG_USING_COUNTING; + + ev->ev_spec = strdup(optarg); + + if (option == 'S' || option == 'P') + ev->ev_count = current_sampling_count; + else + ev->ev_count = -1; + + if (option == 'S' || option == 's') + ev->ev_cpu = current_cpu; + else + ev->ev_cpu = PMC_CPU_ANY; + + ev->ev_descendants = do_descendants; + ev->ev_cumulative = use_cumulative_counts; + + ev->ev_saved = 0LL; + ev->ev_pmcid = PMC_ID_INVALID; + + /* extract event name */ + c = strcspn(optarg, ", \t"); + ev->ev_name = malloc(c + 1); + (void) strncpy(ev->ev_name, optarg, c); + *(ev->ev_name + c) = '\0'; + + STAILQ_INSERT_TAIL(&args.pa_head, ev, ev_next); + + break; + + case 'n': /* sampling count */ + current_sampling_count = strtol(optarg, &end, 0); + if (*end != '\0' || current_sampling_count <= 0) + errx(EX_USAGE, + "ERROR: Illegal count value \"%s\"", + optarg); + break; + + case 'o': /* outputfile */ + if (args.pa_outputfile != NULL) + (void) fclose(args.pa_outputfile); + + if ((args.pa_outputfile = fopen(optarg, "w")) == NULL) + errx(EX_OSERR, "ERROR: cannot open \"%s\" for " + "writing", optarg); + + case 'O': /* sampling output */ + if (args.pa_logfile != NULL) + (void) fclose(args.pa_logfile); + + if ((args.pa_logfile = fopen(optarg, "w")) == NULL) + errx(EX_OSERR, "ERROR: cannot open \"%s\" for " + "writing", optarg); + break; + + case 't': /* target pid */ + pid = strtol(optarg, &end, 0); + if (*end != '\0' || pid <= 0) + errx(EX_USAGE, "ERROR: Illegal pid value " + "\"%s\"", optarg); + + args.pa_flags |= FLAG_HAS_PID; + args.pa_pid = pid; + + break; + + case 'w': /* wait interval */ + interval = strtod(optarg, &end); + if (*end != '\0' || interval <= 0) + errx(EX_USAGE, "ERROR: Illegal wait interval " + "value \"%s\"", optarg); + args.pa_flags |= FLAG_HAS_WAIT_INTERVAL; + args.pa_interval = interval; + + break; + + case '?': + default: + pmcstat_show_usage(); + break; + + } + + args.pa_argc = (argc -= optind); + args.pa_argv = (argv += optind); + + if (argc) + args.pa_flags |= FLAG_HAS_PROCESS; + + /* + * Check invocation syntax. + */ + + if (STAILQ_EMPTY(&args.pa_head)) { + warnx("ERROR: At least one PMC event must be specified"); + pmcstat_show_usage(); + } + + if (argc == 0) { + if (args.pa_pid == -1) { + if (args.pa_flags & FLAG_USING_PROCESS_PMC) + errx(EX_USAGE, "ERROR: the -P or -p options " + "require a target process"); + } else if ((args.pa_flags & FLAG_USING_PROCESS_PMC) == 0) + errx(EX_USAGE, + "ERROR: option -t requires a process-mode pmc " + "specification"); + } else if (args.pa_pid != -1) + errx(EX_USAGE, + "ERROR: option -t cannot be specified with a command " + "name"); + + if (pmc_init() < 0) + err(EX_UNAVAILABLE, + "ERROR: Initialization of the pmc(3) library failed"); + + if ((ncpu = pmc_ncpu()) < 0) + err(EX_OSERR, "ERROR: Cannot determine the number CPUs " + "on the system"); + + if ((npmc = pmc_npmc(0)) < 0) /* assume all CPUs are identical */ + err(EX_OSERR, "ERROR: Cannot determine the number of PMCs " + "on CPU %d", 0); + + /* + * Allocate PMCs. + */ + + if (pmc_pmcinfo(0, &ppmci) < 0) + err(EX_OSERR, "ERROR: cannot retrieve pmc information"); + + assert(ppmci != NULL); + + STAILQ_FOREACH(ev, &args.pa_head, ev_next) + if (pmc_allocate(ev->ev_spec, ev->ev_mode, + (ev->ev_descendants ? PMC_F_DESCENDANTS : 0), + ev->ev_cpu, &ev->ev_pmcid) < 0) + err(EX_OSERR, "ERROR: Cannot allocate %s-mode pmc with " + "specification \"%s\"", + PMC_IS_SYSTEM_MODE(ev->ev_mode) ? "system" : "process", + ev->ev_spec); + + /* compute printout widths */ + STAILQ_FOREACH(ev, &args.pa_head, ev_next) { + int pmc_width; + int pmc_display_width; + int pmc_header_width; + + pmc_width = ppmci->pm_pmcs[ev->ev_pmcid].pm_width; + pmc_header_width = strlen(ev->ev_name) + 2; /* prefix '%c|' */ + pmc_display_width = (int) floor(pmc_width / 3.32193) + 1; + + if (pmc_header_width > pmc_display_width) { + ev->ev_fieldskip = 0; + ev->ev_fieldwidth = pmc_header_width; + } else { + ev->ev_fieldskip = pmc_display_width - + pmc_header_width; + ev->ev_fieldwidth = pmc_display_width; + } + } + + /* Allocate a kqueue */ + if ((pmcstat_kq = kqueue()) < 0) + err(EX_OSERR, "ERROR: Cannot allocate kqueue"); + + /* + * If our output is being set to a terminal, register a handler + * for window size changes. + */ + + if (isatty(fileno(args.pa_outputfile))) { + + if (ioctl(fileno(args.pa_outputfile), TIOCGWINSZ, &ws) < 0) + err(EX_OSERR, "ERROR: Cannot determine window size"); + + pmcstat_displayheight = ws.ws_row - 1; + + EV_SET(&kev, SIGWINCH, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); + + if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) + err(EX_OSERR, "ERROR: Cannot register kevent for " + "SIGWINCH"); + } + + EV_SET(&kev, SIGINT, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); + + if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) + err(EX_OSERR, "ERROR: Cannot register kevent for SIGINT"); + + if (args.pa_flags & FLAG_USING_SAMPLING) { + + /* + * configure log file + */ + + if (args.pa_logfile == NULL) + if ((args.pa_logfile = + fopen(DEFAULT_LOGFILE_NAME, "w")) == NULL) + err(EX_CANTCREAT, "ERROR: Cannot open sampling " + "log file \"%s\"", DEFAULT_LOGFILE_NAME); + + if (pmc_configure_logfile(fileno(args.pa_logfile)) < 0) + err(EX_OSERR, "ERROR: Cannot configure sampling " + "log"); + + STAILQ_FOREACH(ev, &args.pa_head, ev_next) + if (PMC_IS_SAMPLING_MODE(ev->ev_mode) && + pmc_set(ev->ev_pmcid, ev->ev_count) < 0) + err(EX_OSERR, "ERROR: Cannot set sampling count " + "for PMC \"%s\"", ev->ev_name); + } + + /* setup a timer for any counting mode PMCs */ + if (args.pa_flags & FLAG_USING_COUNTING) { + EV_SET(&kev, 0, EVFILT_TIMER, EV_ADD, 0, + args.pa_interval * 1000, NULL); + + if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) + err(EX_OSERR, "ERROR: Cannot register kevent for " + "timer"); + } + + /* attach PMCs to the target process, starting it if specified */ + if (args.pa_flags & FLAG_HAS_PROCESS) + pmcstat_setup_process(&args); + + /* start the pmcs */ + pmcstat_start_pmcs(&args); + + /* start the (commandline) process if needed */ + if (args.pa_flags & FLAG_HAS_PROCESS) + pmcstat_start_process(&args); + + /* Handle SIGINT using the kqueue loop */ + sa.sa_handler = SIG_IGN; + sa.sa_flags = 0; + (void) sigemptyset(&sa.sa_mask); + + if (sigaction(SIGINT, &sa, NULL) < 0) + err(EX_OSERR, "ERROR: Cannot install signal handler"); + + /* + * loop till either the target process (if any) exits, or we + * are killed by a SIGINT. + */ + + running = 1; + do { + if ((c = kevent(pmcstat_kq, NULL, 0, &kev, 1, NULL)) <= 0) { + if (errno != EINTR) + err(EX_OSERR, "ERROR: kevent failed"); + else + continue; + } + + if (kev.flags & EV_ERROR) + errc(EX_OSERR, kev.data, "ERROR: kevent failed"); + + switch (kev.filter) { + case EVFILT_PROC: /* target process exited */ + running = 0; + /* FALLTHROUGH */ + + case EVFILT_TIMER: /* print out counting PMCs */ + pmcstat_print_pmcs(&args); + + if (running == 0) /* final newline */ + (void) fprintf(args.pa_outputfile, "\n"); + break; + + case EVFILT_SIGNAL: + if (kev.ident == SIGINT) { + /* pass the signal on to the child process */ + if ((args.pa_flags & FLAG_HAS_PROCESS) && + (args.pa_flags & FLAG_HAS_PID) == 0) + if (kill(args.pa_pid, SIGINT) != 0) + err(EX_OSERR, "cannot kill " + "child"); + running = 0; + } else if (kev.ident == SIGWINCH) { + if (ioctl(fileno(args.pa_outputfile), + TIOCGWINSZ, &ws) < 0) + err(EX_OSERR, "ERROR: Cannot determine " + "window size"); + pmcstat_displayheight = ws.ws_row - 1; + } else + assert(0); + + break; + } + + } while (running); + + pmcstat_cleanup(&args); + + return 0; +} |