Index: head/usr.sbin/pmc/cmd_pmc_filter.cc =================================================================== --- head/usr.sbin/pmc/cmd_pmc_filter.cc (revision 334873) +++ head/usr.sbin/pmc/cmd_pmc_filter.cc (revision 334874) @@ -1,363 +1,363 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2018, Matthew Macy * * 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 __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 #include #include #include #include #include #include #include #include #include #include #include "cmd_pmc.h" #include #include #include #include using namespace std; using std::unordered_map; typedef unordered_map < int ,string > idmap; typedef pair < int ,string > identry; #define LIST_MAX 64 static struct option longopts[] = { {"lwps", required_argument, NULL, 't'}, {"pids", required_argument, NULL, 'p'}, {"threads", required_argument, NULL, 'T'}, {"processes", required_argument, NULL, 'P'}, {"events", required_argument, NULL, 'e'}, {NULL, 0, NULL, 0} }; -static void +static void __dead2 usage(void) { errx(EX_USAGE, "\t filter log file\n" "\t -e , --events -- comma-delimited list of events to filter on\n" "\t -p , --pids -- comma-delimited list of pids to filter on\n" "\t -P , --processes -- comma-delimited list of process names to filter on\n" "\t -t , --lwps -- comma-delimited list of lwps to filter on\n" "\t -T , --threads -- comma-delimited list of thread names to filter on\n" "\t -x -- toggle inclusive filtering\n" ); } static void parse_intlist(char *strlist, uint32_t *intlist, int *pcount, int (*fn) (const char *)) { char *token; int count, tokenval; count = 0; while ((token = strsep(&strlist, ",")) != NULL && count < LIST_MAX) { if ((tokenval = fn(token)) < 0) errx(EX_USAGE, "ERROR: %s not usable value", token); intlist[count++] = tokenval; } *pcount = count; } static void parse_events(char *strlist, uint32_t intlist[LIST_MAX], int *pcount, char *cpuid) { char *token; int count, tokenval; count = 0; while ((token = strsep(&strlist, ",")) != NULL && count < LIST_MAX) { if ((tokenval = pmc_pmu_idx_get_by_event(cpuid, token)) < 0) errx(EX_USAGE, "ERROR: %s not usable value", token); intlist[count++] = tokenval; } *pcount = count; } static void parse_names(char *strlist, char *namelist[LIST_MAX], int *pcount) { char *token; int count; count = 0; while ((token = strsep(&strlist, ",")) != NULL && count < LIST_MAX) { namelist[count++] = token; } *pcount = count; } struct pmcid_ent { uint32_t pe_pmcid; uint32_t pe_idx; }; #define _PMCLOG_TO_HEADER(T,L) \ ((PMCLOG_HEADER_MAGIC << 24) | \ (PMCLOG_TYPE_ ## T << 16) | \ ((L) & 0xFFFF)) static bool pmc_find_name(idmap & map, uint32_t id, char *list[LIST_MAX], int count) { int i; auto kvpair = map.find(id); if (kvpair == map.end()) { printf("unknown id: %d\n", id); return (false); } auto p = list; for (i = 0; i < count; i++, p++) { if (strstr(kvpair->second.c_str(), *p) != NULL) return (true); } return (false); } static void pmc_log_event(int fd, struct pmclog_ev *ev, bool json) { int len; void *buf; if (json) { string ret = event_to_json(ev); buf = (void*)ret.c_str(); len = ret.size(); } else { len = ev->pl_len; buf = ev->pl_data; } if (write(fd, buf, len) != (ssize_t)len) errx(EX_OSERR, "ERROR: failed output write"); } static void pmc_filter_handler(uint32_t *lwplist, int lwpcount, uint32_t *pidlist, int pidcount, char *events, char *processes, char *threads, bool exclusive, bool json, int infd, int outfd) { struct pmclog_ev ev; struct pmclog_parse_state *ps; struct pmcid_ent *pe; uint32_t eventlist[LIST_MAX]; char cpuid[PMC_CPUID_LEN]; char *proclist[LIST_MAX]; char *threadlist[LIST_MAX]; int i, pmccount, copies, eventcount; int proccount, threadcount; uint32_t idx; idmap pidmap, tidmap; if ((ps = static_cast < struct pmclog_parse_state *>(pmclog_open(infd)))== NULL) errx(EX_OSERR, "ERROR: Cannot allocate pmclog parse state: %s\n", strerror(errno)); threadcount = proccount = eventcount = pmccount = 0; if (processes) parse_names(processes, proclist, &proccount); if (threads) parse_names(threads, threadlist, &threadcount); while (pmclog_read(ps, &ev) == 0) { if (ev.pl_type == PMCLOG_TYPE_INITIALIZE) memcpy(cpuid, ev.pl_u.pl_i.pl_cpuid, PMC_CPUID_LEN); if (ev.pl_type == PMCLOG_TYPE_PMCALLOCATE) pmccount++; } if (events) parse_events(events, eventlist, &eventcount, cpuid); lseek(infd, 0, SEEK_SET); pmclog_close(ps); if ((ps = static_cast < struct pmclog_parse_state *>(pmclog_open(infd)))== NULL) errx(EX_OSERR, "ERROR: Cannot allocate pmclog parse state: %s\n", strerror(errno)); if ((pe = (typeof(pe)) malloc(sizeof(*pe) * pmccount)) == NULL) errx(EX_OSERR, "ERROR: failed to allocate pmcid map"); i = 0; while (pmclog_read(ps, &ev) == 0 && i < pmccount) { if (ev.pl_type == PMCLOG_TYPE_PMCALLOCATE) { pe[i].pe_pmcid = ev.pl_u.pl_a.pl_pmcid; pe[i].pe_idx = ev.pl_u.pl_a.pl_event; i++; } } lseek(infd, 0, SEEK_SET); pmclog_close(ps); if ((ps = static_cast < struct pmclog_parse_state *>(pmclog_open(infd)))== NULL) errx(EX_OSERR, "ERROR: Cannot allocate pmclog parse state: %s\n", strerror(errno)); copies = 0; while (pmclog_read(ps, &ev) == 0) { if (ev.pl_type == PMCLOG_TYPE_THR_CREATE) tidmap[ev.pl_u.pl_tc.pl_tid] = ev.pl_u.pl_tc.pl_tdname; if (ev.pl_type == PMCLOG_TYPE_PROC_CREATE) pidmap[ev.pl_u.pl_pc.pl_pid] = ev.pl_u.pl_pc.pl_pcomm; if (ev.pl_type != PMCLOG_TYPE_CALLCHAIN) { pmc_log_event(outfd, &ev, json); continue; } if (pidcount) { for (i = 0; i < pidcount; i++) if (pidlist[i] == ev.pl_u.pl_cc.pl_pid) break; if ((i == pidcount) == exclusive) continue; } if (lwpcount) { for (i = 0; i < lwpcount; i++) if (lwplist[i] == ev.pl_u.pl_cc.pl_tid) break; if ((i == lwpcount) == exclusive) continue; } if (eventcount) { for (i = 0; i < pmccount; i++) { if (pe[i].pe_pmcid == ev.pl_u.pl_cc.pl_pmcid) break; } if (i == pmccount) errx(EX_USAGE, "ERROR: unallocated pmcid: %d\n", ev.pl_u.pl_cc.pl_pmcid); idx = pe[i].pe_idx; for (i = 0; i < eventcount; i++) { if (idx == eventlist[i]) break; } if ((i == eventcount) == exclusive) continue; } if (proccount && pmc_find_name(pidmap, ev.pl_u.pl_cc.pl_pid, proclist, proccount) == exclusive) continue; if (threadcount && pmc_find_name(tidmap, ev.pl_u.pl_cc.pl_tid, threadlist, threadcount) == exclusive) continue; pmc_log_event(outfd, &ev, json); } } int cmd_pmc_filter(int argc, char **argv) { char *lwps, *pids, *events, *processes, *threads; uint32_t lwplist[LIST_MAX]; uint32_t pidlist[LIST_MAX]; int option, lwpcount, pidcount; int prelogfd, postlogfd; bool exclusive, json; threads = processes = lwps = pids = events = NULL; lwpcount = pidcount = 0; json = exclusive = false; while ((option = getopt_long(argc, argv, "e:jp:t:xP:T:", longopts, NULL)) != -1) { switch (option) { case 'e': events = strdup(optarg); break; case 'j': json = true; break; case 'p': pids = strdup(optarg); break; case 'P': processes = strdup(optarg); break; case 't': lwps = strdup(optarg); break; case 'T': threads = strdup(optarg); break; case 'x': exclusive = !exclusive; break; case '?': default: usage(); } } argc -= optind; argv += optind; if (argc != 2) usage(); if (lwps) parse_intlist(lwps, lwplist, &lwpcount, atoi); if (pids) parse_intlist(pids, pidlist, &pidcount, atoi); if ((prelogfd = open(argv[0], O_RDONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) < 0) errx(EX_OSERR, "ERROR: Cannot open \"%s\" for reading: %s.", argv[0], strerror(errno)); if ((postlogfd = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) < 0) errx(EX_OSERR, "ERROR: Cannot open \"%s\" for writing: %s.", argv[1], strerror(errno)); pmc_filter_handler(lwplist, lwpcount, pidlist, pidcount, events, processes, threads, exclusive, json, prelogfd, postlogfd); return (0); } Index: head/usr.sbin/pmc/cmd_pmc_list.c =================================================================== --- head/usr.sbin/pmc/cmd_pmc_list.c (revision 334873) +++ head/usr.sbin/pmc/cmd_pmc_list.c (revision 334874) @@ -1,128 +1,128 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2018, Matthew Macy * * 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 __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 #include #include #include #include #include #include #include #include #include #include "cmd_pmc.h" static struct option longopts[] = { {"long-desc", no_argument, NULL, 'U'}, {"desc", no_argument, NULL, 'u'}, {"full", no_argument, NULL, 'f'}, {NULL, 0, NULL, 0} }; -static void +static void __dead2 usage(void) { errx(EX_USAGE, "\t list events\n" "\t -u, desc -- short description of event\n" "\t -U, long-desc -- long description of event\n" "\t -f, full -- full event details\n" ); } int cmd_pmc_list_events(int argc, char **argv) { int do_long_descr, do_descr, do_full; int option; do_long_descr = do_descr = do_full = 0; while ((option = getopt_long(argc, argv, "Uuf", longopts, NULL)) != -1) { switch (option) { case 'U': do_long_descr = 1; break; case 'u': do_descr = 1; break; case 'f': do_full = 1; break; case '?': default: usage(); } } argc -= optind; argv += optind; if ((do_long_descr | do_descr | do_full) && argc == 0) { warnx("event or event substring required when option provided\n"); usage(); } if (do_full) pmc_pmu_print_counter_full(argc ? argv[0] : NULL); else if (do_long_descr) pmc_pmu_print_counter_desc_long(argv[0]); else if (do_descr) pmc_pmu_print_counter_desc(argv[0]); else pmc_pmu_print_counters(argv[0]); return (0); } Index: head/usr.sbin/pmc/cmd_pmc_stat.c =================================================================== --- head/usr.sbin/pmc/cmd_pmc_stat.c (revision 334873) +++ head/usr.sbin/pmc/cmd_pmc_stat.c (revision 334874) @@ -1,487 +1,489 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2018, Matthew Macy * * 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 __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 #include #include #include #include #include #include #include #include #include #include "cmd_pmc.h" /* * Return the frequency of the kernel's statistics clock. */ static int getstathz(void) { int mib[2]; size_t size; struct clockinfo clockrate; mib[0] = CTL_KERN; mib[1] = KERN_CLOCKRATE; size = sizeof clockrate; if (sysctl(mib, 2, &clockrate, &size, NULL, 0) == -1) err(1, "sysctl kern.clockrate"); return clockrate.stathz; } #define STAT_MODE_NPMCS 6 #define FIXED_MODE_NPMCS 2 static struct timespec before_ts; #define CYCLES 0 #define INST 1 #define BR 2 #define IAP_START BR #define BR_MISS 3 #define CACHE 4 #define CACHE_MISS 5 static const char *pmc_stat_mode_names[] = { "cycles", "instructions", "branches", "branch-misses", "cache-references", "cache-misses", }; static int pmcstat_sockpair[NSOCKPAIRFD]; -static void +static void __dead2 usage(void) { errx(EX_USAGE, "\t get basic stats from command line program\n" "\t -j , --events comma-delimited list of event specifiers\n" ); } static void showtime(FILE *out, struct timespec *before, struct timespec *after, struct rusage *ru) { char decimal_point; uint64_t real, user, sys; (void)setlocale(LC_NUMERIC, ""); decimal_point = localeconv()->decimal_point[0]; after->tv_sec -= before->tv_sec; after->tv_nsec -= before->tv_nsec; - if (after->tv_nsec < 0) - after->tv_sec--, after->tv_nsec += 1000000000; + if (after->tv_nsec < 0) { + after->tv_sec--; + after->tv_nsec += 1000000000; + } real = (after->tv_sec * 1000000000 + after->tv_nsec) / 1000; user = ru->ru_utime.tv_sec * 1000000 + ru->ru_utime.tv_usec; sys = ru->ru_stime.tv_sec * 1000000 + ru->ru_stime.tv_usec; fprintf(out, "%13jd%c%02ld real\t\t\t#\t%2.02f%% cpu\n", (intmax_t)after->tv_sec, decimal_point, after->tv_nsec / 10000000, 100 * (double)(sys + user + 1) / (double)(real + 1)); fprintf(out, "%13jd%c%02ld user\t\t\t#\t%2.2f%% cpu\n", (intmax_t)ru->ru_utime.tv_sec, decimal_point, ru->ru_utime.tv_usec / 10000, 100 * (double)(user + 1) / (double)(real + 1)); fprintf(out, "%13jd%c%02ld sys\t\t\t#\t%2.02f%% cpu\n", (intmax_t)ru->ru_stime.tv_sec, decimal_point, ru->ru_stime.tv_usec / 10000, 100 * (double)(sys + 1) / (double)(real + 1)); } static const char *stat_mode_cntrs[STAT_MODE_NPMCS]; static const char *stat_mode_names[STAT_MODE_NPMCS]; static void pmc_stat_setup_stat(int system_mode, const char *arg) { const char *new_cntrs[STAT_MODE_NPMCS]; static const char **pmc_stat_mode_cntrs; struct pmcstat_ev *ev; char *counters, *counter; int i, c, start, newcnt; cpuset_t cpumask, rootmask; if (cpuset_getaffinity(CPU_LEVEL_ROOT, CPU_WHICH_PID, -1, sizeof(rootmask), &rootmask) == -1) err(EX_OSERR, "ERROR: Cannot determine the root set of CPUs"); CPU_COPY(&rootmask, &cpumask); if (pmc_pmu_stat_mode(&pmc_stat_mode_cntrs) != 0) errx(EX_USAGE, "ERROR: hwmpc.ko not loaded or stat not supported on host."); if (system_mode && geteuid() != 0) errx(EX_USAGE, "ERROR: system mode counters can only be used as root"); counters = NULL; for (i = 0; i < STAT_MODE_NPMCS; i++) { stat_mode_cntrs[i] = pmc_stat_mode_cntrs[i]; stat_mode_names[i] = pmc_stat_mode_names[i]; } if (arg) { counters = strdup(arg); newcnt = 0; while ((counter = strsep(&counters, ",")) != NULL && newcnt < STAT_MODE_NPMCS - IAP_START) { new_cntrs[newcnt++] = counter; if (pmc_pmu_sample_rate_get(counter) == DEFAULT_SAMPLE_COUNT) errx(EX_USAGE, "ERROR: %s not recognized on host", counter); } start = IAP_START + STAT_MODE_NPMCS - FIXED_MODE_NPMCS - newcnt; for (i = 0; i < newcnt; i++) { stat_mode_cntrs[start + i] = new_cntrs[i]; stat_mode_names[start + i] = new_cntrs[i]; } } if (system_mode) pmc_args.pa_flags |= FLAG_HAS_SYSTEM_PMCS; else pmc_args.pa_flags |= FLAG_HAS_PROCESS_PMCS; pmc_args.pa_flags |= FLAG_HAS_COUNTING_PMCS; pmc_args.pa_flags |= FLAG_HAS_COMMANDLINE | FLAG_HAS_TARGET; pmc_args.pa_flags |= FLAG_HAS_PIPE; pmc_args.pa_required |= FLAG_HAS_COMMANDLINE | FLAG_HAS_TARGET | FLAG_HAS_OUTPUT_LOGFILE; pmc_args.pa_outputpath = strdup("/dev/null"); pmc_args.pa_logfd = pmcstat_open_log(pmc_args.pa_outputpath, PMCSTAT_OPEN_FOR_WRITE); for (i = 0; i < STAT_MODE_NPMCS; i++) { if ((ev = malloc(sizeof(*ev))) == NULL) errx(EX_SOFTWARE, "ERROR: Out of memory."); if (system_mode) ev->ev_mode = PMC_MODE_SC; else ev->ev_mode = PMC_MODE_TC; ev->ev_spec = strdup(stat_mode_cntrs[i]); if (ev->ev_spec == NULL) errx(EX_SOFTWARE, "ERROR: Out of memory."); c = strcspn(strdup(stat_mode_cntrs[i]), ", \t"); ev->ev_name = malloc(c + 1); if (ev->ev_name == NULL) errx(EX_SOFTWARE, "ERROR: Out of memory."); (void)strncpy(ev->ev_name, stat_mode_cntrs[i], c); *(ev->ev_name + c) = '\0'; ev->ev_count = -1; ev->ev_flags = 0; ev->ev_flags |= PMC_F_DESCENDANTS; ev->ev_cumulative = 1; ev->ev_saved = 0LL; ev->ev_pmcid = PMC_ID_INVALID; STAILQ_INSERT_TAIL(&pmc_args.pa_events, ev, ev_next); if (system_mode) { ev->ev_cpu = CPU_FFS(&cpumask) - 1; CPU_CLR(ev->ev_cpu, &cpumask); pmcstat_clone_event_descriptor(ev, &cpumask, &pmc_args); CPU_SET(ev->ev_cpu, &cpumask); } else ev->ev_cpu = PMC_CPU_ANY; } if (clock_gettime(CLOCK_MONOTONIC, &before_ts)) err(1, "clock_gettime"); } static void pmc_stat_print_stat(struct rusage *ru) { struct pmcstat_ev *ev; struct timespec after; uint64_t cvals[STAT_MODE_NPMCS]; uint64_t ticks, value; int hz, i; if (ru) { hz = getstathz(); ticks = hz * (ru->ru_utime.tv_sec + ru->ru_stime.tv_sec) + hz * (ru->ru_utime.tv_usec + ru->ru_stime.tv_usec) / 1000000; if (clock_gettime(CLOCK_MONOTONIC, &after)) err(1, "clock_gettime"); /* * If our round-off on the tick calculation still puts us at 0, * then always assume at least one tick. */ if (ticks == 0) ticks = 1; fprintf(pmc_args.pa_printfile, "%16ld %s\t\t#\t%02.03f M/sec\n", ru->ru_minflt, "page faults", ((double)ru->ru_minflt / (double)ticks) / hz); fprintf(pmc_args.pa_printfile, "%16ld %s\t\t#\t%02.03f M/sec\n", ru->ru_nvcsw, "voluntary csw", ((double)ru->ru_nvcsw / (double)ticks) / hz); fprintf(pmc_args.pa_printfile, "%16ld %s\t#\t%02.03f M/sec\n", ru->ru_nivcsw, "involuntary csw", ((double)ru->ru_nivcsw / (double)ticks) / hz); } bzero(&cvals, sizeof(cvals)); STAILQ_FOREACH(ev, &pmc_args.pa_events, ev_next) { if (pmc_read(ev->ev_pmcid, &value) < 0) err(EX_OSERR, "ERROR: Cannot read pmc \"%s\"", ev->ev_name); for (i = 0; i < STAT_MODE_NPMCS; i++) if (strcmp(ev->ev_name, stat_mode_cntrs[i]) == 0) cvals[i] += value; } fprintf(pmc_args.pa_printfile, "%16jd %s\n", (uintmax_t)cvals[CYCLES], stat_mode_names[CYCLES]); fprintf(pmc_args.pa_printfile, "%16jd %s\t\t#\t%01.03f inst/cycle\n", (uintmax_t)cvals[INST], stat_mode_names[INST], (double)cvals[INST] / cvals[CYCLES]); fprintf(pmc_args.pa_printfile, "%16jd %s\n", (uintmax_t)cvals[BR], stat_mode_names[BR]); if (stat_mode_names[BR_MISS] == pmc_stat_mode_names[BR_MISS]) fprintf(pmc_args.pa_printfile, "%16jd %s\t\t#\t%.03f%%\n", (uintmax_t)cvals[BR_MISS], stat_mode_names[BR_MISS], 100 * ((double)cvals[BR_MISS] / cvals[BR])); else fprintf(pmc_args.pa_printfile, "%16jd %s\n", (uintmax_t)cvals[BR_MISS], stat_mode_names[BR_MISS]); fprintf(pmc_args.pa_printfile, "%16jd %s%s", (uintmax_t)cvals[CACHE], stat_mode_names[CACHE], stat_mode_names[CACHE] != pmc_stat_mode_names[CACHE] ? "\n" : ""); if (stat_mode_names[CACHE] == pmc_stat_mode_names[CACHE]) fprintf(pmc_args.pa_printfile, "\t#\t%.03f refs/inst\n", ((double)cvals[CACHE] / cvals[INST])); fprintf(pmc_args.pa_printfile, "%16jd %s%s", (uintmax_t)cvals[CACHE_MISS], stat_mode_names[CACHE_MISS], stat_mode_names[CACHE_MISS] != pmc_stat_mode_names[CACHE_MISS] ? "\n" : ""); if (stat_mode_names[CACHE_MISS] == pmc_stat_mode_names[CACHE_MISS]) fprintf(pmc_args.pa_printfile, "\t\t#\t%.03f%%\n", 100 * ((double)cvals[CACHE_MISS] / cvals[CACHE])); if (ru) showtime(pmc_args.pa_printfile, &before_ts, &after, ru); } static struct option longopts[] = { {"events", required_argument, NULL, 'j'}, {NULL, 0, NULL, 0} }; static int pmc_stat_internal(int argc, char **argv, int system_mode) { char *event, *r; struct sigaction sa; struct kevent kev; struct rusage ru; struct winsize ws; struct pmcstat_ev *ev; int c, option, runstate; int waitstatus, ru_valid, do_debug; do_debug = ru_valid = 0; r = event = NULL; while ((option = getopt_long(argc, argv, "dj:", longopts, NULL)) != -1) { switch (option) { case 'j': r = event = strdup(optarg); break; case 'd': do_debug = 1; break; case '?': default: usage(); } } pmc_args.pa_argc = (argc -= optind); pmc_args.pa_argv = (argv += optind); if (argc == 0) usage(); pmc_args.pa_flags |= FLAG_HAS_COMMANDLINE; pmc_stat_setup_stat(system_mode, event); free(r); bzero(&ru, sizeof(ru)); EV_SET(&kev, SIGINT, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); if (kevent(pmc_kq, &kev, 1, NULL, 0, NULL) < 0) err(EX_OSERR, "ERROR: Cannot register kevent for SIGINT"); EV_SET(&kev, SIGIO, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); if (kevent(pmc_kq, &kev, 1, NULL, 0, NULL) < 0) err(EX_OSERR, "ERROR: Cannot register kevent for SIGIO"); EV_SET(&kev, 0, EVFILT_TIMER, EV_ADD, 0, 1000, NULL); if (kevent(pmc_kq, &kev, 1, NULL, 0, NULL) < 0) err(EX_OSERR, "ERROR: Cannot register kevent for timer"); STAILQ_FOREACH(ev, &pmc_args.pa_events, ev_next) { if (pmc_allocate(ev->ev_spec, ev->ev_mode, ev->ev_flags, ev->ev_cpu, &ev->ev_pmcid, ev->ev_count) < 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); 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); } /* * An exec() failure of a forked child is signalled by the * child sending the parent a SIGCHLD. We don't register an * actual signal handler for SIGCHLD, but instead use our * kqueue to pick up the signal. */ EV_SET(&kev, SIGCHLD, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); if (kevent(pmc_kq, &kev, 1, NULL, 0, NULL) < 0) err(EX_OSERR, "ERROR: Cannot register kevent for SIGCHLD"); pmcstat_create_process(pmcstat_sockpair, &pmc_args, pmc_kq); if (SLIST_EMPTY(&pmc_args.pa_targets)) errx(EX_DATAERR, "ERROR: No matching target processes."); if (pmc_args.pa_flags & FLAG_HAS_PROCESS_PMCS) pmcstat_attach_pmcs(&pmc_args); /* start the pmcs */ pmc_util_start_pmcs(&pmc_args); /* start the (commandline) process if needed */ pmcstat_start_process(pmcstat_sockpair); /* 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 or we reached the time duration. */ runstate = PMCSTAT_RUNNING; do { if ((c = kevent(pmc_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 has exited */ if (wait4(pmc_util_get_pid(&pmc_args), &waitstatus, 0, &ru) > 0) { getrusage(RUSAGE_CHILDREN, &ru); ru_valid = 1; } break; case EVFILT_READ: /* log file data is present */ break; case EVFILT_TIMER: if (do_debug) pmc_stat_print_stat(NULL); break; case EVFILT_SIGNAL: if (kev.ident == SIGCHLD) { /* * The child process sends us a * SIGCHLD if its exec() failed. We * wait for it to exit and then exit * ourselves. */ (void)wait(&c); runstate = PMCSTAT_FINISHED; } else if (kev.ident == SIGIO) { /* * We get a SIGIO if a PMC loses all * of its targets, or if logfile * writes encounter an error. */ if (wait4(pmc_util_get_pid(&pmc_args), &waitstatus, 0, &ru) > 0) { getrusage(RUSAGE_CHILDREN, &ru); ru_valid = 1; } runstate = pmcstat_close_log(&pmc_args); } else if (kev.ident == SIGINT) { /* Kill the child process if we started it */ if (pmc_args.pa_flags & FLAG_HAS_COMMANDLINE) pmc_util_kill_process(&pmc_args); runstate = pmcstat_close_log(&pmc_args); } else if (kev.ident == SIGWINCH) { if (ioctl(fileno(pmc_args.pa_printfile), TIOCGWINSZ, &ws) < 0) err(EX_OSERR, "ERROR: Cannot determine window size"); pmc_displayheight = ws.ws_row - 1; pmc_displaywidth = ws.ws_col - 1; } else assert(0); break; } } while (runstate != PMCSTAT_FINISHED); if (!ru_valid) warnx("couldn't get rusage"); pmc_stat_print_stat(&ru); pmc_util_cleanup(&pmc_args); return (0); } int cmd_pmc_stat(int argc, char **argv) { return (pmc_stat_internal(argc, argv, 0)); } int cmd_pmc_stat_system(int argc, char **argv) { return (pmc_stat_internal(argc, argv, 1)); } Index: head/usr.sbin/pmc/cmd_pmc_summary.cc =================================================================== --- head/usr.sbin/pmc/cmd_pmc_summary.cc (revision 334873) +++ head/usr.sbin/pmc/cmd_pmc_summary.cc (revision 334874) @@ -1,220 +1,220 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2018, Matthew Macy * * 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 __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 #include #include #include #include #include #include #include #include #include #include #include "cmd_pmc.h" #include #include #include #include using std::unordered_map; typedef unordered_map idmap; typedef unordered_map intmap; typedef unordered_map strintmap; typedef std::pair sampleid; typedef std::pair samplename; typedef unordered_map > eventcountmap; #define P_KPROC 0x00004 /* Kernel process. */ -static void +static void __dead2 usage(void) { errx(EX_USAGE, "\t summarize log file\n" "\t -k , --topk show topk processes for each counter\n" ); } static int pmc_summary_handler(int logfd, int k, bool do_full) { struct pmclog_parse_state *ps; struct pmclog_ev ev; idmap pidmap, tidmap, eventnamemap; strintmap tideventmap, pideventmap; intmap eventmap, pmcidmap, ratemap; intmap kerntidmap, kernpidmap; eventcountmap countmap; ps = static_cast(pmclog_open(logfd)); if (ps == NULL) errx(EX_OSERR, "ERROR: Cannot allocate pmclog parse state: %s\n", strerror(errno)); while (pmclog_read(ps, &ev) == 0) { if (ev.pl_type == PMCLOG_TYPE_PMCALLOCATE) { pmcidmap[ev.pl_u.pl_a.pl_pmcid] = ev.pl_u.pl_a.pl_event; ratemap[ev.pl_u.pl_a.pl_event] = ev.pl_u.pl_a.pl_rate; eventnamemap[ev.pl_u.pl_a.pl_event] = ev.pl_u.pl_a.pl_evname; } if (ev.pl_type == PMCLOG_TYPE_THR_CREATE) { tidmap[ev.pl_u.pl_tc.pl_tid] = ev.pl_u.pl_tc.pl_tdname; kerntidmap[ev.pl_u.pl_tc.pl_tid] = !!(ev.pl_u.pl_tc.pl_flags & P_KPROC); if (tideventmap.find(ev.pl_u.pl_tc.pl_tdname) == tideventmap.end()) tideventmap[ev.pl_u.pl_tc.pl_tdname] = intmap(); } if (ev.pl_type == PMCLOG_TYPE_PROC_CREATE) { pidmap[ev.pl_u.pl_pc.pl_pid] = ev.pl_u.pl_pc.pl_pcomm; kernpidmap[ev.pl_u.pl_pc.pl_pid] = !!(ev.pl_u.pl_pc.pl_flags & P_KPROC); if (pideventmap.find(ev.pl_u.pl_pc.pl_pcomm) == pideventmap.end()) pideventmap[ev.pl_u.pl_pc.pl_pcomm] = intmap(); } if (ev.pl_type == PMCLOG_TYPE_CALLCHAIN) { auto event = pmcidmap[ev.pl_u.pl_cc.pl_pmcid]; if (event == 0) continue; eventmap[event]++; auto tidname = tidmap.find(ev.pl_u.pl_cc.pl_tid); auto pidname = pidmap.find(ev.pl_u.pl_cc.pl_pid); if (tidname != tidmap.end()) { auto &teventmap = tideventmap[tidname->second]; teventmap[event]++; } if (pidname != pidmap.end()) { auto &peventmap = pideventmap[pidname->second]; peventmap[event]++; } } } for (auto &pkv : pideventmap) for (auto &ekv : pkv.second) { auto &samplevec = countmap[ekv.first]; samplevec.emplace_back(ekv.second, pkv.first); } for (auto &kv : countmap) std::sort(kv.second.begin(), kv.second.end(), [](auto &a, auto &b) {return (a.first < b.first);}); if (do_full) { for (auto &kv : countmap) { auto &name = eventnamemap[kv.first]; auto rate = ratemap[kv.first]; std::cout << "idx: " << kv.first << " name: " << name << " rate: " << rate << std::endl; while (!kv.second.empty()) { auto &val = kv.second.back(); kv.second.pop_back(); std::cout << val.second << ": " << val.first << std::endl; } } return (0); } for (auto &kv : countmap) { auto &name = eventnamemap[kv.first]; auto rate = ratemap[kv.first]; std::cout << name << ":" << std::endl; for (auto i = 0; i < k; i++) { auto largest = kv.second.back(); kv.second.pop_back(); std::cout << "\t" << largest.second << ": " << largest.first*rate << std::endl; } } return (0); } static struct option longopts[] = { {"full", no_argument, NULL, 'f'}, {"topk", required_argument, NULL, 'k'}, {NULL, 0, NULL, 0} }; int cmd_pmc_summary(int argc, char **argv) { int option, logfd, k; bool do_full; do_full = false; k = 5; while ((option = getopt_long(argc, argv, "k:f", longopts, NULL)) != -1) { switch (option) { case 'f': do_full = 1; break; case 'k': k = atoi(optarg); break; case '?': default: usage(); } } argc -= optind; argv += optind; if (argc != 1) { printf("argc: %d\n", argc); for (int i = 0; i < argc; i++) printf("%s\n", argv[i]); usage(); } if ((logfd = open(argv[0], O_RDONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) < 0) errx(EX_OSERR, "ERROR: Cannot open \"%s\" for reading: %s.", argv[0], strerror(errno)); return (pmc_summary_handler(logfd, k, do_full)); } Index: head/usr.sbin/pmc/pmc.c =================================================================== --- head/usr.sbin/pmc/pmc.c (revision 334873) +++ head/usr.sbin/pmc/pmc.c (revision 334874) @@ -1,119 +1,119 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2018, Matthew Macy * * 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. * * $FreeBSD$ * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cmd_pmc.h" int pmc_displayheight = DEFAULT_DISPLAY_HEIGHT; int pmc_displaywidth = DEFAULT_DISPLAY_WIDTH; int pmc_kq; struct pmcstat_args pmc_args; struct pmcstat_pmcs pmcstat_pmcs = LIST_HEAD_INITIALIZER(pmcstat_pmcs); struct pmcstat_image_hash_list pmcstat_image_hash[PMCSTAT_NHASH]; struct pmcstat_process_hash_list pmcstat_process_hash[PMCSTAT_NHASH]; struct cmd_handler { const char *ch_name; cmd_disp_t ch_fn; }; static struct cmd_handler disp_table[] = { {"stat", cmd_pmc_stat}, {"stat-system", cmd_pmc_stat_system}, {"list-events", cmd_pmc_list_events}, {"filter", cmd_pmc_filter}, {"summary", cmd_pmc_summary}, {NULL, NULL} }; -static void -usage(void) +static void __dead2 +usage(void) { errx(EX_USAGE, "\t pmc management utility\n" "\t stat run program and print stats\n" "\t stat-system run program and print system wide stats for duration of execution\n" "\t list-events list PMC events available on host\n" "\t filter filter records by lwp, pid, or event\n" ); } static cmd_disp_t disp_lookup(char *name) { struct cmd_handler *hnd; for (hnd = disp_table; hnd->ch_name != NULL; hnd++) if (strcmp(hnd->ch_name, name) == 0) return (hnd->ch_fn); return (NULL); } int main(int argc, char **argv) { cmd_disp_t disp; pmc_args.pa_printfile = stderr; STAILQ_INIT(&pmc_args.pa_events); SLIST_INIT(&pmc_args.pa_targets); if (argc == 1) usage(); if ((disp = disp_lookup(argv[1])) == NULL) usage(); argc--; argv++; /* Allocate a kqueue */ if ((pmc_kq = kqueue()) < 0) err(EX_OSERR, "ERROR: Cannot allocate kqueue"); if (pmc_init() < 0) err(EX_UNAVAILABLE, "ERROR: Initialization of the pmc(3) library failed" ); return (disp(argc, argv)); }