mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-10-29 03:45:02 +01:00
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
This commit is contained in:
1 parent
a53d2e4f5d
commit
93da997ef7
5 files changed
+1574
No files matched your search
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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__ */
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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__ */
|
||||
Reference in new issue
Block a user