tauri-plugin-profiling 0.1.0

Tauri plugin for CPU profiling with flamegraph generation
Documentation
use crate::{config::*, error::*, profiler::*};
use std::path::PathBuf;
use std::sync::Mutex;
use tauri::{AppHandle, Manager, Runtime, State, command};

/// Plugin state holding the active profiling session
pub struct ProfilingState {
    pub config: ProfilingConfig,
    pub session: Mutex<Option<ProfileSession>>,
}

impl ProfilingState {
    pub fn new(config: ProfilingConfig) -> Self {
        Self {
            config,
            session: Mutex::new(None),
        }
    }
}

/// Start CPU profiling
#[command]
pub async fn start_cpu_profile<R: Runtime>(
    app: AppHandle<R>,
    state: State<'_, ProfilingState>,
    options: Option<StartOptions>,
) -> Result<PathBuf> {
    let mut session_lock = state.session.lock().map_err(|_| Error::LockPoisoned)?;

    if session_lock.is_some() {
        return Err(Error::AlreadyRunning);
    }

    let options = options.unwrap_or_default();
    let frequency = options.frequency.unwrap_or(state.config.frequency);

    // Determine output directory
    let output_dir = state
        .config
        .output_dir
        .clone()
        .or_else(|| app.path().app_data_dir().ok().map(|p| p.join("profiles")))
        .ok_or_else(|| Error::PathResolution("Could not determine output directory".into()))?;

    // Ensure output directory exists
    std::fs::create_dir_all(&output_dir)?;

    // Generate output path
    let filename_prefix = options
        .filename
        .unwrap_or_else(|| state.config.filename_prefix.clone());
    let output_path = generate_output_path(&output_dir, &filename_prefix);

    // Start profiling session
    let session = ProfileSession::start(frequency, output_path.clone())?;
    *session_lock = Some(session);

    log::info!("CPU profiling started at {}Hz", frequency);
    Ok(output_path)
}

/// Stop CPU profiling and generate flamegraph
#[command]
pub async fn stop_cpu_profile(state: State<'_, ProfilingState>) -> Result<ProfileResult> {
    let mut session_lock = state.session.lock().map_err(|_| Error::LockPoisoned)?;

    let session = session_lock.take().ok_or(Error::NotRunning)?;

    let result = session.stop()?;

    log::info!(
        "CPU profiling stopped. {} samples collected over {}ms. Flamegraph: {:?}",
        result.sample_count,
        result.duration_ms,
        result.flamegraph_path
    );

    Ok(result)
}

/// Check if profiling is currently active
#[command]
pub fn is_profiling(state: State<'_, ProfilingState>) -> Result<bool> {
    let session_lock = state.session.lock().map_err(|_| Error::LockPoisoned)?;

    Ok(session_lock.is_some())
}

/// Check if profiling is supported on this platform
#[command]
pub fn is_profiling_supported() -> bool {
    cfg!(any(unix, windows))
}