mirror of https://github.com/digint/btrbk
btrbk: refactor action list and stats; add "list all"
parent
31475a303f
commit
bba7c06486
276
btrbk
276
btrbk
|
@ -209,6 +209,12 @@ my %table_formats = (
|
|||
# NOTE: snapshot_path is ambigous and does NOT print SUBVOL_PATH here (should be snapshot_subvolume, left as-is for compatibility)
|
||||
},
|
||||
|
||||
stats => { table => [ qw( -source_host -source_port source_subvolume snapshot_subvolume -target_host -target_port -target_subvolume snapshots -backups ) ],
|
||||
long => [ qw( source_host -source_port source_subvolume snapshot_subvolume target_host -target_port -target_subvolume snapshot_status backup_status snapshots -backups -correlated -orphaned -incomplete ) ],
|
||||
raw => [ qw( source_url source_host source_port source_subvolume snapshot_subvolume snapshot_name target_url target_host target_port target_subvolume snapshot_status backup_status snapshots backups correlated orphaned incomplete ) ],
|
||||
RALIGN => { snapshots=>1, backups=>1, correlated=>1, orphaned=>1, incomplete=>1 },
|
||||
},
|
||||
|
||||
schedule => { table => [ qw( action -host -port subvolume scheme reason ) ],
|
||||
long => [ qw( action host -port root_path subvolume_path scheme reason ) ],
|
||||
raw => [ qw( topic action url host port path hod dow min h d w m y) ],
|
||||
|
@ -400,8 +406,9 @@ commands:
|
|||
clean delete incomplete (garbled) backups
|
||||
stats print snapshot/backup statistics
|
||||
list <subcommand> available subcommands are:
|
||||
backups all backups and corresponding snapshots
|
||||
snapshots all snapshots and corresponding backups
|
||||
all snapshots and backups
|
||||
snapshots snapshots
|
||||
backups backups and correlated snapshots
|
||||
latest most recent snapshots and backups
|
||||
config configured source/snapshot/target relations
|
||||
source configured source/snapshot relations
|
||||
|
@ -5490,14 +5497,15 @@ MAIN:
|
|||
{
|
||||
$action_list = $subcommand;
|
||||
}
|
||||
elsif(($subcommand eq "snapshots") ||
|
||||
elsif(($subcommand eq "all") ||
|
||||
($subcommand eq "snapshots") ||
|
||||
($subcommand eq "backups") ||
|
||||
($subcommand eq "latest"))
|
||||
{
|
||||
$action_resolve = $subcommand;
|
||||
}
|
||||
else {
|
||||
$action_list = "config";
|
||||
$action_resolve = "all";
|
||||
unshift @ARGV, $subcommand if($subcommand ne "");
|
||||
}
|
||||
@filter_args = @ARGV;
|
||||
|
@ -6707,197 +6715,115 @@ MAIN:
|
|||
if($action_resolve)
|
||||
{
|
||||
my @data;
|
||||
my @stats_data;
|
||||
my $stats_snapshots_total = 0;
|
||||
my $stats_backups_total = 0;
|
||||
my $stats_backups_total_correlated = 0;
|
||||
my $stats_backups_total_incomplete = 0;
|
||||
my $stats_backups_total_orphaned = 0;
|
||||
if($action_resolve eq "snapshots")
|
||||
{
|
||||
#
|
||||
# print all snapshots and their receive targets
|
||||
#
|
||||
my %stats = ( snapshots => 0, backups => 0, correlated => 0, incomplete => 0, orphaned => 0 );
|
||||
foreach my $sroot (vinfo_subsection($config, 'volume')) {
|
||||
foreach my $svol (vinfo_subsection($sroot, 'subvolume')) {
|
||||
my $snapshot_name = config_key($svol, "snapshot_name") // die;
|
||||
my $snaproot = vinfo_snapshot_root($svol);
|
||||
# note: we list all snapshot children within $snaproot here, not only the ones matching btrbk naming
|
||||
foreach my $snapshot (sort { $a->{node}{cgen} <=> $b->{node}{cgen} } get_related_snapshots($snaproot, $svol)) {
|
||||
my $snapshot_data = { type => "snapshot",
|
||||
status => ($snapshot->{node}{cgen} == $svol->{node}{gen}) ? "up-to-date" : undef,
|
||||
vinfo_prefixed_keys("source", $svol),
|
||||
vinfo_prefixed_keys("snapshot", $snapshot),
|
||||
snapshot_name => $snapshot_name,
|
||||
};
|
||||
my $found = 0;
|
||||
foreach my $droot (vinfo_subsection($svol, 'target')) {
|
||||
foreach (sort { $a->{SUBVOL_PATH} cmp $b->{SUBVOL_PATH} } get_receive_targets($droot, $snapshot)) {
|
||||
push @data, { %$snapshot_data,
|
||||
type => "snapshot,backup",
|
||||
target_type => $_->{CONFIG}{target_type}, # "send-receive" or "raw"
|
||||
vinfo_prefixed_keys("target", $_),
|
||||
};
|
||||
$found = 1;
|
||||
}
|
||||
}
|
||||
push @data, $snapshot_data unless($found);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
elsif(($action_resolve eq "backups") || ($action_resolve eq "stats"))
|
||||
{
|
||||
#
|
||||
# print all targets and their corresponding source snapshots
|
||||
#
|
||||
foreach my $sroot (vinfo_subsection($config, 'volume')) {
|
||||
foreach my $svol (vinfo_subsection($sroot, 'subvolume')) {
|
||||
my $snapshot_name = config_key($svol, "snapshot_name") // die;
|
||||
my $snaproot = vinfo_snapshot_root($svol);
|
||||
# note: we list all snapshot children within $snaproot here, not only the ones matching btrbk naming
|
||||
my @related_snapshots = get_related_snapshots($snaproot, $svol);
|
||||
my $stats_snapshot_uptodate = "";
|
||||
foreach my $snapshot (@related_snapshots) {
|
||||
if($snapshot->{node}{cgen} == $svol->{node}{gen}) {
|
||||
$stats_snapshot_uptodate = " (up-to-date)";
|
||||
last;
|
||||
}
|
||||
}
|
||||
push @stats_data, [ $svol->{PRINT}, sprintf("%4u snapshots$stats_snapshot_uptodate", scalar(@related_snapshots)) ];
|
||||
$stats_snapshots_total += scalar(@related_snapshots); # NOTE: this adds ALL related snaphots under $sroot (not only the ones created by btrbk!)
|
||||
my @related_snapshots = get_related_snapshots($snaproot, $svol, $snapshot_name);
|
||||
|
||||
my %svol_data = (
|
||||
vinfo_prefixed_keys("source", $svol),
|
||||
snapshot_name => $snapshot_name,
|
||||
);
|
||||
my @sdata = map +{
|
||||
%svol_data,
|
||||
type => "snapshot",
|
||||
status => ($_->{node}{cgen} == $svol->{node}{gen}) ? "up-to-date" : "",
|
||||
vinfo_prefixed_keys("snapshot", $_),
|
||||
_vinfo => $_,
|
||||
_btrbk_date => $_->{node}{BTRBK_DATE},
|
||||
}, @related_snapshots;
|
||||
|
||||
my %svol_stats_data = (
|
||||
%svol_data,
|
||||
snapshot_subvolume => "$snaproot->{PATH}/$snapshot_name.*",
|
||||
snapshot_status => (grep { $_->{status} eq "up-to-date" } @sdata) ? "up-to-date" : "",
|
||||
snapshots => scalar(@sdata),
|
||||
);
|
||||
$stats{snapshots} += scalar(@sdata);
|
||||
|
||||
my (@bdata, @ldata, @stdata);
|
||||
foreach my $droot (vinfo_subsection($svol, 'target')) {
|
||||
my $stats_correlated = 0;
|
||||
my $stats_orphaned = 0;
|
||||
my $stats_incomplete = 0;
|
||||
my $target_up_to_date = 0;
|
||||
foreach my $target_vol (@{vinfo_subvol_list($droot, sort => 'path')}) {
|
||||
my $parent_snapshot;
|
||||
my $incomplete_backup;
|
||||
foreach (@related_snapshots) {
|
||||
if($target_vol->{node}{received_uuid} eq '-') {
|
||||
my %dstats = ( correlated => 0, orphaned => 0, incomplete => 0, uptodate => 0 );
|
||||
my $latest_backup;
|
||||
foreach my $target_vol (@{vinfo_subvol_list($droot, btrbk_direct_leaf => $snapshot_name, sort => 'path')}) {
|
||||
my $target_data = {
|
||||
%svol_data,
|
||||
type => "backup",
|
||||
target_type => $target_vol->{CONFIG}{target_type}, # "send-receive" or "raw"
|
||||
vinfo_prefixed_keys("target", $target_vol),
|
||||
_btrbk_date => $target_vol->{node}{BTRBK_DATE},
|
||||
};
|
||||
|
||||
# incomplete received (garbled) subvolumes have no received_uuid (as of btrfs-progs v4.3.1).
|
||||
# a subvolume in droot matching our naming is considered incomplete if received_uuid is not set!
|
||||
$parent_snapshot = undef;
|
||||
$incomplete_backup = 1;
|
||||
last;
|
||||
if($target_vol->{node}{received_uuid} eq '-') {
|
||||
$dstats{incomplete}++;
|
||||
$target_data->{status} = "incomplete";
|
||||
push @bdata, $target_data;
|
||||
next;
|
||||
}
|
||||
if(_is_correlated($_->{node}, $target_vol->{node})) {
|
||||
$parent_snapshot = $_;
|
||||
|
||||
foreach (@sdata) {
|
||||
if(_is_correlated($_->{_vinfo}{node}, $target_vol->{node})) {
|
||||
$target_data = {
|
||||
%$_,
|
||||
%$target_data,
|
||||
type => "snapshot,backup",
|
||||
_correlated => 1,
|
||||
};
|
||||
$_->{_correlated} = 1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
if($parent_snapshot) {
|
||||
$stats_correlated++;
|
||||
my $up_to_date = ($parent_snapshot->{node}{cgen} == $svol->{node}{gen});
|
||||
push @data, { type => "snapshot,backup",
|
||||
target_type => $target_vol->{CONFIG}{target_type}, # "send-receive" or "raw"
|
||||
vinfo_prefixed_keys("target", $target_vol),
|
||||
vinfo_prefixed_keys("snapshot", $parent_snapshot),
|
||||
vinfo_prefixed_keys("source", $svol),
|
||||
status => $up_to_date ? "up-to-date" : undef,
|
||||
push @bdata, $target_data;
|
||||
$latest_backup = $target_data if(!defined($latest_backup) || (cmp_date($latest_backup->{_btrbk_date}, $target_data->{_btrbk_date}) < 0));
|
||||
|
||||
$dstats{uptodate} ||= ($target_data->{status} // "") eq "up-to-date";
|
||||
$dstats{backups}++;
|
||||
if($target_data->{_correlated}) { $dstats{correlated}++; } else { $dstats{orphaned}++; }
|
||||
}
|
||||
push @ldata, $latest_backup;
|
||||
|
||||
push @stdata, {
|
||||
%svol_stats_data,
|
||||
%dstats,
|
||||
vinfo_prefixed_keys("target", $droot),
|
||||
target_subvolume => "$droot->{PATH}/$snapshot_name.*",
|
||||
backup_status => $dstats{uptodate} ? "up-to-date" : "",
|
||||
};
|
||||
$target_up_to_date ||= $up_to_date;
|
||||
$stats{$_} += $dstats{$_} foreach(qw(backups correlated incomplete orphaned));
|
||||
}
|
||||
else {
|
||||
# don't display all subvolumes in $droot, only the ones matching snapshot_name
|
||||
if(vinfo_is_btrbk_snapshot($target_vol, $snapshot_name)) {
|
||||
if($incomplete_backup) { $stats_incomplete++; } else { $stats_orphaned++; }
|
||||
push @data, { type => "backup",
|
||||
target_type => $target_vol->{CONFIG}{target_type}, # "send-receive" or "raw"
|
||||
# suppress "orphaned" status here (snapshot column is empty anyways)
|
||||
# status => ($incomplete_backup ? "incomplete" : "orphaned"),
|
||||
status => ($incomplete_backup ? "incomplete" : undef),
|
||||
vinfo_prefixed_keys("target", $target_vol),
|
||||
vinfo_prefixed_keys("source", $svol),
|
||||
};
|
||||
|
||||
if($action_resolve eq "snapshots") {
|
||||
push @data, @sdata;
|
||||
} elsif($action_resolve eq "backups") {
|
||||
push @data, @bdata;
|
||||
} elsif($action_resolve eq "all") {
|
||||
push @data, sort { cmp_date($a->{_btrbk_date}, $b->{_btrbk_date}) } (@bdata, grep { !$_->{_correlated} } @sdata);
|
||||
} elsif($action_resolve eq "latest") {
|
||||
my $latest_snapshot = (sort { cmp_date($b->{_btrbk_date}, $a->{_btrbk_date}) } (@sdata, @bdata))[0];
|
||||
push @data, $latest_snapshot if($latest_snapshot && !$latest_snapshot->{_correlated});
|
||||
push @data, @ldata;
|
||||
} elsif($action_resolve eq "stats") {
|
||||
@stdata = ( \%svol_stats_data ) unless(@stdata);
|
||||
push @data, @stdata;
|
||||
}
|
||||
}
|
||||
}
|
||||
my $stats_total = $stats_correlated + $stats_incomplete + $stats_orphaned;
|
||||
$stats_backups_total += $stats_total;
|
||||
$stats_backups_total_correlated += $stats_correlated;
|
||||
$stats_backups_total_incomplete += $stats_incomplete;
|
||||
$stats_backups_total_orphaned += $stats_orphaned;
|
||||
my @stats_detail;
|
||||
push @stats_detail, "up-to-date" if($target_up_to_date);
|
||||
push @stats_detail, "$stats_correlated correlated" if($stats_correlated);
|
||||
push @stats_detail, "$stats_incomplete incomplete" if($stats_incomplete);
|
||||
my $stats_detail_print = join(', ', @stats_detail);
|
||||
$stats_detail_print = " ($stats_detail_print)" if($stats_detail_print);
|
||||
push @stats_data, [ "^-- $droot->{PRINT}/$snapshot_name.*", sprintf("%4u backups$stats_detail_print", $stats_total) ];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
elsif($action_resolve eq "latest")
|
||||
{
|
||||
#
|
||||
# print latest common
|
||||
#
|
||||
foreach my $sroot (vinfo_subsection($config, 'volume')) {
|
||||
foreach my $svol (vinfo_subsection($sroot, 'subvolume')) {
|
||||
my $found = 0;
|
||||
my $snaproot = vinfo_snapshot_root($svol);
|
||||
my $snapshot_basename = config_key($svol, "snapshot_name") // die;
|
||||
my @related_snapshots = sort({ cmp_date($b->{node}{BTRBK_DATE}, $a->{node}{BTRBK_DATE}) } # sort descending
|
||||
get_related_snapshots($snaproot, $svol, $snapshot_basename));
|
||||
foreach my $droot (vinfo_subsection($svol, 'target')) {
|
||||
foreach my $snapshot (@related_snapshots) {
|
||||
my @receive_targets = get_receive_targets($droot, $snapshot, exact => 1);
|
||||
if(scalar(@receive_targets)) {
|
||||
foreach(@receive_targets) {
|
||||
push @data, { type => "latest_common",
|
||||
target_type => $_->{CONFIG}{target_type}, # "send-receive" or "raw"
|
||||
status => ($snapshot->{node}{cgen} == $svol->{node}{gen}) ? "up-to-date" : undef,
|
||||
vinfo_prefixed_keys("source", $svol),
|
||||
vinfo_prefixed_keys("snapshot", $snapshot),
|
||||
vinfo_prefixed_keys("target", $_),
|
||||
};
|
||||
}
|
||||
$found = 1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!$found) {
|
||||
my $latest_snapshot = $related_snapshots[0];
|
||||
push @data, { type => "latest_snapshot",
|
||||
status => ($latest_snapshot && ($latest_snapshot->{node}{cgen} == $svol->{node}{gen})) ? "up-to-date" : undef,
|
||||
vinfo_prefixed_keys("source", $svol),
|
||||
vinfo_prefixed_keys("snapshot", $latest_snapshot), # all unset if no $latest_snapshot
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
die;
|
||||
}
|
||||
|
||||
if($action_resolve eq "stats") {
|
||||
print_header(title => "Statistics",
|
||||
config => $config,
|
||||
time => $start_time,
|
||||
legend => [
|
||||
"up-to-date: latest snapshot/backup is up to date with source subvolume",
|
||||
"correlated: corresponding (received-from) source snapshot is present",
|
||||
],
|
||||
);
|
||||
my $filter = $config->{CMDLINE_FILTER_LIST} ? " (" . join(", ", @{$config->{CMDLINE_FILTER_LIST}}) . ")" : "";
|
||||
my @backup_total = map { $stats{$_} ? "$stats{$_} $_" : () } qw( correlated incomplete );
|
||||
my $bflags = @backup_total ? "(" . join(', ', @backup_total) . ")" : "";
|
||||
|
||||
print_table(\@stats_data, " ");
|
||||
print "\n";
|
||||
my $stats_filter = $config->{CMDLINE_FILTER_LIST} ? join("; ", @{$config->{CMDLINE_FILTER_LIST}}) : "";
|
||||
my @stats_total_detail;
|
||||
push @stats_total_detail, "$stats_backups_total_correlated correlated" if($stats_backups_total_correlated);
|
||||
push @stats_total_detail, "$stats_backups_total_incomplete incomplete" if($stats_backups_total_incomplete);
|
||||
my $stats_total_detail_print = join(', ', @stats_total_detail);
|
||||
$stats_total_detail_print = " ($stats_total_detail_print)" if($stats_total_detail_print);
|
||||
print "Total" . ($stats_filter ? " ($stats_filter)" : "") . ":\n";
|
||||
my $maxlen = ($stats_snapshots_total > $stats_backups_total) ? length($stats_snapshots_total) : length($stats_backups_total);
|
||||
printf("%" . $maxlen . "u snapshots\n", $stats_snapshots_total);
|
||||
printf("%" . $maxlen . "u backups$stats_total_detail_print\n", $stats_backups_total);
|
||||
print_formatted("stats", \@data, paragraph => 1);
|
||||
print "Total${filter}:\n";
|
||||
print_formatted({ table => [ qw( a b c ) ], RALIGN => { a=>1 } },
|
||||
[ { a => $stats{snapshots}, b => "snapshots", c => " " },
|
||||
{ a => $stats{backups}, b => "backups", c => $bflags } ],
|
||||
output_format => "table", no_header => 1);
|
||||
}
|
||||
else {
|
||||
print_formatted("resolved", \@data);
|
||||
|
|
Loading…
Reference in New Issue