1/*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2018, Matthew Macy 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 * 27 */ 28 29#include <sys/param.h> 30#include <sys/cpuset.h> 31#include <sys/event.h> 32#include <sys/queue.h> 33#include <sys/socket.h> 34#include <sys/stat.h> 35#include <sys/sysctl.h> 36#include <sys/time.h> 37#include <sys/ttycom.h> 38#include <sys/user.h> 39#include <sys/wait.h> 40 41#include <assert.h> 42#include <curses.h> 43#include <err.h> 44#include <errno.h> 45#include <fcntl.h> 46#include <getopt.h> 47#include <kvm.h> 48#include <libgen.h> 49#include <limits.h> 50#include <locale.h> 51#include <math.h> 52#include <pmc.h> 53#include <pmclog.h> 54#include <regex.h> 55#include <signal.h> 56#include <stdarg.h> 57#include <stdint.h> 58#include <stdio.h> 59#include <stdlib.h> 60#include <stddef.h> 61#include <string.h> 62#include <sysexits.h> 63#include <unistd.h> 64 65#include <libpmcstat.h> 66#include "cmd_pmc.h" 67 68#include <string> 69#include <unordered_map> 70 71#include <pmcformat.h> 72 73using namespace std; 74using std::unordered_map; 75typedef unordered_map < int ,string > idmap; 76typedef pair < int ,string > identry; 77 78#define LIST_MAX 64 79static struct option longopts[] = { 80 {"lwps", required_argument, NULL, 't'}, 81 {"pids", required_argument, NULL, 'p'}, 82 {"threads", required_argument, NULL, 'T'}, 83 {"processes", required_argument, NULL, 'P'}, 84 {"events", required_argument, NULL, 'e'}, 85 {NULL, 0, NULL, 0} 86}; 87 88static void __dead2 89usage(void) 90{ 91 errx(EX_USAGE, 92 "\t filter log file\n" 93 "\t -e <events>, --events <events> -- comma-delimited list of events to filter on\n" 94 "\t -p <pids>, --pids <pids> -- comma-delimited list of pids to filter on\n" 95 "\t -P <processes>, --processes <processes> -- comma-delimited list of process names to filter on\n" 96 "\t -t <lwps>, --lwps <lwps> -- comma-delimited list of lwps to filter on\n" 97 "\t -T <threads>, --threads <threads> -- comma-delimited list of thread names to filter on\n" 98 "\t -x -- toggle inclusive filtering\n" 99 ); 100} 101 102 103static void 104parse_intlist(char *strlist, uint32_t *intlist, int *pcount, int (*fn) (const char *)) 105{ 106 char *token; 107 int count, tokenval; 108 109 count = 0; 110 while ((token = strsep(&strlist, ",")) != NULL && 111 count < LIST_MAX) { 112 if ((tokenval = fn(token)) < 0) 113 errx(EX_USAGE, "ERROR: %s not usable value", token); 114 intlist[count++] = tokenval; 115 } 116 *pcount = count; 117} 118 119static void 120parse_events(char *strlist, uint32_t intlist[LIST_MAX], int *pcount, char *cpuid) 121{ 122 char *token; 123 int count, tokenval; 124 125 count = 0; 126 while ((token = strsep(&strlist, ",")) != NULL && 127 count < LIST_MAX) { 128 if ((tokenval = pmc_pmu_idx_get_by_event(cpuid, token)) < 0) 129 errx(EX_USAGE, "ERROR: %s not usable value", token); 130 intlist[count++] = tokenval; 131 } 132 *pcount = count; 133} 134 135static void 136parse_names(char *strlist, char *namelist[LIST_MAX], int *pcount) 137{ 138 char *token; 139 int count; 140 141 count = 0; 142 while ((token = strsep(&strlist, ",")) != NULL && 143 count < LIST_MAX) { 144 namelist[count++] = token; 145 } 146 *pcount = count; 147} 148 149 150struct pmcid_ent { 151 uint32_t pe_pmcid; 152 uint32_t pe_idx; 153}; 154#define _PMCLOG_TO_HEADER(T,L) \ 155 ((PMCLOG_HEADER_MAGIC << 24) | \ 156 (PMCLOG_TYPE_ ## T << 16) | \ 157 ((L) & 0xFFFF)) 158 159static bool 160pmc_find_name(idmap & map, uint32_t id, char *list[LIST_MAX], int count) 161{ 162 int i; 163 164 auto kvpair = map.find(id); 165 if (kvpair == map.end()) { 166 printf("unknown id: %d\n", id); 167 return (false); 168 } 169 auto p = list; 170 for (i = 0; i < count; i++, p++) { 171 if (strstr(kvpair->second.c_str(), *p) != NULL) 172 return (true); 173 } 174 return (false); 175} 176 177static void 178pmc_log_event(int fd, struct pmclog_ev *ev, bool json) 179{ 180 string ret; 181 int len; 182 const void *buf; 183 184 if (json) { 185 ret = event_to_json(ev); 186 buf = ret.c_str(); 187 len = ret.size(); 188 } else { 189 len = ev->pl_len; 190 buf = ev->pl_data; 191 } 192 if (write(fd, buf, len) != (ssize_t)len) 193 errx(EX_OSERR, "ERROR: failed output write"); 194} 195 196static void 197pmc_filter_handler(uint32_t *lwplist, int lwpcount, uint32_t *pidlist, int pidcount, 198 char *events, char *processes, char *threads, bool exclusive, bool json, int infd, 199 int outfd) 200{ 201 struct pmclog_ev ev; 202 struct pmclog_parse_state *ps; 203 struct pmcid_ent *pe; 204 uint32_t eventlist[LIST_MAX]; 205 char cpuid[PMC_CPUID_LEN]; 206 char *proclist[LIST_MAX]; 207 char *threadlist[LIST_MAX]; 208 int i, pmccount, eventcount; 209 int proccount, threadcount; 210 uint32_t idx; 211 idmap pidmap, tidmap; 212 213 if ((ps = static_cast < struct pmclog_parse_state *>(pmclog_open(infd)))== NULL) 214 errx(EX_OSERR, "ERROR: Cannot allocate pmclog parse state: %s\n", strerror(errno)); 215 216 threadcount = proccount = eventcount = pmccount = 0; 217 if (processes) 218 parse_names(processes, proclist, &proccount); 219 if (threads) 220 parse_names(threads, threadlist, &threadcount); 221 while (pmclog_read(ps, &ev) == 0) { 222 if (ev.pl_type == PMCLOG_TYPE_INITIALIZE) 223 memcpy(cpuid, ev.pl_u.pl_i.pl_cpuid, PMC_CPUID_LEN); 224 if (ev.pl_type == PMCLOG_TYPE_PMCALLOCATE) 225 pmccount++; 226 } 227 if (events) 228 parse_events(events, eventlist, &eventcount, cpuid); 229 lseek(infd, 0, SEEK_SET); 230 pmclog_close(ps); 231 if ((ps = static_cast < struct pmclog_parse_state *>(pmclog_open(infd)))== NULL) 232 errx(EX_OSERR, "ERROR: Cannot allocate pmclog parse state: %s\n", strerror(errno)); 233 if ((pe = (struct pmcid_ent *) malloc(sizeof(*pe) * pmccount)) == NULL) 234 errx(EX_OSERR, "ERROR: failed to allocate pmcid map"); 235 i = 0; 236 while (pmclog_read(ps, &ev) == 0 && i < pmccount) { 237 if (ev.pl_type == PMCLOG_TYPE_PMCALLOCATE) { 238 pe[i].pe_pmcid = ev.pl_u.pl_a.pl_pmcid; 239 pe[i].pe_idx = ev.pl_u.pl_a.pl_event; 240 i++; 241 } 242 } 243 lseek(infd, 0, SEEK_SET); 244 pmclog_close(ps); 245 if ((ps = static_cast < struct pmclog_parse_state *>(pmclog_open(infd)))== NULL) 246 errx(EX_OSERR, "ERROR: Cannot allocate pmclog parse state: %s\n", strerror(errno)); 247 while (pmclog_read(ps, &ev) == 0) { 248 if (ev.pl_type == PMCLOG_TYPE_THR_CREATE) 249 tidmap[ev.pl_u.pl_tc.pl_tid] = ev.pl_u.pl_tc.pl_tdname; 250 if (ev.pl_type == PMCLOG_TYPE_PROC_CREATE) 251 pidmap[ev.pl_u.pl_pc.pl_pid] = ev.pl_u.pl_pc.pl_pcomm; 252 if (ev.pl_type != PMCLOG_TYPE_CALLCHAIN) { 253 pmc_log_event(outfd, &ev, json); 254 continue; 255 } 256 if (pidcount) { 257 for (i = 0; i < pidcount; i++) 258 if (pidlist[i] == ev.pl_u.pl_cc.pl_pid) 259 break; 260 if ((i == pidcount) == exclusive) 261 continue; 262 } 263 if (lwpcount) { 264 for (i = 0; i < lwpcount; i++) 265 if (lwplist[i] == ev.pl_u.pl_cc.pl_tid) 266 break; 267 if ((i == lwpcount) == exclusive) 268 continue; 269 } 270 if (eventcount) { 271 for (i = 0; i < pmccount; i++) { 272 if (pe[i].pe_pmcid == ev.pl_u.pl_cc.pl_pmcid) 273 break; 274 } 275 if (i == pmccount) 276 errx(EX_USAGE, "ERROR: unallocated pmcid: %d\n", 277 ev.pl_u.pl_cc.pl_pmcid); 278 279 idx = pe[i].pe_idx; 280 for (i = 0; i < eventcount; i++) { 281 if (idx == eventlist[i]) 282 break; 283 } 284 if ((i == eventcount) == exclusive) 285 continue; 286 } 287 if (proccount && 288 pmc_find_name(pidmap, ev.pl_u.pl_cc.pl_pid, proclist, proccount) == exclusive) 289 continue; 290 if (threadcount && 291 pmc_find_name(tidmap, ev.pl_u.pl_cc.pl_tid, threadlist, threadcount) == exclusive) 292 continue; 293 pmc_log_event(outfd, &ev, json); 294 } 295} 296 297int 298cmd_pmc_filter(int argc, char **argv) 299{ 300 char *lwps, *pids, *events, *processes, *threads; 301 uint32_t lwplist[LIST_MAX]; 302 uint32_t pidlist[LIST_MAX]; 303 int option, lwpcount, pidcount; 304 int prelogfd, postlogfd; 305 bool exclusive, json; 306 307 threads = processes = lwps = pids = events = NULL; 308 lwpcount = pidcount = 0; 309 json = exclusive = false; 310 while ((option = getopt_long(argc, argv, "e:jp:t:xP:T:", longopts, NULL)) != -1) { 311 switch (option) { 312 case 'e': 313 events = strdup(optarg); 314 break; 315 case 'j': 316 json = true; 317 break; 318 case 'p': 319 pids = strdup(optarg); 320 break; 321 case 'P': 322 processes = strdup(optarg); 323 break; 324 case 't': 325 lwps = strdup(optarg); 326 break; 327 case 'T': 328 threads = strdup(optarg); 329 break; 330 case 'x': 331 exclusive = !exclusive; 332 break; 333 case '?': 334 default: 335 usage(); 336 } 337 } 338 argc -= optind; 339 argv += optind; 340 if (argc != 2) 341 usage(); 342 343 if (lwps) 344 parse_intlist(lwps, lwplist, &lwpcount, atoi); 345 if (pids) 346 parse_intlist(pids, pidlist, &pidcount, atoi); 347 if ((prelogfd = open(argv[0], O_RDONLY, 348 S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) < 0) 349 errx(EX_OSERR, "ERROR: Cannot open \"%s\" for reading: %s.", argv[0], 350 strerror(errno)); 351 if ((postlogfd = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC, 352 S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) < 0) 353 errx(EX_OSERR, "ERROR: Cannot open \"%s\" for writing: %s.", argv[1], 354 strerror(errno)); 355 356 pmc_filter_handler(lwplist, lwpcount, pidlist, pidcount, events, 357 processes, threads, exclusive, json, prelogfd, postlogfd); 358 return (0); 359} 360