Skip to main content

gwr_track/tracker/
mod.rs

1// Copyright (c) 2023 Graphcore Ltd. All rights reserved.
2
3//! Define the [`Track`] trait a number of [`Tracker`]s.
4
5/// Include the alternative name manager.
6pub mod aka;
7/// Include the CapnProto tracker.
8pub mod capnp;
9/// Include the /dev/null tracker.
10pub mod dev_null;
11/// Include the Perfetto tracker.
12#[cfg(feature = "perfetto")]
13pub mod perfetto;
14/// Include the text-based tracker.
15pub mod text;
16/// Include the types required for tracker.
17pub mod types;
18
19/// Include the multi-tracker.
20pub mod multi_tracker;
21
22use std::cell::RefCell;
23use std::collections::HashMap;
24use std::io;
25use std::rc::Rc;
26
27pub use capnp::CapnProtoTracker;
28pub use dev_null::DevNullTracker;
29use regex::Regex;
30pub use text::TextTracker;
31
32use crate::entity::Capacity;
33use crate::tracker::aka::AlternativeNames;
34use crate::{Id, ROOT};
35
36/// Error used to return configuration errors
37#[derive(Debug)]
38pub struct TrackConfigError(pub String);
39
40/// This is the interface that is supported by all [`Tracker`]s.
41pub trait Track {
42    /// Allocate a new global ID
43    fn unique_id(&self) -> Id;
44
45    /// Determine the most verbose tracking level enabled for an entity.
46    fn enabled_level(&self, id: Id) -> log::Level;
47
48    /// Determine whether tracking is enabled at a given level for an entity.
49    fn is_entity_enabled(&self, id: Id, level: log::Level) -> bool {
50        level <= self.enabled_level(id)
51    }
52
53    /// Return the monitoring window size if it is to be enabled.
54    /// Entity looked up by its ID.
55    fn monitoring_window_size_for(&self, id: Id) -> Option<u64>;
56
57    /// Record an entity being created.
58    fn add_entity(
59        &self,
60        id: Id,
61        entity_name: &str,
62        alternative_names: AlternativeNames,
63    ) -> log::Level;
64
65    /// Track when an entity with the given ID arrives.
66    fn enter(&self, enter_into: Id, enter_obj: Id);
67
68    /// Track when an entity with the given ID leaves.
69    fn exit(&self, exit_from: Id, exit_obj: Id);
70
71    /// Track an entity setting a value.
72    fn value(&self, id: Id, value: f64);
73
74    /// Track when an entity with the given ID is created.
75    fn create_entity(&self, created_by: Id, id: Id, name: &str);
76
77    /// Track when a monitor with the given ID is created.
78    fn create_monitor(&self, created_by: Id, id: Id, name: &str);
79
80    /// Track when a lane with the given ID is created.
81    fn create_lane(&self, created_by: Id, id: Id, name: &str);
82
83    /// Track when a group with the given ID is created.
84    fn create_group(&self, created_by: Id, id: Id, name: &str);
85
86    /// Track when an activity becomes a member of a group.
87    fn add_to_group(&self, activity: Id, group_id: Id);
88
89    /// Track when an activity is no longer a member of a group.
90    fn remove_from_group(&self, activity: Id, group_id: Id);
91
92    /// Track the beginning of a named activity on a lane.
93    fn begin_activity(&self, activity: Id, lane: Id, name: &str);
94
95    /// Track the end of the current activity on a lane.
96    fn end_activity(&self, activity: Id);
97
98    /// Track when an object with the given ID is created.
99    fn create_object(
100        &self,
101        created_by: Id,
102        id: Id,
103        size: usize,
104        units: &str,
105        req_type: u8,
106        details: &str,
107    );
108
109    /// Track the capacity available in an entity.
110    fn capacity(&self, id: Id, capacity: Capacity);
111
112    /// Track when an entity with the given ID is destroyed.
113    fn destroy(&self, destroyed_by: Id, destroyed_obj: Id);
114
115    /// Track when an entity is connected to another entity
116    fn connect(&self, connect_from: Id, connect_to: Id);
117
118    /// Track a log message of the given level.
119    fn log(&self, msg_by: Id, level: log::Level, msg: std::fmt::Arguments);
120
121    /// Advance the time to the time specified in `ns`.
122    fn time(&self, set_by: Id, time_ns: f64);
123
124    /// Perform any pre-exit shutdown/cleanup
125    fn shutdown(&self);
126}
127
128/// The type of a [`Tracker`] that is shared across entities.
129pub type Tracker = Rc<dyn Track>;
130
131/// Create a [`Tracker`] that prints all track events to `stdout`.
132#[must_use]
133pub fn stdout_tracker(level: log::Level) -> Tracker {
134    let entity_manger = EntityManager::new(level);
135    let stdout_writer = Box::new(std::io::BufWriter::new(io::stdout()));
136    let tracker: Tracker = Rc::new(TextTracker::new(entity_manger, stdout_writer));
137    tracker
138}
139
140/// Create a [`Tracker`] that suppresses all track events.
141#[must_use]
142pub fn dev_null_tracker() -> Tracker {
143    let tracer: Tracker = Rc::new(DevNullTracker {});
144    tracer
145}
146
147/// The [`EntityManager`] is responsible for determining entity log / trace
148/// enable states.
149///
150/// This is shared by the [`Text`](crate::tracker::text) and
151/// [`Capnp`](crate::tracker::capnp)-based trackers, as well as the
152/// [`Perfetto`](crate::tracker::perfetto) tracker.
153///
154/// This manager is also used to allocate unique [`Id`] values.
155pub struct EntityManager {
156    /// Level of tracking events to output.
157    default_entity_level: log::Level,
158
159    /// List of regular expressions mapping entity names to log levels.
160    regex_to_entity_level: Vec<(Regex, log::Level)>,
161
162    /// List of regular expressions mapping entity names to log levels.
163    regex_to_enable_monitors_for: Vec<(Regex, u64)>,
164
165    /// Used to assign unique IDs.
166    unique_id: RefCell<u64>,
167
168    /// Keep track of entities that have trace enable/log levels different to
169    /// the default.
170    log_entity_lookup: RefCell<HashMap<Id, log::Level>>,
171
172    /// Keep track of the window size for entities.
173    monitor_window_size_lookup: RefCell<HashMap<Id, u64>>,
174}
175
176impl EntityManager {
177    /// Constructor with default [`log::Level`]
178    #[must_use]
179    pub fn new(default_entity_level: log::Level) -> Self {
180        Self {
181            default_entity_level,
182            regex_to_entity_level: Vec::new(),
183            regex_to_enable_monitors_for: Vec::new(),
184            unique_id: RefCell::new(ROOT.0 + 1),
185            log_entity_lookup: RefCell::new(HashMap::new()),
186            monitor_window_size_lookup: RefCell::new(HashMap::new()),
187        }
188    }
189
190    fn unique_id(&self) -> Id {
191        let mut guard = self.unique_id.borrow_mut();
192        let id = *guard;
193        *guard += 1;
194        Id(id)
195    }
196
197    fn enabled_level(&self, id: Id) -> log::Level {
198        match self.log_entity_lookup.borrow().get(&id) {
199            None => self.default_entity_level,
200            Some(entity_level) => *entity_level,
201        }
202    }
203
204    fn monitoring_window_size_for(&self, id: Id) -> Option<u64> {
205        self.monitor_window_size_lookup.borrow().get(&id).copied()
206    }
207
208    fn add_entity(
209        &self,
210        id: Id,
211        entity_name: &str,
212        alternative_names: AlternativeNames,
213    ) -> log::Level {
214        let entity_level = self.log_level_for(entity_name, alternative_names);
215        if entity_level != self.default_entity_level
216            && self
217                .log_entity_lookup
218                .borrow_mut()
219                .insert(id, entity_level)
220                .is_some()
221        {
222            panic!("Entity ID {id} already seen ({entity_name})");
223        }
224
225        if let Some(window_size_ticks) =
226            self.monitor_window_size_for(entity_name, alternative_names)
227        {
228            self.monitor_window_size_lookup
229                .borrow_mut()
230                .insert(id, window_size_ticks);
231        }
232
233        entity_level
234    }
235
236    fn log_level_for(&self, entity_name: &str, alternative_names: AlternativeNames) -> log::Level {
237        for (regex, level) in &self.regex_to_entity_level {
238            if regex.is_match(entity_name) {
239                return *level;
240            }
241            if let Some(alternative_names) = alternative_names {
242                for name in alternative_names {
243                    if regex.is_match(name.as_str()) {
244                        return *level;
245                    }
246                }
247            }
248        }
249        self.default_entity_level
250    }
251
252    fn monitor_window_size_for(
253        &self,
254        entity_name: &str,
255        alternative_names: AlternativeNames,
256    ) -> Option<u64> {
257        for (regex, window_size_ticks) in &self.regex_to_enable_monitors_for {
258            if regex.is_match(entity_name) {
259                return Some(*window_size_ticks);
260            }
261            if let Some(alternative_names) = alternative_names {
262                for name in alternative_names {
263                    if regex.is_match(name.as_str()) {
264                        return Some(*window_size_ticks);
265                    }
266                }
267            }
268        }
269        None
270    }
271
272    /// Add a filter regular expression to set matching entities to a given
273    /// level.
274    ///
275    /// # Example
276    ///
277    /// ```rust
278    /// use gwr_track::tracker::EntityManager;
279    /// let mut manager = EntityManager::new(log::Level::Warn);
280    /// manager
281    ///     .add_entity_level_filter(".*arb.*", log::Level::Trace)
282    ///     .unwrap();
283    /// ```
284    pub fn add_entity_level_filter(
285        &mut self,
286        regex_str: &str,
287        level: crate::log::Level,
288    ) -> Result<(), TrackConfigError> {
289        match Regex::new(regex_str) {
290            Ok(regex) => self.regex_to_entity_level.push((regex, level)),
291            Err(e) => {
292                return Err(TrackConfigError(format!(
293                    "Failed to parse regex {regex_str}:\n{e}\n"
294                )));
295            }
296        }
297        Ok(())
298    }
299
300    /// Add a filter for ports that should have monitoring enabled
301    /// with the specified window size in ticks.
302    ///
303    /// # Example
304    ///
305    /// ```rust
306    /// use gwr_track::tracker::EntityManager;
307    /// let mut manager = EntityManager::new(log::Level::Warn);
308    /// manager
309    ///     .set_monitor_window_size_for(".*fabric::ingress.*", 250)
310    ///     .unwrap();
311    /// ```
312    pub fn set_monitor_window_size_for(
313        &mut self,
314        regex_str: &str,
315        window_size_ticks: u64,
316    ) -> Result<(), TrackConfigError> {
317        match Regex::new(regex_str) {
318            Ok(regex) => self
319                .regex_to_enable_monitors_for
320                .push((regex, window_size_ticks)),
321            Err(e) => {
322                return Err(TrackConfigError(format!(
323                    "Failed to parse regex {regex_str}:\n{e}\n"
324                )));
325            }
326        }
327        Ok(())
328    }
329}
330
331#[cfg(test)]
332mod tests {
333    use log::Level;
334
335    use super::*;
336
337    fn entity_paths() -> Vec<&'static str> {
338        vec!["top", "top::dev", "top::dev::node0", "top::dev::node1"]
339    }
340
341    #[test]
342    fn no_filters() {
343        let manager = EntityManager::new(Level::Error);
344
345        for p in entity_paths() {
346            assert_eq!(manager.log_level_for(p, None), Level::Error);
347        }
348    }
349
350    #[test]
351    fn filter_dev_trace() {
352        let mut manager = EntityManager::new(Level::Error);
353        manager
354            .add_entity_level_filter(r".*dev.*", Level::Trace)
355            .unwrap();
356
357        let expected_levels = [Level::Error, Level::Trace, Level::Trace, Level::Trace];
358
359        for (i, p) in entity_paths().iter().enumerate() {
360            assert_eq!(manager.log_level_for(p, None), expected_levels[i]);
361        }
362    }
363
364    #[test]
365    fn filter_node0_error() {
366        let mut manager = EntityManager::new(Level::Warn);
367        manager
368            .add_entity_level_filter(r".*node0", Level::Error)
369            .unwrap();
370
371        let expected_levels = [Level::Warn, Level::Warn, Level::Error, Level::Warn];
372
373        for (i, p) in entity_paths().iter().enumerate() {
374            assert_eq!(manager.log_level_for(p, None), expected_levels[i]);
375        }
376    }
377
378    #[test]
379    fn filter_node0_warn() {
380        let mut manager = EntityManager::new(Level::Error);
381        manager
382            .add_entity_level_filter(r".*node0", Level::Warn)
383            .unwrap();
384
385        let expected_levels = [Level::Error, Level::Error, Level::Warn, Level::Error];
386
387        for (i, p) in entity_paths().iter().enumerate() {
388            assert_eq!(manager.log_level_for(p, None), expected_levels[i]);
389        }
390    }
391
392    #[test]
393    fn filter_dev_and_node0_info() {
394        let mut manager = EntityManager::new(Level::Error);
395        // The first pattern seen should be highest priority
396        manager
397            .add_entity_level_filter(r".*node0", Level::Warn)
398            .unwrap();
399        manager
400            .add_entity_level_filter(r".*dev.*", Level::Info)
401            .unwrap();
402
403        let expected_levels = [Level::Error, Level::Info, Level::Warn, Level::Info];
404
405        for (i, p) in entity_paths().iter().enumerate() {
406            assert_eq!(manager.log_level_for(p, None), expected_levels[i]);
407        }
408    }
409
410    #[test]
411    fn filter_log_dev_and_node0_info() {
412        let mut manager = EntityManager::new(Level::Error);
413        // The first pattern seen should be highest priority
414        manager
415            .add_entity_level_filter(r".*node0", Level::Info)
416            .unwrap();
417        manager
418            .add_entity_level_filter(r".*dev.*", Level::Trace)
419            .unwrap();
420        manager
421            .add_entity_level_filter(r"top.*", Level::Warn)
422            .unwrap();
423
424        let expected_levels = [Level::Warn, Level::Trace, Level::Info, Level::Trace];
425
426        for (i, p) in entity_paths().iter().enumerate() {
427            assert_eq!(manager.log_level_for(p, None), expected_levels[i]);
428        }
429    }
430
431    #[test]
432    fn ids() {
433        let manager = EntityManager::new(Level::Error);
434        for i in 0..10 {
435            assert_eq!(manager.unique_id(), Id(i + ROOT.0 + 1));
436        }
437    }
438}