1use 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 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 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
116pub(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}