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::{AttributeDescription, NrCpus, SampleTimeRange};
pub use linux_perf_event_reader;
pub use linux_perf_event_reader::Endianness;
use std::collections::{HashMap, VecDeque};
use std::io::{Read, Seek, SeekFrom};
use std::ops::Deref;
use build_id_event::BuildIdEvent;
use byteorder::{BigEndian, ByteOrder, LittleEndian};
use linear_map::LinearMap;
use linux_perf_event_reader::records::{get_record_event_identifier, RawRecord, RecordParseInfo};
use linux_perf_event_reader::{
AttrFlags, CpuMode, PerfEventHeader, RawData, RecordType, SampleFormat,
};
use perf_file::{PerfFileSection, PerfHeader};
#[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: LinearMap<FlagFeature, Vec<u8>>,
read_offset: u64,
record_data_len: u64,
current_event_body: Vec<u8>,
attributes: Vec<AttributeDescription>,
parse_infos: Vec<RecordParseInfo>,
event_id_to_attr_index: HashMap<u64, usize>,
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 = LinearMap::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.insert(feature, data);
}
let attributes =
if let Some(event_desc_section) = feature_sections.get(&FlagFeature::EventDesc) {
AttributeDescription::parse_event_desc_section::<_, T>(&event_desc_section[..])?
} else if header.event_types_section.size != 0 {
AttributeDescription::parse_event_types_section::<_, T>(
&mut cursor,
&header.event_types_section,
header.attr_size,
)?
} else {
AttributeDescription::parse_attr_section::<_, T>(
&mut cursor,
&header.attr_section,
header.attr_size,
)?
};
if attributes.is_empty() {
return Err(Error::NoAttributes);
}
let mut event_id_to_attr_index = HashMap::new();
for (attr_index, AttributeDescription { event_ids, .. }) in attributes.iter().enumerate() {
for event_id in event_ids {
event_id_to_attr_index.insert(*event_id, attr_index);
}
}
if attributes.len() > 1 {
let has_sample_id_all = attributes[0].attr.flags.contains(AttrFlags::SAMPLE_ID_ALL);
for (attr_index, AttributeDescription { attr, .. }) in attributes.iter().enumerate() {
if !attr.sample_format.contains(SampleFormat::IDENTIFIER) {
return Err(Error::NoIdentifierDespiteMultiEvent(attr_index));
}
if attr.flags.contains(AttrFlags::SAMPLE_ID_ALL) != has_sample_id_all {
return Err(Error::InconsistentSampleIdAllWithMultiEvent(attr_index));
}
}
}
let parse_infos = attributes
.iter()
.map(|attr| RecordParseInfo::new(&attr.attr, endian))
.collect();
cursor.seek(SeekFrom::Start(header.data_section.offset))?;
Ok(Self {
reader: cursor,
endian,
attributes,
parse_infos,
feature_flags: header.flags,
feature_sections,
read_offset: 0,
record_data_len: header.data_section.size,
event_id_to_attr_index,
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) -> &[AttributeDescription] {
&self.attributes
}
pub fn feature_flags(&self) -> FlagFeatureSet {
self.feature_flags
}
pub fn feature_section(&self, feature: FlagFeature) -> Option<&[u8]> {
self.feature_sections.get(&feature).map(Deref::deref)
}
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 = 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> {
match self.feature_section(feature) {
Some(section) => Ok(Some(self.read_string(section)?.0)),
None => Ok(None),
}
}
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)
}
pub fn cmdline(&self) -> Result<Option<Vec<&str>>, Error> {
match self.feature_section(FlagFeature::Cmdline) {
Some(section) => Ok(Some(self.read_string_list(section)?.0)),
None => Ok(None),
}
}
pub fn total_mem(&self) -> Result<Option<u64>, Error> {
let data = match self.feature_section(FlagFeature::TotalMem) {
Some(data) => data,
None => return Ok(None),
};
if data.len() < 8 {
return Err(Error::FeatureSectionTooSmall);
}
let b = data;
let data = [b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]];
let mem = match self.endian {
Endianness::LittleEndian => u64::from_le_bytes(data),
Endianness::BigEndian => u64::from_be_bytes(data),
};
Ok(Some(mem))
}
fn read_string<'s>(&self, s: &'s [u8]) -> Result<(&'s str, &'s [u8]), 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)?;
if rest.len() < len {
return Err(Error::StringLengthTooLong);
}
let (s, rest) = rest.split_at(len);
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, rest))
}
fn read_string_list<'s>(&self, s: &'s [u8]) -> Result<(Vec<&'s str>, &'s [u8]), Error> {
if s.len() < 4 {
return Err(Error::NotEnoughSpaceForStringListLen);
}
let (len_bytes, mut 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::StringListLengthBiggerThanUsize)?;
let mut vec = Vec::with_capacity(len);
for _ in 0..len {
let s;
(s, rest) = self.read_string(rest)?;
vec.push(s);
}
Ok((vec, rest))
}
pub fn next_record(&mut self) -> Result<Option<(usize, RawRecord)>, 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 data = RawData::from(&buffer[..]);
let attr_index = if self.attributes.len() > 1 {
let sample_id_all = self.attributes[0]
.attr
.flags
.contains(AttrFlags::SAMPLE_ID_ALL);
get_record_event_identifier::<T>(record_type, data, sample_id_all)
.and_then(|event_id| self.event_id_to_attr_index.get(&event_id).cloned())
.unwrap_or(0)
} else {
0
};
let parse_info = self.parse_infos[attr_index];
let misc = header.misc;
let raw_event = RawRecord {
record_type,
misc,
data,
parse_info,
};
let timestamp = raw_event.timestamp();
let sort_key = RecordSortKey { timestamp, offset };
let pending_record = PendingRecord {
sort_key,
record_type,
misc,
buffer,
attr_index,
};
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) -> (usize, RawRecord) {
let PendingRecord {
record_type,
misc,
buffer,
attr_index,
..
} = pending_record;
let prev_buffer = std::mem::replace(&mut self.current_event_body, buffer);
self.buffers_for_recycling.push_back(prev_buffer);
(
attr_index,
RawRecord {
record_type,
misc,
data: RawData::from(&self.current_event_body[..]),
parse_info: self.parse_infos[attr_index],
},
)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct PendingRecord {
sort_key: RecordSortKey,
record_type: RecordType,
misc: u16,
buffer: Vec<u8>,
attr_index: usize,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
struct RecordSortKey {
timestamp: Option<u64>,
offset: u64,
}