1use super::{Vertex, VertexMetadata};
6use fuchsia_inspect as inspect;
7
8#[derive(Debug)]
10pub struct Digraph {
11 _node: inspect::Node,
12 topology_node: inspect::Node,
13}
14
15#[derive(Debug, Default)]
17pub struct DigraphOpts {}
18
19impl Digraph {
20 pub fn new(parent: &inspect::Node, _options: DigraphOpts) -> Digraph {
22 let node = parent.create_child("fuchsia.inspect.Graph");
23 let topology_node = node.create_child("topology");
24 Digraph { _node: node, topology_node }
25 }
26
27 pub fn add_vertex<VM: VertexMetadata>(
30 &self,
31 id: VM::Id,
32 init_metadata: impl FnOnce(inspect::Node) -> VM,
33 ) -> Vertex<VM> {
34 Vertex::new(id, &self.topology_node, init_metadata)
35 }
36}
37
38#[cfg(test)]
39mod tests {
40 use super::*;
41 use crate::graph::EdgeMetadata;
42 use diagnostics_assertions::assert_data_tree;
43 use fuchsia_inspect as inspect;
44 use fuchsia_inspect::reader::snapshot::Snapshot;
45 use fuchsia_inspect::{Inspector, Property};
46 use inspect_format::{BlockIndex, BlockType};
47 use std::collections::BTreeSet;
48
49 struct BasicMeta {
50 #[allow(dead_code)] meta: inspect::Node,
52 }
53
54 impl VertexMetadata for BasicMeta {
55 type Id = &'static str;
56 type EdgeMeta = BasicMeta;
57 }
58 impl EdgeMetadata for BasicMeta {}
59
60 struct NoOpWithNumericMeta;
61 impl VertexMetadata for NoOpWithNumericMeta {
62 type Id = &'static str;
63 type EdgeMeta = NumericMeta;
64 }
65 impl EdgeMetadata for NoOpWithNumericMeta {}
66
67 struct NoOpMeta;
68 impl VertexMetadata for NoOpMeta {
69 type Id = &'static str;
70 type EdgeMeta = NoOpMeta;
71 }
72 impl EdgeMetadata for NoOpMeta {}
73
74 struct NumericMeta {
75 node: inspect::Node,
76 int: Option<inspect::IntProperty>,
77 uint: Option<inspect::UintProperty>,
78 double: Option<inspect::DoubleProperty>,
79 }
80 impl EdgeMetadata for NumericMeta {}
81
82 impl VertexMetadata for NumericMeta {
83 type Id = &'static str;
84 type EdgeMeta = NoOpMeta;
85 }
86
87 #[fuchsia::test]
88 fn test_simple_graph() {
89 let inspector = inspect::Inspector::default();
90
91 let graph = Digraph::new(inspector.root(), DigraphOpts::default());
93
94 let mut vertex_foo = graph.add_vertex("element-1", |meta| {
96 meta.record_string("name", "foo");
97 meta.record_uint("level", 1u64);
98 BasicMeta { meta }
99 });
100
101 let mut vertex_bar = graph.add_vertex("element-2", |meta| {
102 meta.record_string("name", "bar");
103 meta.record_int("level", 2i64);
104 BasicMeta { meta }
105 });
106
107 let edge_foo_bar = vertex_foo.add_edge(&mut vertex_bar, |meta| {
109 meta.record_string("src", "on");
110 meta.record_string("dst", "off");
111 meta.record_string("type", "passive");
112 BasicMeta { meta }
113 });
114
115 assert_data_tree!(inspector, root: {
116 "fuchsia.inspect.Graph": {
117 "topology": {
118 "element-1": {
119 "meta": {
120 name: "foo",
121 level: 1u64,
122 },
123 "relationships": {
124 "element-2": {
125 "edge_id": edge_foo_bar.id(),
126 "meta": {
127 "type": "passive",
128 src: "on",
129 dst: "off"
130 }
131 }
132 }
133 },
134 "element-2": {
135 "meta": {
136 name: "bar",
137 level: 2i64,
138 },
139 "relationships": {}
140 }
141 }
142 }
143 });
144 }
145
146 #[fuchsia::test]
147 fn test_all_metadata_types_on_nodes() {
148 let inspector = inspect::Inspector::default();
149
150 let graph = Digraph::new(inspector.root(), DigraphOpts::default());
152
153 let mut vertex = graph.add_vertex("test-node", |node| {
155 let int = Some(node.create_int("int_property", 2i64));
156 let uint = Some(node.create_uint("uint_property", 4u64));
157 let double = Some(node.create_double("double_property", 2.5));
158 NumericMeta { node, int, uint, double }
159 });
160
161 assert_data_tree!(inspector, root: {
162 "fuchsia.inspect.Graph": {
163 "topology": {
164 "test-node": {
165 "meta": {
166 double_property: 2.5,
167 int_property: 2i64,
168 uint_property: 4u64,
169 },
170 "relationships": {}
171 },
172 }
173 }
174 });
175
176 vertex.meta().int.as_ref().unwrap().set(1i64);
178 vertex.meta().uint.as_ref().unwrap().set(3u64);
179 vertex.meta().double.as_ref().unwrap().set(4.25);
180
181 vertex.meta().node.record_int("new_one", 123);
183
184 assert_data_tree!(inspector, root: {
185 "fuchsia.inspect.Graph": {
186 "topology": {
187 "test-node": {
188 "meta": {
189 int_property: 1i64,
190 uint_property: 3u64,
191 double_property: 4.25f64,
192 new_one: 123i64,
193 },
194 "relationships": {}
195 },
196 },
197 }
198 });
199
200 vertex.meta().int = None;
202 vertex.meta().uint = None;
203 vertex.meta().double = None;
204
205 assert_data_tree!(inspector, root: {
206 "fuchsia.inspect.Graph": {
207 "topology": {
208 "test-node": {
209 "meta": {
210 new_one: 123i64,
211 },
212 "relationships": {}
213 },
214 }
215 }
216 });
217 }
218
219 #[fuchsia::test]
220 fn test_all_metadata_types_on_edges() {
221 let inspector = inspect::Inspector::default();
222
223 let graph = Digraph::new(inspector.root(), DigraphOpts::default());
225
226 let mut vertex_one = graph.add_vertex("test-node-1", |_| NoOpWithNumericMeta);
228 let mut vertex_two = graph.add_vertex("test-node-2", |_| NoOpWithNumericMeta);
229 let edge = vertex_one.add_edge(&mut vertex_two, |node| {
230 let int = Some(node.create_int("int_property", 2i64));
231 let uint = Some(node.create_uint("uint_property", 4u64));
232 let double = Some(node.create_double("double_property", 2.5));
233 NumericMeta { node, int, uint, double }
234 });
235
236 assert_data_tree!(inspector, root: {
237 "fuchsia.inspect.Graph": {
238 "topology": {
239 "test-node-1": {
240 "relationships": {
241 "test-node-2": {
242 "edge_id": edge.id(),
243 "meta": {
244 int_property: 2i64,
245 uint_property: 4u64,
246 double_property: 2.5,
247 },
248 }
249 }
250 },
251 "test-node-2": {
252 "relationships": {},
253 }
254 }
255 }
256 });
257
258 edge.maybe_update_meta(|meta| {
259 meta.int.as_ref().unwrap().set(1i64);
261 meta.uint.as_ref().unwrap().set(3u64);
262 meta.double.as_ref().unwrap().set(4.25);
263 meta.node.record_int("new_one", 123);
265 });
266
267 assert_data_tree!(inspector, root: {
268 "fuchsia.inspect.Graph": {
269 "topology": {
270 "test-node-1": {
271 "relationships": {
272 "test-node-2": {
273 "edge_id": edge.id(),
274 "meta": {
275 int_property: 1i64,
276 uint_property: 3u64,
277 double_property: 4.25f64,
278 new_one: 123i64,
279 },
280 }
281 }
282 },
283 "test-node-2": {
284 "relationships": {},
285 }
286 }
287 }
288 });
289
290 edge.maybe_update_meta(|meta| {
292 meta.int = None;
293 meta.uint = None;
294 meta.double = None;
295 });
296
297 assert_data_tree!(inspector, root: {
298 "fuchsia.inspect.Graph": {
299 "topology": {
300 "test-node-1": {
301 "relationships": {
302 "test-node-2": {
303 "edge_id": edge.id(),
304 "meta": {
305 new_one: 123i64,
306 }
307 }
308 }
309 },
310 "test-node-2": {
311 "relationships": {},
312 }
313 }
314 }
315 });
316 }
317
318 #[fuchsia::test]
319 fn test_raii_semantics() {
320 let inspector = inspect::Inspector::default();
321 let graph = Digraph::new(inspector.root(), DigraphOpts::default());
322 let mut foo = graph.add_vertex("foo", |meta| {
323 meta.record_bool("hello", true);
324 BasicMeta { meta }
325 });
326 let mut bar = graph.add_vertex("bar", |meta| {
327 meta.record_bool("hello", false);
328 BasicMeta { meta }
329 });
330 let mut baz = graph.add_vertex("baz", |meta| BasicMeta { meta });
331
332 let edge_foo = bar.add_edge(&mut foo, |meta| {
333 meta.record_string("hey", "hi");
334 BasicMeta { meta }
335 });
336 let edge_to_baz = bar.add_edge(&mut baz, |meta| {
337 meta.record_string("good", "bye");
338 BasicMeta { meta }
339 });
340
341 assert_data_tree!(inspector, root: {
342 "fuchsia.inspect.Graph": {
343 "topology": {
344 "foo": {
345 "meta": {
346 hello: true,
347 },
348 "relationships": {},
349 },
350 "bar": {
351 "meta": {
352 hello: false,
353 },
354 "relationships": {
355 "foo": {
356 "edge_id": edge_foo.id(),
357 "meta": {
358 hey: "hi",
359 },
360 },
361 "baz": {
362 "edge_id": edge_to_baz.id(),
363 "meta": {
364 good: "bye",
365 },
366 }
367 }
368 },
369 "baz": {
370 "meta": {},
371 "relationships": {}
372 }
373 }
374 }
375 });
376
377 drop(edge_foo);
379
380 assert_data_tree!(inspector, root: {
381 "fuchsia.inspect.Graph": {
382 "topology": {
383 "foo": {
384 "meta": {
385 hello: true,
386 },
387 "relationships": {},
388 },
389 "bar": {
390 "meta": {
391 hello: false,
392 },
393 "relationships": {
394 "baz": {
395 "edge_id": edge_to_baz.id(),
396 "meta": {
397 good: "bye",
398 },
399 }
400 }
401 },
402 "baz": {
403 "meta": {},
404 "relationships": {}
405 }
406 }
407 }
408 });
409
410 drop(bar);
412
413 assert_data_tree!(inspector, root: {
414 "fuchsia.inspect.Graph": {
415 "topology": {
416 "foo": {
417 "meta": {
418 hello: true,
419 },
420 "relationships": {}
421 },
422 "baz": {
423 "meta": {},
424 "relationships": {}
425 }
426 }
427 }
428 });
429
430 drop(foo);
432 drop(baz);
433
434 assert_data_tree!(inspector, root: {
435 "fuchsia.inspect.Graph": {
436 "topology": {},
437 }
438 });
439 }
440
441 #[fuchsia::test]
442 fn drop_target_semantics() {
443 let inspector = inspect::Inspector::default();
444 let graph = Digraph::new(inspector.root(), DigraphOpts::default());
445 let mut vertex_one = graph.add_vertex("test-node-1", |_| NoOpMeta);
446 let mut vertex_two = graph.add_vertex("test-node-2", |_| NoOpMeta);
447 let edge = vertex_one.add_edge(&mut vertex_two, |_| NoOpMeta);
448 assert_data_tree!(inspector, root: {
449 "fuchsia.inspect.Graph": {
450 "topology": {
451 "test-node-1": {
452 "relationships": {
453 "test-node-2": {
454 "edge_id": edge.id(),
455 }
456 }
457 },
458 "test-node-2": {
459 "relationships": {},
460 }
461 }
462 }
463 });
464
465 drop(vertex_two);
467
468 assert_data_tree!(inspector, root: {
470 "fuchsia.inspect.Graph": {
471 "topology": {
472 "test-node-1": {
473 "relationships": {}
474 }
475 }
476 }
477 });
478 }
479
480 #[fuchsia::test]
481 fn validate_inspect_vmo_on_edge_drop() {
482 let inspector = inspect::Inspector::default();
483 let graph = Digraph::new(inspector.root(), DigraphOpts::default());
484 let mut vertex_a = graph.add_vertex("a", |meta| BasicMeta { meta });
485 let mut vertex_b = graph.add_vertex("b", |meta| BasicMeta { meta });
486
487 let blocks_before = non_free_blocks(&inspector);
488
489 let edge_a_b = vertex_a.add_edge(&mut vertex_b, |meta| {
492 meta.record_string("src", "on");
493 BasicMeta { meta }
494 });
495 drop(edge_a_b);
496
497 let blocks_after = non_free_blocks(&inspector);
498 assert_eq!(
499 blocks_after.symmetric_difference(&blocks_before).collect::<BTreeSet<_>>(),
500 BTreeSet::new()
501 );
502 }
503
504 #[fuchsia::test]
505 fn validate_inspect_vmo_on_dest_vertex_drop() {
506 let inspector = inspect::Inspector::default();
507 let graph = Digraph::new(inspector.root(), DigraphOpts::default());
508 let mut vertex_a = graph.add_vertex("a", |meta| BasicMeta { meta });
509
510 let initial_blocks = non_free_blocks(&inspector);
511
512 let mut vertex_b = graph.add_vertex("b", |meta| BasicMeta { meta });
513 let _edge_a_b = vertex_a.add_edge(&mut vertex_b, |meta| {
514 meta.record_string("src", "on");
515 BasicMeta { meta }
516 });
517
518 drop(vertex_b);
520
521 let after_blocks = non_free_blocks(&inspector);
522 assert_eq!(
525 after_blocks.symmetric_difference(&initial_blocks).collect::<BTreeSet<_>>(),
526 BTreeSet::new()
527 );
528 }
529
530 #[fuchsia::test]
531 fn validate_inspect_vmo_on_source_vertex_drop() {
532 let inspector = inspect::Inspector::default();
533 let graph = Digraph::new(inspector.root(), DigraphOpts::default());
534
535 let initial_blocks = non_free_blocks(&inspector);
536 let mut vertex_a = graph.add_vertex("a", |meta| BasicMeta { meta });
537
538 let before_blocks = non_free_blocks(&inspector);
539 let mut vertex_b = graph.add_vertex("b", |meta| BasicMeta { meta });
540 let vertex_b_blocks = non_free_blocks(&inspector)
541 .difference(&before_blocks)
542 .cloned()
543 .collect::<BTreeSet<_>>();
544
545 let _edge_a_b = vertex_a.add_edge(&mut vertex_b, |meta| {
546 meta.record_string("src", "on");
547 BasicMeta { meta }
548 });
549
550 drop(vertex_a);
554
555 let mut expected = initial_blocks.union(&vertex_b_blocks).cloned().collect::<BTreeSet<_>>();
556 expected.insert((BlockIndex::new(14), BlockType::StringReference));
558 expected.insert((BlockIndex::new(16), BlockType::StringReference));
559 let after_blocks = non_free_blocks(&inspector);
560 assert_eq!(after_blocks, expected);
561 }
562
563 fn non_free_blocks(inspector: &Inspector) -> BTreeSet<(BlockIndex, BlockType)> {
564 let snapshot = Snapshot::try_from(inspector).unwrap();
565 snapshot
566 .scan()
567 .filter(|block| block.block_type() != Some(BlockType::Free))
568 .map(|b| (b.index(), b.block_type().unwrap()))
569 .collect::<BTreeSet<_>>()
570 }
571}