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.add_entity_level_filter(".*arb.*", log::Level::Trace);
281    /// ```
282    pub fn add_entity_level_filter(
283        &mut self,
284        regex_str: &str,
285        level: crate::log::Level,
286    ) -> Result<(), TrackConfigError> {
287        match Regex::new(regex_str) {
288            Ok(regex) => self.regex_to_entity_level.push((regex, level)),
289            Err(e) => {
290                return Err(TrackConfigError(format!(
291                    "Failed to parse regex {regex_str}:\n{e}\n"
292                )));
293            }
294        }
295        Ok(())
296    }
297
298    /// Add a filter for ports that should have monitoring enabled
299    /// with the specified window size in ticks.
300    ///
301    /// # Example
302    ///
303    /// ```rust
304    /// use gwr_track::tracker::EntityManager;
305    /// let mut manager = EntityManager::new(log::Level::Warn);
306    /// manager.set_monitor_window_size_for(".*fabric::ingress.*", 250);
307    /// ```
308    pub fn set_monitor_window_size_for(
309        &mut self,
310        regex_str: &str,
311        window_size_ticks: u64,
312    ) -> Result<(), TrackConfigError> {
313        match Regex::new(regex_str) {
314            Ok(regex) => self
315                .regex_to_enable_monitors_for
316                .push((regex, window_size_ticks)),
317            Err(e) => {
318                return Err(TrackConfigError(format!(
319                    "Failed to parse regex {regex_str}:\n{e}\n"
320                )));
321            }
322        }
323        Ok(())
324    }
325}
326
327#[cfg(test)]
328mod tests {
329    use log::Level;
330
331    use super::*;
332
333    fn entity_paths() -> Vec<&'static str> {
334        vec!["top", "top::dev", "top::dev::node0", "top::dev::node1"]
335    }
336
337    #[test]
338    fn no_filters() {
339        let manager = EntityManager::new(Level::Error);
340
341        for p in entity_paths() {
342            assert_eq!(manager.log_level_for(p, None), Level::Error);
343        }
344    }
345
346    #[test]
347    fn filter_dev_trace() {
348        let mut manager = EntityManager::new(Level::Error);
349        manager
350            .add_entity_level_filter(r".*dev.*", Level::Trace)
351            .unwrap();
352
353        let expected_levels = [Level::Error, Level::Trace, Level::Trace, Level::Trace];
354
355        for (i, p) in entity_paths().iter().enumerate() {
356            assert_eq!(manager.log_level_for(p, None), expected_levels[i]);
357        }
358    }
359
360    #[test]
361    fn filter_node0_error() {
362        let mut manager = EntityManager::new(Level::Warn);
363        manager
364            .add_entity_level_filter(r".*node0", Level::Error)
365            .unwrap();
366
367        let expected_levels = [Level::Warn, Level::Warn, Level::Error, Level::Warn];
368
369        for (i, p) in entity_paths().iter().enumerate() {
370            assert_eq!(manager.log_level_for(p, None), expected_levels[i]);
371        }
372    }
373
374    #[test]
375    fn filter_node0_warn() {
376        let mut manager = EntityManager::new(Level::Error);
377        manager
378            .add_entity_level_filter(r".*node0", Level::Warn)
379            .unwrap();
380
381        let expected_levels = [Level::Error, Level::Error, Level::Warn, Level::Error];
382
383        for (i, p) in entity_paths().iter().enumerate() {
384            assert_eq!(manager.log_level_for(p, None), expected_levels[i]);
385        }
386    }
387
388    #[test]
389    fn filter_dev_and_node0_info() {
390        let mut manager = EntityManager::new(Level::Error);
391        // The first pattern seen should be highest priority
392        manager
393            .add_entity_level_filter(r".*node0", Level::Warn)
394            .unwrap();
395        manager
396            .add_entity_level_filter(r".*dev.*", Level::Info)
397            .unwrap();
398
399        let expected_levels = [Level::Error, Level::Info, Level::Warn, Level::Info];
400
401        for (i, p) in entity_paths().iter().enumerate() {
402            assert_eq!(manager.log_level_for(p, None), expected_levels[i]);
403        }
404    }
405
406    #[test]
407    fn filter_log_dev_and_node0_info() {
408        let mut manager = EntityManager::new(Level::Error);
409        // The first pattern seen should be highest priority
410        manager
411            .add_entity_level_filter(r".*node0", Level::Info)
412            .unwrap();
413        manager
414            .add_entity_level_filter(r".*dev.*", Level::Trace)
415            .unwrap();
416        manager
417            .add_entity_level_filter(r"top.*", Level::Warn)
418            .unwrap();
419
420        let expected_levels = [Level::Warn, Level::Trace, Level::Info, Level::Trace];
421
422        for (i, p) in entity_paths().iter().enumerate() {
423            assert_eq!(manager.log_level_for(p, None), expected_levels[i]);
424        }
425    }
426
427    #[test]
428    fn ids() {
429        let manager = EntityManager::new(Level::Error);
430        for i in 0..10 {
431            assert_eq!(manager.unique_id(), Id(i + ROOT.0 + 1));
432        }
433    }
434}