fuchsia_inspect_contrib/graph/
events.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use super::types::*;
6use super::MetadataValue;
7use crate::nodes::BoundedListNode;
8use fuchsia_inspect::{self as inspect, InspectTypeReparentable};
9use fuchsia_sync::Mutex;
10use std::collections::VecDeque;
11use std::marker::PhantomData;
12use std::ops::Deref;
13use std::sync::{Arc, Weak};
14
15pub struct MetaEventNode(inspect::Node);
16
17impl MetaEventNode {
18    pub fn new(parent: &inspect::Node) -> Self {
19        Self(parent.create_child("meta"))
20    }
21
22    fn take_node(self) -> inspect::Node {
23        self.0
24    }
25}
26
27impl Deref for MetaEventNode {
28    type Target = inspect::Node;
29    fn deref(&self) -> &Self::Target {
30        &self.0
31    }
32}
33
34// Data container for stats tracking of events in GraphEventsTracker.inner.buffer.
35#[derive(Debug)]
36pub struct ShadowEvent {
37    time: zx::BootInstant,
38}
39
40// Aggregate for stats tracking of events in GraphEventsTracker.inner.buffer.
41#[derive(Debug)]
42pub struct ShadowBuffer {
43    buffer: VecDeque<ShadowEvent>,
44    capacity: usize,
45}
46
47impl ShadowBuffer {
48    pub fn new(capacity: usize) -> Self {
49        Self { buffer: VecDeque::with_capacity(capacity), capacity }
50    }
51    // Add to internal vector, first trimming if it exceeds capacity.
52    pub fn add_entry(&mut self, shadow_event: ShadowEvent) {
53        if self.buffer.len() >= self.capacity {
54            self.buffer.pop_front();
55        }
56        self.buffer.push_back(shadow_event);
57    }
58    // Report history duration.
59    pub fn history_duration(&self) -> zx::BootDuration {
60        if self.buffer.len() < 2 {
61            return zx::BootDuration::ZERO;
62        }
63        self.buffer.back().unwrap().time - self.buffer.front().unwrap().time
64    }
65    // Report whether capacity reached.
66    pub fn at_capacity(&self) -> bool {
67        self.buffer.len() == self.capacity
68    }
69}
70
71// Simple container to share a mutex.
72#[derive(Debug)]
73pub struct Inner {
74    // List of events in chronological order.
75    buffer: BoundedListNode,
76    // Matching list of shadow data for each event in the write-only buffer.
77    // Digraph reads `shadow` to create lazy stats over `buffer`.
78    shadow: ShadowBuffer,
79}
80
81#[derive(Debug)]
82pub struct GraphEventsTracker {
83    // Write-only event list, and its "shadow" metadata for computing stats.
84    inner: Arc<Mutex<Inner>>,
85}
86
87impl GraphEventsTracker {
88    pub fn new(list_node: inspect::Node, max_events: usize) -> Self {
89        Self {
90            inner: Arc::new(Mutex::new(Inner {
91                buffer: BoundedListNode::new(list_node, max_events),
92                shadow: ShadowBuffer::new(max_events),
93            })),
94        }
95    }
96
97    pub fn for_vertex<I>(&self) -> GraphObjectEventTracker<VertexMarker<I>> {
98        GraphObjectEventTracker { inner: self.inner.clone(), _phantom: PhantomData }
99    }
100
101    // Allows closure-capture of weak reference for lazy node pattern.
102    pub fn history_stats_accessor(&self) -> HistoryStatsAccessor {
103        HistoryStatsAccessor(Arc::downgrade(&self.inner))
104    }
105}
106
107#[derive(Clone)]
108pub struct HistoryStatsAccessor(Weak<Mutex<Inner>>);
109
110impl HistoryStatsAccessor {
111    pub fn history_duration(&self) -> zx::BootDuration {
112        self.0
113            .upgrade()
114            .map_or(zx::BootDuration::ZERO, |inner| inner.lock().shadow.history_duration())
115    }
116    pub fn at_capacity(&self) -> bool {
117        self.0.upgrade().is_some_and(|inner| inner.lock().shadow.at_capacity())
118    }
119}
120
121#[derive(Debug)]
122pub struct GraphObjectEventTracker<T> {
123    inner: Arc<Mutex<Inner>>,
124    _phantom: PhantomData<T>,
125}
126
127impl<I> GraphObjectEventTracker<VertexMarker<I>>
128where
129    I: VertexId,
130{
131    pub fn for_edge(&self) -> GraphObjectEventTracker<EdgeMarker> {
132        GraphObjectEventTracker { inner: self.inner.clone(), _phantom: PhantomData }
133    }
134
135    pub fn record_added(&self, id: &I, meta_event_node: MetaEventNode) {
136        let meta_event_node = meta_event_node.take_node();
137        let instant = zx::BootInstant::get();
138        let mut inner = self.inner.lock();
139        inner.buffer.add_entry(|node| {
140            node.record_int("@time", instant.into_nanos());
141            node.record_string("event", "add_vertex");
142            node.record_string("vertex_id", id.get_id().as_ref());
143            let _ = meta_event_node.reparent(node);
144            node.record(meta_event_node);
145        });
146        inner.shadow.add_entry(ShadowEvent { time: instant });
147    }
148
149    pub fn record_removed(&self, id: &str) {
150        let instant = zx::BootInstant::get();
151        let mut inner = self.inner.lock();
152        inner.buffer.add_entry(|node| {
153            node.record_int("@time", instant.into_nanos());
154            node.record_string("event", "remove_vertex");
155            node.record_string("vertex_id", id.get_id().as_ref());
156        });
157        inner.shadow.add_entry(ShadowEvent { time: instant });
158    }
159}
160
161impl GraphObjectEventTracker<EdgeMarker> {
162    pub fn record_added(&self, from: &str, to: &str, id: u64, meta_event_node: MetaEventNode) {
163        let meta_event_node = meta_event_node.take_node();
164        let instant = zx::BootInstant::get();
165        let mut inner = self.inner.lock();
166        inner.buffer.add_entry(|node| {
167            node.record_int("@time", instant.into_nanos());
168            node.record_string("event", "add_edge");
169            node.record_string("from", from);
170            node.record_string("to", to);
171            node.record_uint("edge_id", id);
172            let _ = meta_event_node.reparent(node);
173            node.record(meta_event_node);
174        });
175        inner.shadow.add_entry(ShadowEvent { time: instant });
176    }
177
178    pub fn record_removed(&self, id: u64) {
179        let instant = zx::BootInstant::get();
180        let mut inner = self.inner.lock();
181        inner.buffer.add_entry(|node| {
182            node.record_int("@time", instant.into_nanos());
183            node.record_string("event", "remove_edge");
184            node.record_uint("edge_id", id);
185        });
186        inner.shadow.add_entry(ShadowEvent { time: instant });
187    }
188}
189
190impl<T> GraphObjectEventTracker<T>
191where
192    T: GraphObject,
193{
194    pub fn metadata_updated(&self, id: &T::Id, key: &str, value: &MetadataValue<'_>) {
195        let instant = zx::BootInstant::get();
196        let mut inner = self.inner.lock();
197        inner.buffer.add_entry(|node| {
198            node.record_int("@time", instant.into_nanos());
199            node.record_string("event", "update_key");
200            node.record_string("key", key);
201            value.record_inspect(node, "update");
202            T::write_to_node(node, id);
203        });
204        inner.shadow.add_entry(ShadowEvent { time: instant });
205    }
206
207    pub fn metadata_dropped(&self, id: &T::Id, key: &str) {
208        let instant = zx::BootInstant::get();
209        let mut inner = self.inner.lock();
210        inner.buffer.add_entry(|node| {
211            node.record_int("@time", instant.into_nanos());
212            node.record_string("event", "drop_key");
213            node.record_string("key", key);
214            T::write_to_node(node, id);
215        });
216        inner.shadow.add_entry(ShadowEvent { time: instant });
217    }
218}
219
220#[cfg(test)]
221mod tests {
222    use super::*;
223    use diagnostics_assertions::{assert_data_tree, AnyProperty};
224    use fuchsia_inspect::DiagnosticsHierarchyGetter;
225
226    impl GraphEventsTracker {
227        fn shadow_buffer_len(&self) -> usize {
228            self.inner.lock().shadow.buffer.len()
229        }
230        fn shadow_time_of(&self, index: usize) -> zx::BootInstant {
231            self.inner.lock().shadow.buffer.get(index).unwrap().time
232        }
233    }
234
235    #[test]
236    fn tracker_starts_empty() {
237        let inspector = inspect::Inspector::default();
238        let tracker = GraphEventsTracker::new(inspector.root().create_child("events"), 1);
239        assert_data_tree!(inspector, root: {
240            events: {}
241        });
242        assert_eq!(tracker.shadow_buffer_len(), 0);
243    }
244
245    fn get_time(inspector: &inspect::Inspector, path: &[&str]) -> zx::BootInstant {
246        let instant = inspector
247            .get_diagnostics_hierarchy()
248            .get_property_by_path(path)
249            .and_then(|p| p.int())
250            .unwrap();
251        zx::BootInstant::from_nanos(instant)
252    }
253
254    #[fuchsia::test]
255    fn vertex_add() {
256        let inspector = inspect::Inspector::default();
257        let tracker = GraphEventsTracker::new(inspector.root().create_child("events"), 1);
258        let vertex_tracker = tracker.for_vertex::<u64>();
259        let meta_event_node = MetaEventNode::new(inspector.root());
260        meta_event_node.record_bool("placeholder", true);
261        vertex_tracker.record_added(&123, meta_event_node);
262        assert_data_tree!(inspector, root: {
263            events: {
264                "0": {
265                    "@time": AnyProperty,
266                    event: "add_vertex",
267                    vertex_id: "123",
268                    meta: {
269                        placeholder: true,
270                    }
271                }
272            }
273        });
274        assert_eq!(tracker.shadow_buffer_len(), 1);
275        assert_eq!(get_time(&inspector, &["events", "0", "@time"]), tracker.shadow_time_of(0));
276    }
277
278    #[fuchsia::test]
279    fn vertex_remove() {
280        let inspector = inspect::Inspector::default();
281        let tracker = GraphEventsTracker::new(inspector.root().create_child("events"), 1);
282        let vertex_tracker = tracker.for_vertex::<u64>();
283        vertex_tracker.record_removed("20");
284        assert_data_tree!(inspector, root: {
285            events: {
286                "0": {
287                    "@time": AnyProperty,
288                    event: "remove_vertex",
289                    vertex_id: "20",
290                }
291            }
292        });
293        assert_eq!(tracker.shadow_buffer_len(), 1);
294        assert_eq!(get_time(&inspector, &["events", "0", "@time"]), tracker.shadow_time_of(0));
295    }
296
297    #[fuchsia::test]
298    fn vertex_metadata_update() {
299        let inspector = inspect::Inspector::default();
300        let tracker = GraphEventsTracker::new(inspector.root().create_child("events"), 1);
301        let vertex_tracker = tracker.for_vertex::<u64>();
302        vertex_tracker.metadata_updated(&10, "foo", &MetadataValue::Uint(3));
303        assert_data_tree!(inspector, root: {
304            events: {
305                "0": {
306                    "@time": AnyProperty,
307                    event: "update_key",
308                    vertex_id: "10",
309                    key: "foo",
310                    update: 3u64,
311                }
312            }
313        });
314        assert_eq!(tracker.shadow_buffer_len(), 1);
315        assert_eq!(get_time(&inspector, &["events", "0", "@time"]), tracker.shadow_time_of(0));
316    }
317
318    #[fuchsia::test]
319    fn vertex_metadata_drop() {
320        let inspector = inspect::Inspector::default();
321        let tracker = GraphEventsTracker::new(inspector.root().create_child("events"), 2);
322        let vertex_tracker = tracker.for_vertex::<u64>();
323        vertex_tracker.metadata_updated(&10, "foo", &MetadataValue::Uint(3));
324        vertex_tracker.metadata_dropped(&10, "foo");
325        assert_data_tree!(inspector, root: {
326            events: {
327                "0": {
328                    "@time": AnyProperty,
329                    event: "update_key",
330                    vertex_id: "10",
331                    key: "foo",
332                    update: 3u64,
333                },
334                "1": {
335                    "@time": AnyProperty,
336                    event: "drop_key",
337                    vertex_id: "10",
338                    key: "foo",
339                }
340            }
341        });
342        assert_eq!(tracker.shadow_buffer_len(), 2);
343        assert_eq!(get_time(&inspector, &["events", "0", "@time"]), tracker.shadow_time_of(0));
344        assert_eq!(get_time(&inspector, &["events", "1", "@time"]), tracker.shadow_time_of(1));
345    }
346
347    #[fuchsia::test]
348    fn edge_add() {
349        let inspector = inspect::Inspector::default();
350        let tracker = GraphEventsTracker::new(inspector.root().create_child("events"), 1);
351        let vertex_tracker = tracker.for_vertex::<u64>();
352        let edge_tracker = vertex_tracker.for_edge();
353        let meta_event_node = MetaEventNode::new(inspector.root());
354        meta_event_node.record_bool("placeholder", true);
355        edge_tracker.record_added("src", "dst", 10, meta_event_node);
356        assert_data_tree!(inspector, root: {
357            events: {
358                "0": {
359                    "@time": AnyProperty,
360                    event: "add_edge",
361                    from: "src",
362                    to: "dst",
363                    edge_id: 10u64,
364                    meta: {
365                        placeholder: true,
366                    }
367                }
368            }
369        });
370        assert_eq!(tracker.shadow_buffer_len(), 1);
371        assert_eq!(get_time(&inspector, &["events", "0", "@time"]), tracker.shadow_time_of(0));
372    }
373
374    #[fuchsia::test]
375    fn edge_remove() {
376        let inspector = inspect::Inspector::default();
377        let tracker = GraphEventsTracker::new(inspector.root().create_child("events"), 1);
378        let vertex_tracker = tracker.for_vertex::<u64>();
379        let edge_tracker = vertex_tracker.for_edge();
380        edge_tracker.record_removed(20);
381        assert_data_tree!(inspector, root: {
382            events: {
383                "0": {
384                    "@time": AnyProperty,
385                    event: "remove_edge",
386                    edge_id: 20u64,
387                }
388            }
389        });
390        assert_eq!(tracker.shadow_buffer_len(), 1);
391        assert_eq!(get_time(&inspector, &["events", "0", "@time"]), tracker.shadow_time_of(0));
392    }
393
394    #[fuchsia::test]
395    fn edge_metadata_update() {
396        let inspector = inspect::Inspector::default();
397        let tracker = GraphEventsTracker::new(inspector.root().create_child("events"), 1);
398        let vertex_tracker = tracker.for_vertex::<u64>();
399        let edge_tracker = vertex_tracker.for_edge();
400        edge_tracker.metadata_updated(&10, "foo", &MetadataValue::Uint(3));
401        assert_data_tree!(inspector, root: {
402            events: {
403                "0": {
404                    "@time": AnyProperty,
405                    event: "update_key",
406                    edge_id: 10u64,
407                    key: "foo",
408                    update: 3u64,
409                }
410            }
411        });
412        assert_eq!(tracker.shadow_buffer_len(), 1);
413        assert_eq!(get_time(&inspector, &["events", "0", "@time"]), tracker.shadow_time_of(0));
414    }
415
416    #[fuchsia::test]
417    fn circular_buffer_semantics() {
418        let inspector = inspect::Inspector::default();
419        let tracker = GraphEventsTracker::new(inspector.root().create_child("events"), 2);
420        let vertex_tracker = tracker.for_vertex::<u64>();
421        vertex_tracker.record_removed("20");
422        vertex_tracker.record_removed("30");
423        vertex_tracker.record_removed("40");
424        assert_data_tree!(inspector, root: {
425            events: {
426                "1": {
427                    "@time": AnyProperty,
428                    event: "remove_vertex",
429                    vertex_id: "30",
430                },
431                "2": {
432                    "@time": AnyProperty,
433                    event: "remove_vertex",
434                    vertex_id: "40",
435                }
436            }
437        });
438        assert_eq!(tracker.shadow_buffer_len(), 2);
439        assert_eq!(get_time(&inspector, &["events", "1", "@time"]), tracker.shadow_time_of(0));
440        assert_eq!(get_time(&inspector, &["events", "2", "@time"]), tracker.shadow_time_of(1));
441    }
442}