1#[cfg(feature = "perf")]
31mod enabled {
32 use std::cell::RefCell;
33 use std::fs::{File, OpenOptions};
34 use std::io::{BufWriter, Write};
35 use std::path::Path;
36 use std::time::{Instant, SystemTime, UNIX_EPOCH};
37
38 thread_local! {
40 pub(crate) static LOG_FILE: RefCell<Option<BufWriter<File>>> = const { RefCell::new(None) };
41 static FRAME_COUNTER: RefCell<u64> = const { RefCell::new(0) };
42 static RUN_ID: RefCell<String> = const { RefCell::new(String::new()) };
43 }
44
45 pub struct PerfLogger {
46 _private: (),
47 }
48
49 fn unix_ms() -> u128 {
50 SystemTime::now().duration_since(UNIX_EPOCH).map_or(0, |duration| duration.as_millis())
51 }
52
53 pub(crate) fn write_entry(name: &'static str, ms: f64, extra: Option<(&'static str, usize)>) {
54 let frame = FRAME_COUNTER.with(|c| *c.borrow());
55 let ts_ms = unix_ms();
56 LOG_FILE.with(|f| {
57 if let Some(ref mut file) = *f.borrow_mut() {
58 RUN_ID.with(|run| {
59 let run_id = run.borrow();
60 if let Some((k, v)) = extra {
61 let _ = writeln!(
62 file,
63 r#"{{"run":"{run_id}","frame":{frame},"ts_ms":{ts_ms},"fn":"{name}","ms":{ms:.3},"{k}":{v}}}"#,
64 );
65 } else {
66 let _ = writeln!(
67 file,
68 r#"{{"run":"{run_id}","frame":{frame},"ts_ms":{ts_ms},"fn":"{name}","ms":{ms:.3}}}"#,
69 );
70 }
71 });
72 }
73 });
74 }
75
76 #[allow(clippy::unused_self)]
77 impl PerfLogger {
78 pub fn open(path: &Path, append: bool) -> Option<Self> {
80 let mut options = OpenOptions::new();
81 options.create(true).write(true);
82 if append {
83 options.append(true);
84 } else {
85 options.truncate(true);
86 }
87 let file = options.open(path).ok()?;
88 let mut writer = BufWriter::new(file);
89 let run_id = uuid::Uuid::new_v4().to_string();
90 let ts_ms = unix_ms();
91 let _ = writeln!(
92 writer,
93 r#"{{"event":"run_start","run":"{run_id}","ts_ms":{ts_ms},"pid":{},"version":"{}","append":{append}}}"#,
94 std::process::id(),
95 env!("CARGO_PKG_VERSION")
96 );
97 let _ = writer.flush();
98 LOG_FILE.with(|f| *f.borrow_mut() = Some(writer));
99 RUN_ID.with(|r| *r.borrow_mut() = run_id);
100 FRAME_COUNTER.with(|c| *c.borrow_mut() = 0);
101 Some(Self { _private: () })
102 }
103
104 pub fn next_frame(&mut self) {
106 let frame = FRAME_COUNTER.with(|c| {
107 let mut value = c.borrow_mut();
108 *value += 1;
109 *value
110 });
111 if frame.is_multiple_of(240) {
112 LOG_FILE.with(|f| {
113 if let Some(ref mut file) = *f.borrow_mut() {
114 let _ = file.flush();
115 }
116 });
117 }
118 }
119
120 #[must_use]
122 pub fn start(&self, name: &'static str) -> Timer {
123 Timer { name, start: Instant::now(), extra: None }
124 }
125
126 #[must_use]
128 pub fn start_with(
129 &self,
130 name: &'static str,
131 extra_name: &'static str,
132 extra_val: usize,
133 ) -> Timer {
134 Timer { name, start: Instant::now(), extra: Some((extra_name, extra_val)) }
135 }
136
137 pub fn mark(&self, name: &'static str) {
139 write_entry(name, 0.0, None);
140 }
141
142 pub fn mark_with(&self, name: &'static str, extra_name: &'static str, extra_val: usize) {
144 write_entry(name, 0.0, Some((extra_name, extra_val)));
145 }
146 }
147
148 pub struct Timer {
149 pub(crate) name: &'static str,
150 pub(crate) start: Instant,
151 pub(crate) extra: Option<(&'static str, usize)>,
152 }
153
154 #[allow(clippy::unused_self)]
155 impl Timer {
156 pub fn stop(self) {
158 }
160 }
161
162 impl Drop for Timer {
163 fn drop(&mut self) {
164 let ms = self.start.elapsed().as_secs_f64() * 1000.0;
165 write_entry(self.name, ms, self.extra);
166 }
167 }
168}
169
170#[cfg(not(feature = "perf"))]
171mod disabled {
172 use std::path::Path;
173
174 pub struct PerfLogger;
175 pub struct Timer;
176
177 #[allow(clippy::unused_self)]
178 impl PerfLogger {
179 #[inline]
180 pub fn open(_path: &Path, _append: bool) -> Option<Self> {
181 None
182 }
183 #[inline]
184 pub fn next_frame(&mut self) {}
185 #[inline]
186 #[must_use]
187 pub fn start(&self, _name: &'static str) -> Timer {
188 Timer
189 }
190 #[inline]
191 #[must_use]
192 pub fn start_with(
193 &self,
194 _name: &'static str,
195 _extra_name: &'static str,
196 _extra_val: usize,
197 ) -> Timer {
198 Timer
199 }
200 #[inline]
201 pub fn mark(&self, _name: &'static str) {}
202 #[inline]
203 pub fn mark_with(&self, _name: &'static str, _extra_name: &'static str, _extra_val: usize) {
204 }
205 }
206
207 #[allow(clippy::unused_self)]
208 impl Timer {
209 #[inline]
210 pub fn stop(self) {}
211 }
212}
213
214#[cfg(feature = "perf")]
218#[must_use]
219#[inline]
220pub fn start(name: &'static str) -> Option<Timer> {
221 enabled::LOG_FILE.with(|f| {
223 if f.borrow().is_some() {
224 Some(Timer { name, start: std::time::Instant::now(), extra: None })
225 } else {
226 None
227 }
228 })
229}
230
231#[cfg(feature = "perf")]
232#[must_use]
233#[inline]
234pub fn start_with(name: &'static str, extra_name: &'static str, extra_val: usize) -> Option<Timer> {
235 enabled::LOG_FILE.with(|f| {
236 if f.borrow().is_some() {
237 Some(Timer {
238 name,
239 start: std::time::Instant::now(),
240 extra: Some((extra_name, extra_val)),
241 })
242 } else {
243 None
244 }
245 })
246}
247
248#[cfg(not(feature = "perf"))]
249#[must_use]
250#[inline]
251pub fn start(_name: &'static str) -> Option<Timer> {
252 None
253}
254
255#[cfg(not(feature = "perf"))]
256#[must_use]
257#[inline]
258pub fn start_with(
259 _name: &'static str,
260 _extra_name: &'static str,
261 _extra_val: usize,
262) -> Option<Timer> {
263 None
264}
265
266#[cfg(feature = "perf")]
268#[inline]
269pub fn mark(name: &'static str) {
270 enabled::write_entry(name, 0.0, None);
271}
272
273#[cfg(not(feature = "perf"))]
274#[inline]
275pub fn mark(_name: &'static str) {}
276
277#[cfg(feature = "perf")]
279#[inline]
280pub fn mark_with(name: &'static str, extra_name: &'static str, extra_val: usize) {
281 enabled::write_entry(name, 0.0, Some((extra_name, extra_val)));
282}
283
284#[cfg(not(feature = "perf"))]
285#[inline]
286pub fn mark_with(_name: &'static str, _extra_name: &'static str, _extra_val: usize) {}
287
288#[cfg(feature = "perf")]
289pub use enabled::{PerfLogger, Timer};
290
291#[cfg(not(feature = "perf"))]
292pub use disabled::{PerfLogger, Timer};