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 })?;
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
107pub(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}