use quick_xml::{
events::{attributes::Attributes, Event},
reader::Reader,
};
use std::{
borrow::Cow,
collections::BTreeMap,
io::{self, BufRead},
};
use super::{
common::{fix_partially_demangled_rust_symbol, Occurrences},
Collapse,
};
const REF: &[u8] = b"ref";
const ID: &[u8] = b"id";
const NAME: &[u8] = b"name";
const TRACE_QUERY_RESULT: &[u8] = b"trace-query-result";
const NODE: &[u8] = b"node";
const ROW: &[u8] = b"row";
const BACKTRACE: &[u8] = b"backtrace";
const FRAME: &[u8] = b"frame";
fn is_interested_tag(tag: &[u8]) -> bool {
matches!(tag, TRACE_QUERY_RESULT | NODE | ROW | BACKTRACE | FRAME)
}
struct BacktraceOccurrences {
num: u64,
backtrace: BacktraceId,
}
#[derive(Default)]
struct TagBacktrace {
backtrace: Vec<CurrentTag>,
}
impl TagBacktrace {
fn push_back(&mut self, tag: CurrentTag) {
self.backtrace.push(tag);
}
fn pop_with_name(&mut self, name: &[u8]) -> Option<CurrentTag> {
self.backtrace
.last()
.is_some_and(|t| t.matches(name))
.then(|| self.backtrace.pop())?
}
fn top_mut(&mut self) -> Option<&mut CurrentTag> {
self.backtrace.last_mut()
}
}
enum CurrentTag {
TraceQueryResult {
nodes: Vec<Node>,
},
Node {
rows: Vec<Row>,
},
Row {
backtrace: Option<BacktraceId>,
},
Backtrace {
id: BacktraceId,
frames: Vec<FrameId>,
},
Frame {
id: FrameId,
name: Box<[u8]>,
},
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[repr(transparent)]
struct FrameId(u64);
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[repr(transparent)]
struct BacktraceId(u64);
impl CurrentTag {
fn matches(&self, name: &[u8]) -> bool {
match name {
TRACE_QUERY_RESULT => matches!(self, Self::TraceQueryResult { .. }),
NODE => matches!(self, Self::Node { .. }),
ROW => matches!(self, Self::Row { .. }),
BACKTRACE => matches!(self, Self::Backtrace { .. }),
FRAME => matches!(self, Self::Frame { .. }),
_ => false,
}
}
}
struct Node {
rows: Vec<Row>,
}
struct Row {
backtrace: BacktraceId,
}
struct Backtrace {
id: BacktraceId,
frames: Vec<FrameId>,
}
struct Frame {
id: FrameId,
name: Box<[u8]>,
}
impl BacktraceId {
fn resolve(&self, context: &Folder) -> String {
let backtrace = context
.backtraces
.get(self)
.expect("Backtrace id not registered in collapse context, this is a inferno bug.");
let mut folded = String::new();
let mut first = Some(());
for frame in backtrace.frames.iter().rev() {
if first.take().is_none() {
folded.push(';');
}
let frame = context
.frames
.get(frame)
.expect("Frame id not registered in collapse context, this is a inferno bug.");
let frame_name = String::from_utf8_lossy(&frame.name);
let frame_name = fix_partially_demangled_rust_symbol(&frame_name);
folded.push_str(&frame_name);
}
folded
}
}
fn unescape_xctrace_text(text: Cow<'_, [u8]>) -> io::Result<Box<[u8]>> {
match quick_xml::escape::unescape(&String::from_utf8_lossy(&text)) {
Ok(x) => Ok(x.into_owned().into_bytes().into_boxed_slice()),
Err(e) => invalid_data_error!(
"Invalid xml text from xctrace, which is not expected: {:?}",
e
),
}
}
fn get_u64_from_attributes(key: &'static [u8], attributes: &Attributes) -> io::Result<u64> {
let id = attributes
.clone()
.filter_map(|x| x.ok())
.find_map(|x| (x.key.into_inner() == key).then_some(x.value));
let Some(id) = id else {
return invalid_data_error!("No {} found in attributes", String::from_utf8_lossy(key));
};
let id = String::from_utf8_lossy(&id);
match id.parse() {
Ok(x) => Ok(x),
Err(e) => invalid_data_error!(
"Unrecognized {}: {}: {:?}",
String::from_utf8_lossy(key),
id,
e
),
}
}
fn get_name_from_attributes(attributes: &Attributes) -> io::Result<Box<[u8]>> {
let name = attributes
.clone()
.filter_map(|x| x.ok())
.find_map(|x| (x.key.into_inner() == NAME).then_some(x.value));
match name {
Some(x) => unescape_xctrace_text(x),
None => invalid_data_error!("No name(symbol) found in attributes"),
}
}
fn attributes_to_backtrace(attributes: &Attributes) -> io::Result<BacktraceId> {
get_u64_from_attributes(ID, attributes).map(BacktraceId)
}
fn attributes_to_frame(attributes: &Attributes) -> io::Result<(FrameId, Box<[u8]>)> {
let id = get_u64_from_attributes(ID, attributes)?;
let name = get_name_from_attributes(attributes)?;
Ok((FrameId(id), name))
}
#[derive(Default)]
pub struct Folder {
state_backtrace: TagBacktrace,
backtraces: BTreeMap<BacktraceId, Backtrace>,
frames: BTreeMap<FrameId, Frame>,
}
impl Collapse for Folder {
fn collapse<R, W>(&mut self, reader: R, writer: W) -> std::io::Result<()>
where
R: std::io::BufRead,
W: std::io::Write,
{
let mut reader = Reader::from_reader(reader);
self.collapse_inner(&mut reader, writer)
}
fn is_applicable(&mut self, input: &str) -> Option<bool> {
let mut input = input.as_bytes();
let mut line = String::new();
let mut is_xml = false;
let mut is_xctrace = false;
loop {
if let Ok(n) = input.read_line(&mut line) {
if n == 0 {
break;
}
} else {
return Some(false);
}
let trimmed = line.trim();
if !trimmed.is_empty() {
is_xml = is_xml || trimmed.contains(r#"<?xml version="1.0""#);
is_xctrace = is_xctrace || trimmed.contains("<trace-query-result");
if is_xml && is_xctrace {
return Some(true);
}
}
line.clear();
}
None
}
}
impl Folder {
fn collapse_inner<R, W>(&mut self, reader: &mut Reader<R>, writer: W) -> io::Result<()>
where
R: std::io::BufRead,
W: std::io::Write,
{
let mut buf = Vec::new();
let nodes = loop {
let event = match reader.read_event_into(&mut buf) {
Ok(x) => x,
Err(e) => return invalid_data_error!("Read xml event failed: {:?}", e),
};
match event {
Event::Start(start) => {
let attributes = start.attributes();
let name = start.name().into_inner();
let new_state = match (self.state_backtrace.top_mut(), name) {
(None, TRACE_QUERY_RESULT) => {
Some(CurrentTag::TraceQueryResult { nodes: Vec::new() })
}
(None, _) => {
None
}
(Some(CurrentTag::TraceQueryResult { .. }), NODE) => {
Some(CurrentTag::Node { rows: Vec::new() })
}
(Some(CurrentTag::Node { .. }), ROW) => {
Some(CurrentTag::Row { backtrace: None })
}
(Some(CurrentTag::Row { .. }), BACKTRACE) => {
let id = attributes_to_backtrace(&attributes)?;
Some(CurrentTag::Backtrace {
id,
frames: Vec::new(),
})
}
(Some(CurrentTag::Backtrace { .. }), FRAME) => {
let (id, name) = attributes_to_frame(&attributes)?;
Some(CurrentTag::Frame { id, name })
}
(Some(_), _) => {
None
}
};
if let Some(new_state) = new_state {
self.state_backtrace.push_back(new_state);
}
}
Event::End(end) => {
let name = end.name().into_inner();
if !is_interested_tag(name) {
continue;
}
let Some(state) = self.state_backtrace.pop_with_name(name) else {
return invalid_data_error!(
"Unpaired tag: {}",
String::from_utf8_lossy(name)
);
};
match (self.state_backtrace.top_mut(), state) {
(None, CurrentTag::TraceQueryResult { nodes }) => {
break nodes;
}
(
Some(CurrentTag::TraceQueryResult { nodes }),
CurrentTag::Node { rows },
) => {
nodes.push(Node { rows });
}
(Some(CurrentTag::Node { rows }), CurrentTag::Row { backtrace }) => {
if let Some(backtrace) = backtrace {
rows.push(Row { backtrace });
}
}
(
Some(CurrentTag::Row { backtrace }),
CurrentTag::Backtrace { id, frames },
) => {
let new_backtrace = Backtrace { id, frames };
let ret = self.backtraces.insert(new_backtrace.id, new_backtrace);
if ret.is_some() {
return invalid_data_error!(
"Repeated backtrace id in xctrace output: {:?}",
id
);
}
*backtrace = Some(id);
}
(
Some(CurrentTag::Backtrace { id: _, frames }),
CurrentTag::Frame { id, name },
) => {
let frame = Frame { id, name };
let ret = self.frames.insert(frame.id, frame);
if ret.is_some() {
return invalid_data_error!(
"Repeated frame id in xctrace output: {:?}",
id
);
}
frames.push(id);
}
_ => unreachable!("Bad tag stack, this is a bug of inferno."),
}
}
Event::Empty(empty) => {
let attributes = empty.attributes();
let name = empty.name().into_inner();
match (self.state_backtrace.top_mut(), name) {
(Some(CurrentTag::Row { backtrace }), BACKTRACE) => {
let new_backtrace =
if let Ok(ref_id) = get_u64_from_attributes(REF, &attributes) {
if !self.backtraces.contains_key(&BacktraceId(ref_id)) {
return invalid_data_error!(
"Invalid backtrace ref id: {}",
ref_id
);
}
BacktraceId(ref_id)
} else if let Ok(id) = attributes_to_backtrace(&attributes) {
let backtrace = Backtrace {
id,
frames: Vec::new(),
};
let ret = self.backtraces.insert(id, backtrace);
if ret.is_some() {
return invalid_data_error!(
"Repeated backtrace id in xctrace output: {:?}",
id
);
}
id
} else {
return invalid_data_error!(
"Get ref_id or attributes of backtrace failed."
);
};
*backtrace = Some(new_backtrace);
}
(Some(CurrentTag::Backtrace { id: _, frames }), FRAME) => {
let frame = if let Ok(ref_id) =
get_u64_from_attributes(REF, &attributes)
{
if !self.frames.contains_key(&FrameId(ref_id)) {
return invalid_data_error!("Invalid frame ref id: {}", ref_id);
}
FrameId(ref_id)
} else if let Ok((id, name)) = attributes_to_frame(&attributes) {
let frame = Frame { id, name };
let ret = self.frames.insert(id, frame);
if ret.is_some() {
return invalid_data_error!(
"Repeated frame id in xctrace output: {:?}",
id
);
}
id
} else {
return invalid_data_error!(
"Get ref_id or attributes of frame failed."
);
};
frames.push(frame);
}
_ => {}
}
}
Event::Text(_)
| Event::Comment(_)
| Event::CData(_)
| Event::Decl(_)
| Event::PI(_)
| Event::DocType(_)
| Event::GeneralRef(_) => {}
Event::Eof => return invalid_data_error!("Unexpected EOF"),
}
};
let backtraces = nodes
.into_iter()
.flat_map(|Node { rows }| rows)
.map(|Row { backtrace }| backtrace);
let mut backtrace_occurrences: BTreeMap<BacktraceId, BacktraceOccurrences> =
BTreeMap::new();
for backtrace in backtraces {
let frame = backtrace_occurrences
.entry(backtrace)
.or_insert_with(|| BacktraceOccurrences { num: 0, backtrace });
frame.num += 1;
}
let mut occurrences = Occurrences::new(1);
for BacktraceOccurrences { num, backtrace } in backtrace_occurrences.into_values() {
occurrences.insert_or_add(backtrace.resolve(self), num);
}
occurrences.write_and_clear(writer)
}
}