1#[derive(Clone, Copy, Debug, Default, PartialEq)]
7pub struct FrameSample {
8 pub input_ms: f32,
10 pub view_ms: f32,
12 pub layout_ms: f32,
14 pub render_ms: f32,
16 pub wait_ms: f32,
19}
20
21impl FrameSample {
22 pub fn total(&self) -> f32 {
23 self.input_ms + self.view_ms + self.layout_ms + self.render_ms + self.wait_ms
24 }
25
26 pub fn work(&self) -> f32 {
28 self.input_ms + self.view_ms + self.layout_ms + self.render_ms
29 }
30}
31
32pub const STATS_CAPACITY: usize = 120;
33
34#[derive(Default)]
37pub struct FrameStats {
38 samples: Vec<FrameSample>,
39 head: usize,
40 pub pending_input_ms: f32,
42 pub total: u64,
46}
47
48impl FrameStats {
49 pub fn push(&mut self, mut sample: FrameSample) {
50 sample.input_ms += self.pending_input_ms;
51 self.pending_input_ms = 0.0;
52 self.total += 1;
53 if self.samples.len() < STATS_CAPACITY {
54 self.samples.push(sample);
55 } else {
56 self.samples[self.head] = sample;
57 }
58 self.head = (self.head + 1) % STATS_CAPACITY;
59 }
60
61 pub fn len(&self) -> usize {
64 self.samples.len()
65 }
66
67 pub fn is_empty(&self) -> bool {
68 self.samples.is_empty()
69 }
70
71 pub fn iter(&self) -> impl Iterator<Item = FrameSample> + '_ {
73 let (tail, front) = if self.samples.len() < STATS_CAPACITY {
74 (&self.samples[..], &[][..])
75 } else {
76 let (a, b) = self.samples.split_at(self.head);
77 (b, a)
78 };
79 tail.iter().chain(front.iter()).copied()
80 }
81
82 pub fn last(&self) -> Option<FrameSample> {
83 if self.samples.is_empty() {
84 return None;
85 }
86 let i = (self.head + STATS_CAPACITY - 1) % STATS_CAPACITY;
87 self.samples.get(i).or(self.samples.last()).copied()
88 }
89
90 pub fn avg_total(&self) -> f32 {
91 if self.samples.is_empty() {
92 return 0.0;
93 }
94 self.samples.iter().map(FrameSample::total).sum::<f32>() / self.samples.len() as f32
95 }
96
97 pub fn max_total(&self) -> f32 {
98 self.samples
99 .iter()
100 .map(FrameSample::total)
101 .fold(0.0, f32::max)
102 }
103
104 pub fn avg_work(&self) -> f32 {
105 if self.samples.is_empty() {
106 return 0.0;
107 }
108 self.samples.iter().map(FrameSample::work).sum::<f32>() / self.samples.len() as f32
109 }
110
111 pub fn max_work(&self) -> f32 {
112 self.samples
113 .iter()
114 .map(FrameSample::work)
115 .fold(0.0, f32::max)
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 fn sample(ms: f32) -> FrameSample {
124 FrameSample {
125 view_ms: ms,
126 ..Default::default()
127 }
128 }
129
130 #[test]
131 fn ring_wraps_and_iterates_in_order() {
132 let mut s = FrameStats::default();
133 for i in 0..(STATS_CAPACITY + 10) {
134 s.push(sample(i as f32));
135 }
136 assert_eq!(s.len(), STATS_CAPACITY);
137 assert_eq!(s.total, (STATS_CAPACITY + 10) as u64);
139 let v: Vec<f32> = s.iter().map(|f| f.view_ms).collect();
140 assert_eq!(v.first().copied(), Some(10.0)); assert_eq!(v.last().copied(), Some((STATS_CAPACITY + 9) as f32));
142 assert_eq!(s.last().unwrap().view_ms, (STATS_CAPACITY + 9) as f32);
143 assert!(v.windows(2).all(|w| w[1] > w[0]));
145 }
146
147 #[test]
148 fn pending_input_folds_into_next_sample() {
149 let mut s = FrameStats {
150 pending_input_ms: 0.5,
151 ..Default::default()
152 };
153 s.push(FrameSample {
154 view_ms: 1.0,
155 ..Default::default()
156 });
157 let last = s.last().unwrap();
158 assert_eq!(last.input_ms, 0.5);
159 assert_eq!(s.pending_input_ms, 0.0);
160 assert!((last.total() - 1.5).abs() < 1e-6);
161 }
162
163 #[test]
164 fn aggregates() {
165 let mut s = FrameStats::default();
166 s.push(sample(1.0));
167 s.push(sample(3.0));
168 assert_eq!(s.avg_total(), 2.0);
169 assert_eq!(s.max_total(), 3.0);
170 }
171}