1use alloc::{format, string::String};
4use core::fmt::{self, Display, Formatter};
5use core::ops::AddAssign;
6use core::time::Duration;
7#[cfg(feature = "std")]
8use std::time::Instant;
9
10#[derive(Clone, Debug, Default)]
16pub struct Stats {
17 pub time: Duration,
19 pub calls: f32,
21 pub frames: f32,
23
24 pub objs: Throughput,
26 pub prims: Throughput,
27 pub verts: Throughput,
28 pub frags: Throughput,
29
30 #[cfg(feature = "std")]
31 start: Option<Instant>,
32}
33
34#[derive(Copy, Clone, Debug, Default)]
35pub struct Throughput {
36 pub i: usize,
38 pub o: usize,
40}
41
42impl Stats {
47 pub fn new() -> Self {
49 Self::default()
50 }
51 pub fn start() -> Self {
58 Self {
59 #[cfg(feature = "std")]
60 start: Some(Instant::now()),
61 ..Self::default()
62 }
63 }
64
65 pub fn finish(self) -> Self {
70 Self {
71 #[cfg(feature = "std")]
72 time: self
73 .start
74 .map(|st| st.elapsed())
75 .unwrap_or(self.time),
76 ..self
77 }
78 }
79
80 pub fn per_sec(&self) -> Self {
82 let secs = if self.time.is_zero() {
83 1.0
84 } else {
85 self.time.as_secs_f32()
86 };
87 let [objs, prims, verts, frags] =
88 self.throughput().map(|stat| stat.per_sec(secs));
89 Self {
90 frames: self.frames / secs,
91 calls: self.calls / secs,
92 time: Duration::from_secs(1),
93 objs,
94 prims,
95 verts,
96 frags,
97 #[cfg(feature = "std")]
98 start: None,
99 }
100 }
101 pub fn per_frame(&self) -> Self {
103 let frames = self.frames.max(1.0);
104 let [objs, prims, verts, frags] = self
105 .throughput()
106 .map(|stat| stat.per_frame(frames));
107 Self {
108 frames: 1.0,
109 calls: self.calls / frames,
110 time: self.time.div_f32(frames),
111 objs,
112 prims,
113 verts,
114 frags,
115 #[cfg(feature = "std")]
116 start: None,
117 }
118 }
119
120 fn throughput(&self) -> [Throughput; 4] {
121 [self.objs, self.prims, self.verts, self.frags]
122 }
123
124 fn throughput_mut(&mut self) -> [&mut Throughput; 4] {
125 let Self { objs, prims, verts, frags, .. } = self;
126 [objs, prims, verts, frags]
127 }
128}
129
130impl Throughput {
131 fn per_sec(&self, secs: f32) -> Self {
132 Self {
133 i: (self.i as f32 / secs) as usize,
134 o: (self.o as f32 / secs) as usize,
135 }
136 }
137 fn per_frame(&self, frames: f32) -> Self {
138 Self {
139 i: self.i / frames as usize,
140 o: self.o / frames as usize,
141 }
142 }
143}
144
145impl Display for Stats {
146 #[rustfmt::skip]
147 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
148 let w = f.width().unwrap_or(16);
149 let per_s = self.per_sec();
150 let per_f = self.per_frame();
151 write!(f,
152 " STATS {:>w$} │ {:>w$} │ {:>w$}\n\
153 ────────{empty:─>w$}─┼─{empty:─>w$}─┼─{empty:─>w$}─\n \
154 time {:>w$} │ {empty:w$} │ {:>w$}\n \
155 calls {:>w$} │ {:>w$.1} │ {:>w$.1}\n \
156 frames {:>w$} │ {:>w$.1} │\n\
157 ────────{empty:─>w$}─┼─{empty:─>w$}─┼─{empty:─>w$}─\n",
158 "TOTAL", "PER SEC", "PER FRAME",
159 human_time(self.time), human_time(per_f.time),
160 self.calls, per_s.calls, per_f.calls,
161 self.frames, per_s.frames,
162 empty = ""
163 )?;
164
165 let labels = ["objs", "prims", "verts", "frags"];
166 for (i, lbl) in (0..4).zip(labels) {
167 let [tot, per_s, per_f] = [self, &per_s, &per_f].map(|s| s.throughput()[i]);
168
169 if f.alternate() {
170 writeln!(f, " {lbl:6} {tot:#w$} │ {per_s:#w$} │ {per_f:#w$}")?;
171 } else {
172 writeln!(f, " {lbl:6} {tot:w$} │ {per_s:w$} │ {per_f:w$}")?;
173 }
174 }
175 Ok(())
176 }
177}
178
179impl Display for Throughput {
180 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
181 let &Self { i, o } = self;
182 let w = f.width().unwrap_or(10);
183 if f.alternate() {
184 if i == 0 {
185 write!(f, "{:>w$}", "--")
186 } else {
187 let pct = 100.0 * o as f32 / i as f32;
188 write!(f, "{pct:>w$.1}%", w = w - 1)
189 }
190 } else {
191 let io = format!("{} / {}", human_num(i), human_num(o));
192 write!(f, "{io:>w$}")
193 }
194 }
195}
196
197impl AddAssign for Stats {
198 fn add_assign(&mut self, other: Self) {
200 self.time += other.time;
201 self.calls += other.calls;
202 self.frames += other.frames;
203 for i in 0..4 {
204 *self.throughput_mut()[i] += other.throughput()[i];
205 }
206 }
207}
208
209impl AddAssign for Throughput {
210 fn add_assign(&mut self, rhs: Self) {
211 self.i += rhs.i;
212 self.o += rhs.o;
213 }
214}
215
216fn human_num(n: usize) -> String {
217 if n < 1_000 {
218 format!("{n:5}")
219 } else if n < 100_000 {
220 format!("{:4.1}k", n as f32 / 1_000.)
221 } else if n < 1_000_000 {
222 format!("{:4}k", n / 1_000)
223 } else if n < 100_000_000 {
224 format!("{:4.1}M", n as f32 / 1_000_000.)
225 } else if n < 1_000_000_000 {
226 format!("{:4}M", n / 1_000_000)
227 } else if (n as u64) < 100_000_000_000 {
228 format!("{:4.1}G", n as f32 / 1_000_000_000.)
229 } else {
230 format!("{n:5.1e}")
231 }
232}
233
234fn human_time(d: Duration) -> String {
235 let secs = d.as_secs_f32();
236 if secs < 1e-3 {
237 format!("{:4.1}μs", secs * 1_000_000.)
238 } else if secs < 1.0 {
239 format!("{:4.1}ms", secs * 1_000.)
240 } else if secs < 60.0 {
241 format!("{:.1}s", secs)
242 } else {
243 format!("{:.0}min {:02.0}s", secs / 60.0, secs % 60.0)
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use core::array::from_fn;
250 use core::time::Duration;
251
252 use super::*;
253
254 #[test]
255 fn stats_display() {
256 let [objs, prims, verts, frags] = from_fn(|i| Throughput {
257 i: 12345 * (i + 1),
258 o: 4321 * (i + 1),
259 });
260 let stats = Stats {
261 frames: 1234.0,
262 calls: 5678.0,
263 time: Duration::from_millis(4321),
264 objs,
265 prims,
266 verts,
267 frags,
268 #[cfg(feature = "std")]
269 start: None,
270 };
271
272 assert_eq!(
273 format!("{stats}"),
274 " \
275 STATS TOTAL │ PER SEC │ PER FRAME
276─────────────────────────┼──────────────────┼──────────────────
277 time 4.3s │ │ 3.5ms
278 calls 5678 │ 1314.0 │ 4.6
279 frames 1234 │ 285.6 │
280─────────────────────────┼──────────────────┼──────────────────
281 objs 12.3k / 4.3k │ 2.9k / 1.0k │ 10 / 3
282 prims 24.7k / 8.6k │ 5.7k / 2.0k │ 20 / 7
283 verts 37.0k / 13.0k │ 8.6k / 3.0k │ 30 / 10
284 frags 49.4k / 17.3k │ 11.4k / 4.0k │ 40 / 14
285"
286 );
287
288 assert_eq!(
289 format!("{stats:#}"),
290 " \
291 STATS TOTAL │ PER SEC │ PER FRAME
292─────────────────────────┼──────────────────┼──────────────────
293 time 4.3s │ │ 3.5ms
294 calls 5678 │ 1314.0 │ 4.6
295 frames 1234 │ 285.6 │
296─────────────────────────┼──────────────────┼──────────────────
297 objs 35.0% │ 35.0% │ 30.0%
298 prims 35.0% │ 35.0% │ 35.0%
299 verts 35.0% │ 35.0% │ 33.3%
300 frags 35.0% │ 35.0% │ 35.0%
301"
302 );
303 }
304
305 #[test]
306 fn human_nums() {
307 assert_eq!(human_num(10), " 10");
308 assert_eq!(human_num(123), " 123");
309 assert_eq!(human_num(1_234), " 1.2k");
310 assert_eq!(human_num(12_3456), " 123k");
311 assert_eq!(human_num(1_234_567), " 1.2M");
312 assert_eq!(human_num(123_456_789), " 123M");
313 assert_eq!(human_num(1_234_567_890), " 1.2G");
314 assert_eq!(human_num(123_456_789_000), "1.2e11");
315 }
316
317 #[test]
318 fn human_times() {
319 assert_eq!(human_time(Duration::from_micros(123)), "123.0μs");
320 assert_eq!(human_time(Duration::from_millis(123)), "123.0ms");
321 assert_eq!(human_time(Duration::from_millis(1234)), "1.2s");
322 assert_eq!(human_time(Duration::from_secs(1234)), "21min 34s");
323 }
324}