Changeset View
Changeset View
Standalone View
Standalone View
usr.sbin/pmcstat/pmcstat.c
Show First 20 Lines • Show All 53 Lines • ▼ Show 20 Lines | |||||
#include <libgen.h> | #include <libgen.h> | ||||
#include <limits.h> | #include <limits.h> | ||||
#include <math.h> | #include <math.h> | ||||
#include <pmc.h> | #include <pmc.h> | ||||
#include <pmclog.h> | #include <pmclog.h> | ||||
#include <regex.h> | #include <regex.h> | ||||
#include <signal.h> | #include <signal.h> | ||||
#include <stdarg.h> | #include <stdarg.h> | ||||
#include <stdbool.h> | |||||
#include <stdint.h> | #include <stdint.h> | ||||
#include <stdio.h> | #include <stdio.h> | ||||
#include <stdlib.h> | #include <stdlib.h> | ||||
#include <string.h> | #include <string.h> | ||||
#include <sysexits.h> | #include <sysexits.h> | ||||
#include <unistd.h> | #include <unistd.h> | ||||
#include <libpmcstat.h> | #include <libpmcstat.h> | ||||
▲ Show 20 Lines • Show All 41 Lines • ▼ Show 20 Lines | |||||
int pmcstat_displayheight = DEFAULT_DISPLAY_HEIGHT; | int pmcstat_displayheight = DEFAULT_DISPLAY_HEIGHT; | ||||
int pmcstat_displaywidth = DEFAULT_DISPLAY_WIDTH; | int pmcstat_displaywidth = DEFAULT_DISPLAY_WIDTH; | ||||
static int pmcstat_sockpair[NSOCKPAIRFD]; | static int pmcstat_sockpair[NSOCKPAIRFD]; | ||||
static int pmcstat_kq; | static int pmcstat_kq; | ||||
static kvm_t *pmcstat_kvm; | static kvm_t *pmcstat_kvm; | ||||
static struct kinfo_proc *pmcstat_plist; | static struct kinfo_proc *pmcstat_plist; | ||||
struct pmcstat_args args; | struct pmcstat_args args; | ||||
static bool libpmc_initialized = false; | |||||
static void | static void | ||||
pmcstat_get_cpumask(const char *cpuspec, cpuset_t *cpumask) | pmcstat_get_cpumask(const char *cpuspec, cpuset_t *cpumask) | ||||
{ | { | ||||
int cpu; | int cpu; | ||||
const char *s; | const char *s; | ||||
char *end; | char *end; | ||||
▲ Show 20 Lines • Show All 287 Lines • ▼ Show 20 Lines | pmcstat_topexit(void) | ||||
/* | /* | ||||
* Shutdown ncurses. | * Shutdown ncurses. | ||||
*/ | */ | ||||
clrtoeol(); | clrtoeol(); | ||||
refresh(); | refresh(); | ||||
endwin(); | endwin(); | ||||
} | } | ||||
static inline void | |||||
libpmc_initialize(int *npmc) | |||||
{ | |||||
if (libpmc_initialized) | |||||
return; | |||||
if (pmc_init() < 0) | |||||
err(EX_UNAVAILABLE, "ERROR: Initialization of the pmc(3)" | |||||
" library failed"); | |||||
/* assume all CPUs are identical */ | |||||
if ((*npmc = pmc_npmc(0)) < 0) | |||||
err(EX_OSERR, "ERROR: Cannot determine the number of PMCs on " | |||||
"CPU %d", 0); | |||||
libpmc_initialized = true; | |||||
} | |||||
/* | /* | ||||
* Main | * Main | ||||
*/ | */ | ||||
int | int | ||||
main(int argc, char **argv) | main(int argc, char **argv) | ||||
{ | { | ||||
cpuset_t cpumask, rootmask; | cpuset_t cpumask, dommask, rootmask; | ||||
double interval; | double interval; | ||||
double duration; | double duration; | ||||
int option, npmc; | int option, npmc; | ||||
int c, check_driver_stats; | int c, check_driver_stats; | ||||
int do_callchain, do_descendants, do_logproccsw, do_logprocexit; | int do_callchain, do_descendants, do_logproccsw, do_logprocexit; | ||||
int do_print, do_read, do_listcounters, do_descr; | int do_print, do_read, do_listcounters, do_descr, domains; | ||||
int do_userspace; | int do_userspace, i; | ||||
size_t len; | size_t len; | ||||
int graphdepth; | int graphdepth; | ||||
int pipefd[2], rfd; | int pipefd[2], rfd; | ||||
int use_cumulative_counts; | int use_cumulative_counts; | ||||
short cf, cb; | short cf, cb; | ||||
uint64_t current_sampling_count; | uint64_t current_sampling_count; | ||||
char *end, *tmp, *event; | char *end, *tmp, *event; | ||||
const char *errmsg, *graphfilename; | const char *errmsg, *graphfilename; | ||||
enum pmcstat_state runstate; | enum pmcstat_state runstate; | ||||
struct pmc_driverstats ds_start, ds_end; | struct pmc_driverstats ds_start, ds_end; | ||||
struct pmcstat_ev *ev; | struct pmcstat_ev *ev; | ||||
struct sigaction sa; | struct sigaction sa; | ||||
struct kevent kev; | struct kevent kev; | ||||
struct winsize ws; | struct winsize ws; | ||||
struct stat sb; | struct stat sb; | ||||
char buffer[PATH_MAX]; | char buffer[PATH_MAX]; | ||||
uint32_t caps; | |||||
check_driver_stats = 0; | check_driver_stats = 0; | ||||
current_sampling_count = 0; | current_sampling_count = 0; | ||||
do_callchain = 1; | do_callchain = 1; | ||||
do_descr = 0; | do_descr = 0; | ||||
do_descendants = 0; | do_descendants = 0; | ||||
do_userspace = 0; | do_userspace = 0; | ||||
do_logproccsw = 0; | do_logproccsw = 0; | ||||
do_logprocexit = 0; | do_logprocexit = 0; | ||||
do_listcounters = 0; | do_listcounters = 0; | ||||
domains = 0; | |||||
use_cumulative_counts = 0; | use_cumulative_counts = 0; | ||||
graphfilename = "-"; | graphfilename = "-"; | ||||
args.pa_required = 0; | args.pa_required = 0; | ||||
args.pa_flags = 0; | args.pa_flags = 0; | ||||
args.pa_verbosity = 1; | args.pa_verbosity = 1; | ||||
args.pa_logfd = -1; | args.pa_logfd = -1; | ||||
args.pa_fsroot = ""; | args.pa_fsroot = ""; | ||||
args.pa_samplesdir = "."; | args.pa_samplesdir = "."; | ||||
Show All 13 Lines | main(int argc, char **argv) | ||||
args.pa_mergepmc = 0; | args.pa_mergepmc = 0; | ||||
args.pa_duration = 0.0; | args.pa_duration = 0.0; | ||||
STAILQ_INIT(&args.pa_events); | STAILQ_INIT(&args.pa_events); | ||||
SLIST_INIT(&args.pa_targets); | SLIST_INIT(&args.pa_targets); | ||||
bzero(&ds_start, sizeof(ds_start)); | bzero(&ds_start, sizeof(ds_start)); | ||||
bzero(&ds_end, sizeof(ds_end)); | bzero(&ds_end, sizeof(ds_end)); | ||||
ev = NULL; | ev = NULL; | ||||
event = NULL; | event = NULL; | ||||
caps = 0; | |||||
CPU_ZERO(&cpumask); | CPU_ZERO(&cpumask); | ||||
/* Default to using the running system kernel. */ | /* Default to using the running system kernel. */ | ||||
len = 0; | len = 0; | ||||
if (sysctlbyname("kern.bootfile", NULL, &len, NULL, 0) == -1) | if (sysctlbyname("kern.bootfile", NULL, &len, NULL, 0) == -1) | ||||
err(EX_OSERR, "ERROR: Cannot determine path of running kernel"); | err(EX_OSERR, "ERROR: Cannot determine path of running kernel"); | ||||
args.pa_kernel = malloc(len); | args.pa_kernel = malloc(len); | ||||
if (args.pa_kernel == NULL) | if (args.pa_kernel == NULL) | ||||
errx(EX_SOFTWARE, "ERROR: Out of memory."); | errx(EX_SOFTWARE, "ERROR: Out of memory."); | ||||
if (sysctlbyname("kern.bootfile", args.pa_kernel, &len, NULL, 0) == -1) | if (sysctlbyname("kern.bootfile", args.pa_kernel, &len, NULL, 0) == -1) | ||||
err(EX_OSERR, "ERROR: Cannot determine path of running kernel"); | err(EX_OSERR, "ERROR: Cannot determine path of running kernel"); | ||||
len = sizeof(domains); | |||||
if (sysctlbyname("vm.ndomains", &domains, &len, NULL, 0) == -1) | |||||
err(EX_OSERR, "ERROR: Cannot get number of domains"); | |||||
/* | /* | ||||
* The initial CPU mask specifies the root mask of this process | * The initial CPU mask specifies the root mask of this process | ||||
* which is usually all CPUs in the system. | * which is usually all CPUs in the system. | ||||
*/ | */ | ||||
if (cpuset_getaffinity(CPU_LEVEL_ROOT, CPU_WHICH_PID, -1, | if (cpuset_getaffinity(CPU_LEVEL_ROOT, CPU_WHICH_PID, -1, | ||||
sizeof(rootmask), &rootmask) == -1) | sizeof(rootmask), &rootmask) == -1) | ||||
err(EX_OSERR, "ERROR: Cannot determine the root set of CPUs"); | err(EX_OSERR, "ERROR: Cannot determine the root set of CPUs"); | ||||
▲ Show 20 Lines • Show All 124 Lines • ▼ Show 20 Lines | case 'N': | ||||
do_callchain = !do_callchain; | do_callchain = !do_callchain; | ||||
args.pa_required |= FLAG_HAS_SAMPLING_PMCS; | args.pa_required |= FLAG_HAS_SAMPLING_PMCS; | ||||
break; | break; | ||||
case 'p': /* process virtual counting PMC */ | case 'p': /* process virtual counting PMC */ | ||||
case 's': /* system-wide counting PMC */ | case 's': /* system-wide counting PMC */ | ||||
case 'P': /* process virtual sampling PMC */ | case 'P': /* process virtual sampling PMC */ | ||||
case 'S': /* system-wide sampling PMC */ | case 'S': /* system-wide sampling PMC */ | ||||
caps = 0; | |||||
if ((ev = malloc(sizeof(*ev))) == NULL) | if ((ev = malloc(sizeof(*ev))) == NULL) | ||||
errx(EX_SOFTWARE, "ERROR: Out of memory."); | errx(EX_SOFTWARE, "ERROR: Out of memory."); | ||||
switch (option) { | switch (option) { | ||||
case 'p': ev->ev_mode = PMC_MODE_TC; break; | case 'p': ev->ev_mode = PMC_MODE_TC; break; | ||||
case 's': ev->ev_mode = PMC_MODE_SC; break; | case 's': ev->ev_mode = PMC_MODE_SC; break; | ||||
case 'P': ev->ev_mode = PMC_MODE_TS; break; | case 'P': ev->ev_mode = PMC_MODE_TS; break; | ||||
case 'S': ev->ev_mode = PMC_MODE_SS; break; | case 'S': ev->ev_mode = PMC_MODE_SS; break; | ||||
▲ Show 20 Lines • Show All 51 Lines • ▼ Show 20 Lines | case 'S': /* system-wide sampling PMC */ | ||||
/* extract event name */ | /* extract event name */ | ||||
c = strcspn(optarg, ", \t"); | c = strcspn(optarg, ", \t"); | ||||
ev->ev_name = malloc(c + 1); | ev->ev_name = malloc(c + 1); | ||||
if (ev->ev_name == NULL) | if (ev->ev_name == NULL) | ||||
errx(EX_SOFTWARE, "ERROR: Out of memory."); | errx(EX_SOFTWARE, "ERROR: Out of memory."); | ||||
(void) strncpy(ev->ev_name, optarg, c); | (void) strncpy(ev->ev_name, optarg, c); | ||||
*(ev->ev_name + c) = '\0'; | *(ev->ev_name + c) = '\0'; | ||||
libpmc_initialize(&npmc); | |||||
if (args.pa_flags & FLAG_HAS_SYSTEM_PMCS) { | |||||
if (pmc_allocate(ev->ev_spec, ev->ev_mode, | |||||
kevans: @np hit an assertion similar to @gallatin's below in `pmcstat -TS` mode. It turns out that this… | |||||
Not Done Inline ActionsSee D41978 for my attempt to remedy the situation. mhorne: See D41978 for my attempt to remedy the situation. | |||||
ev->ev_flags, ev->ev_cpu, &ev->ev_pmcid, | |||||
ev->ev_count) < 0) | |||||
err(EX_OSERR, "ERROR: Cannot allocate " | |||||
"system-mode pmc with specification" | |||||
" \"%s\"", ev->ev_spec); | |||||
if (pmc_capabilities(ev->ev_pmcid, &caps)) { | |||||
pmc_release(ev->ev_pmcid); | |||||
err(EX_OSERR, "ERROR: Cannot get pmc " | |||||
"capabilities"); | |||||
} | |||||
} | |||||
STAILQ_INSERT_TAIL(&args.pa_events, ev, ev_next); | STAILQ_INSERT_TAIL(&args.pa_events, ev, ev_next); | ||||
if ((caps & PMC_CAP_SYSWIDE) == PMC_CAP_SYSWIDE) | |||||
break; | |||||
if ((caps & PMC_CAP_DOMWIDE) == PMC_CAP_DOMWIDE) { | |||||
CPU_ZERO(&cpumask); | |||||
/* | |||||
* Get number of domains and allocate one | |||||
* counter in each. | |||||
* First already allocated. | |||||
*/ | |||||
for (i = 1; i < domains; i++) { | |||||
CPU_ZERO(&dommask); | |||||
cpuset_getaffinity(CPU_LEVEL_WHICH, | |||||
CPU_WHICH_DOMAIN, i, sizeof(dommask), | |||||
&dommask); | |||||
CPU_SET(CPU_FFS(&dommask) - 1, &cpumask); | |||||
} | |||||
args.pa_flags |= FLAGS_HAS_CPUMASK; | |||||
} | |||||
if (option == 's' || option == 'S') { | if (option == 's' || option == 'S') { | ||||
CPU_CLR(ev->ev_cpu, &cpumask); | CPU_CLR(ev->ev_cpu, &cpumask); | ||||
pmc_id_t saved_pmcid = ev->ev_pmcid; | |||||
ev->ev_pmcid = PMC_ID_INVALID; | |||||
pmcstat_clone_event_descriptor(ev, &cpumask, &args); | pmcstat_clone_event_descriptor(ev, &cpumask, &args); | ||||
ev->ev_pmcid = saved_pmcid; | |||||
CPU_SET(ev->ev_cpu, &cpumask); | CPU_SET(ev->ev_cpu, &cpumask); | ||||
} | } | ||||
break; | break; | ||||
case 'n': /* sampling count */ | case 'n': /* sampling count */ | ||||
current_sampling_count = strtol(optarg, &end, 0); | current_sampling_count = strtol(optarg, &end, 0); | ||||
if (*end != '\0' || current_sampling_count <= 0) | if (*end != '\0' || current_sampling_count <= 0) | ||||
▲ Show 20 Lines • Show All 321 Lines • ▼ Show 20 Lines | "ERROR: option -O is required if counting and sampling PMCs are specified together." | ||||
if (args.pa_flags & FLAG_DO_ANNOTATE) { | if (args.pa_flags & FLAG_DO_ANNOTATE) { | ||||
args.pa_graphfile = fopen(graphfilename, "w"); | args.pa_graphfile = fopen(graphfilename, "w"); | ||||
if (args.pa_graphfile == NULL) | if (args.pa_graphfile == NULL) | ||||
err(EX_OSERR, "ERROR: cannot open \"%s\" for writing", | err(EX_OSERR, "ERROR: cannot open \"%s\" for writing", | ||||
graphfilename); | graphfilename); | ||||
} | } | ||||
/* if we've been asked to process a log file, skip init */ | /* if we've been asked to process a log file, skip init */ | ||||
if ((args.pa_flags & FLAG_READ_LOGFILE) == 0) { | if ((args.pa_flags & FLAG_READ_LOGFILE) == 0) | ||||
if (pmc_init() < 0) | libpmc_initialize(&npmc); | ||||
err(EX_UNAVAILABLE, | |||||
"ERROR: Initialization of the pmc(3) library failed" | |||||
); | |||||
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 a kqueue */ | /* Allocate a kqueue */ | ||||
if ((pmcstat_kq = kqueue()) < 0) | if ((pmcstat_kq = kqueue()) < 0) | ||||
err(EX_OSERR, "ERROR: Cannot allocate kqueue"); | err(EX_OSERR, "ERROR: Cannot allocate kqueue"); | ||||
/* Setup the logfile as the source. */ | /* Setup the logfile as the source. */ | ||||
if (args.pa_flags & FLAG_READ_LOGFILE) { | if (args.pa_flags & FLAG_READ_LOGFILE) { | ||||
/* | /* | ||||
* Print the log in textual form if we haven't been | * Print the log in textual form if we haven't been | ||||
▲ Show 20 Lines • Show All 56 Lines • ▼ Show 20 Lines | check_driver_stats = (args.pa_flags & FLAG_HAS_SAMPLING_PMCS) || | ||||
(args.pa_flags & FLAG_HAS_OUTPUT_LOGFILE); | (args.pa_flags & FLAG_HAS_OUTPUT_LOGFILE); | ||||
/* | /* | ||||
if (args.pa_flags & FLAG_READ_LOGFILE) { | if (args.pa_flags & FLAG_READ_LOGFILE) { | ||||
* Allocate PMCs. | * Allocate PMCs. | ||||
*/ | */ | ||||
STAILQ_FOREACH(ev, &args.pa_events, ev_next) { | STAILQ_FOREACH(ev, &args.pa_events, ev_next) { | ||||
if (pmc_allocate(ev->ev_spec, ev->ev_mode, | if (ev->ev_pmcid == PMC_ID_INVALID && | ||||
pmc_allocate(ev->ev_spec, ev->ev_mode, | |||||
Not Done Inline ActionsThis utterly breaks system wide profiling for me on all arches that I've tried (amd64, arm64). The cause is that we don't add the pmc for CPU0, as the ID is not invalid. This causes samples from CPU0 to fail to be recognized, and to cause an assertion when analyzing logs: Core was generated by `pmcstat -R out.pmclog_cpi01 -z 32 -G out.pmcstat_cpi01'. Program terminated with signal SIGABRT, Aborted. Sent by thr_kill() from pid 13680 and user 0. #0 thr_kill () at thr_kill.S:4 4 thr_kill.S: No such file or directory. (gdb) bt #0 thr_kill () at thr_kill.S:4 #1 0x00000f8a5e2021a4 in __raise (s=s@entry=0x6) at /data/ocafirmware/FreeBSD/lib/libc/gen/raise.c:52 #2 0x00000f8a5e2b3039 in abort () at /data/ocafirmware/FreeBSD/lib/libc/stdlib/abort.c:67 #3 0x00000f8a5e1e5011 in __assert (func=<optimized out>, file=<optimized out>, line=<optimized out>, failedexpr=<optimized out>) at /data/ocafirmware/FreeBSD/lib/libc/gen/assert.c:51 #4 0x00000f8236919e09 in pmcstat_analyze_log (args=0xf823691e858 <args>, plugins=0xf823691e120 <plugins>, pmcstat_stats=0xf823691e980 <pmcstat_stats>, pmcstat_kernproc=0xf8a602a50f0, pmcstat_mergepmc=0x0, pmcstat_npmcs=0xf823691e9b4 <pmcstat_npmcs>, ps_samples_period=0xf823691e9b8 <ps_samples_period>) at /data/ocafirmware/FreeBSD/lib/libpmcstat/libpmcstat_logging.c:293 #5 0x00000f823691367a in pmcstat_process_log () at /data/ocafirmware/FreeBSD/usr.sbin/pmcstat/pmcstat_log.c:534 #6 0x00000f8236912fc8 in main (argc=<optimized out>, argv=<optimized out>) at /data/ocafirmware/FreeBSD/usr.sbin/pmcstat/pmcstat.c:1411 (gdb) frame 4 #4 0x00000f8236919e09 in pmcstat_analyze_log (args=0xf823691e858 <args>, plugins=0xf823691e120 <plugins>, pmcstat_stats=0xf823691e980 <pmcstat_stats>, pmcstat_kernproc=0xf8a602a50f0, pmcstat_mergepmc=0x0, pmcstat_npmcs=0xf823691e9b4 <pmcstat_npmcs>, ps_samples_period=0xf823691e9b8 <ps_samples_period>) at /data/ocafirmware/FreeBSD/lib/libpmcstat/libpmcstat_logging.c:293 293 /data/ocafirmware/FreeBSD/lib/libpmcstat/libpmcstat_logging.c: No such file or directory. gallatin: This utterly breaks system wide profiling for me on all arches that I've tried (amd64, arm64). | |||||
ev->ev_flags, ev->ev_cpu, &ev->ev_pmcid, | ev->ev_flags, ev->ev_cpu, &ev->ev_pmcid, | ||||
ev->ev_count) < 0) | ev->ev_count) < 0) | ||||
err(EX_OSERR, | err(EX_OSERR, | ||||
"ERROR: Cannot allocate %s-mode pmc with specification \"%s\"", | "ERROR: Cannot allocate %s-mode pmc with specification \"%s\"", | ||||
PMC_IS_SYSTEM_MODE(ev->ev_mode) ? | PMC_IS_SYSTEM_MODE(ev->ev_mode) ? | ||||
"system" : "process", ev->ev_spec); | "system" : "process", ev->ev_spec); | ||||
if (PMC_IS_SAMPLING_MODE(ev->ev_mode) && | if (PMC_IS_SAMPLING_MODE(ev->ev_mode) && | ||||
▲ Show 20 Lines • Show All 322 Lines • Show Last 20 Lines |
@np hit an assertion similar to @gallatin's below in pmcstat -TS mode. It turns out that this is too early to allocate system events, the pmclog hasn't been configured yet so libpmcstat will not be made aware of any system-mode counters that are allocated. (Thus, logs recorded with -O are also not aware of these)