#![doc(html_root_url = "https://docs.rs/ccache_stats_reader/0.1.2")]
#![cfg_attr(feature = "external_doc", feature(external_doc))]
#![cfg_attr(feature = "non_exhaustive", feature(non_exhaustive))]
#![cfg_attr(feature = "external_doc", doc(include = "lib.md"))]
#![cfg_attr(
not(feature = "external_doc"),
doc = "This crate implements a simple interface for accessing `ccache` \
stats without needing an `exec` call."
)]
mod cache_field;
pub use crate::cache_field::{
CacheField, CacheFieldData, CacheFieldFormat, CacheFieldMeta,
};
use crate::cache_field::{FIELD_DATA_ORDER, FIELD_DISPLAY_ORDER};
#[cfg_attr(feature = "external_doc", doc(include = "ErrorKind.md"))]
#[cfg_attr(
not(feature = "external_doc"),
doc = "An enum for wrapping various errors emitted by this crate."
)]
#[cfg_attr(feature = "non_exhaustive", non_exhaustive)]
#[derive(Debug)]
pub enum ErrorKind {
IoError(std::io::Error),
Stringy(String),
SysTime(std::time::SystemTimeError),
ParseU64Error {
input_value: String,
input_line: usize,
input_file: PathBuf,
},
CacheLeafNonFile {
input_path: PathBuf,
},
}
impl From<std::io::Error> for ErrorKind {
fn from(e: std::io::Error) -> Self { ErrorKind::IoError(e) }
}
impl From<std::time::SystemTimeError> for ErrorKind {
fn from(e: std::time::SystemTimeError) -> Self { ErrorKind::SysTime(e) }
}
impl std::fmt::Display for ErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ErrorKind::IoError(e) => e.fmt(f),
ErrorKind::SysTime(e) => e.fmt(f),
ErrorKind::Stringy(s) => write!(f, "{}", s),
ErrorKind::ParseU64Error {
input_value,
input_file,
input_line,
} => write!(
f,
"could not parse u64 from value {:?} in {:?} line {}",
input_value, input_file, input_line
),
ErrorKind::CacheLeafNonFile { input_path } => write!(
f,
"expected path {:?} to be a readable file, not a directory",
input_path
),
}
}
}
impl std::error::Error for ErrorKind {}
use chrono::{TimeZone, Utc};
#[cfg_attr(feature = "external_doc", doc(include = "CacheLeaf.md"))]
#[cfg_attr(
not(feature = "external_doc"),
doc = "A leaf container for one sub-cache of a ccache directory."
)]
#[derive(Debug, Clone, Copy)]
pub struct CacheLeaf {
fields: CacheFieldData,
mtime: chrono::DateTime<Utc>,
}
impl Default for CacheLeaf {
fn default() -> Self {
Self { fields: Default::default(), mtime: Utc.timestamp(0, 0) }
}
}
use std::{
fs::File,
io::{BufRead, BufReader},
path::PathBuf,
};
impl CacheLeaf {
pub fn read_file(f: PathBuf) -> Result<Self, ErrorKind> {
let mut me: Self = Default::default();
let my_file = File::open(&f)?;
let my_meta = my_file.metadata()?;
if my_meta.is_dir() {
return Err(ErrorKind::CacheLeafNonFile { input_path: f });
}
me.mtime = Utc.timestamp(
my_meta
.modified()?
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as i64,
0,
);
let mut bufreader = BufReader::with_capacity(100, my_file);
let mut buf = String::new();
for field in FIELD_DATA_ORDER {
match bufreader.read_line(&mut buf) {
Ok(0) => break,
Err(e) => return Err(ErrorKind::IoError(e)),
Ok(_n) => {
if buf.ends_with('\n') {
let _ = buf.pop();
if buf.ends_with('\r') {
let _ = buf.pop();
}
}
let field_addr: usize = field.as_usize();
if let Ok(v) = buf.parse::<u64>() {
me.fields.set_field(*field, v);
} else {
return Err(ErrorKind::ParseU64Error {
input_line: field_addr,
input_value: buf,
input_file: f,
});
}
buf.clear();
},
}
}
Ok(me)
}
}
#[cfg_attr(feature = "external_doc", doc(include = "CacheDir.md"))]
#[cfg_attr(
not(feature = "external_doc"),
doc = "A container for collecting statistics from a ccache directory."
)]
#[derive(Debug, Clone, Copy)]
pub struct CacheDir {
fields: CacheFieldData,
mtime: chrono::DateTime<Utc>,
}
impl Default for CacheDir {
fn default() -> Self {
Self { fields: Default::default(), mtime: Utc.timestamp(0, 0) }
}
}
impl CacheDir {
pub fn read_dir<P>(d: P) -> Result<Self, ErrorKind>
where
P: Into<PathBuf>,
{
let mut me: Self = Default::default();
let dir: PathBuf = d.into();
me.add_leaf(dir.join("stats"))?;
for i in 0..=0xF {
if let Some(c) = std::char::from_digit(i, 16) {
me.add_leaf(dir.join(c.to_string()).join("stats"))?;
}
}
Ok(me)
}
fn stash_field(&mut self, field: CacheField, value: u64) {
let current_value = self.fields.get_field(field);
match field {
CacheField::ZeroTimeStamp => {
if value > current_value {
self.fields.set_field(field, value);
}
},
_ => {
self.fields.set_field(field, current_value + value);
},
}
}
fn add_leaf(&mut self, f: PathBuf) -> Result<(), ErrorKind> {
let leaf_result = CacheLeaf::read_file(f);
if let Ok(leaf) = &leaf_result {
self.merge_leaf(leaf);
return Ok(());
}
if let Err(e) = leaf_result {
if let ErrorKind::IoError(io) = &e {
if io.kind() == std::io::ErrorKind::NotFound {
return Ok(());
}
}
return Err(e);
}
Ok(())
}
fn merge_leaf(&mut self, leaf: &CacheLeaf) {
for field in FIELD_DATA_ORDER {
let value = leaf.fields.get_field(*field);
self.stash_field(*field, value);
}
if self.mtime < leaf.mtime {
self.mtime = leaf.mtime;
}
}
}
use chrono::Local;
#[cfg_attr(
feature = "external_doc",
doc(include = "CacheFieldCollection.md")
)]
#[cfg_attr(
not(feature = "external_doc"),
doc = "An abstact representation of 'a thing' that can expose data \
about its unit."
)]
pub trait CacheFieldCollection {
fn fields(&self) -> &CacheFieldData;
fn mtime(&self) -> &chrono::DateTime<Utc>;
fn get_field(&self, f: CacheField) -> u64 { self.fields().get_field(f) }
fn iter<'a>(
&'a self,
) -> Box<dyn Iterator<Item = (CacheField, u64)> + 'a> {
Box::new(
FIELD_DISPLAY_ORDER
.iter()
.map(move |&field| (field, self.get_field(field).to_owned())),
)
}
fn write_raw(
&self, mut fh: impl std::io::Write,
) -> Result<(), ErrorKind> {
let mtime = self.mtime();
writeln!(fh, "stats_updated_timestamp\t{}", mtime.timestamp())?;
for (field, value) in self.iter() {
if field.metadata().is_flag_never() {
continue;
}
writeln!(fh, "{}\t{}", field.metadata().id, value)?;
}
Ok(())
}
fn write_pretty(
&self, mut fh: impl std::io::Write,
) -> Result<(), ErrorKind> {
let mtime = self.mtime();
writeln!(
fh,
"{:<30} {:>9}",
"stats updated",
Local.timestamp(mtime.timestamp(), 0),
)?;
for (field, value) in self.iter() {
let meta = field.metadata();
if meta.is_flag_never() {
continue;
}
if !meta.is_flag_always() && value == 0u64 {
continue;
}
writeln!(
fh,
"{:<30} {:>9}",
field.metadata().message,
field.format_value(value)
)?;
}
Ok(())
}
}
impl CacheFieldCollection for CacheLeaf {
fn fields(&self) -> &CacheFieldData { &self.fields }
fn mtime(&self) -> &chrono::DateTime<Utc> { &self.mtime }
}
impl CacheFieldCollection for CacheDir {
fn fields(&self) -> &CacheFieldData { &self.fields }
fn mtime(&self) -> &chrono::DateTime<Utc> { &self.mtime }
}