This PR builds out GPUI's benchmark harness so render benchmarks measure
realistic frame costs using GPUI-owned, runtime-gated instrumentation.
It adds measurements for frame draw time, dirty-to-draw latency,
invalidation coalescing, and frame-budget overruns, and runs benchmark
workloads with production-like concurrency.
## How it works
**Frame timings flow through the GPUI profiler.** `Window::draw` emits a
`FrameTiming { window_id, dirty_at, invalidations, draw_start, draw_end
}` event into a global ring buffer in `gpui::profiler`, mirroring the
existing task-timing channel. Collection is runtime-gated by
`profiler::set_frame_trace_enabled` (one relaxed atomic load when
disabled — no `Instant::now` calls in production). `BenchReport` is a
pure listener: it drains events through a cursor-based
`FrameTimingCollector` and builds histograms in the bench layer.
`Window` carries no bench-only cfg fields, and the same event channel
can later feed the miniprofiler UI or an in-app frame-time HUD.
**`BenchDispatcher`** is a multithreaded `PlatformDispatcher` for
benchmarks: background tasks run on a worker pool (same priority queue
as `LinuxDispatcher`, with task-profiler hooks), timers fire in real
time on a dedicated thread, and foreground tasks queue until the bench
thread drains them with a blocking `run_until_idle()`. Unlike
`TestDispatcher`, work executes in parallel in real time, so wall-clock
measurements reflect production concurrency. In-flight accounting is
panic-safe via drop guards.
**`gpui::bench_platform()`** returns a per-process `TestPlatform` backed
by the `BenchDispatcher`, cached in a thread-local so worker threads
persist across Criterion calibration passes. This replaces the earlier
approach of constructing a real platform per invocation, which had
process-global singleton issues, never ran foreground tasks (no run loop
pumped the main queue), and couldn't open windows on headless CI.
**Text shaping** uses `NoopTextSystem`: deterministic across
machines/font installations and CI-portable. Measured cost of this
trade: ~10% of editor draw time vs `MacTextSystem` (Noop still emits one
glyph per character at fixed advances, so downstream layout/paint
structure is preserved).
**GPU coverage (macOS only for now).** `PlatformHeadlessRenderer` gained
`render_scene`, which encodes and submits the scene to Metal against a
cached offscreen target without blocking on completion or reading pixels
back — matching production `present()` CPU cost (`render_scene_to_image`
would overstate it: it waits for the GPU and copies pixels back).
`TestWindow::draw` forwards scenes to the renderer, the real
`MetalAtlas` means glyph/SVG rasterization happens during paint, and
`bench_renderer` presents after each measured update. Platforms without
a headless renderer degrade to discarding the scene.
## Example
```rust
#[gpui::bench]
fn editor_render(cx: &mut BenchAppContext) {
init_context(cx);
let buffer = cx.update(|cx| { /* build a MultiBuffer */ });
let mut window = cx.add_empty_window();
let editor = window.update(|window, cx| {
let editor = window.replace_root(cx, |window, cx| {
let mut editor = Editor::new(EditorMode::full(), buffer, None, window, cx);
editor.set_style(editor::EditorStyle::default(), window, cx);
editor
});
window.focus(&editor.focus_handle(cx), cx);
editor
});
let mut move_down = true;
cx.bench_renderer(editor, move |editor, window, cx| {
if move_down {
editor.move_down(&MoveDown, window, cx);
} else {
editor.move_up(&MoveUp, window, cx);
}
move_down = !move_down;
});
}
```
## Example output (release, M-series)
```
editor_render time: [329.75 µs 330.17 µs 330.69 µs]
GPUI bench report (all observed iterations): editor_render
note: includes Criterion warmup/calibration
window dirty-to-draw:
samples: 31533
mean: 0.321ms
p50: 0.322ms
p90: 0.336ms
p95: 0.342ms
p99: 0.360ms
max: 0.504ms
frame budget overruns total: 0
frame budget overruns max: 0
window draw:
samples: 31533
mean: 0.295ms
p50: 0.295ms
p90: 0.307ms
p95: 0.313ms
p99: 0.330ms
max: 0.455ms
frame budget overruns total: 0
frame budget overruns max: 0
invalidations per frame: mean 5.00, max 5
```
(`invalidations per frame: mean 5.00` is real signal: each `move_down`
notifies the window five times before the draw.)
## Known limitations
- **Draw-per-flush**: the harness draws synchronously when effects flush
rather than coalescing invalidations to a vsync tick, so `dirty-to-draw`
excludes queueing delay, and `frame budget overruns` is a draw-time
budget proxy rather than actual missed presents. A frame-paced mode is
natural follow-up work.
- **GPU submission is measured on macOS only**; other platforms have no
headless renderer yet.
- The GPUI report includes Criterion warmup/calibration samples (noted
in the output); Criterion's `time` is the regression-gating number.
- `run_until_idle` waits for queued, running, and already-due work, but
not for timers that haven't reached their due time — the dispatcher runs
in real time and can't skip ahead like `TestDispatcher`'s virtual clock.
## Future work
- A vsync-like frame-pacing mode (suppress draw-on-flush; tick-driven
draw + present) so dirty-to-draw captures queueing delay
- Record present duration in `FrameTiming` so the report can split draw
vs present
- Benches that scroll through novel content (cold layout caches) and an
agent-panel render bench
- Headless renderers for Windows/Linux
- Move benches into a dedicated crate
Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the UI/UX checklist
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Release Notes:
- N/A
Reopening @yara-blue's change to try to fix CI:
> This disables the GPUI profiler by default, it is opt in using the
feature gpui/profiler. We had one report of a possible deadlock, until
that is resolved the profiler will be disabled. By doing this with a
feature we can keep the code and fix forward on nightly post release. It
will also be useful to other GPUI users who are not using the profiler
infrastructure and now no longer need to pay the overhead (0.1%).
Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Release Notes:
- N/A
---------
Co-authored-by: Yara <git@yara.blue>
We have a lot of long blocking tasks on both the foreground and
background, this is a start of getting some insight into those.
We will now log tasks running longer then 100ms on the foreground or
background. Hanging actions will also be logged including their name. We
simultaneously collect statistics on task and action performance and
send those to telemetry. This includes quantiles and averages for each
hanging task.
Finally this adds tree dev actions:
- hang action
- hang foreground
- hang background
These cause a hang to check if hang reporting is working and in the
future telemetry.
Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Closes #ISSUE
Release Notes:
- Added logging and telemetry of tasks and actions with performance
issues
Needs https://github.com/zed-industries/zed/pull/54635 for the profile
overrides added into default settings json to work.
Part of https://github.com/zed-industries/zed/issues/48968
Another part of the fix related seems to be
https://github.com/zed-industries/zed/pull/45669 ?
Using the steps from the issue and profiling on macOs had shown that Zed
has 2 memory "leaks" in play when a certain file is being rewritten a
lot of times.
* First, the thread profiler registers a lot of tasks' data and fills
its buffer to the limit:
<img width="3456" height="2158" alt="image"
src="https://github.com/user-attachments/assets/f183312d-4389-4072-8915-d54e60419b08"
/>
* Second, if the buffer gets open, the undo history fragments start to
creep up infinitely:
<img width="3456" height="2158" alt="image"
src="https://github.com/user-attachments/assets/61a2b66b-81fd-4973-9c3c-c339f886d9b2"
/>
The PR aims to solve the first issue by disabling the profiling by
default, yet leaving the way to turn in on quickly with settings.
The memory usage profiling shows that the memory usage is now
dynamically affected by the new setting:
<img width="2032" height="1136" alt="image"
src="https://github.com/user-attachments/assets/8a6c76b9-6fb7-44bc-ac1d-3c34afe7c575"
/>
While the test directory being thrashed with the script from the issue,
* first, Zed starts with the profiling disabled
* then gets the profiling enabled which results in the memory growth
close to 1 minute mark of the screenshot
* last, the profiling gets disabled again, releasing all the memory
accumulated
Release Notes:
- Improved Zed's default memory usage
Previously, the GPUI profiler allocates one CircularBuffer per thread,
and `CircularBuffer<N>` always preallocates space for N entries. As a
result it allocates ~20MB/thread, and on my machine about 33 threads are
created at startup for a total of 600MB used.
In this PR I change it to use a VecDeque that can gradually grow up to
20MB as data is written. At least in my experiments it seems that this
caps overall usage at about 21MB perhaps because only one thread writes
very much usage data.
Since this is fixed overhead for everyone running Zed it seems like a
worthwhile gain.
This also folds duplicated code across platforms into the common gpui
profiler.
Before:
<img width="4804" height="2192" alt="Image"
src="https://github.com/user-attachments/assets/7060ee5b-ef80-49cb-b7be-de33e9a2e7a5"
/>
After:
<img width="5052" height="1858" alt="image"
src="https://github.com/user-attachments/assets/513494df-0974-4604-9796-15a12ef1c134"
/>
I got here from #35780 but I don't think this is tree-size related, it
seems to be fixed overhead.
Release Notes:
- Improved: Significantly less memory used to record internal profiling
information.
---------
Co-authored-by: MrSubidubi <finn@zed.dev>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Implements a basic web platform for the wasm32-unknown-unknown target
for gpui
Release Notes:
- N/A *or* Added/Fixed/Improved ...
---------
Co-authored-by: John Tur <john-tur@outlook.com>
#2874 on steroids
Before you mark this PR as ready for review, make sure that you have:
- [ ] Added a solid test coverage and/or screenshots from doing manual
testing
- [ ] Done a self-review taking into account security and performance
aspects
- [ ] Aligned any UI changes with the [UI
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
Release Notes:
- N/A
---------
Co-authored-by: Eric Holk <eric@zed.dev>
## Motivation
This PR unifies the async execution infrastructure between GPUI and
other components that depend on the `scheduler` crate (such as our cloud
codebase). By having a scheduler that lives independently of GPUI, we
can enable deterministic testing across the entire stack - testing GPUI
applications alongside cloud services with a single, unified scheduler.
## Summary
This PR completes the integration of the `scheduler` crate into GPUI,
unifying async execution and enabling deterministic testing of GPUI
combined with other components that depend on the scheduler crate.
## Key Changes
### Scheduler Integration (Phases 1-5, previously completed)
- `TestDispatcher` now delegates to `TestScheduler` for timing, clock,
RNG, and task scheduling
- `PlatformScheduler` implements the `Scheduler` trait for production
use
- GPUI executors wrap scheduler executors, selecting `TestScheduler` or
`PlatformScheduler` based on environment
- Unified blocking logic via `Scheduler::block()`
### Dead Code Cleanup
- Deleted orphaned `crates/gpui/src/platform/platform_scheduler.rs`
(older incompatible version)
## Intentional Removals
### `spawn_labeled` and `deprioritize` removed
The `TaskLabel` system (`spawn_labeled`, `deprioritize`) was removed
during this integration. It was only used in a few places for test
ordering control.
cc @maxbrunsfeld @as-cii - The new priority-weighted scheduling in
`TestScheduler` provides similar functionality through
`Priority::High/Medium/Low`. If `deprioritize` is important for specific
test scenarios, we could add it back to the scheduler crate. Let me know
if this is blocking anything.
### `start_waiting` / `finish_waiting` debug methods removed
Replaced by `TracingWaker` in `TestScheduler` - run tests with
`PENDING_TRACES=1` to see backtraces of pending futures when parking is
forbidden.
### Realtime Priority removed
The realtime priority feature was unused in the codebase. I'd prefer to
reintroduce it when we have an actual use case, as the implementation
(bounded channel with capacity 1) could potentially block the main
thread. Having a real use case will help us validate the design.
## Testing
- All GPUI tests pass
- All scheduler tests pass
- Clippy clean
## Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ GPUI │
│ ┌──────────────────────┐ ┌────────────────────────────┐ │
│ │ gpui::Background- │ │ gpui::ForegroundExecutor │ │
│ │ Executor │ │ - wraps scheduler:: │ │
│ │ - scheduler: Arc< │ │ ForegroundExecutor │ │
│ │ dyn Scheduler> │ └────────────┬───────────────┘ │
│ └──────────┬───────────┘ │ │
│ │ │ │
│ └──────────┬──────────────────┘ │
│ ▼ │
│ ┌───────────────────────┐ │
│ │ Arc<dyn Scheduler> │ │
│ └───────────┬───────────┘ │
│ ┌──────────────┴──────────────┐ │
│ ▼ ▼ │
│ ┌──────────────────┐ ┌────────────────────┐ │
│ │ PlatformScheduler│ │ TestScheduler │ │
│ │ (production) │ │ (deterministic) │ │
│ └──────────────────┘ └────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
Release Notes:
- N/A
---------
Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Yara <git@yara.blue>
Co-authored-by: Zed Zippy <234243425+zed-zippy[bot]@users.noreply.github.com>
Improves the scheduler by allowing tasks to have a set priority which
will significantly improve responsiveness.
Release notes:
- N/A
---------
Co-authored-by: Yara <git@yara.blue>
Co-authored-by: dvdsk <noreply@davidsk.dev>
Improves the scheduler by allowing tasks to have a set priority which
will significantly improve responsiveness.
Release notes:
- N/A
---------
Co-authored-by: Yara <git@yara.blue>