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>>(
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 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
117pub(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}