Speedup configuration options lookup

We now avoid excessive ETS lookups; instead, we use dynamically
compiled module 'ejabberd_options' keeping the configuration options
This commit is contained in:
Evgeniy Khramtsov 2017-04-16 15:29:10 +03:00
parent 3b14b35252
commit 78dba217bf
3 changed files with 141 additions and 31 deletions

View File

@ -38,7 +38,8 @@
is_elixir_enabled/0, v_dbs/1, v_dbs_mods/1,
default_db/1, default_db/2, default_ram_db/1, default_ram_db/2,
default_queue_type/1, queue_dir/0, fsm_limit_opts/1,
use_cache/1, cache_size/1, cache_missed/1, cache_life_time/1]).
use_cache/1, cache_size/1, cache_missed/1, cache_life_time/1,
dump/0]).
-export([start/2]).
@ -773,6 +774,7 @@ set_opts(State) ->
end,
case mnesia:transaction(F) of
{atomic, _} ->
recompile_options(),
set_log_level();
{aborted,{no_exists,Table}} ->
MnesiaDirectory = mnesia:system_info(directory),
@ -788,7 +790,7 @@ set_opts(State) ->
end.
set_log_level() ->
Level = ejabberd_config:get_option(
Level = get_option(
loglevel,
fun(P) when P>=0, P=<5 -> P end,
4),
@ -806,7 +808,8 @@ add_option(Opt, Val) ->
mnesia:transaction(fun() ->
mnesia:write(#local_config{key = Opt,
value = Val})
end).
end),
recompile_options().
-spec prepare_opt_val(any(), any(), check_fun(), any()) -> any().
@ -871,22 +874,23 @@ get_option(Opt, F) ->
get_option(Opt, F, Default) when is_atom(Opt) ->
get_option({Opt, global}, F, Default);
get_option(Opt, F, Default) ->
case Opt of
{O, global} when is_atom(O) -> ok;
{O, H} when is_atom(O), is_binary(H) -> ok;
_ -> ?WARNING_MSG("Option ~p has invalid (outdated?) format. "
"This is likely a bug", [Opt])
end,
case ets:lookup(local_config, Opt) of
[#local_config{value = Val}] ->
prepare_opt_val(Opt, Val, F, Default);
_ ->
case Opt of
{Key, Host} when Host /= global ->
get_option({Key, global}, F, Default);
_ ->
Default
end
{Key, Host} = case Opt of
{O, global} when is_atom(O) -> Opt;
{O, H} when is_atom(O), is_binary(H) -> Opt;
_ ->
?WARNING_MSG("Option ~p has invalid (outdated?) "
"format. This is likely a bug", [Opt]),
{undefined, global}
end,
case ejabberd_options:is_known(Key) of
true ->
try ejabberd_options:Key(Host) of
Val -> prepare_opt_val(Opt, Val, F, Default)
catch _:function_clause ->
Default
end;
false ->
Default
end.
-spec has_option(atom() | {atom(), global | binary()}) -> any().
@ -1481,7 +1485,7 @@ opt_type(_) ->
-spec may_hide_data(any()) -> any().
may_hide_data(Data) ->
case ejabberd_config:get_option(
case get_option(
hide_sensitive_log_data,
fun(false) -> false;
(true) -> true
@ -1531,3 +1535,79 @@ cache_missed(Host) ->
%% NOTE: the integer value returned is in *seconds*
cache_life_time(Host) ->
get_option({cache_life_time, Host}, opt_type(cache_life_time), 3600).
%%%===================================================================
%%% Dynamic config compilation
%%%===================================================================
-spec recompile_options() -> ok.
recompile_options() ->
Exprs = get_exprs(),
try compile_exprs(Exprs)
catch E:R ->
?CRITICAL_MSG("Failed to compile ejabberd_options:~n~s",
[string:join(Exprs, io_lib:nl())]),
erlang:raise(E, R, erlang:get_stacktrace())
end.
-spec get_exprs() -> [string()].
get_exprs() ->
Opts = lists:foldl(
fun(#local_config{key = {Opt, Host}, value = Val}, D) ->
Hosts = maps:get(Opt, D, #{}),
maps:put(Opt, maps:put(Host, Val, Hosts), D)
end, #{}, ets:tab2list(local_config)),
Funs = maps:fold(
fun(Opt, Vals, Acc) ->
HostVals = lists:reverse(lists:keysort(1, maps:to_list(Vals))),
[string:join(
lists:map(
fun({global, Val}) ->
io_lib:format("'~s'(_) -> ~p", [Opt, Val]);
({Host, Val}) ->
io_lib:format("'~s'(~p) -> ~p", [Opt, Host, Val])
end, HostVals),
";" ++ io_lib:nl()) ++ "."|Acc]
end, [], Opts),
Module = "-module(ejabberd_options).",
Export = "-compile(export_all).",
Knowns = maps:fold(
fun(Opt, _, Acc) ->
io_lib:format("is_known('~s') -> true;~n", [Opt]) ++ Acc
end, "", Opts) ++ "is_known(_) -> false.",
[Module, Export, Knowns|Funs].
-spec compile_exprs([string()]) -> ok.
compile_exprs(Exprs) ->
Forms = lists:map(
fun(Expr) ->
{ok, Tokens, _} = erl_scan:string(lists:flatten(Expr)),
{ok, Form} = erl_parse:parse_form(Tokens),
Form
end, Exprs),
{ok, Code} = case compile:forms(Forms, []) of
{ok, ejabberd_options, Bin} -> {ok, Bin};
{ok, ejabberd_options, Bin, _Warnings} -> {ok, Bin};
Error -> Error
end,
{module, _} = code:load_binary(ejabberd_options, "nofile", Code),
ok.
%% @doc This is only for debugging purposes, likely to report a bug
-spec dump() -> ok.
dump() ->
ETSFile = filename:join("/tmp", "ejabberd_options.ets"),
ErlFile = filename:join("/tmp", "ejabberd_options.erl"),
ETSData = io_lib:format("~p~n", [ets:tab2list(local_config)]),
ErlData = io_lib:format("~s~n", [str:join(get_exprs(), io_lib:nl())]),
case file:write_file(ETSFile, ETSData) of
ok -> io:format("ETS data written to ~s~n", [ETSFile]);
{error, Reason1} ->
io:format("Failed to write to ~s: ~s",
[ETSFile, file:format_error(Reason1)])
end,
case file:write_file(ErlFile, ErlData) of
ok -> io:format("Dynamic module written to ~s~n", [ErlFile]);
{error, Reason2} ->
io:format("Failed to write to ~s: ~s",
[ErlFile, file:format_error(Reason2)])
end.

View File

@ -408,17 +408,6 @@ add_listener(PortIP, Module, Opts) ->
PortIP1 = {Port, IPT, Proto},
case start_listener(PortIP1, Module, Opts) of
{ok, _Pid} ->
Ports = case ejabberd_config:get_option(
listen, fun validate_cfg/1) of
undefined ->
[];
Ls ->
Ls
end,
Ports1 = lists:keydelete(PortIP1, 1, Ports),
Ports2 = [{PortIP1, Module, Opts} | Ports1],
Ports3 = lists:map(fun transform_option/1, Ports2),
ejabberd_config:add_option(listen, Ports3),
ok;
{error, {already_started, _Pid}} ->
{error, {already_started, PortIP}};

41
src/ejabberd_options.erl Normal file
View File

@ -0,0 +1,41 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% @doc
%%% This is a stub module which will be replaced during
%%% configuration load, see ejabberd_config:recompile_options/0.
%%% The only purpose of this file is to shut up xref/dialyzer
%%% @end
%%% Created : 16 Apr 2017 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2017 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%-------------------------------------------------------------------
-module(ejabberd_options).
%% API
-export([is_known/1]).
%%%===================================================================
%%% API
%%%===================================================================
is_known(_) ->
false.
%%%===================================================================
%%% Internal functions
%%%===================================================================