1 |
|
%%============================================================================== |
2 |
|
%% Copyright 2016 Erlang Solutions Ltd. |
3 |
|
%% |
4 |
|
%% Licensed under the Apache License, Version 2.0 (the "License"); |
5 |
|
%% you may not use this file except in compliance with the License. |
6 |
|
%% You may obtain a copy of the License at |
7 |
|
%% |
8 |
|
%% http://www.apache.org/licenses/LICENSE-2.0 |
9 |
|
%% |
10 |
|
%% Unless required by applicable law or agreed to in writing, software |
11 |
|
%% distributed under the License is distributed on an "AS IS" BASIS, |
12 |
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 |
|
%% See the License for the specific language governing permissions and |
14 |
|
%% limitations under the License. |
15 |
|
%%============================================================================== |
16 |
|
|
17 |
|
-module(shaper). |
18 |
|
-author('konrad.zemek@erlang-solutions.com'). |
19 |
|
|
20 |
|
-export([new/1, update/2]). |
21 |
|
|
22 |
|
-record(shaper, { |
23 |
|
max_rate :: undefined | pos_integer(), |
24 |
|
tokens = 0 :: non_neg_integer(), |
25 |
|
last_update = erlang:monotonic_time(millisecond) :: integer() |
26 |
|
}). |
27 |
|
|
28 |
|
-type shaper() :: #shaper{} | none. |
29 |
|
|
30 |
|
-export_type([shaper/0]). |
31 |
|
|
32 |
|
-spec new(atom()) -> shaper(). |
33 |
|
new(Name) -> |
34 |
13421 |
case mongoose_config:lookup_opt([shaper, Name]) of |
35 |
|
{ok, #{max_rate := MaxRatePerSecond}} -> |
36 |
110 |
#shaper{max_rate = MaxRatePerSecond, |
37 |
|
tokens = MaxRatePerSecond, |
38 |
|
last_update = erlang:monotonic_time(millisecond)}; |
39 |
13311 |
{error, not_found} -> none |
40 |
|
end. |
41 |
|
|
42 |
|
%% @doc Update shaper. |
43 |
|
%% `Delay' is how many milliseconds to wait. |
44 |
|
-spec update(shaper(), Size :: non_neg_integer()) -> {shaper(), Delay :: non_neg_integer()}. |
45 |
|
update(none, _Size) -> |
46 |
59099 |
{none, 0}; |
47 |
|
update(#shaper{max_rate = MaxRatePerSecond, |
48 |
|
tokens = LastAvailableTokens, |
49 |
|
last_update = LastUpdate}, NowUsed) -> |
50 |
1101 |
Now = erlang:monotonic_time(millisecond), |
51 |
|
% How much we might have recovered since last time, in milliseconds arithmetic |
52 |
1101 |
GrowthPerMillisecond = MaxRatePerSecond / 1000, |
53 |
1101 |
MilliSecondsSinceLastUpdate = (Now - LastUpdate), |
54 |
1101 |
PossibleTokenGrowth = round(GrowthPerMillisecond * MilliSecondsSinceLastUpdate), |
55 |
|
% Available plus recovered cannot grow higher than the actual rate limit |
56 |
1101 |
ExactlyAvailableNow = min(MaxRatePerSecond, LastAvailableTokens + PossibleTokenGrowth), |
57 |
1101 |
TokensAvailable = max(0, ExactlyAvailableNow - NowUsed), |
58 |
1101 |
TokensOverused = max(0, NowUsed - ExactlyAvailableNow), |
59 |
1101 |
MaybeDelay = round(TokensOverused / GrowthPerMillisecond), |
60 |
1101 |
{#shaper{max_rate = MaxRatePerSecond, |
61 |
|
tokens = TokensAvailable, |
62 |
|
last_update = Now + MaybeDelay}, |
63 |
|
MaybeDelay}. |