diff options
Diffstat (limited to 'lib/sasl/src/overload.erl')
-rw-r--r-- | lib/sasl/src/overload.erl | 224 |
1 files changed, 224 insertions, 0 deletions
diff --git a/lib/sasl/src/overload.erl b/lib/sasl/src/overload.erl new file mode 100644 index 0000000000..3a9a51e8bf --- /dev/null +++ b/lib/sasl/src/overload.erl @@ -0,0 +1,224 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(overload). + +-export([start_link/0, request/0, set_config_data/2, + get_overload_info/0]). + +-export([init/1, handle_call/3, handle_info/2, terminate/2, + format_status/2]). + +%%%----------------------------------------------------------------- +%%% This is a rewrite of overload from BS.3, by Peter H�gfeldt. +%%% +%%% DESCRIPTION +%%% +%%% This module implements a server process that keeps record of the +%%% intensity of calls of the request/0 function, and answers accept or +%%% reject depending on if the current average intensity is not greater +%%% than a specified maximum intensity. +%%% +%%% The intensity i is calculated according to the formula: +%%% i(n) = exp(-K*(T(n) - T(n-1)))*i(n-1) + Kappa +%%% where i(n) is the intensity at event n, Kappa is a constant, and +%%% T(n) is the time at event n. +%%% +%%% The constant Kappa can be thought of as 1 / T, where T is the time +%%% constant. Kappa is externally referred to as Weight. +%%% +%%% We keep track of two intensities: the total call intensity and the +%%% intensity of accepted calls. +%%%----------------------------------------------------------------- +%%% TODO +%%% +%%% 3. Hysteresis. +%%% +%%%----------------------------------------------------------------- + +-record(state, {total = 0, accept = 0, max, prev_t = get_now(), + kappa, call_counts = {0, 0}, alarm = clear}). + +-define(clear_timeout, 30000). + +start_link() -> + gen_server:start_link({local, overload}, overload, [], []). + +init([]) -> + process_flag(priority, high), + MaxIntensity = fetch_config_data(overload_max_intensity), + Kappa = fetch_config_data(overload_weight), + {ok, #state{max = MaxIntensity, kappa = Kappa}}. + +%%----------------------------------------------------------------- +%% Func: request/0 +%% Purpose: This is a request to proceed, e.g. a request to +%% establish a call. +%% Returns: accept | reject +%%----------------------------------------------------------------- +request() -> gen_server:call(overload, request). + +%%----------------------------------------------------------------- +%% Func: set_config_data/2 +%% Purpose: Set configuration data, and reset intensities. +%% Arguments: MaxIntensity (real > 0), Weight (real > 0). +%% Returns: ok | {error, What} +%% This function is for debugging purposes and is therefore not +%% documented at all. +%%----------------------------------------------------------------- +set_config_data(MaxIntensity, Weight) -> + gen_server:call(overload, {set_config_data, MaxIntensity, Weight}). +%%----------------------------------------------------------------- +%% Func: get_overload_info/0 +%% Returns: A list of tagged items: TotalIntensity, AcceptIntensity, +%% MaxIntensity, Weight, TotalRequests, AcceptedRequests. +%%----------------------------------------------------------------- +get_overload_info() -> gen_server:call(overload, get_overload_info). + +%%%----------------------------------------------------------------- +%%% Callback functions from gen_server +%%%----------------------------------------------------------------- +handle_call(request, _From, State) -> + #state{total = TI, accept = AI, kappa = Kappa, prev_t = PrevT, + alarm = Alarm} = State, + {TR, AR} = State#state.call_counts, + T = get_now(), + CurI = new_intensity(AI, T, PrevT, Kappa), + NewTI = new_intensity(TI, T, PrevT, Kappa) + Kappa, + if + CurI =< State#state.max -> + %% Hysteresis: If alarm is set, and current intensity has + %% fallen below 75% of max intensity, clear alarm. + NewAlarm = if + CurI =< 0.75*State#state.max -> + clear_alarm(Alarm); + true -> + Alarm + end, + {reply, accept, State#state{call_counts = {TR+1, AR+1}, + prev_t = T, total = NewTI, + accept = CurI + Kappa, + alarm = NewAlarm}, + ?clear_timeout}; + true -> + %% Set alarm if not already set. + NewAlarm = set_alarm(Alarm), + {reply, reject, + State#state{call_counts = {TR+1, AR}, prev_t = T, + total = NewTI, accept = CurI, + alarm = NewAlarm}, + ?clear_timeout} + end; +handle_call({set_config_data, MaxIntensity, Weight}, _From, _State) -> + {reply, ok, #state{max = MaxIntensity, kappa = Weight}, + ?clear_timeout}; +handle_call(get_overload_info, _From, State) -> + #state{max = MI, total = TI, accept = AI, kappa = Kappa, + prev_t = PrevT, call_counts = {TR, AR}} = State, + T = get_now(), + CurI = new_intensity(AI, T, PrevT, Kappa), + NewTI = new_intensity(TI, T, PrevT, Kappa), + Reply = [{total_intensity, NewTI}, {accept_intensity, CurI}, + {max_intensity, MI}, {weight, Kappa}, + {total_requests, TR}, {accepted_requests, AR}], + {reply, Reply, State#state{total = NewTI, accept = CurI}, + ?clear_timeout}. + +handle_info(timeout, State) -> + #state{total = TI, accept = AI, kappa = Kappa, prev_t = PrevT, + alarm = Alarm} = State, + T = get_now(), + CurI = new_intensity(AI, T, PrevT, Kappa), + NewTI = new_intensity(TI, T, PrevT, Kappa), + if + CurI < 0.75* State#state.max -> + NewAlarm = clear_alarm(Alarm), + {noreply, State#state{total = NewTI, accept = CurI, + alarm = NewAlarm}}; + + true -> + {noreply, State#state{total = NewTI, accept = CurI}, + ?clear_timeout} + end; + +handle_info(_, State) -> + {noreply, State, ?clear_timeout}. + +terminate(_Reason, _State) -> + ok. + +%%----------------------------------------------------------------- +%% Internal functions +%%----------------------------------------------------------------- +fetch_config_data(Tag) -> + case application:get_env(sasl, Tag) of + {ok, Value} -> Value; + _ -> fetch_default_data(Tag) + end. + +fetch_default_data(overload_max_intensity) -> 0.8; +fetch_default_data(overload_weight) -> 0.1. + +set_alarm(clear) -> + alarm_handler:set_alarm({overload, []}), + set; +set_alarm(Alarm) -> + Alarm. + +clear_alarm(set) -> + alarm_handler:clear_alarm(overload), + clear; +clear_alarm(Alarm) -> + Alarm. + +%%----------------------------------------------------------------- +%% The catch protects against floating-point exception. +%% +new_intensity(I, T, PrevT, K) -> + Diff = sub(T, PrevT)/1000, + case catch (I*math:exp(-K*Diff)) of + {'EXIT', _} -> % Assume zero. + 0.0; + Res -> + Res + end. + +%% Mask equal to 2^27 - 1, used below. +-define(mask27, 16#7ffffff). + +%% Returns number of milliseconds in the range [0, 2^27 - 1]. Must have +%% this since statistics(wall_clock) wraps. Having 2^27 -1 as the max +%% assures that we always get non-negative integers. 2^27 milliseconds +%% are approx. 37.28 hours. +get_now() -> + element(1, statistics(wall_clock)) band ?mask27. + +%% Returns (X - Y) mod 2^27 (which is in the range [0, 2^27 - 1]). +sub(X, Y) -> + (X + (bnot Y) + 1) band ?mask27. + +format_status(Opt, [PDict, #state{max = MI, total = TI, accept = AI, + kappa = K, + call_counts = {TR, AR}}]) -> + [{data, [{"Total Intensity", TI}, + {"Accept Intensity", AI}, + {"Max Intensity", MI}, + {"Weight", K}, + {"Total requests", TR}, + {"Accepted requests", AR}]} | + misc_supp:format_pdict(Opt, PDict, [])]. |