Skip to main content

diagnostics_hierarchy/serialization/
serialize.rs

1// Copyright 2020 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 crate::{ArrayContent, DiagnosticsHierarchy, ExponentialHistogram, LinearHistogram, Property};
6use base64::engine::Engine as _;
7use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
8use either::Either;
9use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer};
10use std::collections::BTreeSet;
11
12impl<Key> Serialize for DiagnosticsHierarchy<Key>
13where
14    Key: AsRef<str>,
15{
16    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
17        let mut s = serializer.serialize_map(Some(1))?;
18        let name = self.name.clone();
19        s.serialize_entry(&name, &SerializableHierarchyFields { hierarchy: self, moniker: None })?;
20        s.end()
21    }
22}
23
24pub struct SerializableHierarchyFields<'a, Key> {
25    pub hierarchy: &'a DiagnosticsHierarchy<Key>,
26    pub moniker: Option<&'a str>,
27}
28
29fn get_name<'a, K: AsRef<str>>(
30    a: Either<&'a Property<K>, &'a DiagnosticsHierarchy<K>>,
31) -> &'a str {
32    match a {
33        Either::Left(property) => property.name(),
34        Either::Right(node) => &node.name,
35    }
36}
37
38impl<Key> Serialize for SerializableHierarchyFields<'_, Key>
39where
40    Key: AsRef<str>,
41{
42    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
43        let items = self.hierarchy.properties.len() + self.hierarchy.children.len();
44        let mut s = serializer.serialize_map(Some(items))?;
45        let mut it = self
46            .hierarchy
47            .properties
48            .iter()
49            .map(Either::Left)
50            .chain(self.hierarchy.children.iter().map(Either::Right));
51        // From analysis of Inspect files, 99.5% of nodes have =< 16 children.
52        let mut seen_names = if items >= 16 { Some(BTreeSet::new()) } else { None };
53        while let Some(val) = it.next() {
54            let name = get_name(val);
55            let is_duplicate = if let Some(ref mut seen) = seen_names {
56                !seen.insert(name)
57            } else {
58                it.clone().any(|a| get_name(a) == name)
59            };
60
61            if is_duplicate {
62                log::warn!(
63                    item_name:? = name,
64                    emitting_component:? = self.moniker;
65                    "Encountered duplicate names while serializing Inspect"
66                );
67            }
68
69            match val {
70                Either::Left(property) => {
71                    let name = property.name();
72                    match property {
73                        Property::String(_, value) => s.serialize_entry(name, &value)?,
74                        Property::Int(_, value) => s.serialize_entry(name, &value)?,
75                        Property::Uint(_, value) => s.serialize_entry(name, &value)?,
76                        Property::Double(_, value) => {
77                            let value = if value.is_nan()
78                                || (value.is_infinite() && value.is_sign_positive())
79                            {
80                                f64::MAX
81                            } else if value.is_infinite() && value.is_sign_negative() {
82                                f64::MIN
83                            } else {
84                                *value
85                            };
86                            s.serialize_entry(name, &value)?;
87                        }
88                        Property::Bool(_, value) => s.serialize_entry(name, &value)?,
89                        Property::Bytes(_, array) => s.serialize_entry(
90                            name,
91                            &format!("b64:{}", BASE64_STANDARD.encode(array)),
92                        )?,
93                        Property::DoubleArray(_, array) => {
94                            s.serialize_entry(name, &array)?;
95                        }
96                        Property::IntArray(_, array) => {
97                            s.serialize_entry(name, &array)?;
98                        }
99                        Property::UintArray(_, array) => {
100                            s.serialize_entry(name, &array)?;
101                        }
102                        Property::StringList(_, list) => {
103                            s.serialize_entry(name, &list)?;
104                        }
105                    }
106                }
107                Either::Right(child) => s.serialize_entry(
108                    &child.name,
109                    &SerializableHierarchyFields { hierarchy: child, moniker: self.moniker },
110                )?,
111            }
112        }
113        s.end()
114    }
115}
116
117// A condensed histogram has a vec of counts and a vec of corresponding indexes.
118// For serialization, histograms should be condensed if fewer than 50% of the
119// counts are nonzero. We don't worry about decondensing histograms with more
120// than 50% nonzero counts - they'd still be correct, but we shouldn't get them.
121pub(crate) fn maybe_condense_histogram<T>(
122    counts: &[T],
123    indexes: &Option<Vec<usize>>,
124) -> Option<(Vec<T>, Vec<usize>)>
125where
126    T: PartialEq + num_traits::Zero + Copy,
127{
128    if indexes.is_some() {
129        return None;
130    }
131    let mut condensed_counts = vec![];
132    let mut indexes = vec![];
133    let cutoff_len = counts.len() / 2;
134    for (index, count) in counts.iter().enumerate() {
135        if *count != T::zero() {
136            indexes.push(index);
137            condensed_counts.push(*count);
138            if condensed_counts.len() > cutoff_len {
139                return None;
140            }
141        }
142    }
143    Some((condensed_counts, indexes))
144}
145
146macro_rules! impl_serialize_for_array_value {
147    ($($type:ty,)*) => {
148        $(
149            impl Serialize for ArrayContent<$type> {
150                fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
151                    match self {
152                        ArrayContent::LinearHistogram(LinearHistogram {
153                            floor,
154                            step,
155                            counts,
156                            indexes,
157                            size,
158                        }) => {
159                            let condensation = maybe_condense_histogram(counts, indexes);
160                            let (counts, indexes) = match condensation {
161                                None => (counts.to_vec(), indexes.as_ref().map(|v| v.to_vec())),
162                                Some((cc, ci)) => (cc, Some(ci)),
163                            };
164                            LinearHistogram {
165                                floor: *floor,
166                                step: *step,
167                                counts,
168                                indexes,
169                                size: *size,
170                            }.serialize(serializer)
171                        }
172                        ArrayContent::ExponentialHistogram(ExponentialHistogram {
173                            floor,
174                            initial_step,
175                            step_multiplier,
176                            counts,
177                            indexes,
178                            size,
179                        }) => {
180                            let condensation = maybe_condense_histogram(counts, indexes);
181                            let (counts, indexes) = match condensation {
182                                None => (counts.to_vec(), indexes.as_ref().map(|v| v.to_vec())),
183                                Some((cc, ci)) => (cc, Some(ci)),
184                            };
185                            ExponentialHistogram {
186                                floor: *floor,
187                                size: *size,
188                                initial_step: *initial_step,
189                                step_multiplier: *step_multiplier,
190                                counts,
191                                indexes,
192                            }
193                            .serialize(serializer)
194                        }
195                        ArrayContent::Values(values) => {
196                            let mut s = serializer.serialize_seq(Some(values.len()))?;
197                            for value in values {
198                                s.serialize_element(&value)?;
199                            }
200                            s.end()
201                        }
202                    }
203                }
204            }
205        )*
206    }
207}
208
209impl_serialize_for_array_value![i64, u64, f64,];
210
211#[cfg(test)]
212mod tests {
213    use super::*;
214    use crate::{ArrayFormat, hierarchy};
215
216    #[fuchsia::test]
217    fn serialize_json() {
218        let mut hierarchy = test_hierarchy();
219        hierarchy.sort();
220        let expected = expected_json();
221        let result = serde_json::to_string_pretty(&hierarchy).expect("failed to serialize");
222        let parsed_json_expected: serde_json::Value = serde_json::from_str(&expected).unwrap();
223        let parsed_json_result: serde_json::Value = serde_json::from_str(&result).unwrap();
224        assert_eq!(parsed_json_result, parsed_json_expected);
225    }
226
227    #[fuchsia::test]
228    fn serialize_doubles() {
229        let hierarchy = hierarchy! {
230            root: {
231                inf: f64::INFINITY,
232                neg_inf: f64::NEG_INFINITY,
233                nan: f64::NAN,
234            }
235        };
236        let result = serde_json::to_string_pretty(&hierarchy).expect("serialized");
237        assert_eq!(
238            result,
239            r#"{
240  "root": {
241    "inf": 1.7976931348623157e+308,
242    "neg_inf": -1.7976931348623157e+308,
243    "nan": 1.7976931348623157e+308
244  }
245}"#
246        );
247    }
248
249    fn test_hierarchy() -> DiagnosticsHierarchy {
250        DiagnosticsHierarchy::new(
251            "root",
252            vec![
253                Property::UintArray("array".to_string(), ArrayContent::Values(vec![0, 2, 4])),
254                Property::Bool("bool_true".to_string(), true),
255                Property::Bool("bool_false".to_string(), false),
256                Property::StringList(
257                    "string_list".to_string(),
258                    vec!["foo".to_string(), "bar".to_string()],
259                ),
260                Property::StringList("empty_string_list".to_string(), vec![]),
261            ],
262            vec![
263                DiagnosticsHierarchy::new(
264                    "a",
265                    vec![
266                        Property::Double("double".to_string(), 2.5),
267                        Property::DoubleArray(
268                            "histogram".to_string(),
269                            ArrayContent::new(
270                                vec![0.0, 2.0, 4.0, 1.0, 3.0, 4.0],
271                                ArrayFormat::ExponentialHistogram,
272                            )
273                            .unwrap(),
274                        ),
275                        Property::Bytes("bytes".to_string(), vec![5u8, 0xf1, 0xab]),
276                    ],
277                    vec![],
278                ),
279                DiagnosticsHierarchy::new(
280                    "b",
281                    vec![
282                        Property::Int("int".to_string(), -2),
283                        Property::String("string".to_string(), "some value".to_string()),
284                        Property::IntArray(
285                            "histogram".to_string(),
286                            ArrayContent::new(vec![0, 2, 4, 1, 3], ArrayFormat::LinearHistogram)
287                                .unwrap(),
288                        ),
289                        Property::IntArray(
290                            "condensed_histogram".to_string(),
291                            ArrayContent::new(vec![0, 2, 0, 1, 0], ArrayFormat::LinearHistogram)
292                                .unwrap(),
293                        ),
294                    ],
295                    vec![],
296                ),
297            ],
298        )
299    }
300
301    fn expected_json() -> String {
302        r#"{
303  "root": {
304    "array": [
305      0,
306      2,
307      4
308    ],
309    "bool_false": false,
310    "bool_true": true,
311    "empty_string_list": [],
312    "string_list": [
313      "foo",
314      "bar"
315    ],
316    "a": {
317      "bytes": "b64:BfGr",
318      "double": 2.5,
319      "histogram": {
320        "size": 3,
321        "floor": 0.0,
322        "initial_step": 2.0,
323        "step_multiplier": 4.0,
324        "counts": [1.0, 3.0, 4.0]
325      }
326    },
327    "b": {
328      "histogram": {
329        "floor": 0,
330        "step": 2,
331        "counts": [
332          4,
333          1,
334          3
335        ],
336        "size": 3
337      },
338    "condensed_histogram": {
339        "size": 3,
340        "floor": 0,
341        "step": 2,
342        "counts": [
343          1
344        ],
345        "indexes": [
346          1
347        ]
348      },
349      "int": -2,
350      "string": "some value"
351    }
352  }
353}"#
354        .to_string()
355    }
356}