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