use crate::event::{Event, stack_kind_from_header};
use crate::stacks::format_stack_frames_hex;
use anyhow::{Result, anyhow};
use probex_common::{
CpuSampleEvent, EventHeader, EventType, IoUringCompleteEvent, MAX_CPU_SAMPLE_FRAMES,
PageFaultEvent, ProcessExitEvent, ProcessForkEvent, SchedSwitchEvent, SyscallEnterEvent,
SyscallExitEvent,
};
pub fn read_unaligned_from_bytes<T: Copy>(data: &[u8]) -> Option<T> {
if data.len() < std::mem::size_of::<T>() {
return None;
}
Some(unsafe { std::ptr::read_unaligned(data.as_ptr() as *const T) })
}
pub fn event_base(event_type: &'static str, header: EventHeader) -> Event {
Event {
event_type,
ts_ns: header.timestamp_ns,
pid: header.pid,
tgid: header.tgid,
stack_id: (header.stack_id >= 0).then_some(header.stack_id),
kernel_stack_id: (header.kernel_stack_id >= 0).then_some(header.kernel_stack_id),
stack_kind: stack_kind_from_header(header.stack_kind),
cpu: header.cpu,
..Default::default()
}
}
pub fn parse_event(data: &[u8]) -> Result<Event> {
let header = read_unaligned_from_bytes::<EventHeader>(data).ok_or_else(|| {
anyhow!(
"event payload too short for EventHeader: {} bytes",
data.len()
)
})?;
let event_type = EventType::try_from(header.event_type)
.map_err(|value| anyhow!("unknown event_type discriminant: {value}"))?;
match event_type {
EventType::SchedSwitch => {
let event = read_unaligned_from_bytes::<SchedSwitchEvent>(data)
.ok_or_else(|| anyhow!("payload too short for SchedSwitchEvent"))?;
Ok(Event {
prev_pid: Some(event.prev_pid),
next_pid: Some(event.next_pid),
prev_state: Some(event.prev_state),
..event_base("sched_switch", event.header)
})
}
EventType::ProcessFork => {
let event = read_unaligned_from_bytes::<ProcessForkEvent>(data)
.ok_or_else(|| anyhow!("payload too short for ProcessForkEvent"))?;
Ok(Event {
parent_pid: Some(event.parent_pid),
child_pid: Some(event.child_pid),
..event_base("process_fork", event.header)
})
}
EventType::ProcessExit => {
let event = read_unaligned_from_bytes::<ProcessExitEvent>(data)
.ok_or_else(|| anyhow!("payload too short for ProcessExitEvent"))?;
Ok(Event {
exit_code: Some(event.exit_code),
..event_base("process_exit", event.header)
})
}
EventType::PageFault => {
let event = read_unaligned_from_bytes::<PageFaultEvent>(data)
.ok_or_else(|| anyhow!("payload too short for PageFaultEvent"))?;
Ok(Event {
address: Some(event.address),
error_code: Some(event.error_code),
..event_base("page_fault", event.header)
})
}
EventType::SyscallReadEnter => {
let event = read_unaligned_from_bytes::<SyscallEnterEvent>(data)
.ok_or_else(|| anyhow!("payload too short for SyscallEnterEvent"))?;
Ok(Event {
fd: Some(event.fd),
count: Some(event.count),
..event_base("syscall_read_enter", event.header)
})
}
EventType::SyscallReadExit => {
let event = read_unaligned_from_bytes::<SyscallExitEvent>(data)
.ok_or_else(|| anyhow!("payload too short for SyscallExitEvent"))?;
Ok(Event {
ret: Some(event.ret),
..event_base("syscall_read_exit", event.header)
})
}
EventType::SyscallWriteEnter => {
let event = read_unaligned_from_bytes::<SyscallEnterEvent>(data)
.ok_or_else(|| anyhow!("payload too short for SyscallEnterEvent"))?;
Ok(Event {
fd: Some(event.fd),
count: Some(event.count),
..event_base("syscall_write_enter", event.header)
})
}
EventType::SyscallWriteExit => {
let event = read_unaligned_from_bytes::<SyscallExitEvent>(data)
.ok_or_else(|| anyhow!("payload too short for SyscallExitEvent"))?;
Ok(Event {
ret: Some(event.ret),
..event_base("syscall_write_exit", event.header)
})
}
EventType::SyscallMmapEnter => {
let event = read_unaligned_from_bytes::<SyscallEnterEvent>(data)
.ok_or_else(|| anyhow!("payload too short for SyscallEnterEvent"))?;
Ok(Event {
address: Some(event.fd as u64),
count: Some(event.count),
..event_base("syscall_mmap_enter", event.header)
})
}
EventType::SyscallMmapExit => {
let event = read_unaligned_from_bytes::<SyscallExitEvent>(data)
.ok_or_else(|| anyhow!("payload too short for SyscallExitEvent"))?;
Ok(Event {
ret: Some(event.ret),
..event_base("syscall_mmap_exit", event.header)
})
}
EventType::SyscallMunmapEnter => {
let event = read_unaligned_from_bytes::<SyscallEnterEvent>(data)
.ok_or_else(|| anyhow!("payload too short for SyscallEnterEvent"))?;
Ok(Event {
address: Some(event.fd as u64),
count: Some(event.count),
..event_base("syscall_munmap_enter", event.header)
})
}
EventType::SyscallMunmapExit => {
let event = read_unaligned_from_bytes::<SyscallExitEvent>(data)
.ok_or_else(|| anyhow!("payload too short for SyscallExitEvent"))?;
Ok(Event {
ret: Some(event.ret),
..event_base("syscall_munmap_exit", event.header)
})
}
EventType::SyscallBrkEnter => {
let event = read_unaligned_from_bytes::<SyscallEnterEvent>(data)
.ok_or_else(|| anyhow!("payload too short for SyscallEnterEvent"))?;
Ok(Event {
address: Some(event.fd as u64),
..event_base("syscall_brk_enter", event.header)
})
}
EventType::SyscallBrkExit => {
let event = read_unaligned_from_bytes::<SyscallExitEvent>(data)
.ok_or_else(|| anyhow!("payload too short for SyscallExitEvent"))?;
Ok(Event {
ret: Some(event.ret),
..event_base("syscall_brk_exit", event.header)
})
}
EventType::SyscallIoUringSetupEnter => {
let event = read_unaligned_from_bytes::<SyscallEnterEvent>(data)
.ok_or_else(|| anyhow!("payload too short for SyscallEnterEvent"))?;
Ok(Event {
fd: Some(event.fd),
count: Some(event.count),
..event_base("syscall_io_uring_setup_enter", event.header)
})
}
EventType::SyscallIoUringSetupExit => {
let event = read_unaligned_from_bytes::<SyscallExitEvent>(data)
.ok_or_else(|| anyhow!("payload too short for SyscallExitEvent"))?;
Ok(Event {
ret: Some(event.ret),
..event_base("syscall_io_uring_setup_exit", event.header)
})
}
EventType::SyscallIoUringEnterEnter => {
let event = read_unaligned_from_bytes::<SyscallEnterEvent>(data)
.ok_or_else(|| anyhow!("payload too short for SyscallEnterEvent"))?;
Ok(Event {
fd: Some(event.fd),
count: Some(event.count),
..event_base("syscall_io_uring_enter_enter", event.header)
})
}
EventType::SyscallIoUringEnterExit => {
let event = read_unaligned_from_bytes::<SyscallExitEvent>(data)
.ok_or_else(|| anyhow!("payload too short for SyscallExitEvent"))?;
Ok(Event {
ret: Some(event.ret),
..event_base("syscall_io_uring_enter_exit", event.header)
})
}
EventType::SyscallIoUringRegisterEnter => {
let event = read_unaligned_from_bytes::<SyscallEnterEvent>(data)
.ok_or_else(|| anyhow!("payload too short for SyscallEnterEvent"))?;
Ok(Event {
fd: Some(event.fd),
count: Some(event.count),
..event_base("syscall_io_uring_register_enter", event.header)
})
}
EventType::SyscallIoUringRegisterExit => {
let event = read_unaligned_from_bytes::<SyscallExitEvent>(data)
.ok_or_else(|| anyhow!("payload too short for SyscallExitEvent"))?;
Ok(Event {
ret: Some(event.ret),
..event_base("syscall_io_uring_register_exit", event.header)
})
}
EventType::CpuSample => {
let event = read_unaligned_from_bytes::<CpuSampleEvent>(data)
.ok_or_else(|| anyhow!("payload too short for CpuSampleEvent"))?;
let frame_count = usize::from(event.frame_count).min(MAX_CPU_SAMPLE_FRAMES);
let mut frames: Vec<u64> = event.frames[..frame_count].to_vec();
frames.reverse();
let mut out = event_base("cpu_sample", event.header);
if let Some(stack_frames) = format_stack_frames_hex(&frames) {
out.stack_frames = Some(stack_frames.clone());
out.stack_trace = Some(format!("[user];{stack_frames}"));
} else {
out.stack_kind = None;
}
Ok(out)
}
EventType::SyscallFsyncEnter => {
let event = read_unaligned_from_bytes::<SyscallEnterEvent>(data)
.ok_or_else(|| anyhow!("payload too short for SyscallEnterEvent"))?;
Ok(Event {
fd: Some(event.fd),
..event_base("syscall_fsync_enter", event.header)
})
}
EventType::SyscallFsyncExit => {
let event = read_unaligned_from_bytes::<SyscallExitEvent>(data)
.ok_or_else(|| anyhow!("payload too short for SyscallExitEvent"))?;
Ok(Event {
ret: Some(event.ret),
..event_base("syscall_fsync_exit", event.header)
})
}
EventType::SyscallFdatasyncEnter => {
let event = read_unaligned_from_bytes::<SyscallEnterEvent>(data)
.ok_or_else(|| anyhow!("payload too short for SyscallEnterEvent"))?;
Ok(Event {
fd: Some(event.fd),
..event_base("syscall_fdatasync_enter", event.header)
})
}
EventType::SyscallFdatasyncExit => {
let event = read_unaligned_from_bytes::<SyscallExitEvent>(data)
.ok_or_else(|| anyhow!("payload too short for SyscallExitEvent"))?;
Ok(Event {
ret: Some(event.ret),
..event_base("syscall_fdatasync_exit", event.header)
})
}
EventType::IoUringComplete => {
let event = read_unaligned_from_bytes::<IoUringCompleteEvent>(data)
.ok_or_else(|| anyhow!("payload too short for IoUringCompleteEvent"))?;
Ok(Event {
submit_ts_ns: Some(event.submit_ts_ns),
io_uring_opcode: Some(event.opcode),
io_uring_res: Some(event.res),
..event_base("io_uring_complete", event.header)
})
}
}
}