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}