use std::collections::HashMap;
use std::io;
use std::iter;
use log::warn;
#[derive(Debug, PartialEq, Eq, Hash)]
pub(super) struct Frame<'a> {
pub(super) function: &'a str,
pub(super) depth: usize,
}
#[derive(Debug, PartialEq)]
pub(super) struct TimedFrame<'a> {
pub(super) location: Frame<'a>,
pub(super) start_time: usize,
pub(super) end_time: usize,
pub(super) delta: Option<isize>,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub(super) struct FrameTime {
pub(super) start_time: usize,
pub(super) delta: Option<isize>,
}
fn flow<'a, LI, TI>(
tmp: &mut HashMap<Frame<'a>, FrameTime>,
frames: &mut Vec<TimedFrame<'a>>,
last: LI,
this: TI,
time: usize,
delta: Option<isize>,
) where
LI: IntoIterator<Item = &'a str>,
TI: IntoIterator<Item = &'a str>,
{
let mut this = this.into_iter().peekable();
let mut last = last.into_iter().peekable();
let mut shared_depth = 0;
while last.peek() == this.peek() {
if last.peek().is_none() {
break;
}
last.next();
this.next();
shared_depth += 1;
}
for (i, func) in last.enumerate() {
let key = Frame {
function: func,
depth: shared_depth + i,
};
let frame_time = tmp.remove(&key).unwrap_or_else(|| {
unreachable!("did not have start time for {:?}", key);
});
let frame = TimedFrame {
location: key,
start_time: frame_time.start_time,
end_time: time,
delta: frame_time.delta,
};
frames.push(frame);
}
let mut i = 0;
while this.peek().is_some() {
let func = this.next().unwrap();
let key = Frame {
function: func,
depth: shared_depth + i,
};
let is_last = this.peek().is_none();
let delta = match delta {
Some(_) if !is_last => Some(0),
d => d,
};
let frame_time = FrameTime {
start_time: time,
delta,
};
if let Some(frame_time) = tmp.insert(key, frame_time) {
unreachable!(
"start time {} already registered for frame",
frame_time.start_time
);
}
i += 1;
}
}
pub(super) fn frames<'a, I>(
lines: I,
suppress_sort_check: bool,
) -> quick_xml::Result<(Vec<TimedFrame<'a>>, usize, usize, usize)>
where
I: IntoIterator<Item = &'a str>,
{
let mut time = 0;
let mut ignored = 0;
let mut last = "";
let mut tmp = Default::default();
let mut frames = Default::default();
let mut delta = None;
let mut delta_max = 1;
let mut stripped_fractional_samples = false;
let mut prev_line = None;
for line in lines {
let mut line = line.trim();
if !suppress_sort_check {
if let Some(prev_line) = prev_line {
if prev_line > line {
return Err(quick_xml::Error::Io(io::Error::new(
io::ErrorKind::InvalidData,
"unsorted input lines detected",
)));
}
}
}
let nsamples =
if let Some(samples) = parse_nsamples(&mut line, &mut stripped_fractional_samples) {
if let Some(original_samples) =
parse_nsamples(&mut line, &mut stripped_fractional_samples)
{
delta = Some(samples as isize - original_samples as isize);
delta_max = std::cmp::max(delta.unwrap().unsigned_abs(), delta_max);
}
samples
} else {
ignored += 1;
continue;
};
if line.is_empty() {
ignored += 1;
continue;
}
let stack = line;
let this = iter::once("").chain(stack.split(';'));
if last.is_empty() {
flow(&mut tmp, &mut frames, None, this, time, delta);
} else {
flow(
&mut tmp,
&mut frames,
iter::once("").chain(last.split(';')),
this,
time,
delta,
);
}
last = stack;
time += nsamples;
prev_line = Some(line);
}
if !last.is_empty() {
flow(
&mut tmp,
&mut frames,
iter::once("").chain(last.split(';')),
None,
time,
delta,
);
}
Ok((frames, time, ignored, delta_max))
}
fn parse_nsamples(line: &mut &str, stripped_fractional_samples: &mut bool) -> Option<usize> {
if let Some((samplesi, doti)) = rfind_samples(line) {
let mut samples = &line[samplesi..];
if !*stripped_fractional_samples
&& doti < samples.len() - 1
&& !samples[doti + 1..].chars().all(|c| c == '0')
{
*stripped_fractional_samples = true;
warn!(
"The input data has fractional sample counts that will be truncated to integers. \
If you need to retain the extra precision you can scale up the sample data and \
use the --factor option to scale it back down."
);
}
samples = &samples[..doti];
let nsamples = samples.parse::<usize>().ok()?;
*line = line[..samplesi].trim_end();
Some(nsamples)
} else {
None
}
}
pub(super) fn rfind_samples(line: &str) -> Option<(usize, usize)> {
let samplesi = line.rfind(' ')? + 1;
let samples = &line[samplesi..];
if let Some(doti) = samples.find('.') {
if samples[..doti]
.chars()
.chain(samples[doti + 1..].chars())
.all(|c| c.is_ascii_digit())
{
Some((samplesi, doti))
} else {
None
}
} else if !samples.chars().all(|c| c.is_ascii_digit()) {
None
} else {
Some((samplesi, line.len() - samplesi))
}
}