Skip to main content

fuchsia_inspect_bench_utils/
lib.rs

1// Copyright 2022 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 fidl_fuchsia_inspect::{TreeMarker, TreeProxy};
6use fuchsia_async::{Scope, Task};
7use fuchsia_criterion::{FuchsiaCriterion, criterion};
8use fuchsia_inspect::Inspector;
9use fuchsia_inspect::hierarchy::{DiagnosticsHierarchy, DiagnosticsHierarchyGetter};
10use futures::{FutureExt, future};
11use inspect_runtime::TreeServerSendPreference;
12use inspect_runtime::service::handle_request_stream;
13use rand::distr::Uniform;
14use rand::rngs::StdRng;
15use rand::{Rng, SeedableRng};
16use std::borrow::Cow;
17use std::collections::{HashSet, VecDeque};
18use std::future::IntoFuture;
19use std::time::Duration;
20
21/// Spawns a tree server for the test purposes.
22pub fn spawn_server(
23    inspector: Inspector,
24) -> Result<(TreeProxy, Task<Result<(), anyhow::Error>>), anyhow::Error> {
25    let (tree, request_stream) = fidl::endpoints::create_proxy_and_stream::<TreeMarker>();
26    let scope = Scope::new_with_name("tree_server_scope");
27    let tree_server_task = scope.compute(handle_request_stream(
28        inspector,
29        TreeServerSendPreference::default(),
30        request_stream,
31        scope.new_child_with_name("tree_server"),
32    ));
33    Ok((tree, Task::spawn(future::join(tree_server_task, scope.into_future()).map(|(r, _)| r))))
34}
35
36pub struct CriterionConfig {
37    pub warm_up_time: Duration,
38    pub measurement_time: Duration,
39    pub sample_size: usize,
40}
41
42impl Default for CriterionConfig {
43    fn default() -> CriterionConfig {
44        CriterionConfig {
45            warm_up_time: Duration::from_millis(1),
46            measurement_time: Duration::from_millis(100),
47            sample_size: 10,
48        }
49    }
50}
51
52pub fn configured_criterion(config: CriterionConfig) -> FuchsiaCriterion {
53    let CriterionConfig { warm_up_time, measurement_time, sample_size } = config;
54    let mut c = FuchsiaCriterion::default();
55    let internal_c: &mut criterion::Criterion = &mut c;
56    *internal_c = std::mem::take(internal_c)
57        .warm_up_time(warm_up_time)
58        .measurement_time(measurement_time)
59        // We must reduce the sample size from the default of 100, otherwise
60        // Criterion will sometimes override the 1ms + 500ms suggested times
61        // and run for much longer.
62        .sample_size(sample_size);
63    c
64}
65
66pub struct InspectHierarchyGenerator<R: SeedableRng + Rng> {
67    rng: R,
68    pub inspector: Inspector,
69}
70
71impl<R: SeedableRng + Rng> InspectHierarchyGenerator<R> {
72    pub fn new(rng: R, inspector: Inspector) -> Self {
73        Self { rng, inspector }
74    }
75
76    /// Generates prufer sequence by sampling uniformly from the rng.
77    fn generate_prufer_sequence(&mut self, n: usize) -> Vec<usize> {
78        (0..n - 2).map(|_| self.rng.sample(Uniform::new(0, n).unwrap())).collect()
79    }
80
81    /// Generate hierarchy from prufer sequence
82    fn generate_hierarchy_from_prufer(&mut self, sequence: &[usize]) {
83        let n = sequence.len() + 2;
84        let mut degree = vec![1; n];
85
86        for node in sequence {
87            degree[*node] += 1;
88        }
89
90        let mut ptr = 0;
91        while ptr < n && degree[ptr] != 1 {
92            ptr += 1;
93        }
94        let mut leaf = ptr;
95
96        let mut edges: Vec<Vec<usize>> = vec![Vec::new(); n];
97        for v in sequence {
98            edges[leaf].push(*v);
99            edges[*v].push(leaf);
100
101            degree[*v] -= 1;
102            if degree[*v] == 1 && *v < ptr {
103                leaf = *v;
104            } else {
105                ptr += 1;
106                while ptr < n && degree[ptr] != 1 {
107                    ptr += 1;
108                }
109                leaf = ptr;
110            }
111        }
112        edges[leaf].push(n - 1);
113        edges[n - 1].push(leaf);
114
115        // Now we have the tree in undirectred form
116        // we will construct the inspect hierarchy assuming 0 to be
117        // inspector root
118        let mut unprocessed_nodes = VecDeque::new();
119        unprocessed_nodes.push_back((0, self.inspector.root().clone_weak()));
120
121        let mut processed_edges: HashSet<(usize, usize)> = HashSet::with_capacity(n - 1);
122
123        while let Some((u, node)) = unprocessed_nodes.pop_front() {
124            for v in &edges[u] {
125                // Already processed v -> u
126                if processed_edges.contains(&(*v, u)) {
127                    continue;
128                }
129                // Processed edge from u -> v
130                processed_edges.insert((u, *v));
131
132                let child_node = node.create_child(format!("child_{}", *v));
133
134                // Add IntProperty to the child node
135                child_node.record_uint("value", *v as u64);
136                unprocessed_nodes.push_back((*v, child_node.clone_weak()));
137                node.record(child_node);
138            }
139        }
140    }
141
142    /// Generate random inspect hierarchy with n nodes.
143    pub fn generate_hierarchy(&mut self, n: usize) {
144        let prufer_sequence = self.generate_prufer_sequence(n);
145        self.generate_hierarchy_from_prufer(&prufer_sequence);
146    }
147}
148
149impl<R: SeedableRng + Rng> DiagnosticsHierarchyGetter<String> for InspectHierarchyGenerator<R> {
150    async fn get_diagnostics_hierarchy<'a>(&'a self) -> Cow<'_, DiagnosticsHierarchy>
151    where
152        String: 'a,
153    {
154        self.inspector.get_diagnostics_hierarchy().await
155    }
156}
157
158/// [size] must be >1.
159pub fn filled_hierarchy_generator(seed: u64, size: usize) -> InspectHierarchyGenerator<StdRng> {
160    assert!(size > 1, "Generator requires a size greater than 1");
161    let inspector = Inspector::default();
162    let mut hierarchy_generator =
163        InspectHierarchyGenerator::new(StdRng::seed_from_u64(seed), inspector);
164    hierarchy_generator.generate_hierarchy(size);
165    hierarchy_generator
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171    use diagnostics_assertions::assert_json_diff;
172
173    #[fuchsia::test]
174    async fn random_generated_hierarchy_is_reproducible() {
175        let hierarchy_generator = filled_hierarchy_generator(0, 10);
176        assert_json_diff!(
177        hierarchy_generator,
178        root: {
179          child_6: {
180            value: 6,
181            child_5: {
182              value: 5,
183              child_3: {
184                value: 3
185              },
186              child_9: {
187                value: 9
188              }
189            }
190          },
191          child_8: {
192            value: 8,
193            child_1: {
194              value: 1
195            },
196            child_7: {
197              value: 7,
198              child_2: {
199                value: 2
200              },
201              child_4: {
202                value: 4
203              }
204            }
205          }
206        });
207    }
208}