summaryrefslogtreecommitdiff
path: root/src/ejabberd_config.erl
diff options
context:
space:
mode:
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>2017-04-16 15:29:10 +0300
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>2017-04-16 15:29:10 +0300
commit78dba217bfb28c040b2a8a8afc0879f18ed27c6b (patch)
treeecac4511a2c27a86749a8afd51710ff596dc4409 /src/ejabberd_config.erl
parentDefault config file should be ejabberd.yml (diff)
Speedup configuration options lookup
We now avoid excessive ETS lookups; instead, we use dynamically compiled module 'ejabberd_options' keeping the configuration options
Diffstat (limited to 'src/ejabberd_config.erl')
-rw-r--r--src/ejabberd_config.erl120
1 files changed, 100 insertions, 20 deletions
diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl
index 7d05e30d..bfb4192f 100644
--- a/src/ejabberd_config.erl
+++ b/src/ejabberd_config.erl
@@ -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.