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