diff --git a/lib/libpmc/libpmc_pmu_util.c b/lib/libpmc/libpmc_pmu_util.c index 84128171e9eb..d278ab3e6d3d 100644 --- a/lib/libpmc/libpmc_pmu_util.c +++ b/lib/libpmc/libpmc_pmu_util.c @@ -62,8 +62,8 @@ static struct pmu_alias pmu_alias_table[] = { {"BRANCH-MISSES-RETIRED", "BR_MISP_RETIRED.ALL_BRANCHES"}, {"cycles", "tsc-tsc"}, {"instructions", "inst-retired.any_p"}, - {"branch-mispredicts", "br_misp_retired.all_branches" }, - {"branches", "br_inst_retired.all_branches" }, + {"branch-mispredicts", "br_misp_retired.all_branches"}, + {"branches", "br_inst_retired.all_branches"}, {"interrupts", "hw_interrupts.received"}, {"ic-misses", "frontend_retired.l1i_miss"}, {NULL, NULL}, @@ -106,18 +106,22 @@ struct pmu_event_desc { }; static const struct pmu_events_map * -pmu_events_map_get(void) +pmu_events_map_get(const char *cpuid) { size_t s; char buf[64]; const struct pmu_events_map *pme; - if (sysctlbyname("kern.hwpmc.cpuid", (void *)NULL, &s, - (void *)NULL, 0) == -1) - return (NULL); - if (sysctlbyname("kern.hwpmc.cpuid", buf, &s, - (void *)NULL, 0) == -1) - return (NULL); + if (cpuid != NULL) { + memcpy(buf, cpuid, 64); + } else { + if (sysctlbyname("kern.hwpmc.cpuid", (void *)NULL, &s, + (void *)NULL, 0) == -1) + return (NULL); + if (sysctlbyname("kern.hwpmc.cpuid", buf, &s, + (void *)NULL, 0) == -1) + return (NULL); + } for (pme = pmu_events_map; pme->cpuid != NULL; pme++) if (strcmp(buf, pme->cpuid) == 0) return (pme); @@ -125,13 +129,13 @@ pmu_events_map_get(void) } static const struct pmu_event * -pmu_event_get(const char *event_name, int *idx) +pmu_event_get(const char *cpuid, const char *event_name, int *idx) { const struct pmu_events_map *pme; const struct pmu_event *pe; int i; - if ((pme = pmu_events_map_get()) == NULL) + if ((pme = pmu_events_map_get(cpuid)) == NULL) return (NULL); for (i = 0, pe = pme->table; pe->name || pe->desc || pe->event; pe++, i++) { if (pe->name == NULL) @@ -145,6 +149,18 @@ pmu_event_get(const char *event_name, int *idx) return (NULL); } +int +pmc_pmu_idx_get_by_event(const char *cpuid, const char *event) +{ + int idx; + const char *realname; + + realname = pmu_alias_get(event); + if (pmu_event_get(cpuid, realname, &idx) == NULL) + return (-1); + return (idx); +} + const char * pmc_pmu_event_get_by_idx(int idx) { @@ -152,7 +168,7 @@ pmc_pmu_event_get_by_idx(int idx) const struct pmu_event *pe; int i; - if ((pme = pmu_events_map_get()) == NULL) + if ((pme = pmu_events_map_get(NULL)) == NULL) return (NULL); for (i = 0, pe = pme->table; (pe->name || pe->desc || pe->event) && i < idx; pe++, i++); return (pe->name); @@ -218,9 +234,9 @@ pmc_pmu_sample_rate_get(const char *event_name) struct pmu_event_desc ped; event_name = pmu_alias_get(event_name); - if ((pe = pmu_event_get(event_name, NULL)) == NULL) + if ((pe = pmu_event_get(NULL, event_name, NULL)) == NULL) return (DEFAULT_SAMPLE_COUNT); - if (pe->alias && (pe = pmu_event_get(pe->alias, NULL)) == NULL) + if (pe->alias && (pe = pmu_event_get(NULL, pe->alias, NULL)) == NULL) return (DEFAULT_SAMPLE_COUNT); if (pe->event == NULL) return (DEFAULT_SAMPLE_COUNT); @@ -233,7 +249,7 @@ int pmc_pmu_enabled(void) { - return (pmu_events_map_get() != NULL); + return (pmu_events_map_get(NULL) != NULL); } void @@ -250,7 +266,7 @@ pmc_pmu_print_counters(const char *event_name) if (debug != NULL && strcmp(debug, "true") == 0) do_debug = 1; - if ((pme = pmu_events_map_get()) == NULL) + if ((pme = pmu_events_map_get(NULL)) == NULL) return; for (pe = pme->table; pe->name || pe->desc || pe->event; pe++) { if (pe->name == NULL) @@ -269,7 +285,7 @@ pmc_pmu_print_counter_desc(const char *ev) const struct pmu_events_map *pme; const struct pmu_event *pe; - if ((pme = pmu_events_map_get()) == NULL) + if ((pme = pmu_events_map_get(NULL)) == NULL) return; for (pe = pme->table; pe->name || pe->desc || pe->event; pe++) { if (pe->name == NULL) @@ -286,7 +302,7 @@ pmc_pmu_print_counter_desc_long(const char *ev) const struct pmu_events_map *pme; const struct pmu_event *pe; - if ((pme = pmu_events_map_get()) == NULL) + if ((pme = pmu_events_map_get(NULL)) == NULL) return; for (pe = pme->table; pe->name || pe->desc || pe->event; pe++) { if (pe->name == NULL) @@ -306,7 +322,7 @@ pmc_pmu_print_counter_full(const char *ev) const struct pmu_events_map *pme; const struct pmu_event *pe; - if ((pme = pmu_events_map_get()) == NULL) + if ((pme = pmu_events_map_get(NULL)) == NULL) return; for (pe = pme->table; pe->name || pe->desc || pe->event; pe++) { if (pe->name == NULL) @@ -350,9 +366,9 @@ pmc_pmu_pmcallocate(const char *event_name, struct pmc_op_pmcallocate *pm) bzero(iap, sizeof(*iap)); event_name = pmu_alias_get(event_name); pm->pm_caps |= (PMC_CAP_READ | PMC_CAP_WRITE); - if ((pe = pmu_event_get(event_name, &idx)) == NULL) + if ((pe = pmu_event_get(NULL, event_name, &idx)) == NULL) return (ENOENT); - if (pe->alias && (pe = pmu_event_get(pe->alias, &idx)) == NULL) + if (pe->alias && (pe = pmu_event_get(NULL, pe->alias, &idx)) == NULL) return (ENOENT); if (pe->event == NULL) return (ENOENT); @@ -361,11 +377,11 @@ pmc_pmu_pmcallocate(const char *event_name, struct pmc_op_pmcallocate *pm) if (strcasestr(event_name, "UNC_") == event_name || - strcasestr(event_name, "uncore") != NULL) { + strcasestr(event_name, "uncore") != NULL) { pm->pm_class = PMC_CLASS_UCP; pm->pm_caps |= PMC_CAP_QUALIFIER; } else if ((ped.ped_umask == -1) || - (ped.ped_event == 0x0 && ped.ped_umask == 0x3)) { + (ped.ped_event == 0x0 && ped.ped_umask == 0x3)) { pm->pm_class = PMC_CLASS_IAF; } else { pm->pm_class = PMC_CLASS_IAP; @@ -458,10 +474,17 @@ pmc_pmu_event_get_by_idx(int idx __unused) { return (NULL); } + int pmc_pmu_stat_mode(const char ***a __unused) { return (EOPNOTSUPP); } +int +pmc_pmu_idx_get_by_event(const char *e __unused) +{ + return (-1); +} + #endif diff --git a/lib/libpmc/pmc.h b/lib/libpmc/pmc.h index 186eb47e49f5..476f86fa9961 100644 --- a/lib/libpmc/pmc.h +++ b/lib/libpmc/pmc.h @@ -121,6 +121,7 @@ void pmc_pmu_print_counter_full(const char *); uint64_t pmc_pmu_sample_rate_get(const char *); int pmc_pmu_pmcallocate(const char *, struct pmc_op_pmcallocate *); const char *pmc_pmu_event_get_by_idx(int idx); +int pmc_pmu_idx_get_by_event(const char*, const char *); int pmc_pmu_stat_mode(const char ***); __END_DECLS diff --git a/lib/libpmc/pmclog.c b/lib/libpmc/pmclog.c index 8000fa856a79..4ed65ebad75b 100644 --- a/lib/libpmc/pmclog.c +++ b/lib/libpmc/pmclog.c @@ -78,28 +78,6 @@ __FBSDID("$FreeBSD$"); * performance critical paths. */ -enum pmclog_parser_state { - PL_STATE_NEW_RECORD, /* in-between records */ - PL_STATE_EXPECTING_HEADER, /* header being read */ - PL_STATE_PARTIAL_RECORD, /* header present but not the record */ - PL_STATE_ERROR /* parsing error encountered */ -}; - -struct pmclog_parse_state { - enum pmclog_parser_state ps_state; - enum pmc_cputype ps_arch; /* log file architecture */ - uint32_t ps_version; /* hwpmc version */ - int ps_initialized; /* whether initialized */ - int ps_count; /* count of records processed */ - off_t ps_offset; /* stream byte offset */ - union pmclog_entry ps_saved; /* saved partial log entry */ - int ps_svcount; /* #bytes saved */ - int ps_fd; /* active fd or -1 */ - char *ps_buffer; /* scratch buffer if fd != -1 */ - char *ps_data; /* current parse pointer */ - size_t ps_len; /* length of buffered data */ -}; - #define PMCLOG_HEADER_FROM_SAVED_STATE(PS) \ (* ((uint32_t *) &(PS)->ps_saved)) @@ -299,7 +277,7 @@ pmclog_get_event(void *cookie, char **data, ssize_t *len, } PMCLOG_INITIALIZE_READER(le, ps->ps_saved); - + ev->pl_data = le; PMCLOG_READ32(le,h); if (!PMCLOG_HEADER_CHECK_MAGIC(h)) { @@ -348,8 +326,8 @@ pmclog_get_event(void *cookie, char **data, ssize_t *len, case PMCLOG_TYPE_INITIALIZE: PMCLOG_READ32(le,ev->pl_u.pl_i.pl_version); PMCLOG_READ32(le,ev->pl_u.pl_i.pl_arch); - PMCLOG_READ32(le, noop); PMCLOG_READSTRING(le, ev->pl_u.pl_i.pl_cpuid, PMC_CPUID_LEN); + memcpy(ev->pl_u.pl_i.pl_cpuid, le, PMC_CPUID_LEN); ps->ps_version = ev->pl_u.pl_i.pl_version; ps->ps_arch = ev->pl_u.pl_i.pl_arch; ps->ps_initialized = 1; @@ -434,6 +412,7 @@ pmclog_get_event(void *cookie, char **data, ssize_t *len, ev->pl_offset = (ps->ps_offset += evlen); ev->pl_count = (ps->ps_count += 1); + ev->pl_len = evlen; ev->pl_state = PMCLOG_OK; return 0; diff --git a/lib/libpmc/pmclog.h b/lib/libpmc/pmclog.h index 72f6432d9a6f..0d2c7d0f8b4c 100644 --- a/lib/libpmc/pmclog.h +++ b/lib/libpmc/pmclog.h @@ -152,6 +152,8 @@ struct pmclog_ev { size_t pl_count; /* count of records so far */ struct timespec pl_ts; /* log entry timestamp */ enum pmclog_type pl_type; /* type of log entry */ + void *pl_data; + int pl_len; union { /* log entry data */ struct pmclog_ev_callchain pl_cc; struct pmclog_ev_closelog pl_cl; @@ -172,6 +174,28 @@ struct pmclog_ev { } pl_u; }; +enum pmclog_parser_state { + PL_STATE_NEW_RECORD, /* in-between records */ + PL_STATE_EXPECTING_HEADER, /* header being read */ + PL_STATE_PARTIAL_RECORD, /* header present but not the record */ + PL_STATE_ERROR /* parsing error encountered */ +}; + +struct pmclog_parse_state { + enum pmclog_parser_state ps_state; + enum pmc_cputype ps_arch; /* log file architecture */ + uint32_t ps_version; /* hwpmc version */ + int ps_initialized; /* whether initialized */ + int ps_count; /* count of records processed */ + off_t ps_offset; /* stream byte offset */ + union pmclog_entry ps_saved; /* saved partial log entry */ + int ps_svcount; /* #bytes saved */ + int ps_fd; /* active fd or -1 */ + char *ps_buffer; /* scratch buffer if fd != -1 */ + char *ps_data; /* current parse pointer */ + size_t ps_len; /* length of buffered data */ +}; + #define PMCLOG_FD_NONE (-1) __BEGIN_DECLS diff --git a/usr.sbin/pmc/Makefile b/usr.sbin/pmc/Makefile index 6a7d530aab38..6a0438452822 100644 --- a/usr.sbin/pmc/Makefile +++ b/usr.sbin/pmc/Makefile @@ -8,6 +8,6 @@ MAN= LIBADD= kvm pmc m ncursesw pmcstat elf SRCS= pmc.c pmc_util.c cmd_pmc_stat.c \ - cmd_pmc_list.c + cmd_pmc_list.c cmd_pmc_filter.c .include diff --git a/usr.sbin/pmc/cmd_pmc.h b/usr.sbin/pmc/cmd_pmc.h index 1ed4393a0363..463c30b0fb87 100644 --- a/usr.sbin/pmc/cmd_pmc.h +++ b/usr.sbin/pmc/cmd_pmc.h @@ -41,6 +41,7 @@ extern struct pmcstat_args pmc_args; typedef int (*cmd_disp_t)(int, char **); int cmd_pmc_stat(int, char **); +int cmd_pmc_filter(int, char **); int cmd_pmc_stat_system(int, char **); int cmd_pmc_list_events(int, char **); diff --git a/usr.sbin/pmc/cmd_pmc_filter.c b/usr.sbin/pmc/cmd_pmc_filter.c new file mode 100644 index 000000000000..6d0bc36f1401 --- /dev/null +++ b/usr.sbin/pmc/cmd_pmc_filter.c @@ -0,0 +1,269 @@ +/*- + * 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" + +#define LIST_MAX 64 +static struct option longopts[] = { + {"threads", no_argument, NULL, 't'}, + {"pids", no_argument, NULL, 'p'}, + {"events", no_argument, NULL, 'e'}, + {NULL, 0, NULL, 0} +}; + +static void +usage(void) +{ + errx(EX_USAGE, + "\t filter log file\n" + "\t -t , --threads -- comma-delimited list of lwps to filter on\n" + "\t -p , --pids -- comma-delimited list of pids to filter on\n" + "\t -e , --events -- comma-delimited list of events to filter on\n" + ); +} + + +static void +parse_intlist(char *strlist, int *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, int *intlist, 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; +} + +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 void +pmc_filter_handler(uint32_t *lwplist, int lwpcount, uint32_t *pidlist, int pidcount, + char *events, 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]; + int i, pmccount, copies, eventcount; + uint32_t idx, h; + off_t dstoff; + + if ((ps = pmclog_open(infd)) == NULL) + errx(EX_OSERR, "ERROR: Cannot allocate pmclog parse state: %s\n", strerror(errno)); + + pmccount = 0; + 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 = pmclog_open(infd)) == NULL) + errx(EX_OSERR, "ERROR: Cannot allocate pmclog parse state: %s\n", strerror(errno)); + if ((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 = pmclog_open(infd)) == NULL) + errx(EX_OSERR, "ERROR: Cannot allocate pmclog parse state: %s\n", strerror(errno)); + dstoff = copies = 0; + while (pmclog_read(ps, &ev) == 0) { + dstoff += ev.pl_len; + h = *(uint32_t *)ev.pl_data; + if (ev.pl_type != PMCLOG_TYPE_CALLCHAIN) { + if (write(outfd, ev.pl_data, ev.pl_len) != (ssize_t)ev.pl_len) + errx(EX_OSERR, "ERROR: failed output write"); + continue; + } + if (pidcount) { + for (i = 0; i < pidcount; i++) + if (pidlist[i] == ev.pl_u.pl_cc.pl_pid) + break; + if (i == pidcount) + continue; + } + if (lwpcount) { + for (i = 0; i < lwpcount; i++) + if (lwplist[i] == ev.pl_u.pl_cc.pl_tid) + break; + if (i == lwpcount) + 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) + continue; + } + if (write(outfd, ev.pl_data, ev.pl_len) != (ssize_t)ev.pl_len) + errx(EX_OSERR, "ERROR: failed output write"); + } +} + +int +cmd_pmc_filter(int argc, char **argv) +{ + char *lwps, *pids, *events; + uint32_t lwplist[LIST_MAX]; + uint32_t pidlist[LIST_MAX]; + int option, lwpcount, pidcount; + int prelogfd, postlogfd; + + lwps = pids = events = NULL; + lwpcount = pidcount = 0; + while ((option = getopt_long(argc, argv, "t:p:e:", longopts, NULL)) != -1) { + switch (option) { + case 't': + lwps = strdup(optarg); + break; + case 'p': + pids = strdup(optarg); + break; + case 'e': + events = strdup(optarg); + 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, + prelogfd, postlogfd); + return (0); +} diff --git a/usr.sbin/pmc/pmc.c b/usr.sbin/pmc/pmc.c index a8a16e7bdfca..fb8268fc224a 100644 --- a/usr.sbin/pmc/pmc.c +++ b/usr.sbin/pmc/pmc.c @@ -65,6 +65,7 @@ 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}, {NULL, NULL} }; @@ -76,6 +77,7 @@ usage(void) "\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" ); }