From 0dfc7735639067a12a4ccc63487f67169c17b9e1 Mon Sep 17 00:00:00 2001 From: jkoshy Date: Fri, 27 Apr 2007 12:09:31 +0000 Subject: 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. --- usr.sbin/pmcstat/Makefile | 2 +- usr.sbin/pmcstat/pmcstat.8 | 39 +++++---- usr.sbin/pmcstat/pmcstat.c | 197 ++++++++++++++++++++++++++++++++++----------- usr.sbin/pmcstat/pmcstat.h | 15 +++- 4 files changed, 184 insertions(+), 69 deletions(-) (limited to 'usr.sbin/pmcstat') diff --git a/usr.sbin/pmcstat/Makefile b/usr.sbin/pmcstat/Makefile index 102fe38..d212c3f 100644 --- a/usr.sbin/pmcstat/Makefile +++ b/usr.sbin/pmcstat/Makefile @@ -6,7 +6,7 @@ PROG= pmcstat MAN= pmcstat.8 DPADD= ${LIBPMC} ${LIBM} -LDADD= -lpmc -lm +LDADD= -lkvm -lpmc -lm WARNS?= 6 diff --git a/usr.sbin/pmcstat/pmcstat.8 b/usr.sbin/pmcstat/pmcstat.8 index cd7ed74..951dba6 100644 --- a/usr.sbin/pmcstat/pmcstat.8 +++ b/usr.sbin/pmcstat/pmcstat.8 @@ -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" diff --git a/usr.sbin/pmcstat/pmcstat.c b/usr.sbin/pmcstat/pmcstat.c index ae52c3b..b0c2600 100644 --- a/usr.sbin/pmcstat/pmcstat.c +++ b/usr.sbin/pmcstat/pmcstat.c @@ -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 #include +#include #include #include #include #include #include #include +#include #include #include #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -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(®, spec, REG_EXTENDED|REG_NOSUB)) != 0) { + regerror(rv, ®, 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(®, kp->ki_comm, 1, ®match, 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, ®, errbuf, sizeof(errbuf)); + errx(EX_SOFTWARE, "ERROR: Regex evalation failed: %s", + errbuf); + } + } + + regfree(®); + + return; + + outofmemory: + errx(EX_SOFTWARE, "Out of memory."); + /*NOTREACHED*/ +} + uint32_t pmcstat_get_cpumask(const char *cpuspec) { @@ -252,11 +336,29 @@ pmcstat_get_cpumask(const char *cpuspec) } 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,8 +1078,18 @@ 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) - pmcstat_attach_pmcs(&args); + 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), diff --git a/usr.sbin/pmcstat/pmcstat.h b/usr.sbin/pmcstat/pmcstat.h index 4a6ff4a..5b062a9 100644 --- a/usr.sbin/pmcstat/pmcstat.h +++ b/usr.sbin/pmcstat/pmcstat.h @@ -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); -- cgit v1.1