use std::cell::Ref;
use std::cell::RefCell;
use std::cell::RefMut;
use std::collections::HashMap;
use std::collections::HashSet;
use std::fmt::Write as _;
use std::mem;
use std::rc::Rc;
use crate::key::Key;
use crate::measure::MeasureVisitorImpl;
use crate::measure::Visitor;
#[derive(Default, Eq, PartialEq, Clone, Debug)]
struct TreeData {
size: usize,
rem_size: usize,
unique: bool,
children: HashMap<Key, Tree>,
}
#[derive(Default, Debug, Clone, Eq, PartialEq)]
struct Tree(Rc<RefCell<TreeData>>);
impl Tree {
fn borrow_mut(&self) -> RefMut<TreeData> {
self.0.borrow_mut()
}
fn borrow(&self) -> Ref<TreeData> {
self.0.borrow()
}
fn child(&self, name: Key) -> Tree {
self.0
.borrow_mut()
.children
.entry(name)
.or_default()
.clone()
}
fn children(&self) -> Vec<Tree> {
self.0.borrow().children.values().cloned().collect()
}
fn write_flame_graph(&self, stack: &[&str], w: &mut String) {
let borrow = self.borrow();
if borrow.rem_size > 0 {
if stack.is_empty() {
} else {
writeln!(w, "{} {}", stack.join(";"), borrow.rem_size).unwrap();
}
}
let mut children: Vec<(&Key, &Tree)> = Vec::from_iter(&borrow.children);
let mut stack = stack.to_vec();
children.sort_by_key(|(k, _)| *k);
for (key, child) in children {
stack.push(key);
child.write_flame_graph(&stack, w);
stack.pop().unwrap();
}
}
fn to_flame_graph(&self) -> String {
let mut s = String::new();
self.write_flame_graph(&[], &mut s);
s
}
}
#[derive(Default, Clone, Debug)]
struct TreeStack {
stack: Vec<Tree>,
tree: Tree,
}
impl TreeStack {
fn down(&mut self, key: Key) {
self.stack.push(self.tree.clone());
let child = self.tree.child(key);
self.tree = child;
}
#[must_use]
fn up(&mut self) -> bool {
if let Some(pop) = self.stack.pop() {
self.tree = pop;
true
} else {
false
}
}
}
#[derive(Debug)]
pub struct FlameGraphBuilder {
visited_shared: HashSet<*const ()>,
current: TreeStack,
shared: Vec<TreeStack>,
root: Tree,
entered_root_visitor: bool,
}
impl Default for FlameGraphBuilder {
fn default() -> FlameGraphBuilder {
let root = Tree::default();
FlameGraphBuilder {
visited_shared: HashSet::new(),
current: TreeStack {
stack: Vec::new(),
tree: root.clone(),
},
shared: Vec::new(),
root,
entered_root_visitor: false,
}
}
}
impl FlameGraphBuilder {
pub fn root_visitor(&mut self) -> Visitor {
assert!(!self.entered_root_visitor);
self.entered_root_visitor = true;
Visitor { visitor: self }
}
fn finish(self) -> Tree {
assert!(self.shared.is_empty());
assert!(self.current.stack.is_empty());
assert!(!self.entered_root_visitor);
Self::update_sizes(self.root.clone());
self.root
}
pub fn finish_and_write_flame_graph(self) -> String {
self.finish().to_flame_graph()
}
fn update_sizes(tree: Tree) {
for child in tree.children() {
Self::update_sizes(child);
}
let children_size = if tree.borrow().unique {
0
} else {
tree.children()
.into_iter()
.map(|child| child.borrow().size)
.sum::<usize>()
};
let mut size = tree.borrow().size;
if size < children_size {
size = children_size;
tree.borrow_mut().size = size;
}
tree.borrow_mut().rem_size = size.saturating_sub(children_size);
}
}
impl MeasureVisitorImpl for FlameGraphBuilder {
fn enter_impl(&mut self, name: Key, size: usize) {
self.current.down(name);
self.current.tree.borrow_mut().size += size;
}
fn enter_unique_impl(&mut self, name: Key, size: usize) {
self.current.down(name);
self.current.tree.borrow_mut().size += size;
self.current.tree.borrow_mut().unique = true;
}
#[must_use]
fn enter_shared_impl(&mut self, name: Key, size: usize, _ptr: *const ()) -> bool {
self.current.down(name);
self.current.tree.borrow_mut().size += size;
if !self.visited_shared.insert(_ptr) {
self.exit_impl();
return false;
}
self.shared.push(mem::take(&mut self.current));
self.current = TreeStack {
stack: Vec::new(),
tree: self.root.clone(),
};
true
}
fn exit_impl(&mut self) {
assert!(self.entered_root_visitor);
let up = self.current.up();
if !up {
if let Some(mut shared) = self.shared.pop() {
assert!(shared.up());
self.current = shared;
} else {
self.entered_root_visitor = false;
}
}
}
}
#[cfg(test)]
mod tests {
use crate::flamegraph::FlameGraphBuilder;
use crate::flamegraph::Tree;
use crate::key::Key;
#[test]
fn test_empty() {
let mut fg = FlameGraphBuilder::default();
fg.root_visitor().exit();
let tree = fg.finish();
let expected = Tree::default();
assert_eq!(expected, tree);
assert_eq!("", tree.to_flame_graph());
}
#[test]
fn test_simple() {
let mut fg = FlameGraphBuilder::default();
fg.root_visitor().visit_simple(Key::new("a"), 10);
let tree = fg.finish();
let expected = Tree::default();
expected.borrow_mut().size = 10;
expected.child(Key::new("a")).borrow_mut().size = 10;
expected.child(Key::new("a")).borrow_mut().rem_size = 10;
assert_eq!(expected, tree);
assert_eq!("a 10\n", tree.to_flame_graph());
}
#[test]
fn test_unique() {
let mut fg = FlameGraphBuilder::default();
let mut visitor = fg.root_visitor();
let mut s = visitor.enter(Key::new("Struct"), 10);
s.visit_simple(Key::new("a"), 3);
let mut un = s.enter_unique(Key::new("p"), 6);
un.visit_simple(Key::new("x"), 13);
un.exit();
s.exit();
visitor.exit();
let tree = fg.finish();
assert_eq!(
"\
Struct 1\n\
Struct;a 3\n\
Struct;p 6\n\
Struct;p;x 13\n\
",
tree.to_flame_graph(),
"{:#?}",
tree,
);
}
#[test]
fn test_shared() {
let p = 10;
let mut fg = FlameGraphBuilder::default();
let mut visitor = fg.root_visitor();
for _ in 0..2 {
let mut s = visitor.enter(Key::new("Struct"), 10);
s.visit_simple(Key::new("a"), 3);
{
let sh = s.enter_shared(Key::new("p"), 6, &p as *const i32 as *const ());
if let Some(mut sh) = sh {
sh.visit_simple(Key::new("Shared"), 13);
sh.exit();
}
}
s.exit();
}
visitor.exit();
let tree = fg.finish();
assert_eq!(
"\
Shared 13\n\
Struct 2\n\
Struct;a 6\n\
Struct;p 12\n\
",
tree.to_flame_graph(),
"{:#?}",
tree,
);
}
}