blob: 833af3909c005777b21a577c3fa6b881255798b6 [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.
use {
anyhow::{format_err, Error},
glob::glob,
std::fs,
tracing::{info, warn},
};
const CURRENT_PATH: &str = "/cache/current";
const PREVIOUS_PATH: &str = "/cache/previous";
// Throw away stuff from two boots ago. Move stuff in the "current"
// directory to the "previous" directory.
pub fn shuffle_at_boot() {
// These may fail if /cache was wiped. This is WAI and should not signal an error.
fs::remove_dir_all(PREVIOUS_PATH)
.map_err(|e| info!("Could not delete {}: {:?}", PREVIOUS_PATH, e))
.ok();
fs::rename(CURRENT_PATH, PREVIOUS_PATH)
.map_err(|e| info!("Could not move {} to {}: {:?}", CURRENT_PATH, PREVIOUS_PATH, e))
.ok();
}
// Write a VMO's contents to the appropriate file.
pub fn write(service_name: &str, tag: &str, data: &str) {
// /cache/ may be deleted any time. It's OK to try to create CURRENT_PATH if it alreay exists.
let path = format!("{}/{}", CURRENT_PATH, service_name);
fs::create_dir_all(&path)
.map_err(|e| warn!("Could not create directory {}: {:?}", path, e))
.ok();
fs::write(&format!("{}/{}", path, tag), data)
.map_err(|e| warn!("Could not write file {}/{}: {:?}", path, tag, e))
.ok();
}
// All the names in the previous-boot directory.
// TODO(fxbug.dev/71350): If this gets big, use Lazy Inspect.
pub(crate) fn remembered_data() -> Result<Vec<(String, Vec<(String, String)>)>, Error> {
// Counter for number of tags successfully retrieved. If no persisted tags were
// retrieved, this method returns an error.
let mut tags_retrieved = 0;
let mut service_entries = Vec::new();
// Create an iterator over all subdirectories of /cache/previous
// which contains persisted data from the last boot.
for service_path in glob(&format!("{}/*", PREVIOUS_PATH))
.map_err(|e| format_err!("Failed to read previous-path glob pattern: {:?}", e))?
{
match service_path {
Err(e) => {
// If our glob pattern was valid, but we encountered glob errors while iterating, just warn
// since there may still be some persisted metrics.
warn!(
"Encountered GlobError; contents could not be read to determine if glob pattern was matched: {:?}",
e
)
}
Ok(path) => {
if let Some(name) = path.file_name() {
let service_name = name.to_string_lossy().to_string();
let mut tag_entries = Vec::new();
for tag_path in
glob(&format!("{}/{}/*", PREVIOUS_PATH, service_name)).map_err(|e| {
format_err!(
"Failed to read previous service persistence pattern: {:?}",
e
)
})?
{
match tag_path {
Ok(path) => {
if let Some(tag_name) = path.file_name() {
let tag_name = tag_name.to_string_lossy().to_string();
match fs::read(path.clone()) {
Ok(text) => {
// TODO(cphoenix): We want to encode failures at retrieving persisted
// metrics in the inspect hierarchy so clients know why their data is
// missing.
match std::str::from_utf8(&text) {
Ok(contents) => {
tags_retrieved += 1;
tag_entries
.push((tag_name, contents.to_owned()));
}
Err(e) => {
warn!(
"Failed to parse persisted bytes at path: {:?} into text: {:?}",
path, e
);
}
}
}
Err(e) => {
warn!(
"Failed to retrieve text persisted at path: {:?}: {:?}",
path, e
);
}
}
}
}
Err(e) => {
// If our glob pattern was valid, but we encountered glob errors while iterating, just warn
// since there may still be some persisted metrics.
warn!(
"Encountered GlobError; contents could not be read to determine if glob pattern was matched: {:?}",
e
)
}
}
}
service_entries.push((service_name, tag_entries));
}
}
};
}
if tags_retrieved == 0 {
info!("No persisted data was successfully retrieved.");
}
Ok(service_entries)
}