Add times and disk space to the report
This commit is contained in:
parent
948ca3f2b5
commit
ec8c5ff42d
|
@ -423,6 +423,7 @@ dependencies = [
|
|||
"humansize",
|
||||
"indicatif",
|
||||
"itertools 0.10.3",
|
||||
"libc",
|
||||
"log",
|
||||
"metrics",
|
||||
"serde",
|
||||
|
|
|
@ -33,4 +33,5 @@ hostname = "0.3.1"
|
|||
yama = { path = "../yama", version = "0.6.0-alpha.1" }
|
||||
metrics = "0.17.1"
|
||||
bare-metrics-recorder = { version = "0.1.0" }
|
||||
comfy-table = "6.0.0-rc.1"
|
||||
comfy-table = "6.0.0-rc.1"
|
||||
libc = "0.2.126"
|
||||
|
|
|
@ -317,9 +317,9 @@ fn main() -> anyhow::Result<()> {
|
|||
let descriptor = load_descriptor(Path::new(".")).unwrap();
|
||||
let destination = &descriptor.piles[&pile_name];
|
||||
let report = datman::commands::report::generate_report(destination, &descriptor)?;
|
||||
// TODO Display report
|
||||
// TODO E-mail report (Can just pipe through aha and then apprise though!)
|
||||
|
||||
datman::commands::report::print_report(&report)?;
|
||||
datman::commands::report::print_filesystem_space(&destination.path)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
use crate::commands::backup::split_pointer_name;
|
||||
use crate::descriptor::{Descriptor, DestPileDescriptor};
|
||||
use anyhow::Context;
|
||||
use chrono::{DateTime, Utc};
|
||||
use chrono::{Date, DateTime, Utc};
|
||||
use comfy_table::presets::UTF8_FULL;
|
||||
use comfy_table::{Cell, Color, ContentArrangement, Table};
|
||||
use comfy_table::{Attribute, Cell, Color, ContentArrangement, Table};
|
||||
use humansize::FileSize;
|
||||
use itertools::Itertools;
|
||||
use log::info;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::ffi::CString;
|
||||
use std::io::Read;
|
||||
use std::mem;
|
||||
use std::mem::size_of;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::path::Path;
|
||||
use yama::chunking::RecursiveUnchunker;
|
||||
use yama::commands::{load_pile_descriptor, open_pile, retrieve_tree_node};
|
||||
use yama::definitions::{ChunkId, RecursiveChunkRef, TreeNode};
|
||||
|
@ -212,10 +217,86 @@ fn collect_chunk_ids_from_chunkref<RP: RawPile>(
|
|||
}
|
||||
|
||||
pub fn print_report(report: &Report) -> anyhow::Result<()> {
|
||||
print_time_report(report)?;
|
||||
print_size_report(report)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn print_time_report(report: &Report) -> anyhow::Result<()> {
|
||||
println!("\nBackup times");
|
||||
let mut table = Table::new();
|
||||
table
|
||||
.load_preset(UTF8_FULL)
|
||||
.set_content_arrangement(ContentArrangement::DynamicFullWidth);
|
||||
.set_content_arrangement(ContentArrangement::DynamicFullWidth)
|
||||
.enforce_styling();
|
||||
|
||||
table.set_header(vec![
|
||||
Cell::new("Source name").fg(Color::Cyan),
|
||||
Cell::new("Last backed up").fg(Color::Cyan),
|
||||
]);
|
||||
|
||||
let today = Utc::today();
|
||||
|
||||
let sort_by_dates: Vec<(Option<Date<Utc>>, String)> = report
|
||||
.last_source_backups
|
||||
.iter()
|
||||
.map(|(name, datetime)| (datetime.map(|dt| dt.date()), name.to_owned()))
|
||||
.sorted()
|
||||
.collect();
|
||||
|
||||
for (date, source_name) in sort_by_dates {
|
||||
match date {
|
||||
None => {
|
||||
table.add_row(vec![
|
||||
Cell::new(source_name).fg(Color::Magenta),
|
||||
Cell::new("NEVER").fg(Color::Red).add_attributes(vec![
|
||||
Attribute::SlowBlink,
|
||||
Attribute::RapidBlink,
|
||||
Attribute::Bold,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
Some(date) => {
|
||||
let number_of_days = today.signed_duration_since(date).num_days();
|
||||
let num_days_human = if number_of_days > 0 {
|
||||
format!("{number_of_days} days ago")
|
||||
} else {
|
||||
format!("today")
|
||||
};
|
||||
|
||||
let colour = if number_of_days < 2 {
|
||||
Color::Green
|
||||
} else if number_of_days < 14 {
|
||||
Color::Yellow
|
||||
} else {
|
||||
Color::Red
|
||||
};
|
||||
|
||||
let formatted_date = date.format("%F");
|
||||
|
||||
let mut val_cell =
|
||||
Cell::new(format!("{formatted_date} {num_days_human}")).fg(colour);
|
||||
if number_of_days > 28 {
|
||||
val_cell = val_cell.add_attribute(Attribute::SlowBlink);
|
||||
}
|
||||
|
||||
table.add_row(vec![Cell::new(source_name).fg(Color::Magenta), val_cell]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("{table}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn print_size_report(report: &Report) -> anyhow::Result<()> {
|
||||
println!("\nPile size");
|
||||
let mut table = Table::new();
|
||||
table
|
||||
.load_preset(UTF8_FULL)
|
||||
.set_content_arrangement(ContentArrangement::DynamicFullWidth)
|
||||
.enforce_styling();
|
||||
//.set_width(100);
|
||||
table.set_header(vec![
|
||||
Cell::new("Pointer name").fg(Color::Cyan),
|
||||
|
@ -248,13 +329,76 @@ fn format_size(chunks: u32, average_chunk_size: Option<f64>) -> String {
|
|||
let est_size_suffix = average_chunk_size
|
||||
.map(|bytes_per_chunk| {
|
||||
let num_bytes = (chunks as f64 * bytes_per_chunk) as u64;
|
||||
format!(
|
||||
" ~{}",
|
||||
num_bytes
|
||||
.file_size(humansize::file_size_opts::BINARY)
|
||||
.unwrap()
|
||||
)
|
||||
let mut format = humansize::file_size_opts::BINARY;
|
||||
format.decimal_places = 1;
|
||||
format!(" ~{}", num_bytes.file_size(format).unwrap())
|
||||
})
|
||||
.unwrap_or_default();
|
||||
format!("{} c{}", chunks, est_size_suffix)
|
||||
}
|
||||
|
||||
pub fn print_filesystem_space(pile_path: &Path) -> anyhow::Result<()> {
|
||||
let path_c = CString::new(pile_path.as_os_str().as_bytes()).unwrap();
|
||||
let stats = unsafe {
|
||||
let mut stats: libc::statfs = mem::zeroed();
|
||||
match libc::statfs(path_c.as_ptr(), &mut stats) {
|
||||
0 => Ok(stats),
|
||||
other => Err(std::io::Error::from_raw_os_error(other)),
|
||||
}
|
||||
}?;
|
||||
|
||||
// On a BTRFS system with 2 disks in RAID1, note (about df -h):
|
||||
// - 'Size' shows the average size of the two disks. I think of it as 'ideal size'.
|
||||
// - 'Avail' seems to show the actual number of bytes usable.
|
||||
// - 'Used' seems to show the actual number of bytes used.
|
||||
// In short: probably avoid relying on 'size'.
|
||||
|
||||
let block_size = stats.f_bsize as i64;
|
||||
let used_bytes = (stats.f_blocks - stats.f_bfree) as i64 * block_size;
|
||||
let avail_bytes = stats.f_bavail as i64 * block_size;
|
||||
let usable_bytes = used_bytes + avail_bytes;
|
||||
let theoretical_size = stats.f_blocks as i64 * block_size;
|
||||
|
||||
let mut format = humansize::file_size_opts::BINARY;
|
||||
format.decimal_places = 1;
|
||||
format.decimal_zeroes = 1;
|
||||
|
||||
println!("\nFilesystem Information");
|
||||
|
||||
let mut table = Table::new();
|
||||
table
|
||||
.load_preset(UTF8_FULL)
|
||||
.set_content_arrangement(ContentArrangement::DynamicFullWidth)
|
||||
.enforce_styling();
|
||||
//.set_width(100);
|
||||
table.set_header(vec![
|
||||
Cell::new("Theoretical Size").fg(Color::Cyan),
|
||||
Cell::new("Usable Size").fg(Color::Cyan),
|
||||
Cell::new("Used").fg(Color::Cyan),
|
||||
Cell::new("Available").fg(Color::Cyan),
|
||||
]);
|
||||
|
||||
let available_space_colour = if avail_bytes < 8 * 1024 * 1024 * 1024 {
|
||||
Color::Red
|
||||
} else if avail_bytes < 64 * 1024 * 1024 * 1024 {
|
||||
Color::Yellow
|
||||
} else {
|
||||
Color::Green
|
||||
};
|
||||
|
||||
table.add_row(vec![
|
||||
Cell::new(format!(
|
||||
"{:>9}",
|
||||
theoretical_size.file_size(&format).unwrap()
|
||||
))
|
||||
.fg(Color::Blue),
|
||||
Cell::new(format!("{:>9}", usable_bytes.file_size(&format).unwrap())).fg(Color::Blue),
|
||||
Cell::new(format!("{:>9}", used_bytes.file_size(&format).unwrap())).fg(Color::Blue),
|
||||
Cell::new(format!("{:>9}", avail_bytes.file_size(&format).unwrap()))
|
||||
.fg(available_space_colour),
|
||||
]);
|
||||
|
||||
print!("{table}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue