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