use std::collections::hash_map::Entry;
use std::collections::hash_map::Entry::{Vacant, Occupied};
use std::path::{Path, PathBuf};
use std::fs::File;
use std::fmt::Write;
use std::io::BufReader;
use tracing::{debug, info, trace, warn};
use crate::{ReadOnly, Writable};
use crate::event::DataFieldRef;
use crate::PathBufInteger;
use crate::openat::{OpenAt, DupFd};
use crate::procfs;
use crate::perf_event::{AncillaryData, PerfSession};
use crate::perf_event::{RingBufSessionBuilder, RingBufBuilder};
use crate::perf_event::rb::{RingBufOptions, cgroup_sample_supported};
use crate::perf_event::abi::PERF_RECORD_MISC_SWITCH_OUT;
use crate::helpers::callstack::{CallstackHelp, CallstackReader};
use crate::helpers::exporting::*;
use crate::helpers::exporting::process::{ExportProcessOSHooks, MetricValue};
use crate::helpers::exporting::universal::*;
use crate::helpers::exporting::modulemetadata::{ModuleMetadata, ElfModuleMetadata};
use crate::page_size_to_mask;
use crate::os::system_page_size;
use crate::ruwind::elf::*;
use crate::ruwind::{ModuleAccessor, UnwindType};
use symbols::{ElfSymbolReader, R2RLoadedLayoutSymbolTransformer, R2RMapSymbolReader};
use self::symbols::PerfMapSymbolReader;
pub type Session = PerfSession;
pub type SessionBuilder = RingBufSessionBuilder;
#[derive(Clone)]
pub(crate) struct OSExportProcess {
root_fs: Option<OpenAt>,
}
impl OSExportProcess {
pub(crate) fn new() -> Self {
Self {
root_fs: None,
}
}
}
trait ExportProcessLinuxExt {
fn add_root_fs(
&mut self,
path_buf: &mut PathBuf) -> anyhow::Result<()>;
fn add_matching_elf_symbols(
&mut self,
elf_metadata: &ModuleMetadataLookup,
addrs: &mut HashSet<u64>,
frames: &mut Vec<u64>,
callstacks: &InternedCallstacks,
strings: &mut InternedStrings);
fn find_symbol_files(
&self,
bin_path: &str,
metadata: &ElfModuleMetadata,
sym_types_requested: u32,
strings: &InternedStrings) -> Vec<File>;
fn check_candidate_symbol_file(
&self,
binary_build_id: Option<&[u8; 20]>,
filename: &PathBuf) -> Option<(File, u32)>;
fn add_matching_readytorun_symbols(
&mut self,
pe_metadata: &ModuleMetadataLookup,
addrs: &mut HashSet<u64>,
frames: &mut Vec<u64>,
callstacks: &InternedCallstacks,
strings: &mut InternedStrings);
fn find_readytorun_map_file(
&self,
bin_path: &str,
metadata: &PEModuleMetadata,
strings: &InternedStrings) -> Option<R2RMapSymbolReader>;
}
impl ExportProcessLinuxExt for ExportProcess {
fn add_root_fs(
&mut self,
path_buf: &mut PathBuf) -> anyhow::Result<()> {
path_buf.clear();
path_buf.push("/proc");
path_buf.push_u32(self.pid());
path_buf.push("root");
path_buf.push(".");
if let Ok(root) = File::open(path_buf) {
self.os.root_fs = Some(OpenAt::new(root));
}
Ok(())
}
fn add_matching_elf_symbols(
&mut self,
elf_metadata: &ModuleMetadataLookup,
addrs: &mut HashSet<u64>,
frames: &mut Vec<u64>,
callstacks: &InternedCallstacks,
strings: &mut InternedStrings) {
addrs.clear();
frames.clear();
if self.os.root_fs.is_none() {
warn!("add_matching_elf_symbols: skipped, pid={}, no root_fs available", self.pid());
return;
}
let page_size = self.system_page_size();
let page_mask = self.system_page_mask();
for map_index in 0..self.mappings().len() {
let map = self.mappings().get(map_index).unwrap();
if map.anon() {
continue;
}
ExportProcess::get_unique_user_ips(
&self.samples(),
addrs,
frames,
&callstacks,
Some(map));
if addrs.is_empty() {
continue;
}
frames.clear();
for addr in addrs.iter() {
frames.push(*addr);
}
let filename = match strings.from_id(map.filename_id()) {
Ok(str) => str,
Err(_) => continue
};
info!("Resolving symbols for file: {}", filename);
let dev_node = match map.node() {
Some(key) => key,
None => {
debug!("add_matching_elf_symbols: no dev_node for file={}, skipping", filename);
continue
}
};
if let Some(ModuleMetadata::Elf(metadata)) = elf_metadata.get(dev_node) {
let sym_files = self.find_symbol_files(
filename,
metadata,
SYMBOL_TYPE_ELF_SYMTAB | SYMBOL_TYPE_ELF_DYNSYM,
strings);
if sym_files.is_empty() {
debug!("add_matching_elf_symbols: no symbol files found for file={}", filename);
}
for sym_file in sym_files {
let p_offset = metadata.p_offset() & page_mask;
let p_vaddr = metadata.p_vaddr() & page_mask;
let load_header = ElfLoadHeader::new(p_offset, p_vaddr);
let mut sym_reader = ElfSymbolReader::new(sym_file, load_header, page_size);
let map_mut = self.mappings_mut().get_mut(map_index).unwrap();
map_mut.add_matching_symbols(
frames,
&mut sym_reader,
strings);
}
}
}
}
fn find_symbol_files(
&self,
bin_path: &str,
metadata: &ElfModuleMetadata,
sym_types_requested: u32,
strings: &InternedStrings) -> Vec<File> {
let mut symbol_files = Vec::new();
let mut sym_types_found = 0u32;
let mut path_buf = PathBuf::new();
path_buf.push(bin_path);
if let Some((sym_file, types_found)) = self.check_candidate_symbol_file(
metadata.build_id(),
&path_buf) {
symbol_files.push(sym_file);
sym_types_found |= types_found;
if sym_types_found == sym_types_requested {
return symbol_files
}
}
path_buf.clear();
path_buf.push(format!("{}.dbg", bin_path));
if let Some((sym_file, types_found)) = self.check_candidate_symbol_file(
metadata.build_id(),
&path_buf) {
symbol_files.push(sym_file);
sym_types_found |= types_found;
if sym_types_found == sym_types_requested {
return symbol_files
}
}
path_buf.clear();
path_buf.push(format!("{}.debug", bin_path));
if let Some((sym_file, types_found)) = self.check_candidate_symbol_file(
metadata.build_id(),
&path_buf) {
symbol_files.push(sym_file);
sym_types_found |= types_found;
if sym_types_found == sym_types_requested {
return symbol_files
}
}
if let Some(debug_link) = metadata.debug_link(strings) {
path_buf.clear();
path_buf.push(debug_link);
if let Some((sym_file, types_found)) = self.check_candidate_symbol_file(
metadata.build_id(),
&path_buf) {
symbol_files.push(sym_file);
sym_types_found |= types_found;
if sym_types_found == sym_types_requested {
return symbol_files
}
}
path_buf.clear();
path_buf.push(bin_path);
if let Some(bin_dir_path) = path_buf.parent() {
let mut path_buf = PathBuf::new();
path_buf.push(bin_dir_path);
path_buf.push(debug_link);
if let Some((sym_file, types_found)) = self.check_candidate_symbol_file(
metadata.build_id(),
&path_buf) {
symbol_files.push(sym_file);
sym_types_found |= types_found;
if sym_types_found == sym_types_requested {
return symbol_files
}
}
path_buf.clear();
path_buf.push(bin_dir_path);
path_buf.push(".debug");
path_buf.push(debug_link);
if let Some((sym_file, types_found)) = self.check_candidate_symbol_file(
metadata.build_id(),
&path_buf) {
symbol_files.push(sym_file);
sym_types_found |= types_found;
if sym_types_found == sym_types_requested {
return symbol_files
}
}
path_buf.clear();
path_buf.push("/usr/lib/debug");
path_buf.push(&bin_dir_path.to_str().unwrap()[1..]);
path_buf.push(debug_link);
if let Some((sym_file, types_found)) = self.check_candidate_symbol_file(
metadata.build_id(),
&path_buf) {
symbol_files.push(sym_file);
sym_types_found |= types_found;
if sym_types_found == sym_types_requested {
return symbol_files
}
}
}
}
if let Some(build_id) = metadata.build_id() {
let build_id_string: String = build_id.iter().fold(
String::default(),
|mut str, byte| {
write!(&mut str, "{:02x}", byte).unwrap_or_default();
str
});
path_buf.clear();
path_buf.push("/usr/lib/debug/.build-id/");
path_buf.push(format!("{}/{}.debug",
&build_id_string[0..2],
&build_id_string[2..]));
if let Some((sym_file, types_found)) = self.check_candidate_symbol_file(
metadata.build_id(),
&path_buf) {
symbol_files.push(sym_file);
sym_types_found |= types_found;
if sym_types_found == sym_types_requested {
return symbol_files
}
}
}
path_buf.clear();
path_buf.push("/usr/lib/debug");
path_buf.push(format!("{}{}", &bin_path[1..], ".debug"));
if let Some((sym_file, types_found)) = self.check_candidate_symbol_file(
metadata.build_id(),
&path_buf) {
symbol_files.push(sym_file);
sym_types_found |= types_found;
if sym_types_found == sym_types_requested {
return symbol_files
}
}
path_buf.clear();
path_buf.push("/usr/lib/debug");
path_buf.push(&bin_path[1..]);
if let Some((sym_file, types_found)) = self.check_candidate_symbol_file(
metadata.build_id(),
&path_buf) {
symbol_files.push(sym_file);
sym_types_found |= types_found;
if sym_types_found == sym_types_requested {
return symbol_files
}
}
if bin_path.len() > 9 && &bin_path[0..9] == "/usr/lib/" {
path_buf.clear();
path_buf.push("/usr/lib/debug/lib/");
path_buf.push(&bin_path[9..]);
if let Some((sym_file, types_found)) = self.check_candidate_symbol_file(
metadata.build_id(),
&path_buf) {
symbol_files.push(sym_file);
sym_types_found |= types_found;
if sym_types_found == sym_types_requested {
return symbol_files
}
}
}
symbol_files
}
fn check_candidate_symbol_file(
&self,
binary_build_id: Option<&[u8; 20]>,
filename: &PathBuf) -> Option<(File, u32)> {
let mut matching_sym_file = None;
if let Ok(mut reader) = self.open_file(filename) {
info!("Checking candidate symbol file: {:?}", filename);
let mut build_id_buf: [u8; 20] = [0; 20];
if let Ok(sym_build_id) = get_build_id(&mut reader, &mut build_id_buf) {
match sym_build_id {
Some(sym_id) => {
match binary_build_id {
Some(bin_id) => {
if build_id_equals(bin_id, sym_id) {
info!("Symbol file accepted: {:?} (build_id matches)", filename);
matching_sym_file = Some(reader);
} else {
info!("Symbol file rejected: {:?} (build_id mismatch)", filename);
}
}
None => {
info!("Symbol file rejected: {:?} (symbol file has build_id but binary does not)", filename);
return None;
}
}
},
None => {
match binary_build_id {
Some(_) => {
info!("Symbol file rejected: {:?} (binary has build_id but symbol file does not)", filename);
return None;
}
None => {
info!("Symbol file accepted: {:?} (neither has build_id)", filename);
matching_sym_file = Some(reader);
}
}
}
}
}
}
if let Some(mut reader) = matching_sym_file {
let mut sections = Vec::new();
let mut sym_flags = 0;
if get_section_metadata(&mut reader, None, SHT_SYMTAB, &mut sections).is_err() {
return None;
}
if !sections.is_empty() {
sym_flags |= SYMBOL_TYPE_ELF_SYMTAB;
}
sections.clear();
if get_section_metadata(&mut reader, None, SHT_DYNSYM, &mut sections).is_err() {
return None;
}
if !sections.is_empty() {
sym_flags |= SYMBOL_TYPE_ELF_DYNSYM;
}
if sym_flags != 0 {
return Some((reader, sym_flags));
}
}
None
}
fn add_matching_readytorun_symbols(
&mut self,
pe_metadata: &ModuleMetadataLookup,
addrs: &mut HashSet<u64>,
frames: &mut Vec<u64>,
callstacks: &InternedCallstacks,
strings: &mut InternedStrings) {
addrs.clear();
frames.clear();
if self.os.root_fs.is_none() {
return;
}
for map_index in 0..self.mappings().len() {
let map = self.mappings().get(map_index).unwrap();
if map.anon() {
continue;
}
Self::get_unique_user_ips(
&self.samples(),
addrs,
frames,
&callstacks,
Some(map));
if addrs.is_empty() {
continue;
}
frames.clear();
for addr in addrs.iter() {
frames.push(*addr);
}
let filename = match strings.from_id(map.filename_id()) {
Ok(str) => str,
Err(_) => continue
};
info!("Resolving ReadyToRun symbols for file: {}", filename);
let dev_node = match map.node() {
Some(key) => key,
None => continue
};
if let Some(ModuleMetadata::PE(metadata)) = pe_metadata.get(dev_node) {
if let Some(sym_reader) = self.find_readytorun_map_file(filename, metadata, strings) {
let mut transform_sym_reader = R2RLoadedLayoutSymbolTransformer::new(sym_reader, metadata.text_loaded_layout_offset());
let map_mut = self.mappings_mut().get_mut(map_index).unwrap();
map_mut.add_matching_symbols(
frames,
&mut transform_sym_reader,
strings);
}
} else {
info!("Skipping ReadyToRun symbols for {}: not a PE file", filename);
}
}
}
fn find_readytorun_map_file(
&self,
bin_path: &str,
metadata: &PEModuleMetadata,
strings: &InternedStrings) -> Option<R2RMapSymbolReader> {
let mut path_buf = PathBuf::new();
path_buf.push(bin_path);
path_buf.pop();
if let Some(filename) = metadata.perfmap_name(strings) {
path_buf.push(filename);
info!("Checking ReadyToRun map file: {:?}", path_buf);
if let Ok(file) = self.open_file(&path_buf) {
let mut reader = R2RMapSymbolReader::new(file);
reader.reset();
if *metadata.perfmap_sig() != [0; 16] && metadata.perfmap_sig() == reader.signature() {
info!("ReadyToRun map file accepted: {:?} (signature matches)", path_buf);
return Some(reader);
} else {
info!("ReadyToRun map file rejected: {:?} (signature mismatch or invalid)", path_buf);
}
} else {
info!("ReadyToRun map file rejected: {:?} (could not open file)", path_buf);
}
}
None
}
}
#[cfg(target_os = "linux")]
impl ExportProcessOSHooks for ExportProcess {
fn os_open_file(
&self,
path: &Path) -> anyhow::Result<File> {
match &self.os.root_fs {
None => {
anyhow::bail!("Root fs is not set or had an error.");
},
Some(root_fs) => {
root_fs.open_file(path)
}
}
}
fn system_page_mask(&self) -> u64 {
let page_size = self.system_page_size();
page_size_to_mask(page_size)
}
fn system_page_size(&self) -> u64 {
system_page_size()
}
}
pub(crate) fn default_export_settings() -> ExportSettings {
let helper = if cfg!(target_arch="x86_64") {
CallstackHelper::new().with_dwarf_unwinding()
} else {
CallstackHelper::new()
};
ExportSettings::new(helper)
}
pub(crate) struct OSExportSettings {
process_fs: bool,
}
impl OSExportSettings {
pub(crate) fn new() -> Self {
Self {
process_fs: true,
}
}
}
pub trait ExportSettingsLinuxExt {
fn without_process_fs(self) -> Self;
}
impl ExportSettingsLinuxExt for ExportSettings {
fn without_process_fs(self) -> Self {
let mut clone = self;
clone.os.process_fs = false;
clone
}
}
pub(crate) struct OSExportSampler {
reader: CallstackReader,
ancillary: ReadOnly<AncillaryData>,
time_field: DataFieldRef,
pid_field: DataFieldRef,
tid_field: DataFieldRef,
cgroup_field: DataFieldRef,
}
impl OSExportSampler {
fn new(
session: &PerfSession,
reader: &CallstackReader) -> Self {
Self {
reader: reader.clone(),
ancillary: session.ancillary_data(),
time_field: session.time_data_ref(),
pid_field: session.pid_field_ref(),
tid_field: session.tid_data_ref(),
cgroup_field: session.cgroup_data_ref(),
}
}
}
#[cfg(target_os = "linux")]
impl ExportSamplerOSHooks for ExportSampler {
fn os_event_time(
&self,
data: &EventData) -> anyhow::Result<u64> {
self.os.time_field.get_u64(data.full_data())
}
fn os_event_pid(
&self,
data: &EventData) -> anyhow::Result<u32> {
self.os.pid_field.get_u32(data.full_data())
}
fn os_event_tid(
&self,
data: &EventData) -> anyhow::Result<u32> {
self.os.tid_field.get_u32(data.full_data())
}
fn os_event_cpu(
&self,
_data: &EventData) -> anyhow::Result<u16> {
Ok(self.os.ancillary.borrow().cpu() as u16)
}
fn os_event_version(
&self,
_data: &EventData) -> anyhow::Result<Option<u16>> {
Ok(None)
}
fn os_event_id(
&self,
_data: &EventData) -> anyhow::Result<Option<usize>> {
Ok(None)
}
fn os_event_op_code(
&self,
_data: &EventData) -> anyhow::Result<Option<u16>> {
Ok(None)
}
fn os_event_span_id(
&self,
_data: &EventData) -> anyhow::Result<Option<[u8; 8]>> {
Ok(None)
}
fn os_event_trace_id(
&self,
_data: &EventData) -> anyhow::Result<Option<[u8; 16]>> {
Ok(None)
}
fn os_event_activity_id(
&self,
_data: &EventData) -> anyhow::Result<Option<[u8; 16]>> {
Ok(None)
}
fn os_event_related_activity_id(
&self,
_data: &EventData) -> anyhow::Result<Option<[u8; 16]>> {
Ok(None)
}
fn os_event_cgroup(
&self,
data: &EventData) -> anyhow::Result<Option<u64>> {
Ok(self.os.cgroup_field.try_get_u64(data.full_data()))
}
fn os_event_callstack(
&mut self,
data: &EventData) -> anyhow::Result<()> {
Ok(self.os.reader.read_frames(
data.full_data(),
&mut self.frames))
}
}
pub(crate) struct OSExportMachine {
cswitches: HashMap<u32, ExportCSwitch>,
dev_nodes: ExportDevNodeLookup,
va_offsets: HashMap<ExportDevNode, u64>,
path_buf: Writable<PathBuf>,
}
impl OSExportMachine {
pub(crate) fn new() -> Self {
Self {
cswitches: HashMap::new(),
dev_nodes: ExportDevNodeLookup::new(),
va_offsets: HashMap::new(),
path_buf: Writable::new(PathBuf::new()),
}
}
fn fork_exec(
machine: &mut ExportMachine,
pid: u32,
ppid: u32) -> anyhow::Result<()> {
let fork = machine.process_mut(ppid).fork(pid);
machine.procs.insert(pid, fork);
Ok(())
}
fn event_sampled_count_closure(
machine: &Writable<ExportMachine>,
session: &PerfSession,
callstack_reader: &CallstackReader,
kind: &str) -> impl FnMut(&EventData) -> anyhow::Result<()> + 'static {
let ancillary = session.ancillary_data();
let time_field = session.time_data_ref();
let pid_field = session.pid_field_ref();
let tid_field = session.tid_data_ref();
let reader = callstack_reader.clone();
let kind = machine.borrow_mut().sample_kind(kind);
let event_machine = machine.clone();
let mut frames: Vec<u64> = Vec::new();
move |data| {
let full_data = data.full_data();
let ancillary = ancillary.borrow();
let cpu = ancillary.cpu() as u16;
let time = time_field.get_u64(full_data)?;
let pid = pid_field.get_u32(full_data)?;
let tid = tid_field.get_u32(full_data)?;
frames.clear();
reader.read_frames(
full_data,
&mut frames);
event_machine.borrow_mut().add_sample(
time,
MetricValue::Count(1),
pid,
tid,
cpu,
kind,
&frames)
}
}
fn hook_to_perf_session(
mut machine: ExportMachine,
session: &mut PerfSession) -> anyhow::Result<Writable<ExportMachine>> {
let cpu_profiling = machine.settings.cpu_profiling;
let cswitches = machine.settings.cswitches;
let soft_page_faults = machine.settings.soft_page_faults;
let hard_page_faults = machine.settings.hard_page_faults;
let events = machine.settings.events.take();
let callstack_reader = match machine.settings.callstack_helper.take() {
Some(callstack_helper) => { callstack_helper.to_reader() },
None => { anyhow::bail!("No callstack reader specified."); }
};
let empty_record_type = machine.record_type(ExportRecordType::default());
let machine = Writable::new(machine);
let callstack_machine = machine.clone();
let callstack_reader = callstack_reader.with_unwind(
move |request| {
let machine = callstack_machine.borrow_mut();
if let Some(process) = machine.find_process(request.pid()) {
request.unwind_process(
process,
&machine.os.dev_nodes);
}
});
if let Some(events) = events {
let shared_sampler = Writable::new(
ExportSampler::new(
&machine,
OSExportSampler::new(
session,
&callstack_reader)));
let shared_proxy = Writable::new(ExportProxy::default());
for mut callback in events {
if callback.event.is_none() {
continue;
}
let mut event = callback.event.take().unwrap();
let mut event_machine = machine.borrow_mut();
let mut builder = ExportBuiltContext::new(
&mut event_machine,
&event,
session);
(callback.built)(&mut builder)?;
let sample_kind = builder.take_sample_kind();
let record_type = builder.take_record_type();
let sample_kind = match sample_kind {
Some(kind) => { kind },
None => { event_machine.sample_kind(event.name()) }
};
let record_type = match record_type {
Some(record_type) => { record_type },
None => { empty_record_type },
};
let event_sampler = shared_sampler.clone();
let event_proxy = shared_proxy.clone();
event.add_callback(move |data| {
(callback.trace)(
&mut ExportTraceContext::new(
event_sampler.clone(),
event_proxy.clone(),
sample_kind,
record_type,
data))
});
if event.get_proxy_id().is_some() {
shared_proxy.borrow_mut().add_event(event);
} else {
session.add_event(event)?;
}
}
}
if cpu_profiling {
let closure = Self::event_sampled_count_closure(
&machine,
session,
&callstack_reader,
"cpu");
session.cpu_profile_event().add_callback(closure);
}
if soft_page_faults {
let closure = Self::event_sampled_count_closure(
&machine,
session,
&callstack_reader,
"soft_page_fault");
session.soft_page_fault_event().add_callback(closure);
}
if hard_page_faults {
let closure = Self::event_sampled_count_closure(
&machine,
session,
&callstack_reader,
"hard_page_fault");
session.hard_page_fault_event().add_callback(closure);
}
if cswitches {
let ancillary = session.ancillary_data();
let time_field = session.time_data_ref();
let pid_field = session.pid_field_ref();
let tid_field = session.tid_data_ref();
let reader = callstack_reader.clone();
let kind = machine.borrow_mut().sample_kind("cswitch");
let event = session.cswitch_profile_event();
let event_machine = machine.clone();
let mut frames: Vec<u64> = Vec::new();
event.add_callback(move |data| {
let full_data = data.full_data();
let ancillary = ancillary.borrow();
let cpu = ancillary.cpu() as u16;
let time = time_field.get_u64(full_data)?;
let pid = pid_field.get_u32(full_data)?;
let tid = tid_field.get_u32(full_data)?;
if pid == 0 || tid == 0 {
return Ok(());
}
frames.clear();
reader.read_frames(
full_data,
&mut frames);
let mut machine = event_machine.borrow_mut();
let sample = machine.make_sample(
time,
MetricValue::Duration(0),
tid,
cpu,
kind,
&frames);
machine.os.cswitches.entry(tid).or_default().sample = Some(sample);
Ok(())
});
let misc_field = session.misc_data_ref();
let time_field = session.time_data_ref();
let pid_field = session.pid_field_ref();
let tid_field = session.tid_data_ref();
let event = session.cswitch_event();
let event_machine = machine.clone();
event.add_callback(move |data| {
let full_data = data.full_data();
let misc = misc_field.get_u16(full_data)?;
let time = time_field.get_u64(full_data)?;
let pid = pid_field.get_u32(full_data)?;
let tid = tid_field.get_u32(full_data)?;
if pid == 0 || tid == 0 {
return Ok(());
}
let mut machine = event_machine.borrow_mut();
match machine.os.cswitches.entry(tid) {
Occupied(mut entry) => {
let entry = entry.get_mut();
if misc & PERF_RECORD_MISC_SWITCH_OUT == 0 {
if entry.start_time == 0 {
let _ = entry.sample.take();
return Ok(());
}
let start_time = entry.start_time;
let duration = time - start_time;
entry.start_time = 0;
if let Some(mut sample) = entry.sample.take() {
*sample.time_mut() = start_time;
*sample.value_mut() = MetricValue::Duration(duration);
let _ = machine.add_process_sample(pid, sample);
}
} else {
entry.start_time = time;
}
},
_ => { }
}
Ok(())
});
}
let time_field = session.time_data_ref();
let event = session.mmap_event();
let event_machine = machine.clone();
let fmt = event.format();
let pid = fmt.get_field_ref_unchecked("pid");
let addr = fmt.get_field_ref_unchecked("addr");
let len = fmt.get_field_ref_unchecked("len");
let pgoffset = fmt.get_field_ref_unchecked("pgoffset");
let maj = fmt.get_field_ref_unchecked("maj");
let min = fmt.get_field_ref_unchecked("min");
let ino = fmt.get_field_ref_unchecked("ino");
let prot = fmt.get_field_ref_unchecked("prot");
let filename = fmt.get_field_ref_unchecked("filename[]");
const PROT_EXEC: u32 = 4;
event.add_callback(move |data| {
let fmt = data.format();
let full_data = data.full_data();
let data = data.event_data();
let prot = fmt.get_u32(prot, data)?;
if prot & PROT_EXEC != PROT_EXEC {
return Ok(());
}
event_machine.borrow_mut().add_mmap_exec(
time_field.get_u64(full_data)?,
fmt.get_u32(pid, data)?,
fmt.get_u64(addr, data)?,
fmt.get_u64(len, data)?,
fmt.get_u64(pgoffset, data)?,
fmt.get_u32(maj, data)?,
fmt.get_u32(min, data)?,
fmt.get_u64(ino, data)?,
fmt.get_str(filename, data)?)
});
let time_field = session.time_data_ref();
let event = session.comm_event();
let event_machine = machine.clone();
let fmt = event.format();
let pid = fmt.get_field_ref_unchecked("pid");
let tid = fmt.get_field_ref_unchecked("tid");
let comm = fmt.get_field_ref_unchecked("comm[]");
event.add_callback(move |data| {
let fmt = data.format();
let full_data = data.full_data();
let data = data.event_data();
let pid = fmt.get_u32(pid, data)?;
let tid = fmt.get_u32(tid, data)?;
if pid != tid {
return Ok(())
}
event_machine.borrow_mut().add_comm_exec(
pid,
fmt.get_str(comm, data)?,
time_field.get_u64(full_data)?)
});
let time_field = session.time_data_ref();
let event = session.exit_event();
let event_machine = machine.clone();
let fmt = event.format();
let pid = fmt.get_field_ref_unchecked("pid");
event.add_callback(move |data| {
let fmt = data.format();
let full_data = data.full_data();
let data = data.event_data();
let pid = fmt.get_u32(pid, data)?;
event_machine.borrow_mut().add_comm_exit(
pid,
time_field.get_u64(full_data)?)
});
let event = session.fork_event();
let event_machine = machine.clone();
let fmt = event.format();
let pid = fmt.get_field_ref_unchecked("pid");
let ppid = fmt.get_field_ref_unchecked("ppid");
let tid = fmt.get_field_ref_unchecked("tid");
event.add_callback(move |data| {
let fmt = data.format();
let data = data.event_data();
let pid = fmt.get_u32(pid, data)?;
let tid = fmt.get_u32(tid, data)?;
if pid != tid {
return Ok(());
}
Self::fork_exec(
&mut event_machine.borrow_mut(),
pid,
fmt.get_u32(ppid, data)?)
});
Ok(machine)
}
fn resolve_perf_map_symbols(
machine: &mut ExportMachine) {
let mut frames = Vec::new();
let mut addrs = HashSet::new();
let mut path_buf = machine.os.path_buf.borrow_mut();
path_buf.clear();
path_buf.push("/tmp");
for proc in machine.procs.values_mut() {
if !proc.has_anon_mappings() {
continue;
}
let ns_pid = proc.ns_pid();
if ns_pid.is_none() {
continue;
}
path_buf.push(format!("perf-{}.map", ns_pid.unwrap()));
info!("Checking perf-map file: {:?}", path_buf);
let file = proc.open_file(&path_buf);
path_buf.pop();
let file = match file {
Ok(f) => f,
Err(e) => {
warn!("Unable to open perf-map file: pid={}, ns_pid={:?}, error={}", proc.pid(), ns_pid, e);
continue;
}
};
info!("Perf-map file accepted: file opened successfully");
let mut sym_reader = PerfMapSymbolReader::new(file);
proc.add_matching_anon_symbols(
&mut addrs,
&mut frames,
&mut sym_reader,
&machine.callstacks,
&mut machine.strings);
}
}
fn resolve_readytorun_symbols(
machine: &mut ExportMachine) {
let mut frames = Vec::new();
let mut addrs = HashSet::new();
for proc in machine.procs.values_mut() {
proc.add_matching_readytorun_symbols(
&machine.module_metadata,
&mut addrs,
&mut frames,
&machine.callstacks,
&mut machine.strings);
}
}
fn resolve_elf_symbols(
machine: &mut ExportMachine) {
let mut frames = Vec::new();
let mut addrs = HashSet::new();
for proc in machine.procs.values_mut() {
proc.add_matching_elf_symbols(
&machine.module_metadata,
&mut addrs,
&mut frames,
&machine.callstacks,
&mut machine.strings);
}
}
fn load_elf_metadata(
machine: &mut ExportMachine) {
let mut package_buf = Vec::new();
for proc in machine.procs.values() {
for map in proc.mappings() {
if let Some(key) = map.node() {
if machine.module_metadata.contains(key) {
continue;
}
if map.anon() {
continue;
}
if let Ok(filename) = machine.strings.from_id(map.filename_id()) {
info!("Loading ELF metadata from file: {}", filename);
if let Ok(file) = proc.open_file(Path::new(filename)) {
let mut reader = BufReader::new(file);
let mut sections = Vec::new();
let mut section_offsets = Vec::new();
if is_elf_file(&mut reader).unwrap_or(false) {
if let ModuleMetadata::Elf(elf_metadata) = machine.module_metadata.entry(*key)
.or_insert(ModuleMetadata::Elf(ElfModuleMetadata::new())) {
if get_section_offsets(&mut reader, None, &mut section_offsets).is_err() {
continue;
}
if get_section_metadata(&mut reader, None, SHT_NOTE, &mut sections).is_err() {
continue;
}
let mut build_id: [u8; 20] = [0; 20];
if let Ok(id) = read_build_id(&mut reader, §ions, §ion_offsets, &mut build_id) {
elf_metadata.set_build_id(id);
}
if let Ok(load_header) = get_load_header(&mut reader) {
elf_metadata.set_p_offset(load_header.p_offset());
elf_metadata.set_p_vaddr(load_header.p_vaddr());
}
if read_package_metadata(&mut reader, §ions, §ion_offsets, &mut package_buf).is_ok() {
if let Ok(metadata) = std::str::from_utf8(&package_buf) {
elf_metadata.set_version_metadata(metadata, &mut machine.strings);
}
}
sections.clear();
if get_section_metadata(&mut reader, None, SHT_PROGBITS, &mut sections).is_err() {
continue;
}
let mut debug_link_buf: [u8; 1024] = [0; 1024];
if let Ok(Some(debug_link)) = read_debug_link(&mut reader, §ions, §ion_offsets, &mut debug_link_buf) {
let str_val = get_str(debug_link);
debug!("ELF debug link: link={}", str_val);
elf_metadata.set_debug_link(Some(str_val.to_owned()), &mut machine.strings);
}
}
}
}
}
}
}
}
}
}
struct ExportDevNodeLookup {
fds: HashMap<ExportDevNode, DupFd>,
}
impl ExportDevNodeLookup {
pub(crate) fn new() -> Self {
Self {
fds: HashMap::new(),
}
}
fn contains(
&self,
key: &ExportDevNode) -> bool {
self.fds.contains_key(key)
}
fn entry(
&mut self,
key: ExportDevNode) -> Entry<'_, ExportDevNode, DupFd> {
self.fds.entry(key)
}
pub(crate) fn open(
&self,
node: &ExportDevNode) -> Option<File> {
match self.fds.get(node) {
Some(fd) => { fd.open() },
None => { None },
}
}
}
impl ModuleAccessor for ExportDevNodeLookup {
fn open(
&self,
key: &ExportDevNode) -> Option<File> {
self.open(key)
}
}
#[cfg(target_os = "linux")]
impl ExportMachineOSHooks for ExportMachine {
fn os_add_kernel_mappings_with(
&mut self,
kernel_symbols: &mut impl ExportSymbolReader) {
struct CachedKernelSymbol {
start: u64,
end: u64,
name_id: usize,
}
let mut frames = Vec::new();
let mut addrs = HashSet::new();
let mut all_addrs_set: HashSet<u64> = HashSet::new();
for proc in self.procs.values() {
proc.get_unique_kernel_ips(
&mut addrs,
&mut frames,
&self.callstacks);
for addr in &addrs {
all_addrs_set.insert(*addr);
}
}
if all_addrs_set.is_empty() {
return;
}
let mut all_addrs: Vec<u64> = all_addrs_set.into_iter().collect();
all_addrs.sort();
kernel_symbols.reset();
let mut matched_symbols: Vec<CachedKernelSymbol> = Vec::new();
while kernel_symbols.next() {
let start = kernel_symbols.start();
let end = kernel_symbols.end();
let has_match = match all_addrs.binary_search(&start) {
Ok(_) => true,
Err(i) => i < all_addrs.len() && all_addrs[i] < end,
};
if has_match {
let name_id = self.strings.to_id(kernel_symbols.name());
matched_symbols.push(CachedKernelSymbol {
start,
end,
name_id,
});
}
}
let mut seen = vec![false; matched_symbols.len()];
for proc in self.procs.values_mut() {
proc.get_unique_kernel_ips(
&mut addrs,
&mut frames,
&self.callstacks);
if addrs.is_empty() {
continue;
}
let mut kernel = ExportMapping::new(
0,
self.strings.to_id("vmlinux"),
KERNEL_START,
KERNEL_END,
0,
false,
self.map_index,
UnwindType::DWARF);
self.map_index += 1;
seen.fill(false);
let mut added = 0usize;
for addr in &addrs {
let idx = match matched_symbols.binary_search_by_key(addr, |s| s.start) {
Ok(i) => i,
Err(0) => continue,
Err(i) => i - 1,
};
let sym = &matched_symbols[idx];
if *addr >= sym.start && *addr < sym.end && !seen[idx] {
seen[idx] = true;
added += 1;
let symbol = ExportSymbol::new(sym.name_id, sym.start, sym.end);
trace!("Adding symbol to mapping: mapping_id={}, name={}, start={:#x}, end={:#x}",
kernel.id(),
self.strings.from_id(sym.name_id).unwrap_or(""),
sym.start, sym.end);
kernel.add_symbol(symbol);
}
}
if added > 0 {
info!("Added symbols to mapping: mapping_id={}, start={:#x}, added_count={}",
kernel.id(), kernel.start(), added);
}
proc.add_mapping(kernel);
}
}
fn os_add_dynamic_symbol(
&mut self,
symbol: &DynamicSymbol) -> anyhow::Result<()> {
let pid = symbol.pid();
if let Some(proc) = self.find_process(pid) {
if proc.needs_dynamic_symbol(symbol, &self.callstacks) {
let symbol = symbol.to_export_time_symbol(self);
self.process_mut(pid).add_dynamic_symbol(symbol);
}
}
Ok(())
}
fn os_add_mmap_exec(
&mut self,
pid: u32,
mapping: &mut ExportMapping,
filename: &str) -> anyhow::Result<()> {
match mapping.node() {
Some(node) => {
let node = *node;
if !self.os.dev_nodes.contains(&node) {
let mut va_offset = 0u64;
if let Some(process) = self.find_process(pid) {
if let Ok(file) = process.open_file(Path::new(filename)) {
if let Vacant(entry) = self.os.dev_nodes.entry(node) {
let dup_fd = entry.insert(DupFd::new(file));
if let Some(file) = dup_fd.open() {
let mut reader = BufReader::new(file);
if let Ok(load_header) = get_load_header(&mut reader) {
va_offset = load_header
.p_vaddr()
.saturating_sub(load_header.p_offset());
}
}
}
}
}
self.os.va_offsets.insert(node, va_offset);
}
let va_offset = *self.os.va_offsets.get(&node).unwrap_or(&0);
if va_offset != 0 {
mapping.set_va_offset(va_offset);
}
},
None => {}
}
Ok(())
}
fn os_add_comm_exec(
&mut self,
pid: u32,
_comm: &str) -> anyhow::Result<()> {
let path_buf = self.os.path_buf.clone();
let fs = self.settings.os.process_fs;
let proc = self.process_mut(pid);
let mut path_buf = path_buf.borrow_mut();
*proc.ns_pid_mut() = procfs::ns_pid(&mut path_buf, pid);
if fs {
proc.add_root_fs(&mut path_buf)?;
}
Ok(())
}
fn os_capture_file_symbol_metadata(&mut self) {
OSExportMachine::load_elf_metadata(self);
self.load_pe_metadata();
}
fn os_resolve_local_file_symbols(&mut self) {
OSExportMachine::resolve_elf_symbols(self);
OSExportMachine::resolve_readytorun_symbols(self);
}
fn os_resolve_local_anon_symbols(&mut self) {
OSExportMachine::resolve_perf_map_symbols(self);
}
fn os_qpc_time() -> u64 {
let mut t = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
unsafe {
libc::clock_gettime(
libc::CLOCK_MONOTONIC_RAW,
&mut t);
}
((t.tv_sec * 1000000000) + t.tv_nsec) as u64
}
fn os_qpc_freq() -> u64 {
let mut t = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
unsafe {
libc::clock_getres(
libc::CLOCK_MONOTONIC_RAW,
&mut t);
}
(1000000000 / t.tv_nsec) as u64
}
fn os_cpu_count() -> u32 {
unsafe {
libc::sysconf(libc::_SC_NPROCESSORS_ONLN) as u32
}
}
fn os_system_page_size() -> u64 {
system_page_size()
}
}
impl ExportBuilderHelp for RingBufSessionBuilder {
fn with_exporter_events(
self,
settings: &ExportSettings) -> Self {
let mut builder = self;
let mut kernel = RingBufBuilder::for_kernel()
.with_executable_mmap_records()
.with_comm_records()
.with_task_records();
if settings.cpu_profiling {
let profiling = RingBufBuilder::for_profiling(settings.cpu_freq);
builder = builder.with_profiling_events(profiling);
}
if settings.cswitches {
let cswitches = RingBufBuilder::for_cswitches();
builder = builder.with_cswitch_events(cswitches);
kernel = kernel.with_cswitch_records();
}
if settings.soft_page_faults {
let faults = RingBufBuilder::for_soft_page_faults();
builder = builder.with_soft_page_faults_events(faults);
}
if settings.hard_page_faults {
let faults = RingBufBuilder::for_hard_page_faults();
builder = builder.with_hard_page_faults_events(faults);
}
if settings.events.is_some() {
let mut tracepoint = RingBufBuilder::for_tracepoint();
if cgroup_sample_supported() {
tracepoint = tracepoint.with_cgroup_data();
}
builder = builder.with_tracepoint_events(tracepoint);
}
builder = builder.with_kernel_events(kernel);
match &settings.callstack_helper {
Some(callstack_helper) => {
builder.with_callstack_help(callstack_helper)
},
None => { builder },
}
}
}
impl ExportSessionHelp for PerfSession {
fn build_exporter(
&mut self,
settings: ExportSettings) -> anyhow::Result<Writable<ExportMachine>> {
OSExportMachine::hook_to_perf_session(
ExportMachine::new(settings),
self)
}
}
#[cfg(target_os = "linux")]
impl UniversalExporterOSHooks for UniversalExporter {
fn os_parse_until(
mut self,
_name: &str,
until: impl Fn() -> bool + Send + 'static) -> anyhow::Result<Writable<ExportMachine>> {
let settings = self.settings()?;
let page_size = unsafe { libc::sysconf(libc::_SC_PAGE_SIZE) as usize };
let page_count = self.cpu_buf_bytes() / page_size;
let mut builder = RingBufSessionBuilder::new()
.with_page_count(page_count)
.with_exporter_events(&settings);
if let Some(target_pids) = &settings.target_pids {
for pid in target_pids {
builder = builder.with_target_pid(*pid);
}
}
if let Some(target_cpus) = &settings.target_cpus {
for cpu in target_cpus {
builder = builder.with_target_cpu(*cpu);
}
}
let mut builder = self.run_build_hooks(builder)?;
let mut session = builder.build()?;
let exporter = session.build_exporter(settings)?;
let os_parse_loop = || {
self.run_export_hooks(&exporter)?;
exporter.borrow_mut().mark_start();
session.enable()?;
let env_handle = session.spawn_capture_environment();
session.parse_until(until)?;
session.disable()?;
exporter.borrow_mut().mark_end();
let _ = env_handle.join();
session.parse_all()?;
self.run_parsed_hooks(&exporter)?;
Ok(())
};
match os_parse_loop() {
Ok(()) => Ok(exporter),
Err(err) => {
exporter.borrow_mut().cleanup();
Err(err)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
use std::os::linux::fs::MetadataExt;
use crate::tracefs::TraceFS;
use crate::perf_event::RingBufSessionBuilder;
use crate::helpers::callstack::CallstackHelper;
use graph::{DefaultExportGraphMetricValueConverter, ExportGraphMetricValueConverter};
#[test]
#[ignore]
fn it_works() {
let helper = CallstackHelper::new()
.with_dwarf_unwinding();
let mut settings = ExportSettings::new(helper)
.with_cpu_profiling(1000)
.with_cswitches();
let tracefs = TraceFS::open().unwrap();
let user_fault = tracefs.find_event("exceptions", "page_fault_user").unwrap();
let kernel_fault = tracefs.find_event("exceptions", "page_fault_kernel").unwrap();
settings = settings.with_event(
user_fault,
move |builder| {
builder.set_sample_kind("page_fault_user");
Ok(())
},
move |tracer| {
tracer.sample_builder().save_value(MetricValue::Count(1))
});
settings = settings.with_event(
kernel_fault,
move |builder| {
builder.set_sample_kind("page_fault_kernel");
Ok(())
},
move |tracer| {
tracer.sample_builder().save_value(MetricValue::Count(1))
});
let mut builder = RingBufSessionBuilder::new()
.with_page_count(256)
.with_exporter_events(&settings);
let mut session = builder.build().unwrap();
let exporter = session.build_exporter(settings).unwrap();
let duration = std::time::Duration::from_secs(1);
session.lost_event().add_callback(|_| {
println!("WARN: Lost event data");
Ok(())
});
session.lost_samples_event().add_callback(|_| {
println!("WARN: Lost samples data");
Ok(())
});
session.enable().unwrap();
let env_handle = session.spawn_capture_environment();
session.parse_for_duration(duration).unwrap();
session.disable().unwrap();
let _ = env_handle.join();
let mut exporter = exporter.borrow_mut();
exporter.add_kernel_mappings();
let strings = exporter.strings();
println!("File roots:");
for process in exporter.processes() {
let mut comm = "Unknown";
if let Some(comm_id) = process.comm_id() {
if let Ok(value) = strings.from_id(comm_id) {
comm = value;
}
}
let file = process.open_file(Path::new("."));
match file {
Ok(file) => {
match file.metadata() {
Ok(meta) => {
println!("{}: ino: {}, dev: {}", comm, meta.st_ino(), meta.st_dev());
},
Err(error) => {
println!("Error({}): {:?}", comm, error);
}
}
},
Err(error) => {
println!("Error({}): {:?}", comm, error);
}
}
}
let kinds = exporter.sample_kinds();
for process in exporter.processes() {
let mut comm = "Unknown";
if let Some(comm_id) = process.comm_id() {
if let Ok(value) = strings.from_id(comm_id) {
comm = value;
}
}
let converter = DefaultExportGraphMetricValueConverter::default();
println!(
"{}: {} ({} Samples)",
process.pid(),
comm,
process.samples().len());
for sample in process.samples() {
println!(
"{}: {:x} ({}) TID={},Kind={},Value={}",
sample.time(),
sample.ip(),
sample.callstack_id(),
sample.tid(),
kinds[sample.kind() as usize],
converter.convert(&exporter, sample.value()));
}
if process.samples().len() > 0 {
println!();
}
}
}
#[test]
#[ignore]
fn kernel_symbols() {
let mut reader = KernelSymbolReader::new();
let mut count = 0;
reader.reset();
while reader.next() {
println!(
"{:x} - {:x}: {}",
reader.start(),
reader.end(),
reader.name());
count += 1;
}
assert!(count > 0);
}
}