Skip to content

Commit

Permalink
Allow caching HiPE-compilation results
Browse files Browse the repository at this point in the history
That way HiPE compilation can be performed during package installation
and will not waste time during every startup.

rabbit_hipe is refactored to support both modes of compilation - during
every server startup or separately with caching in the filesystem.
  • Loading branch information
binarin committed May 20, 2016
1 parent 776dd24 commit 9b89347
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 25 deletions.
27 changes: 27 additions & 0 deletions docs/rabbitmqctl.1.xml
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,33 @@
</para>
</listitem>
</varlistentry>

<varlistentry>
<term><cmdsynopsis><command>hipe_compile</command> <arg choice="req"><replaceable>directory</replaceable></arg></cmdsynopsis></term>
<listitem>
<para>
Performs HiPE-compilation and caches resulting
.beam-files in the given directory.
</para>
<para>
Parent directories are created if necessary. Any
existing <command>.beam</command> files from the
directory are automatically deleted prior to
compilation.
</para>
<para>
To use this precompiled files, you should set
<command>RABBITMQ_SERVER_CODE_PATH</command> environment
variable to directory specified in
<command>hipe_compile</command> invokation.
</para>
<para role="example-prefix">For example:</para>
<screen role="example">rabbitmqctl hipe_compile /tmp/rabbit-hipe/ebin</screen>
<para role="example">
HiPE-compiles modules and stores them to /tmp/rabbit-hipe/ebin directory.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect2>

Expand Down
1 change: 1 addition & 0 deletions scripts/rabbitmq-env
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ DEFAULT_NODE_PORT=5672
[ "x" = "x$RABBITMQ_MNESIA_BASE" ] && RABBITMQ_MNESIA_BASE=${MNESIA_BASE}
[ "x" = "x$RABBITMQ_SERVER_START_ARGS" ] && RABBITMQ_SERVER_START_ARGS=${SERVER_START_ARGS}
[ "x" = "x$RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS" ] && RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS=${SERVER_ADDITIONAL_ERL_ARGS}
[ "x" = "x$RABBITMQ_SERVER_CODE_PATH" ] && RABBITMQ_SERVER_CODE_PATH=${SERVER_CODE_PATH}
[ "x" = "x$RABBITMQ_MNESIA_DIR" ] && RABBITMQ_MNESIA_DIR=${MNESIA_DIR}
[ "x" = "x$RABBITMQ_MNESIA_DIR" ] && RABBITMQ_MNESIA_DIR=${RABBITMQ_MNESIA_BASE}/${RABBITMQ_NODENAME}

Expand Down
6 changes: 5 additions & 1 deletion scripts/rabbitmq-server
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,15 @@ ensure_thread_pool_size() {
}

start_rabbitmq_server() {
# "-pa ${RABBITMQ_SERVER_CODE_PATH}" should be the very first
# command-line argument. In case of using cached HiPE-compilation,
# this will allow for compiled versions of erlang built-in modules
# (e.g. lists) to be loaded.
ensure_thread_pool_size
check_start_params &&
RABBITMQ_CONFIG_FILE=$RABBITMQ_CONFIG_FILE \
exec ${ERL_DIR}erl \
-pa ${RABBITMQ_EBIN_ROOT} \
-pa ${RABBITMQ_SERVER_CODE_PATH} ${RABBITMQ_EBIN_ROOT} \
${RABBITMQ_START_RABBIT} \
${RABBITMQ_NAME_TYPE} ${RABBITMQ_NODENAME} \
-boot "${SASL_BOOT_FILE}" \
Expand Down
13 changes: 12 additions & 1 deletion src/rabbit_control_main.erl
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
reset,
force_reset,
rotate_logs,
hipe_compile,

{join_cluster, [?RAM_DEF]},
change_cluster_node_type,
Expand Down Expand Up @@ -113,7 +114,7 @@
[stop, stop_app, start_app, wait, reset, force_reset, rotate_logs,
join_cluster, change_cluster_node_type, update_cluster_nodes,
forget_cluster_node, rename_cluster_node, cluster_status, status,
environment, eval, force_boot, help, node_health_check]).
environment, eval, force_boot, help, node_health_check, hipe_compile]).

