blob: 08ba3846888c5515be1f9318125f8785080a67de [file] [log] [blame]
// Copyright 2021 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 {
anyhow::{anyhow, Error},
byteorder::{BigEndian, ByteOrder, WriteBytesExt},
fuchsia_component::server::ServiceFs,
fuchsia_inspect::{Inspector, Node},
futures::stream::StreamExt,
lazy_static::lazy_static,
std::{
fs,
io::Read,
path::{Path, PathBuf},
},
tracing::{info, warn},
};
const U64_SIZE: usize = (u64::BITS / 8) as usize;
const DEFAULT_VALUE: u64 = 1;
const FILENAME: &str = "counter";
lazy_static! {
static ref DIRECTORIES: Vec<&'static str> = vec!["data", "cache", "tmp"];
static ref INSPECTOR: Inspector = Inspector::new();
}
#[fuchsia::main(logging = true)]
async fn main() -> Result<(), Error> {
info!("Initializing and serving inspect on servicefs");
let mut fs = ServiceFs::new();
let mut inspect_nodes = Vec::new();
inspect_runtime::serve(&INSPECTOR, &mut fs)?;
for dirname in &*DIRECTORIES {
let path = PathBuf::from(format!("/{}/{}", dirname, FILENAME));
let node = INSPECTOR.root().create_child(*dirname);
info!("Attempting to read and write from {:?}", path);
process_file(&path, &node);
inspect_nodes.push(node);
}
fs.take_and_serve_directory_handle()?;
Ok(fs.collect().await)
}
/// Attempts to read a single u64 from the supplied path then write an incremented value back into
/// the same path. If any errors are encountered during reading the write will use a default
/// value.
fn process_file(path: &Path, inspect_node: &Node) {
let updated_value = match read_file(path) {
Ok(value) => {
info!("Successfully read a value from {:?}: {}", path, value);
inspect_node.record_uint("read_value", value);
value.wrapping_add(1)
}
Err(err) => {
let error_string = format!("{:?}", err);
warn!("Error reading previous value from {:?}: {}", path, error_string);
inspect_node.record_string("read_error", error_string);
DEFAULT_VALUE
}
};
match write_file(path, updated_value) {
Ok(()) => {
info!("Successfully wrote a value to {:?}: {}", path, updated_value);
inspect_node.record_uint("write_value", updated_value);
}
Err(err) => {
let error_string = format!("{:?}", err);
warn!("Error writing updated value to {:?}: {}", path, error_string);
inspect_node.record_string("write_error", error_string);
}
}
}
/// Reads a single u64 from the supplied path, returning an error if the file does not exists or is
/// not exactly the right length.
fn read_file(path: &Path) -> Result<u64, Error> {
let mut buf = Vec::<u8>::new();
let mut file = fs::File::open(path).map_err(|err| anyhow!("Error opening file: {:?}", err))?;
let size =
file.read_to_end(&mut buf).map_err(|err| anyhow!("Error reading file: {:?}", err))?;
match size {
U64_SIZE => Ok(BigEndian::read_u64(&buf)),
_ => Err(anyhow!("File length invalid: {}", size)),
}
}
/// Writes the supplied u64 into the supplied path, returning any errors that are encountered.
fn write_file(path: &Path, value: u64) -> Result<(), Error> {
let mut file =
fs::File::create(path).map_err(|err| anyhow!("Error creating file: {:?}", err))?;
file.write_u64::<BigEndian>(value).map_err(|err| anyhow!("Error writing value: {:?}", err))
}
#[cfg(test)]
mod tests {
use {
super::*,
fuchsia_inspect::{assert_data_tree, AnyProperty},
tempfile::TempDir,
test_case::test_case,
};
fn make_path() -> (TempDir, PathBuf) {
let dir = TempDir::new().expect("error creating tempdir");
let path = dir.path().join("testfile");
(dir, path)
}
fn make_file(data: Vec<u8>) -> (TempDir, PathBuf) {
let (dir, path) = make_path();
fs::write(&path, &data).expect("error writing file");
(dir, path)
}
#[test_case(vec![0, 0, 0, 0, 1, 2, 3, 4], Some(0x01020304); "valid")]
#[test_case(vec![0, 0, 0, 0, 1, 2, 3], None; "too short")]
#[test_case(vec![0, 0, 0, 0, 1, 2, 3, 4, 5], None; "too long")]
fn test_read_existing_file(content: Vec<u8>, expected: Option<u64>) {
let (_tempdir, path) = make_file(content);
match expected {
Some(value) => assert_eq!(read_file(&path).unwrap(), value),
None => assert!(read_file(&path).is_err(), "read should fail"),
}
}
#[test]
fn test_read_missing_file() {
let (_tempdir, path) = make_path();
assert!(read_file(&path).is_err(), "read should fail");
}
#[test_case(vec![0, 0, 0, 0, 1, 2, 3, 4], Some(0x01020304), 0x01020305; "valid")]
#[test_case(vec![255, 255, 255, 255, 255, 255, 255, 255], Some(u64::MAX), 0x0; "rollover")]
#[test_case(vec![0, 0, 0, 0, 1, 2], None, DEFAULT_VALUE; "content too short")]
fn test_process_existing_file(content: Vec<u8>, read: Option<u64>, write: u64) {
let (_tempdir, path) = make_file(content);
let inspector = &Inspector::new();
process_file(&path, inspector.root());
assert_eq!(read_file(&path).unwrap(), write);
match read {
Some(read_value) => assert_data_tree!(
inspector,
root: contains {
read_value: read_value,
write_value: write,
}),
None => assert_data_tree!(
inspector,
root: contains {
read_error: AnyProperty,
write_value: write,
}),
}
}
#[test]
fn test_process_missing_file() {
let (_tempdir, path) = make_path();
let inspector = &Inspector::new();
process_file(&path, inspector.root());
assert_eq!(read_file(&path).unwrap(), DEFAULT_VALUE);
assert_data_tree!(
inspector,
root: contains {
read_error: AnyProperty,
write_value: DEFAULT_VALUE,
});
}
#[test]
fn test_process_invalid_directory() {
let inspector = &Inspector::new();
process_file(&PathBuf::from("/i_dont_exist"), inspector.root());
assert_data_tree!(
inspector,
root: contains {
read_error: AnyProperty,
write_error: AnyProperty,
});
}
}