lingon/
performance.rs

1use std::sync::{Arc, Mutex};
2use lazy_static::lazy_static;
3use std::time::Instant;
4use std::borrow::BorrowMut;
5
6lazy_static! {
7    pub static ref PERF_COUNTER: Arc<Mutex<Collector>> = Arc::new(Mutex::new(Collector::new()));
8}
9
10pub struct Marker {
11    start: Instant,
12    id: usize,
13}
14
15impl Drop for Marker {
16    fn drop(&mut self) {
17        PERF_COUNTER.lock().unwrap().end(self)
18    }
19}
20
21pub struct Counter {
22    name: &'static str,
23    file: &'static str,
24    line: u32,
25
26    total_calls: usize,
27    calls_this_frame: usize,
28    total_time: f64,
29    time_this_frame: f64,
30}
31
32impl Counter {
33    pub fn new(name: &'static str,
34               file: &'static str,
35               line: u32) -> Self {
36        Self {
37            name,
38            file,
39            line,
40
41            total_calls: 0,
42            calls_this_frame: 0,
43            total_time: 0.0,
44            time_this_frame: 0.0,
45        }
46    }
47
48    fn add(&mut self, start: Instant) {
49        self.total_calls += 1;
50        self.calls_this_frame += 1;
51        let time = Instant::now().duration_since(start).as_secs_f64();
52        self.total_time += time;
53        self.time_this_frame += time;
54    }
55}
56
57#[derive(Copy, Clone, Debug)]
58pub enum CaptureWindow {
59    Nothing,
60    Silent,
61    CaptureFor(usize),
62    LogEvery(usize),
63    Everything,
64}
65
66impl CaptureWindow {
67    fn should_log(&self, frame: usize) -> bool {
68        match self {
69            CaptureWindow::Everything | CaptureWindow::CaptureFor(_) => true,
70            CaptureWindow::LogEvery(x) => (frame % x) == 0,
71            _ => false,
72        }
73    }
74
75    fn should_capture(&self) -> bool {
76        !matches!(self, CaptureWindow::Nothing)
77    }
78
79    fn step(self) -> Self {
80        match self {
81            CaptureWindow::CaptureFor(0) => CaptureWindow::Nothing,
82            CaptureWindow::CaptureFor(x) => CaptureWindow::CaptureFor(x - 1),
83            x => x,
84        }
85    }
86}
87
88pub fn capture_for(window: CaptureWindow) {
89    PERF_COUNTER.lock().unwrap().borrow_mut().window = window;
90}
91
92pub fn frame() {
93    let mut counter = PERF_COUNTER.lock().unwrap();
94    let counter = counter.borrow_mut();
95    if counter.window.should_capture() {
96        counter.frame();
97    }
98    if counter.window.should_log(counter.num_frames) {
99        counter.log();
100    }
101}
102
103#[macro_export]
104macro_rules! counter {
105    ( $name:expr ) => {
106        {
107            let info = lingon::performance::Counter::new(
108                $name,
109                std::file!(),
110                std::line!(),
111            );
112            let counter = lingon_macro::perf_counter!();
113            lingon::performance::PERF_COUNTER.lock().unwrap().start(counter, info)
114        }
115    };
116}
117
118pub struct Collector {
119    counters: Vec<Option<Counter>>,
120    window: CaptureWindow,
121
122    start: Instant,
123    num_frames: usize,
124    last_time: f64,
125    weighted_time: f64,
126    total_time: f64,
127    min_frame_time: f64,
128    max_frame_time: f64,
129}
130
131impl Collector {
132    fn new() -> Self {
133        Self {
134            counters: Vec::new(),
135
136            window: CaptureWindow::LogEvery(100),
137            start: Instant::now(),
138            num_frames: 0,
139            last_time: 0.0,
140            weighted_time: 0.0,
141            total_time: 0.0,
142            min_frame_time: f64::MAX,
143            max_frame_time: f64::MIN,
144        }
145    }
146
147    pub fn start(&mut self, id: usize, counter: Counter) -> Marker {
148        if self.counters.len() <= id {
149            self.counters.resize_with(id + 1, || None);
150        }
151
152        if matches!(self.counters[id], None) {
153            self.counters[id] = Some(counter);
154        }
155        Marker {
156            id,
157            start: Instant::now(),
158        }
159    }
160
161    pub fn end(&mut self, marker: &mut Marker) {
162        self.counters.get_mut(marker.id).unwrap().as_mut().unwrap().add(marker.start);
163    }
164
165    pub fn frame(&mut self) {
166        let end = Instant::now();
167        let frame_time = end.duration_since(self.start).as_secs_f64();
168
169        self.window = self.window.step();
170        self.start = end;
171        self.num_frames += 1;
172        self.total_time += frame_time;
173        self.min_frame_time = frame_time.min(self.min_frame_time);
174        self.max_frame_time = frame_time.max(self.max_frame_time);
175        self.last_time = frame_time;
176
177        let weighting = 0.8;
178        self.weighted_time = self.weighted_time * (1.0 - weighting) + frame_time * weighting;
179    }
180
181    pub fn log(&mut self) {
182        println!("PERFORMANCE: #{}\nthis: {:<5.5} wgh: {:<5.5} avg: {:<5.5} min: {:<5.5} max: {:<5.5}",
183            self.num_frames,
184            self.last_time,
185            self.weighted_time,
186            self.total_time / (self.num_frames as f64),
187            self.min_frame_time,
188            self.max_frame_time,
189        );
190        for counter in self.counters.iter().filter_map(|x| x.as_ref()) {
191            println!(" {} ({}:{}) - {:<5.5} {:<5.5}",
192                counter.name,
193                counter.file,
194                counter.line,
195                counter.time_this_frame / (counter.calls_this_frame as f64),
196                counter.total_time / (counter.total_calls as f64),
197            )
198        }
199    }
200}