File Coverage

blib/lib/DBIx/BatchChunker/LoopState.pm
Criterion Covered Total %
statement 52 52 100.0
branch 7 10 70.0
condition 6 9 66.6
subroutine 15 15 100.0
pod n/a
total 80 86 93.0


line stmt bran cond sub pod time code
1             package DBIx::BatchChunker::LoopState;
2              
3             our $AUTHORITY = 'cpan:GSG';
4             # ABSTRACT: Loop state object for DBIx::BatchChunker
5 9     9   135836 use version;
  9         42  
  9         80  
6             our $VERSION = 'v1.0.2'; # VERSION
7              
8 9     9   1513 use Moo;
  9         7726  
  9         77  
9 9     9   5865 use MooX::StrictConstructor;
  9         8474  
  9         86  
10              
11 9     9   39959 use Types::Standard qw( InstanceOf ArrayRef HashRef Int Str Num Maybe );
  9         89099  
  9         185  
12 9     9   51655 use Types::Numbers qw( UnsignedInt PositiveNum PositiveOrZeroNum FloatSafeNum );
  9         368543  
  9         107  
13 9     9   28782 use Type::Utils;
  9         11334  
  9         136  
14              
15 9     9   19009 use Math::BigInt upgrade => 'Math::BigFloat';
  9         23  
  9         114  
16 9     9   1022 use Math::BigFloat;
  9         19  
  9         107  
17 9     9   1848 use Time::HiRes qw( time );
  9         1578  
  9         100  
18              
19             # Don't export the above, but don't conflict with StrictConstructor, either
20 9     9   1439 use namespace::clean -except => [qw< new meta >];
  9         20530  
  9         115  
