| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Date::RetentionPolicy; |
|
2
|
|
|
|
|
|
|
$Date::RetentionPolicy::VERSION = '0.01'; |
|
3
|
3
|
|
|
3
|
|
3343
|
use Moo; |
|
|
3
|
|
|
|
|
29733
|
|
|
|
3
|
|
|
|
|
13
|
|
|
4
|
3
|
|
|
3
|
|
3821
|
use Scalar::Util 'looks_like_number'; |
|
|
3
|
|
|
|
|
6
|
|
|
|
3
|
|
|
|
|
130
|
|
|
5
|
3
|
|
|
3
|
|
2338
|
use DateTime; |
|
|
3
|
|
|
|
|
1283521
|
|
|
|
3
|
|
|
|
|
123
|
|
|
6
|
3
|
|
|
3
|
|
1681
|
use DateTime::Format::Flexible; |
|
|
3
|
|
|
|
|
365781
|
|
|
|
3
|
|
|
|
|
35
|
|
|
7
|
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
# ABSTRACT: Prune a list of dates down to the ones you want to keep |
|
9
|
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
has retain => ( is => 'rw', required => 1 ); |
|
12
|
|
|
|
|
|
|
has time_zone => ( is => 'rw', default => sub { 'floating' } ); |
|
13
|
|
|
|
|
|
|
has reach_factor => ( is => 'rw', default => sub { .5 } ); |
|
14
|
|
|
|
|
|
|
has reference_date => ( is => 'rw' ); |
|
15
|
|
|
|
|
|
|
has auto_sync => ( is => 'rw' ); |
|
16
|
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
sub reference_date_or_default { |
|
18
|
341
|
|
|
341
|
1
|
829
|
my $self= shift; |
|
19
|
|
|
|
|
|
|
# Use override, else 'now' rounded up to next day boundary of timezone |
|
20
|
341
|
|
|
|
|
1076
|
my $start= $self->reference_date; |
|
21
|
341
|
50
|
|
|
|
1008
|
return $start->clone if ref $start; |
|
22
|
341
|
50
|
|
|
|
1852
|
return $self->_coerce_date($start) if defined $start; |
|
23
|
0
|
|
|
|
|
0
|
return DateTime->now(time_zone => $self->time_zone) |
|
24
|
|
|
|
|
|
|
->add(days => 1, seconds => -1)->truncate(to => 'day'); |
|
25
|
0
|
|
|
|
|
0
|
return $start; |
|
26
|
|
|
|
|
|
|
} |
|
27
|
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
sub prune { |
|
30
|
341
|
|
|
341
|
0
|
487977
|
my ($self, $list)= @_; |
|
31
|
341
|
|
|
|
|
1431
|
my $processed= $self->_sort_and_mark_retention($list); |
|
32
|
|
|
|
|
|
|
# Divide the elements into two lists. Make a set of which indexes |
|
33
|
|
|
|
|
|
|
# we're keeping, then iterate the original list to preserve the caller's |
|
34
|
|
|
|
|
|
|
# list order. |
|
35
|
341
|
|
|
|
|
976
|
my (@retain, @prune); |
|
36
|
341
|
|
|
|
|
22948
|
my %keep= map +($_->[1] => 1), grep $_->[2], @$processed; |
|
37
|
21765
|
100
|
|
|
|
43224
|
push @{ $keep{$_}? \@retain : \@prune }, $list->[$_] |
|
38
|
341
|
|
|
|
|
2950
|
for 0..$#$list; |
|
39
|
341
|
|
|
|
|
1963
|
@$list= @retain; |
|
40
|
341
|
|
|
|
|
6533
|
return \@prune; |
|
41
|
|
|
|
|
|
|
} |
|
42
|
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
sub _sort_and_mark_retention { |
|
44
|
341
|
|
|
341
|
|
1007
|
my ($self, $list, $trace)= @_; |
|
45
|
|
|
|
|
|
|
# Each list element needs to be a date object, (but preserve the original) |
|
46
|
|
|
|
|
|
|
# and the list needs to be sorted in cronological order. |
|
47
|
341
|
|
|
|
|
2726
|
my @sorted= sort { $a->[0] <=> $b->[0] } |
|
|
21425
|
|
|
|
|
32266
|
|
|
48
|
|
|
|
|
|
|
# tuple of [ Epoch, ListIndex, KeepBoolean ]. |
|
49
|
|
|
|
|
|
|
# A hash would be more readable but there could be a lot of these. |
|
50
|
|
|
|
|
|
|
map [ $self->_coerce_to_epoch($list->[$_]), $_, 0 ], |
|
51
|
|
|
|
|
|
|
0..$#$list; |
|
52
|
|
|
|
|
|
|
# Never prune things newer than the reference date |
|
53
|
341
|
|
|
|
|
2575
|
my $ref_date= $self->reference_date_or_default; |
|
54
|
341
|
|
66
|
|
|
2396
|
for (my $i= $#sorted; $i >= 0 && $sorted[$i][0] > $ref_date->epoch; --$i) { |
|
55
|
4636
|
|
|
|
|
31457
|
$sorted[$i][2]= 1; |
|
56
|
|
|
|
|
|
|
} |
|
57
|
|
|
|
|
|
|
# Set the boolean to true for each element that a rule wants to keep |
|
58
|
|
|
|
|
|
|
$self->_mark_for_retention($ref_date, $_, \@sorted, $trace) |
|
59
|
341
|
|
|
|
|
2292
|
for @{ $self->retain }; |
|
|
341
|
|
|
|
|
1966
|
|
|
60
|
341
|
|
|
|
|
5912
|
return \@sorted; |
|
61
|
|
|
|
|
|
|
} |
|
62
|
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
sub _mark_for_retention { |
|
64
|
1019
|
|
|
1019
|
|
11982
|
my ($self, $reference_date, $rule, $list, $trace)= @_; |
|
65
|
1019
|
|
|
|
|
2291
|
my ($interval, $history, $reach_factor)= @{$rule}{'interval','history','reach_factor'}; |
|
|
1019
|
|
|
|
|
3443
|
|
|
66
|
1019
|
50
|
|
|
|
4161
|
$reach_factor= $self->reach_factor unless defined $reach_factor; |
|
67
|
1019
|
|
|
|
|
3841
|
my $next_date= $reference_date->clone->subtract(%$history)->add(%$interval); |
|
68
|
1019
|
|
|
|
|
2105921
|
my $epoch= $next_date->epoch; |
|
69
|
1019
|
|
|
|
|
8584
|
my $search_idx= 0; |
|
70
|
1019
|
|
|
|
|
3500
|
my $next_epoch= $next_date->add(%$interval)->epoch; |
|
71
|
1019
|
|
|
|
|
969377
|
my $radius= -($epoch - $next_epoch) * $reach_factor; |
|
72
|
1019
|
|
|
|
|
2322
|
my $drift= 0; # only used for auto_sync |
|
73
|
1019
|
|
|
|
|
1623
|
my $rule_key; |
|
74
|
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
# The epoch variables track the current date interval, and the _idx |
|
76
|
|
|
|
|
|
|
# variables track our position in the list. |
|
77
|
1019
|
|
66
|
|
|
2745
|
while ($epoch-abs($drift) <= $reference_date->epoch && $search_idx < @$list) { |
|
78
|
24256
|
|
|
|
|
197099
|
my $best; |
|
79
|
24256
|
|
100
|
|
|
99736
|
for (my $i= $search_idx; $i < @$list and $list->[$i][0] < $epoch+$drift+$radius; ++$i) { |
|
80
|
102115
|
100
|
100
|
|
|
288804
|
if ($list->[$i][0] >= $epoch+$drift-$radius |
|
|
|
|
100
|
|
|
|
|
|
81
|
|
|
|
|
|
|
and (!defined $best or abs($list->[$i][0] - ($epoch+$drift)) < abs($list->[$best][0] - ($epoch+$drift))) |
|
82
|
|
|
|
|
|
|
) { |
|
83
|
35970
|
|
|
|
|
52045
|
$best= $i; |
|
84
|
|
|
|
|
|
|
} |
|
85
|
|
|
|
|
|
|
# update the start_idx for next interval iteration |
|
86
|
102115
|
100
|
|
|
|
374452
|
$search_idx= $i+1 if $list->[$i][0] < $next_epoch-$radius*2; |
|
87
|
|
|
|
|
|
|
} |
|
88
|
24256
|
100
|
|
|
|
49643
|
if (defined $best) { |
|
89
|
20160
|
|
|
|
|
35213
|
$list->[$best][2]= 1; # mark as a keeper |
|
90
|
|
|
|
|
|
|
# If option enabled, drift toward the time we found, so that gap between next |
|
91
|
|
|
|
|
|
|
# is closer to $interval |
|
92
|
20160
|
100
|
|
|
|
66169
|
$drift= $list->[$best][0] - $epoch |
|
93
|
|
|
|
|
|
|
if $self->auto_sync; |
|
94
|
|
|
|
|
|
|
} |
|
95
|
24256
|
50
|
|
|
|
48262
|
if ($trace) { |
|
96
|
0
|
0
|
|
|
|
0
|
$rule_key= join ',', map "$_=$interval->{$_}", keys %$interval |
|
97
|
|
|
|
|
|
|
unless defined $rule_key; |
|
98
|
0
|
0
|
|
|
|
0
|
if (!$trace->{$rule_key}) { |
|
99
|
0
|
|
|
|
|
0
|
$trace->{$rule_key}{idx}= scalar keys %$trace; |
|
100
|
0
|
|
|
|
|
0
|
$trace->{$rule_key}{radius}= $radius; |
|
101
|
0
|
|
|
|
|
0
|
$trace->{$rule_key}{name}= $rule_key; |
|
102
|
|
|
|
|
|
|
} |
|
103
|
0
|
|
|
|
|
0
|
push @{$trace->{$rule_key}{interval}}, { epoch => $epoch, best => $best, drift => $drift }; |
|
|
0
|
|
|
|
|
0
|
|
|
104
|
|
|
|
|
|
|
} |
|
105
|
24256
|
|
|
|
|
34273
|
$epoch= $next_epoch; |
|
106
|
24256
|
|
|
|
|
74411
|
$next_epoch= $next_date->add(%$interval)->epoch; |
|
107
|
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
# if auto_sync enabled, cause drift to decay back toward 0 |
|
109
|
24256
|
100
|
|
|
|
23132595
|
$drift= int($drift * 7/8) |
|
110
|
|
|
|
|
|
|
if $drift; |
|
111
|
|
|
|
|
|
|
} |
|
112
|
|
|
|
|
|
|
} |
|
113
|
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
sub visualize { |
|
116
|
0
|
|
|
0
|
1
|
0
|
my ($self, $list)= @_; |
|
117
|
0
|
|
|
|
|
0
|
my $trace= {}; |
|
118
|
0
|
|
|
|
|
0
|
my $processed= $self->_sort_and_mark_retention($list, $trace); |
|
119
|
0
|
|
|
|
|
0
|
$processed->[$_][1]= $_ for 0..$#$processed; # change indexes to index within processed list |
|
120
|
0
|
|
|
|
|
0
|
my @claimed; |
|
121
|
0
|
|
|
|
|
0
|
my @things= @$processed; |
|
122
|
0
|
|
|
|
|
0
|
my @columns; |
|
123
|
|
|
|
|
|
|
my %rule_to_col; |
|
124
|
|
|
|
|
|
|
# Convert each trace to a similar arrayref structure as the processed items, for sorting |
|
125
|
0
|
|
|
|
|
0
|
for my $rule_trace (sort { $a->{idx} <=> $b->{idx} } values %$trace) { |
|
|
0
|
|
|
|
|
0
|
|
|
126
|
0
|
|
|
|
|
0
|
push @columns, { name => $rule_trace->{name}, width => 20 }; |
|
127
|
0
|
|
|
|
|
0
|
$rule_to_col{$rule_trace->{name}}= $#columns; |
|
128
|
0
|
|
|
|
|
0
|
for (@{ $rule_trace->{interval} }) { |
|
|
0
|
|
|
|
|
0
|
|
|
129
|
0
|
0
|
|
|
|
0
|
push @{$claimed[$_->{best}]}, $rule_trace->{name} if defined $_->{best}; |
|
|
0
|
|
|
|
|
0
|
|
|
130
|
|
|
|
|
|
|
push @things, |
|
131
|
|
|
|
|
|
|
[ $_->{epoch} + $_->{drift} + $rule_trace->{radius}, 'ival-newest', $rule_trace->{name} ], |
|
132
|
|
|
|
|
|
|
[ $_->{epoch} + $_->{drift}, 'ival', $rule_trace->{name} ], |
|
133
|
0
|
|
|
|
|
0
|
[ $_->{epoch} + $_->{drift} - $rule_trace->{radius}, 'ival-oldest', $rule_trace->{name} ]; |
|
134
|
|
|
|
|
|
|
} |
|
135
|
|
|
|
|
|
|
} |
|
136
|
0
|
|
|
|
|
0
|
push @columns, { name => 'timestamp', width => 20 }; |
|
137
|
0
|
|
|
|
|
0
|
@things= sort { $a->[0] <=> $b->[0] } @things; |
|
|
0
|
|
|
|
|
0
|
|
|
138
|
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
# Walk from oldest to newest, displaying timestamps alongside the epock interval points |
|
140
|
0
|
|
|
|
|
0
|
my $cur_time= 0; |
|
141
|
0
|
|
|
|
|
0
|
my @in_interval= ( 0 ) x @columns; |
|
142
|
0
|
|
|
|
|
0
|
my @row= map $_->{name}, @columns; |
|
143
|
0
|
|
|
|
|
0
|
my $format= join(' ', map '%-'.$_->{width}.'s', @columns)."\n"; |
|
144
|
0
|
|
|
|
|
0
|
my $out= ''; |
|
145
|
|
|
|
|
|
|
my $emit= sub { |
|
146
|
|
|
|
|
|
|
# if in_interval, display a vertical bar as a graphic |
|
147
|
0
|
|
|
0
|
|
0
|
for (0..$#in_interval) { |
|
148
|
0
|
0
|
0
|
|
|
0
|
$row[$_] ||= '|' if $in_interval[$_]; |
|
149
|
|
|
|
|
|
|
} |
|
150
|
0
|
|
|
|
|
0
|
$out .= sprintf $format, @row; |
|
151
|
0
|
|
|
|
|
0
|
@row= ('') x @columns; |
|
152
|
0
|
|
|
|
|
0
|
}; |
|
153
|
0
|
|
|
|
|
0
|
for (@things) { |
|
154
|
0
|
0
|
|
|
|
0
|
$emit->() if $cur_time != $_->[0]; |
|
155
|
0
|
|
|
|
|
0
|
$cur_time= $_->[0]; |
|
156
|
0
|
0
|
|
|
|
0
|
if ($_->[1] eq 'ival') { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
157
|
0
|
|
|
|
|
0
|
$row[ $rule_to_col{ $_->[2] } ]= $self->_coerce_date($_->[0]); |
|
158
|
|
|
|
|
|
|
} elsif ($_->[1] eq 'ival-newest') { |
|
159
|
0
|
|
|
|
|
0
|
$row[ $rule_to_col{ $_->[2] } ]= '---'; |
|
160
|
0
|
|
|
|
|
0
|
--$in_interval[ $rule_to_col{ $_->[2] } ]; |
|
161
|
|
|
|
|
|
|
} elsif ($_->[1] eq 'ival-oldest') { |
|
162
|
0
|
|
|
|
|
0
|
$row[ $rule_to_col{ $_->[2] } ]= '---'; |
|
163
|
0
|
|
|
|
|
0
|
++$in_interval[ $rule_to_col{ $_->[2] } ]; |
|
164
|
|
|
|
|
|
|
} else { |
|
165
|
0
|
0
|
|
|
|
0
|
$row[-1]= $self->_coerce_date($_->[0]).($_->[2]? ' +':' x'); |
|
166
|
0
|
0
|
|
|
|
0
|
if ($claimed[$_->[1]]) { |
|
167
|
0
|
|
|
|
|
0
|
$row[-1] .= ' '.join ', ', @{ $claimed[$_->[1]] }; |
|
|
0
|
|
|
|
|
0
|
|
|
168
|
|
|
|
|
|
|
} |
|
169
|
|
|
|
|
|
|
} |
|
170
|
|
|
|
|
|
|
} |
|
171
|
0
|
|
|
|
|
0
|
$emit->(); |
|
172
|
0
|
|
|
|
|
0
|
return $out; |
|
173
|
|
|
|
|
|
|
} |
|
174
|
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
sub _coerce_date { |
|
176
|
1801
|
|
|
1801
|
|
3394
|
my ($self, $thing)= @_; |
|
177
|
1801
|
100
|
33
|
|
|
10033
|
my $date= ref $thing && ref($thing)->can('set_time_zone')? $_->clone |
|
|
|
50
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
: looks_like_number($thing)? DateTime->from_epoch(epoch => $thing) |
|
179
|
|
|
|
|
|
|
: DateTime::Format::Flexible->parse_datetime($thing); |
|
180
|
1801
|
|
|
|
|
10330693
|
$date->set_time_zone($self->time_zone); |
|
181
|
1801
|
|
|
|
|
77681
|
return $date; |
|
182
|
|
|
|
|
|
|
} |
|
183
|
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
sub _coerce_to_epoch { |
|
185
|
21765
|
|
|
21765
|
|
50909
|
my ($self, $thing)= @_; |
|
186
|
21765
|
100
|
66
|
|
|
88729
|
return $thing if !ref $thing && looks_like_number($thing); |
|
187
|
1460
|
|
|
|
|
3395
|
return $self->_coerce_date($thing)->epoch; |
|
188
|
|
|
|
|
|
|
} |
|
189
|
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
1; |
|
191
|
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
__END__ |
|
193
|
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
=pod |
|
195
|
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
=encoding UTF-8 |
|
197
|
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
=head1 NAME |
|
199
|
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
Date::RetentionPolicy - Prune a list of dates down to the ones you want to keep |
|
201
|
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
=head1 VERSION |
|
203
|
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
version 0.01 |
|
205
|
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
207
|
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
my $rp= Date::RetentionPolicy->new( |
|
209
|
|
|
|
|
|
|
retain => [ |
|
210
|
|
|
|
|
|
|
{ interval => { hours => 6 }, history => { months => 3 } }, |
|
211
|
|
|
|
|
|
|
{ interval => { days => 1 }, history => { months => 6 } }, |
|
212
|
|
|
|
|
|
|
{ interval => { days => 7 }, history => { months => 9 } }, |
|
213
|
|
|
|
|
|
|
] |
|
214
|
|
|
|
|
|
|
); |
|
215
|
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
my $dates= [ '2018-01-01 03:23:00', '2018-01-01 09:45:00', ... ]; |
|
217
|
|
|
|
|
|
|
my $pruned= $rp->prune($dates); |
|
218
|
|
|
|
|
|
|
for (@$pruned) { |
|
219
|
|
|
|
|
|
|
# delete the backup dated $_ |
|
220
|
|
|
|
|
|
|
... |
|
221
|
|
|
|
|
|
|
} |
|
222
|
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
224
|
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
Often when making backups of a thing, you want to have more frequent snapshots |
|
226
|
|
|
|
|
|
|
for recent dates, but don't need that frequency further back in time, and want |
|
227
|
|
|
|
|
|
|
to delete some of the older ones to save space. |
|
228
|
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
The problem of deciding which snapshots to delete is non-trivial because |
|
230
|
|
|
|
|
|
|
backups often don't complete on a timely schedule (despite being started on |
|
231
|
|
|
|
|
|
|
a schedule) or have discontinuities from production mishaps, and it would be |
|
232
|
|
|
|
|
|
|
bad if your script wiped out the only backup in an interval just because it |
|
233
|
|
|
|
|
|
|
didn't look like one of the "main" timestamps. Also it would be bad if the |
|
234
|
|
|
|
|
|
|
jitter from the time zone or time of day that you run the pruning process |
|
235
|
|
|
|
|
|
|
caused the script to round differently and delete the backups it had |
|
236
|
|
|
|
|
|
|
previously decided to keep. |
|
237
|
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
This module uses an algorithm where you first define the intervals which |
|
239
|
|
|
|
|
|
|
should retain a backup, then assign the existing timestamps to those intervals |
|
240
|
|
|
|
|
|
|
(possibly reaching across the interval boundary a bit in order to preserve |
|
241
|
|
|
|
|
|
|
a nearby timestamp; see L<reach_factor>) thus making an intelligent decision |
|
242
|
|
|
|
|
|
|
about which timestamps to keep. |
|
243
|
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
=head1 DATES |
|
245
|
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
This module currently depends on DateTime, but I'm happy to accept patches |
|
247
|
|
|
|
|
|
|
to allow it to work with other Date classes. |
|
248
|
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
=head1 ATTRIBUTES |
|
250
|
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
=head2 retain |
|
252
|
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
An arrayref of specifications for what to preserve. Each element should be a |
|
254
|
|
|
|
|
|
|
hashref containing C<history> and C<interval>. C<history> specifies how far |
|
255
|
|
|
|
|
|
|
backward from L</reference_date> to apply the intervals, and C<interval> |
|
256
|
|
|
|
|
|
|
specifies the time difference between the backups that need preserved. |
|
257
|
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
As an example, consider |
|
259
|
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
retain => [ |
|
261
|
|
|
|
|
|
|
{ interval => { days => 1 }, history => { days => 20 } }, |
|
262
|
|
|
|
|
|
|
{ interval => { hours => 1 }, history => { hours => 48 } }, |
|
263
|
|
|
|
|
|
|
] |
|
264
|
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
This will attempt to preserve timestamps near the marks of L</reference_date>, |
|
266
|
|
|
|
|
|
|
an hour before that, an hour before that, and so on for the past 48 hours. |
|
267
|
|
|
|
|
|
|
It will also attempt to preserve L</reference_date>, a day before that, a day |
|
268
|
|
|
|
|
|
|
before that, and so on for the past 20 days. |
|
269
|
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
There is another setting called L</reach_factor> that determines how far from |
|
271
|
|
|
|
|
|
|
the desired timestamp the algorithm will look for something to preserve. The |
|
272
|
|
|
|
|
|
|
default C<reach_factor> of C<0.5> means that it will scan from half an interval |
|
273
|
|
|
|
|
|
|
back in time until half an interval forward in time looking for the closest |
|
274
|
|
|
|
|
|
|
timestamp to preserve. In some cases, you may want a narrower or wider search |
|
275
|
|
|
|
|
|
|
distance, and you can set C<reach_factor> accordingly. You can also supply it |
|
276
|
|
|
|
|
|
|
as another hash key for a retain rule for per-rule customization. |
|
277
|
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
retain => [ |
|
279
|
|
|
|
|
|
|
{ interval => { days => 1 }, history => { days => 20 }, reach_factor => .75 } |
|
280
|
|
|
|
|
|
|
] |
|
281
|
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
=head2 time_zone |
|
283
|
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
When date strings are involved, parse them as this time zone before converting |
|
285
|
|
|
|
|
|
|
to an epoch value used in the calculations. The default is C<'floating'>. |
|
286
|
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
=head2 reach_factor |
|
288
|
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
The multiplier for how far to look in each direction from an interval point. |
|
290
|
|
|
|
|
|
|
See discussion in L</retain>. |
|
291
|
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
=head2 reference_date |
|
293
|
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
The end-point from which all intervals will be calculated. There is no |
|
295
|
|
|
|
|
|
|
default, to allow L</reference_date_or_default> to always pick up the current |
|
296
|
|
|
|
|
|
|
time when called. |
|
297
|
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
=head2 reference_date_or_default |
|
299
|
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
Read-only. Return (a clone of) L</reference_date>, or if it isn't set, return |
|
301
|
|
|
|
|
|
|
the current date in the designated L</time_zone> rounded up to the next day |
|
302
|
|
|
|
|
|
|
boundary. |
|
303
|
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
=head2 auto_sync |
|
305
|
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
While walking backward through time intervals looking for backups, adjust the |
|
307
|
|
|
|
|
|
|
interval endpoint to be closer to whatever match it found. This might allow |
|
308
|
|
|
|
|
|
|
the algorithm to essentially adjust the C<reference_date> to match whatever |
|
309
|
|
|
|
|
|
|
schedule your backups are running on. This is not enabled by default. |
|
310
|
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
=head1 METHODS |
|
312
|
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
=head1 prune |
|
314
|
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
my $pruned_arrayref= $self->prune( \@times ); |
|
316
|
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
C<@times> may be an array of epoch numbers, DateTime objects, or date strings |
|
318
|
|
|
|
|
|
|
in any format recognized by L<DateTime::Format::Flexible>. Epochs are |
|
319
|
|
|
|
|
|
|
currently the most efficient type of argument since that's what the algorithm |
|
320
|
|
|
|
|
|
|
operates on. |
|
321
|
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
=head2 visualize |
|
323
|
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
print $rp->visualize( \@list ); |
|
325
|
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
This method takes a list of timestamps, sorts and marks them for retention, |
|
327
|
|
|
|
|
|
|
and then returns printable text showing the retention intervals and which |
|
328
|
|
|
|
|
|
|
increment it decided to keep. The text is simple ascii-art, and requires |
|
329
|
|
|
|
|
|
|
a monospace font to display correctly. |
|
330
|
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
=head1 AUTHOR |
|
332
|
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
Michael Conrad <mconrad@intellitree.com> |
|
334
|
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
|
336
|
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
This software is copyright (c) 2018 by IntelliTree Solutions llc. |
|
338
|
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
This is free software; you can redistribute it and/or modify it under |
|
340
|
|
|
|
|
|
|
the same terms as the Perl 5 programming language system itself. |
|
341
|
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
=cut |