blob: bbe945df617b4352ee82a21f37cec2b00fd9a1c1 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
diagnostics_data::{InspectData, InspectHandleName},
diagnostics_hierarchy::{
ArrayContent, DiagnosticsHierarchy, ExponentialHistogram, LinearHistogram, Property,
},
either::Either,
nom::HexDisplay,
num_traits::{Bounded, Zero},
std::{
fmt,
ops::{Add, AddAssign, Mul, MulAssign},
},
};
const INDENT: usize = 2;
const HEX_DISPLAY_CHUNK_SIZE: usize = 16;
pub fn format<W>(w: &mut W, path: &str, diagnostics_hierarchy: DiagnosticsHierarchy) -> fmt::Result
where
W: fmt::Write,
{
writeln!(w, "{}:", path)?;
output_hierarchy(w, &diagnostics_hierarchy, 1)
}
pub fn output_schema<W>(w: &mut W, schema: &InspectData) -> fmt::Result
where
W: fmt::Write,
{
writeln!(w, "{}:", schema.moniker)?;
writeln!(w, " metadata:")?;
if let Some(errors) = &schema.metadata.errors {
writeln!(w, " errors = {}", errors.join(", "))?;
}
if let Some(name) = &schema.metadata.name {
match name {
InspectHandleName::Filename(f) => {
writeln!(w, " filename = {}", f)?;
}
InspectHandleName::Name(n) => {
writeln!(w, " name = {}", n)?;
}
}
}
write!(w, " component_url = ")?;
match &schema.metadata.component_url {
Some(url) => writeln!(w, "{}", url)?,
None => writeln!(w, "null")?,
}
writeln!(w, " timestamp = {}", schema.metadata.timestamp)?;
match &schema.payload {
Some(hierarchy) => {
writeln!(w, " payload:")?;
output_hierarchy(w, &hierarchy, 2)
}
None => writeln!(w, " payload: null"),
}
}
fn output_hierarchy<W>(
w: &mut W,
diagnostics_hierarchy: &DiagnosticsHierarchy,
indent: usize,
) -> fmt::Result
where
W: fmt::Write,
{
let name_indent = " ".repeat(INDENT * indent);
let value_indent = " ".repeat(INDENT * (indent + 1));
let array_broken_line_indent = " ".repeat(INDENT * (indent + 2));
writeln!(w, "{}{}:", name_indent, diagnostics_hierarchy.name)?;
for property in &diagnostics_hierarchy.properties {
match property {
Property::String(name, value) => writeln!(w, "{}{} = {}", value_indent, name, value)?,
Property::Int(name, value) => writeln!(w, "{}{} = {}", value_indent, name, value)?,
Property::Uint(name, value) => writeln!(w, "{}{} = {}", value_indent, name, value)?,
Property::Double(name, value) => {
writeln!(w, "{}{} = {:.6}", value_indent, name, value)?
}
Property::Bytes(name, array) => {
let byte_str = array.to_hex(HEX_DISPLAY_CHUNK_SIZE);
writeln!(w, "{}{} = Binary:\n{}", value_indent, name, byte_str.trim())?;
}
Property::Bool(name, value) => writeln!(w, "{}{} = {}", value_indent, name, value)?,
Property::IntArray(name, array) => output_array(w, &value_indent, &name, &array)?,
Property::UintArray(name, array) => output_array(w, &value_indent, &name, &array)?,
Property::DoubleArray(name, array) => output_array(w, &value_indent, &name, &array)?,
Property::StringList(name, list) => {
let max_line_length = 100;
let length_of_brackets = 2;
let length_of_comma_space = 2;
let total_len = list
.iter()
.fold(length_of_brackets, |acc, v| acc + v.len() + length_of_comma_space);
if total_len < max_line_length {
writeln!(w, "{}{} = {:?}", value_indent, name, list)?;
} else {
writeln!(w, "{}{} = [", value_indent, name)?;
for v in list {
writeln!(w, r#"{}"{}","#, array_broken_line_indent, v)?;
}
writeln!(w, "{}]", value_indent)?;
}
}
}
}
for child in &diagnostics_hierarchy.children {
output_hierarchy(w, child, indent + 1)?;
}
Ok(())
}
trait FromUsize {
fn from_usize(n: usize) -> Self;
}
impl FromUsize for i64 {
fn from_usize(n: usize) -> Self {
i64::try_from(n).unwrap()
}
}
impl FromUsize for u64 {
fn from_usize(n: usize) -> Self {
u64::try_from(n).unwrap()
}
}
impl FromUsize for f64 {
fn from_usize(n: usize) -> Self {
u64::try_from(n).unwrap() as f64
}
}
trait ExponentialBucketBound {
fn bound(floor: Self, initial_step: Self, step_multiplier: Self, index: usize) -> Self;
}
impl ExponentialBucketBound for i64 {
fn bound(floor: Self, initial_step: Self, step_multiplier: Self, index: usize) -> Self {
floor + initial_step * i64::pow(step_multiplier, index as u32)
}
}
impl ExponentialBucketBound for u64 {
fn bound(floor: Self, initial_step: Self, step_multiplier: Self, index: usize) -> Self {
floor + initial_step * u64::pow(step_multiplier, index as u32)
}
}
impl ExponentialBucketBound for f64 {
fn bound(floor: Self, initial_step: Self, step_multiplier: Self, index: usize) -> Self {
floor + initial_step * f64::powi(step_multiplier, (index as u64) as i32)
}
}
struct ExponentialBucketBoundsArgs<T> {
floor: T,
initial_step: T,
step_multiplier: T,
index: usize,
size: usize,
}
fn exponential_bucket_bounds<T>(args: ExponentialBucketBoundsArgs<T>) -> (T, T)
where
T: Bounded + Add<Output = T> + ExponentialBucketBound + Copy,
{
let ExponentialBucketBoundsArgs { floor, initial_step, step_multiplier, index, size } = args;
match index {
0 => (T::min_value(), floor),
1 if size == 2 => (floor, T::max_value()),
1 => (floor, floor + initial_step),
index if index == size - 1 => {
(T::bound(floor, initial_step, step_multiplier, index - 2), T::max_value())
}
_ => (
T::bound(floor, initial_step, step_multiplier, index - 2),
T::bound(floor, initial_step, step_multiplier, index - 1),
),
}
}
struct LinearBucketBoundsArgs<T> {
floor: T,
step: T,
index: usize,
size: usize,
}
fn linear_bucket_bounds<T>(args: LinearBucketBoundsArgs<T>) -> (T, T)
where
T: Bounded + Add<Output = T> + Mul<Output = T> + FromUsize + Copy,
{
let LinearBucketBoundsArgs { floor, step, index, size } = args;
match index {
0 => (T::min_value(), floor),
index if index == size - 1 => (floor + step * T::from_usize(index - 1), T::max_value()),
_ => (floor + step * T::from_usize(index - 1), floor + step * T::from_usize(index)),
}
}
fn output_histogram<T, F, W>(
w: &mut W,
indent: &str,
name: &str,
histogram_type: &str,
counts: &[T],
indexes: &Option<Vec<usize>>,
size: usize,
bound_calculator: F,
) -> fmt::Result
where
W: fmt::Write,
T: NumberFormat + fmt::Display + PartialOrd + Zero,
F: Fn(usize) -> (T, T),
{
let value_indent = format!("{}{}", indent, " ".repeat(INDENT));
write!(
w,
"{indent}{name}:\n\
{value_indent}type = {histogram_type}\n\
{value_indent}size = {size}\n\
{value_indent}buckets = ["
)?;
let mut nonzero_counts = match indexes {
None => Either::Left(counts.iter().enumerate().filter(|(_, count)| **count > T::zero())),
Some(indexes) => Either::Right(
indexes
.iter()
.zip(counts)
.filter(|(_, count)| **count > T::zero())
.map(|(u, t)| (*u, t)),
),
}
.peekable();
while let Some((index, count)) = nonzero_counts.next() {
let (low_bound, high_bound) = bound_calculator(index);
write!(w, "[{},{})={}", low_bound.format(), high_bound.format(), count)?;
if nonzero_counts.peek().is_some() {
write!(w, ", ")?;
}
}
write!(w, "]\n")
}
fn output_array<T, W>(
w: &mut W,
value_indent: &str,
name: &str,
array: &ArrayContent<T>,
) -> fmt::Result
where
W: fmt::Write,
T: AddAssign
+ MulAssign
+ Copy
+ Add<Output = T>
+ fmt::Display
+ NumberFormat
+ Bounded
+ Mul<Output = T>
+ ExponentialBucketBound
+ FromUsize
+ PartialOrd
+ Zero,
{
match array {
ArrayContent::Values(values) => {
write!(w, "{value_indent}{name} = [")?;
for (i, value) in values.iter().enumerate() {
write!(w, "{value}")?;
if i < values.len() - 1 {
w.write_str(", ")?;
}
}
writeln!(w, "]")
}
ArrayContent::LinearHistogram(LinearHistogram { floor, step, counts, indexes, size }) => {
let bucket_bounder = |index| {
linear_bucket_bounds::<T>(LinearBucketBoundsArgs {
floor: *floor,
step: *step,
index,
size: *size,
})
};
output_histogram(
w,
value_indent,
name,
"linear",
counts,
indexes,
*size,
bucket_bounder,
)
}
ArrayContent::ExponentialHistogram(ExponentialHistogram {
floor,
initial_step,
step_multiplier,
counts,
indexes,
size,
}) => {
let bucket_bounder = |index| {
exponential_bucket_bounds::<T>(ExponentialBucketBoundsArgs {
floor: *floor,
initial_step: *initial_step,
step_multiplier: *step_multiplier,
index,
size: *size,
})
};
output_histogram(
w,
value_indent,
name,
"exponential",
counts,
indexes,
*size,
bucket_bounder,
)
}
}
}
trait NumberFormat {
fn format(&self) -> String;
}
impl NumberFormat for i64 {
fn format(&self) -> String {
match *self {
i64::MAX => "<max>".to_string(),
std::i64::MIN => "<min>".to_string(),
x => format!("{}", x),
}
}
}
impl NumberFormat for u64 {
fn format(&self) -> String {
match *self {
u64::MAX => "<max>".to_string(),
x => format!("{}", x),
}
}
}
impl NumberFormat for usize {
fn format(&self) -> String {
match *self {
std::usize::MAX => "<max>".to_string(),
x => format!("{}", x),
}
}
}
impl NumberFormat for f64 {
fn format(&self) -> String {
if *self == std::f64::MAX || *self == std::f64::INFINITY {
"inf".to_string()
} else if *self == std::f64::MIN || *self == std::f64::NEG_INFINITY {
"-inf".to_string()
} else {
format!("{}", self)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use test_case::test_case;
#[fuchsia::test]
fn test_array_line_breaks() {
let h = DiagnosticsHierarchy {
name: "test".into(),
properties: vec![Property::StringList(
"short_array".to_owned(),
vec!["short".into(), "array".into()],
)],
children: vec![],
missing: vec![],
};
let mut buf = String::new();
output_hierarchy(&mut buf, &h, 2).unwrap();
let expected = r#" test:
short_array = ["short", "array"]
"#;
assert_eq!(expected, buf);
// joined up would be 104 characters
let long_array = vec![
"12345678".into(),
"12345678".into(),
"12345678".into(),
"12345678".into(),
"12345678".into(),
"12345678".into(),
"12345678".into(),
"12345678".into(),
"12345678".into(),
"12345678".into(),
"12345678".into(),
"12345678".into(),
"12345678".into(),
];
let h = DiagnosticsHierarchy {
name: "test".into(),
properties: vec![Property::StringList("long_array".to_owned(), long_array)],
children: vec![],
missing: vec![],
};
let mut buf = String::new();
output_hierarchy(&mut buf, &h, 2).unwrap();
let expected = r#" test:
long_array = [
"12345678",
"12345678",
"12345678",
"12345678",
"12345678",
"12345678",
"12345678",
"12345678",
"12345678",
"12345678",
"12345678",
"12345678",
"12345678",
]
"#;
assert_eq!(expected, buf);
}
#[test_case(LinearBucketBoundsArgs { floor: -10, step: 2, index: 0, size: 4 }, (i64::MIN, -10))]
#[test_case(LinearBucketBoundsArgs { floor: -10, step: 2, index: 1, size: 4 }, (-10, -8))]
#[test_case(LinearBucketBoundsArgs { floor: -10, step: 2, index: 2, size: 4 }, (-8, -6))]
#[test_case(LinearBucketBoundsArgs { floor: -10, step: 2, index: 3, size: 4 }, (-6, i64::MAX))]
#[fuchsia::test]
fn test_linear_bucket_bounds_i64(args: LinearBucketBoundsArgs<i64>, bounds: (i64, i64)) {
// floor, step, index, size
assert_eq!(linear_bucket_bounds(args), bounds);
}
// u64 min is 0, so the underflow bucket may be weird
#[test_case(LinearBucketBoundsArgs { floor: 0, step: 2, index: 0, size: 4 }, (0, 0))]
#[test_case(LinearBucketBoundsArgs { floor: 0, step: 2, index: 1, size: 4 }, (0, 2))]
#[test_case(LinearBucketBoundsArgs { floor: 0, step: 2, index: 2, size: 4 }, (2, 4))]
#[test_case(LinearBucketBoundsArgs { floor: 0, step: 2, index: 3, size: 4 }, (4, u64::MAX))]
#[test_case(LinearBucketBoundsArgs { floor: 1, step: 2, index: 0, size: 4 }, (0, 1))]
#[test_case(LinearBucketBoundsArgs { floor: 1, step: 2, index: 1, size: 4 }, (1, 3))]
#[test_case(LinearBucketBoundsArgs { floor: 1, step: 2, index: 2, size: 4 }, (3, 5))]
#[test_case(LinearBucketBoundsArgs { floor: 1, step: 2, index: 3, size: 4 }, (5, u64::MAX))]
#[fuchsia::test]
fn test_linear_bucket_bounds_u64(args: LinearBucketBoundsArgs<u64>, bounds: (u64, u64)) {
assert_eq!(linear_bucket_bounds(args), bounds);
}
#[test_case(
LinearBucketBoundsArgs { floor: -0.5, step: 0.5, index: 0, size: 4 },
(f64::MIN, -0.5)
)]
#[test_case(LinearBucketBoundsArgs { floor: -0.5, step: 0.5, index: 1, size: 4 }, (-0.5, 0.0))]
#[test_case(LinearBucketBoundsArgs { floor: -0.5, step: 0.5, index: 2, size: 4 }, (0.0, 0.5))]
#[test_case(
LinearBucketBoundsArgs { floor: -0.5, step: 0.5, index: 3, size: 4 },
(0.5, f64::MAX)
)]
#[fuchsia::test]
fn test_linear_bucket_bounds_f64(args: LinearBucketBoundsArgs<f64>, bounds: (f64, f64)) {
assert_eq!(linear_bucket_bounds(args), bounds);
}
// Check logic for few-bucket histograms. 2-bucket shouldn't occur, but let's treat it
// well anyway. (Using i64)
#[test_case(LinearBucketBoundsArgs { floor: -10, step: 2, index: 0, size: 3 }, (i64::MIN, -10))]
#[test_case(LinearBucketBoundsArgs { floor: -10, step: 2, index: 1, size: 3 }, (-10, -8))]
#[test_case(LinearBucketBoundsArgs { floor: -10, step: 2, index: 2, size: 3 }, (-8, i64::MAX))]
#[test_case(LinearBucketBoundsArgs { floor: -10, step: 2, index: 0, size: 2 }, (i64::MIN, -10))]
#[test_case(LinearBucketBoundsArgs { floor: -10, step: 2, index: 1, size: 2 }, (-10, i64::MAX))]
#[fuchsia::test]
fn test_linear_small_bucket_bounds(args: LinearBucketBoundsArgs<i64>, bounds: (i64, i64)) {
// floor, step, index, size
assert_eq!(linear_bucket_bounds(args), bounds);
}
// Test cases for i64 exponential
#[test_case(
ExponentialBucketBoundsArgs {
floor: -10,
initial_step: 2,
step_multiplier: 3,
index: 0,
size: 5 },
(i64::MIN, -10)
)]
#[test_case(
ExponentialBucketBoundsArgs {
floor: -10,
initial_step: 2,
step_multiplier: 3,
index: 1,
size: 5 },
(-10, -8)
)]
#[test_case(
ExponentialBucketBoundsArgs {
floor: -10,
initial_step: 2,
step_multiplier: 3,
index: 2,
size: 5
},
(-8, -4)
)]
#[test_case(
ExponentialBucketBoundsArgs {
floor: -10,
initial_step: 2,
step_multiplier: 3,
index: 3,
size: 5
},
(-4, 8)
)]
#[test_case(
ExponentialBucketBoundsArgs {
floor: -10,
initial_step: 2,
step_multiplier: 3,
index: 4,
size: 5
},
(8, i64::MAX)
)]
#[fuchsia::test]
fn test_exponential_bucket_bounds_i64(
args: ExponentialBucketBoundsArgs<i64>,
bounds: (i64, i64),
) {
assert_eq!(exponential_bucket_bounds(args), bounds);
}
// Test cases for u64 exponential
#[test_case(
ExponentialBucketBoundsArgs {
floor: 0,
initial_step: 2,
step_multiplier: 3,
index: 0,
size: 5
},
(0, 0)
)]
#[test_case(
ExponentialBucketBoundsArgs {
floor: 0,
initial_step: 2,
step_multiplier: 3,
index: 1,
size: 5
},
(0, 2)
)]
#[test_case(
ExponentialBucketBoundsArgs {
floor: 0,
initial_step: 2,
step_multiplier: 3,
index: 2,
size: 5
},
(2, 6)
)]
#[test_case(
ExponentialBucketBoundsArgs {
floor: 0,
initial_step: 2,
step_multiplier: 3,
index: 3,
size: 5
},
(6, 18)
)]
#[test_case(
ExponentialBucketBoundsArgs {
floor: 0,
initial_step: 2,
step_multiplier: 3,
index: 4,
size: 5 },
(18, u64::MAX)
)]
#[test_case(
ExponentialBucketBoundsArgs {
floor: 1,
initial_step: 2,
step_multiplier: 3,
index: 0,
size: 5
},
(0, 1)
)]
#[test_case(
ExponentialBucketBoundsArgs {
floor: 1,
initial_step: 2,
step_multiplier: 3,
index: 1,
size: 5
},
(1, 3)
)]
#[test_case(
ExponentialBucketBoundsArgs {
floor: 1,
initial_step: 2,
step_multiplier: 3,
index: 2,
size: 5
},
(3, 7)
)]
#[test_case(
ExponentialBucketBoundsArgs {
floor: 1,
initial_step: 2,
step_multiplier: 3,
index: 3,
size: 5
},
(7, 19)
)]
#[test_case(
ExponentialBucketBoundsArgs {
floor: 1,
initial_step: 2,
step_multiplier: 3,
index: 4,
size: 5
},
(19, u64::MAX)
)]
#[fuchsia::test]
fn test_exponential_bucket_bounds_u64(
args: ExponentialBucketBoundsArgs<u64>,
bounds: (u64, u64),
) {
assert_eq!(exponential_bucket_bounds(args), bounds);
}
// Test cases for f64 exponential
#[test_case(
ExponentialBucketBoundsArgs {
floor: -0.5,
initial_step: 0.5,
step_multiplier: 3.0,
index: 0,
size: 5 },
(f64::MIN, -0.5)
)]
#[test_case(
ExponentialBucketBoundsArgs {
floor: -0.5,
initial_step: 0.5,
step_multiplier: 3.0,
index: 1,
size: 5 },
(-0.5, 0.0)
)]
#[test_case(
ExponentialBucketBoundsArgs {
floor: -0.5,
initial_step: 0.5,
step_multiplier: 3.0,
index: 2,
size: 5 },
(0.0, 1.0)
)]
#[test_case(
ExponentialBucketBoundsArgs {
floor: -0.5,
initial_step: 0.5,
step_multiplier: 3.0,
index: 3,
size: 5 },
(1.0, 4.0)
)]
#[test_case(
ExponentialBucketBoundsArgs {
floor: -0.5,
initial_step: 0.5,
step_multiplier: 3.0,
index: 4,
size: 5 },
(4.0, f64::MAX)
)]
#[fuchsia::test]
fn test_exponential_bucket_bounds_f64(
args: ExponentialBucketBoundsArgs<f64>,
bounds: (f64, f64),
) {
assert_eq!(exponential_bucket_bounds(args), bounds);
}
// Check logic for few-bucket histograms. 2-bucket shouldn't occur, but let's treat it
// well anyway. (Using i64)
#[test_case(
ExponentialBucketBoundsArgs {
floor: -10,
initial_step: 2,
step_multiplier: 3,
index: 0,
size: 4
},
(i64::MIN, -10)
)]
#[test_case(
ExponentialBucketBoundsArgs {
floor: -10,
initial_step: 2,
step_multiplier: 3,
index: 1,
size: 4
},
(-10, -8)
)]
#[test_case(
ExponentialBucketBoundsArgs {
floor: -10,
initial_step: 2,
step_multiplier: 3,
index: 2,
size: 4
},
(-8, -4)
)]
#[test_case(
ExponentialBucketBoundsArgs {
floor: -10,
initial_step: 2,
step_multiplier: 3,
index: 3,
size: 4
},
(-4, i64::MAX)
)]
#[test_case(
ExponentialBucketBoundsArgs {
floor: -10,
initial_step: 2,
step_multiplier: 3,
index: 0,
size: 3
},
(i64::MIN, -10)
)]
#[test_case(
ExponentialBucketBoundsArgs {
floor: -10,
initial_step: 2,
step_multiplier: 3,
index: 1,
size: 3
},
(-10, -8)
)]
#[test_case(
ExponentialBucketBoundsArgs {
floor: -10,
initial_step: 2,
step_multiplier: 3,
index: 2,
size: 3
},
(-8, i64::MAX)
)]
#[test_case(
ExponentialBucketBoundsArgs {
floor: -10,
initial_step: 2,
step_multiplier: 3,
index: 0,
size: 2
},
(i64::MIN, -10)
)]
#[test_case(
ExponentialBucketBoundsArgs {
floor: -10,
initial_step: 2,
step_multiplier: 3,
index: 1,
size: 2
},
(-10, i64::MAX)
)]
#[fuchsia::test]
fn test_exponential_small_bucket_bounds(
args: ExponentialBucketBoundsArgs<i64>,
bounds: (i64, i64),
) {
assert_eq!(exponential_bucket_bounds(args), bounds);
}
}