diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 6e85b9989d..9dd236260e 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1198,6 +1198,7 @@ impl InputRateTracker { /// A point-in-time snapshot of the input-latency histograms for a window, /// suitable for external formatting. #[cfg(feature = "input-latency-histogram")] +#[derive(Clone)] pub struct InputLatencySnapshot { /// Histogram of input-to-frame latency samples, in nanoseconds. pub latency_histogram: Histogram, diff --git a/crates/input_latency_ui/src/input_latency_ui.rs b/crates/input_latency_ui/src/input_latency_ui.rs index cdeb7fe9e6..4054c6b3c0 100644 --- a/crates/input_latency_ui/src/input_latency_ui.rs +++ b/crates/input_latency_ui/src/input_latency_ui.rs @@ -11,19 +11,52 @@ actions!( ] ); -/// Generates a formatted text report of the input-to-frame latency histogram -/// for the given window. If a previous report was generated (tracked via a -/// global on the `App`), includes a delta section showing changes since that -/// report. -pub fn format_input_latency_report(window: &Window, cx: &mut App) -> String { +/// The data needed to format an input-latency report, captured synchronously +/// by [`snapshot_input_latency_report`]. Formatting via +/// [`format_input_latency_report`] is pure computation and can happen on a +/// background thread. +pub struct InputLatencyReportData { + snapshot: InputLatencySnapshot, + previous_snapshot: Option, + previous_timestamp: Option>, + timestamp: chrono::DateTime, + /// The collab username of the user whose machine produced the data. + /// Set when the report will be placed in a buffer visible to other + /// collaborators, so it's clear whose latency was measured. + reported_by: Option, +} + +/// Captures the input-to-frame latency histogram for the given window, along +/// with the previous report's baseline (tracked via a global on the `App`) for +/// delta computation. Advances the baseline to this snapshot. +pub fn snapshot_input_latency_report( + window: &Window, + reported_by: Option, + cx: &mut App, +) -> InputLatencyReportData { let snapshot = window.input_latency_snapshot(); + let timestamp = chrono::Local::now(); let state = cx.default_global::(); - let report = format_report(&snapshot, state); + + let data = InputLatencyReportData { + snapshot: snapshot.clone(), + previous_snapshot: state.previous_snapshot.take(), + previous_timestamp: state.previous_timestamp.take(), + timestamp, + reported_by, + }; state.previous_snapshot = Some(snapshot); - state.previous_timestamp = Some(chrono::Local::now()); + state.previous_timestamp = Some(timestamp); - report + data +} + +/// Generates a formatted text report from a previously captured +/// [`InputLatencyReportData`]. If the capture included a previous snapshot, +/// includes a delta section showing changes since that report. +pub fn format_input_latency_report(data: &InputLatencyReportData) -> String { + format_report(data) } #[derive(Default)] @@ -138,7 +171,8 @@ fn count_frames_in_range(histogram: &Histogram, low_ns: u64, high_ns: u64) .sum() } -fn format_report(snapshot: &InputLatencySnapshot, previous: &ReporterState) -> String { +fn format_report(data: &InputLatencyReportData) -> String { + let snapshot = &data.snapshot; let histogram = &snapshot.latency_histogram; let total = histogram.len(); @@ -157,7 +191,7 @@ fn format_report(snapshot: &InputLatencySnapshot, previous: &ReporterState) -> S ("max ", 1.0), ]; - let now = chrono::Local::now(); + let now = data.timestamp; let mut report = String::new(); report.push_str("Input Latency Histogram\n"); @@ -165,6 +199,9 @@ fn format_report(snapshot: &InputLatencySnapshot, previous: &ReporterState) -> S let timestamp = now.format("%Y-%m-%d %H:%M:%S %Z"); report.push_str(&format!("Timestamp: {timestamp}\n")); + if let Some(reported_by) = &data.reported_by { + report.push_str(&format!("Reported from {reported_by}'s machine\n")); + } report.push_str(&format!("Samples: {total}\n")); if snapshot.mid_draw_events_dropped > 0 { report.push_str(&format!( @@ -217,7 +254,7 @@ fn format_report(snapshot: &InputLatencySnapshot, previous: &ReporterState) -> S // Delta section: compare against the previous report's snapshot. if let (Some(prev_snapshot), Some(prev_timestamp)) = - (&previous.previous_snapshot, &previous.previous_timestamp) + (&data.previous_snapshot, &data.previous_timestamp) { let prev_latency = &prev_snapshot.latency_histogram; let prev_total = prev_latency.len(); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index dd10791190..76d27fb231 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -918,15 +918,45 @@ fn register_actions( _: &input_latency_ui::DumpInputLatencyHistogram, window: &mut Window, cx: &mut Context| { - let report = - input_latency_ui::format_input_latency_report(window, cx); let project = workspace.project().clone(); - let buffer = project.update(cx, |project, cx| { - project.create_local_buffer(&report, None, true, cx) - }); - let editor = - cx.new(|cx| Editor::for_buffer(buffer, Some(project), window, cx)); - workspace.add_item_to_active_pane(Box::new(editor), None, true, window, cx); + // In a collab session the report buffer is visible to other + // participants, so attribute the data to this user's machine. + let reported_by = if project.read(cx).is_shared() + || project.read(cx).is_via_collab() + { + workspace + .user_store() + .read(cx) + .current_user() + .map(|user| user.username.to_string()) + } else { + None + }; + let report_data = input_latency_ui::snapshot_input_latency_report( + window, + reported_by, + cx, + ); + cx.spawn_in(window, async move |workspace, cx| { + let report = cx + .background_spawn(async move { + input_latency_ui::format_input_latency_report(&report_data) + }) + .await; + let buffer = project + .update(cx, |project, cx| project.create_buffer(None, true, cx)) + .await?; + buffer.update(cx, |buffer, cx| { + buffer.set_text(report, cx); + }); + workspace.update_in(cx, |workspace, window, cx| { + let editor = cx + .new(|cx| Editor::for_buffer(buffer, Some(project), window, cx)); + workspace + .add_item_to_active_pane(Box::new(editor), None, true, window, cx); + }) + }) + .detach_and_log_err(cx); }, ) .register_action( @@ -936,39 +966,42 @@ fn register_actions( cx: &mut Context| { let json = accessibility_tree_dump(window); let language = workspace.app_state().languages.language_for_name("JSON"); + let project = workspace.project().clone(); cx.spawn_in(window, async move |workspace, cx| { let language = language.await.log_err(); - workspace - .update_in(cx, |workspace, window, cx| { - let project = workspace.project().clone(); - let buffer = project.update(cx, |project, cx| { - project.create_local_buffer(&json, language, true, cx) - }); - let title = "Accessibility Tree".to_string(); - let buffer = cx.new(|cx| { - MultiBuffer::singleton(buffer, cx).with_title(title.clone()) - }); - let editor = cx.new(|cx| { - let mut editor = Editor::for_multibuffer( - buffer, - Some(project), - window, - cx, - ); - editor.set_breadcrumb_header(title); - editor - }); - workspace.add_item_to_active_pane( - Box::new(editor), - None, - true, + let buffer = project + .update(cx, |project, cx| { + project.create_buffer(language, true, cx) + }) + .await?; + buffer.update(cx, |buffer, cx| { + buffer.set_text(json, cx); + }); + workspace.update_in(cx, |workspace, window, cx| { + let title = "Accessibility Tree".to_string(); + let buffer = cx.new(|cx| { + MultiBuffer::singleton(buffer, cx).with_title(title.clone()) + }); + let editor = cx.new(|cx| { + let mut editor = Editor::for_multibuffer( + buffer, + Some(project), window, cx, ); - }) - .log_err(); + editor.set_breadcrumb_header(title); + editor + }); + workspace.add_item_to_active_pane( + Box::new(editor), + None, + true, + window, + cx, + ); + }) }) - .detach(); + .detach_and_log_err(cx); }, ) .register_action(