Files
Ely-GPUI-Components/examples/gallery/pages/debug/profiling.rs
T
ZacharyZhang-NY 74d6bd1771 T20: Debug
- DebugToolbar, BreakpointList, CallStack with folded library frames, ThreadList
- VariablesPanel and WatchPanel, colored by kind, changes washed
- HexViewer at a base address with the byte under the pointer lit on both sides
- Disassembly with source lines, registers and numbers colored, gutter breakpoints
- Flamegraph that zooms by frame, TimelineProfiler that zooms and pans by wheel
- NetworkInspector with a waterfall, a filter and a request's headers and body
- icons: redo-dot, arrow-down-to-dot, arrow-up-from-dot
- gallery: the Debug page
2026-09-26 02:38:50 -04:00

247 lines
7.4 KiB
Rust

use std::rc::Rc;
use ely_gpui_component::{
debug::{Flamegraph, NetworkInspector, ProfileFrame, Request, Span, TimelineProfiler},
forms::{Input, TextInput},
};
use gpui::{App, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
use crate::ui::{keep, section, set};
fn frame(name: &str, own: u64, calls: Vec<ProfileFrame>) -> ProfileFrame {
ProfileFrame {
name: name.to_string().into(),
own,
calls,
}
}
/// A frame's worth of an app's time, sampled.
fn profile() -> ProfileFrame {
frame(
"main",
4,
vec![
frame(
"run_loop",
6,
vec![
frame(
"draw",
10,
vec![
frame(
"layout",
38,
vec![
frame("measure_text", 22, vec![]),
frame("taffy::compute", 30, vec![]),
],
),
frame(
"paint",
26,
vec![
frame("shape_line", 18, vec![]),
frame("rasterize", 12, vec![]),
],
),
],
),
frame("dispatch_event", 14, vec![frame("on_click", 9, vec![])]),
],
),
frame("io_thread", 20, vec![frame("read", 16, vec![])]),
],
)
}
fn spans() -> Rc<Vec<Span>> {
let span = |track, name: &str, start, end| Span {
track,
name: name.to_string().into(),
start,
end,
};
Rc::new(vec![
span(0, "event", 0.0, 1.2),
span(0, "layout", 1.2, 5.6),
span(0, "paint", 5.6, 9.1),
span(0, "event", 16.6, 17.3),
span(0, "layout", 17.3, 20.4),
span(0, "paint", 20.4, 23.0),
span(1, "shape", 2.0, 4.8),
span(1, "shape", 18.0, 19.6),
span(2, "gc", 9.5, 12.0),
span(3, "read", 0.5, 14.0),
span(3, "parse", 14.0, 15.2),
])
}
pub fn profiles(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
let focus = keep("debug-focus", Vec::<usize>::new, window, cx);
let hovered = keep("debug-hovered", || None::<Vec<usize>>, window, cx);
let range = keep("debug-range", || (0.0_f64, 25.0_f64), window, cx);
let picked = keep("debug-span", || Some(1usize), window, cx);
let (now_focus, now_hovered, now_range, now_picked) = (
focus.read(cx).clone(),
hovered.read(cx).clone(),
*range.read(cx),
*picked.read(cx),
);
let (zoom, hover, pan, pick) = (
focus.clone(),
hovered.clone(),
range.clone(),
picked.clone(),
);
let timeline = TimelineProfiler::new(
"debug-timeline",
["Main", "Text", "Memory", "IO"],
spans(),
now_range,
)
.on_range(move |next, _, cx| set(&pan, next, cx))
.on_select(move |ix, _, cx| set(&pick, Some(ix), cx));
let timeline = match now_picked {
Some(ix) => timeline.selected(ix),
None => timeline,
};
section(
"PerformanceProfiler / Flamegraph / TimelineProfiler",
"Where the time went: each function as wide as its samples with its callees under it; press a frame to zoom in and a caller to zoom back out. Work over time on tracks; the wheel zooms around the pointer and a sideways wheel pans.",
cx,
)
.child(
div().w(px(840.)).child(
Flamegraph::new("debug-flame", profile())
.focus(now_focus)
.hovered(now_hovered)
.on_focus(move |path, _, cx| set(&zoom, path, cx))
.on_hover(move |path, _, cx| {
if *hover.read(cx) != path {
set(&hover, path, cx)
}
}),
),
)
.child(div().w(px(840.)).child(timeline))
}
fn requests() -> Vec<Request> {
let request = |method: &'static str,
url: &'static str,
status,
kind: &'static str,
size,
start,
duration,
body: Option<&'static str>| Request {
method: method.into(),
url: url.into(),
status,
kind: kind.into(),
size,
start,
duration,
headers: vec![
(
"content-type".into(),
if kind == "fetch" {
"application/json".into()
} else {
"text/html".into()
},
),
("cache-control".into(), "no-store".into()),
],
body: body.map(Into::into),
};
vec![
request(
"GET",
"https://ely.dev/",
Some(200),
"document",
18_400,
0.0,
120.0,
None,
),
request(
"GET",
"https://ely.dev/static/app.js",
Some(200),
"script",
212_000,
130.0,
240.0,
None,
),
request(
"GET",
"https://ely.dev/static/inter.woff2",
Some(304),
"font",
0,
140.0,
60.0,
None,
),
request(
"GET",
"https://api.ely.dev/v1/quotes?symbol=ELY",
Some(200),
"fetch",
2_300,
380.0,
95.0,
Some("{ \"symbol\": \"ELY\", \"price\": 128.4, \"change\": 0.012 }"),
),
request(
"POST",
"https://api.ely.dev/v1/orders",
Some(503),
"fetch",
180,
420.0,
310.0,
Some("{ \"error\": \"orders are paused\" }"),
),
request(
"GET",
"https://api.ely.dev/v1/stream",
None,
"fetch",
0,
500.0,
400.0,
None,
),
]
}
pub fn network(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
let picked = keep("debug-request", || Some(4usize), window, cx);
let query = window.use_keyed_state("debug-network-query", cx, |window, cx| {
TextInput::new(window, cx).placeholder("Filter addresses")
});
let now = *picked.read(cx);
let text: SharedString = query.read(cx).text().to_string().into();
let pick = picked.clone();
let inspector = NetworkInspector::new("debug-network", requests())
.query(text)
.on_select(move |ix, _, cx| set(&pick, Some(ix), cx));
let inspector = match now {
Some(ix) => inspector.selected(ix),
None => inspector,
};
section(
"NetworkInspector",
"Requests in the order they started, with status, kind, size and time, and a waterfall of when each ran; a filter keeps matching addresses. Press one to read its headers and body.",
cx,
)
.child(div().w(px(320.)).child(Input::new(&query)))
.child(div().w(px(840.)).child(inspector))
}