File Coverage

blib/lib/Algorithm/TokenBucket.pm
Criterion Covered Total %
statement 54 54 100.0
branch 6 6 100.0
condition 4 5 80.0
subroutine 16 16 100.0
pod 6 8 75.0
total 86 89 96.6


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