Fix crash when dumping debug buffers in a collab session (#61616)
The `dev: dump input latency histogram` and `dev: dump accessibility tree` actions called `Project::create_local_buffer`, which panics on remote projects — so running either one as a guest in a collab session aborted the whole app. Both now use the async `Project::create_buffer` + `set_text` instead (the same pattern `open_log_file` uses), which routes through the collab protocol on remote projects. Since the collab-created buffer is visible to other participants, the latency report now includes a "Reported from <username>'s machine" line when the project is shared, using the collab username so we don't expose anything that isn't already shared with the session. While restructuring the handler I also split snapshotting from formatting: the histogram snapshot (and the delta baseline) is captured synchronously when the action fires, and the report string is built on a background task. The accessibility tree dump stays synchronous because gpui doesn't currently expose the captured tree data outside the window borrow, and adding public API for a debug action's JSON pretty-print didn't seem worth it. Release Notes: - Fixed a crash when running the `dev: dump input latency histogram` or `dev: dump accessibility tree` actions in a shared project.
This commit is contained in:
1 parent
e2f35dd8c8
commit
5d2df6acd5
3 files changed
+117
-46
No files matched your search
@@ -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<u64>,
|
||||
|
||||
@@ -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<InputLatencySnapshot>,
|
||||
previous_timestamp: Option<chrono::DateTime<chrono::Local>>,
|
||||
timestamp: chrono::DateTime<chrono::Local>,
|
||||
/// 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<String>,
|
||||
}
|
||||
|
||||
/// 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<String>,
|
||||
cx: &mut App,
|
||||
) -> InputLatencyReportData {
|
||||
let snapshot = window.input_latency_snapshot();
|
||||
let timestamp = chrono::Local::now();
|
||||
let state = cx.default_global::<ReporterState>();
|
||||
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<u64>, 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();
|
||||
|
||||
+68
-35
@@ -918,15 +918,45 @@ fn register_actions(
|
||||
_: &input_latency_ui::DumpInputLatencyHistogram,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>| {
|
||||
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<Workspace>| {
|
||||
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(
|
||||
|
||||
Reference in new issue
Block a user