diff options
| author | Ali Mashtizadeh <ali@mashtizadeh.com> | 2026-07-28 17:46:22 +0000 |
|---|---|---|
| committer | Warner Losh <imp@FreeBSD.org> | 2026-07-28 19:27:44 +0000 |
| commit | 93da997ef759061670c1575eec92f922a645a5fd (patch) | |
| tree | 27931b0211f0cc36e5a849a070ac9de2fedb423d | |
| parent | a53d2e4f5db588cfd59cdebf04536b6ca7a70e4b (diff) | |
pmc: new pmc log processing framework
View is a class for building PMC log processing tools it is designed to
work with the new PMC record command that adds a header with additional
CPU information. The new framework processes PMC logs about 2.5 times
faster and in about half the code as libpmcstat.
Sponsored by: Netflix
Reviewed by: adrian
Differential Revision: https://reviews.freebsd.org/D57776
| -rw-r--r-- | usr.sbin/pmc/headers.hh | 82 | ||||
| -rw-r--r-- | usr.sbin/pmc/util.cc | 82 | ||||
| -rw-r--r-- | usr.sbin/pmc/util.hh | 39 | ||||
| -rw-r--r-- | usr.sbin/pmc/view.cc | 960 | ||||
| -rw-r--r-- | usr.sbin/pmc/view.hh | 411 |
5 files changed, 1574 insertions, 0 deletions
diff --git a/usr.sbin/pmc/headers.hh b/usr.sbin/pmc/headers.hh new file mode 100644 index 000000000000..29f76cd7cc2c --- /dev/null +++ b/usr.sbin/pmc/headers.hh @@ -0,0 +1,82 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2026, Netflix, Inc. + * + * This software was developed by Ali Mashtizadeh under the sponsorship from + * Netflix, Inc. + * + * 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. + * + */ + +#ifndef __HEADERS_HH__ +#define __HEADERS_HH__ + +#define PMC_HEADER_MAGIC 0x504D434C +#define PMC_HEADER_VERSION 0x00 + +#define PMC_ARCH_AMD64 0x01 +#define PMC_ARCH_ARM64 0x02 +#define PMC_ARCH_PPC64 0x03 +#define PMC_ARCH_RISCV64 0x04 + +struct pmchdr_header +{ + uint32_t magic; + uint16_t version; + uint16_t arch; +}; + +#define INFOHDR_TYPE_DONE 0x00 +#define INFOHDR_TYPE_SYSINFO 0x01 +#define INFOHDR_TYPE_PMCINFO 0x02 +#define INFOHDR_TYPE_CPUID 0x03 + +struct pmchdr_infohdr +{ + uint8_t type; + uint8_t reserved0; + uint16_t length; + uint32_t _reserved1; +}; + +struct pmchdr_sysinfo +{ + char cpumodel[64]; + char osrelease[64]; + char buildid[64]; +}; + +struct pmchdr_pmcinfo +{ + uint64_t rate; + char pmc[]; +}; + +struct pmchdr_cpuidinfo +{ + uint32_t cpuid[]; +}; + +#endif + diff --git a/usr.sbin/pmc/util.cc b/usr.sbin/pmc/util.cc new file mode 100644 index 000000000000..b6e43b9dd130 --- /dev/null +++ b/usr.sbin/pmc/util.cc @@ -0,0 +1,82 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2026, Netflix, Inc. + * + * This software was developed by Ali Mashtizadeh under the sponsorship from + * Netflix, Inc. + * + * 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 <string> +#include <unordered_set> + +#include "util.hh" + +std::string +basename(const std::string &path) +{ + size_t s; + + s = path.rfind("/"); + if (s == std::string::npos) + return (path); + else + return (path.substr(s + 1)); +} + +void +split_and_insert(std::unordered_set<int> *set, const std::string &str) +{ + size_t pos = 0; + + while (pos < str.length()) { + size_t end = str.find(",", pos); + if (end == str.npos) { + set->insert(std::stoi(str.substr(pos))); + break; + } + + set->insert(std::stoi(str.substr(pos, end - pos))); + pos = end + 1; + } +} + +void +split_and_insert(std::unordered_set<std::string> *set, const std::string &str) +{ + size_t pos = 0; + + while (pos < str.length()) { + size_t end = str.find(",", pos); + if (end == str.npos) { + set->insert(str.substr(pos)); + break; + } + + set->insert(str.substr(pos, end - pos)); + pos = end + 1; + } +} + diff --git a/usr.sbin/pmc/util.hh b/usr.sbin/pmc/util.hh new file mode 100644 index 000000000000..03c350721b2b --- /dev/null +++ b/usr.sbin/pmc/util.hh @@ -0,0 +1,39 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2026, Netflix, Inc. + * + * This software was developed by Ali Mashtizadeh under the sponsorship from + * Netflix, Inc. + * + * 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. + * + */ +#ifndef __UTIL_H__ +#define __UTIL_H__ + +std::string basename(const std::string &path); +void split_and_insert(std::unordered_set<int> *set, const std::string &input); +void split_and_insert(std::unordered_set<std::string> *set, const std::string &input); + +#endif /* __UTIL_HH__ */ + diff --git a/usr.sbin/pmc/view.cc b/usr.sbin/pmc/view.cc new file mode 100644 index 000000000000..352856939a66 --- /dev/null +++ b/usr.sbin/pmc/view.cc @@ -0,0 +1,960 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2026, Netflix, Inc. + * + * This software was developed by Ali Mashtizadeh under the sponsorship from + * Netflix, Inc. + * + * 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 <sys/types.h> +#include <sys/param.h> + +#include <assert.h> +#include <err.h> +#include <fcntl.h> +#include <gelf.h> +#include <inttypes.h> +#include <libelf.h> +#include <pmclog.h> +#include <sysexits.h> +#include <unistd.h> + +#include <cxxabi.h> +#include <iostream> +#include <map> +#include <set> +#include <string> +#include <sstream> +#include <unordered_map> +#include <unordered_set> + +#include <dev/hwpmc/hwpmc_ibs.h> +#include "util.hh" +#include "view.hh" + +std::string +syminfo::to_string(bool show_line) +{ + int status; + char *demangled; + std::stringstream ss; + + if (name == "") { + ss << "0x" << std::hex << offset; + } else if (name.length() >= 2 && name[0] == '_' && name[1] == 'Z') { + demangled = abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &status); + if (status == 0) { + ss << demangled; + free(demangled); + } else { + ss << name; + } + } else { + ss << name; + } + + if (!show_line) + return ss.str(); + + if (line) { + ss << ":" << std::dec << line; + } else if (funcoff) { + ss << "+0x" << std::hex << funcoff; + } + + return ss.str(); +} + +pmcview::pmcview() : tscfreq(0), pmcid(), pmcinfo(), procs(), tidtopid(), + images(), sysroot(""), filter() +{ + char *root; + + root = getenv("SYSROOT"); + if (root) { + sysroot = root; + } +} + +pmcview::~pmcview() +{ +} + +void +pmcview::setfilter(const pmcfilter &pmcfilter) +{ + filter = pmcfilter; +} + +static int +readlog(int logfd, void *buf, size_t len) +{ + int status; + char *cur = (char *)buf; + size_t left = len; + + while (left != 0) { + status = read(logfd, cur, left); + if (status < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + else + return status; + } + if (status == 0) + return status; + + cur += status; + left -= status; + } + + return len; +} + +int +pmcview::process_sysinfo(int logfd, const pmchdr_infohdr &infohdr) +{ + int status; + pmchdr_sysinfo sysinfo; + + if (infohdr.length != sizeof(sysinfo)) + errx(EX_IOERR, "PMC log headers have an unexpected size"); + + status = readlog(logfd, &sysinfo, sizeof(sysinfo)); + if (status < 0 || status != infohdr.length) + errx(EX_IOERR, "readlog"); + + cpumodel = sysinfo.cpumodel; + osrelease = sysinfo.osrelease; + buildid = sysinfo.buildid; + + return 0; +} + +int +pmcview::process_pmcinfo(int logfd, const pmchdr_infohdr &infohdr) +{ + int status; + union { + pmchdr_pmcinfo pmcinfo; + __unused char reserve_max_size[256]; + }; + + status = readlog(logfd, &pmcinfo, infohdr.length); + if (status < 0 || status != infohdr.length) + errx(EX_IOERR, "readlog"); + + extpmcinfo.emplace_back(pmcinfo.rate, std::string(pmcinfo.pmc)); + + return 0; +} + +int +pmcview::process_cpuidinfo(int logfd, const pmchdr_infohdr &infohdr) +{ + int status; + pmchdr_cpuidinfo *cpuidinfo; + int offset, len; + uint32_t root, maxleaf, count; + + len = infohdr.length / 4; + cpuidinfo = (pmchdr_cpuidinfo *)new uint32_t[len]; + + status = readlog(logfd, cpuidinfo, infohdr.length); + if (status < 0 || status != infohdr.length) + errx(EX_IOERR, "readlog"); + + offset = 0; + + while (offset < len) { + maxleaf = cpuidinfo->cpuid[offset]; + + /* + * In x86 the roots contains the maximum leaf number present. + */ + root = maxleaf & 0xFFFF0000; + count = maxleaf & 0x0000FFFF; + + for (uint32_t i = 0; i <= count; i++) { + cpuid[root + i] = { cpuidinfo->cpuid[4 * i + offset], + cpuidinfo->cpuid[4 * i + 1 + offset], + cpuidinfo->cpuid[4 * i + 2 + offset], + cpuidinfo->cpuid[4 * i + 3 + offset] }; + } + + offset += 4 * (count + 1); + } + + delete[] cpuidinfo; + + return 0; +} + +void +pmcview::process(int logfd) +{ + int status; + pmchdr_header hdr; + struct pmclog_parse_state *ps; + + // Read header + status = readlog(logfd, &hdr, sizeof(hdr)); + if (status < 0 || status != sizeof(hdr)) + err(EX_IOERR, "readlog"); + if (hdr.magic != PMC_HEADER_MAGIC) + errx(EX_DATAERR, "PMC log magic mismatch!"); + if (hdr.version > PMC_HEADER_VERSION) + errx(EX_DATAERR, "PMC log version newer than supported!"); + + // Save architecture + arch = hdr.arch; + + while (1) { + pmchdr_infohdr infohdr; + + status = readlog(logfd, &infohdr, sizeof(infohdr)); + if (status < 0 || status != sizeof(infohdr)) + errx(EX_IOERR, "readlog"); + + if (infohdr.type == INFOHDR_TYPE_DONE) { + break; + } else if (infohdr.type == INFOHDR_TYPE_SYSINFO) { + process_sysinfo(logfd, infohdr); + } else if (infohdr.type == INFOHDR_TYPE_PMCINFO) { + process_pmcinfo(logfd, infohdr); + } else if (infohdr.type == INFOHDR_TYPE_CPUID) { + process_cpuidinfo(logfd, infohdr); + } + } + + ps = static_cast<struct pmclog_parse_state*>(pmclog_open(logfd)); + if (ps == NULL) { + errx(EX_OSERR, "ERROR: Cannot allocate pmclog parse state: %s\n", + strerror(errno)); + } + + process(ps); + + pmclog_close(ps); +} + +void +pmcview::process(struct pmclog_ev &p) +{ + switch (p.pl_type) { + case PMCLOG_TYPE_INITIALIZE: + process(p.pl_u.pl_i); + return; + case PMCLOG_TYPE_CLOSELOG: + process(p.pl_u.pl_cl); + return; + case PMCLOG_TYPE_PMCALLOCATE: + process(p.pl_u.pl_a); + return; + case PMCLOG_TYPE_PMCALLOCATEDYN: + process(p.pl_u.pl_ad); + return; + case PMCLOG_TYPE_PROC_CREATE: + process(p.pl_u.pl_pc); + return; + case PMCLOG_TYPE_PROCEXIT: + process(p.pl_u.pl_e); + return; + case PMCLOG_TYPE_PROCEXEC: + process(p.pl_u.pl_x); + return; + case PMCLOG_TYPE_PROCFORK: + process(p.pl_u.pl_f); + return; + case PMCLOG_TYPE_SYSEXIT: + process(p.pl_u.pl_se); + return; + case PMCLOG_TYPE_MAP_IN: + process(p.pl_u.pl_mi); + return; + case PMCLOG_TYPE_MAP_OUT: + process(p.pl_u.pl_mo); + return; + case PMCLOG_TYPE_THR_CREATE: + process(p.pl_u.pl_tc); + return; + case PMCLOG_TYPE_THR_EXIT: + process(p.pl_u.pl_te); + return; + case PMCLOG_TYPE_CALLCHAIN: + process(p.pl_u.pl_cc); + return; + case PMCLOG_TYPE_PMCATTACH: [[fallthrough]]; + case PMCLOG_TYPE_PMCDETACH: + case PMCLOG_TYPE_USERDATA: + case PMCLOG_TYPE_PROCCSW: + case PMCLOG_TYPE_DROPNOTIFY: + return; + }; +} + +void +pmcview::process(struct pmclog_parse_state *p) +{ + struct pmclog_ev ev; + + while (pmclog_read(p, &ev) == 0) { + process(ev); + } +} + +void +pmcview::process(struct pmclog_ev_initialize &p) +{ + tscfreq = p.pl_tsc_freq; +} + +void +pmcview::process(__unused struct pmclog_ev_closelog &p) +{ +} + +void +pmcview::process(struct pmclog_ev_pmcallocate &p) +{ + pmcid[p.pl_pmcid] = p.pl_event; + if (pmcinfo.find(p.pl_event) == pmcinfo.end()) { + pmcinfo.emplace(p.pl_event, p); + } +} + +void +pmcview::process(struct pmclog_ev_pmcallocatedyn &p) +{ + pmcid[p.pl_pmcid] = p.pl_event; + if (pmcinfo.find(p.pl_event) == pmcinfo.end()) { + pmcinfo.emplace(p.pl_event, p); + } +} + +void +pmcview::process(struct pmclog_ev_proccreate &p) +{ + procs[p.pl_pid] = procinfo(p); + + proccreate(p.pl_pid); +} + +void +pmcview::process(struct pmclog_ev_procexit &p) +{ + /* + * XXX: Seems we can recieve samples after the process exits we need + * some way to delay cleanup, then after the delayed cleanup discard + * delayed events. + */ + procs.erase(p.pl_pid); + + procexit(p.pl_pid); +} + +void +pmcview::process(struct pmclog_ev_procexec &p) +{ + procs[p.pl_pid].map.clear(); + procs[p.pl_pid].name = basename(p.pl_pathname); + procs[p.pl_pid].fullpath = p.pl_pathname; + procs[p.pl_pid].baseaddr = p.pl_baseaddr; + procs[p.pl_pid].dynaddr = p.pl_dynaddr; + + image im = loadimage(p.pl_pathname); + + mapimage(p.pl_pid, im, im.vaddr + p.pl_dynaddr); + + /* + * Map the dynamic runtime loader + */ + if (im.isdynamic) { + image rtldim = loadimage(im.loader); + + mapimage(p.pl_pid, rtldim, p.pl_baseaddr); + } + + procexec(p.pl_pid); +} + +void +pmcview::process(struct pmclog_ev_procfork &p) +{ + procs[p.pl_newpid] = procs[p.pl_oldpid]; + + proccreate(p.pl_newpid); +} + +void +pmcview::process(struct pmclog_ev_sysexit &p) +{ + procs.erase(p.pl_pid); +} + +void +pmcview::process(struct pmclog_ev_threadcreate &p) +{ + procs[p.pl_pid].threads[p.pl_tid] = threadinfo(p.pl_tdname); + tidtopid[p.pl_tid] = p.pl_pid; +} + +void +pmcview::process(struct pmclog_ev_threadexit &p) +{ + pid_t pid = tidtopid[p.pl_tid]; + procs[pid].threads.erase(p.pl_tid); + tidtopid.erase(p.pl_tid); +} + +/* + * Load the ELF file to compute the values needed for the memory map and check + * if the dwarf symbols are available. + */ +image +pmcview::loadimage(const std::string &path) +{ + std::string fullpath; + image im; + GElf_Ehdr eh; + Elf *e; + const char *elf; + uint64_t start; + uint64_t end; + size_t shstrndx; + int fd, i; + bool foundexec; + + if (images.find(path) != images.end()) + return images[path]; + + im = image(); + + if (path == "unknown") { + return (im); + } + + fullpath = sysroot + path; + + if (access(fullpath.c_str(), R_OK) != 0) + return (im); + + fd = open(fullpath.c_str(), O_RDONLY, 0); + if (fd < 0) { + warnx("WARNING: Cannot open \"%s\".", + fullpath.c_str()); + return (im); + } + + e = elf_begin(fd, ELF_C_READ, NULL); + if (e == NULL) { + warnx("WARNING: Cannot read \"%s\".", + fullpath.c_str()); + close(fd); + return (im); + } + + if (gelf_getehdr(e, &eh) != &eh) { + warnx("WARNING: Cannot read the ELF header for \"%s\": %s.", + fullpath.c_str(), elf_errmsg(-1)); + goto done; + } + + if (eh.e_type != ET_EXEC && eh.e_type != ET_DYN && eh.e_type != ET_REL) { + warnx("WARNING: ELF file type is unsupported for \"%s\".", + fullpath.c_str());; + goto done; + } + + elf = elf_rawfile(e, NULL); + if (elf == NULL) { + warnx("WARNING: Cannot read the ELF file for \"%s\": %s.", + fullpath.c_str(), elf_errmsg(-1)); + goto done; + } + + if (eh.e_type != ET_REL) { + foundexec = false; + for (i = 0; i < eh.e_phnum; i++) { + GElf_Phdr ph; + + if (gelf_getphdr(e, i, &ph) != &ph) { + warnx("WARNING: Cannot read program header for \"%s\": %s.", + fullpath.c_str(), elf_errmsg(-1)); + goto done; + } + + if (ph.p_type == PT_DYNAMIC) { + im.isdynamic = 1; + continue; + } + + if (ph.p_type == PT_INTERP) { + im.loader = elf + ph.p_offset; + } + + if (ph.p_type == PT_LOAD) { + if ((ph.p_flags & PF_X) != 0 && !foundexec) { + im.vaddr = ph.p_vaddr & ~(ph.p_align - 1); + foundexec = true; + } + } + } + } + + elf_getshdrstrndx(e, &shstrndx); + + start = ~(0x0ULL); + end = 0; + for (i = 0; i < eh.e_shnum; i++) { + GElf_Shdr sh; + Elf_Scn *scn; + char *scname; + + scn = elf_getscn(e, i); + if (scn == NULL) { + warnx("WARNING: Could not retrieve section descriptor for \"%s\".", + fullpath.c_str()); + goto done; + } + + if (gelf_getshdr(scn, &sh) != &sh) { + warnx("WARNING: Could not retrieve section header for \"%s\".", + fullpath.c_str()); + goto done; + } + + if (sh.sh_flags & SHF_EXECINSTR) { + start = std::min(start, sh.sh_addr); + end = std::max(end, sh.sh_addr + sh.sh_size); + } + + // Check if dwarf is embedded + scname = elf_strptr(e, shstrndx, sh.sh_name); + if (scname != NULL && strcmp(scname, ".debug_info") == 0) + im.dwarf = fullpath; + } + + im.start = start; + im.end = end; + im.binary = path; + im.path = fullpath; + im.name = basename(fullpath); + + /* + * If the dwarf symbols aren't embedded check: + * 1. SYSROOT + /path + .debug + * 2. SYSROOT + /usr/lib/debug + /path + .debug + */ + if (im.dwarf == "") { + std::string sympath = fullpath + ".debug"; + if (access(sympath.c_str(), R_OK) == 0) { + im.dwarf = sympath; + } + } + if (im.dwarf == "") { + std::string sympath = sysroot + "/usr/lib/debug" + path + ".debug"; + if (access(sympath.c_str(), R_OK) == 0) { + im.dwarf = sympath; + } + } + + images[path] = im; + +done: + elf_end(e); + close(fd); + + return (im); +} + +void +pmcview::loadsymboltable(image *im, Elf *e, Elf_Scn *scn, GElf_Shdr *sh) +{ + size_t n, nsyms; + char *fname; + GElf_Sym sym; + Elf_Data *data; + syminfo si; + + si = syminfo(); + + if ((data = elf_getdata(scn, nullptr)) == nullptr) + return; + + nsyms = sh->sh_size / sh->sh_entsize; + + for (n = 0; n < nsyms; n++) { + if (gelf_getsym(data, (int) n, &sym) != &sym) + return; + + // XXX: We should load globals as well + if (GELF_ST_TYPE(sym.st_info) != STT_FUNC) + continue; + + if (sym.st_shndx == STN_UNDEF) + continue; + + if ((fname = elf_strptr(e, sh->sh_link, sym.st_name)) == NULL) + continue; + + // XXX: Extra checks to make sure we don't get corrupted + for (int i = 0; fname[i] != 0 && i < 32; i++) { + if (fname[i] < 0) { + printf("EEEK SYMBOL\n"); + printf("%s\n", fname); + assert(false); + } + } + + si.offset = sym.st_value; + si.length = sym.st_size; + si.binary = im->name; + si.name = fname; + + im->symbols[si.offset] = si; + } +} + +void +pmcview::loadsymbols(image *im) +{ + int i; + int fd; + Elf *e; + Elf_Scn *scn; + GElf_Ehdr eh; + GElf_Shdr sh; + + if (access(im->path.c_str(), R_OK) != 0) + return; + + fd = open(im->path.c_str(), O_RDONLY, 0); + if (fd < 0) { + warnx("WARNING: Cannot open \"%s\".", + im->path.c_str()); + return; + } + + e = elf_begin(fd, ELF_C_READ, NULL); + if (e == NULL) { + warnx("WARNING: Cannot read \"%s\".", + im->path.c_str()); + close(fd); + return; + } + + if (gelf_getehdr(e, &eh) != &eh) { + warnx("WARNING: Cannot read the ELF header for \"%s\": %s.", + im->path.c_str(), elf_errmsg(-1)); + goto done; + } + + for (i = 0; i < eh.e_shnum; i++) { + scn = elf_getscn(e, i); + if (scn == NULL) { + warnx("WARNING: Could not retrieve section descriptor for \"%s\".", + im->path.c_str()); + goto done; + } + + if (gelf_getshdr(scn, &sh) != &sh) { + warnx("WARNING: Could not retrieve section header for \"%s\".", + im->path.c_str()); + goto done; + } + + if (sh.sh_type == SHT_SYMTAB || sh.sh_type == SHT_DYNSYM) { + loadsymboltable(im, e, scn, &sh); + } + } + +done: + elf_end(e); + close(fd); +} + +void +pmcview::mapimage(int pid, const image &im, uint64_t start) +{ + uint64_t offset; + vmmap map; + + // Ignore empty images + if (im.start == 0 && im.end == 0) + return; + + /* + * XXX: Need to adjust the address for the PowerPC kernel that is + * dynamic. Wonder if we can fix this elsewhere, because we should need + * a way to deal with this for KASLR?. + */ + + offset = start - im.vaddr; + map.lowpc = im.start + offset; + map.highpc = im.end + offset; + map.image = im.binary; + + procs[pid].map[start] = map; +} + +void +pmcview::process(struct pmclog_ev_map_in &p) +{ + // Kernel map-in events should be mapped to pid 0 + pid_t pid = (p.pl_pid == -1) ? 0 : p.pl_pid; + + image im = loadimage(p.pl_pathname); + + mapimage(pid, im, p.pl_start); +} + +void +pmcview::process(struct pmclog_ev_map_out &p) +{ + // Kernel map-in events should be mapped to pid 0 + pid_t pid = (p.pl_pid == -1) ? 0 : p.pl_pid; + procinfo &proc = procs[pid]; + + /* + * XXX: We should handle all the mmap/munmap cases but only executable + * file mappings are included. + */ + proc.map.erase(p.pl_start); +} + +void +pmcview::process(struct pmclog_ev_callchain &p) +{ + int i; + uint8_t *hdr = (uint8_t *)&p.pl_pc[0]; + uint8_t type, len; + uintfptr_t *cc = &p.pl_pc[1]; + ibsfetchinfo ibsf; + ibsopinfo ibso; + + /* + * Callchain events are always attributed to one of the kernel + * processes. Make sure nobody breaks our assumption. + */ + assert(p.pl_pid != ~(uint32_t)0); + + ibsf.len = 0; + ibso.len = 0; + + if (p.pl_cpuflags & PMC_CC_F_MULTIPART) { + for (i = 0; i < 4; i++) { + type = hdr[2 * i]; + len = hdr[2 * i + 1]; + + switch (type) { + case PMC_CC_MULTIPART_IBS_FETCH: + ibsf.len = len; + ibsf.ctl = cc[PMC_MPIDX_FETCH_CTL]; + ibsf.extctl = cc[PMC_MPIDX_FETCH_EXTCTL]; + ibsf.linaddr = cc[PMC_MPIDX_FETCH_LINADDR]; + ibsf.physaddr = cc[PMC_MPIDX_FETCH_PHYSADDR]; + break; + case PMC_CC_MULTIPART_IBS_OP: + ibso.len = len; + ibso.ctl = cc[PMC_MPIDX_OP_CTL]; + ibso.rip = cc[PMC_MPIDX_OP_RIP]; + ibso.data = cc[PMC_MPIDX_OP_DATA]; + ibso.data2 = cc[PMC_MPIDX_OP_DATA2]; + ibso.data3 = cc[PMC_MPIDX_OP_DATA3]; + ibso.linaddr = cc[PMC_MPIDX_OP_DC_LINADDR]; + ibso.physaddr = cc[PMC_MPIDX_OP_DC_PHYSADDR]; + ibso.tgtrip = cc[PMC_MPIDX_OP_TGT_RIP]; + ibso.data4 = cc[PMC_MPIDX_OP_DATA4]; + break; + } + + cc += len; + } + } + + /* + * Discard delayed events so we do not get unattributed samples. + */ + auto pinfo = procs.find(p.pl_pid); + if (pinfo == procs.end()) + return; + + // Filter on pid, tid, program, thread and events + if (filter.filterpid && filter.pids.count(p.pl_pid) == 0) + return; + if (filter.filtertid && filter.tids.count(p.pl_tid) == 0) + return; + if (filter.filterprogram && filter.programs.count(pinfo->second.name) == 0) + return; + if (filter.filterthread) { + auto tinfo = pinfo->second.threads.find(p.pl_tid); + if (tinfo == pinfo->second.threads.end()) + return; + if (filter.threads.count(tinfo->second.name) == 0) + return; + } + if (filter.filterevent) { + auto pmc = pmcid.find(p.pl_pmcid); + if (pmc == pmcid.end()) + return; + if (filter.events.count(pmcinfo[pmc->second].name) == 0) + return; + } + + // Filter on cpuset + int cpunum = PMC_CALLCHAIN_CPUFLAGS_TO_CPU(p.pl_cpuflags); + if (filter.filtercpu && !CPU_ISSET(cpunum, &filter.cpus)) { + return; + } + + // Filter on user/kernel mode + int usermode = PMC_CALLCHAIN_CPUFLAGS_TO_USERMODE(p.pl_cpuflags); + if (filter.useronly && usermode == 0) { + return; + } + if (filter.kernelonly && usermode != 0) { + return; + } + + // Advanced filters for AMD IBS + if (ibsf.len) { + if (filter.ibs_ldlat > IBS_FETCH_CTL_TO_LAT(ibsf.ctl)) + return; + callchain(p, ibsf, cc, len); + } else if (ibso.len) { + if (filter.ibs_oplat > IBS_OP_DATA_TO_COMPTORET(ibso.data)) + return; + if (filter.ibs_ldlat > IBS_OP_DATA3_TO_DCLAT(ibso.data3)) + return; + if (filter.ibs_mmio) { + if (((ibso.data3 & IBS_OP_DATA3_STORE) == 0) && + ((ibso.data3 & IBS_OP_DATA3_LOAD) == 0)) + return; + if (((ibso.data3 & IBS_OP_DATA3_UCMEMACCESS) == 0) && + ((ibso.data3 & IBS_OP_DATA3_WCMEMACCESS) == 0)) + return; + } + callchain(p, ibso, cc, len); + } else { + callchain(p, cc, len); + } +} + +uint32_t +pmcview::pmcidtoeventid(uint32_t id) +{ + return pmcid[id]; +} + +std::string +pmcview::pidtoname(pid_t pid) +{ + auto p = procs.find(pid); + if (p == procs.end()) + return (""); + else + return (p->second.name); +} + +syminfo +pmcview::addrtosymbol(pid_t pid, uint64_t addr) +{ + syminfo si; + procinfo &proc = procs[pid]; + vmmap *vm; + image *im; + + im = nullptr; + auto v = proc.map.upper_bound(addr); + if (v != proc.map.begin()) { + v--; + if (v->second.lowpc <= addr && v->second.highpc >= addr) { + vm = &v->second; + im = &images[v->second.image]; + } + } + + if (im == nullptr) { +#ifdef DEBUG_VIEW + printf("Symbol not found for 0x%lx in %s\n", addr, proc.name.c_str()); + printvm(pid); +#endif + return syminfo(); + } + + // Adjust address into the ELF's virtual address space + addr -= vm->lowpc - im->start; + + // Load symbols if they haven't been loaded + if (im->symbols.size() == 0) { + loadsymbols(im); + } + + // Look for the first symbol + auto s = im->symbols.upper_bound(addr); + if (s != im->symbols.begin()) + s--; + if (s->second.offset <= addr && + (s->second.offset + s->second.length + 1) >= addr) { + si = s->second; + si.funcoff = addr - si.offset; + return si; + } + + // Special case for assembly functions in the kernel + if (s->second.offset <= addr && s->second.length == 0 && pid == 0) { + si = s->second; + si.funcoff = addr - si.offset; + return si; + } + +#ifdef DEBUG_VIEW + printf("Symbol not found in image %lx\n", addr); + printf("%lx %lx %s\n", s->second.offset, s->second.s_length, s->second.s_name.c_str()); + printf("%lx\n", addr); +#endif + + si = syminfo(); + si.binary = im->name; + std::stringstream str = std::stringstream(); + str << "0x" << std::hex << addr; + si.name = str.str(); + return si; +} + +void +pmcview::printvm(pid_t pid) +{ + procinfo &proc = procs[pid]; + + printf("%-18s %-18s %s\n", "Low PC", "High PC", "Image"); + for (auto &i : proc.map) { + printf("0x%08" PRIx64 " 0x%08" PRIx64 " %s\n", i.second.lowpc, + i.second.highpc, i.second.image.c_str()); + } +} + diff --git a/usr.sbin/pmc/view.hh b/usr.sbin/pmc/view.hh new file mode 100644 index 000000000000..062325b096dc --- /dev/null +++ b/usr.sbin/pmc/view.hh @@ -0,0 +1,411 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2026, Netflix, Inc. + * + * This software was developed by Ali Mashtizadeh under the sponsorship from + * Netflix, Inc. + * + * 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. + * + */ +#ifndef __VIEW_HH__ +#define __VIEW_HH__ + +#include <libdwarf.h> +#include <libelf.h> +#include <libutil.h> +#include <gelf.h> + +#include "headers.hh" +#include "util.hh" + +/* + * PMC counter state. + */ +struct pmcinfo +{ + pmcinfo() : name(), rate(-1), event(0), count(0) { } + pmcinfo(struct pmclog_ev_pmcallocate &p) { + name = p.pl_evname; + rate = p.pl_rate; + event = p.pl_event; + count = 0; + } + pmcinfo(struct pmclog_ev_pmcallocatedyn &p) { + name = p.pl_evname; + rate = 0; + event = p.pl_event; + count = 0; + } + ~pmcinfo() { } + std::string name; + int rate; + uint32_t event; + uint64_t count; // Available for application use +}; + +/* + * Extended pmcinfo structure stores the complete event description passed to + * libpmc. + */ +struct pmcinfox +{ + pmcinfox() : rate(0), event() { } + pmcinfox(uint32_t rate, std::string event) : rate(rate), event(event) { } + ~pmcinfox() { } + uint32_t rate; + std::string event; +}; + +/* + * CPUID Leafs for X86 Logs + */ +struct cpuidleaf +{ + uint32_t eax; + uint32_t ebx; + uint32_t ecx; + uint32_t edx; +}; + +/* + * Stores the address range for an executable segment in the process. + */ +struct vmmap +{ + vmmap() : lowpc(0), highpc(0), image("") { } + ~vmmap() { } + uint64_t lowpc; + uint64_t highpc; + std::string image; +}; + +struct threadinfo +{ + threadinfo() : name("") { } + threadinfo(const std::string &name) : name(name) { } + ~threadinfo() { } + std::string name; +}; + +struct procinfo +{ + procinfo() : name(""), fullpath(""), map(), threads(), + baseaddr(0), dynaddr(0) { } + procinfo(struct pmclog_ev_proccreate &p) { + std::string pname = p.pl_pcomm; + + if ((p.pl_flags & P_KPROC) != 0) + name = "[" + pname + "]"; + else + name = pname; + map = std::map<uint64_t, vmmap>(); + threads = std::map<pid_t, threadinfo>(); + baseaddr = 0; + dynaddr = 0; + } + ~procinfo() { } + std::string name; + std::string fullpath; + std::map<uint64_t, vmmap> map; + std::map<pid_t, threadinfo> threads; + uint64_t baseaddr; + uint64_t dynaddr; +}; + +/* + * Symbol information + */ +struct syminfo +{ + syminfo() : binary(""), name(""), offset(0), length(0), funcoff(0), + line(0), column(0) { } + ~syminfo() { } + std::string to_string(bool show_line = true); + std::string binary; + std::string name; + uint64_t offset; + uint64_t length; + uint64_t funcoff; + uint32_t line; + uint32_t column; +}; + +struct image +{ + image() : isdynamic(false), vaddr(0), start(0), end(0), + offset(0), name(""), binary(""), path(""), loader(""), + dwarf(""), symbols(), dbg_fd(-1), dbg_elf(NULL) { } + ~image() { } + bool isdynamic; + uint64_t vaddr; + uint64_t start; + uint64_t end; + uint64_t offset; + std::string name; + std::string binary; + std::string path; + std::string loader; + std::string dwarf; + std::map<uint64_t, syminfo> symbols; + int dbg_fd; + Elf *dbg_elf; + Dwarf_Debug dbg_dwarf; +}; + +struct pmcfilter +{ + pmcfilter() : filterpid(false), filtertid(false), + filterprogram(false), filterthread(false), + filterevent(false), filtercpu(false), + kernelonly(false), useronly(false), + pids(), tids(), programs(), threads(), + ibs_ldlat(0), ibs_oplat(0) { CPU_ZERO(&cpus); } + ~pmcfilter() { } + bool filterpid; + bool filtertid; + bool filterprogram; + bool filterthread; + bool filterevent; + bool filtercpu; + bool kernelonly; + bool useronly; + std::unordered_set<int> pids; + std::unordered_set<int> tids; + std::unordered_set<std::string> programs; + std::unordered_set<std::string> threads; + std::unordered_set<std::string> events; + cpuset_t cpus; + /* + * Advanced filters for AMD IBS but should be generalized to support + * other processors. + */ + uint64_t ibs_ldlat; + uint64_t ibs_oplat; + bool ibs_mmio; + void addpids(const char *ids) { + split_and_insert(&pids, ids); + filterpid = true; + } + void addtids(const char *ids) { + split_and_insert(&tids, ids); + filtertid = true; + } + void addthreads(const char *ids) { + split_and_insert(&threads, ids); + filterthread = true; + } + void addprograms(const char *ids) { + split_and_insert(&programs, ids); + filterprogram = true; + } + void addevents(const char *ids) { + split_and_insert(&events, ids); + filterevent = true; + } + void addcpus(const char *ids) { + if (cpuset_parselist(ids, &cpus) == CPUSET_PARSE_OK) + filtercpu = true; + else + fprintf(stderr, "Cannot parse cpulist"); + } +}; + +/* + * These macros help provide a uniform filter facility across all passes. + * Filter options that are specific to a special feature, e.g., IBS, should + * only be accessible through long options, and we reserve option number 0-9 + * for tool specific arguments. + */ +#define PMCFILTER_PRINTOPTS() \ + do { \ + printf("Filter Options:\n"); \ + printf("\t-t,--lwps Filter by thread id\n"); \ + printf("\t-p,--pids Filter by process id\n"); \ + printf("\t-T,--threads Filter by thread name\n"); \ + printf("\t-P,--processes Filter by process name\n"); \ + printf("\t-e,--event Filter by event\n"); \ + printf("\t-c,--cpus Filter by CPU id\n"); \ + printf("\t-k,--kernel Show only kernel events\n"); \ + printf("\t-u,--user Show only user events\n"); \ + printf("\nIBS Fetch Options:\n"); \ + printf("\t--ldlat Filter by load latency\n"); \ + printf("\nIBS Op Options:\n"); \ + printf("\t--ldlat Filter by load latency\n"); \ + printf("\t--oplat Filter by operation latency\n"); \ + printf("\t--mmio Show only mmio events\n"); \ + } while (0) +#define PMCFILTER_LOPTS \ + { "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' }, \ + { "cpus", required_argument, NULL, 'c' }, \ + { "kernel", no_argument, NULL, 'k' }, \ + { "user", no_argument, NULL, 'u' }, \ + { "ldlat", required_argument, NULL, 10 }, \ + { "oplat", required_argument, NULL, 11 }, \ + { "mmio", no_argument, NULL, 12 } +#define PMCFILTER_SOPTS "t:p:T:P:e:c:ku" +#define PMCFILTER_CASE(_filt) \ + case 't': \ + _filt.addtids(optarg); \ + break; \ + case 'p': \ + _filt.addpids(optarg); \ + break; \ + case 'T': \ + _filt.addthreads(optarg); \ + break; \ + case 'P': \ + _filt.addprograms(optarg); \ + break; \ + case 'e': \ + _filt.addevents(optarg); \ + break; \ + case 'c': \ + _filt.addcpus(optarg); \ + break; \ + case 'k': \ + _filt.kernelonly = true; \ + break; \ + case 'u': \ + _filt.useronly = true; \ + break; \ + case 10: \ + _filt.ibs_ldlat = atoi(optarg); \ + break; \ + case 11: \ + _filt.ibs_oplat = atoi(optarg); \ + break; \ + case 12: \ + _filt.ibs_mmio = true; \ + break; + +struct ibsfetchinfo +{ + int len; + uint64_t ctl; + uint64_t extctl; + uint64_t linaddr; + uint64_t physaddr; +}; + +struct ibsopinfo +{ + int len; + uint64_t ctl; + uint64_t rip; + uint64_t data; + uint64_t data2; + uint64_t data3; + uint64_t linaddr; + uint64_t physaddr; + uint64_t tgtrip; + uint64_t data4; +}; + +class pmcview +{ +protected: + pmcview(); + virtual ~pmcview(); +public: + // Filter + void setfilter(const pmcfilter &pmcfilter); + // View Hooks + virtual void proccreate(__unused pid_t pid) { }; + virtual void procexec(__unused pid_t pid) { }; + virtual void procexit(__unused pid_t pid) { }; + virtual void callchain(struct pmclog_ev_callchain &p, + __unused ibsfetchinfo &f, uintfptr_t *cc, int len) { + callchain(p, cc, len); + } + virtual void callchain(struct pmclog_ev_callchain &p, + __unused ibsopinfo &o, uintfptr_t *cc, int len) { + callchain(p, cc, len); + } + virtual void callchain(struct pmclog_ev_callchain &p, + __unused uintfptr_t *cc, __unused int len) { + callchain(p); + } + virtual void callchain(__unused struct pmclog_ev_callchain &p) { } + // Console/File output + virtual void print() { }; + virtual int process_sysinfo(int logfd, const pmchdr_infohdr &infohdr); + virtual int process_pmcinfo(int logfd, const pmchdr_infohdr &infohdr); + virtual int process_cpuidinfo(int logfd, const pmchdr_infohdr &infohdr); + // Process log + virtual void process(int logfd); + virtual void process(struct pmclog_ev &p); + virtual void process(struct pmclog_parse_state *ps); + // Generic events + virtual void process(struct pmclog_ev_initialize &p); + virtual void process(struct pmclog_ev_closelog &p); + virtual void process(struct pmclog_ev_pmcallocate &p); + virtual void process(struct pmclog_ev_pmcallocatedyn &p); + // Process events + virtual void process(struct pmclog_ev_proccreate &p); + virtual void process(struct pmclog_ev_procexit &p); + virtual void process(struct pmclog_ev_procexec &p); + virtual void process(struct pmclog_ev_procfork &p); + virtual void process(struct pmclog_ev_sysexit &p); + // Thread events + virtual void process(struct pmclog_ev_threadcreate &p); + virtual void process(struct pmclog_ev_threadexit &p); + // Map events + virtual void process(struct pmclog_ev_map_in &p); + virtual void process(struct pmclog_ev_map_out &p); + // Callchain events + virtual void process(struct pmclog_ev_callchain &p); +protected: + // Helper functions for views + uint32_t pmcidtoeventid(uint32_t pmcid); + std::string pidtoname(pid_t pid); + syminfo addrtosymbol(pid_t pid, uint64_t addr); + void printvm(pid_t pid); + // Fields available to views + uint64_t tscfreq; + std::unordered_map<uint32_t, uint32_t> pmcid; + std::unordered_map<uint32_t, pmcinfo> pmcinfo; + std::unordered_map<pid_t, procinfo> procs; + std::unordered_map<pid_t, pid_t> tidtopid; + std::unordered_map<std::string, image> images; + std::string sysroot; + pmcfilter filter; + // Extended info + int arch; + std::string cpumodel; + std::string osrelease; + std::string buildid; + std::vector<pmcinfox> extpmcinfo; + std::map<uint32_t, cpuidleaf> cpuid; // x86 Only +private: + image loadimage(const std::string &path); + void mapimage(pid_t pid, const image &im, uint64_t linkaddr); + void loadsymboltable(image *im, Elf *e, Elf_Scn *scn, GElf_Shdr *sh); + void loadsymbols(image *im); +}; + +#endif /* __VIEW_HH__ */ |
