gpui: Record input-to-frame latency histogram (#53822)
Adds instrumentation to track input-to-frame latency in GPUI windows, helping diagnose input responsiveness issues. ## What this does - Records the time between when an input event is dispatched and when the resulting frame is presented, capturing worst-case latency when multiple events are coalesced into a single frame. - Tracks how many input events get coalesced per rendered frame. - Both metrics are stored in [HdrHistogram](https://docs.rs/hdrhistogram) instances with 3 significant digits of precision. - Latency is only recorded when the input event actually causes a redraw (i.e. marks the window dirty), so idle mouse moves and other no-op events don't skew the data. - Adds a `Dump Input Latency Histogram` command that opens a buffer with a formatted report including percentile breakdowns and visual distribution bars. ## Example output The report shows percentile latencies, a bucketed distribution with bar charts, and a per-frame event coalescing breakdown. Release Notes: - N/A --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Cole Miller <cole@zed.dev>
This commit is contained in:
1 parent
c3f7b25e16
commit
be57a3750d
9 files changed
+404
-5
No files matched your search
Generated
+25
@@ -7584,6 +7584,7 @@ dependencies = [
|
||||
"gpui_shared_string",
|
||||
"gpui_util",
|
||||
"gpui_web",
|
||||
"hdrhistogram",
|
||||
"http_client",
|
||||
"image",
|
||||
"inventory",
|
||||
@@ -8065,6 +8066,20 @@ dependencies = [
|
||||
"hashbrown 0.15.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hdrhistogram"
|
||||
version = "7.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"byteorder",
|
||||
"crossbeam-channel",
|
||||
"flate2",
|
||||
"nom 7.1.3",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "headers"
|
||||
version = "0.3.9"
|
||||
@@ -8815,6 +8830,15 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "input_latency_ui"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"gpui",
|
||||
"hdrhistogram",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inspector_ui"
|
||||
version = "0.1.0"
|
||||
@@ -22241,6 +22265,7 @@ dependencies = [
|
||||
"http_client",
|
||||
"image",
|
||||
"image_viewer",
|
||||
"input_latency_ui",
|
||||
"inspector_ui",
|
||||
"install_cli",
|
||||
"itertools 0.14.0",
|
||||
|
||||
@@ -103,6 +103,7 @@ members = [
|
||||
"crates/http_client_tls",
|
||||
"crates/icons",
|
||||
"crates/image_viewer",
|
||||
"crates/input_latency_ui",
|
||||
"crates/inspector_ui",
|
||||
"crates/install_cli",
|
||||
"crates/journal",
|
||||
@@ -357,6 +358,7 @@ image_viewer = { path = "crates/image_viewer" }
|
||||
edit_prediction_types = { path = "crates/edit_prediction_types" }
|
||||
edit_prediction_ui = { path = "crates/edit_prediction_ui" }
|
||||
edit_prediction_context = { path = "crates/edit_prediction_context" }
|
||||
input_latency_ui = { path = "crates/input_latency_ui" }
|
||||
inspector_ui = { path = "crates/inspector_ui" }
|
||||
install_cli = { path = "crates/install_cli" }
|
||||
journal = { path = "crates/journal" }
|
||||
@@ -584,6 +586,7 @@ globset = "0.4"
|
||||
heapless = "0.9.2"
|
||||
handlebars = "4.3"
|
||||
heck = "0.5"
|
||||
hdrhistogram = "7"
|
||||
heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
|
||||
hex = "0.4.3"
|
||||
human_bytes = "0.4.1"
|
||||
|
||||
@@ -38,6 +38,7 @@ screen-capture = [
|
||||
"scap",
|
||||
]
|
||||
windows-manifest = ["dep:embed-resource"]
|
||||
input-latency-histogram = ["dep:hdrhistogram"]
|
||||
|
||||
[lib]
|
||||
path = "src/gpui.rs"
|
||||
@@ -96,6 +97,7 @@ sum_tree.workspace = true
|
||||
taffy = "=0.9.0"
|
||||
thiserror.workspace = true
|
||||
gpui_util.workspace = true
|
||||
hdrhistogram = { workspace = true, optional = true }
|
||||
waker-fn = "1.2.0"
|
||||
lyon = "1.0"
|
||||
pin-project = "1.1.10"
|
||||
|
||||
+119
-3
@@ -28,6 +28,8 @@ use futures::FutureExt;
|
||||
use futures::channel::oneshot;
|
||||
use gpui_util::post_inc;
|
||||
use gpui_util::{ResultExt, measure};
|
||||
#[cfg(feature = "input-latency-histogram")]
|
||||
use hdrhistogram::Histogram;
|
||||
use itertools::FoldWhile::{Continue, Done};
|
||||
use itertools::Itertools;
|
||||
use parking_lot::RwLock;
|
||||
@@ -105,6 +107,7 @@ struct WindowInvalidatorInner {
|
||||
pub dirty: bool,
|
||||
pub draw_phase: DrawPhase,
|
||||
pub dirty_views: FxHashSet<EntityId>,
|
||||
pub update_count: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -119,12 +122,14 @@ impl WindowInvalidator {
|
||||
dirty: true,
|
||||
draw_phase: DrawPhase::None,
|
||||
dirty_views: FxHashSet::default(),
|
||||
update_count: 0,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn invalidate_view(&self, entity: EntityId, cx: &mut App) -> bool {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
inner.update_count += 1;
|
||||
inner.dirty_views.insert(entity);
|
||||
if inner.draw_phase == DrawPhase::None {
|
||||
inner.dirty = true;
|
||||
@@ -140,13 +145,21 @@ impl WindowInvalidator {
|
||||
}
|
||||
|
||||
pub fn set_dirty(&self, dirty: bool) {
|
||||
self.inner.borrow_mut().dirty = dirty
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
inner.dirty = dirty;
|
||||
if dirty {
|
||||
inner.update_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_phase(&self, phase: DrawPhase) {
|
||||
self.inner.borrow_mut().draw_phase = phase
|
||||
}
|
||||
|
||||
pub fn update_count(&self) -> usize {
|
||||
self.inner.borrow().update_count
|
||||
}
|
||||
|
||||
pub fn take_views(&self) -> FxHashSet<EntityId> {
|
||||
mem::take(&mut self.inner.borrow_mut().dirty_views)
|
||||
}
|
||||
@@ -958,6 +971,8 @@ pub struct Window {
|
||||
/// Tracks recent input event timestamps to determine if input is arriving at a high rate.
|
||||
/// Used to selectively enable VRR optimization only when input rate exceeds 60fps.
|
||||
pub(crate) input_rate_tracker: Rc<RefCell<InputRateTracker>>,
|
||||
#[cfg(feature = "input-latency-histogram")]
|
||||
input_latency_tracker: InputLatencyTracker,
|
||||
last_input_modality: InputModality,
|
||||
pub(crate) refreshing: bool,
|
||||
pub(crate) activation_observers: SubscriberSet<(), AnyObserver>,
|
||||
@@ -1026,6 +1041,88 @@ impl InputRateTracker {
|
||||
}
|
||||
}
|
||||
|
||||
/// A point-in-time snapshot of the input-latency histograms for a window,
|
||||
/// suitable for external formatting.
|
||||
#[cfg(feature = "input-latency-histogram")]
|
||||
pub struct InputLatencySnapshot {
|
||||
/// Histogram of input-to-frame latency samples, in nanoseconds.
|
||||
pub latency_histogram: Histogram<u64>,
|
||||
/// Histogram of input events coalesced per rendered frame.
|
||||
pub events_per_frame_histogram: Histogram<u64>,
|
||||
/// Count of input events that arrived mid-draw and were excluded from
|
||||
/// latency recording.
|
||||
pub mid_draw_events_dropped: u64,
|
||||
}
|
||||
|
||||
/// Records the time between when the first input event in a frame is dispatched
|
||||
/// and when the resulting frame is presented, capturing worst-case latency when
|
||||
/// multiple events are coalesced into a single frame.
|
||||
#[cfg(feature = "input-latency-histogram")]
|
||||
struct InputLatencyTracker {
|
||||
/// Timestamp of the first unrendered input event in the current frame;
|
||||
/// cleared when a frame is presented.
|
||||
first_input_at: Option<Instant>,
|
||||
/// Count of input events received since the last frame was presented.
|
||||
pending_input_count: u64,
|
||||
/// Histogram of input-to-frame latency samples, in nanoseconds.
|
||||
latency_histogram: Histogram<u64>,
|
||||
/// Histogram of input events coalesced per rendered frame.
|
||||
events_per_frame_histogram: Histogram<u64>,
|
||||
/// Count of input events that arrived mid-draw and were excluded from
|
||||
/// latency recording because their effects won't appear until the next frame.
|
||||
mid_draw_events_dropped: u64,
|
||||
}
|
||||
|
||||
#[cfg(feature = "input-latency-histogram")]
|
||||
impl InputLatencyTracker {
|
||||
fn new() -> Result<Self> {
|
||||
Ok(Self {
|
||||
first_input_at: None,
|
||||
pending_input_count: 0,
|
||||
latency_histogram: Histogram::new(3)
|
||||
.map_err(|e| anyhow!("Failed to create input latency histogram: {e}"))?,
|
||||
events_per_frame_histogram: Histogram::new(3)
|
||||
.map_err(|e| anyhow!("Failed to create events per frame histogram: {e}"))?,
|
||||
mid_draw_events_dropped: 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// Record that an input event was dispatched at the given time.
|
||||
/// Only the first event's timestamp per frame is retained (worst-case latency).
|
||||
fn record_input(&mut self, dispatch_time: Instant) {
|
||||
self.first_input_at.get_or_insert(dispatch_time);
|
||||
self.pending_input_count += 1;
|
||||
}
|
||||
|
||||
/// Record that an input event arrived during a draw phase and was excluded
|
||||
/// from latency tracking.
|
||||
fn record_mid_draw_input(&mut self) {
|
||||
self.mid_draw_events_dropped += 1;
|
||||
}
|
||||
|
||||
/// Record that a frame was presented, flushing pending latency and coalescing samples.
|
||||
fn record_frame_presented(&mut self) {
|
||||
if let Some(first_input_at) = self.first_input_at.take() {
|
||||
let latency_nanos = first_input_at.elapsed().as_nanos() as u64;
|
||||
self.latency_histogram.record(latency_nanos).ok();
|
||||
}
|
||||
if self.pending_input_count > 0 {
|
||||
self.events_per_frame_histogram
|
||||
.record(self.pending_input_count)
|
||||
.ok();
|
||||
self.pending_input_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn snapshot(&self) -> InputLatencySnapshot {
|
||||
InputLatencySnapshot {
|
||||
latency_histogram: self.latency_histogram.clone(),
|
||||
events_per_frame_histogram: self.events_per_frame_histogram.clone(),
|
||||
mid_draw_events_dropped: self.mid_draw_events_dropped,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) enum DrawPhase {
|
||||
None,
|
||||
@@ -1482,6 +1579,8 @@ impl Window {
|
||||
hovered,
|
||||
needs_present,
|
||||
input_rate_tracker,
|
||||
#[cfg(feature = "input-latency-histogram")]
|
||||
input_latency_tracker: InputLatencyTracker::new()?,
|
||||
last_input_modality: InputModality::Mouse,
|
||||
refreshing: false,
|
||||
activation_observers: SubscriberSet::new(),
|
||||
@@ -2367,12 +2466,20 @@ impl Window {
|
||||
}
|
||||
|
||||
#[profiling::function]
|
||||
fn present(&self) {
|
||||
fn present(&mut self) {
|
||||
self.platform_window.draw(&self.rendered_frame.scene);
|
||||
#[cfg(feature = "input-latency-histogram")]
|
||||
self.input_latency_tracker.record_frame_presented();
|
||||
self.needs_present.set(false);
|
||||
profiling::finish_frame!();
|
||||
}
|
||||
|
||||
/// Returns a snapshot of the current input-latency histograms.
|
||||
#[cfg(feature = "input-latency-histogram")]
|
||||
pub fn input_latency_snapshot(&self) -> InputLatencySnapshot {
|
||||
self.input_latency_tracker.snapshot()
|
||||
}
|
||||
|
||||
fn draw_roots(&mut self, cx: &mut App) {
|
||||
self.invalidator.set_phase(DrawPhase::Prepaint);
|
||||
self.tooltip_bounds.take();
|
||||
@@ -4119,6 +4226,9 @@ impl Window {
|
||||
/// Dispatch a mouse or keyboard event on the window.
|
||||
#[profiling::function]
|
||||
pub fn dispatch_event(&mut self, event: PlatformInput, cx: &mut App) -> DispatchEventResult {
|
||||
#[cfg(feature = "input-latency-histogram")]
|
||||
let dispatch_time = Instant::now();
|
||||
let update_count_before = self.invalidator.update_count();
|
||||
// Track input modality for focus-visible styling and hover suppression.
|
||||
// Hover is suppressed during keyboard modality so that keyboard navigation
|
||||
// doesn't show hover highlights on the item under the mouse cursor.
|
||||
@@ -4228,8 +4338,14 @@ impl Window {
|
||||
self.dispatch_key_event(any_key_event, cx);
|
||||
}
|
||||
|
||||
if self.invalidator.is_dirty() {
|
||||
if self.invalidator.update_count() > update_count_before {
|
||||
self.input_rate_tracker.borrow_mut().record_input();
|
||||
#[cfg(feature = "input-latency-histogram")]
|
||||
if self.invalidator.not_drawing() {
|
||||
self.input_latency_tracker.record_input(dispatch_time);
|
||||
} else {
|
||||
self.input_latency_tracker.record_mid_draw_input();
|
||||
}
|
||||
}
|
||||
|
||||
DispatchEventResult {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "input_latency_ui"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/input_latency_ui.rs"
|
||||
|
||||
[dependencies]
|
||||
chrono.workspace = true
|
||||
gpui = { workspace = true, features = ["input-latency-histogram"] }
|
||||
hdrhistogram.workspace = true
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../LICENSE-GPL
|
||||
@@ -0,0 +1,216 @@
|
||||
use gpui::{App, Global, InputLatencySnapshot, Window, actions};
|
||||
use hdrhistogram::Histogram;
|
||||
|
||||
actions!(
|
||||
dev,
|
||||
[
|
||||
/// Opens a buffer showing the input-to-frame latency histogram for the current window.
|
||||
DumpInputLatencyHistogram,
|
||||
]
|
||||
);
|
||||
|
||||
/// 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 {
|
||||
let snapshot = window.input_latency_snapshot();
|
||||
let state = cx.default_global::<ReporterState>();
|
||||
let report = format_report(&snapshot, state);
|
||||
|
||||
state.previous_snapshot = Some(snapshot);
|
||||
state.previous_timestamp = Some(chrono::Local::now());
|
||||
|
||||
report
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ReporterState {
|
||||
previous_snapshot: Option<InputLatencySnapshot>,
|
||||
previous_timestamp: Option<chrono::DateTime<chrono::Local>>,
|
||||
}
|
||||
|
||||
impl Global for ReporterState {}
|
||||
|
||||
fn format_report(snapshot: &InputLatencySnapshot, previous: &ReporterState) -> String {
|
||||
let histogram = &snapshot.latency_histogram;
|
||||
let total = histogram.len();
|
||||
|
||||
if total == 0 {
|
||||
return "No input latency samples recorded yet.\n\nTry typing or clicking in a buffer first.".to_string();
|
||||
}
|
||||
|
||||
let percentiles: &[(&str, f64)] = &[
|
||||
("min ", 0.0),
|
||||
("p50 ", 0.50),
|
||||
("p75 ", 0.75),
|
||||
("p90 ", 0.90),
|
||||
("p95 ", 0.95),
|
||||
("p99 ", 0.99),
|
||||
("p99.9", 0.999),
|
||||
("max ", 1.0),
|
||||
];
|
||||
|
||||
let now = chrono::Local::now();
|
||||
|
||||
let mut report = String::new();
|
||||
report.push_str("Input Latency Histogram\n");
|
||||
report.push_str("=======================\n");
|
||||
|
||||
let timestamp = now.format("%Y-%m-%d %H:%M:%S %Z");
|
||||
report.push_str(&format!("Timestamp: {timestamp}\n"));
|
||||
report.push_str(&format!("Samples: {total}\n"));
|
||||
if snapshot.mid_draw_events_dropped > 0 {
|
||||
report.push_str(&format!(
|
||||
"Mid-draw events excluded: {}\n",
|
||||
snapshot.mid_draw_events_dropped
|
||||
));
|
||||
}
|
||||
|
||||
write_latency_percentiles(&mut report, "Percentiles", histogram, percentiles);
|
||||
write_latency_distribution(&mut report, "Distribution", histogram);
|
||||
|
||||
let coalesce = &snapshot.events_per_frame_histogram;
|
||||
let coalesce_total = coalesce.len();
|
||||
if coalesce_total > 0 {
|
||||
report.push('\n');
|
||||
report.push_str("Events coalesced per frame:\n");
|
||||
for (label, quantile) in percentiles {
|
||||
let value = if *quantile == 0.0 {
|
||||
coalesce.min()
|
||||
} else if *quantile == 1.0 {
|
||||
coalesce.max()
|
||||
} else {
|
||||
coalesce.value_at_quantile(*quantile)
|
||||
};
|
||||
report.push_str(&format!(" {label}: {value:>6} events\n"));
|
||||
}
|
||||
|
||||
report.push('\n');
|
||||
report.push_str("Distribution:\n");
|
||||
let bar_width = 30usize;
|
||||
let max_count = coalesce.max();
|
||||
for n in 1..=max_count {
|
||||
let count = coalesce
|
||||
.iter_recorded()
|
||||
.filter(|value| value.value_iterated_to() == n)
|
||||
.map(|value| value.count_at_value())
|
||||
.sum::<u64>();
|
||||
if count == 0 {
|
||||
continue;
|
||||
}
|
||||
let fraction = count as f64 / coalesce_total as f64;
|
||||
let bar_len = (fraction * bar_width as f64) as usize;
|
||||
let bar = "\u{2588}".repeat(bar_len);
|
||||
report.push_str(&format!(
|
||||
" {n:>6} events: {count:>6} ({:>5.1}%) {bar}\n",
|
||||
fraction * 100.0,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Delta section: compare against the previous report's snapshot.
|
||||
if let (Some(prev_snapshot), Some(prev_timestamp)) =
|
||||
(&previous.previous_snapshot, &previous.previous_timestamp)
|
||||
{
|
||||
let prev_latency = &prev_snapshot.latency_histogram;
|
||||
let prev_total = prev_latency.len();
|
||||
let delta_total = total - prev_total;
|
||||
|
||||
report.push('\n');
|
||||
report.push_str("Delta Since Last Report\n");
|
||||
report.push_str("-----------------------\n");
|
||||
let prev_ts = prev_timestamp.format("%Y-%m-%d %H:%M:%S %Z");
|
||||
let elapsed_secs = (now - *prev_timestamp).num_seconds().max(0);
|
||||
report.push_str(&format!(
|
||||
"Previous report: {prev_ts} ({elapsed_secs}s ago)\n"
|
||||
));
|
||||
report.push_str(&format!("New samples: {delta_total}\n"));
|
||||
|
||||
if delta_total > 0 {
|
||||
let mut delta_histogram = histogram.clone();
|
||||
delta_histogram.subtract(prev_latency).ok();
|
||||
|
||||
write_latency_percentiles(
|
||||
&mut report,
|
||||
"Percentiles (new samples only)",
|
||||
&delta_histogram,
|
||||
percentiles,
|
||||
);
|
||||
write_latency_distribution(
|
||||
&mut report,
|
||||
"Distribution (new samples only)",
|
||||
&delta_histogram,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
report
|
||||
}
|
||||
|
||||
fn write_latency_percentiles(
|
||||
report: &mut String,
|
||||
heading: &str,
|
||||
histogram: &Histogram<u64>,
|
||||
percentiles: &[(&str, f64)],
|
||||
) {
|
||||
let ns_to_ms = |ns: u64| ns as f64 / 1_000_000.0;
|
||||
|
||||
report.push('\n');
|
||||
report.push_str(heading);
|
||||
report.push_str(":\n");
|
||||
for (label, quantile) in percentiles {
|
||||
let value_ns = if *quantile == 0.0 {
|
||||
histogram.min()
|
||||
} else if *quantile == 1.0 {
|
||||
histogram.max()
|
||||
} else {
|
||||
histogram.value_at_quantile(*quantile)
|
||||
};
|
||||
let hz = if value_ns > 0 {
|
||||
1_000_000_000.0 / value_ns as f64
|
||||
} else {
|
||||
f64::INFINITY
|
||||
};
|
||||
report.push_str(&format!(
|
||||
" {label}: {:>8.2}ms ({:>7.1} Hz)\n",
|
||||
ns_to_ms(value_ns),
|
||||
hz
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn write_latency_distribution(report: &mut String, heading: &str, histogram: &Histogram<u64>) {
|
||||
const BUCKETS: &[(u64, u64, &str, &str)] = &[
|
||||
(0, 4_000_000, "0\u{2013}4ms", "(excellent)"),
|
||||
(4_000_000, 8_000_000, "4\u{2013}8ms", "(120fps)"),
|
||||
(8_000_000, 16_000_000, "8\u{2013}16ms", "(60fps)"),
|
||||
(16_000_000, 33_000_000, "16\u{2013}33ms", "(30fps)"),
|
||||
(33_000_000, 100_000_000, "33\u{2013}100ms", ""),
|
||||
(100_000_000, u64::MAX, "100ms+", "(sluggish)"),
|
||||
];
|
||||
let bar_width = 30usize;
|
||||
let total = histogram.len() as f64;
|
||||
|
||||
report.push('\n');
|
||||
report.push_str(heading);
|
||||
report.push_str(":\n");
|
||||
for (low, high, range, note) in BUCKETS {
|
||||
let count: u64 = histogram
|
||||
.iter_recorded()
|
||||
.filter(|value| value.value_iterated_to() >= *low && value.value_iterated_to() < *high)
|
||||
.map(|value| value.count_at_value())
|
||||
.sum();
|
||||
let fraction = if total > 0.0 {
|
||||
count as f64 / total
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let bar_len = (fraction * bar_width as f64) as usize;
|
||||
let bar = "\u{2588}".repeat(bar_len);
|
||||
report.push_str(&format!(
|
||||
" {range:>8} {note:<11}: {count:>6} ({:>5.1}%) {bar}\n",
|
||||
fraction * 100.0,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -117,7 +117,7 @@ git_hosting_providers.workspace = true
|
||||
git_ui.workspace = true
|
||||
go_to_line.workspace = true
|
||||
system_specs.workspace = true
|
||||
gpui.workspace = true
|
||||
gpui = { workspace = true, features = ["input-latency-histogram"] }
|
||||
gpui_platform = {workspace = true, features=["screen-capture", "font-kit", "wayland", "x11"]}
|
||||
image = { workspace = true, optional = true }
|
||||
semver.workspace = true
|
||||
@@ -133,6 +133,7 @@ edit_prediction.workspace = true
|
||||
edit_prediction_ui.workspace = true
|
||||
http_client.workspace = true
|
||||
image_viewer.workspace = true
|
||||
input_latency_ui.workspace = true
|
||||
inspector_ui.workspace = true
|
||||
install_cli.workspace = true
|
||||
journal.workspace = true
|
||||
@@ -228,6 +229,7 @@ zlog_settings.workspace = true
|
||||
etw_tracing.workspace = true
|
||||
windows.workspace = true
|
||||
gpui = { workspace = true, features = [
|
||||
"input-latency-histogram",
|
||||
"windows-manifest",
|
||||
] }
|
||||
|
||||
@@ -236,6 +238,7 @@ winresource = "0.1"
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
|
||||
gpui = { workspace = true, features = [
|
||||
"input-latency-histogram",
|
||||
"wayland",
|
||||
"x11",
|
||||
] }
|
||||
@@ -252,7 +255,7 @@ pkg-config = "0.3.22"
|
||||
[dev-dependencies]
|
||||
call = { workspace = true, features = ["test-support"] }
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["input-latency-histogram", "test-support"] }
|
||||
image_viewer = { workspace = true, features = ["test-support"] }
|
||||
itertools.workspace = true
|
||||
language = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -782,6 +782,22 @@ fn register_actions(
|
||||
) {
|
||||
workspace
|
||||
.register_action(|_, _: &OpenDocs, _, cx| cx.open_url(DOCS_URL))
|
||||
.register_action(
|
||||
|workspace: &mut Workspace,
|
||||
_: &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);
|
||||
},
|
||||
)
|
||||
.register_action(|_, _: &Minimize, window, _| {
|
||||
window.minimize_window();
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user