Files
filmcraft/crates/engine/src/perf.rs
T
justjohn12345andClaude Opus 5.5 4f3e119ac2 MEM4: decode into the planes of evicted frames
With MEM1-MEM3 twelve clips on a timeline still took 5.1 GB, only 2.5 GB of
it live: each decoded picture allocated three planes, each eviction freed
them, and the system allocator kept the freed pages.

filmcraft_frame::pool keeps the planes of frames the GOP caches evict (up to
192 MiB per sample type; a frame something else still holds is left alone).
The H.264 decoder, through a plane allocator hook that keeps the crate free
of dependencies, and the VideoToolbox path take their output planes from it.

perf.stats reports decode.planePoolMB and decode.planesReused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 16:57:16 -04:00

100 lines
3.8 KiB
Rust

//! `perf.stats`: performance counters an agent can read (headless engine part). The desktop UI
//! extends the same query with playback, frame-worker and UI timings (`ui-egui` `perf.rs`).
use serde_json::{Value, json};
use crate::Session;
/// Decoder / GOP-cache counters since the process started (they only grow: diff two readings to
/// measure an interval).
pub fn decode_json() -> Value {
let g = filmcraft_codecs::gop_stats();
let hw = filmcraft_codecs::hw::hw_stats();
json!({
"requests": g.hits + g.misses,
"cacheHits": g.hits,
"cacheMisses": g.misses,
"cacheHitRate": g.hit_rate(),
"seeks": g.seeks,
"samplesDecoded": g.decoded,
"samplesSkipped": g.skipped,
"draftFrames": g.draft,
"h264Threads": filmcraft_codecs::video::h264_threads(),
"evicted": g.evicted,
// sources that hold a decoder right now (idle ones give theirs up beyond a cap)
"liveDecoders": filmcraft_codecs::live_decoders(),
// decoded frames all sources hold, and the budget idle sources are trimmed to
"cacheMB": filmcraft_codecs::cached_bytes() as f64 / 1e6,
"cacheBudgetMB": filmcraft_codecs::FRAME_BUDGET as f64 / 1e6,
// plane buffers of evicted frames waiting for the next decoded pictures, and how many
// planes were decoded into a recycled buffer
"planePoolMB": filmcraft_frame::pool::stats().idle_bytes as f64 / 1e6,
"planesReused": filmcraft_frame::pool::stats().reused,
"decodeMs": g.decode_ns as f64 / 1e6,
"decodeMsPerSample": g.decode_ms_per_sample(),
"framesDecoded": g.frames,
// Settings ▸ Playback ▸ Hardware decoding: pictures from OS hardware decoders vs ours,
// hardware decoders created, streams handed to software up front, mid-stream fallbacks.
"hardware": {
"enabled": filmcraft_codecs::hw::hardware_decoding(),
"frames": hw.frames,
"softwareFrames": g.frames.saturating_sub(hw.frames),
"sessions": hw.sessions,
"declined": hw.declined,
"fallbacks": hw.fallbacks,
},
})
}
/// The engine's `perf.stats`.
pub fn stats(s: &Session) -> Value {
let running = s.jobs.iter().filter(|j| j.result.lock().map(|r| r.is_none()).unwrap_or(false)).count();
json!({
"decode": decode_json(),
"media": {"openSources": s.media.open_sources()},
"jobs": {"total": s.jobs.len(), "running": running},
})
}
#[cfg(test)]
mod tests {
use serde_json::json;
use crate::Session;
#[test]
fn perf_stats_query_reports_decode_counters() {
let mut s = Session::default();
assert!(s.execute("perf.stats", json!({})).is_ok(), "available without a project");
s.execute("file.openDemoProject", json!({})).unwrap();
let undo = s.history.undo.len();
let v = s.execute("perf.stats", json!({})).unwrap();
for k in [
"requests",
"cacheHitRate",
"seeks",
"samplesDecoded",
"samplesSkipped",
"draftFrames",
"h264Threads",
"decodeMs",
"decodeMsPerSample",
"framesDecoded",
"liveDecoders",
"cacheMB",
"cacheBudgetMB",
"planePoolMB",
"planesReused",
] {
assert!(v["decode"][k].is_number(), "decode.{k} in {v}");
}
for k in ["frames", "softwareFrames", "sessions", "declined", "fallbacks"] {
assert!(v["decode"]["hardware"][k].is_number(), "decode.hardware.{k} in {v}");
}
assert!(v["decode"]["hardware"]["enabled"].is_boolean());
assert!(v["media"]["openSources"].is_number());
assert_eq!(v["jobs"]["running"], json!(0));
assert_eq!(s.history.undo.len(), undo, "a query adds no undo step");
}
}