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   17179 use 5.008;
  1         3  
4              
5 1     1   3 use warnings;
  1         2  
  1         20  
6 1     1   2 use strict;
  1         4  
  1         33  
7              
8             our $VERSION = 0.38;
9              
10 1     1   4 use Time::HiRes qw/time/;
  1         1  
  1         6  
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 = Algorithm::TokenBucket->new(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 =
54             Algorithm::TokenBucket->new(@{Storable::retrieve('bucket.stored')});
55              
56             =head1 DESCRIPTION
57              
58             The 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 algorithm is based
63             on the C. Other rate limiters may keep track of
64             I incoming items in memory. It allows them to be more accurate.
65              
66             FYI, the C, C, C, and C terms
67             are taken from the L
68             page of the L
69             system documentation.
70              
71             =head1 INTERFACE
72              
73             =cut
74              
75 1     1   593 use fields qw/info_rate burst_size _tokens _last_check_time/;
  1         1110  
  1         3  
76              
77             =head2 METHODS
78              
79             =over 4
80              
81             =item new($$;$$)
82              
83             The constructor requires at least the C in items per
84             second and the C in items as its input parameters. It can also
85             take the current token counter and last check time but this usage is mostly
86             intended for restoring a saved bucket. See L.
87              
88             =cut
89              
90             sub new {
91 3     3 1 989 my $class = shift;
92 3         14 fields::new($class)->_init(@_);
93             }
94              
95             sub _init {
96 4     4   2842 my Algorithm::TokenBucket $self = shift;
97              
98 4         15 @$self{qw/info_rate burst_size _tokens _last_check_time/} = @_;
99 4   66     26 $self->{_last_check_time} ||= time;
100 4   100     14 $self->{_tokens} ||= 0;
101              
102 4         13 return $self;
103             }
104              
105             =item state()
106              
107             Returns the state of the bucket as a list. Use it for storing purposes.
108             Buckets also natively support freezing and thawing with L by
109             providing C callbacks.
110              
111             =cut
112              
113             sub state {
114 4     4 1 32 my Algorithm::TokenBucket $self = shift;
115              
116 4         139 return @$self{qw/info_rate burst_size _tokens _last_check_time/};
117             }
118              
119 1     1   148 use constant PACK_FORMAT => "d4"; # "F4" is not 5.6 compatible
  1         2  
  1         297  
120              
121             sub STORABLE_freeze {
122 1     1 0 2542 my ($self, $cloning) = @_;
123 1         5 return pack(PACK_FORMAT(), $self->state);
124             }
125              
126             sub STORABLE_thaw {
127 1     1 0 28 my ($self, $cloning, $state) = @_;
128 1         7 return $self->_init(unpack(PACK_FORMAT(), $state));
129             }
130              
131             sub _token_flow {
132 188857     188857   109286 my Algorithm::TokenBucket $self = shift;
133              
134 188857         174356 my $time = time;
135              
136             $self->{_tokens} +=
137 188857         183765 ($time - $self->{_last_check_time}) * $self->{info_rate};
138              
139 188857 100       228193 if ($self->{_tokens} > $self->{burst_size}) {
140 4         6 $self->{_tokens} = $self->{burst_size};
141             }
142              
143 188857         146301 $self->{_last_check_time} = $time;
144             }
145              
146             =item conform($)
147              
148             This method returns true if the bucket contains at least I tokens and
149             false otherwise. In the case that it is true, it is allowed to transmit or
150             process I items (not exactly right because I can be fractional) from
151             the stream. A bucket never conforms to an I greater than C.
152              
153             =cut
154              
155             sub conform {
156 188796     188796 1 120238 my Algorithm::TokenBucket $self = shift;
157 188796         109007 my $size = shift;
158              
159 188796         161607 $self->_token_flow;
160              
161 188796         205964 return $self->{_tokens} >= $size;
162             }
163              
164             =item count($)
165              
166             This method removes I (or all if there are fewer than I available)
167             tokens from the bucket. It does not return a meaningful value.
168              
169             =cut
170              
171             sub count {
172 55     55 1 68 my Algorithm::TokenBucket $self = shift;
173 55         51 my $size = shift;
174              
175 55         106 $self->_token_flow;
176              
177 55 100       141 ($self->{_tokens} -= $size) < 0 and $self->{_tokens} = 0;
178             }
179              
180             =item until($)
181              
182             This method returns the number of seconds until I tokens can be removed
183             from the bucket. It is especially useful in multitasking environments like
184             L where you cannot busy-wait. One can safely schedule the next
185             C<< conform($N) >> check in C<< until($N) >> seconds instead of checking
186             repeatedly.
187              
188             Note that C does not take into account C. This means
189             that a bucket will not conform to I even after sleeping for C<< until($N) >>
190             seconds if I is greater than C.
191              
192             =cut
193              
194             sub until {
195 3     3 1 4 my Algorithm::TokenBucket $self = shift;
196 3         3 my $size = shift;
197              
198 3         6 $self->_token_flow;
199              
200 3 100       7 if ($self->{_tokens} >= $size) {
201             # can conform() right now
202 1         7 return 0;
203             } else {
204 2         4 my $needed = $size - $self->{_tokens};
205 2         9 return ($needed / $self->{info_rate});
206             }
207             }
208              
209             =item get_token_count()
210              
211             Returns the current number of tokens in the bucket. This method may be
212             useful for inspection or debugging purposes. You should not examine
213             the state of the bucket for rate limiting purposes.
214              
215             This number will frequently be fractional so it is not exactly a
216             "count".
217              
218             =cut
219              
220             sub get_token_count {
221 3     3 1 1354 my Algorithm::TokenBucket $self = shift;
222 3         7 $self->_token_flow;
223 3         12 return $self->{_tokens};
224             }
225              
226             1;
227             __END__