fuchsia_inspect_contrib/graph/
metadata.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::events::{GraphObjectEventTracker, MetaEventNode};
6use super::types::{EdgeMarker, GraphObject, VertexMarker};
7use super::VertexId;
8use fuchsia_inspect::{self as inspect, ArrayProperty, Property};
9use std::borrow::Cow;
10use std::collections::BTreeMap;
11use std::ops::Deref;
12
13/// An enum encoding all the possible metadata value types and contents.
14#[derive(Debug)]
15pub enum MetadataValue<'a> {
16    Int(i64),
17    Uint(u64),
18    Double(f64),
19    Str(Cow<'a, str>),
20    Bool(bool),
21    IntVec(Vec<i64>),
22    UintVec(Vec<u64>),
23    DoubleVec(Vec<f64>),
24}
25
26impl MetadataValue<'_> {
27    pub(crate) fn record_inspect(&self, node: &inspect::Node, key: &str) {
28        match self {
29            Self::Int(value) => node.record_int(key, *value),
30            Self::Uint(value) => node.record_uint(key, *value),
31            Self::Double(value) => node.record_double(key, *value),
32            Self::Str(value) => node.record_string(key, value.as_ref()),
33            Self::Bool(value) => node.record_bool(key, *value),
34            Self::IntVec(value) => {
35                node.atomic_update(|node| {
36                    let prop = node.create_int_array(key, value.len());
37                    for (idx, v) in value.iter().enumerate() {
38                        prop.set(idx, *v);
39                    }
40                    node.record(prop);
41                });
42            }
43            Self::UintVec(value) => {
44                node.atomic_update(|node| {
45                    let prop = node.create_uint_array(key, value.len());
46                    for (idx, v) in value.iter().enumerate() {
47                        prop.set(idx, *v);
48                    }
49                    node.record(prop);
50                });
51            }
52            Self::DoubleVec(value) => {
53                node.atomic_update(|node| {
54                    let prop = node.create_double_array(key, value.len());
55                    for (idx, v) in value.iter().enumerate() {
56                        prop.set(idx, *v);
57                    }
58                    node.record(prop);
59                });
60            }
61        }
62    }
63}
64
65impl<'a, T> From<&'a T> for MetadataValue<'a>
66where
67    T: VertexId,
68{
69    fn from(value: &'a T) -> MetadataValue<'a> {
70        Self::Str(value.get_id())
71    }
72}
73
74macro_rules! impl_from_for_metadata_value {
75    ($([($($type:ty),*), $name:ident, $cast_type:ty]),*) => {
76        $($(
77            impl From<$type> for MetadataValue<'_> {
78                fn from(value: $type) -> MetadataValue<'static> {
79                    MetadataValue::$name(value as $cast_type)
80                }
81            }
82        )*)*
83    };
84}
85
86impl_from_for_metadata_value!(
87    [(i8, i16, i32, i64), Int, i64],
88    [(u8, u16, u32, u64), Uint, u64],
89    [(f32, f64), Double, f64]
90);
91
92impl From<bool> for MetadataValue<'_> {
93    fn from(value: bool) -> MetadataValue<'static> {
94        MetadataValue::Bool(value)
95    }
96}
97
98impl<'a> From<&'a str> for MetadataValue<'a> {
99    fn from(value: &'a str) -> MetadataValue<'a> {
100        MetadataValue::Str(Cow::Borrowed(value))
101    }
102}
103
104impl From<String> for MetadataValue<'_> {
105    fn from(value: String) -> MetadataValue<'static> {
106        MetadataValue::Str(Cow::Owned(value))
107    }
108}
109
110impl<'a> From<Cow<'a, str>> for MetadataValue<'a> {
111    fn from(value: Cow<'a, str>) -> MetadataValue<'a> {
112        MetadataValue::Str(value)
113    }
114}
115
116impl<'a> From<Vec<i64>> for MetadataValue<'a> {
117    fn from(value: Vec<i64>) -> MetadataValue<'a> {
118        MetadataValue::IntVec(value)
119    }
120}
121
122impl<'a> From<Vec<u8>> for MetadataValue<'a> {
123    fn from(value: Vec<u8>) -> MetadataValue<'a> {
124        MetadataValue::UintVec(value.into_iter().map(|v| v as u64).collect())
125    }
126}
127
128impl<'a> From<Vec<u64>> for MetadataValue<'a> {
129    fn from(value: Vec<u64>) -> MetadataValue<'a> {
130        MetadataValue::UintVec(value)
131    }
132}
133
134impl<'a> From<Vec<f64>> for MetadataValue<'a> {
135    fn from(value: Vec<f64>) -> MetadataValue<'a> {
136        MetadataValue::DoubleVec(value)
137    }
138}
139
140/// A metadata item used to initialize metadata key value pairs of nodes and edges.
141pub struct Metadata<'a> {
142    /// The key of the metadata field.
143    pub(crate) key: Cow<'a, str>,
144    /// The value of the metadata field.
145    inner: InnerMetadata<'a>,
146}
147
148impl<'a> Metadata<'a> {
149    /// Create a new metadata item with the given `key` and `value`.
150    pub fn new(key: impl Into<Cow<'a, str>>, value: impl Into<MetadataValue<'a>>) -> Self {
151        Self {
152            key: key.into(),
153            inner: InnerMetadata::Value { value: value.into(), track_events: false },
154        }
155    }
156
157    pub fn nested(
158        key: impl Into<Cow<'a, str>>,
159        value: impl IntoIterator<Item = Metadata<'a>> + 'a,
160    ) -> Self {
161        Self { key: key.into(), inner: InnerMetadata::Nested(Box::new(value.into_iter())) }
162    }
163
164    /// Require that changes to this metadata node must be tracked. To support tracking events
165    /// about changes the graph must be configured to support tracking events.
166    /// This is a no-op if called on a nested node. It only works on leaf properties.
167    pub fn track_events(mut self) -> Self {
168        match &mut self.inner {
169            InnerMetadata::Value { ref mut track_events, .. } => *track_events = true,
170            InnerMetadata::Nested(_) => {}
171        }
172        self
173    }
174}
175
176enum InnerMetadata<'a> {
177    Value {
178        value: MetadataValue<'a>,
179        // Whether or not changes to this metadata field should be tracked.
180        track_events: bool,
181    },
182    Nested(Box<dyn Iterator<Item = Metadata<'a>> + 'a>),
183}
184
185#[derive(Debug)]
186pub struct VertexGraphMetadata<I>
187where
188    I: VertexId,
189{
190    inner: GraphMetadata<VertexMarker<I>>,
191}
192
193impl<I: VertexId> VertexGraphMetadata<I> {
194    /// Set the value of the metadata field at `key` or insert a new one if not
195    /// present.
196    pub fn set<'a, 'b>(
197        &mut self,
198        key: impl Into<Cow<'a, str>>,
199        value: impl Into<MetadataValue<'b>>,
200    ) {
201        self.inner.set(key, value, None);
202    }
203
204    pub fn set_and_track<'a, 'b>(
205        &mut self,
206        key: impl Into<Cow<'a, str>>,
207        value: impl Into<MetadataValue<'b>>,
208    ) {
209        self.inner.set(key, value, Some(true));
210    }
211
212    /// Remove the metadata field at `key`.
213    pub fn remove(&mut self, key: &str) {
214        self.inner.remove(key);
215    }
216
217    pub fn remove_and_track(&mut self, key: &str) {
218        self.inner.remove_and_track(key);
219    }
220
221    pub(crate) fn new<'a>(
222        parent: &inspect::Node,
223        id: I,
224        initial_metadata: impl Iterator<Item = Metadata<'a>>,
225        events_tracker: Option<GraphObjectEventTracker<VertexMarker<I>>>,
226    ) -> Self {
227        let (inner, meta_event_node) =
228            GraphMetadata::new(parent, id, initial_metadata, events_tracker);
229        if let Some(ref events_tracker) = inner.events_tracker {
230            events_tracker.record_added(&inner.id, meta_event_node);
231        }
232        Self { inner }
233    }
234
235    pub(crate) fn events_tracker(&self) -> Option<&GraphObjectEventTracker<VertexMarker<I>>> {
236        self.inner.events_tracker.as_ref()
237    }
238
239    pub(crate) fn id(&self) -> &I {
240        &self.inner.id
241    }
242}
243
244#[derive(Debug)]
245pub struct EdgeGraphMetadata {
246    inner: GraphMetadata<EdgeMarker>,
247}
248
249impl EdgeGraphMetadata {
250    /// Set the value of the metadata field at `key` or insert a new one if not
251    /// present.
252    pub fn set<'a, 'b>(
253        &mut self,
254        key: impl Into<Cow<'a, str>>,
255        value: impl Into<MetadataValue<'b>>,
256    ) {
257        self.inner.set(key, value, None);
258    }
259
260    /// Remove the metadata field at `key`.
261    pub fn remove(&mut self, key: &str) {
262        self.inner.remove(key);
263    }
264
265    pub(crate) fn new<'a>(
266        parent: &inspect::Node,
267        id: u64,
268        initial_metadata: impl Iterator<Item = Metadata<'a>>,
269        events_tracker: Option<GraphObjectEventTracker<EdgeMarker>>,
270        from_id: &str,
271        to_id: &str,
272    ) -> Self {
273        let (inner, meta_event_node) =
274            GraphMetadata::new(parent, id, initial_metadata, events_tracker);
275        if let Some(ref events_tracker) = inner.events_tracker {
276            events_tracker.record_added(from_id, to_id, id, meta_event_node);
277        }
278        Self { inner }
279    }
280
281    pub(crate) fn events_tracker(&self) -> Option<&GraphObjectEventTracker<EdgeMarker>> {
282        self.inner.events_tracker.as_ref()
283    }
284
285    pub(crate) fn id(&self) -> u64 {
286        self.inner.id
287    }
288
289    pub(crate) fn noop(id: <EdgeMarker as GraphObject>::Id) -> EdgeGraphMetadata {
290        Self {
291            inner: GraphMetadata {
292                id,
293                map: BTreeMap::default(),
294                events_tracker: None,
295                node: inspect::Node::default(),
296            },
297        }
298    }
299}
300
301#[derive(Debug)]
302#[allow(dead_code)] // TODO(https://fxbug.dev/338660036)
303enum MetadataProperty {
304    Int(inspect::IntProperty),
305    Uint(inspect::UintProperty),
306    Double(inspect::DoubleProperty),
307    Str(inspect::StringProperty),
308    Bool(inspect::BoolProperty),
309    IntVec(inspect::IntArrayProperty),
310    UintVec(inspect::UintArrayProperty),
311    DoubleVec(inspect::DoubleArrayProperty),
312}
313
314/// The metadata of a vertex or edge in the graph.
315#[derive(Debug)]
316struct GraphMetadata<T: GraphObject> {
317    id: T::Id,
318    map: BTreeMap<String, (MetadataProperty, bool)>,
319    events_tracker: Option<GraphObjectEventTracker<T>>,
320    node: inspect::Node,
321}
322
323impl<T> GraphMetadata<T>
324where
325    T: GraphObject,
326{
327    fn new<'a>(
328        parent: &inspect::Node,
329        id: T::Id,
330        initial_metadata: impl Iterator<Item = Metadata<'a>>,
331        events_tracker: Option<GraphObjectEventTracker<T>>,
332    ) -> (Self, MetaEventNode) {
333        let node = parent.create_child("meta");
334        let meta_event_node = MetaEventNode::new(parent);
335
336        let mut map = BTreeMap::default();
337        node.atomic_update(|node| {
338            for Metadata { key, inner } in initial_metadata.into_iter() {
339                match inner {
340                    InnerMetadata::Nested(_) => {
341                        Self::insert_nested_to_map(
342                            node,
343                            meta_event_node.deref(),
344                            &mut vec![],
345                            &mut map,
346                            Metadata { key, inner },
347                        );
348                    }
349                    InnerMetadata::Value { value, track_events } => {
350                        let key = key.to_string();
351                        if events_tracker.is_some() && track_events {
352                            value.record_inspect(&meta_event_node, &key);
353                        }
354                        Self::insert_to_map(node, &mut map, &[key.into()], value, track_events)
355                    }
356                }
357            }
358        });
359        (Self { id, node, map, events_tracker }, meta_event_node)
360    }
361
362    fn set<'a, 'b>(
363        &mut self,
364        key: impl Into<Cow<'a, str>>,
365        value: impl Into<MetadataValue<'b>>,
366        maybe_track: Option<bool>,
367    ) {
368        let key: Cow<'a, str> = key.into();
369        // Sometimes, dynamically-created properties need tracking.
370        if let Some(choice) = maybe_track {
371            if let Some((_, ref mut track_events)) = self.map.get_mut(key.as_ref()) {
372                if *track_events != choice {
373                    *track_events = choice;
374                }
375            }
376        }
377        let value = value.into();
378
379        // Turn this log line on to understand if setting a property had issues.
380        log::debug!("trying to set key={:?} to value={:?}", &key, &value);
381        match (self.map.get(key.as_ref()), value) {
382            (
383                Some((MetadataProperty::Int(property), track_events)),
384                value @ MetadataValue::Int(x),
385            ) => {
386                property.set(x);
387                if *track_events {
388                    self.log_update_key(key.as_ref(), &value)
389                }
390            }
391            (
392                Some((MetadataProperty::Uint(property), track_events)),
393                value @ MetadataValue::Uint(x),
394            ) => {
395                property.set(x);
396                if *track_events {
397                    self.log_update_key(key.as_ref(), &value)
398                }
399            }
400            (
401                Some((MetadataProperty::Double(property), track_events)),
402                value @ MetadataValue::Double(x),
403            ) => {
404                property.set(x);
405                if *track_events {
406                    self.log_update_key(key.as_ref(), &value)
407                }
408            }
409            (
410                Some((MetadataProperty::Bool(property), track_events)),
411                value @ MetadataValue::Bool(x),
412            ) => {
413                property.set(x);
414                if *track_events {
415                    self.log_update_key(key.as_ref(), &value)
416                }
417            }
418            (
419                Some((MetadataProperty::Str(property), track_events)),
420                ref value @ MetadataValue::Str(ref x),
421            ) => {
422                property.set(x);
423                if *track_events {
424                    self.log_update_key(key.as_ref(), value)
425                }
426            }
427            (Some((MetadataProperty::IntVec(_), _)), MetadataValue::IntVec(_)) => {
428                // TODO(https://fxbug.dev/338660036): implement later.
429            }
430            (Some((MetadataProperty::UintVec(_), _)), MetadataValue::UintVec(_)) => {
431                // TODO(https://fxbug.dev/338660036): implement later.
432            }
433            (Some((MetadataProperty::DoubleVec(_), _)), MetadataValue::DoubleVec(_)) => {
434                // TODO(https://fxbug.dev/338660036): implement later.
435            }
436            (Some((_, track_events)), value) => {
437                let track_events = *track_events; // drops ownership
438                if track_events {
439                    self.log_update_key(key.as_ref(), &value)
440                }
441                Self::insert_to_map(&self.node, &mut self.map, &[key], value, track_events);
442            }
443            (None, value) => {
444                let track_events = maybe_track.unwrap_or(false);
445                if track_events {
446                    self.log_update_key(key.as_ref(), &value)
447                }
448                Self::insert_to_map(&self.node, &mut self.map, &[key], value, track_events);
449            }
450        }
451    }
452
453    fn remove(&mut self, key: &str) {
454        self.map.remove(key);
455    }
456
457    fn remove_and_track(&mut self, key: &str) {
458        if self.map.remove(key).is_some() {
459            if let Some(events_tracker) = &self.events_tracker {
460                events_tracker.metadata_dropped(&self.id, key);
461            }
462        }
463    }
464
465    fn log_update_key(&self, key: &str, value: &MetadataValue<'_>) {
466        if let Some(events_tracker) = &self.events_tracker {
467            events_tracker.metadata_updated(&self.id, key, value);
468        }
469    }
470
471    fn insert_to_map(
472        node: &inspect::Node,
473        map: &mut BTreeMap<String, (MetadataProperty, bool)>,
474        key_path: &[Cow<'_, str>],
475        value: MetadataValue<'_>,
476        track_events: bool,
477    ) {
478        let node_key = key_path.last().unwrap().as_ref();
479        let map_key = key_path.join("/");
480        match value {
481            MetadataValue::Int(value) => {
482                let property = node.create_int(node_key, value);
483                map.insert(map_key, (MetadataProperty::Int(property), track_events));
484            }
485            MetadataValue::Uint(value) => {
486                let property = node.create_uint(node_key, value);
487                map.insert(map_key, (MetadataProperty::Uint(property), track_events));
488            }
489            MetadataValue::Double(value) => {
490                let property = node.create_double(node_key, value);
491                map.insert(map_key, (MetadataProperty::Double(property), track_events));
492            }
493            MetadataValue::Str(value) => {
494                let property = node.create_string(node_key, value);
495                map.insert(map_key, (MetadataProperty::Str(property), track_events));
496            }
497            MetadataValue::Bool(value) => {
498                let property = node.create_bool(node_key, value);
499                map.insert(map_key, (MetadataProperty::Bool(property), track_events));
500            }
501            MetadataValue::IntVec(value) => {
502                let property = node.atomic_update(|node| {
503                    let property = node.create_int_array(node_key, value.len());
504                    for (idx, v) in value.into_iter().enumerate() {
505                        property.set(idx, v);
506                    }
507                    property
508                });
509                map.insert(map_key, (MetadataProperty::IntVec(property), track_events));
510            }
511            MetadataValue::UintVec(value) => {
512                let property = node.atomic_update(|node| {
513                    let property = node.create_uint_array(node_key, value.len());
514                    for (idx, v) in value.into_iter().enumerate() {
515                        property.set(idx, v);
516                    }
517                    property
518                });
519                map.insert(map_key, (MetadataProperty::UintVec(property), track_events));
520            }
521            MetadataValue::DoubleVec(value) => {
522                let property = node.atomic_update(|node| {
523                    let property = node.create_double_array(node_key, value.len());
524                    for (idx, v) in value.into_iter().enumerate() {
525                        property.set(idx, v);
526                    }
527                    property
528                });
529                map.insert(map_key, (MetadataProperty::DoubleVec(property), track_events));
530            }
531        }
532    }
533
534    fn insert_nested_to_map<'a>(
535        parent: &inspect::Node,
536        parent_event_node: &inspect::Node,
537        key_path: &mut Vec<Cow<'a, str>>,
538        map: &mut BTreeMap<String, (MetadataProperty, bool)>,
539        metadata: Metadata<'a>,
540    ) -> bool {
541        let Metadata { key, inner: InnerMetadata::Nested(children) } = metadata else {
542            unreachable!("We can only reach this function with nested nodes.");
543        };
544        let meta_node = parent.create_child(key.as_ref());
545        let event_node = parent_event_node.create_child(key.as_ref());
546
547        key_path.push(key);
548
549        let mut recorded_events = false;
550        for Metadata { inner: child_inner, key: child_key } in children {
551            match child_inner {
552                InnerMetadata::Nested(_) => {
553                    recorded_events |= Self::insert_nested_to_map(
554                        &meta_node,
555                        &event_node,
556                        key_path,
557                        map,
558                        Metadata { inner: child_inner, key: child_key },
559                    );
560                }
561                InnerMetadata::Value { value, track_events } => {
562                    if track_events {
563                        recorded_events = true;
564                        value.record_inspect(&event_node, &child_key);
565                    }
566                    key_path.push(child_key);
567                    Self::insert_to_map(&meta_node, map, key_path, value, track_events);
568                    key_path.pop();
569                }
570            }
571        }
572
573        key_path.pop();
574
575        // Tie the lifetime of the nodes to that of the parent. If there were no events recorded on
576        // this node, we can safely drop the event_node.
577        parent.record(meta_node);
578        if recorded_events {
579            parent_event_node.record(event_node);
580        }
581
582        recorded_events
583    }
584}