mod build_id_event;
mod dso_key;
mod error;
mod flag_feature;
mod flag_sections;
mod perf_file;
pub use dso_key::DsoKey;
pub use error::{Error, ReadError};
pub use flag_feature::{FlagFeature, FlagFeatureSet};
pub use flag_sections::{NrCpus, SampleTimeRange};
pub use linux_perf_event_reader;
use std::collections::{HashMap, VecDeque};
use std::io::{Cursor, Read, Seek, SeekFrom};
use build_id_event::BuildIdEvent;
use byteorder::{BigEndian, ByteOrder, LittleEndian};
use linux_perf_event_reader::records::{ParsedRecord, RawRecord, RecordParseInfo};
use linux_perf_event_reader::{CpuMode, PerfEventAttr, PerfEventHeader, RawData, RecordType};
use perf_file::{PerfFileSection, PerfHeader};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Endianness {
LittleEndian,
BigEndian,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DsoBuildId {
pub path: Vec<u8>,
pub build_id: Vec<u8>,
}
pub struct PerfFileReader<R: Read> {
reader: R,
endian: Endianness,
feature_flags: FlagFeatureSet,
feature_sections: Vec<(FlagFeature, Vec<u8>)>,
read_offset: u64,
record_data_len: u64,
current_event_body: Vec<u8>,
attributes: PerfEventAttr,
parse_info: RecordParseInfo,
remaining_pending_records: VecDeque<PendingRecord>,
buffers_for_recycling: VecDeque<Vec<u8>>,
}
impl<C: Read + Seek> PerfFileReader<C> {
pub fn parse_file(mut cursor: C) -> Result<Self, Error> {
let header = PerfHeader::parse(&mut cursor)?;
match &header.magic {
b"PERFILE2" => {
Self::parse_file_impl::<LittleEndian>(cursor, header, Endianness::LittleEndian)
}
b"2ELIFREP" => {
Self::parse_file_impl::<BigEndian>(cursor, header, Endianness::BigEndian)
}
_ => Err(Error::UnrecognizedMagicValue(header.magic)),
}
}
fn parse_file_impl<T>(
mut cursor: C,
header: PerfHeader,
endian: Endianness,
) -> Result<Self, Error>
where
T: ByteOrder,
{
let feature_pos = header.data_section.offset + header.data_section.size;
cursor.seek(SeekFrom::Start(feature_pos))?;
let mut feature_sections_info = Vec::new();
for flag in header.flags.iter() {
let section = PerfFileSection::parse::<_, T>(&mut cursor)?;
if let Some(feature) = FlagFeature::from_int(flag) {
feature_sections_info.push((feature, section));
} else {
eprintln!("Unrecognized flag feature {}", flag);
}
}
let mut feature_sections = Vec::new();
for (feature, section) in feature_sections_info {
let offset = section.offset;
let size = usize::try_from(section.size).map_err(|_| Error::SectionSizeTooBig)?;
let mut data = vec![0; size];
cursor.seek(SeekFrom::Start(offset))?;
cursor.read_exact(&mut data)?;
feature_sections.push((feature, data));
}
let attrs_offset = header.attr_section.offset;
let attrs_size = header.attr_section.size;
cursor.seek(SeekFrom::Start(attrs_offset))?;
let mut perf_event_attrs = Vec::new();
let attr_size = header.attr_size;
let mut offset = 0;
while offset + attr_size <= attrs_size {
let attr = PerfEventAttr::parse::<_, T>(&mut cursor, Some(attr_size as u32))
.map_err(|_| ReadError::PerfEventAttr)?;
perf_event_attrs.push(attr);
offset += attr_size;
}
let attributes = perf_event_attrs.remove(0);
let parse_info = RecordParseInfo::from_attr(&attributes);
cursor.seek(SeekFrom::Start(header.data_section.offset))?;
Ok(Self {
reader: cursor,
endian,
attributes,
feature_flags: header.flags,
feature_sections,
read_offset: 0,
record_data_len: header.data_section.size,
parse_info,
remaining_pending_records: VecDeque::new(),
buffers_for_recycling: VecDeque::new(),
current_event_body: Vec::new(),
})
}
}
impl<R: Read> PerfFileReader<R> {
pub fn endian(&self) -> Endianness {
self.endian
}
pub fn attributes(&self) -> &PerfEventAttr {
&self.attributes
}
pub fn feature_flags(&self) -> FlagFeatureSet {
self.feature_flags
}
pub fn feature_section(&self, feature: FlagFeature) -> Option<&[u8]> {
self.feature_sections
.iter()
.find_map(|(f, d)| if *f == feature { Some(&d[..]) } else { None })
}
pub fn build_ids(&self) -> Result<HashMap<DsoKey, DsoBuildId>, Error> {
let section_data = match self.feature_section(FlagFeature::BuildId) {
Some(section) => section,
None => return Ok(HashMap::new()),
};
let mut cursor = Cursor::new(section_data);
let mut build_ids = HashMap::new();
loop {
let event = match self.endian {
Endianness::LittleEndian => BuildIdEvent::parse::<_, LittleEndian>(&mut cursor),
Endianness::BigEndian => BuildIdEvent::parse::<_, BigEndian>(&mut cursor),
};
let event = match event {
Ok(e) => e,
Err(_) => break,
};
let misc = event.header.misc;
let path = event.file_path;
let build_id = event.build_id;
let dso_key = match DsoKey::detect(&path, CpuMode::from_misc(misc)) {
Some(dso_key) => dso_key,
None => continue,
};
build_ids.insert(dso_key, DsoBuildId { path, build_id });
}
Ok(build_ids)
}
pub fn sample_time_range(&self) -> Result<Option<SampleTimeRange>, Error> {
let section_data = match self.feature_section(FlagFeature::SampleTime) {
Some(section) => section,
None => return Ok(None),
};
let time_range = match self.endian {
Endianness::LittleEndian => SampleTimeRange::parse::<_, LittleEndian>(section_data)?,
Endianness::BigEndian => SampleTimeRange::parse::<_, BigEndian>(section_data)?,
};
Ok(Some(time_range))
}
fn feature_string(&self, feature: FlagFeature) -> Result<Option<&str>, Error> {
self.feature_section(feature)
.map(|section| self.read_string(section))
.transpose()
}
pub fn hostname(&self) -> Result<Option<&str>, Error> {
self.feature_string(FlagFeature::Hostname)
}
pub fn os_release(&self) -> Result<Option<&str>, Error> {
self.feature_string(FlagFeature::OsRelease)
}
pub fn perf_version(&self) -> Result<Option<&str>, Error> {
self.feature_string(FlagFeature::Version)
}
pub fn arch(&self) -> Result<Option<&str>, Error> {
self.feature_string(FlagFeature::Arch)
}
pub fn nr_cpus(&self) -> Result<Option<NrCpus>, Error> {
self.feature_section(FlagFeature::NrCpus)
.map(|section| {
Ok(match self.endian {
Endianness::LittleEndian => NrCpus::parse::<_, LittleEndian>(section),
Endianness::BigEndian => NrCpus::parse::<_, BigEndian>(section),
}?)
})
.transpose()
}
pub fn cpu_desc(&self) -> Result<Option<&str>, Error> {
self.feature_string(FlagFeature::CpuDesc)
}
pub fn cpu_id(&self) -> Result<Option<&str>, Error> {
self.feature_string(FlagFeature::CpuId)
}
pub fn is_stats(&self) -> bool {
self.feature_flags.has_flag(FlagFeature::Stat)
}
fn read_string<'s>(&self, s: &'s [u8]) -> Result<&'s str, Error> {
if s.len() < 4 {
return Err(Error::NotEnoughSpaceForStringLen);
}
let (len_bytes, rest) = s.split_at(4);
let len_bytes = [len_bytes[0], len_bytes[1], len_bytes[2], len_bytes[3]];
let len = match self.endian {
Endianness::LittleEndian => u32::from_le_bytes(len_bytes),
Endianness::BigEndian => u32::from_be_bytes(len_bytes),
};
let len = usize::try_from(len).map_err(|_| Error::StringLengthBiggerThanUsize)?;
let s = &rest.get(..len as usize).ok_or(Error::StringLengthTooLong)?;
let actual_len = memchr::memchr(0, s).unwrap_or(s.len());
let s = std::str::from_utf8(&s[..actual_len]).map_err(|_| Error::StringUtf8)?;
Ok(s)
}
pub fn next_record(&mut self) -> Result<Option<ParsedRecord>, Error> {
if self.remaining_pending_records.is_empty() {
self.read_current_round()?;
}
if let Some(pending_record) = self.remaining_pending_records.pop_front() {
return Ok(Some(self.convert_pending_record(pending_record)?));
}
Ok(None)
}
fn read_current_round(&mut self) -> Result<(), Error> {
if self.endian == Endianness::LittleEndian {
self.read_current_round_impl::<byteorder::LittleEndian>()
} else {
self.read_current_round_impl::<byteorder::BigEndian>()
}
}
fn read_current_round_impl<T: ByteOrder>(&mut self) -> Result<(), Error> {
assert!(self.remaining_pending_records.is_empty());
while self.read_offset < self.record_data_len {
let offset = self.read_offset;
let header = PerfEventHeader::parse::<_, T>(&mut self.reader)?;
let size = header.size as usize;
if size < PerfEventHeader::STRUCT_SIZE {
return Err(Error::InvalidPerfEventSize);
}
self.read_offset += u64::from(header.size);
let record_type = RecordType(header.type_);
if record_type == RecordType::FINISHED_ROUND {
if self.remaining_pending_records.is_empty() {
continue;
} else {
break;
}
}
let event_body_len = size - PerfEventHeader::STRUCT_SIZE;
let mut buffer = self.buffers_for_recycling.pop_front().unwrap_or_default();
buffer.resize(event_body_len, 0);
self.reader
.read_exact(&mut buffer)
.map_err(|_| ReadError::PerfEventData)?;
let misc = header.misc;
let raw_event = RawRecord {
record_type,
misc,
data: RawData::from(&buffer[..]),
};
let timestamp = raw_event.timestamp::<T>(&self.parse_info);
let sort_key = RecordSortKey { timestamp, offset };
let pending_record = PendingRecord {
sort_key,
record_type,
misc,
buffer,
};
self.remaining_pending_records.push_back(pending_record);
}
self.remaining_pending_records
.make_contiguous()
.sort_unstable_by_key(|r| r.sort_key);
Ok(())
}
fn convert_pending_record(
&mut self,
pending_record: PendingRecord,
) -> Result<ParsedRecord, Error> {
let PendingRecord {
record_type,
misc,
buffer,
..
} = pending_record;
let prev_buffer = std::mem::replace(&mut self.current_event_body, buffer);
self.buffers_for_recycling.push_back(prev_buffer);
let data = RawData::from(&self.current_event_body[..]);
let raw_record = RawRecord {
record_type,
misc,
data,
};
Ok(if self.endian == Endianness::LittleEndian {
raw_record.to_parsed::<byteorder::LittleEndian>(&self.parse_info)
} else {
raw_record.to_parsed::<byteorder::BigEndian>(&self.parse_info)
}?)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct PendingRecord {
sort_key: RecordSortKey,
record_type: RecordType,
misc: u16,
buffer: Vec<u8>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
struct RecordSortKey {
timestamp: Option<u64>,
offset: u64,
}