wisp/renderer/stats.rs
1use crate::view::syntax::HighlightStats;
2use std::time::Instant;
3
4/// Work one [`Renderer`](super::Renderer) did since the last
5/// [`Renderer::take_stats`](super::Renderer::take_stats), so tests can bound
6/// per-frame rendering work by counting it instead of timing it.
7///
8/// Byte counters measure input re-processed, not output produced: an item whose
9/// rendering is O(content) every frame shows up as its content size again and
10/// again, whatever the drawn output looks like.
11#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
12pub struct RenderStats {
13 pub frames: u64,
14 /// Item renders that ran rather than being served from a cache. A streaming
15 /// item rebuilds as its content grows and an open tool call as its spinner
16 /// moves; anything else rebuilding is wasted work.
17 pub item_rebuilds: u64,
18 pub markdown_bytes_parsed: u64,
19 /// Largest live region any single render produced. Native scrollback commits
20 /// are supposed to bound this to roughly the viewport height.
21 pub max_live_rows: usize,
22 pub history_rows_inserted: u64,
23 pub highlight: HighlightStats,
24 pub ns_layout: u64,
25 pub ns_live: u64,
26 pub ns_draw: u64,
27 pub ns_item_rebuild: u64,
28}
29
30/// One measured stretch of a draw. Compiled to nothing outside the `testing`
31/// feature, so production draws pay only the zero-sized struct.
32pub(super) struct Lap {
33 #[cfg(feature = "testing")]
34 start: Instant,
35}
36
37impl Lap {
38 pub(super) fn start() -> Self {
39 #[cfg(feature = "testing")]
40 return Self { start: Instant::now() };
41 #[cfg(not(feature = "testing"))]
42 Self {}
43 }
44
45 #[cfg_attr(not(feature = "testing"), expect(clippy::unused_self))]
46 pub(super) fn ns(self) -> u64 {
47 #[cfg(feature = "testing")]
48 return u64::try_from(self.start.elapsed().as_nanos()).unwrap_or(u64::MAX);
49 #[cfg(not(feature = "testing"))]
50 0
51 }
52}