mirror of
https://github.com/storytold/filmcraft.git
synced 2026-11-01 14:08:26 -06:00
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>
100 lines
3.8 KiB
Rust
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");
|
|
}
|
|
}
|