use crate::format_printer::FormatPrinter;
use crate::trace_context::TraceContext;
use crate::trace_event::*;
use crate::TypeKind;
use tracing::{debug, warn};
use zerocopy::FromBytes;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum EventSource {
#[default]
RingBuf,
PerfEventArray,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum ParsedInstruction {
PrintString {
content: String,
},
PrintVariable {
name: String,
type_encoding: TypeKind,
formatted_value: String,
raw_data: Vec<u8>,
},
ExprError {
expr: String,
error_code: u8,
flags: u8,
failing_addr: u64,
},
PrintComplexFormat {
formatted_output: String,
},
PrintComplexVariable {
name: String,
access_path: String,
type_index: u16,
formatted_value: String,
raw_data: Vec<u8>,
},
Backtrace {
requested_depth: u8,
flags: u8,
status: BacktraceStatus,
error_code: u16,
frames: Vec<ParsedBacktraceFrame>,
},
EndInstruction {
total_instructions: u16,
execution_status: u8,
},
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ParsedBacktraceFrame {
pub module_cookie: u64,
pub pc: u64,
pub raw_ip: u64,
pub flags: u16,
}
#[derive(Debug, Clone)]
pub struct ParsedTraceEvent {
pub trace_id: u64,
pub timestamp: u64,
pub pid: u32,
pub tid: u32,
pub instructions: Vec<ParsedInstruction>,
}
impl ParsedTraceEvent {
pub fn has_formatted_output(&self) -> bool {
self.instructions
.iter()
.any(|instruction| !matches!(instruction, ParsedInstruction::EndInstruction { .. }))
}
pub fn try_for_each_formatted_output<E>(
&self,
mut emit: impl FnMut(&str) -> Result<(), E>,
) -> Result<(), E> {
let mut i = 0;
while i < self.instructions.len() {
match &self.instructions[i] {
ParsedInstruction::PrintString { content } => {
if content.contains("{}") {
let (formatted, consumed) =
self.format_string_with_variables(content, i + 1);
emit(&formatted)?;
i += consumed;
} else {
emit(content)?;
i += 1;
}
}
ParsedInstruction::EndInstruction { .. } => {
i += 1;
}
instruction => {
let line = instruction.to_display_string();
emit(&line)?;
i += 1;
}
}
}
Ok(())
}
pub fn to_formatted_output(&self) -> Vec<String> {
let mut output = Vec::new();
let _ =
self.try_for_each_formatted_output(|line| -> Result<(), std::convert::Infallible> {
output.push(line.to_string());
Ok(())
});
output
}
fn format_string_with_variables(
&self,
format_string: &str,
start_index: usize,
) -> (String, usize) {
let placeholder_count = format_string.matches("{}").count();
let mut consumed = 1; let mut result = String::with_capacity(format_string.len());
let mut remaining = format_string;
for instruction_index in
start_index..(start_index + placeholder_count).min(self.instructions.len())
{
let Some(pos) = remaining.find("{}") else {
break;
};
if let Some(ParsedInstruction::PrintVariable {
formatted_value, ..
}) = self.instructions.get(instruction_index)
{
result.push_str(&remaining[..pos]);
result.push_str(formatted_value);
consumed += 1;
remaining = &remaining[pos + 2..];
} else {
break;
}
}
result.push_str(remaining);
(result, consumed)
}
}
#[derive(Debug, Clone)]
pub enum ParseState {
WaitingForHeader,
WaitingForMessage {
header: TraceEventHeader,
},
WaitingForInstructions {
header: TraceEventHeader,
message: TraceEventMessage,
instructions: Vec<ParsedInstruction>,
},
Complete,
}
pub struct StreamingTraceParser {
parse_state: ParseState,
buffer: Vec<u8>,
event_source: EventSource,
}
impl Default for StreamingTraceParser {
fn default() -> Self {
Self::new()
}
}
impl StreamingTraceParser {
pub fn new() -> Self {
Self::with_event_source(EventSource::RingBuf)
}
pub fn with_event_source(event_source: EventSource) -> Self {
Self {
parse_state: ParseState::WaitingForHeader,
buffer: Vec::with_capacity(1024),
event_source,
}
}
pub fn process_segment(
&mut self,
data: &[u8],
trace_context: &TraceContext,
) -> Result<Option<ParsedTraceEvent>, String> {
self.buffer.extend_from_slice(data);
debug!(
"Processing segment of {} bytes, buffer now has {} bytes, state: {:?}",
data.len(),
self.buffer.len(),
self.parse_state
);
loop {
let state = std::mem::replace(&mut self.parse_state, ParseState::Complete);
let consumed = match state {
ParseState::WaitingForHeader => {
let (header, _rest) = match TraceEventHeader::read_from_prefix(&self.buffer) {
Ok((h, r)) => (h, r),
Err(_) => {
self.parse_state = ParseState::WaitingForHeader;
debug!(
"Waiting for more data for header (have {} bytes, need {})",
self.buffer.len(),
std::mem::size_of::<TraceEventHeader>()
);
return Ok(None);
}
};
let magic = header.magic;
if magic != crate::consts::MAGIC {
return Err(format!("Invalid magic number: 0x{magic:x}"));
}
debug!("Received valid header: magic=0x{magic:x}");
self.parse_state = ParseState::WaitingForMessage { header };
std::mem::size_of::<TraceEventHeader>()
}
ParseState::WaitingForMessage { header } => {
let (message, _rest) = match TraceEventMessage::read_from_prefix(&self.buffer) {
Ok((m, r)) => (m, r),
Err(_) => {
self.parse_state = ParseState::WaitingForMessage { header };
debug!(
"Waiting for more data for message (have {} bytes, need {})",
self.buffer.len(),
std::mem::size_of::<TraceEventMessage>()
);
return Ok(None);
}
};
let trace_id = message.trace_id;
let pid = message.pid;
let tid = message.tid;
debug!(
"Received message: trace_id={}, pid={}, tid={}",
trace_id, pid, tid
);
self.parse_state = ParseState::WaitingForInstructions {
header,
message,
instructions: Vec::new(),
};
std::mem::size_of::<TraceEventMessage>()
}
ParseState::WaitingForInstructions {
header,
message,
mut instructions,
} => {
match self.try_parse_instruction(&self.buffer, trace_context)? {
Some((parsed_instruction, consumed_bytes)) => {
if matches!(
parsed_instruction,
ParsedInstruction::EndInstruction { .. }
) {
instructions.push(parsed_instruction);
let complete_event = ParsedTraceEvent {
trace_id: message.trace_id,
timestamp: message.timestamp,
pid: message.pid,
tid: message.tid,
instructions,
};
debug!(
"Completed trace event with {} instructions",
complete_event.instructions.len()
);
self.parse_state = ParseState::WaitingForHeader;
match self.event_source {
EventSource::RingBuf => {
self.buffer.drain(..consumed_bytes);
debug!(
"RingBuf mode: consumed {} bytes, {} bytes remain in buffer",
consumed_bytes,
self.buffer.len()
);
}
EventSource::PerfEventArray => {
self.buffer.clear();
debug!("PerfEventArray mode: cleared buffer after complete event");
}
}
return Ok(Some(complete_event));
} else {
instructions.push(parsed_instruction);
self.parse_state = ParseState::WaitingForInstructions {
header,
message,
instructions,
};
consumed_bytes
}
}
None => {
self.parse_state = ParseState::WaitingForInstructions {
header,
message,
instructions,
};
debug!("Waiting for more data for instruction");
return Ok(None);
}
}
}
ParseState::Complete => {
warn!("Received data while in Complete state, resetting");
self.parse_state = ParseState::WaitingForHeader;
continue;
}
};
if consumed > 0 {
self.buffer.drain(..consumed);
debug!(
"Consumed {} bytes, buffer now has {} bytes",
consumed,
self.buffer.len()
);
}
}
}
fn try_parse_instruction(
&self,
data: &[u8],
trace_context: &TraceContext,
) -> Result<Option<(ParsedInstruction, usize)>, String> {
let (inst_header, _rest) = match InstructionHeader::read_from_prefix(data) {
Ok((h, r)) => (h, r),
Err(_) => return Ok(None),
};
let expected_total_size =
std::mem::size_of::<InstructionHeader>() + inst_header.data_length as usize;
if data.len() < expected_total_size {
debug!(
"Waiting for complete instruction: have {} bytes, need {} bytes",
data.len(),
expected_total_size
);
return Ok(None);
}
let inst_data = &data[std::mem::size_of::<InstructionHeader>()..expected_total_size];
let instruction = match inst_header.inst_type {
t if t == InstructionType::PrintStringIndex as u8 => {
let (data_struct, _) = PrintStringIndexData::read_from_prefix(inst_data)
.map_err(|_| "Invalid PrintStringIndex data".to_string())?;
let string_index = data_struct.string_index;
let string_content = trace_context
.get_string(string_index)
.ok_or_else(|| format!("Invalid string index: {string_index}"))?;
ParsedInstruction::PrintString {
content: string_content.to_string(),
}
}
t if t == InstructionType::PrintVariableIndex as u8 => {
let (data_struct, _) = PrintVariableIndexData::read_from_prefix(inst_data)
.map_err(|_| "Invalid PrintVariableIndex data".to_string())?;
let var_name_index = data_struct.var_name_index;
let var_name = trace_context
.get_variable_name(var_name_index)
.ok_or_else(|| format!("Invalid variable index: {var_name_index}"))?;
let var_data_offset = std::mem::size_of::<PrintVariableIndexData>();
if inst_data.len() < var_data_offset + data_struct.data_len as usize {
return Err("Invalid variable data length".to_string());
}
let var_data =
&inst_data[var_data_offset..var_data_offset + data_struct.data_len as usize];
let type_encoding =
TypeKind::from_u8(data_struct.type_encoding).unwrap_or(TypeKind::Unknown);
let type_index = data_struct.type_index; tracing::debug!("streaming_parser - type_index = {}", type_index);
tracing::debug!(
"streaming_parser - TraceContext has {} types",
trace_context.types.len()
);
let formatted_value = match trace_context.get_type(type_index) {
Some(type_info) => {
tracing::debug!(
"streaming_parser - Found type_info for index {}",
type_index
);
crate::format_printer::FormatPrinter::format_data_with_type_info(
var_data, type_info,
)
}
None => {
tracing::debug!(
"streaming_parser - No type_info found for index {}",
type_index
);
format!(
"<COMPILER_ERROR: type_index {type_index} not found in TraceContext>"
)
}
};
ParsedInstruction::PrintVariable {
name: var_name.to_string(),
type_encoding,
formatted_value,
raw_data: var_data.to_vec(),
}
}
t if t == InstructionType::ExprError as u8 => {
let (data_struct, _) =
crate::trace_event::ExprErrorData::read_from_prefix(inst_data)
.map_err(|_| "Invalid ExprError data".to_string())?;
let si = data_struct.string_index;
let expr = match trace_context.get_string(si) {
Some(s) => s.to_string(),
None => format!("<INVALID_EXPR_INDEX_{si}>"),
};
ParsedInstruction::ExprError {
expr,
error_code: data_struct.error_code,
flags: data_struct.flags,
failing_addr: data_struct.failing_addr,
}
}
t if t == InstructionType::PrintComplexFormat as u8 => {
let (format_data, _) = PrintComplexFormatData::read_from_prefix(inst_data)
.map_err(|_| "Invalid PrintComplexFormat data".to_string())?;
let mut complex_variables = Vec::new();
let mut data_offset = std::mem::size_of::<PrintComplexFormatData>();
for _ in 0..format_data.arg_count {
if data_offset + PRINT_COMPLEX_FORMAT_ARG_ACCESS_PATH_OFFSET > inst_data.len() {
return Err("Invalid PrintComplexFormat argument data".to_string());
}
let var_name_index = u16::from_le_bytes([
inst_data[data_offset + PRINT_COMPLEX_FORMAT_ARG_VAR_NAME_INDEX_OFFSET],
inst_data[data_offset + PRINT_COMPLEX_FORMAT_ARG_VAR_NAME_INDEX_OFFSET + 1],
]);
let type_index = u16::from_le_bytes([
inst_data[data_offset + PRINT_COMPLEX_FORMAT_ARG_TYPE_INDEX_OFFSET],
inst_data[data_offset + PRINT_COMPLEX_FORMAT_ARG_TYPE_INDEX_OFFSET + 1],
]);
let access_path_len = inst_data
[data_offset + PRINT_COMPLEX_FORMAT_ARG_ACCESS_PATH_LEN_OFFSET]
as usize;
let status = inst_data[data_offset + PRINT_COMPLEX_FORMAT_ARG_STATUS_OFFSET];
data_offset += PRINT_COMPLEX_FORMAT_ARG_ACCESS_PATH_OFFSET;
if data_offset + access_path_len > inst_data.len() {
return Err("Invalid PrintComplexFormat access path".to_string());
}
let access_path_bytes = &inst_data[data_offset..data_offset + access_path_len];
let access_path = String::from_utf8_lossy(access_path_bytes).to_string();
data_offset += access_path_len;
if data_offset + PRINT_COMPLEX_FORMAT_ARG_DATA_LEN_SIZE > inst_data.len() {
return Err("Invalid PrintComplexFormat data length".to_string());
}
let data_len =
u16::from_le_bytes([inst_data[data_offset], inst_data[data_offset + 1]]);
data_offset += PRINT_COMPLEX_FORMAT_ARG_DATA_LEN_SIZE;
if data_offset + data_len as usize > inst_data.len() {
return Err("Invalid PrintComplexFormat variable data".to_string());
}
let var_data = inst_data[data_offset..data_offset + data_len as usize].to_vec();
data_offset += data_len as usize;
complex_variables.push(crate::format_printer::ParsedComplexVariable {
var_name_index,
type_index,
access_path,
status,
data: var_data,
});
}
let formatted_output =
crate::format_printer::FormatPrinter::format_complex_print_data(
format_data.format_string_index,
&complex_variables,
trace_context,
);
ParsedInstruction::PrintComplexFormat { formatted_output }
}
t if t == InstructionType::Backtrace as u8 => {
let (data_struct, _) = BacktraceData::read_from_prefix(inst_data)
.map_err(|_| "Invalid Backtrace data".to_string())?;
let requested_depth = data_struct.requested_depth;
let frame_count = data_struct.frame_count;
let flags = data_struct.flags;
let status = BacktraceStatus::from_u8(data_struct.status);
let error_code = data_struct.error_code;
let frame_offset = BACKTRACE_DATA_SIZE;
let available_frames =
inst_data.len().saturating_sub(frame_offset) / BACKTRACE_FRAME_DATA_SIZE;
let parsed_frames = frame_count as usize;
if frame_count > requested_depth || parsed_frames > available_frames {
return Err("Invalid Backtrace data".to_string());
}
let mut frames = Vec::with_capacity(parsed_frames);
for index in 0..parsed_frames {
let start = frame_offset + index * BACKTRACE_FRAME_DATA_SIZE;
let end = start + BACKTRACE_FRAME_DATA_SIZE;
let (frame, _) = BacktraceFrameData::read_from_prefix(&inst_data[start..end])
.map_err(|_| "Invalid Backtrace frame data".to_string())?;
frames.push(ParsedBacktraceFrame {
module_cookie: frame.module_cookie,
pc: frame.pc,
raw_ip: frame.raw_ip,
flags: frame.flags,
});
}
ParsedInstruction::Backtrace {
requested_depth,
flags,
status,
error_code,
frames,
}
}
t if t == InstructionType::PrintComplexVariable as u8 => {
let (data_struct, _) = PrintComplexVariableData::read_from_prefix(inst_data)
.map_err(|_| "Invalid PrintComplexVariable data".to_string())?;
let var_name_index = data_struct.var_name_index;
let var_name = trace_context
.get_variable_name(var_name_index)
.ok_or_else(|| format!("Invalid variable index: {var_name_index}"))?;
let access_path_len = data_struct.access_path_len as usize;
let struct_size = std::mem::size_of::<PrintComplexVariableData>();
if inst_data.len() < struct_size + access_path_len {
return Err("Invalid PrintComplexVariable access path length".to_string());
}
let access_path_bytes = &inst_data[struct_size..struct_size + access_path_len];
let access_path = String::from_utf8_lossy(access_path_bytes);
let var_data_offset = struct_size + access_path_len;
if inst_data.len() < var_data_offset + data_struct.data_len as usize {
return Err("Invalid PrintComplexVariable data length".to_string());
}
let var_data =
&inst_data[var_data_offset..var_data_offset + data_struct.data_len as usize];
let formatted_value = FormatPrinter::format_complex_variable_with_status(
var_name_index,
data_struct.type_index,
&access_path,
var_data,
data_struct.status,
trace_context,
);
ParsedInstruction::PrintComplexVariable {
name: var_name.to_string(),
access_path: access_path.to_string(),
type_index: data_struct.type_index,
formatted_value,
raw_data: var_data.to_vec(),
}
}
t if t == InstructionType::EndInstruction as u8 => {
let (data_struct, _) = EndInstructionData::read_from_prefix(inst_data)
.map_err(|_| "Invalid EndInstruction data".to_string())?;
ParsedInstruction::EndInstruction {
total_instructions: data_struct.total_instructions,
execution_status: data_struct.execution_status,
}
}
_ => {
return Err(format!(
"Unknown instruction type: {}",
inst_header.inst_type
))
}
};
Ok(Some((instruction, expected_total_size)))
}
pub fn reset(&mut self) {
self.parse_state = ParseState::WaitingForHeader;
self.buffer.clear();
}
pub fn get_state(&self) -> &ParseState {
&self.parse_state
}
}
impl ParsedInstruction {
pub fn to_display_string(&self) -> String {
match self {
ParsedInstruction::PrintString { content } => {
format!("print \"{content}\"")
}
ParsedInstruction::PrintVariable {
name,
type_encoding,
formatted_value,
raw_data: _,
} => {
format!("{name} ({type_encoding:?}): {formatted_value}")
}
ParsedInstruction::ExprError {
expr,
error_code,
flags,
failing_addr,
} => {
let reason = match *error_code {
1 => "null deref",
2 => "read error",
3 => "access error",
4 => "truncated",
5 => "offsets unavailable",
6 => "zero length",
_ => "error",
};
fn readable_flags(expr: &str, flags: u8) -> Option<String> {
if flags == 0 {
return None;
}
let mut tags: Vec<&'static str> = Vec::new();
let is_memcmp = expr.contains("memcmp(");
let is_strncmp = expr.contains("strncmp(") || expr.contains("starts_with(");
if is_memcmp {
if (flags & 0x01) != 0 {
tags.push("first-arg read-fail");
}
if (flags & 0x02) != 0 {
tags.push("second-arg read-fail");
}
if (flags & 0x04) != 0 {
tags.push("len-clamped");
}
if (flags & 0x08) != 0 {
tags.push("len=0");
}
} else if is_strncmp {
if (flags & 0x01) != 0 {
tags.push("read-fail");
}
if (flags & 0x04) != 0 {
tags.push("len-clamped");
}
if (flags & 0x08) != 0 {
tags.push("len=0");
}
} else {
return Some(format!("0x{flags:02x}"));
}
if tags.is_empty() {
None
} else {
Some(tags.join(","))
}
}
let flags_text = readable_flags(expr, *flags);
let addr_text = if *failing_addr != 0 {
format!("at 0x{failing_addr:016x}")
} else {
"at NULL".to_string()
};
let base = format!("ExprError: {expr} ({reason} {addr_text}");
match flags_text {
Some(f) => format!("{base}, flags: {f})"),
None => format!("{base})"),
}
}
ParsedInstruction::PrintComplexFormat { formatted_output } => formatted_output.clone(),
ParsedInstruction::PrintComplexVariable {
name: _,
access_path: _,
type_index: _,
formatted_value,
raw_data: _,
} => {
formatted_value.clone()
}
ParsedInstruction::Backtrace {
requested_depth,
status,
error_code,
frames,
..
} => {
let mut lines = vec![format!(
"backtrace(max_depth={requested_depth}, frames={}, status={})",
frames.len(),
status.label()
)];
for (index, frame) in frames.iter().enumerate() {
lines.push(format!(
" #{index} cookie=0x{:016x} pc=0x{:x} raw=0x{:x}",
frame.module_cookie, frame.pc, frame.raw_ip
));
}
if *status != BacktraceStatus::Complete {
let suffix = match backtrace_error_label(*error_code) {
Some("unknown") => format!(" (code={error_code})"),
Some(label) => format!(" ({label}, code={error_code})"),
None => String::new(),
};
lines.push(format!(" stopped: {}{}", status.label(), suffix));
}
lines.join("\n")
}
ParsedInstruction::EndInstruction {
total_instructions,
execution_status,
} => {
let status_str = match *execution_status {
0 => "success",
1 => "partial_failure",
2 => "complete_failure",
_ => "unknown",
};
format!("end({total_instructions} instructions, {status_str})")
}
}
}
pub fn instruction_type(&self) -> String {
match self {
ParsedInstruction::PrintString { .. } => "PrintString".to_string(),
ParsedInstruction::PrintVariable { .. } => "PrintVariable".to_string(),
ParsedInstruction::ExprError { .. } => "ExprError".to_string(),
ParsedInstruction::PrintComplexFormat { .. } => "PrintComplexFormat".to_string(),
ParsedInstruction::PrintComplexVariable { .. } => "PrintComplexVariable".to_string(),
ParsedInstruction::Backtrace { .. } => "Backtrace".to_string(),
ParsedInstruction::EndInstruction { .. } => "EndInstruction".to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_streaming_parser() {
let mut trace_context = TraceContext::new();
let _str_idx = trace_context.add_string("hello world".to_string());
let mut parser = StreamingTraceParser::new();
let header = TraceEventHeader {
magic: crate::consts::MAGIC,
};
let message = TraceEventMessage {
trace_id: 12345,
timestamp: 1000,
pid: 1001,
tid: 2002,
};
let header_bytes = zerocopy::IntoBytes::as_bytes(&header);
let result = parser
.process_segment(header_bytes, &trace_context)
.unwrap();
assert!(result.is_none());
let message_bytes = zerocopy::IntoBytes::as_bytes(&message);
let result = parser
.process_segment(message_bytes, &trace_context)
.unwrap();
assert!(result.is_none());
}
#[test]
fn test_parse_exprerror_instruction() {
let mut trace_context = TraceContext::new();
let expr_idx = trace_context.add_string("memcmp(buf, hex(\"504f\"), 2)".to_string());
let mut parser = StreamingTraceParser::new();
let header = TraceEventHeader {
magic: crate::consts::MAGIC,
};
let header_bytes = zerocopy::IntoBytes::as_bytes(&header);
assert!(parser
.process_segment(header_bytes, &trace_context)
.unwrap()
.is_none());
let message = TraceEventMessage {
trace_id: 1,
timestamp: 0,
pid: 123,
tid: 456,
};
let message_bytes = zerocopy::IntoBytes::as_bytes(&message);
assert!(parser
.process_segment(message_bytes, &trace_context)
.unwrap()
.is_none());
let mut inst = Vec::new();
inst.push(InstructionType::ExprError as u8); inst.extend_from_slice(
&(std::mem::size_of::<crate::trace_event::ExprErrorData>() as u16).to_le_bytes(),
); inst.push(0u8); inst.extend_from_slice(&expr_idx.to_le_bytes()); inst.push(1u8); inst.push(0u8); inst.extend_from_slice(&0x1234_5678_9abc_def0u64.to_le_bytes());
inst.push(InstructionType::EndInstruction as u8);
inst.extend_from_slice(&(std::mem::size_of::<EndInstructionData>() as u16).to_le_bytes());
inst.push(0u8); inst.extend_from_slice(&1u16.to_le_bytes()); inst.push(1u8); inst.push(0u8);
let event = parser
.process_segment(&inst, &trace_context)
.unwrap()
.expect("complete event");
assert_eq!(event.trace_id, 1);
assert_eq!(event.pid, 123);
assert_eq!(event.tid, 456);
assert_eq!(event.instructions.len(), 2);
match &event.instructions[0] {
ParsedInstruction::ExprError {
expr,
error_code,
flags,
failing_addr,
} => {
assert_eq!(expr, "memcmp(buf, hex(\"504f\"), 2)");
assert_eq!(*error_code, 1);
assert_eq!(*flags, 0);
assert_eq!(*failing_addr, 0x1234_5678_9abc_def0u64);
}
other => panic!("unexpected first instruction: {other:?}"),
}
match &event.instructions[1] {
ParsedInstruction::EndInstruction {
total_instructions,
execution_status,
} => {
assert_eq!(*total_instructions, 1);
assert_eq!(*execution_status, 1); }
other => panic!("unexpected last instruction: {other:?}"),
}
}
#[test]
fn test_parse_backtrace_instruction_with_frames() {
let trace_context = TraceContext::new();
let mut parser = StreamingTraceParser::new();
let header = TraceEventHeader {
magic: crate::consts::MAGIC,
};
parser
.process_segment(zerocopy::IntoBytes::as_bytes(&header), &trace_context)
.unwrap();
let message = TraceEventMessage {
trace_id: 7,
timestamp: 0,
pid: 100,
tid: 101,
};
parser
.process_segment(zerocopy::IntoBytes::as_bytes(&message), &trace_context)
.unwrap();
let mut inst = Vec::new();
inst.push(InstructionType::Backtrace as u8);
inst.extend_from_slice(
&((BACKTRACE_DATA_SIZE + BACKTRACE_FRAME_DATA_SIZE) as u16).to_le_bytes(),
);
inst.push(0);
inst.push(8); inst.push(1); inst.push(BACKTRACE_FLAG_INLINE);
inst.push(BacktraceStatus::UnsupportedCfi as u8);
inst.extend_from_slice(&0u16.to_le_bytes());
inst.extend_from_slice(&0u16.to_le_bytes());
inst.extend_from_slice(&0x1122_3344_5566_7788u64.to_le_bytes());
inst.extend_from_slice(&0x1234u64.to_le_bytes());
inst.extend_from_slice(&0x7fff_0000_1234u64.to_le_bytes());
inst.extend_from_slice(&0u16.to_le_bytes());
inst.extend_from_slice(&0u16.to_le_bytes());
inst.extend_from_slice(&0u32.to_le_bytes());
inst.push(InstructionType::EndInstruction as u8);
inst.extend_from_slice(&(std::mem::size_of::<EndInstructionData>() as u16).to_le_bytes());
inst.push(0);
inst.extend_from_slice(&1u16.to_le_bytes());
inst.push(1);
inst.push(0);
let event = parser
.process_segment(&inst, &trace_context)
.unwrap()
.expect("complete event");
match &event.instructions[0] {
ParsedInstruction::Backtrace {
requested_depth,
flags,
status,
frames,
..
} => {
assert_eq!(*requested_depth, 8);
assert_eq!(*flags, BACKTRACE_FLAG_INLINE);
assert_eq!(*status, BacktraceStatus::UnsupportedCfi);
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].module_cookie, 0x1122_3344_5566_7788);
assert_eq!(frames[0].pc, 0x1234);
assert_eq!(frames[0].raw_ip, 0x7fff_0000_1234);
}
other => panic!("unexpected instruction: {other:?}"),
}
}
#[test]
fn test_format_string_only_consumes_contiguous_variables() {
let event = ParsedTraceEvent {
trace_id: 1,
timestamp: 0,
pid: 10,
tid: 11,
instructions: vec![
ParsedInstruction::PrintString {
content: "{} {}".to_string(),
},
ParsedInstruction::PrintString {
content: "literal".to_string(),
},
ParsedInstruction::PrintVariable {
name: "value".to_string(),
type_encoding: TypeKind::I32,
formatted_value: "42".to_string(),
raw_data: vec![42],
},
ParsedInstruction::EndInstruction {
total_instructions: 3,
execution_status: 0,
},
],
};
assert_eq!(
event.to_formatted_output(),
vec![
"{} {}".to_string(),
"literal".to_string(),
"value (I32): 42".to_string(),
]
);
}
}