#![allow(clippy::needless_doctest_main)]
#![warn(
missing_debug_implementations,
missing_docs,
rust_2018_idioms,
unreachable_pub,
bad_style,
const_err,
dead_code,
improper_ctypes,
non_shorthand_field_patterns,
no_mangle_generic_items,
overflowing_literals,
path_statements,
patterns_in_fns_without_body,
private_in_public,
unconditional_recursion,
unused,
unused_allocation,
unused_comparisons,
unused_parens,
while_true
)]
pub use error::Error;
use error::Kind;
use petgraph::{dot::Dot, graphmap::GraphMap, Directed};
use std::{
fs::File,
io::{BufWriter, Write},
path::Path,
sync::{Arc, Mutex},
};
use tracing::{span, Subscriber};
use tracing_subscriber::{layer::Context, registry::LookupSpan, Layer};
mod error;
type CallGraph = GraphMap<&'static str, usize, Directed>;
#[derive(Clone, Debug)]
pub struct GraphLayer {
graph: Arc<Mutex<CallGraph>>,
top_node: Option<&'static str>,
}
impl GraphLayer {
#[allow(clippy::clone_double_ref)]
pub fn enable_top_node(mut self, name: &'static str) -> Self {
self = self.disable_top_node();
self.top_node = Some(name.clone());
self.graph.lock().unwrap().add_node(name);
self
}
pub fn disable_top_node(mut self) -> Self {
if let Some(name) = self.top_node.take() {
self.graph.lock().unwrap().remove_node(name);
}
self
}
}
#[must_use]
#[derive(Debug)]
pub struct FlushGuard<W>
where
W: Write + 'static,
{
graph: Arc<Mutex<CallGraph>>,
writer: W,
}
impl<W> FlushGuard<W>
where
W: Write + 'static,
{
pub fn flush(&mut self) -> Result<(), Error> {
let graph = match self.graph.lock() {
Ok(graph) => graph,
Err(e) => {
if !std::thread::panicking() {
panic!("{}", e);
} else {
return Ok(());
}
}
};
writeln!(self.writer, "{:?}", Dot::new(&*graph))
.map_err(Kind::FlushFile)
.map_err(Error)?;
self.writer.flush().map_err(Kind::FlushFile).map_err(Error)
}
}
impl<W> Drop for FlushGuard<W>
where
W: Write + 'static,
{
fn drop(&mut self) {
match self.flush() {
Ok(_) => (),
Err(e) => e.report(),
}
}
}
impl Default for GraphLayer {
fn default() -> Self {
let graph = CallGraph::new();
Self {
graph: Arc::new(Mutex::new(graph)),
top_node: None,
}
}
}
impl GraphLayer {
pub fn new() -> Self {
Default::default()
}
pub fn flush_on_drop<W>(&self, writer: W) -> FlushGuard<W>
where
W: Write + 'static,
{
FlushGuard {
graph: self.graph.clone(),
writer,
}
}
}
impl GraphLayer {
pub fn with_file(path: impl AsRef<Path>) -> Result<(Self, FlushGuard<BufWriter<File>>), Error> {
let path = path.as_ref();
let file = File::create(path)
.map_err(|source| Kind::CreateFile {
path: path.into(),
source,
})
.map_err(Error)?;
let writer = BufWriter::new(file);
let layer = Self::new();
let guard = layer.flush_on_drop(writer);
Ok((layer, guard))
}
}
impl<S> Layer<S> for GraphLayer
where
S: Subscriber + for<'span> LookupSpan<'span>,
{
fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) {
let mut locked = self.graph.lock().unwrap();
let first = ctx.span(id).expect("expected: span id exists in registry");
let node_b = first.name();
locked.add_node(node_b);
let node_a = if let Some(parent) = first.parent() {
parent.name()
} else if let Some(name) = self.top_node {
name
} else {
return;
};
if let Some(weight) = locked.edge_weight_mut(node_a, node_b) {
*weight += 1;
} else {
locked.add_edge(node_a, node_b, 1);
}
}
}