MFP4: Enhancements to pmcstat(8):

- Allow the "-t" option to take a regular expression naming command
  line processes to attach process PMCs to.

- Update the manual page and add an example showing the use of the
  new functionality.

- Update the (c) year on the affected source files.
This commit is contained in:
jkoshy 2007-04-27 12:09:31 +00:00
parent d4fd0c015b
commit 0dfc773563
4 changed files with 184 additions and 69 deletions

View File

@ -6,7 +6,7 @@ PROG= pmcstat
MAN= pmcstat.8
DPADD= ${LIBPMC} ${LIBM}
LDADD= -lpmc -lm
LDADD= -lkvm -lpmc -lm
WARNS?= 6

View File

@ -23,7 +23,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd April 22, 2007
.Dd April 23, 2007
.Os
.Dt PMCSTAT 8
.Sh NAME
@ -50,7 +50,7 @@
.Op Fl q
.Op Fl r Ar fsroot
.Op Fl s Ar event-spec
.Op Fl t Ar pid
.Op Fl t Ar process-spec
.Op Fl v
.Op Fl w Ar secs
.Op Ar command Op Ar args
@ -63,18 +63,21 @@ utility measures system performance using the facilities provided by
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,
whole, and those seen when a specified set of processes are executing
on the system's CPUs.
If a specific set of processes is being targeted (for example,
if the
.Fl t Ar pid
.Fl t Ar process-spec
option is specified, or if a command line is specified using
.Ar command ) ,
then measurement occurs till the target process exits or
the
then measurement occurs till
.Ar command
exits, or till all target processes specified by the
.Fl t Ar process-spec
options exit, or till the
.Nm
utility is interrupted by the user.
If a specific process is not targeted for measurement, then
If a specific set of processes is not targeted for measurement, then
.Nm
will perform system-wide measurements till interrupted by the
user.
@ -237,12 +240,13 @@ The default is
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 t Ar process-spec
Attach process mode PMCs to the processes named by argument
.Ar process-spec .
Argument
.Ar process-spec
may be a non-negative integer denoting a specific process id, or a
regular expression for selecting processes based on their command names.
.It Fl v
Increase verbosity.
.It Fl w Ar secs
@ -273,6 +277,11 @@ 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"
.Pp
To measure processor instructions retired for all processes named
.Dq emacs
use:
.Dl "pmcstat -t '^emacs$' -p instructions"
.Pp
To count instruction tlb-misses on CPUs 0 and 2 on a Intel
Pentium Pro/Pentium III SMP system use:
.Dl "pmcstat -c 0,2 -s p6-itlb-miss"

View File

