%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2007-2018. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% The Initial Developer of the Original Code is Ericsson AB.
%%
%% %CopyrightEnd%
%%
%%%-------------------------------------------------------------------
%%% File : erts_alloc_config.erl
%%% Author : Rickard Green
%%% Description : Generate an erts_alloc configuration suitable for
%%% a limited amount of runtime scenarios.
%%%
%%% Created : 9 May 2007 by Rickard Green
%%%-------------------------------------------------------------------
-module(erts_alloc_config).
-record(state, {have_scenario = false,
alloc}).
-record(alloc, {name,
enabled,
need_config_change,
alloc_util,
instances,
strategy,
acul,
low_mbc_blocks_size,
high_mbc_blocks_size,
sbct,
segments}).
-record(conf,
{segments,
format_to}).
-record(segment, {size,number}).
-define(PRINT_WITDH, 76).
-define(SERVER, '__erts_alloc_config__').
-define(KB, 1024).
-define(MB, 1048576).
-define(B2KB(B), ((((B) - 1) div ?KB) + 1)).
-define(ROUNDUP(V, R), ((((V) - 1) div (R)) + 1)*(R)).
-define(LARGE_GROWTH_ABS_LIMIT, 20*?MB).
-define(MBC_MSEG_LIMIT, 150).
-define(FRAG_FACT, 1.25).
-define(GROWTH_SEG_FACT, 2).
-define(MIN_SEG_SIZE, 1*?MB).
-define(SMALL_GROWTH_SEGS, 5).
-define(ALLOC_UTIL_ALLOCATOR(A),
A == binary_alloc;
A == std_alloc;
A == ets_alloc;
A == fix_alloc;
A == eheap_alloc;
A == ll_alloc;
A == sl_alloc;
A == temp_alloc;
A == driver_alloc).
-define(ALLOCATORS,
[binary_alloc,
ets_alloc,
eheap_alloc,
fix_alloc,
ll_alloc,
mseg_alloc,
sl_alloc,
std_alloc,
sys_alloc,
temp_alloc,
driver_alloc]).
-define(MMBCS_DEFAULTS,
[{binary_alloc, 131072},
{std_alloc, 131072},
{ets_alloc, 131072},
{fix_alloc, 131072},
{eheap_alloc, 524288},
{ll_alloc, 131072},
{sl_alloc, 131072},
{temp_alloc, 131072},
{driver_alloc, 131072}]).
%%%
%%% Exported interface
%%%
-export([save_scenario/0,
make_config/0,
make_config/1,
stop/0]).
%% Test and debug export
-export([state/0]).
save_scenario() ->
req(save_scenario).
make_config() ->
make_config(group_leader()).
make_config(FileName) when is_list(FileName) ->
case file:open(FileName, [write]) of
{ok, IODev} ->
Res = req({make_config, IODev}),
ok = file:close(IODev),
Res;
Error ->
Error
end;
make_config(IODev) ->
req({make_config, IODev}).
stop() ->
req(stop).
%% state() is intentionally undocumented, and is for testing
%% and debugging only...
state() ->
req(state).
%%%
%%% Server
%%%
req(Req) ->
Ref = make_ref(),
ReqMsg = {request, self(), Ref, Req},
req(ReqMsg, Ref, true).
req(ReqMsg, Ref, TryStart) ->
req(ReqMsg, Ref, TryStart, erlang:monitor(process, ?SERVER)).
req(ReqMsg, Ref, TryStart, Mon) ->
(catch ?SERVER ! ReqMsg),
receive
{response, Ref, Res} ->
erlang:demonitor(Mon, [flush]),
Res;
{'DOWN', Mon, _, _, noproc} ->
case TryStart of
true -> start_server(Ref, ReqMsg);
false -> {error, server_died}
end;
{'DOWN', Mon, _, _, Reason} ->
{error, Reason}
end.
start_server(Ref, ReqMsg) ->
Starter = self(),
Pid = spawn(fun () ->
register(?SERVER, self()),
Starter ! {Ref, self(), started},
server_loop(make_state())
end),
Mon = erlang:monitor(process, Pid),
receive
{Ref, Pid, started} ->
req(ReqMsg, Ref, false, Mon);
{'DOWN', Mon, _, _, _} ->
req(ReqMsg, Ref, false)
end.
server_loop(State) ->
NewState = receive
{request, From, Ref, save_scenario} ->
Alloc = save_scenario(State#state.alloc),
From ! {response, Ref, ok},
State#state{alloc = Alloc, have_scenario = true};
{request, From, Ref, {make_config, IODev}} ->
case State#state.have_scenario of
true ->
Conf = #conf{segments = ?MBC_MSEG_LIMIT,
format_to = IODev},
Res = mk_config(Conf, State#state.alloc),
From ! {response, Ref, Res},
ok;
_ ->
From ! {response, Ref, no_scenario_saved},
ok
end,
State;
{request, From, Ref, stop} ->
From ! {response, Ref, ok},
exit(normal);
{request, From, Ref, state} ->
From ! {response, Ref, State},
State;
{request, From, Ref, Req} ->
From ! {response, Ref, {unknown_request, Req}},
State;
_ ->
State
end,
server_loop(NewState).
carrier_migration_support(aoff) ->
true;
carrier_migration_support(aoffcbf) ->
true;
carrier_migration_support(aoffcaobf) ->
true;
carrier_migration_support(_) ->
false.
allocator_instances(ll_alloc, Strategy) ->
case carrier_migration_support(Strategy) of
true -> erlang:system_info(schedulers);
false -> 1
end;
allocator_instances(_A, undefined) ->
1;
allocator_instances(_A, _Strategy) ->
erlang:system_info(schedulers).
strategy(temp_alloc, _AI) ->
af;
strategy(A, AI) ->
try
{A, OptList} = lists:keyfind(A, 1, AI),
{as, S} = lists:keyfind(as, 1, OptList),
S
catch
_ : _ ->
undefined
end.
strategy_str(af) ->
"A fit";
strategy_str(gf) ->
"Good fit";
strategy_str(bf) ->
"Best fit";
strategy_str(aobf) ->
"Address order best fit";
strategy_str(aoff) ->
"Address order first fit";
strategy_str(aoffcbf) ->
"Address order first fit carrier best fit";
strategy_str(aoffcaobf) ->
"Address order first fit carrier adress order best fit";
strategy_str(ageffcaoff) ->
"Age order first fit carrier address order first fit";
strategy_str(ageffcbf) ->
"Age order first fit carrier best fit";
strategy_str(ageffcaobf) ->
"Age order first fit carrier adress order best fit".
default_acul(A, S) ->
case carrier_migration_support(S) of
false ->
0;
true ->
case A of
ll_alloc -> 85;
eheap_alloc -> 45;
_ -> 60
end
end.
make_state() ->
{_, _, _, AI} = erlang:system_info(allocator),
#state{alloc = lists:map(fun (A) ->
S = strategy(A, AI),
#alloc{name = A,
strategy = S,
acul = default_acul(A, S),
instances = allocator_instances(A, S)}
end,
?ALLOCATORS)}.
%%
%% Save scenario
%%
ai_value(Key1, Key2, AI) ->
case lists:keysearch(Key1, 1, AI) of
{value, {Key1, Value1}} ->
case lists:keysearch(Key2, 1, Value1) of
{value, Result} -> Result;
_ -> undefined
end;
_ -> undefined
end.
chk_mbcs_blocks_size(#alloc{low_mbc_blocks_size = undefined,
high_mbc_blocks_size = undefined} = Alc,
Min,
Max) ->
Alc#alloc{low_mbc_blocks_size = Min,
high_mbc_blocks_size = Max,
enabled = true};
chk_mbcs_blocks_size(#alloc{low_mbc_blocks_size = LowBS,
high_mbc_blocks_size = HighBS} = Alc,
Min,
Max) ->
true = is_integer(LowBS),
true = is_integer(HighBS),
Alc1 = case Min < LowBS of
true -> Alc#alloc{low_mbc_blocks_size = Min};
false -> Alc
end,
case Max > HighBS of
true -> Alc1#alloc{high_mbc_blocks_size = Max};
false -> Alc1
end.
set_alloc_util(#alloc{alloc_util = AU} = Alc, AU) ->
Alc;
set_alloc_util(Alc, Val) ->
Alc#alloc{alloc_util = Val}.
chk_sbct(#alloc{sbct = undefined} = Alc, AI) ->
case ai_value(options, sbct, AI) of
{sbct, Bytes} when is_integer(Bytes) -> Alc#alloc{sbct = b2kb(Bytes)};
_ -> Alc
end;
chk_sbct(Alc, _AI) ->
Alc.
save_scenario(AlcList) ->
%% The high priority is not really necessary. It is
%% used since it will make retrieval of allocator
%% information less spread out in time on a highly
%% loaded system.
OP = process_flag(priority, high),
Res = do_save_scenario(AlcList),
process_flag(priority, OP),
Res.
save_ai2(Alc, AI) ->
Alc1 = chk_sbct(Alc, AI),
case ai_value(mbcs, blocks_size, AI) of
{blocks_size, MinBS, _, MaxBS} ->
set_alloc_util(chk_mbcs_blocks_size(Alc1, MinBS, MaxBS), true);
_ ->
set_alloc_util(Alc, false)
end.
save_ai(Alc, [{instance, 0, AI}]) ->
save_ai2(Alc, AI);
save_ai(Alc, [{instance, _, _}, {instance, _, _}| _]) ->
Alc#alloc{enabled = true, need_config_change = true};
save_ai(Alc, AI) ->
save_ai2(Alc, AI). % Non erts_alloc_util allocator
do_save_scenario(AlcList) ->
lists:map(fun (#alloc{enabled = false} = Alc) ->
Alc;
(#alloc{name = Name} = Alc) ->
case erlang:system_info({allocator, Name}) of
undefined ->
exit({bad_allocator_name, Name});
false ->
Alc#alloc{enabled = false};
AI when is_list(AI) ->
save_ai(Alc, AI)
end
end,
AlcList).
%%
%% Make configuration
%%
conf_size(Bytes) when is_integer(Bytes), Bytes < 0 ->
exit({bad_value, Bytes});
conf_size(Bytes) when is_integer(Bytes), Bytes < 1*?MB ->
?ROUNDUP(?B2KB(Bytes), 256);
conf_size(Bytes) when is_integer(Bytes), Bytes < 10*?MB ->
?ROUNDUP(?B2KB(Bytes), ?B2KB(1*?MB));
conf_size(Bytes) when is_integer(Bytes), Bytes < 100*?MB ->
?ROUNDUP(?B2KB(Bytes), ?B2KB(2*?MB));
conf_size(Bytes) when is_integer(Bytes), Bytes < 256*?MB ->
?ROUNDUP(?B2KB(Bytes), ?B2KB(5*?MB));
conf_size(Bytes) when is_integer(Bytes) ->
?ROUNDUP(?B2KB(Bytes), ?B2KB(10*?MB)).
sbct(#conf{format_to = FTO}, #alloc{name = A, sbct = SBCT}) ->
fc(FTO, "Sbc threshold size of ~p kilobytes.", [SBCT]),
format(FTO, " +M~csbct ~p~n", [alloc_char(A), SBCT]).
default_mmbcs(temp_alloc = A, _Insts) ->
{value, {A, MMBCS_Default}} = lists:keysearch(A, 1, ?MMBCS_DEFAULTS),
MMBCS_Default;
default_mmbcs(A, Insts) ->
{value, {A, MMBCS_Default}} = lists:keysearch(A, 1, ?MMBCS_DEFAULTS),
I = case Insts > 4 of
true -> 4;
_ -> Insts
end,
?ROUNDUP(MMBCS_Default div I, ?B2KB(1*?KB)).
mmbcs(#conf{format_to = FTO},
#alloc{name = A, instances = Insts, low_mbc_blocks_size = BlocksSize}) ->
BS = case A of
temp_alloc -> BlocksSize;
_ -> BlocksSize div Insts
end,
DefMMBCS = default_mmbcs(A, Insts),
case {Insts, BS > DefMMBCS} of
{1, true} ->
MMBCS = conf_size(BS),
fc(FTO, "Main mbc size of ~p kilobytes.", [MMBCS]),
format(FTO, " +M~cmmbcs ~p~n", [alloc_char(A), MMBCS]);
_ ->
MMBCS = ?B2KB(DefMMBCS),
fc(FTO, "Main mbc size of ~p kilobytes.", [MMBCS]),
format(FTO, " +M~cmmbcs ~p~n", [alloc_char(A), MMBCS]),
ok
end.
smbcs_lmbcs(#conf{format_to = FTO},
#alloc{name = A, segments = Segments}) ->
MBCS = Segments#segment.size,
AC = alloc_char(A),
fc(FTO, "Mseg mbc size of ~p kilobytes.", [MBCS]),
format(FTO, " +M~csmbcs ~p +M~clmbcs ~p~n", [AC, MBCS, AC, MBCS]),
ok.
alloc_char(binary_alloc) -> $B;
alloc_char(std_alloc) -> $D;
alloc_char(ets_alloc) -> $E;
alloc_char(fix_alloc) -> $F;
alloc_char(eheap_alloc) -> $H;
alloc_char(ll_alloc) -> $L;
alloc_char(mseg_alloc) -> $M;
alloc_char(driver_alloc) -> $R;
alloc_char(sl_alloc) -> $S;
alloc_char(temp_alloc) -> $T;
alloc_char(sys_alloc) -> $Y;
alloc_char(Alloc) ->
exit({bad_allocator, Alloc}).
conf_alloc(#conf{format_to = FTO},
#alloc{name = A, enabled = false}) ->
fcl(FTO, A),
fcp(FTO,
"WARNING: ~p has been disabled. Consider enabling ~p by passing "
"the \"+M~ce true\" command line argument and rerun "
"erts_alloc_config.",
[A, A, alloc_char(A)]);
conf_alloc(#conf{format_to = FTO},
#alloc{name = A, need_config_change = true}) ->
fcl(FTO, A),
fcp(FTO,
"WARNING: ~p has been configured in a way that prevents "
"erts_alloc_config from creating a configuration. The configuration "
"will be automatically adjusted to fit erts_alloc_config if you "
"use the \"+Mea config\" command line argument while running "
"erts_alloc_config.",
[A]);
conf_alloc(#conf{format_to = FTO} = Conf,
#alloc{name = A, alloc_util = true} = Alc) ->
fcl(FTO, A),
chk_xnote(Conf, Alc),
au_conf_alloc(Conf, Alc),
format(FTO, "#~n", []);
conf_alloc(#conf{format_to = FTO} = Conf, #alloc{name = A} = Alc) ->
fcl(FTO, A),
chk_xnote(Conf, Alc).
chk_xnote(#conf{format_to = FTO},
#alloc{name = sys_alloc}) ->
fcp(FTO, "Cannot be configured. Default malloc implementation used.");
chk_xnote(#conf{format_to = FTO},
#alloc{name = mseg_alloc}) ->
fcp(FTO, "Default configuration used.");
chk_xnote(#conf{format_to = FTO},
#alloc{name = ll_alloc}) ->
fcp(FTO,
"Note, blocks allocated with ll_alloc are very "
"seldom deallocated. Placing blocks in mseg "
"carriers is therefore very likely only a waste "
"of resources.");
chk_xnote(#conf{}, #alloc{}) ->
ok.
au_conf_alloc(#conf{format_to = FTO} = Conf,
#alloc{name = A,
alloc_util = true,
instances = Insts,
acul = Acul,
strategy = Strategy,
low_mbc_blocks_size = Low,
high_mbc_blocks_size = High} = Alc) ->
fcp(FTO, "Usage of mbcs: ~p - ~p kilobytes", [?B2KB(Low), ?B2KB(High)]),
case Insts of
1 ->
fc(FTO, "One instance used."),
format(FTO, " +M~ct false~n", [alloc_char(A)]);
_ ->
fc(FTO, "~p + 1 instances used.",
[Insts]),
format(FTO, " +M~ct true~n", [alloc_char(A)]),
case Strategy of
undefined ->
ok;
_ ->
fc(FTO, "Allocation strategy: ~s.",
[strategy_str(Strategy)]),
format(FTO, " +M~cas ~s~n", [alloc_char(A),
atom_to_list(Strategy)])
end,
case carrier_migration_support(Strategy) of
false ->
ok;
true ->
fc(FTO, "Abandon carrier utilization limit of ~p%.", [Acul]),
format(FTO, " +M~cacul ~p~n", [alloc_char(A), Acul])
end
end,
mmbcs(Conf, Alc),
smbcs_lmbcs(Conf, Alc),
sbct(Conf, Alc).
calc_seg_size(Growth, Segs) ->
conf_size(round(Growth*?FRAG_FACT*?GROWTH_SEG_FACT) div Segs).
calc_growth_segments(Conf, AlcList0) ->
CalcSmall = fun (#alloc{name = ll_alloc, instances = 1} = Alc, Acc) ->
{Alc#alloc{segments = #segment{size = conf_size(0),
number = 0}},
Acc};
(#alloc{alloc_util = true,
instances = Insts,
low_mbc_blocks_size = LowMBC,
high_mbc_blocks_size = High} = Alc,
{SL, AL}) ->
Low = case Insts of
1 -> LowMBC;
_ -> 0
end,
Growth = High - Low,
case Growth >= ?LARGE_GROWTH_ABS_LIMIT of
true ->
{Alc, {SL, AL+1}};
false ->
Segs = ?SMALL_GROWTH_SEGS,
SegSize = calc_seg_size(Growth, Segs),
{Alc#alloc{segments
= #segment{size = SegSize,
number = Segs}},
{SL - Segs, AL}}
end;
(Alc, Acc) -> {Alc, Acc}
end,
{AlcList1, {SegsLeft, AllocsLeft}}
= lists:mapfoldl(CalcSmall, {Conf#conf.segments, 0}, AlcList0),
case AllocsLeft of
0 ->
AlcList1;
_ ->
SegsPerAlloc = case (SegsLeft div AllocsLeft) + 1 of
SPA when SPA < ?SMALL_GROWTH_SEGS ->
?SMALL_GROWTH_SEGS;
SPA ->
SPA
end,
CalcLarge = fun (#alloc{alloc_util = true,
segments = undefined,
instances = Insts,
low_mbc_blocks_size = LowMBC,
high_mbc_blocks_size = High} = Alc) ->
Low = case Insts of
1 -> LowMBC;
_ -> 0
end,
Growth = High - Low,
SegSize = calc_seg_size(Growth,
SegsPerAlloc),
Alc#alloc{segments
= #segment{size = SegSize,
number = SegsPerAlloc}};
(Alc) ->
Alc
end,
lists:map(CalcLarge, AlcList1)
end.
mk_config(#conf{format_to = FTO} = Conf, AlcList) ->
format_header(FTO),
Res = lists:foreach(fun (Alc) -> conf_alloc(Conf, Alc) end,
calc_growth_segments(Conf, AlcList)),
format_footer(FTO),
Res.
format_header(FTO) ->
{Y,Mo,D} = erlang:date(),
{H,Mi,S} = erlang:time(),
fcl(FTO),
fcl(FTO, "erts_alloc configuration"),
fcl(FTO),
fcp(FTO,
"This erts_alloc configuration was automatically "
"generated at ~w-~2..0w-~2..0w ~2..0w:~2..0w.~2..0w by "
"erts_alloc_config.",
[Y, Mo, D, H, Mi, S]),
fcp(FTO,
"~s was used when generating the configuration.",
[string:trim(erlang:system_info(system_version), both, "$\n")]),
case erlang:system_info(schedulers) of
1 -> ok;
Schdlrs ->
fcp(FTO,
"NOTE: This configuration was made for ~p schedulers. "
"It is very important that ~p schedulers are used.",
[Schdlrs, Schdlrs])
end,
fcp(FTO,
"This configuration is intended as a suggestion and "
"may need to be adjusted manually. Instead of modifying "
"this file, you are advised to write another configuration "
"file and override values that you want to change. "
"Doing it this way simplifies things when you want to "
"rerun erts_alloc_config."),
fcp(FTO,
"This configuration is based on the actual use of "
"multi-block carriers (mbcs) for a set of different "
"runtime scenarios. Note that this configuration may "
"perform bad, ever horrible, for other runtime "
"scenarios."),
fcp(FTO,
"You are advised to rerun erts_alloc_config if the "
"applications run when the configuration was made "
"are changed, or if the load on the applications have "
"changed since the configuration was made. You are also "
"advised to rerun erts_alloc_config if the Erlang runtime "
"system used is changed."),
fcp(FTO,
"Note, that the singel-block carrier (sbc) parameters "
"very much effects the use of mbcs. Therefore, if you "
"change the sbc parameters, you are advised to rerun "
"erts_alloc_config."),
fcp(FTO,
"For more information see the erts_alloc_config(3) "
"documentation."),
ok.
format_footer(FTO) ->
fcl(FTO).
%%%
%%% Misc.
%%%
b2kb(B) when is_integer(B) ->
MaxKB = (1 bsl erlang:system_info(wordsize)*8) div 1024,
case ?B2KB(B) of
KB when KB > MaxKB -> MaxKB;
KB -> KB
end.
format(false, _Frmt) ->
ok;
format(IODev, Frmt) ->
io:format(IODev, Frmt, []).
format(false, _Frmt, _Args) ->
ok;
format(IODev, Frmt, Args) ->
io:format(IODev, Frmt, Args).
%% fcp: format comment paragraf
fcp(IODev, Frmt, Args) ->
fc(IODev, Frmt, Args),
format(IODev, "#~n").
fcp(IODev, Frmt) ->
fc(IODev, Frmt),
format(IODev, "#~n").
%% fc: format comment
fc(IODev, Frmt, Args) ->
fc(IODev, lists:flatten(io_lib:format(Frmt, Args))).
fc(IODev, String) ->
fc_aux(IODev, string:lexemes(String, " "), 0).
fc_aux(_IODev, [], 0) ->
ok;
fc_aux(IODev, [], _Len) ->
format(IODev, "~n");
fc_aux(IODev, [T|Ts], 0) ->
Len = 2 + string:length(T),
format(IODev, "# ~s", [T]),
fc_aux(IODev, Ts, Len);
fc_aux(IODev, [T|Ts] = ATs, Len) ->
TLength = string:length(T),
case (TLength + Len) >= ?PRINT_WITDH of
true ->
format(IODev, "~n"),
fc_aux(IODev, ATs, 0);
false ->
NewLen = Len + 1 + TLength,
format(IODev, " ~s", [T]),
fc_aux(IODev, Ts, NewLen)
end.
%% fcl: format comment line
fcl(FTO) ->
EndStr = "# ",
Precision = string:length(EndStr),
FieldWidth = -1*(?PRINT_WITDH),
format(FTO, "~*.*.*s~n", [FieldWidth, Precision, $-, EndStr]).
fcl(FTO, A) when is_atom(A) ->
fcl(FTO, atom_to_list(A));
fcl(FTO, Str) when is_list(Str) ->
Str2 = "# --- " ++ Str ++ " ",
Precision = string:length(Str2),
FieldWidth = -1*(?PRINT_WITDH),
format(FTO, "~*.*.*s~n", [FieldWidth, Precision, $-, Str2]).