Profile frame stages via sidecar histogram → ely::servo::perf

This commit is contained in:
2026-05-10 22:30:57 -04:00
parent 18fd20b577
commit f7027e6ea5
9 changed files with 649 additions and 159 deletions
+2
View File
@@ -23,6 +23,8 @@ serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
sha2.workspace = true
thiserror.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
ureq.workspace = true
url.workspace = true
+13
View File
@@ -52,6 +52,7 @@ actions!(
);
fn main() {
init_tracing();
let pending_deep_links = PendingDeepLinks::default();
pending_deep_links.push(startup_deep_links(env::args().skip(1)));
let open_url_queue = pending_deep_links.clone();
@@ -320,6 +321,18 @@ fn quit(_: &Quit, cx: &mut App) {
cx.quit();
}
/// Install the global tracing subscriber. `RUST_LOG` drives the
/// filter; absent it, only `warn` and above leak through so day-to-day
/// runs stay quiet. The perf target is silent by default —
/// `RUST_LOG=ely::servo::perf=info` flips on the frame-time stream
/// without touching the rest of the app. We swallow re-init errors so
/// tests that share the process state with main don't blow up.
fn init_tracing() {
use tracing_subscriber::{EnvFilter, fmt};
let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("warn"));
let _ = fmt().with_env_filter(filter).with_target(true).try_init();
}
fn open_private_window(_: &OpenPrivateWindow, cx: &mut App) {
if open_browser_window(cx, BrowserWindowMode::Private).is_some() {
cx.activate(true);
+54
View File
@@ -95,6 +95,10 @@ impl ServoLiveClient {
return Err(ServoLiveError::SidecarFailed { message: error });
}
if let Some(perf) = response.perf.as_ref() {
log_frame_perf(perf);
}
let Some(report) = response.frame else {
return Ok(None);
};
@@ -332,6 +336,56 @@ enum LiveRequest {
struct LiveResponse {
error: Option<String>,
frame: Option<LiveFrameReport>,
#[serde(default)]
perf: Option<LiveFramePerfSummary>,
}
/// Aggregated frame-stage timings rolled up every N frames by the
/// sidecar. We accept anything matching the wire shape and let the
/// `tracing` event echo the percentiles verbatim — the sidecar is
/// the source of truth for histogram boundaries.
#[derive(Deserialize)]
struct LiveFramePerfSummary {
window: u32,
context: String,
paint_p50_us: u64,
paint_p95_us: u64,
paint_p99_us: u64,
encode_p50_us: u64,
encode_p95_us: u64,
encode_p99_us: u64,
write_p50_us: u64,
write_p95_us: u64,
write_p99_us: u64,
total_p50_us: u64,
total_p95_us: u64,
total_p99_us: u64,
}
/// Emit one structured `tracing` event per perf summary, on a
/// dedicated target so `RUST_LOG=ely::servo::perf=info` flips the
/// stream on without dragging the rest of the app along. Filtering
/// happens upstream in the subscriber — this call is a single
/// pointer + integer push.
fn log_frame_perf(summary: &LiveFramePerfSummary) {
tracing::info!(
target: "ely::servo::perf",
window = summary.window,
context = %summary.context,
paint_p50_us = summary.paint_p50_us,
paint_p95_us = summary.paint_p95_us,
paint_p99_us = summary.paint_p99_us,
encode_p50_us = summary.encode_p50_us,
encode_p95_us = summary.encode_p95_us,
encode_p99_us = summary.encode_p99_us,
write_p50_us = summary.write_p50_us,
write_p95_us = summary.write_p95_us,
write_p99_us = summary.write_p99_us,
total_p50_us = summary.total_p50_us,
total_p95_us = summary.total_p95_us,
total_p99_us = summary.total_p99_us,
"frame_perf",
);
}
#[derive(Deserialize)]