animato_devtools/
perf_monitor.rs1use animato_driver::{AnimationId, DriverFrameProfile};
4use std::collections::VecDeque;
5
6#[derive(Clone, Debug, PartialEq)]
8pub struct AnimationCostRecord {
9 pub id: AnimationId,
11 pub label: Option<String>,
13 pub update_time_ms: f32,
15}
16
17#[derive(Clone, Debug, PartialEq)]
19pub struct PerformanceMonitor {
20 frame_times: VecDeque<f32>,
21 window_size: usize,
22 animation_costs: Vec<AnimationCostRecord>,
23 active_animation_count: usize,
24}
25
26impl PerformanceMonitor {
27 pub fn new(window_size: usize) -> Self {
29 Self {
30 frame_times: VecDeque::with_capacity(window_size.max(1)),
31 window_size: window_size.max(1),
32 animation_costs: Vec::new(),
33 active_animation_count: 0,
34 }
35 }
36
37 pub fn record_frame(&mut self, dt: f32) {
39 if self.frame_times.len() == self.window_size {
40 self.frame_times.pop_front();
41 }
42 self.frame_times.push_back(dt.max(0.0));
43 }
44
45 pub fn record_profile(&mut self, profile: &DriverFrameProfile) {
47 self.record_frame(profile.dt);
48 self.active_animation_count = profile.animation_costs.len();
49 self.animation_costs.clear();
50 self.animation_costs
51 .extend(
52 profile
53 .animation_costs
54 .iter()
55 .map(|cost| AnimationCostRecord {
56 id: cost.id,
57 label: cost.label.clone(),
58 update_time_ms: cost.update_time_ms,
59 }),
60 );
61 }
62
63 pub fn fps(&self) -> f32 {
65 let avg = self.avg_frame_time_ms();
66 if avg <= f32::EPSILON {
67 0.0
68 } else {
69 1000.0 / avg
70 }
71 }
72
73 pub fn avg_frame_time_ms(&self) -> f32 {
75 if self.frame_times.is_empty() {
76 return 0.0;
77 }
78 self.frame_times.iter().sum::<f32>() * 1000.0 / self.frame_times.len() as f32
79 }
80
81 pub fn max_frame_time_ms(&self) -> f32 {
83 self.frame_times.iter().copied().fold(0.0_f32, f32::max) * 1000.0
84 }
85
86 pub fn frame_budget_usage(&self, target_fps: f32) -> f32 {
88 if target_fps <= 0.0 {
89 return 0.0;
90 }
91 self.avg_frame_time_ms() / (1000.0 / target_fps)
92 }
93
94 pub fn exceeds_budget(&self, target_fps: f32) -> bool {
96 self.frame_budget_usage(target_fps) > 1.0
97 }
98
99 pub fn animation_costs(&self) -> &[AnimationCostRecord] {
101 &self.animation_costs
102 }
103
104 pub fn active_animation_count(&self) -> usize {
106 self.active_animation_count
107 }
108}
109
110impl Default for PerformanceMonitor {
111 fn default() -> Self {
112 Self::new(120)
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn calculates_fps_and_budget() {
122 let mut monitor = PerformanceMonitor::new(2);
123 monitor.record_frame(1.0 / 60.0);
124 monitor.record_frame(1.0 / 30.0);
125 assert!(monitor.fps() > 0.0);
126 assert!(monitor.avg_frame_time_ms() > 0.0);
127 assert!(monitor.max_frame_time_ms() >= monitor.avg_frame_time_ms());
128 assert!(monitor.frame_budget_usage(60.0) > 0.0);
129 }
130}