gallery/vendor/i-slint-backend-android-activity-1.18.1/stg.rs
Greg Pomerantz d5bbb41bbd Jank-hunt perf feature: FR/STG/DECODE instrumentation, off by default
Residual scroll jank at 120 Hz (suspected Slint event-loop bug) needs
per-frame + per-stage timing the logcat ring buffer can't hold. New
cargo feature `perf` (default OFF — all sites are cfg-gated, zero
overhead when off):

- FR: per-frame app-side render cost + frame gap, from the rendering
  notifier (BeforeRendering/AfterRendering).
- STG: event-loop stage costs (wake/timers/events/poll/lastrender) from
  timestamps instrumented into the pinned vendor copy of
  i-slint-backend-android-activity (stg.rs; Cargo.toml now depends on
  it directly + optional under `perf`).
- DECODE: per-thumbnail-job cost on the workers.
- All lines mirrored to /sdcard/Download/.gallery_perf.log (logcat
  rotates too fast at ~120 lines/s).

Format + reading guide: docs/BUGS/scroll-jank-120hz.md.
2026-10-29 08:11:51 -04:00

49 lines
2.2 KiB
Rust

//! STG (stage-timestamp) instrumentation for the jank hunt, 2026-10-28.
//!
//! The app's FR hook (rendering notifier) measures render cost and frame
//! gaps; this measures what happens AROUND rendering inside the event
//! loop: Choreographer wake latency, timer callbacks (the app's 8 ms
//! drain tick runs in `update_timers_and_animations`), and event
//! processing. Timestamps are plain atomics — the UI thread only does
//! atomic loads/stores here; the APP formats the log line from its own
//! tick (never log from the UI thread: logd backpressure).
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Instant;
static T0: std::sync::OnceLock<Instant> = std::sync::OnceLock::new();
pub fn now_us() -> u64 {
T0.get_or_init(Instant::now).elapsed().as_micros() as u64
}
// Per-iteration stage marks (us since process start); reset at loop top.
pub static S_START: AtomicU64 = AtomicU64::new(0);
pub static S_FIRST_EV: AtomicU64 = AtomicU64::new(0);
pub static S_POLL_END: AtomicU64 = AtomicU64::new(0);
pub static S_RENDER_START: AtomicU64 = AtomicU64::new(0);
pub static S_RENDER_END: AtomicU64 = AtomicU64::new(0);
// Accumulated per-iteration costs.
pub static S_TIMER_US: AtomicU64 = AtomicU64::new(0);
pub static S_EVENT_US: AtomicU64 = AtomicU64::new(0);
pub static S_N_EVENTS: AtomicU64 = AtomicU64::new(0);
// Full do_render duration of the PREVIOUS iteration (the AfterRendering
// hook fires mid-render, so the current iteration's render can't be read
// yet): covers renderer.draw PLUS the wgpu surface tail (flush_and_submit
// gr_context.submit + queue.present — the Mali driver calls).
pub static S_LAST_RENDER_US: AtomicU64 = AtomicU64::new(0);
/// One iteration of the event loop: start, first-event, poll-end,
/// render-start, render-end, timer-us, event-us, n-events.
pub fn snapshot() -> (u64, u64, u64, u64, u64, u64, u64, u64) {
(
S_START.load(Ordering::Relaxed),
S_FIRST_EV.load(Ordering::Relaxed),
S_POLL_END.load(Ordering::Relaxed),
S_RENDER_START.load(Ordering::Relaxed),
S_RENDER_END.load(Ordering::Relaxed),
S_TIMER_US.load(Ordering::Relaxed),
S_EVENT_US.load(Ordering::Relaxed),
S_N_EVENTS.load(Ordering::Relaxed),
)
}