aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAli Mashtizadeh <ali@mashtizadeh.com>2026-07-28 17:46:22 +0000
committerWarner Losh <imp@FreeBSD.org>2026-07-28 19:27:44 +0000
commit93da997ef759061670c1575eec92f922a645a5fd (patch)
tree27931b0211f0cc36e5a849a070ac9de2fedb423d
parenta53d2e4f5db588cfd59cdebf04536b6ca7a70e4b (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.hh82
-rw-r--r--usr.sbin/pmc/util.cc82
-rw-r--r--usr.sbin/pmc/util.hh39
-rw-r--r--usr.sbin/pmc/view.cc960
-rw-r--r--usr.sbin/pmc/view.hh411
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__ */