diff options
Diffstat (limited to 'lib/runtime_tools/src/erts_alloc_config.erl')
-rw-r--r-- | lib/runtime_tools/src/erts_alloc_config.erl | 670 |
1 files changed, 670 insertions, 0 deletions
diff --git a/lib/runtime_tools/src/erts_alloc_config.erl b/lib/runtime_tools/src/erts_alloc_config.erl new file mode 100644 index 0000000000..0bcb202fd8 --- /dev/null +++ b/lib/runtime_tools/src/erts_alloc_config.erl @@ -0,0 +1,670 @@ +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-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. +%% +%% 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, + 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(MAX_ALLOCATOR_INSTANCES, 16). + +-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 == 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}, + {eheap_alloc, 524288}, + {ll_alloc, 2097152}, + {sl_alloc, 131072}, + {temp_alloc, 131072}, + {driver_alloc, 131072}]). + +-define(MMMBC_DEFAULTS, + [{binary_alloc, 10}, + {std_alloc, 10}, + {ets_alloc, 10}, + {eheap_alloc, 10}, + {ll_alloc, 0}, + {sl_alloc, 10}, + {temp_alloc, 10}, + {driver_alloc, 10}]). + + +%%% +%%% 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}), + 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}; + _ -> + From ! {response, Ref, no_scenario_saved} + 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). + +allocator_instances(temp_alloc) -> + erlang:system_info(schedulers) + 1; +allocator_instances(ll_alloc) -> + 1; +allocator_instances(_Allocator) -> + case erlang:system_info(schedulers) of + Schdlrs when Schdlrs =< ?MAX_ALLOCATOR_INSTANCES -> Schdlrs; + _Schdlrs -> ?MAX_ALLOCATOR_INSTANCES + end. + +make_state() -> + #state{alloc = lists:map(fun (A) -> + #alloc{name = A, + instances = allocator_instances(A)} + 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), 128); +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, + case BS > default_mmbcs(A, Insts) of + true -> + MMBCS = conf_size(BS), + fc(FTO, "Main mbc size of ~p kilobytes.", [MMBCS]), + format(FTO, " +M~cmmbcs ~p~n", [alloc_char(A), MMBCS]); + false -> + ok + end. + +smbcs_lmbcs_mmmbc(#conf{format_to = FTO}, + #alloc{name = A, instances = Insts, segments = Segments}) -> + MMMBC = case {A, Insts} of + {_, 1} -> Segments#segment.number; + {temp_alloc, _} -> Segments#segment.number; + _ -> (Segments#segment.number div Insts) + 1 + end, + 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]), + fc(FTO, "Max ~p mseg mbcs.", [MMMBC]), + format(FTO, " +M~cmmmbc ~p~n", [AC, MMMBC]), + 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 = fix_alloc}) -> + fcp(FTO, "Cannot be configured."); +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, + 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 instances used.", + [Insts]), + format(FTO, " +M~ct ~p~n", [alloc_char(A), Insts]) + end, + mmbcs(Conf, Alc), + smbcs_lmbcs_mmmbc(Conf, Alc), + sbct(Conf, Alc). + +large_growth(Low, High) -> + High - Low >= ?LARGE_GROWTH_ABS_LIMIT. + +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} = Alc, Acc) -> + {Alc#alloc{segments = #segment{size = 0, + number = 0}}, + Acc}; + (#alloc{alloc_util = true, + low_mbc_blocks_size = Low, + high_mbc_blocks_size = High} = Alc, + {SL, AL}) -> + Growth = High - Low, + case large_growth(Low, High) 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, + low_mbc_blocks_size = Low, + high_mbc_blocks_size = High} = Alc) -> + 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:strip(erlang:system_info(system_version), both, $\n)]), + case erlang:system_info(schedulers) of + 1 -> ok; + Schdlrs -> + MinSchdlrs = case Schdlrs > ?MAX_ALLOCATOR_INSTANCES of + true -> ?MAX_ALLOCATOR_INSTANCES; + false -> Schdlrs + end, + fcp(FTO, + "NOTE: This configuration was made for ~p schedulers. " + "It is very important that at least ~p schedulers " + "are used.", + [Schdlrs, MinSchdlrs]) + 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:tokens(String, " "), 0). + +fc_aux(_IODev, [], 0) -> + ok; +fc_aux(IODev, [], _Len) -> + format(IODev, "~n"); +fc_aux(IODev, [T|Ts], 0) -> + Len = 2 + length(T), + format(IODev, "# ~s", [T]), + fc_aux(IODev, Ts, Len); +fc_aux(IODev, [T|_Ts] = ATs, Len) when (length(T) + Len) >= ?PRINT_WITDH -> + format(IODev, "~n"), + fc_aux(IODev, ATs, 0); +fc_aux(IODev, [T|Ts], Len) -> + NewLen = Len + 1 + length(T), + format(IODev, " ~s", [T]), + fc_aux(IODev, Ts, NewLen). + +%% fcl: format comment line +fcl(FTO) -> + EndStr = "# ", + Precision = 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 = length(Str2), + FieldWidth = -1*(?PRINT_WITDH), + format(FTO, "~*.*.*s~n", [FieldWidth, Precision, $-, Str2]). |