//! Non-blocking logger (Android). `log` calls on the UI thread must NEVER //! block: logd-pipe backpressure during bursts was observed to block the //! UI thread 10–40 ms (docs/BUGS/scroll-jank-120hz.md). Records are //! formatted at the call site, pushed into a bounded channel; a drain //! thread does the real `__android_log_write` and DROPS records when //! saturated. //! //! `framelog`: FR lines are also mirrored to //! /sdcard/Download/.gallery_perf.log — the logcat ring buffer rotates //! every few seconds, which destroys the gap analysis. tools/fr_capture.py //! pulls this file. #[cfg(target_os = "android")] pub mod android { use std::io::Write as _; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::mpsc; use log::{Level, Log, Metadata, Record}; extern "C" { fn __android_log_write(prio: i32, tag: *const u8, msg: *const u8) -> i32; } const TAG: &[u8] = b"gallery\0"; const PERF_PATH: &str = "/sdcard/Download/.gallery_perf.log"; fn perf_line(msg: &str) -> bool { msg.starts_with("FR ") || msg.starts_with("PERF ") || msg.starts_with("gallery: [W") } pub struct Logger { tx: mpsc::SyncSender<(i32, String)>, dropped: AtomicUsize, } pub fn init() { let (tx, rx) = mpsc::sync_channel::<(i32, String)>(4096); std::thread::Builder::new() .name("log-drain".into()) .spawn(move || { // Opened lazily (MANAGE_EXTERNAL_STORAGE may be granted // later); retried until it works. let mut perf: Option> = None; let mut n = 0usize; for (prio, msg) in rx { if cfg!(feature = "framelog") && perf_line(&msg) { if perf.is_none() { if let Ok(f) = std::fs::OpenOptions::new() .create(true) .append(true) .open(PERF_PATH) { perf = Some(std::io::BufWriter::new(f)); } } if let Some(w) = perf.as_mut() { let _ = w.write_all(msg.as_bytes()); let _ = w.write_all(b"\n"); n += 1; if n >= 32 { let _ = w.flush(); n = 0; } } } let b = msg.into_bytes(); let end = b.iter().position(|&c| c == 0).unwrap_or(b.len()); let mut safe: Vec = b[..end].iter().copied().filter(|&c| c != b'\n').collect(); safe.push(0); unsafe { __android_log_write(prio, TAG.as_ptr(), safe.as_ptr()); } } }) .ok(); let logger = Logger { tx, dropped: AtomicUsize::new(0) }; let _ = log::set_boxed_logger(Box::new(logger)).map(|()| log::set_max_level(log::LevelFilter::Info)); } impl Logger { fn prio(l: Level) -> i32 { match l { Level::Error => 6, Level::Warn => 5, Level::Info => 4, _ => 3, } } } impl Log for Logger { fn enabled(&self, m: &Metadata) -> bool { m.level() <= Level::Info } fn log(&self, record: &Record) { if !self.enabled(record.metadata()) { return; } let mut s = String::with_capacity(96); use std::fmt::Write as _; let _ = write!(&mut s, "{}", record.args()); if self.tx.try_send((Self::prio(record.level()), s)).is_err() { self.dropped.fetch_add(1, Ordering::Relaxed); } } fn flush(&self) {} } } #[cfg(not(target_os = "android"))] pub fn init() { // Desktop: the binary installs its own minimal logger. }