21              
22             #pod =encoding utf8
23             #pod
24             #pod =head1 SYNOPSIS
25             #pod
26             #pod sub chunk_method {
27             #pod my ($bc, $rs) = @_;
28             #pod
29             #pod my $loop_state = $bc->loop_state;
30             #pod # introspect stuff
31             #pod }
32             #pod
33             #pod =head1 DESCRIPTION
34             #pod
35             #pod This is the loop state object used during BatchChunker runs. It only exists within the
36             #pod BatchChunker execution loop, and would generally only be accessible through the coderef
37             #pod or method referenced within that loop.
38             #pod
39             #pod This is a quasi-private object and its API may be subject to change, but the module is
40             #pod in a pretty stable state at this point. While permissions are available to write to
41             #pod the attributes, it is highly recommended to not do so unless you know exactly what you
42             #pod doing. These are mostly available for introspection of loop progress.
43             #pod
44             #pod =head1 ATTRIBUTES
45             #pod
46             #pod =head2 batch_chunker
47             #pod
48             #pod Reference back to the parent L object.
49             #pod
50             #pod =cut
51              
52             has batch_chunker => (
53             is => 'ro',
54             isa => InstanceOf['DBIx::BatchChunker'],
55             required => 1,
56             weak_ref => 1,
57             );
58              
59             #pod =head2 progress_bar
60             #pod
61             #pod The progress bar being used in the loop. This may be different than
62             #pod L, since it could be auto-generated.
63             #pod
64             #pod If you're trying to access the progress bar for debug or display purposes, it's best to
65             #pod use this attribute:
66             #pod
67             #pod my $progress_bar = $bc->loop_state->progress_bar;
68             #pod $progress_bar->message('Found something here');
69             #pod
70             #pod =cut
71              
72             has progress_bar => (
73             is => 'rw',
74             isa => InstanceOf['Term::ProgressBar'],
75             required => 1,
76             );
77              
78             #pod =head2 total_timer
79             #pod
80             #pod Epoch timer for the start of the entire operation.
81             #pod
82             #pod =cut
83              
84             has total_timer => (
85             is => 'ro',
86             isa => PositiveNum,
87             lazy => 0, # must be set on creation
88             default => sub { time() },
89             );
90              
91             #pod =head2 chunk_timer
92             #pod
93             #pod Epoch timer for the start of each chunk.
94             #pod
95             #pod =for Pod::Coverage timer
96             #pod
97             #pod =cut
98              
99             has chunk_timer => (
100             is => 'rw',
101             isa => PositiveNum,
102             default => sub { time() },
103             );
104              
105             # Backwards-compatibility
106             *timer = \&chunk_timer;
107              
108 1446     1446   56410 sub _mark_chunk_timer { shift->chunk_timer(time); }
109              
110             #pod =head2 start
111             #pod
112             #pod The real start ID that the loop is currently on. May continue to exist within iterations
113             #pod if chunk resizing is trying to find a valid range. Otherwise, this value will become
114             #pod undef when a chunk is finally processed.
115             #pod
116             #pod =cut
117              
118             has start => (
119             is => 'rw',
120             isa => Maybe[UnsignedInt],
121             lazy => 1,
122             default => sub { shift->batch_chunker->min_id },
123             );
124              
125             #pod =head2 end
126             #pod
127             #pod The real end ID that the loop is currently looking at. This is always redefined at the
128             #pod beginning of the loop.
129             #pod
130             #pod =cut
131              
132             has end => (
133             is => 'rw',
134             isa => UnsignedInt,
135             lazy => 1,
136             default => sub {
137             my $self = shift;
138             $self->start + $self->batch_chunker->chunk_size - 1;
139             },
140             );
141              
142             #pod =head2 prev_end
143             #pod
144             #pod Last "processed" value of L. This also includes skipped blocks. Used in L
145             #pod calculations and to determine if the end of the loop has been reached.
146             #pod
147             #pod Initially C, if no blocks have been processed yet.
148             #pod
149             #pod =cut
150              
151             has prev_end => (
152             is => 'rw',
153             isa => Maybe[UnsignedInt],
154             lazy => 1,
155             default => undef,
156             );
157              
158             #pod =head2 last_range
159             #pod
160             #pod A hashref of min/max values used for the bisecting of one block, measured in chunk
161             #pod multipliers. Cleared out after a block has been processed or skipped.
162             #pod
163             #pod =cut
164              
165             has last_range => (
166             is => 'rw',
167             isa => HashRef,
168             default => sub { {} },
169             );
170              
171             #pod =head2 last_timings
172             #pod
173             #pod An arrayref of hashrefs, containing data for the previous 5 runs. This data is used for
174             #pod runtime targeting.
175             #pod
176             #pod =cut
177              
178             has last_timings => (
179             is => 'rw',
180             isa => ArrayRef,
181             default => sub { [] },
182             );
183              
184 9     9   236 sub _reset_last_timings { shift->last_timings([]) }
185              
186             #pod =head2 multiplier_range
187             #pod
188             #pod The range (in units of L) between the start and end IDs. This starts at 1
189             #pod (at the beginning of the loop), but may expand or shrink depending on chunk count checks.
190             #pod Resets after block processing.
191             #pod
192             #pod =cut
193              
194             has multiplier_range => (
195             is => 'rw',
196             isa => FloatSafeNum,
197             lazy => 1,
198             default => sub {
199             shift->batch_chunker->_use_bignums ? Math::BigFloat->new(0) : 0;
200             },
201             );
202              
203             #pod =head2 multiplier_step
204             #pod
205             #pod Determines how fast L increases, so that chunk resizing happens at an
206             #pod accelerated pace. Speeds or slows depending on what kind of limits the chunk count
207             #pod checks are hitting. Resets after block processing.
208             #pod
209             #pod =cut
210              
211             has multiplier_step => (
212             is => 'rw',
213             isa => FloatSafeNum,
214             lazy => 1,
215             default => sub {
216             shift->batch_chunker->_use_bignums ? Math::BigFloat->new(1) : 1;
217             },
218             );
219              
220             sub _increase_multiplier {
221 66     66   164 my $ls = shift;
222 66         1440 my $lr = $ls->last_range;
223              
224 66 50 66     2978 $lr->{min} = $ls->multiplier_range if !defined $lr->{min} || $ls->multiplier_range > $lr->{min};
225              
226             # If we have a min/max range, bisect down the middle, which will now be higher than the
227             # previous minimum. If not, keep accelerating the stepping.
228             $ls->multiplier_step(
229 66 50       2159 defined $lr->{max} ? ($lr->{max} - $lr->{min}) / 2 : $ls->multiplier_step * 2
230             );
231             }
232              
233             sub _decrease_multiplier {
234 86     86   239 my $ls = shift;
235 86         2253 my $lr = $ls->last_range;
236              
237 86 50 66     3353 $lr->{max} = $ls->multiplier_range if !defined $lr->{max} || $ls->multiplier_range < $lr->{max};
238              
239             # If we have a min/max range, bisect down the middle. If not, walk back to the previous range
240             # and decelerate the stepping, which should bring it to a halfway point from this range and
241             # last.
242 86   66     12806 $ls->multiplier_range( $lr->{min} || ($ls->multiplier_range - $ls->multiplier_step) );
243             $ls->multiplier_step(
244 86 100       175696 defined $lr->{min} ? ($lr->{max} - $lr->{min}) / 2 : $ls->multiplier_step / 2
245             );
246             }
247              
248             #pod =head2 checked_count
249             #pod
250             #pod A check counter to make sure the chunk resizing isn't taking too long. After ten checks,
251             #pod it will give up, assuming the block is safe to process.
252             #pod
253             #pod =cut
254              
255             has checked_count => (
256             is => 'rw',
257             isa => Int,
258             default => 0,
259             );
260              
261             #pod =head2 chunk_size
262             #pod
263             #pod The I chunk size, which might be adjusted by runtime targeting.
264             #pod
265             #pod =cut
266              
267             has chunk_size => (
268             is => 'rw',
269             isa => UnsignedInt,
270             lazy => 1,
271             default => sub { shift->batch_chunker->chunk_size },
272             );
273              
274             #pod =head2 chunk_count
275             #pod
276             #pod Records the results of the C query for chunk resizing.
277             #pod
278             #pod =cut
279              
280             has chunk_count => (
281             is => 'rw',
282             isa => Maybe[UnsignedInt],
283             default => undef,
284             );
285              
286             #pod =head2 prev_check
287             #pod
288             #pod A short string recording what happened during the last chunk resizing check. Exists
289             #pod purely for debugging purposes.
290             #pod
291             #pod =cut
292              
293             has prev_check => (
294             is => 'rw',
295             isa => Str,
296             default => '',
297             );
298              
299             #pod =head2 prev_runtime
300             #pod
301             #pod The number of seconds the previously processed chunk took to run, not including sleep
302             #pod time.
303             #pod
304             #pod =cut
305              
306             has prev_runtime => (
307             is => 'rw',
308             isa => Maybe[PositiveOrZeroNum],
309             default => undef,
310             );
311              
312             sub _reset_chunk_state {
313 582     582   1896 my $ls = shift;
314 582         31683 $ls->start (undef);
315 582         47526 $ls->prev_end($ls->end);
316 582         256300 $ls->_mark_chunk_timer;
317              
318 582         58345 $ls->last_range ({});
319 582         47475 $ls->multiplier_range(0);
320 582         43708 $ls->multiplier_step (1);
321 582         46836 $ls->checked_count (0);
322              
323 582 100       58421 if ($ls->batch_chunker->_use_bignums) {
324 101         1664 $ls->multiplier_range( Math::BigFloat->new(0) );
325 101         236382 $ls->multiplier_step ( Math::BigFloat->new(1) );
326             }
327             }
328              
329             1;
330              
331             __END__