extern crate inferno;
use inferno::flamegraph;
use std::cell::UnsafeCell;
use std::collections::HashMap;
use std::fmt;
use std::time::Instant;
thread_local!(static EVENTS: UnsafeCell<Vec<Event>> = UnsafeCell::new(Vec::with_capacity(2048)));
fn with_events<T>(f: impl FnOnce(&mut Vec<Event>) -> T) -> T {
EVENTS.with(|e| {
let r = unsafe { &mut *e.get() };
f(r)
})
}
#[non_exhaustive]
pub enum FmtStr {
Str1(&'static str),
Str2(&'static str, &'static str),
Str3(&'static str, &'static str, &'static str),
StrNum(&'static str, u64),
}
impl From<&'static str> for FmtStr {
#[inline(always)]
fn from(value: &'static str) -> Self {
Self::Str1(value)
}
}
impl fmt::Display for FmtStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Str1(s0) => write!(f, "{}", s0),
Self::Str2(s0, s1) => write!(f, "{}{}", s0, s1),
Self::Str3(s0, s1, s2) => write!(f, "{}{}{}", s0, s1, s2),
Self::StrNum(s0, n1) => write!(f, "{}{}", s0, n1),
}
}
}
enum Event {
Start { time: Instant, tag: FmtStr },
End { time: Instant },
}
struct SpanGuard;
impl Drop for SpanGuard {
#[inline(always)]
fn drop(&mut self) {
end();
}
}
#[inline(always)]
#[must_use = "Use a let binding to extend the lifetime of the guard to the scope which to profile."]
pub fn start_guard(name: impl Into<FmtStr>) -> impl Drop {
start(name);
SpanGuard
}
pub fn start(tag: impl Into<FmtStr>) {
with_events(|events| {
let event = Event::Start {
time: Instant::now(),
tag: tag.into(),
};
events.push(event);
});
}
pub fn end() {
with_events(|e| {
e.push(Event::End {
time: Instant::now(),
})
});
}
pub fn clear() {
with_events(|e| e.clear());
}
fn lines() -> Vec<String> {
with_events(|events| {
struct Frame {
name: String,
start: Instant,
}
let mut stack = Vec::<Frame>::new();
let mut collapsed = HashMap::<_, u128>::new();
for event in events.iter() {
match event {
Event::Start { time, tag } => {
let mut name = format!("{}", tag).replace(";", "").replace(" ", "_");
if let Some(parent) = stack.last() {
name = format!("{};{}", &parent.name, name);
}
let frame = Frame {
name: name,
start: *time,
};
stack.push(frame);
}
Event::End { time } => {
let Frame { name, start } = stack.pop().unwrap();
let elapsed = (*time - start).as_nanos();
let entry = collapsed.entry(name).or_default();
*entry = entry.wrapping_add(elapsed);
if let Some(parent) = stack.last() {
let entry = collapsed.entry(parent.name.clone()).or_default();
*entry = entry.wrapping_sub(elapsed);
}
}
}
}
assert!(stack.is_empty(), "Mimatched start/end");
collapsed
.iter()
.map(|(name, time)| format!("{} {}", name, time))
.collect()
})
}
pub fn to_svg<W: std::io::Write, F: FnOnce() -> W>(f: F) -> Result<(), impl std::error::Error> {
let lines = lines();
let mut options = flamegraph::Options {
count_name: "ns".to_owned(),
..Default::default()
};
flamegraph::from_lines(&mut options, lines.iter().rev().map(|s| s.as_str()), f())
}