Add times and disk space to the report
This commit is contained in:
parent
948ca3f2b5
commit
ec8c5ff42d
|
@ -423,6 +423,7 @@ dependencies = [
|
||||||
"humansize",
|
"humansize",
|
||||||
"indicatif",
|
"indicatif",
|
||||||
"itertools 0.10.3",
|
"itertools 0.10.3",
|
||||||
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"metrics",
|
"metrics",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
|
@ -34,3 +34,4 @@ yama = { path = "../yama", version = "0.6.0-alpha.1" }
|
||||||
metrics = "0.17.1"
|
metrics = "0.17.1"
|
||||||
bare-metrics-recorder = { version = "0.1.0" }
|
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 descriptor = load_descriptor(Path::new(".")).unwrap();
|
||||||
let destination = &descriptor.piles[&pile_name];
|
let destination = &descriptor.piles[&pile_name];
|
||||||
let report = datman::commands::report::generate_report(destination, &descriptor)?;
|
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_report(&report)?;
|
||||||
|
datman::commands::report::print_filesystem_space(&destination.path)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,14 +1,19 @@
|
||||||
use crate::commands::backup::split_pointer_name;
|
use crate::commands::backup::split_pointer_name;
|
||||||
use crate::descriptor::{Descriptor, DestPileDescriptor};
|
use crate::descriptor::{Descriptor, DestPileDescriptor};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{Date, DateTime, Utc};
|
||||||
use comfy_table::presets::UTF8_FULL;
|
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 humansize::FileSize;
|
||||||
|
use itertools::Itertools;
|
||||||
use log::info;
|
use log::info;
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
use std::ffi::CString;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
use std::mem;
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
use std::path::Path;
|
||||||
use yama::chunking::RecursiveUnchunker;
|
use yama::chunking::RecursiveUnchunker;
|
||||||
use yama::commands::{load_pile_descriptor, open_pile, retrieve_tree_node};
|
use yama::commands::{load_pile_descriptor, open_pile, retrieve_tree_node};
|
||||||
use yama::definitions::{ChunkId, RecursiveChunkRef, TreeNode};
|
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<()> {
|
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();
|
let mut table = Table::new();
|
||||||
table
|
table
|
||||||
.load_preset(UTF8_FULL)
|
.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);
|
//.set_width(100);
|
||||||
table.set_header(vec![
|
table.set_header(vec![
|
||||||
Cell::new("Pointer name").fg(Color::Cyan),
|
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
|
let est_size_suffix = average_chunk_size
|
||||||
.map(|bytes_per_chunk| {
|
.map(|bytes_per_chunk| {
|
||||||
let num_bytes = (chunks as f64 * bytes_per_chunk) as u64;
|
let num_bytes = (chunks as f64 * bytes_per_chunk) as u64;
|
||||||
format!(
|
let mut format = humansize::file_size_opts::BINARY;
|
||||||
" ~{}",
|
format.decimal_places = 1;
|
||||||
num_bytes
|
format!(" ~{}", num_bytes.file_size(format).unwrap())
|
||||||
.file_size(humansize::file_size_opts::BINARY)
|
|
||||||
.unwrap()
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
format!("{} c{}", chunks, est_size_suffix)
|
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