use rustc_hash::FxHashMap;
use std::cmp;
use std::time::SystemTime;
use crate::{LightweightEvent, ProfilingData};
struct PerThreadState {
stack: Vec<LightweightEvent>,
stack_id: String,
start: SystemTime,
end: SystemTime,
total_event_time_nanos: u64,
}
pub fn collapse_stacks<'a>(profiling_data: &ProfilingData) -> FxHashMap<String, u64> {
let mut counters = FxHashMap::default();
let mut threads = FxHashMap::<_, PerThreadState>::default();
for current_event in profiling_data
.iter()
.rev()
.filter(|e| e.payload.is_interval())
{
let start = current_event.start().unwrap();
let end = current_event.end().unwrap();
let thread = threads
.entry(current_event.thread_id)
.or_insert(PerThreadState {
stack: Vec::new(),
stack_id: "rustc".to_owned(),
start,
end,
total_event_time_nanos: 0,
});
thread.start = cmp::min(thread.start, start);
while let Some(current_top) = thread.stack.last().cloned() {
if current_top.contains(¤t_event) {
break;
}
let popped = thread.stack.pop().unwrap();
let popped = profiling_data.to_full_event(&popped);
let new_stack_id_len = thread.stack_id.len() - (popped.label.len() + 1);
thread.stack_id.truncate(new_stack_id_len);
}
if !thread.stack.is_empty() {
counters
.entry(thread.stack_id.clone())
.and_modify(|self_time| {
*self_time -= current_event.duration().unwrap().as_nanos() as u64;
});
} else {
thread.total_event_time_nanos += current_event.duration().unwrap().as_nanos() as u64;
}
thread.stack_id.push(';');
thread
.stack_id
.push_str(&profiling_data.to_full_event(¤t_event).label[..]);
let self_time = counters.entry(thread.stack_id.clone()).or_default();
*self_time += current_event.duration().unwrap().as_nanos() as u64;
thread.stack.push(current_event)
}
let mut rustc_time = 0;
for thread in threads.values() {
rustc_time += thread.end.duration_since(thread.start).unwrap().as_nanos() as u64
- thread.total_event_time_nanos;
}
counters.insert("rustc".to_owned(), rustc_time);
counters
}
#[cfg(test)]
mod test {
use crate::ProfilingDataBuilder;
use rustc_hash::FxHashMap;
#[test]
fn basic_test() {
let mut b = ProfilingDataBuilder::new();
b.interval("Query", "e1", 0, 1, 2, |_| {});
b.interval("Query", "e2", 0, 3, 9, |b| {
b.interval("Query", "e1", 0, 4, 8, |b| {
b.interval("Query", "e3", 0, 5, 7, |_| {});
b.integer("ArtifactSize", "e4", 0, 100);
});
});
let profiling_data = b.into_profiling_data();
let recorded_stacks = super::collapse_stacks(&profiling_data);
let mut expected_stacks = FxHashMap::<String, u64>::default();
expected_stacks.insert("rustc;e2;e1;e3".into(), 2);
expected_stacks.insert("rustc;e2;e1".into(), 2);
expected_stacks.insert("rustc;e2".into(), 2);
expected_stacks.insert("rustc;e1".into(), 1);
expected_stacks.insert("rustc".into(), 1);
assert_eq!(expected_stacks, recorded_stacks);
}
#[test]
fn multi_threaded_test() {
let mut b = ProfilingDataBuilder::new();
b.interval("Query", "e1", 1, 1, 2, |_| {});
b.interval("Query", "e1", 1, 3, 4, |_| {});
b.interval("Query", "e1", 2, 1, 2, |b| {
b.instant("Instant", "e4", 2, 100);
});
b.interval("Query", "e2", 2, 2, 5, |b| {
b.interval("Query", "e3", 2, 3, 4, |_| {});
b.integer("ArtifactSize", "e4", 2, 1);
});
let profiling_data = b.into_profiling_data();
let recorded_stacks = super::collapse_stacks(&profiling_data);
let mut expected_stacks = FxHashMap::<String, u64>::default();
expected_stacks.insert("rustc;e2;e3".into(), 1);
expected_stacks.insert("rustc;e2".into(), 2);
expected_stacks.insert("rustc;e1".into(), 3);
expected_stacks.insert("rustc".into(), 1);
assert_eq!(expected_stacks, recorded_stacks);
}
}