tauri_plugin_profiling/
lib.rs

1//! CPU profiling plugin for Tauri v2.
2//!
3//! Provides sampling-based CPU profiling with flamegraph generation.
4//!
5//! # Example
6//!
7//! ```rust,ignore
8//! use tauri_plugin_profiling::ProfilingExt;
9//!
10//! tauri::Builder::default()
11//!     .plugin(tauri_plugin_profiling::init())
12//!     .setup(|app| {
13//!         app.start_cpu_profile()?;
14//!         // ... work ...
15//!         let result = app.stop_cpu_profile()?;
16//!         println!("Flamegraph: {:?}", result.flamegraph_path);
17//!         Ok(())
18//!     });
19//! ```
20//!
21//! # Platform Support
22//!
23//! - **macOS/Linux**: Uses pprof-rs with SIGPROF-based sampling
24//! - **Windows**: Uses SuspendThread + StackWalk64
25
26mod commands;
27mod config;
28mod error;
29mod profiler;
30
31pub use config::{ProfileResult, ProfilingConfig, StartOptions};
32pub use error::{Error, Result};
33
34use commands::ProfilingState;
35use tauri::{
36    Manager, Runtime,
37    plugin::{Builder, TauriPlugin},
38};
39
40/// Extension trait for CPU profiling on Tauri app handles and windows.
41pub trait ProfilingExt<R: Runtime> {
42    /// Start CPU profiling with default options.
43    fn start_cpu_profile(&self) -> Result<()>;
44
45    /// Start CPU profiling with custom options.
46    fn start_cpu_profile_with_options(&self, options: StartOptions) -> Result<()>;
47
48    /// Stop profiling and generate a flamegraph.
49    fn stop_cpu_profile(&self) -> Result<ProfileResult>;
50
51    /// Check if profiling is currently active.
52    fn is_profiling(&self) -> Result<bool>;
53}
54
55impl<R: Runtime, T: Manager<R>> ProfilingExt<R> for T {
56    fn start_cpu_profile(&self) -> Result<()> {
57        self.start_cpu_profile_with_options(StartOptions::default())
58    }
59
60    fn start_cpu_profile_with_options(&self, options: StartOptions) -> Result<()> {
61        let state = self.state::<ProfilingState>();
62        let mut session_lock = state.session.lock().map_err(|_| Error::LockPoisoned)?;
63
64        if session_lock.is_some() {
65            return Err(Error::AlreadyRunning);
66        }
67
68        let frequency = options.frequency.unwrap_or(state.config.frequency);
69
70        // Determine output directory
71        let app = self.app_handle();
72        let output_dir = state
73            .config
74            .output_dir
75            .clone()
76            .or_else(|| app.path().app_data_dir().ok().map(|p| p.join("profiles")))
77            .ok_or_else(|| Error::PathResolution("Could not determine output directory".into()))?;
78
79        std::fs::create_dir_all(&output_dir)?;
80
81        let filename_prefix = options
82            .filename
83            .unwrap_or_else(|| state.config.filename_prefix.clone());
84        let output_path = profiler::generate_output_path(&output_dir, &filename_prefix);
85
86        let session = profiler::ProfileSession::start(frequency, output_path)?;
87        *session_lock = Some(session);
88
89        Ok(())
90    }
91
92    fn stop_cpu_profile(&self) -> Result<ProfileResult> {
93        let state = self.state::<ProfilingState>();
94        let mut session_lock = state.session.lock().map_err(|_| Error::LockPoisoned)?;
95
96        let session = session_lock.take().ok_or(Error::NotRunning)?;
97        session.stop()
98    }
99
100    fn is_profiling(&self) -> Result<bool> {
101        let state = self.state::<ProfilingState>();
102        let session_lock = state.session.lock().map_err(|_| Error::LockPoisoned)?;
103        Ok(session_lock.is_some())
104    }
105}
106
107/// Initialize the plugin with default configuration (100Hz sampling).
108pub fn init<R: Runtime>() -> TauriPlugin<R> {
109    Builder::new("profiling")
110        .setup(|app, _api| {
111            app.manage(ProfilingState::new(ProfilingConfig::default()));
112            Ok(())
113        })
114        .invoke_handler(tauri::generate_handler![
115            commands::start_cpu_profile,
116            commands::stop_cpu_profile,
117            commands::is_profiling,
118            commands::is_profiling_supported,
119        ])
120        .build()
121}
122
123/// Initialize the plugin with custom configuration.
124pub fn init_with_config<R: Runtime>(config: ProfilingConfig) -> TauriPlugin<R> {
125    Builder::new("profiling")
126        .setup(move |app, _api| {
127            app.manage(ProfilingState::new(config.clone()));
128            Ok(())
129        })
130        .invoke_handler(tauri::generate_handler![
131            commands::start_cpu_profile,
132            commands::stop_cpu_profile,
133            commands::is_profiling,
134            commands::is_profiling_supported,
135        ])
136        .build()
137}