@ -1,5 +1,5 @@
/*-
* Copyright (c) 2003-2006, Joseph Koshy
* Copyright (c) 2003-2007, Joseph Koshy
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -29,23 +29,27 @@ __FBSDID("$FreeBSD$");
#include <sys/types.h>
#include <sys/event.h>
#include <sys/param.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <sys/time.h>
#include <sys/ttycom.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <kvm.h>
#include <libgen.h>
#include <limits.h>
#include <math.h>
#include <pmc.h>
#include <pmclog.h>
#include <regex.h>
#include <signal.h>
#include <stdarg.h>
#include <stdint.h>
@ -100,19 +104,32 @@ int pmcstat_interrupt = 0;
int pmcstat_displayheight = DEFAULT_DISPLAY_HEIGHT;
int pmcstat_sockpair[NSOCKPAIRFD];
int pmcstat_kq;
kvm_t *pmcstat_kvm;
struct kinfo_proc *pmcstat_plist;
void
pmcstat_attach_pmcs(struct pmcstat_args *a)
{
struct pmcstat_ev *ev;
struct pmcstat_target *pt;
int count;
/* Attach all process PMCs to the child process. */
STAILQ_FOREACH(ev, &a->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);
/* Attach all process PMCs to target processes. */
count = 0;
STAILQ_FOREACH(ev, &a->pa_events, ev_next) {
if (PMC_IS_SYSTEM_MODE(ev->ev_mode))
continue;
SLIST_FOREACH(pt, &a->pa_targets, pt_next)
if (pmc_attach(ev->ev_pmcid, pt->pt_pid) == 0)
count++;
else if (errno != ESRCH)
err(EX_OSERR, "ERROR: cannot attach pmc "
"\"%s\" to process %d", ev->ev_name,
(int) pt->pt_pid);
}
if (count == 0)
errx(EX_DATAERR, "ERROR: No processes were attached to.");
}
@ -122,14 +139,14 @@ pmcstat_cleanup(struct pmcstat_args *a)
struct pmcstat_ev *ev, *tmp;
/* release allocated PMCs. */
STAILQ_FOREACH_SAFE(ev, &a->pa_head, ev_next, tmp)
STAILQ_FOREACH_SAFE(ev, &a->pa_events, ev_next, tmp)
if (ev->ev_pmcid != PMC_ID_INVALID) {
if (pmc_release(ev->ev_pmcid) < 0)
err(EX_OSERR, "ERROR: cannot release pmc "
"0x%x \"%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);
STAILQ_REMOVE(&a->pa_events, ev, pmcstat_ev, ev_next);
free(ev);
}
@ -170,7 +187,7 @@ pmcstat_clone_event_descriptor(struct pmcstat_args *a, struct pmcstat_ev *ev,
ev_clone->ev_saved = ev->ev_saved;
ev_clone->ev_spec = strdup(ev->ev_spec);
STAILQ_INSERT_TAIL(&a->pa_head, ev_clone, ev_next);
STAILQ_INSERT_TAIL(&a->pa_events, ev_clone, ev_next);
cpumask &= ~(1 << cpu);
}
@ -180,12 +197,14 @@ void
pmcstat_create_process(struct pmcstat_args *a)
{
char token;
pid_t pid;
struct kevent kev;
struct pmcstat_target *pt;
if (socketpair(AF_UNIX, SOCK_STREAM, 0, pmcstat_sockpair) < 0)
err(EX_OSERR, "ERROR: cannot create socket pair");
switch (a->pa_pid = fork()) {
switch (pid = fork()) {
case -1:
err(EX_OSERR, "ERROR: cannot fork");
/*NOTREACHED*/
@ -215,11 +234,16 @@ pmcstat_create_process(struct pmcstat_args *a)
}
/* Ask to be notified via a kevent when the target process exits. */
EV_SET(&kev, a->pa_pid, EVFILT_PROC, EV_ADD|EV_ONESHOT, NOTE_EXIT, 0,
EV_SET(&kev, pid, EVFILT_PROC, EV_ADD|EV_ONESHOT, NOTE_EXIT, 0,
NULL);
if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0)
err(EX_OSERR, "ERROR: cannot monitor child process %d",
a->pa_pid);
err(EX_OSERR, "ERROR: cannot monitor child process %d", pid);
if ((pt = malloc(sizeof(*pt))) == NULL)
errx(EX_SOFTWARE, "ERROR: Out of memory.");
pt->pt_pid = pid;
SLIST_INSERT_HEAD(&a->pa_targets, pt, pt_next);
/* Wait for the child to signal that its ready to go. */
if (read(pmcstat_sockpair[PARENTSOCKET], &token, 1) < 0)
@ -228,6 +252,66 @@ pmcstat_create_process(struct pmcstat_args *a)
return;
}
void
pmcstat_find_targets(struct pmcstat_args *a, const char *spec)
{
int n, nproc, pid, rv;
struct pmcstat_target *pt;
char errbuf[_POSIX2_LINE_MAX], *end;
static struct kinfo_proc *kp;
regex_t reg;
regmatch_t regmatch;
/* First check if we've been given a process id. */
pid = strtol(spec, &end, 0);
if (end != spec && pid >= 0) {
if ((pt = malloc(sizeof(*pt))) == NULL)
goto outofmemory;
pt->pt_pid = pid;
SLIST_INSERT_HEAD(&a->pa_targets, pt, pt_next);
return;
}
/* Otherwise treat arg as a regular expression naming processes. */
if (pmcstat_kvm == NULL) {
if ((pmcstat_kvm = kvm_openfiles(NULL, "/dev/null", NULL, 0,
errbuf)) == NULL)
err(EX_OSERR, "ERROR: Cannot open kernel \"%s\"",
errbuf);
if ((pmcstat_plist = kvm_getprocs(pmcstat_kvm, KERN_PROC_PROC,
0, &nproc)) == NULL)
err(EX_OSERR, "ERROR: Cannot get process list: %s",
kvm_geterr(pmcstat_kvm));
}
if ((rv = regcomp(&reg, spec, REG_EXTENDED|REG_NOSUB)) != 0) {
regerror(rv, &reg, errbuf, sizeof(errbuf));
err(EX_DATAERR, "ERROR: Failed to compile regex \"%s\": %s",
spec, errbuf);
}
for (n = 0, kp = pmcstat_plist; n < nproc; n++, kp++) {
if ((rv = regexec(&reg, kp->ki_comm, 1, &regmatch, 0)) == 0) {
if ((pt = malloc(sizeof(*pt))) == NULL)
goto outofmemory;
pt->pt_pid = kp->ki_pid;
SLIST_INSERT_HEAD(&a->pa_targets, pt, pt_next);
} else if (rv != REG_NOMATCH) {
regerror(rv, &reg, errbuf, sizeof(errbuf));
errx(EX_SOFTWARE, "ERROR: Regex evalation failed: %s",
errbuf);
}
}
regfree(&reg);
return;
outofmemory:
errx(EX_SOFTWARE, "Out of memory.");
/*NOTREACHED*/
}
uint32_t
pmcstat_get_cpumask(const char *cpuspec)
{
@ -251,12 +335,30 @@ pmcstat_get_cpumask(const char *cpuspec)
return (cpumask);
}
void
pmcstat_kill_process(struct pmcstat_args *a)
{
struct pmcstat_target *pt;
assert(a->pa_flags & FLAG_HAS_COMMANDLINE);
/*
* If a command line was specified, it would be the very first
* in the list, before any other processes specified by -t.
*/
pt = SLIST_FIRST(&a->pa_targets);
assert(pt != NULL);
if (kill(pt->pt_pid, SIGINT) != 0)
err(EX_OSERR, "ERROR: cannot signal child process");
}
void
pmcstat_start_pmcs(struct pmcstat_args *a)
{
struct pmcstat_ev *ev;
STAILQ_FOREACH(ev, &args.pa_head, ev_next) {
STAILQ_FOREACH(ev, &args.pa_events, ev_next) {
assert(ev->ev_pmcid != PMC_ID_INVALID);
@ -278,7 +380,7 @@ pmcstat_print_headers(struct pmcstat_args *a)
(void) fprintf(a->pa_printfile, PRINT_HEADER_PREFIX);
STAILQ_FOREACH(ev, &a->pa_head, ev_next) {
STAILQ_FOREACH(ev, &a->pa_events, ev_next) {
if (PMC_IS_SAMPLING_MODE(ev->ev_mode))
continue;
@ -309,7 +411,7 @@ pmcstat_print_counters(struct pmcstat_args *a)
extra_width = sizeof(PRINT_HEADER_PREFIX) - 1;
STAILQ_FOREACH(ev, &a->pa_head, ev_next) {
STAILQ_FOREACH(ev, &a->pa_events, ev_next) {
/* skip sampling mode counters */
if (PMC_IS_SAMPLING_MODE(ev->ev_mode))
@ -422,7 +524,6 @@ main(int argc, char **argv)
int pipefd[2];
int use_cumulative_counts;
uint32_t cpumask;
pid_t pid;
char *end, *tmp;
const char *errmsg;
enum pmcstat_state runstate;
@ -444,7 +545,6 @@ main(int argc, char **argv)
args.pa_required = 0;
args.pa_flags = 0;
args.pa_verbosity = 1;
args.pa_pid = (pid_t) -1;
args.pa_logfd = -1;
args.pa_fsroot = "";
args.pa_kernel = strdup("/boot/kernel");
@ -452,7 +552,8 @@ main(int argc, char **argv)
args.pa_printfile = stderr;
args.pa_interval = DEFAULT_WAIT_INTERVAL;
args.pa_mapfilename = NULL;
STAILQ_INIT(&args.pa_head);
STAILQ_INIT(&args.pa_events);
SLIST_INIT(&args.pa_targets);
bzero(&ds_start, sizeof(ds_start));
bzero(&ds_end, sizeof(ds_end));
ev = NULL;
@ -535,7 +636,7 @@ main(int argc, char **argv)
if (option == 'P' || option == 'p') {
args.pa_flags |= FLAG_HAS_PROCESS_PMCS;
args.pa_required |= (FLAG_HAS_COMMANDLINE |
FLAG_HAS_PID);
FLAG_HAS_TARGET);
}
if (option == 'P' || option == 'S') {
@ -581,7 +682,7 @@ main(int argc, char **argv)
(void) strncpy(ev->ev_name, optarg, c);
*(ev->ev_name + c) = '\0';
STAILQ_INSERT_TAIL(&args.pa_head, ev, ev_next);
STAILQ_INSERT_TAIL(&args.pa_events, ev, ev_next);
if (option == 's' || option == 'S')
pmcstat_clone_event_descriptor(&args, ev,
@ -633,15 +734,11 @@ main(int argc, char **argv)
args.pa_flags |= FLAG_READ_LOGFILE;
break;
case 't': /* target pid */
pid = strtol(optarg, &end, 0);
if (*end != '\0' || pid <= 0)
errx(EX_USAGE, "ERROR: Illegal pid value "
"\"%s\".", optarg);
case 't': /* target pid or process name */
pmcstat_find_targets(&args, optarg);
args.pa_flags |= FLAG_HAS_PID;
args.pa_flags |= FLAG_HAS_TARGET;
args.pa_required |= FLAG_HAS_PROCESS_PMCS;
args.pa_pid = pid;
break;
case 'v': /* verbose */
@ -690,32 +787,32 @@ main(int argc, char **argv)
errmsg = NULL;
if (args.pa_flags & FLAG_HAS_COMMANDLINE)
errmsg = "a command line specification";
else if (args.pa_flags & FLAG_HAS_PID)
else if (args.pa_flags & FLAG_HAS_TARGET)
errmsg = "option -t";
else if (!STAILQ_EMPTY(&args.pa_head))
else if (!STAILQ_EMPTY(&args.pa_events))
errmsg = "a PMC event specification";
if (errmsg)
errx(EX_USAGE, "ERROR: option -R may not be used with "
"%s.", errmsg);
} else if (STAILQ_EMPTY(&args.pa_head))
} else if (STAILQ_EMPTY(&args.pa_events))
/* All other uses require a PMC spec. */
pmcstat_show_usage();
/* check for -t pid without a process PMC spec */
if ((args.pa_required & FLAG_HAS_PID) &&
if ((args.pa_required & FLAG_HAS_TARGET) &&
(args.pa_flags & FLAG_HAS_PROCESS_PMCS) == 0)
errx(EX_USAGE, "ERROR: option -t requires a process mode PMC "
"to be specified.");
/* check for process-mode options without a command or -t pid */
if ((args.pa_required & FLAG_HAS_PROCESS_PMCS) &&
(args.pa_flags & (FLAG_HAS_COMMANDLINE | FLAG_HAS_PID)) == 0)
(args.pa_flags & (FLAG_HAS_COMMANDLINE | FLAG_HAS_TARGET)) == 0)
errx(EX_USAGE, "ERROR: options -d, -E, -p, -P, and -W require "
"a command line or target process.");
/* check for -p | -P without a target process of some sort */
if ((args.pa_required & (FLAG_HAS_COMMANDLINE | FLAG_HAS_PID)) &&
(args.pa_flags & (FLAG_HAS_COMMANDLINE | FLAG_HAS_PID)) == 0)
if ((args.pa_required & (FLAG_HAS_COMMANDLINE | FLAG_HAS_TARGET)) &&
(args.pa_flags & (FLAG_HAS_COMMANDLINE | FLAG_HAS_TARGET)) == 0)
errx(EX_USAGE, "ERROR: options -P and -p require a "
"target process or a command line.");
@ -743,12 +840,6 @@ main(int argc, char **argv)
errx(EX_USAGE, "ERROR: options -n and -O require at least "
"one sampling mode PMC to be specified.");
if ((args.pa_flags & (FLAG_HAS_PID | FLAG_HAS_COMMANDLINE)) ==
(FLAG_HAS_PID | FLAG_HAS_COMMANDLINE))
errx(EX_USAGE,
"ERROR: option -t cannot be specified with a command "
"line.");
/* check if -g is being used correctly */
if ((args.pa_flags & FLAG_DO_GPROF) &&
!(args.pa_flags & (FLAG_HAS_SAMPLING_PMCS|FLAG_READ_LOGFILE)))
@ -894,7 +985,7 @@ main(int argc, char **argv)
* Allocate PMCs.
*/
STAILQ_FOREACH(ev, &args.pa_head, ev_next) {
STAILQ_FOREACH(ev, &args.pa_events, ev_next) {
if (pmc_allocate(ev->ev_spec, ev->ev_mode,
ev->ev_flags, ev->ev_cpu, &ev->ev_pmcid) < 0)
err(EX_OSERR, "ERROR: Cannot allocate %s-mode pmc with "
@ -909,7 +1000,7 @@ main(int argc, char **argv)
}
/* compute printout widths */
STAILQ_FOREACH(ev, &args.pa_head, ev_next) {
STAILQ_FOREACH(ev, &args.pa_events, ev_next) {
int counter_width;
int display_width;
int header_width;
@ -987,9 +1078,19 @@ main(int argc, char **argv)
err(EX_OSERR, "ERROR: Cannot retrieve driver statistics");
/* Attach process pmcs to the target process. */
if (args.pa_pid != -1)
if (args.pa_flags & FLAG_HAS_TARGET) {
if (SLIST_EMPTY(&args.pa_targets))
errx(EX_DATAERR, "ERROR: No matching target "
"processes.");
else
pmcstat_attach_pmcs(&args);
if (pmcstat_kvm) {
kvm_close(pmcstat_kvm);
pmcstat_kvm = NULL;
}
}
/* start the pmcs */
pmcstat_start_pmcs(&args);
@ -1069,9 +1170,7 @@ main(int argc, char **argv)
} else if (kev.ident == SIGINT) {
/* Kill the child process if we started it */
if (args.pa_flags & FLAG_HAS_COMMANDLINE)
if (kill(args.pa_pid, SIGINT) != 0)
err(EX_OSERR, "ERROR: cannot "
"signal child process");
pmcstat_kill_process(&args);
runstate = PMCSTAT_FINISHED;
} else if (kev.ident == SIGWINCH) {
if (ioctl(fileno(args.pa_printfile),

View File

@ -1,5 +1,5 @@
/*-
* Copyright (c) 2005-2006, Joseph Koshy
* Copyright (c) 2005-2007, Joseph Koshy
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -29,7 +29,7 @@
#ifndef _PMCSTAT_H_
#define _PMCSTAT_H_
#define FLAG_HAS_PID 0x00000001 /* explicit pid */
#define FLAG_HAS_TARGET 0x00000001 /* process target */
#define FLAG_HAS_WAIT_INTERVAL 0x00000002 /* -w secs */
#define FLAG_HAS_OUTPUT_LOGFILE 0x00000004 /* -O file or pipe */
#define FLAG_HAS_COMMANDLINE 0x00000008 /* command */
@ -94,11 +94,15 @@ struct pmcstat_ev {
char *ev_spec; /* event specification */
};
struct pmcstat_target {
SLIST_ENTRY(pmcstat_target) pt_next;
pid_t pt_pid;
};
struct pmcstat_args {
int pa_flags; /* argument flags */
int pa_required; /* required features */
int pa_verbosity; /* verbosity level */
pid_t pa_pid; /* attached to pid */
FILE *pa_printfile; /* where to send printed output */
int pa_logfd; /* output log file */
char *pa_inputpath; /* path to input log */
@ -111,7 +115,8 @@ struct pmcstat_args {
double pa_interval; /* printing interval in seconds */
int pa_argc;
char **pa_argv;
STAILQ_HEAD(, pmcstat_ev) pa_head;
STAILQ_HEAD(, pmcstat_ev) pa_events;
SLIST_HEAD(, pmcstat_target) pa_targets;
} args;
/* Function prototypes */
@ -121,7 +126,9 @@ void pmcstat_clone_event_descriptor(struct pmcstat_args *_a,
struct pmcstat_ev *_ev, uint32_t _cpumask);
int pmcstat_close_log(struct pmcstat_args *_a);
void pmcstat_create_process(struct pmcstat_args *_a);
void pmcstat_find_targets(struct pmcstat_args *_a, const char *_arg);
void pmcstat_initialize_logging(struct pmcstat_args *_a);
void pmcstat_kill_process(struct pmcstat_args *_a);
int pmcstat_open_log(const char *_p, int _mode);
void pmcstat_print_counters(struct pmcstat_args *_a);
void pmcstat_print_headers(struct pmcstat_args *_a);