]> granicus.if.org Git - ejabberd/commitdiff
Speedup configuration options lookup
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>
Sun, 16 Apr 2017 12:29:10 +0000 (15:29 +0300)
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>
Sun, 16 Apr 2017 12:29:10 +0000 (15:29 +0300)
We now avoid excessive ETS lookups; instead, we use dynamically
compiled module 'ejabberd_options' keeping the configuration options

src/ejabberd_config.erl
src/ejabberd_listener.erl
src/ejabberd_options.erl [new file with mode: 0644]

index 7d05e30d1405b4e4e92de106a1681ce8d5ce4e29..bfb4192fd361d2f2bfe0576aa93099f25befd3b8 100644 (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.
index 6e3ba4599fa4cb0a31f35f7457f44c324b99181b..fc6cacc5fec91b077e16401f5d3c8c9411f059b4 100644 (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}};
diff --git a/src/ejabberd_options.erl b/src/ejabberd_options.erl
new file mode 100644 (file)
index 0000000..bcde437
--- /dev/null
@@ -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
+%%%===================================================================