use std::fs::File;
use std::io::BufWriter;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use tauri::{AppHandle, Manager, Runtime};
use tracing_flame::FlushGuard;
use crate::Result;
pub struct FlameState {
pub(crate) guard: Arc<Mutex<Option<FlushGuard<BufWriter<File>>>>>,
pub(crate) folded_path: Arc<Mutex<Option<PathBuf>>>,
}
impl Default for FlameState {
fn default() -> Self {
Self {
guard: Arc::new(Mutex::new(None)),
folded_path: Arc::new(Mutex::new(None)),
}
}
}
pub fn setup_flamegraph<R: Runtime>(app: &AppHandle<R>) {
app.manage(FlameState::default());
}
pub type BoxedFlameLayer =
Box<dyn tracing_subscriber::Layer<tracing_subscriber::Registry> + Send + Sync + 'static>;
pub struct FlameGuard {
guard: FlushGuard<BufWriter<File>>,
folded_path: PathBuf,
}
pub fn create_flame_layer_with_path(folded_path: &Path) -> Result<(BoxedFlameLayer, FlameGuard)> {
use tracing_subscriber::Layer;
if let Some(parent) = folded_path.parent() {
std::fs::create_dir_all(parent)?;
}
let (layer, guard) = tracing_flame::FlameLayer::with_file(folded_path)
.map_err(|e| std::io::Error::other(e.to_string()))?;
let flame_guard = FlameGuard {
guard,
folded_path: folded_path.to_path_buf(),
};
Ok((layer.boxed(), flame_guard))
}
pub trait FlameExt<R: Runtime> {
fn register_flamegraph(&self, guard: FlameGuard) -> Result<()>;
}
impl<R: Runtime> FlameExt<R> for AppHandle<R> {
fn register_flamegraph(&self, guard: FlameGuard) -> Result<()> {
let state = self.state::<FlameState>();
*state
.guard
.lock()
.map_err(|e| crate::Error::LockPoisoned(e.to_string()))? = Some(guard.guard);
*state
.folded_path
.lock()
.map_err(|e| crate::Error::LockPoisoned(e.to_string()))? = Some(guard.folded_path);
Ok(())
}
}
pub fn create_flame_layer<R: Runtime>(app_handle: &AppHandle<R>) -> Result<BoxedFlameLayer> {
use tracing_subscriber::Layer;
let log_dir = app_handle.path().app_log_dir()?;
std::fs::create_dir_all(&log_dir)?;
let folded_path = log_dir.join("profile.folded");
let (layer, guard) = tracing_flame::FlameLayer::with_file(&folded_path)
.map_err(|e| std::io::Error::other(e.to_string()))?;
let state = app_handle.state::<FlameState>();
*state
.guard
.lock()
.map_err(|e| crate::Error::LockPoisoned(e.to_string()))? = Some(guard);
*state
.folded_path
.lock()
.map_err(|e| crate::Error::LockPoisoned(e.to_string()))? = Some(folded_path);
Ok(layer.boxed())
}
pub fn generate_flamegraph_svg(folded_path: &std::path::Path) -> Result<PathBuf> {
use inferno::flamegraph::{self, Options};
use std::io::{BufRead, BufReader};
let svg_path = folded_path.with_extension("svg");
let file = File::open(folded_path)?;
let reader = BufReader::new(file);
let lines: Vec<String> = reader.lines().collect::<std::io::Result<Vec<_>>>()?;
let mut options = Options::default();
options.title = "Flamegraph".to_string();
let svg_file = File::create(&svg_path)?;
flamegraph::from_lines(&mut options, lines.iter().map(|s| s.as_str()), svg_file)
.map_err(|e| std::io::Error::other(e.to_string()))?;
Ok(svg_path)
}
pub fn generate_flamechart_svg(folded_path: &std::path::Path) -> Result<PathBuf> {
use inferno::flamegraph::{self, Options};
use std::io::{BufRead, BufReader};
let svg_path = folded_path.with_extension("flamechart.svg");
let file = File::open(folded_path)?;
let reader = BufReader::new(file);
let lines: Vec<String> = reader.lines().collect::<std::io::Result<Vec<_>>>()?;
let mut options = Options::default();
options.title = "Flamechart".to_string();
options.flame_chart = true;
let svg_file = File::create(&svg_path)?;
flamegraph::from_lines(&mut options, lines.iter().map(|s| s.as_str()), svg_file)
.map_err(|e| std::io::Error::other(e.to_string()))?;
Ok(svg_path)
}