blob: 9266dfba18e069fa40b572c72a7c2543ee3db6c7 [file] [log] [blame]
// Copyright 2020 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.
/// Logs an error message if the passed in `result` is an error.
#[macro_export]
macro_rules! log_if_err {
($result:expr, $log_prefix:expr) => {
if let Err(e) = $result.as_ref() {
log::error!("{}: {}", $log_prefix, e);
}
};
}
/// Waits for a file at the given path to exist using fdio Watcher APIs. The provided `path` is
/// split into a directory path and a file name, then a watcher is set up on the directory path. If
/// the directory path itself does not exist, then this function is called recursively to wait for
/// it to be created as well.
fn wait_for_path(path: &std::path::Path) -> Result<(), anyhow::Error> {
use {anyhow::format_err, fuchsia_zircon as zx};
let svc_dir = path.parent().ok_or(format_err!("Invalid service path"))?;
let svc_name = path.file_name().ok_or(format_err!("Invalid service name"))?;
match fdio::watch_directory(
&std::fs::File::open(&svc_dir)
.map_err(|e| anyhow::format_err!("Failed to open service path: {}", e))?,
zx::sys::ZX_TIME_INFINITE,
|_event, found| if found == svc_name { Err(zx::Status::STOP) } else { Ok(()) },
) {
zx::Status::STOP => Ok(()),
zx::Status::PEER_CLOSED => wait_for_path(svc_dir),
e => Err(format_err!(
"Failed to find {:?} at path {} (watch_directory result = {})",
svc_name,
svc_dir.display(),
e
)),
}
}
/// Create and connect a FIDL proxy to the service at `path`. Calls `wait_for_path` to ensure the
/// path exists before attempting a connection.
pub fn connect_proxy<T: fidl::endpoints::ServiceMarker>(
path: &String,
) -> Result<T::Proxy, anyhow::Error> {
let (proxy, server) = fidl::endpoints::create_proxy::<T>()
.map_err(|e| anyhow::format_err!("Failed to create proxy: {}", e))?;
// Verify the path exists before attempting to connect to it. We do this because when connecting
// to drivers, a connection to a missing driver path would succeed but calls to it would fail.
// So instead of requiring us to implement logic at a higher layer to poll repeatedly until a
// driver is present, just verify the path exists here using the appropriate watcher APIs.
wait_for_path(&std::path::Path::new(path))?;
fdio::service_connect(path, server.into_channel())
.map_err(|s| anyhow::format_err!("Failed to connect to service at {}: {}", path, s))?;
Ok(proxy)
}
/// The number of nanoseconds since the system was powered on.
pub fn get_current_timestamp() -> crate::types::Nanoseconds {
crate::types::Nanoseconds(fuchsia_async::Time::now().into_nanos())
}
use fidl_fuchsia_cobalt::HistogramBucket;
/// Convenient wrapper for creating and storing an integer histogram to use with Cobalt.
pub struct CobaltIntHistogram {
/// Underlying histogram data storage.
data: Vec<HistogramBucket>,
/// Number of data values that have been added to the histogram.
data_count: u32,
/// Histogram configuration parameters.
config: CobaltIntHistogramConfig,
}
/// Histogram configuration parameters used by CobaltIntHistogram.
pub struct CobaltIntHistogramConfig {
pub floor: i64,
pub num_buckets: u32,
pub step_size: u32,
}
impl CobaltIntHistogram {
/// Create a new CobaltIntHistogram.
pub fn new(config: CobaltIntHistogramConfig) -> Self {
Self { data: Self::new_vec(config.num_buckets), data_count: 0, config }
}
/// Create a new Vec<HistogramBucket> that represents the underlying histogram storage. Two
/// extra buckets are added for underflow and overflow.
fn new_vec(num_buckets: u32) -> Vec<HistogramBucket> {
(0..num_buckets + 2).map(|i| HistogramBucket { index: i, count: 0 }).collect()
}
/// Add a data value to the histogram.
pub fn add_data(&mut self, n: i64) {
// Add one to index to account for underflow bucket at index 0
let mut index = 1 + (n - self.config.floor) / self.config.step_size as i64;
// Clamp index to 0 and self.data.len() - 1, which Cobalt uses for underflow and overflow,
// respectively
index = num_traits::clamp(index, 0, self.data.len() as i64 - 1);
self.data[index as usize].count += 1;
self.data_count += 1;
}
/// Get the number of data elements that have been added to the histogram.
pub fn count(&self) -> u32 {
self.data_count
}
/// Clear the histogram.
pub fn clear(&mut self) {
self.data = Self::new_vec(self.config.num_buckets);
self.data_count = 0;
}
/// Get the underlying Vec<HistogramBucket> of the histogram.
pub fn get_data(&self) -> Vec<HistogramBucket> {
self.data.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
/// CobaltIntHistogram: tests that data added to the CobaltIntHistogram is correctly counted and
/// bucketed.
#[test]
fn test_cobalt_histogram_data() {
// Create the histogram and verify initial data count is 0
let mut hist = CobaltIntHistogram::new(CobaltIntHistogramConfig {
floor: 50,
step_size: 10,
num_buckets: 3,
});
assert_eq!(hist.count(), 0);
// Add some arbitrary values, making sure some do not land on the bucket boundary to further
// verify the bucketing logic
hist.add_data(50);
hist.add_data(65);
hist.add_data(75);
hist.add_data(79);
// Verify the values were counted and bucketed properly
assert_eq!(hist.count(), 4);
assert_eq!(
hist.get_data(),
vec![
HistogramBucket { index: 0, count: 0 }, // underflow
HistogramBucket { index: 1, count: 1 },
HistogramBucket { index: 2, count: 1 },
HistogramBucket { index: 3, count: 2 },
HistogramBucket { index: 4, count: 0 } // overflow
]
);
// Verify `clear` works as expected
hist.clear();
assert_eq!(hist.count(), 0);
assert_eq!(
hist.get_data(),
vec![
HistogramBucket { index: 0, count: 0 }, // underflow
HistogramBucket { index: 1, count: 0 },
HistogramBucket { index: 2, count: 0 },
HistogramBucket { index: 3, count: 0 },
HistogramBucket { index: 4, count: 0 }, // overflow
]
);
}
/// CobaltIntHistogram: tests that invalid data values are logged in the correct
/// underflow/overflow buckets.
#[test]
fn test_cobalt_histogram_invalid_data() {
let mut hist = CobaltIntHistogram::new(CobaltIntHistogramConfig {
floor: 0,
step_size: 1,
num_buckets: 2,
});
hist.add_data(-2);
hist.add_data(-1);
hist.add_data(0);
hist.add_data(1);
hist.add_data(2);
assert_eq!(
hist.get_data(),
vec![
HistogramBucket { index: 0, count: 2 }, // underflow
HistogramBucket { index: 1, count: 1 },
HistogramBucket { index: 2, count: 1 },
HistogramBucket { index: 3, count: 1 } // overflow
]
);
}
/// Tests that the `get_current_timestamp` function returns the expected current timestamp.
#[test]
fn test_get_current_timestamp() {
use crate::types::Nanoseconds;
let exec = fuchsia_async::Executor::new_with_fake_time().unwrap();
exec.set_fake_time(fuchsia_async::Time::from_nanos(0));
assert_eq!(get_current_timestamp(), Nanoseconds(0));
exec.set_fake_time(fuchsia_async::Time::from_nanos(1000));
assert_eq!(get_current_timestamp(), Nanoseconds(1000));
}
}