use std::{ffi::c_void, fmt::Debug, path::Path};
use serde::Serialize;
#[derive(Debug, Clone)]
pub struct FrameInfo {
pub filename: Option<std::path::PathBuf>,
pub colno: Option<u32>,
pub lineno: Option<u32>,
pub fn_address: Option<*mut c_void>,
pub fn_name: Option<String>,
}
#[derive(Debug)]
pub struct Allocation {
pub size: usize,
pub stack: Vec<FrameInfo>,
}
#[derive(Debug)]
pub struct Stats {
pub allocations: Vec<Allocation>,
}
impl Stats {
pub fn into_tree(mut self) -> Tree<Key, usize> {
let cwd = std::env::current_dir().unwrap();
let cwd = cwd.to_str().unwrap();
let allocation = self.allocations.pop().unwrap();
let mut stack = allocation.stack;
stack.reverse();
let key = loop {
let root = stack.pop();
let root = match root {
None => panic!("stack is empty"),
Some(root) => root,
};
let key = TryInto::<Key>::try_into(root);
if let Ok(key) = key {
break key;
}
};
let mut tree = Tree {
category: guess_category(cwd, key.filename.as_str()),
key,
value: 0,
children: Vec::new(),
};
let mut pointer = &mut tree;
for info in stack {
let key: Key = match info.try_into() {
Ok(key) => key,
Err(_) => continue,
};
let c = Tree {
category: guess_category(cwd, key.filename.as_str()),
key,
value: 0,
children: Vec::new(),
};
pointer.children.push(c);
pointer = pointer.children.last_mut().unwrap();
}
for mut allocation in self.allocations {
allocation.stack.reverse();
let mut pointer = &mut tree;
for info in allocation.stack {
let key: Key = match info.try_into() {
Ok(key) => key,
Err(_) => continue,
};
let found = pointer.children.iter().position(|c| c.key == key);
pointer = if let Some(found) = found {
pointer.children.get_mut(found).unwrap()
} else {
let c = Tree {
category: guess_category(cwd, key.filename.as_str()),
key,
value: allocation.size,
children: Vec::new(),
};
pointer.children.push(c);
pointer.children.last_mut().unwrap()
};
}
}
tree
}
}
#[derive(Debug, PartialEq, Eq, Serialize)]
pub struct Key {
pub filename: String,
pub colno: u32,
pub lineno: u32,
#[serde(skip_serializing)]
pub fn_address: *mut c_void,
pub fn_name: String,
}
impl TryFrom<FrameInfo> for Key {
type Error = &'static str;
fn try_from(value: FrameInfo) -> Result<Self, Self::Error> {
let filename = value.filename.ok_or("filename is None")?;
let filename = filename.to_str().ok_or("filename is not valid UTF-8")?;
let filename = filename.to_string();
let colno = value.colno.ok_or("colno is None")?;
let lineno = value.lineno.ok_or("lineno is None")?;
let fn_address = value.fn_address.ok_or("fn_address is None")?;
let fn_name = value.fn_name.ok_or("fn_name is None")?;
Ok(Key {
filename,
colno,
lineno,
fn_address,
fn_name,
})
}
}
#[derive(Debug, Serialize)]
pub struct Tree<K: Debug + Serialize, V: Debug + Serialize> {
pub key: K,
pub value: V,
pub category: Category,
pub children: Vec<Tree<K, V>>,
}
impl<K: Debug + Serialize, V: Debug + Serialize> Tree<K, V> {
pub fn print_flamegraph<P>(&self, path: P)
where
P: AsRef<Path>,
{
let d = serde_json::to_string(&self).unwrap();
let html = include_str!("../template.html");
let html = html.replace("{undefined}", &d);
std::fs::write(path, html).unwrap();
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Category {
RustStdLib,
RustC,
Deps,
Application,
Unknown,
}
fn guess_category(cwd: &str, filename: &str) -> Category {
if filename.contains("/rustc/") {
Category::RustC
} else if filename.contains("/rustlib/") {
Category::RustStdLib
} else if filename.contains("cargo/registry/src") {
Category::Deps
} else if filename.starts_with(cwd) {
Category::Application
} else {
Category::Unknown
}
}