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