File Coverage

blib/lib/Algorithm/TokenBucket.pm
Criterion Covered Total %
statement 53 53 100.0
branch 6 6 100.0
condition 4 5 80.0
subroutine 16 16 100.0
pod 6 8 75.0
total 85 88 96.5


line stmt bran cond sub pod time code
1             package Algorithm::TokenBucket;
2              
3 1     1   27413 use 5.006;
  1         5  
4              
5             our $VERSION = 0.37;
6              
7 1     1   6 use warnings;
  1         3  
  1         34  
8 1     1   5 use strict;
  1         5  
  1         32  
9              
10 1     1   4 use Time::HiRes qw/time/;
  1         1  
  1         5  
11              
12             =head1 NAME
13              
14             Algorithm::TokenBucket - Token bucket rate limiting algorithm
15              
16             =head1 SYNOPSIS
17              
18             use Algorithm::TokenBucket;
19              
20             # configure a bucket to limit a stream up to 100 items per hour
21             # with bursts of 5 items max
22             my $bucket = new Algorithm::TokenBucket 100 / 3600, 5;
23              
24             # wait until we are allowed to process 3 items
25             until ($bucket->conform(3)) {
26             sleep 0.1;
27             # do things
28             }
29            
30             # process 3 items because we now can
31             process(3);
32              
33             # leak (flush) bucket
34             $bucket->count(3); # same as $bucket->count(1) for 1..3;
35              
36             if ($bucket->conform(10)) {
37             die;
38             # because a bucket with the burst size of 5
39             # will never conform to 10
40             }
41              
42             my $time = Time::HiRes::time;
43             while (Time::HiRes::time - $time < 7200) { # two hours
44             # be bursty
45             if ($bucket->conform(5)) {
46             process(5);
47             $bucket->count(5);
48             }
49             }
50             # we're likely to have processed 200 items (and hogged CPU)
51              
52             Storable::store $bucket, 'bucket.stored';
53             my $bucket1 = new Algorithm::TokenBucket
54             @{Storable::retrieve('bucket.stored')};
55              
56             =head1 DESCRIPTION
57              
58             Token Bucket algorithm is a flexible way of imposing a rate limit
59             against a stream of items. It is also very easy to combine several
60             rate-limiters in an C or C fashion.
61              
62             Each bucket has a constant memory footprint because the
63             algorithm is based on C.
64             Other rate limiters available on CPAN keep track of I incoming
65             items in memory. It allows them to be much more accurate.
66              
67             FYI, C, C, C, C terms are
68             taken from L page.
69              
70             =head1 INTERFACE
71              
72             =cut
73              
74 1     1   2366 use fields qw/info_rate burst_size _tokens _last_check_time/;
  1         1480  
  1         7  
75              
76             =head2 METHODS
77              
78             =over 4
79              
80             =item new($$;$$)
81              
82             The constructor takes as parameters at least C in
83             items per second and C in items. It can also take current
84             token counter and last check time but this usage is mostly intended for
85             restoring a saved bucket. See L.
86              
87             =cut
88              
89             sub new {
90 3     3 1 1886 my $class = shift;
91 3         15 fields::new($class)->_init(@_);
92             }
93              
94             sub _init {
95 4     4   8613 my Algorithm::TokenBucket $self = shift;
96              
97 4         21 @$self{qw/info_rate burst_size _tokens _last_check_time/} = @_;
98 4   66     34 $self->{_last_check_time} ||= time;
99 4   100     19 $self->{_tokens} ||= 0;
100              
101 4         25 return $self;
102             }
103              
104             =item state()
105              
106             This method returns the state of the bucket as a list. Use it for storing purposes.
107             Buckets also natively support freezing and thawing with L by
108             providing STORABLE_* callbacks.
109              
110             =cut
111              
112             sub state {
113 4     4 1 54 my Algorithm::TokenBucket $self = shift;
114              
115 4         183 return @$self{qw/info_rate burst_size _tokens _last_check_time/};
116             }
117              
118 1     1   232 use constant PACK_FORMAT => "d4"; # "F4" is not 5.6 compatible
  1         1  
  1         415  
119              
120             sub STORABLE_freeze {
121 1     1 0 8270 my ( $self, $cloning ) = @_;
122 1         11 return pack(PACK_FORMAT(),$self->state);
123             }
124              
125             sub STORABLE_thaw {
126 1     1 0 40 my ( $self, $cloning, $state ) = @_;
127 1         12 return $self->_init(unpack(PACK_FORMAT(),$state));
128             }
129              
130             sub _token_flow {
131 107468     107468   112163 my Algorithm::TokenBucket $self = shift;
132              
133 107468         193438 my $time = time;
134              
135 107468         179314 $self->{_tokens} += ($time - $self->{_last_check_time}) * $self->{info_rate};
136 107468 100       209491 $self->{_tokens} > $self->{burst_size} and $self->{_tokens} = $self->{burst_size};
137              
138 107468         153205 $self->{_last_check_time}= $time;
139             }
140              
141             =item conform($)
142              
143             This method checks if the bucket contains at least I tokens. In that
144             case it is allowed to transmit or process I items (not
145             exactly right because I can be fractional) from the stream. A bucket never
146             conforms to an I greater than C.
147              
148             The method returns a boolean value.
149              
150             =cut
151              
152             sub conform {
153 107407     107407 1 114471 my Algorithm::TokenBucket $self = shift;
154 107407         110858 my $size = shift;
155              
156 107407         171940 $self->_token_flow;
157              
158 107407         213094 return $self->{_tokens} >= $size;
159             }
160              
161             =item count($)
162              
163             This method removes I (or all if there are less than I available) tokens from the bucket.
164             Does not return a meaningful value.
165              
166             =cut
167              
168             sub count {
169 55     55 1 77 my Algorithm::TokenBucket $self = shift;
170 55         72 my $size = shift;
171              
172 55         109 $self->_token_flow;
173              
174 55 100       279 ($self->{_tokens} -= $size) < 0 and $self->{_tokens} = 0;
175             }
176              
177             =item until($)
178              
179             This method returns the number of seconds until I tokens can be removed from the bucket.
180             It is especially useful in multitasking environments like L where you
181             cannot busy-wait. One can safely schedule the next conform($N) check in until($N)
182             seconds instead of checking repeatedly.
183              
184             Note that until() does not take into account C. This means
185             that a bucket will not conform to I even after sleeping for until($N)
186             seconds if I is greater than C.
187              
188             =cut
189              
190             sub until {
191 3     3 1 14 my Algorithm::TokenBucket $self = shift;
192 3         8 my $size = shift;
193              
194 3         14 $self->_token_flow;
195              
196 3 100       20 if ( $self->{_tokens} >= $size ) {
197             # can conform() right now
198 1         10 return 0;
199             } else {
200 2         7 my $needed = $size - $self->{_tokens};
201 2         21 return ( $needed / $self->{info_rate} );
202             }
203             }
204              
205             =item get_token_count()
206              
207             Returns the current number of tokens in the bucket. This method may be
208             useful for inspection or debugging purposes. You should not examine
209             the state of the bucket for rate limiting purposes.
210              
211             This number will frequently be fractional so it is not exactly a
212             "count".
213              
214             =cut
215              
216             sub get_token_count {
217 3     3 1 1946 my Algorithm::TokenBucket $self = shift;
218 3         12 $self->_token_flow;
219 3         22 return $self->{_tokens};
220             }
221              
222             1;
223             __END__