Skip to content

Commit 67e9244

Browse files
synleclaude
andcommitted
feat: fetch_all_state parallel sidecar call + benchmark telemetry
- Add fetch_all_state Tauri command that runs get_monitors, get_dark_mode, and get_volume in parallel via tauri::async_runtime::spawn - Frontend uses single fetch_all_state call instead of 3 separate invokes - Add benchmark timing (START + duration) to all data-loading commands: get_monitors, get_dark_mode, get_volume, get_preferences, get_accessibility_trusted — only logged when debug_logging is on - Fix get_preferences benchmark deadlock (drop lock before logging END) - Call get_accessibility_trusted on popup open + visibility change Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 7f6a1fa commit 67e9244

8 files changed

Lines changed: 107 additions & 10 deletions

File tree

src-tauri/src/config.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -613,15 +613,21 @@ pub fn reset_to_defaults() {
613613
#[tauri::command]
614614
pub fn get_preferences(state: tauri::State<'_, crate::AppState>) -> Result<Preferences, String> {
615615
let t0 = std::time::Instant::now();
616+
// Log START before acquiring the lock (write_debug_log uses try_lock internally,
617+
// so it works here since we haven't locked yet).
618+
write_debug_log(&state, "benchmark: get_preferences — START");
616619
let prefs = state.preferences.lock().map_err(|e| e.to_string())?;
617620
let result = prefs.clone();
621+
let elapsed = t0.elapsed().as_secs_f64() * 1000.0;
622+
let n_monitors = result.monitor_configs.len();
623+
let n_profiles = result.profiles.len();
624+
// Drop the lock BEFORE logging the END so write_debug_log can acquire it.
625+
drop(prefs);
618626
write_debug_log(
619627
&state,
620628
&format!(
621629
"benchmark: get_preferences — {:.1}ms (in-memory, {} monitors, {} profiles)",
622-
t0.elapsed().as_secs_f64() * 1000.0,
623-
result.monitor_configs.len(),
624-
result.profiles.len(),
630+
elapsed, n_monitors, n_profiles,
625631
),
626632
);
627633
Ok(result)

src-tauri/src/dark_mode.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub async fn get_dark_mode(
1919
state: tauri::State<'_, crate::AppState>,
2020
) -> Result<bool, String> {
2121
let t0 = std::time::Instant::now();
22+
crate::config::write_debug_log(&state, "benchmark: get_dark_mode — START");
2223

2324
if let Some(cached) = state.sidecar_cache.get_dark_mode() {
2425
crate::config::write_debug_log(

src-tauri/src/display.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ pub async fn get_monitors(
203203
state: tauri::State<'_, crate::AppState>,
204204
) -> Result<Vec<Monitor>, String> {
205205
let t0 = std::time::Instant::now();
206+
crate::config::write_debug_log(&state, "benchmark: get_monitors — START");
206207

207208
// Return cached monitors if fresh
208209
if let Some(cached) = state.sidecar_cache.get_monitors() {
@@ -213,8 +214,13 @@ pub async fn get_monitors(
213214
return Ok(cached);
214215
}
215216

217+
crate::config::write_debug_log(&state, "benchmark: get_monitors — calling sidecar...");
216218
let monitors = detect_monitors().await;
217219
let t_sidecar = t0.elapsed();
220+
crate::config::write_debug_log(
221+
&state,
222+
&format!("benchmark: get_monitors — sidecar returned in {:.1}ms ({} displays)", t_sidecar.as_secs_f64() * 1000.0, monitors.len()),
223+
);
218224

219225
let mut prefs = state.preferences.lock().map_err(|e| e.to_string())?;
220226

src-tauri/src/lib.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,73 @@ pub fn server_port() -> u16 {
4444
SERVER_PORT.load(Ordering::Relaxed)
4545
}
4646

47+
/// Response from `fetch_all_state` — all sidecar data in one call.
48+
#[derive(serde::Serialize)]
49+
#[serde(rename_all = "camelCase")]
50+
pub struct AllState {
51+
pub monitors: Vec<display::Monitor>,
52+
pub is_dark: bool,
53+
pub volume: u32,
54+
}
55+
56+
/// Fetches monitors, dark mode, and volume from the sidecar in parallel.
57+
/// Returns all three in a single response so the frontend only makes one
58+
/// IPC call. Logs benchmark timing for each sub-call and the total.
59+
#[tauri::command]
60+
async fn fetch_all_state(
61+
app: tauri::AppHandle,
62+
) -> Result<AllState, String> {
63+
use tauri::Manager;
64+
let t0 = std::time::Instant::now();
65+
if let Some(s) = app.try_state::<AppState>() {
66+
config::write_debug_log(&s, "benchmark: fetch_all_state — START");
67+
}
68+
69+
// Run all 3 sidecar calls in parallel using spawned tasks.
70+
// Each task gets its own AppHandle clone (cheap Arc clone).
71+
let a1 = app.clone();
72+
let a2 = app.clone();
73+
let a3 = app.clone();
74+
75+
let h_monitors = tauri::async_runtime::spawn(async move {
76+
let state = a1.state::<AppState>();
77+
display::get_monitors(state).await
78+
});
79+
let h_dark = tauri::async_runtime::spawn(async move {
80+
let state = a2.state::<AppState>();
81+
dark_mode::get_dark_mode(state).await
82+
});
83+
let h_volume = tauri::async_runtime::spawn(async move {
84+
let state = a3.state::<AppState>();
85+
volume::get_volume(state).await
86+
});
87+
88+
let monitors_result = h_monitors.await.map_err(|e| e.to_string())?;
89+
let dark_result = h_dark.await.map_err(|e| e.to_string())?;
90+
let volume_result = h_volume.await.map_err(|e| e.to_string())?;
91+
92+
let monitors = monitors_result.unwrap_or_default();
93+
let is_dark = dark_result.unwrap_or(false);
94+
let volume = volume_result.unwrap_or(0);
95+
96+
let elapsed = t0.elapsed().as_secs_f64() * 1000.0;
97+
if let Some(s) = app.try_state::<AppState>() {
98+
config::write_debug_log(
99+
&s,
100+
&format!(
101+
"benchmark: fetch_all_state — {:.1}ms total ({} monitors, is_dark={}, volume={})",
102+
elapsed, monitors.len(), is_dark, volume,
103+
),
104+
);
105+
}
106+
107+
Ok(AllState {
108+
monitors,
109+
is_dark,
110+
volume,
111+
})
112+
}
113+
47114
pub struct AppState {
48115
pub preferences: std::sync::Mutex<config::Preferences>,
49116
pub last_tray_rect: std::sync::Mutex<Option<tauri::Rect>>,
@@ -380,6 +447,7 @@ pub fn run() {
380447
sidecar_cache: sidecar_cache::SidecarCache::new(),
381448
})
382449
.invoke_handler(tauri::generate_handler![
450+
fetch_all_state,
383451
display::get_monitors,
384452
display::set_brightness,
385453
display::set_all_brightness,

src-tauri/src/tiling/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,6 +1011,7 @@ pub fn get_accessibility_trusted(
10111011
state: tauri::State<'_, crate::AppState>,
10121012
) -> bool {
10131013
let t0 = std::time::Instant::now();
1014+
crate::config::write_debug_log(&state, "benchmark: get_accessibility_trusted — START");
10141015

10151016
if let Some(cached) = state.sidecar_cache.get_accessibility() {
10161017
crate::config::write_debug_log(

src-tauri/src/volume.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub async fn get_volume(
1919
state: tauri::State<'_, crate::AppState>,
2020
) -> Result<u32, String> {
2121
let t0 = std::time::Instant::now();
22+
crate::config::write_debug_log(&state, "benchmark: get_volume — START");
2223

2324
if let Some(cached) = state.sidecar_cache.get_volume() {
2425
crate::config::write_debug_log(

src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://raw.githubusercontent.com/nicoverbruggen/tauri-v2-docs/refs/heads/main/schemas/config.schema.json",
33
"productName": "Display DJ",
4-
"version": "6.3.17",
4+
"version": "6.3.18",
55
"identifier": "com.synle.display-dj",
66
"build": {
77
"beforeDevCommand": "npm run dev",

src/App.tsx

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,27 @@ function App() {
8383
}
8484
}, []);
8585

86+
/** Fetches monitors, dark mode, and volume in a single parallel sidecar call. */
87+
const fetchAllState = useCallback(async () => {
88+
try {
89+
const state = await invoke<{
90+
monitors: Monitor[];
91+
isDark: boolean;
92+
volume: number;
93+
}>('fetch_all_state');
94+
setMonitors(state.monitors);
95+
setDarkMode(state.isDark);
96+
setVolume(state.volume);
97+
} catch (e) {
98+
console.error('Failed to fetch all state:', e);
99+
}
100+
}, []);
101+
86102
useEffect(() => {
87-
fetchMonitors();
88-
fetchDarkMode();
89-
fetchVolume();
103+
fetchAllState();
90104
fetchPreferences();
91105
fetchKeepAwake();
106+
invoke<boolean>('get_accessibility_trusted').catch(() => {});
92107
invoke<string>('get_app_version')
93108
.then(setVersion)
94109
.catch(() => {});
@@ -105,10 +120,9 @@ function App() {
105120
// Also refetch when window becomes visible
106121
const handleVisibility = () => {
107122
if (document.visibilityState === 'visible') {
108-
fetchMonitors();
109-
fetchDarkMode();
110-
fetchVolume();
123+
fetchAllState();
111124
fetchKeepAwake();
125+
invoke<boolean>('get_accessibility_trusted').catch(() => {});
112126
}
113127
};
114128
document.addEventListener('visibilitychange', handleVisibility);

0 commit comments

Comments
 (0)