btrbk: refactor action list and stats; add "list all"

pull/358/head
Axel Burri 2020-12-13 23:54:25 +01:00
parent 31475a303f
commit bba7c06486
1 changed files with 110 additions and 184 deletions

294
btrbk
View File

@ -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) # 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 ) ], schedule => { table => [ qw( action -host -port subvolume scheme reason ) ],
long => [ qw( action host -port root_path subvolume_path 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) ], 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 clean delete incomplete (garbled) backups
stats print snapshot/backup statistics stats print snapshot/backup statistics
list <subcommand> available subcommands are: list <subcommand> available subcommands are:
backups all backups and corresponding snapshots all snapshots and backups
snapshots all snapshots and corresponding backups snapshots snapshots
backups backups and correlated snapshots
latest most recent snapshots and backups latest most recent snapshots and backups
config configured source/snapshot/target relations config configured source/snapshot/target relations
source configured source/snapshot relations source configured source/snapshot relations
@ -5490,14 +5497,15 @@ MAIN:
{ {
$action_list = $subcommand; $action_list = $subcommand;
} }
elsif(($subcommand eq "snapshots") || elsif(($subcommand eq "all") ||
($subcommand eq "snapshots") ||
($subcommand eq "backups") || ($subcommand eq "backups") ||
($subcommand eq "latest")) ($subcommand eq "latest"))
{ {
$action_resolve = $subcommand; $action_resolve = $subcommand;
} }
else { else {
$action_list = "config"; $action_resolve = "all";
unshift @ARGV, $subcommand if($subcommand ne ""); unshift @ARGV, $subcommand if($subcommand ne "");
} }
@filter_args = @ARGV; @filter_args = @ARGV;
@ -6707,198 +6715,116 @@ MAIN:
if($action_resolve) if($action_resolve)
{ {
my @data; my @data;
my @stats_data; my %stats = ( snapshots => 0, backups => 0, correlated => 0, incomplete => 0, orphaned => 0 );
my $stats_snapshots_total = 0; foreach my $sroot (vinfo_subsection($config, 'volume')) {
my $stats_backups_total = 0; foreach my $svol (vinfo_subsection($sroot, 'subvolume')) {
my $stats_backups_total_correlated = 0; my $snaproot = vinfo_snapshot_root($svol);
my $stats_backups_total_incomplete = 0; my $snapshot_name = config_key($svol, "snapshot_name") // die;
my $stats_backups_total_orphaned = 0; my @related_snapshots = get_related_snapshots($snaproot, $svol, $snapshot_name);
if($action_resolve eq "snapshots")
{
#
# print all snapshots and their receive targets
#
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!)
foreach my $droot (vinfo_subsection($svol, 'target')) { my %svol_data = (
my $stats_correlated = 0; vinfo_prefixed_keys("source", $svol),
my $stats_orphaned = 0; snapshot_name => $snapshot_name,
my $stats_incomplete = 0; );
my $target_up_to_date = 0; my @sdata = map +{
foreach my $target_vol (@{vinfo_subvol_list($droot, sort => 'path')}) { %svol_data,
my $parent_snapshot; type => "snapshot",
my $incomplete_backup; status => ($_->{node}{cgen} == $svol->{node}{gen}) ? "up-to-date" : "",
foreach (@related_snapshots) { vinfo_prefixed_keys("snapshot", $_),
if($target_vol->{node}{received_uuid} eq '-') { _vinfo => $_,
# incomplete received (garbled) subvolumes have no received_uuid (as of btrfs-progs v4.3.1). _btrbk_date => $_->{node}{BTRBK_DATE},
# a subvolume in droot matching our naming is considered incomplete if received_uuid is not set! }, @related_snapshots;
$parent_snapshot = undef;
$incomplete_backup = 1; my %svol_stats_data = (
last; %svol_data,
} snapshot_subvolume => "$snaproot->{PATH}/$snapshot_name.*",
if(_is_correlated($_->{node}, $target_vol->{node})) { snapshot_status => (grep { $_->{status} eq "up-to-date" } @sdata) ? "up-to-date" : "",
$parent_snapshot = $_; snapshots => scalar(@sdata),
last; );
} $stats{snapshots} += scalar(@sdata);
}
if($parent_snapshot) { my (@bdata, @ldata, @stdata);
$stats_correlated++; foreach my $droot (vinfo_subsection($svol, 'target')) {
my $up_to_date = ($parent_snapshot->{node}{cgen} == $svol->{node}{gen}); my %dstats = ( correlated => 0, orphaned => 0, incomplete => 0, uptodate => 0 );
push @data, { type => "snapshot,backup", my $latest_backup;
target_type => $target_vol->{CONFIG}{target_type}, # "send-receive" or "raw" foreach my $target_vol (@{vinfo_subvol_list($droot, btrbk_direct_leaf => $snapshot_name, sort => 'path')}) {
vinfo_prefixed_keys("target", $target_vol), my $target_data = {
vinfo_prefixed_keys("snapshot", $parent_snapshot), %svol_data,
vinfo_prefixed_keys("source", $svol), type => "backup",
status => $up_to_date ? "up-to-date" : undef, target_type => $target_vol->{CONFIG}{target_type}, # "send-receive" or "raw"
}; vinfo_prefixed_keys("target", $target_vol),
$target_up_to_date ||= $up_to_date; _btrbk_date => $target_vol->{node}{BTRBK_DATE},
} };
else {
# don't display all subvolumes in $droot, only the ones matching snapshot_name # incomplete received (garbled) subvolumes have no received_uuid (as of btrfs-progs v4.3.1).
if(vinfo_is_btrbk_snapshot($target_vol, $snapshot_name)) { # a subvolume in droot matching our naming is considered incomplete if received_uuid is not set!
if($incomplete_backup) { $stats_incomplete++; } else { $stats_orphaned++; } if($target_vol->{node}{received_uuid} eq '-') {
push @data, { type => "backup", $dstats{incomplete}++;
target_type => $target_vol->{CONFIG}{target_type}, # "send-receive" or "raw" $target_data->{status} = "incomplete";
# suppress "orphaned" status here (snapshot column is empty anyways) push @bdata, $target_data;
# status => ($incomplete_backup ? "incomplete" : "orphaned"), next;
status => ($incomplete_backup ? "incomplete" : undef),
vinfo_prefixed_keys("target", $target_vol),
vinfo_prefixed_keys("source", $svol),
};
}
}
} }
my $stats_total = $stats_correlated + $stats_incomplete + $stats_orphaned;
$stats_backups_total += $stats_total; foreach (@sdata) {
$stats_backups_total_correlated += $stats_correlated; if(_is_correlated($_->{_vinfo}{node}, $target_vol->{node})) {
$stats_backups_total_incomplete += $stats_incomplete; $target_data = {
$stats_backups_total_orphaned += $stats_orphaned; %$_,
my @stats_detail; %$target_data,
push @stats_detail, "up-to-date" if($target_up_to_date); type => "snapshot,backup",
push @stats_detail, "$stats_correlated correlated" if($stats_correlated); _correlated => 1,
push @stats_detail, "$stats_incomplete incomplete" if($stats_incomplete); };
my $stats_detail_print = join(', ', @stats_detail); $_->{_correlated} = 1;
$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; last;
} }
} }
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}++; }
} }
if(!$found) { push @ldata, $latest_backup;
my $latest_snapshot = $related_snapshots[0];
push @data, { type => "latest_snapshot", push @stdata, {
status => ($latest_snapshot && ($latest_snapshot->{node}{cgen} == $svol->{node}{gen})) ? "up-to-date" : undef, %svol_stats_data,
vinfo_prefixed_keys("source", $svol), %dstats,
vinfo_prefixed_keys("snapshot", $latest_snapshot), # all unset if no $latest_snapshot vinfo_prefixed_keys("target", $droot),
}; target_subvolume => "$droot->{PATH}/$snapshot_name.*",
} backup_status => $dstats{uptodate} ? "up-to-date" : "",
};
$stats{$_} += $dstats{$_} foreach(qw(backups correlated incomplete orphaned));
}
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;
} }
} }
} }
else {
die;
}
if($action_resolve eq "stats") { if($action_resolve eq "stats") {
print_header(title => "Statistics", my $filter = $config->{CMDLINE_FILTER_LIST} ? " (" . join(", ", @{$config->{CMDLINE_FILTER_LIST}}) . ")" : "";
config => $config, my @backup_total = map { $stats{$_} ? "$stats{$_} $_" : () } qw( correlated incomplete );
time => $start_time, my $bflags = @backup_total ? "(" . join(', ', @backup_total) . ")" : "";
legend => [
"up-to-date: latest snapshot/backup is up to date with source subvolume",
"correlated: corresponding (received-from) source snapshot is present",
],
);
print_table(\@stats_data, " "); print_formatted("stats", \@data, paragraph => 1);
print "\n"; print "Total${filter}:\n";
my $stats_filter = $config->{CMDLINE_FILTER_LIST} ? join("; ", @{$config->{CMDLINE_FILTER_LIST}}) : ""; print_formatted({ table => [ qw( a b c ) ], RALIGN => { a=>1 } },
my @stats_total_detail; [ { a => $stats{snapshots}, b => "snapshots", c => " " },
push @stats_total_detail, "$stats_backups_total_correlated correlated" if($stats_backups_total_correlated); { a => $stats{backups}, b => "backups", c => $bflags } ],
push @stats_total_detail, "$stats_backups_total_incomplete incomplete" if($stats_backups_total_incomplete); output_format => "table", no_header => 1);
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);
}
else { else {
print_formatted("resolved", \@data); print_formatted("resolved", \@data);
} }