1use std::any::Any;
8use std::cmp::{Eq, PartialEq};
9use std::collections::HashMap;
10use std::fmt::{Display, Formatter, Result as FmtResult};
11use std::sync::{atomic::AtomicU64, Arc, Mutex, RwLock};
12use std::time::SystemTime;
13
14use serde::Serialize;
15use serde_json::{error::Error, value::Value};
16use thiserror::Error;
17
18impl Display for TraceClass {
19 fn fmt(&self, f: &mut Formatter) -> FmtResult {
20 match self {
21 TraceClass::Timing => write!(f, "consumed_time"),
22 TraceClass::Event => write!(f, "registered_events"),
23 }
24 }
25}
26
27macro_rules! enum_str {
28 ($m:meta
29 pub enum $name:ident {
30 $($variant:ident = $val:expr),*,
31 }) => {
32 #[$m]
33 pub enum $name {
34 $($variant = $val),*
35 }
36
37 impl $name {
38 fn name(&self) -> String {
39 match self {
40 $($name::$variant => format!("{}", $name::$variant)),*
41 }
42 }
43 }
44 };
45}
46
47enum_str! {
48derive(Hash, Eq, PartialEq)
49pub enum TraceClass {
50 Timing = 1,
51 Event = 2,
52}
53}
54
55#[derive(Error, Debug)]
56pub enum TraceError {
57 #[error("serialize error: {0}")]
58 Serde(Error),
59}
60
61type Result<T> = std::result::Result<T, TraceError>;
62
63#[derive(Serialize, Default)]
65pub struct TimingTracerClass {
66 #[serde(flatten)]
69 records: Mutex<HashMap<String, f32>>,
70}
71
72pub trait TracerClass: Send + Sync + 'static {
73 fn release(&self) -> Result<Value>;
74 fn as_any(&self) -> &dyn Any;
75}
76
77impl TracerClass for TimingTracerClass {
78 fn release(&self) -> Result<Value> {
79 serde_json::to_value(self).map_err(TraceError::Serde)
80 }
81 fn as_any(&self) -> &dyn Any {
82 self
83 }
84}
85
86pub fn trace_timing<F: FnOnce() -> T, T>(
87 point: &str,
88 tracer: Option<&TimingTracerClass>,
89 f: F,
90) -> T {
91 let begin = SystemTime::now();
92 let r = f();
93 let elapsed = SystemTime::now().duration_since(begin).unwrap();
94
95 if let Some(t) = tracer {
97 t.records
98 .lock()
99 .unwrap()
100 .insert(point.to_string(), elapsed.as_secs_f32());
101 }
102
103 r
104}
105
106pub struct BuildRootTracer {
111 tracers: RwLock<HashMap<TraceClass, Arc<dyn TracerClass>>>,
112}
113
114impl BuildRootTracer {
115 pub fn register(&self, class: TraceClass, tracer: Arc<dyn TracerClass>) {
116 let mut guard = self.tracers.write().unwrap();
117 if guard.get(&class).is_none() {
120 guard.insert(class, tracer);
121 }
122 }
123
124 pub fn tracer(&self, class: TraceClass) -> Option<Arc<dyn TracerClass>> {
125 let g = self.tracers.read().unwrap();
126 (&g).get(&class).cloned()
128 }
129
130 pub fn dump_summary_map(&self) -> Result<serde_json::Map<String, serde_json::Value>> {
131 let mut map = serde_json::Map::new();
132 for c in self.tracers.write().unwrap().iter() {
133 map.insert(c.0.name(), c.1.release()?);
134 }
135 Ok(map)
136 }
137}
138
139#[derive(Serialize)]
140#[serde(untagged)]
141#[allow(dead_code)]
142pub enum TraceEvent {
143 Counter(AtomicU64),
144 Fixed(u64),
145 Desc(String),
146}
147
148#[derive(Serialize, Default)]
149pub struct EventTracerClass {
150 #[serde(flatten)]
151 pub events: RwLock<HashMap<String, TraceEvent>>,
152}
153
154impl TracerClass for EventTracerClass {
155 fn release(&self) -> Result<Value> {
156 serde_json::to_value(self).map_err(TraceError::Serde)
157 }
158 fn as_any(&self) -> &dyn Any {
159 self
160 }
161}
162
163lazy_static! {
164 pub static ref BUILDING_RECORDER: BuildRootTracer = BuildRootTracer {
165 tracers: RwLock::new(HashMap::default())
166 };
167}
168
169#[macro_export]
170macro_rules! root_tracer {
171 () => {
172 &$crate::trace::BUILDING_RECORDER as &$crate::trace::BuildRootTracer
173 };
174}
175
176#[macro_export]
177macro_rules! timing_tracer {
178 () => {
179 root_tracer!()
180 .tracer($crate::trace::TraceClass::Timing)
181 .as_ref()
182 .map(|t| {
183 t.as_any()
184 .downcast_ref::<$crate::trace::TimingTracerClass>()
185 .unwrap()
186 })
187 };
188 ($f:block, $key:expr) => {
189 $crate::trace::trace_timing($key, timing_tracer!(), || $f)
190 };
191 ($f:block, $key:expr, $t:ty) => {
192 $crate::trace::trace_timing::<_, $t>($key, timing_tracer!(), || $f)
193 };
194}
195
196#[macro_export]
197macro_rules! register_tracer {
198 ($class:expr, $r:ty) => {
199 root_tracer!().register($class, std::sync::Arc::new(<$r>::default()));
200 };
201}
202
203#[macro_export]
204macro_rules! event_tracer {
205 () => {
206 root_tracer!()
207 .tracer($crate::trace::TraceClass::Event)
208 .as_ref()
209 .map(|t| {
210 t.as_any()
211 .downcast_ref::<$crate::trace::EventTracerClass>()
212 .unwrap()
213 })
214 };
215 ($event:expr, $desc:expr) => {
216 event_tracer!().events.write().unwrap().insert(
217 $event.to_string(),
218 $crate::trace::TraceEvent::Fixed($desc as u64),
219 )
220 };
221 ($event:expr, +$value:expr) => {
222 let mut new: bool = true;
223
224 if let Some(t) = event_tracer!() {
225 if let Some($crate::trace::TraceEvent::Counter(ref e)) =
226 t.events.read().unwrap().get($event)
227 {
228 e.fetch_add($value as u64, std::sync::atomic::Ordering::Relaxed);
229 new = false;
230 }
231
232 if new {
233 if let Ok(ref mut guard) = t.events.write() {
236 if let Some($crate::trace::TraceEvent::Counter(ref e)) = guard.get($event) {
237 e.fetch_add($value as u64, std::sync::atomic::Ordering::Relaxed);
238 } else {
239 guard.insert(
240 $event.to_string(),
241 $crate::trace::TraceEvent::Counter(std::sync::atomic::AtomicU64::new(
242 $value as u64,
243 )),
244 );
245 }
246 }
247 }
248 }
249 };
250 ($event:expr, $format:expr, $value:expr) => {
251 if let Some(t) = event_tracer!() {
252 if let Ok(ref mut guard) = t.events.write() {
253 guard.insert(
254 $event.to_string(),
255 $crate::trace::TraceEvent::Desc(format!($format, $value)),
256 );
257 }
258 }
259 };
260}
261
262#[cfg(test)]
263pub mod tests {
264 use crate::trace::TimingTracerClass;
265
266 use super::{EventTracerClass, TraceClass};
267 use std::thread;
268
269 #[test]
270 fn test_event_trace() {
271 register_tracer!(TraceClass::Event, EventTracerClass);
272
273 let t1 = thread::Builder::new()
274 .spawn(move || {
275 for _i in 0..100 {
276 event_tracer!("event_1", +2);
277 event_tracer!("event_2", +3);
278 }
279 })
280 .unwrap();
281
282 let t2 = thread::Builder::new()
283 .spawn(move || {
284 for _i in 0..100 {
285 event_tracer!("event_1", +2);
286 event_tracer!("event_2", +3);
287 }
288 })
289 .unwrap();
290
291 let t3 = thread::Builder::new()
292 .spawn(move || {
293 for _i in 0..100 {
294 event_tracer!("event_1", +2);
295 event_tracer!("event_2", +3);
296 }
297 })
298 .unwrap();
299
300 t1.join().unwrap();
301 t2.join().unwrap();
302 t3.join().unwrap();
303
304 let map = root_tracer!().dump_summary_map().unwrap();
305 assert_eq!(map["registered_events"]["event_1"].as_u64(), Some(600));
306 assert_eq!(map["registered_events"]["event_2"].as_u64(), Some(900));
307 }
308
309 #[test]
310 fn test_timing_trace() {
311 register_tracer!(TraceClass::Timing, TimingTracerClass);
312 let f = || ();
313
314 let t1 = thread::Builder::new()
315 .spawn(move || {
316 for i in 0..100 {
317 timing_tracer!({ f() }, format!("t1.{}", i).as_str());
318 }
319 })
320 .unwrap();
321
322 let t2 = thread::Builder::new()
323 .spawn(move || {
324 for i in 0..100 {
325 timing_tracer!({ f() }, format!("t2.{}", i).as_str());
326 }
327 })
328 .unwrap();
329 let t3 = thread::Builder::new()
330 .spawn(move || {
331 for i in 0..100 {
332 timing_tracer!({ f() }, format!("t3.{}", i).as_str());
333 }
334 })
335 .unwrap();
336
337 t1.join().unwrap();
338 t2.join().unwrap();
339 t3.join().unwrap();
340 assert_eq!(timing_tracer!().unwrap().records.lock().unwrap().len(), 300);
341 }
342}