File Coverage

blib/lib/Data/Throttler_CHI.pm
Criterion Covered Total %
statement 37 49 75.5
branch 10 20 50.0
condition 2 4 50.0
subroutine 6 7 85.7
pod 2 2 100.0
total 57 82 69.5


line stmt bran cond sub pod time code
1             package Data::Throttler_CHI;
2              
3             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
4             our $DATE = '2020-02-19'; # DATE
5             our $DIST = 'Data-Throttler_CHI'; # DIST
6             our $VERSION = '0.003'; # VERSION
7              
8 1     1   100242 use strict;
  1         12  
  1         31  
9 1     1   5 use warnings;
  1         2  
  1         77  
10 1     1   1683 use Log::ger;
  1         51  
  1         5  
11              
12 1     1   246 use List::Util qw(sum);
  1         2  
  1         627  
13              
14             sub new {
15 1     1 1 112712 my ($class, %args) = @_;
16              
17 1 50       6 defined $args{max_items} or die "new: Please specify max_items";
18 1 50       7 $args{max_items} >= 1 or die "new: max_items must be at least 1";
19 1 50       3 defined $args{interval} or die "new: Please specify interval";
20 1 50       4 $args{interval} >= 1 or die "new: interval must be at least 1";
21 1 50       3 defined $args{cache} or die "new: Please specify cache";
22              
23             # calculate nof_buckets
24 1         2 my $nof_buckets;
25 1 50       4 if (defined $args{nof_buckets}) {
26 0 0       0 $args{nof_buckets} >= 1 or die "new: nof_buckets must be at least 1";
27 0         0 $nof_buckets = $args{nof_buckets};
28             } else {
29 1         8 $nof_buckets = $args{interval} ** 0.5;
30             }
31 1         4 $nof_buckets = int($nof_buckets);
32             #log_trace "nof_buckets: $nof_buckets";
33              
34             # XXX warn if accuracy (interval/nof_buckets) is too low (e.g. 5 min?)
35              
36             my $self = {
37             t0 => time(),
38             max_items => $args{max_items},
39             interval => $args{interval},
40             cache => $args{cache},
41             nof_buckets => $nof_buckets,
42 1         6 secs_per_bucket => $args{interval} / $nof_buckets,
43             };
44 1         6 bless $self, $class;
45             }
46              
47             sub _print_buckets {
48 0     0   0 require Data::Dmp;
49              
50 0         0 my ($self, $now) = @_;
51              
52 0         0 my $all_hits = $self->{cache}->get_multi_arrayref([map {"hits.$_"} 1..$self->{nof_buckets}]);
  0         0  
53 0   0     0 my $total_hits = sum(grep {defined} @$all_hits) || 0;
54              
55 0 0       0 my $all_expires_in = [map {my $e = $self->{cache}->get_expires_at("hits.$_"); defined($e) ? $e-$now : undef} 1..$self->{nof_buckets}];
  0         0  
  0         0  
56              
57 0         0 print " hits : ",Data::Dmp::dmp($all_hits)," total: $total_hits\n";
58 0         0 print " expires_in: ",Data::Dmp::dmp($all_expires_in), "\n";
59             }
60              
61             sub try_push {
62 6     6 1 21 my $self = shift;
63              
64 6         14 my $now = time();
65              
66 6         23 my $secs_after_latest_interval = ($now - $self->{t0}) % $self->{interval};
67             my $bucket_num = int(
68             $secs_after_latest_interval / $self->{interval} * $self->{nof_buckets}
69 6         22 ) + 1; # 1 .. nof_buckets
70              
71 6         40 my $hits = $self->{cache}->get("hits.$bucket_num");
72              
73             my $all_hits = $self->{cache}->get_multi_arrayref(
74 6         696 [map {"hits.$_"} 1..$self->{nof_buckets}]);
  6         38  
75 6   100     661 my $total_hits = sum(grep {defined} @$all_hits) || 0;
76              
77             #$self->_print_buckets($now);
78 6 100       28 return 0 if $total_hits >= $self->{max_items};
79              
80 4 100       10 if ($hits) {
81             $self->{cache}->set(
82             "hits.$bucket_num", $hits+1,
83 2         14 {expires_at=>$self->{cache}->get_expires_at("hits.$bucket_num")});
84             } else {
85             $self->{cache}->set(
86             "hits.$bucket_num", 1,
87 2         19 {expires_at => $now + $self->{interval} - $secs_after_latest_interval + ($bucket_num-1) * $self->{secs_per_bucket}});
88             }
89              
90             #$self->_print_buckets($now);
91 4         761 1;
92             }
93              
94             1;
95             # ABSTRACT: Data::Throttler-like throttler with CHI backend
96              
97             __END__
98              
99             =pod
100              
101             =encoding UTF-8
102              
103             =head1 NAME
104              
105             Data::Throttler_CHI - Data::Throttler-like throttler with CHI backend
106              
107             =head1 VERSION
108              
109             This document describes version 0.003 of Data::Throttler_CHI (from Perl distribution Data-Throttler_CHI), released on 2020-02-19.
110              
111             =head1 SYNOPSIS
112              
113             use Data::Throttler_CHI;
114             use CHI;
115              
116             my $throttler = Data::Throttler_CHI->new(
117             max_items => 100,
118             interval => 3600,
119             cache => CHI->new(driver=>"Memory", datastore=>{}),
120             #nof_buckets => 100, # optional, default: int(sqrt(interval))
121             );
122              
123             if ($throttle->try_push) {
124             print "Item can be pushed\n";
125             } else {
126             print "Item must wait\n";
127             }
128              
129             =head1 DESCRIPTION
130              
131             EXPERIMENTAL, PROOF OF CONCEPT.
132              
133             This module tries to use L<CHI> as the backend for data throttling. It presents
134             an interface similar to, but simpler than, L<Data::Throttler>.
135              
136             =head1 METHODS
137              
138             =head2 new
139              
140             Usage:
141              
142             my $throttler = Data::Throttler_CHI->new(%args);
143              
144             Known arguments (C<*> means required):
145              
146             =over
147              
148             =item * max_items*
149              
150             =item * interval*
151              
152             =item * cache*
153              
154             CHI instance.
155              
156             =item * nof_buckets
157              
158             Optional. Int. Number of buckets. By default calculated using:
159             int(sqrt(interval)).
160              
161             =back
162              
163             =head2 try_push
164              
165             Usage:
166              
167             $bool = $throttler->try_push(%args);
168              
169             Return 1 if data can be pushed, or 0 if it must wait.
170              
171             Known arguments:
172              
173             =over
174              
175             =back
176              
177             =head1 HOMEPAGE
178              
179             Please visit the project's homepage at L<https://metacpan.org/release/Data-Throttler_CHI>.
180              
181             =head1 SOURCE
182              
183             Source repository is at L<https://github.com/perlancar/perl-Data-Throttler_CHI>.
184              
185             =head1 BUGS
186              
187             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Data-Throttler_CHI>
188              
189             When submitting a bug or request, please include a test-file or a
190             patch to an existing test-file that illustrates the bug or desired
191             feature.
192              
193             =head1 SEE ALSO
194              
195             L<Data::Throttler>
196              
197             L<CHI>
198              
199             =head1 AUTHOR
200              
201             perlancar <perlancar@cpan.org>
202              
203             =head1 COPYRIGHT AND LICENSE
204              
205             This software is copyright (c) 2020 by perlancar@cpan.org.
206              
207             This is free software; you can redistribute it and/or modify it under
208             the same terms as the Perl 5 programming language system itself.
209              
210             =cut