use crate::raw_data::RawData;
use crate::utils::HexValue;
use crate::{
constants, CommonData, CpuMode, Endianness, RecordIdParseInfo, RecordParseInfo, RecordType,
SampleRecord,
};
use byteorder::{BigEndian, ByteOrder, LittleEndian};
use std::fmt;
pub fn get_record_identifier<T: ByteOrder>(
record_type: RecordType,
mut data: RawData,
sample_id_all: bool,
) -> Option<u64> {
if record_type.is_user_type() {
None
} else if record_type == RecordType::SAMPLE {
data.read_u64::<T>().ok()
} else if sample_id_all {
let id_offset_from_start = data.len().checked_sub(8)?;
data.skip(id_offset_from_start).ok()?;
data.read_u64::<T>().ok()
} else {
None
}
}
pub fn get_record_id<T: ByteOrder>(
record_type: RecordType,
mut data: RawData,
parse_info: &RecordIdParseInfo,
) -> Option<u64> {
if record_type.is_user_type() {
return None;
}
if record_type == RecordType::SAMPLE {
if let Some(id_offset_from_start) = parse_info.sample_record_id_offset_from_start {
data.skip(id_offset_from_start as usize).ok()?;
data.read_u64::<T>().ok()
} else {
None
}
} else if let Some(id_offset_from_end) = parse_info.nonsample_record_id_offset_from_end {
let id_offset_from_start = data.len().checked_sub(id_offset_from_end as usize)?;
data.skip(id_offset_from_start).ok()?;
data.read_u64::<T>().ok()
} else {
None
}
}
pub fn get_record_timestamp<T: ByteOrder>(
record_type: RecordType,
mut data: RawData,
parse_info: &RecordParseInfo,
) -> Option<u64> {
if record_type.is_user_type() {
return None;
}
if record_type == RecordType::SAMPLE {
if let Some(time_offset_from_start) = parse_info.sample_record_time_offset_from_start {
data.skip(time_offset_from_start as usize).ok()?;
data.read_u64::<T>().ok()
} else {
None
}
} else if let Some(time_offset_from_end) = parse_info.nonsample_record_time_offset_from_end {
let time_offset_from_start = data.len().checked_sub(time_offset_from_end as usize)?;
data.skip(time_offset_from_start).ok()?;
data.read_u64::<T>().ok()
} else {
None
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(clippy::large_enum_variant)]
#[non_exhaustive]
pub enum EventRecord<'a> {
Sample(SampleRecord<'a>),
Comm(CommOrExecRecord<'a>),
Exit(ForkOrExitRecord),
Fork(ForkOrExitRecord),
Mmap(MmapRecord<'a>),
Mmap2(Mmap2Record<'a>),
Lost(LostRecord),
Throttle(ThrottleRecord),
Unthrottle(ThrottleRecord),
ContextSwitch(ContextSwitchRecord),
Raw(RawEventRecord<'a>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ForkOrExitRecord {
pub pid: i32,
pub ppid: i32,
pub tid: i32,
pub ptid: i32,
pub timestamp: u64,
}
impl ForkOrExitRecord {
pub fn parse<T: ByteOrder>(data: RawData) -> Result<Self, std::io::Error> {
let mut cur = data;
let pid = cur.read_i32::<T>()?;
let ppid = cur.read_i32::<T>()?;
let tid = cur.read_i32::<T>()?;
let ptid = cur.read_i32::<T>()?;
let timestamp = cur.read_u64::<T>()?;
Ok(Self {
pid,
ppid,
tid,
ptid,
timestamp,
})
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct CommOrExecRecord<'a> {
pub pid: i32,
pub tid: i32,
pub name: RawData<'a>,
pub is_execve: bool,
}
impl<'a> CommOrExecRecord<'a> {
pub fn parse<T: ByteOrder>(data: RawData<'a>, misc: u16) -> Result<Self, std::io::Error> {
let mut cur = data;
let pid = cur.read_i32::<T>()?;
let tid = cur.read_i32::<T>()?;
let name = cur.read_string().unwrap_or(cur);
let is_execve = misc & constants::PERF_RECORD_MISC_COMM_EXEC != 0;
Ok(Self {
pid,
tid,
name,
is_execve,
})
}
}
impl<'a> fmt::Debug for CommOrExecRecord<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
use std::str;
let mut map = fmt.debug_map();
map.entry(&"pid", &self.pid).entry(&"tid", &self.tid);
if let Ok(string) = str::from_utf8(&self.name.as_slice()) {
map.entry(&"name", &string);
} else {
map.entry(&"name", &self.name);
}
map.entry(&"is_execve", &self.is_execve);
map.finish()
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct MmapRecord<'a> {
pub pid: i32,
pub tid: i32,
pub address: u64,
pub length: u64,
pub page_offset: u64,
pub is_executable: bool,
pub cpu_mode: CpuMode,
pub path: RawData<'a>,
}
impl<'a> MmapRecord<'a> {
pub fn parse<T: ByteOrder>(data: RawData<'a>, misc: u16) -> Result<Self, std::io::Error> {
let mut cur = data;
let pid = cur.read_i32::<T>()?;
let tid = cur.read_i32::<T>()?;
let address = cur.read_u64::<T>()?;
let length = cur.read_u64::<T>()?;
let page_offset = cur.read_u64::<T>()?;
let path = cur.read_string().unwrap_or(cur); let is_executable = misc & constants::PERF_RECORD_MISC_MMAP_DATA == 0;
Ok(MmapRecord {
pid,
tid,
address,
length,
page_offset,
is_executable,
cpu_mode: CpuMode::from_misc(misc),
path,
})
}
}
impl<'a> fmt::Debug for MmapRecord<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
fmt.debug_map()
.entry(&"pid", &self.pid)
.entry(&"tid", &self.tid)
.entry(&"address", &HexValue(self.address))
.entry(&"length", &HexValue(self.length))
.entry(&"page_offset", &HexValue(self.page_offset))
.entry(&"cpu_mode", &self.cpu_mode)
.entry(&"path", &&*String::from_utf8_lossy(&self.path.as_slice()))
.finish()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Mmap2FileId {
InodeAndVersion(Mmap2InodeAndVersion),
BuildId(Vec<u8>),
}
#[derive(Clone, PartialEq, Eq)]
pub struct Mmap2Record<'a> {
pub pid: i32,
pub tid: i32,
pub address: u64,
pub length: u64,
pub page_offset: u64,
pub file_id: Mmap2FileId,
pub protection: u32,
pub flags: u32,
pub cpu_mode: CpuMode,
pub path: RawData<'a>,
}
impl<'a> Mmap2Record<'a> {
pub fn parse<T: ByteOrder>(data: RawData<'a>, misc: u16) -> Result<Self, std::io::Error> {
let mut cur = data;
let pid = cur.read_i32::<T>()?;
let tid = cur.read_i32::<T>()?;
let address = cur.read_u64::<T>()?;
let length = cur.read_u64::<T>()?;
let page_offset = cur.read_u64::<T>()?;
let file_id = if misc & constants::PERF_RECORD_MISC_MMAP_BUILD_ID != 0 {
let build_id_len = cur.read_u8()?;
assert!(build_id_len <= 20);
let _align = cur.read_u8()?;
let _align = cur.read_u16::<T>()?;
let mut build_id_bytes = [0; 20];
cur.read_exact(&mut build_id_bytes)?;
Mmap2FileId::BuildId(build_id_bytes[..build_id_len as usize].to_owned())
} else {
let major = cur.read_u32::<T>()?;
let minor = cur.read_u32::<T>()?;
let inode = cur.read_u64::<T>()?;
let inode_generation = cur.read_u64::<T>()?;
Mmap2FileId::InodeAndVersion(Mmap2InodeAndVersion {
major,
minor,
inode,
inode_generation,
})
};
let protection = cur.read_u32::<T>()?;
let flags = cur.read_u32::<T>()?;
let path = cur.read_string().unwrap_or(cur);
Ok(Mmap2Record {
pid,
tid,
address,
length,
page_offset,
file_id,
protection,
flags,
cpu_mode: CpuMode::from_misc(misc),
path,
})
}
}
impl<'a> fmt::Debug for Mmap2Record<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
fmt.debug_map()
.entry(&"pid", &self.pid)
.entry(&"tid", &self.tid)
.entry(&"address", &HexValue(self.address))
.entry(&"length", &HexValue(self.length))
.entry(&"page_offset", &HexValue(self.page_offset))
.entry(&"protection", &HexValue(self.protection as _))
.entry(&"flags", &HexValue(self.flags as _))
.entry(&"cpu_mode", &self.cpu_mode)
.entry(&"path", &&*String::from_utf8_lossy(&self.path.as_slice()))
.finish()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Mmap2InodeAndVersion {
pub major: u32,
pub minor: u32,
pub inode: u64,
pub inode_generation: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LostRecord {
pub id: u64,
pub count: u64,
}
impl LostRecord {
pub fn parse<T: ByteOrder>(data: RawData) -> Result<Self, std::io::Error> {
let mut cur = data;
let id = cur.read_u64::<T>()?;
let count = cur.read_u64::<T>()?;
Ok(LostRecord { id, count })
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ThrottleRecord {
pub id: u64,
pub timestamp: u64,
}
impl ThrottleRecord {
pub fn parse<T: ByteOrder>(data: RawData) -> Result<Self, std::io::Error> {
let mut cur = data;
let timestamp = cur.read_u64::<T>()?;
let id = cur.read_u64::<T>()?;
Ok(ThrottleRecord { id, timestamp })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ContextSwitchRecord {
In {
prev_pid: Option<i32>,
prev_tid: Option<i32>,
},
Out {
next_pid: Option<i32>,
next_tid: Option<i32>,
preempted: TaskWasPreempted,
},
}
impl ContextSwitchRecord {
pub fn from_misc(misc: u16) -> Self {
Self::from_misc_pid_tid(misc, None, None)
}
pub fn parse_cpu_wide<T: ByteOrder>(data: RawData, misc: u16) -> Result<Self, std::io::Error> {
let mut cur = data;
let pid = cur.read_i32::<T>()?;
let tid = cur.read_i32::<T>()?;
Ok(Self::from_misc_pid_tid(misc, Some(pid), Some(tid)))
}
pub fn from_misc_pid_tid(misc: u16, pid: Option<i32>, tid: Option<i32>) -> Self {
let is_out = misc & constants::PERF_RECORD_MISC_SWITCH_OUT != 0;
if is_out {
let is_out_preempt = misc & constants::PERF_RECORD_MISC_SWITCH_OUT_PREEMPT != 0;
ContextSwitchRecord::Out {
next_pid: pid,
next_tid: tid,
preempted: if is_out_preempt {
TaskWasPreempted::Yes
} else {
TaskWasPreempted::No
},
}
} else {
ContextSwitchRecord::In {
prev_pid: pid,
prev_tid: tid,
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TaskWasPreempted {
Yes,
No,
}
#[derive(Clone, PartialEq, Eq)]
pub struct RawEventRecord<'a> {
pub record_type: RecordType,
pub misc: u16,
pub data: RawData<'a>,
pub parse_info: RecordParseInfo,
}
impl<'a> RawEventRecord<'a> {
pub fn new(
record_type: RecordType,
misc: u16,
data: RawData<'a>,
parse_info: RecordParseInfo,
) -> Self {
Self {
record_type,
misc,
data,
parse_info,
}
}
pub fn common_data(&self) -> Result<CommonData, std::io::Error> {
if self.record_type.is_user_type() {
return Ok(Default::default());
}
if self.record_type == RecordType::SAMPLE {
CommonData::parse_sample(self.data, &self.parse_info)
} else {
CommonData::parse_nonsample(self.data, &self.parse_info)
}
}
pub fn timestamp(&self) -> Option<u64> {
match self.parse_info.endian {
Endianness::LittleEndian => self.timestamp_impl::<LittleEndian>(),
Endianness::BigEndian => self.timestamp_impl::<BigEndian>(),
}
}
fn timestamp_impl<T: ByteOrder>(&self) -> Option<u64> {
get_record_timestamp::<T>(self.record_type, self.data, &self.parse_info)
}
pub fn id(&self) -> Option<u64> {
match self.parse_info.endian {
Endianness::LittleEndian => self.id_impl::<LittleEndian>(),
Endianness::BigEndian => self.id_impl::<BigEndian>(),
}
}
fn id_impl<T: ByteOrder>(&self) -> Option<u64> {
get_record_id::<T>(self.record_type, self.data, &self.parse_info.id_parse_info)
}
pub fn parse(&self) -> Result<EventRecord<'a>, std::io::Error> {
match self.parse_info.endian {
Endianness::LittleEndian => self.parse_impl::<LittleEndian>(),
Endianness::BigEndian => self.parse_impl::<BigEndian>(),
}
}
fn parse_impl<T: ByteOrder>(&self) -> Result<EventRecord<'a>, std::io::Error> {
let parse_info = &self.parse_info;
let event = match self.record_type {
RecordType::MMAP => EventRecord::Mmap(MmapRecord::parse::<T>(self.data, self.misc)?),
RecordType::LOST => EventRecord::Lost(LostRecord::parse::<T>(self.data)?),
RecordType::COMM => {
EventRecord::Comm(CommOrExecRecord::parse::<T>(self.data, self.misc)?)
}
RecordType::EXIT => EventRecord::Exit(ForkOrExitRecord::parse::<T>(self.data)?),
RecordType::THROTTLE => EventRecord::Throttle(ThrottleRecord::parse::<T>(self.data)?),
RecordType::UNTHROTTLE => {
EventRecord::Unthrottle(ThrottleRecord::parse::<T>(self.data)?)
}
RecordType::FORK => EventRecord::Fork(ForkOrExitRecord::parse::<T>(self.data)?),
RecordType::SAMPLE => {
EventRecord::Sample(SampleRecord::parse::<T>(self.data, self.misc, parse_info)?)
}
RecordType::MMAP2 => EventRecord::Mmap2(Mmap2Record::parse::<T>(self.data, self.misc)?),
RecordType::SWITCH => {
EventRecord::ContextSwitch(ContextSwitchRecord::from_misc(self.misc))
}
RecordType::SWITCH_CPU_WIDE => EventRecord::ContextSwitch(
ContextSwitchRecord::parse_cpu_wide::<T>(self.data, self.misc)?,
),
_ => EventRecord::Raw(self.clone()),
};
Ok(event)
}
}
impl<'a> fmt::Debug for RawEventRecord<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
fmt.debug_map()
.entry(&"record_type", &self.record_type)
.entry(&"misc", &self.misc)
.entry(&"data.len", &self.data.len())
.finish()
}
}