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