observability 0.1.3

Experimental tracing ideas
Documentation
use tracing_core::field::Field;
use tracing_subscriber::field::Visit;

use chrono::SecondsFormat;
use std::path::PathBuf;

pub(crate) struct EventFieldFlameVisitor {
    pub samples: usize,
    name: &'static str,
}

impl EventFieldFlameVisitor {
    pub(crate) fn flame() -> Self {
        EventFieldFlameVisitor {
            samples: 0,
            name: "time.busy",
        }
    }
    pub(crate) fn ice() -> Self {
        EventFieldFlameVisitor {
            samples: 0,
            name: "time.idle",
        }
    }
}

impl Visit for EventFieldFlameVisitor {
    fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
        if field.name() == self.name {
            parse_time(&mut self.samples, value);
        }
    }
}

pub(crate) struct FlameTimed {
    path: PathBuf,
}

impl FlameTimed {
    pub(crate) fn new(path: PathBuf) -> Self {
        Self { path }
    }
}

impl Drop for FlameTimed {
    fn drop(&mut self) {
        save_flamegraph(self.path.clone());
    }
}

pub(crate) fn toml_path() -> Option<PathBuf> {
    let path = std::env::var_os("CARGO_MANIFEST_DIR").or_else(|| {
        println!("failed to get cargo manifest dir for flames");
        None
    })?;
    Some(PathBuf::from(path))
}

fn save_flamegraph(path: PathBuf) -> Option<()> {
    println!("path {:?}", path);
    let now = chrono::Local::now().to_rfc3339_opts(SecondsFormat::Secs, true);
    let inf = std::fs::File::open(path.join("flames.folded"))
        .ok()
        .or_else(|| {
            eprintln!("failed to create flames dir");
            None
        })?;
    let reader = std::io::BufReader::new(inf);

    let out = std::fs::File::create(path.join(format!("tracing_flame_{}.svg", now)))
        .ok()
        .or_else(|| {
            eprintln!("failed to create flames inferno");
            None
        })?;
    let writer = std::io::BufWriter::new(out);

    let mut opts = inferno::flamegraph::Options::default();
    inferno::flamegraph::from_reader(&mut opts, reader, writer).unwrap();
    Some(())
}

fn parse_time(samples: &mut usize, value: &dyn std::fmt::Debug) {
    let v = format!("{:?}", value);
    if v.ends_with("ns") {
        if let Ok(v) = v.trim_end_matches("ns").parse::<f64>() {
            *samples = v as usize;
        }
    } else if v.ends_with("µs") {
        if let Ok(v) = v.trim_end_matches("µs").parse::<f64>() {
            *samples = (v * 1000.0) as usize;
        }
    } else if v.ends_with("ms") {
        if let Ok(v) = v.trim_end_matches("ms").parse::<f64>() {
            *samples = (v * 1000000.0) as usize;
        }
    } else if v.ends_with('s') {
        if let Ok(v) = v.trim_end_matches('s').parse::<f64>() {
            *samples = (v * 1000000000.0) as usize;
        }
    }
}