-define(COMMANDS_WITH_TIMEOUT,
[list_user_permissions, list_policies, list_queues, list_exchanges,
Expand Down Expand Up @@ -383,6 +384,16 @@ action(rotate_logs, Node, Args = [Suffix], _Opts, Inform) ->
Inform("Rotating logs to files with suffix \"~s\"", [Suffix]),
call(Node, {rabbit, rotate_logs, Args});

action(hipe_compile, _Node, [TargetDir], _Opts, _Inform) ->
ok = application:load(rabbit),
case rabbit_hipe:can_hipe_compile() of
true ->
{ok, _, _} = rabbit_hipe:compile_to_directory(TargetDir),
ok;
false ->
"HiPE compilation is not supported"
end;

action(close_connection, Node, [PidStr, Explanation], _Opts, Inform) ->
Inform("Closing connection \"~s\"", [PidStr]),
rpc_call(Node, rabbit_networking, close_connection,
Expand Down
10 changes: 10 additions & 0 deletions src/rabbit_file.erl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
-export([append_file/2, ensure_parent_dirs_exist/1]).
-export([rename/2, delete/1, recursive_delete/1, recursive_copy/2]).
-export([lock_file/1]).
-export([filename_as_a_directory/1]).

-import(file_handle_cache, [with_handle/1, with_handle/2]).

Expand Down Expand Up @@ -58,6 +59,7 @@
(file:filename(), file:filename())
-> rabbit_types:ok_or_error({file:filename(), file:filename(), any()})).
-spec(lock_file/1 :: (file:filename()) -> rabbit_types:ok_or_error('eexist')).
-spec(filename_as_a_directory/1 :: (file:filename()) -> file:filename()).

-endif.

Expand Down Expand Up @@ -305,3 +307,11 @@ lock_file(Path) ->
ok = prim_file:close(Lock)
end)
end.

filename_as_a_directory(FileName) ->
case lists:last(FileName) of
"/" ->
FileName;
_ ->
FileName ++ "/"
end.
89 changes: 66 additions & 23 deletions src/rabbit_hipe.erl
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
%% practice 2 processes seems just as fast as any other number > 1,
%% and keeps the progress bar realistic-ish.
-define(HIPE_PROCESSES, 2).
-export([maybe_hipe_compile/0, log_hipe_result/1]).

%% HiPE compilation happens before we have log handlers - so we have
%% to io:format/2, it's all we can do.
-export([maybe_hipe_compile/0, log_hipe_result/1]).
-export([compile_to_directory/1]).
-export([can_hipe_compile/0]).

%% Compile and load during server startup sequence
maybe_hipe_compile() ->
{ok, Want} = application:get_env(rabbit, hipe_compile),
Can = code:which(hipe) =/= non_existing,
case {Want, Can} of
case {Want, can_hipe_compile()} of
{true, true} -> hipe_compile();
{true, false} -> false;
{false, _} -> {ok, disabled}
Expand All @@ -33,38 +33,49 @@ log_hipe_result(false) ->
rabbit_log:warning(
"Not HiPE compiling: HiPE not found in this Erlang installation.~n").

hipe_compile() ->
hipe_compile(fun compile_and_load/1, false).

compile_to_directory(Dir0) ->
Dir = rabbit_file:filename_as_a_directory(Dir0),
ok = prepare_ebin_directory(Dir),
hipe_compile(fun (Mod) -> compile_and_save(Mod, Dir) end, true).

need_compile(Mod, Force) ->
Exists = code:which(Mod) =/= non_existing,
%% We skip modules already natively compiled. This
%% happens when RabbitMQ is stopped (just the
%% application, not the entire node) and started
%% again.
NotYetCompiled = not already_hipe_compiled(Mod),
NotVersioned = not compiled_with_version_support(Mod),
Exists andalso (Force orelse (NotYetCompiled andalso NotVersioned)).

%% HiPE compilation happens before we have log handlers and can take a
%% long time, so make an exception to our no-stdout policy and display
%% progress via stdout.
hipe_compile() ->
hipe_compile(CompileFun, Force) ->
{ok, HipeModulesAll} = application:get_env(rabbit, hipe_modules),
HipeModules = [HM || HM <- HipeModulesAll,
code:which(HM) =/= non_existing andalso
%% We skip modules already natively compiled. This
%% happens when RabbitMQ is stopped (just the
%% application, not the entire node) and started
%% again.
already_hipe_compiled(HM)
andalso (not compiled_with_version_support(HM))],
HipeModules = lists:filter(fun(Mod) -> need_compile(Mod, Force) end, HipeModulesAll),
case HipeModules of
[] -> {ok, already_compiled};
_ -> do_hipe_compile(HipeModules)
_ -> do_hipe_compile(HipeModules, CompileFun)
end.

already_hipe_compiled(Mod) ->
try
%% OTP 18.x or later
Mod:module_info(native) =:= false
Mod:module_info(native) =:= true
%% OTP prior to 18.x
catch error:badarg ->
code:is_module_native(Mod) =:= false
code:is_module_native(Mod) =:= true
end.

compiled_with_version_support(Mod) ->
proplists:get_value(erlang_version_support, Mod:module_info(attributes))
=/= undefined.

do_hipe_compile(HipeModules) ->
do_hipe_compile(HipeModules, CompileFun) ->
Count = length(HipeModules),
io:format("~nHiPE compiling: |~s|~n |",
[string:copies("-", Count)]),
Expand All @@ -79,11 +90,7 @@ do_hipe_compile(HipeModules) ->
%% advanced API does not load automatically the code, except if the
%% 'load' option is set.
PidMRefs = [spawn_monitor(fun () -> [begin
{M, Beam, _} =
code:get_object_code(M),
{ok, _} =
hipe:compile(M, [], Beam,
[o3, load]),
CompileFun(M),
io:format("#")
end || M <- Ms]
end) ||
Expand All @@ -101,3 +108,39 @@ split(L, N) -> split0(L, [[] || _ <- lists:seq(1, N)]).

split0([], Ls) -> Ls;
split0([I | Is], [L | Ls]) -> split0(Is, Ls ++ [[I | L]]).

prepare_ebin_directory(Dir) ->
ok = rabbit_file:ensure_dir(Dir),
ok = delete_beam_files(Dir),
ok.

delete_beam_files(Dir) ->
{ok, Files} = file:list_dir(Dir),
lists:foreach(fun(File) ->
case filename:extension(File) of
".beam" ->
ok = file:delete(filename:join([Dir, File]));
_ ->
ok
end
end,
Files).

compile_and_load(Mod) ->
{Mod, Beam, _} = code:get_object_code(Mod),
{ok, _} = hipe:compile(Mod, [], Beam, [o3, load]).

compile_and_save(Module, Dir) ->
{Module, BeamCode, _} = code:get_object_code(Module),
BeamName = filename:join([Dir, atom_to_list(Module) ++ ".beam"]),
{ok, {Architecture, NativeCode}} = hipe:compile(Module, [], BeamCode, [o3]),
{ok, _, Chunks0} = beam_lib:all_chunks(BeamCode),
ChunkName = hipe_unified_loader:chunk_name(Architecture),
Chunks1 = lists:keydelete(ChunkName, 1, Chunks0),
Chunks = Chunks1 ++ [{ChunkName,NativeCode}],
{ok, BeamPlusNative} = beam_lib:build_module(Chunks),
ok = file:write_file(BeamName, BeamPlusNative),
BeamName.

can_hipe_compile() ->
code:which(hipe) =/= non_existing.

0 comments on commit 9b89347

Please sign in to comment.