nydus_utils/
trace.rs

1// Copyright 2020 Ant Group. All rights reserved.
2//
3// SPDX-License-Identifier: Apache-2.0
4
5//! Trace image building procedure
6
7use 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/// Used to measure time consuming and gather all tracing points when building image.
64#[derive(Serialize, Default)]
65pub struct TimingTracerClass {
66    // Generally speaking, we won't have many timing tracers act from multiple points.
67    // So `Mutex` should fill our requirements.
68    #[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    // Not expect poisoned lock.
96    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
106/// The root tracer manages all kinds of tracers registered to it.
107/// The statistics/events/records can be printed out or persisted from the root
108/// tracer. When building procedure is finished, root tracer can dump all tracing
109/// points to specified output file.
110pub 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        // In case a certain class is registered multiple times, e.g. from several
118        // concurrently running test cases.
119        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        // Safe to unwrap because tracers should always be enabled
127        (&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                // Double check to close the race that another thread has already inserted.
234                // Cast integer to u64 should be reliable for most cases.
235                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}