diff --git a/Makefile b/Makefile index cf32120cd21..8c8202f93d4 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,7 @@ test_preset: test_deps run: deps compile quickrun quickrun: etc/ejabberd.cfg certs_priv - erl -sname mongooseim@localhost -setcookie ejabberd -pa deps/*/ebin apps/*/ebin -config rel/files/app.config -s ejabberd + erl -sname mongooseim@localhost -setcookie ejabberd -pa ebin deps/*/ebin apps/*/ebin -config rel/files/app.config -s mongooseim etc/ejabberd.cfg: @mkdir -p $(@D) diff --git a/apps/ejabberd/src/ejabberd_c2s.erl b/apps/ejabberd/src/ejabberd_c2s.erl index 359b4fc6b11..32df5ffbc47 100644 --- a/apps/ejabberd/src/ejabberd_c2s.erl +++ b/apps/ejabberd/src/ejabberd_c2s.erl @@ -80,7 +80,6 @@ start_link(SockData, Opts) -> ?GEN_FSM:start_link(ejabberd_c2s, [SockData, Opts], fsm_limit_opts(Opts) ++ ?FSMOPTS). - socket_type() -> xml_stream. @@ -1590,12 +1589,10 @@ change_shaper(StateData, JID) -> -spec send_text(state(), Text :: binary()) -> any(). -send_text(StateData, Text) when StateData#state.xml_socket -> - ?DEBUG("Send Text on stream = ~p", [lists:flatten(Text)]), - (StateData#state.sockmod):send_xml(StateData#state.socket, - {xmlstreamraw, Text}); send_text(StateData, Text) -> ?DEBUG("Send XML on stream = ~p", [Text]), + Size = size(Text), + mongoose_metrics:update([data, xmpp, sent, xml_stanza_size], Size), (StateData#state.sockmod):send(StateData#state.socket, Text). -spec maybe_send_element_safe(state(), El :: jlib:xmlel()) -> any(). @@ -1657,11 +1654,11 @@ send_header(StateData, Server, Version, Lang) -> "" -> ""; _ -> [" xml:lang='", Lang, "'"] end, - Header = io_lib:format(?STREAM_HEADER, + Header = list_to_binary(io_lib:format(?STREAM_HEADER, [StateData#state.streamid, Server, VersionStr, - LangStr]), + LangStr])), send_text(StateData, Header). -spec maybe_send_trailer_safe(State :: state()) -> any(). diff --git a/apps/ejabberd/src/ejabberd_c2s.hrl b/apps/ejabberd/src/ejabberd_c2s.hrl index 274b0074ce3..86127a4a788 100644 --- a/apps/ejabberd/src/ejabberd_c2s.hrl +++ b/apps/ejabberd/src/ejabberd_c2s.hrl @@ -118,7 +118,7 @@ "id='~s' from='~s'~s~s>" ). --define(STREAM_TRAILER, ""). +-define(STREAM_TRAILER, <<"">>). -define(INVALID_NS_ERR, ?SERR_INVALID_NAMESPACE). -define(INVALID_XML_ERR, ?SERR_XML_NOT_WELL_FORMED). diff --git a/apps/ejabberd/src/ejabberd_odbc.erl b/apps/ejabberd/src/ejabberd_odbc.erl index 6bbb09983c6..eeffabe7de9 100644 --- a/apps/ejabberd/src/ejabberd_odbc.erl +++ b/apps/ejabberd/src/ejabberd_odbc.erl @@ -68,6 +68,9 @@ session_established/2, session_established/3]). +%% internal usage +-export([get_db_info/1]). + -include("ejabberd.hrl"). -record(state, {db_ref, @@ -168,6 +171,8 @@ keep_alive(PID) -> ?GEN_FSM:sync_send_event(PID, {sql_cmd, {sql_query, ?KEEPALIVE_QUERY}, now()}, ?KEEPALIVE_TIMEOUT). +get_db_info(Pid) -> + ?GEN_FSM:sync_send_all_state_event(Pid, get_db_info). %% This function is intended to be used from inside an sql_transaction: sql_query_t(Query) -> QRes = sql_query_internal(Query), @@ -379,6 +384,9 @@ session_established(Event, State) -> handle_event(_Event, StateName, State) -> {next_state, StateName, State}. +handle_sync_event(get_db_info, _, StateName, + #state{db_ref = DbRef, db_type = DbType} = State) -> + {reply, {ok, DbType, DbRef}, StateName, State}; handle_sync_event(_Event, _From, StateName, State) -> {reply, {error, badarg}, StateName, State}. diff --git a/apps/ejabberd/src/ejabberd_receiver.erl b/apps/ejabberd/src/ejabberd_receiver.erl index 71b79d11920..73654edc4c6 100644 --- a/apps/ejabberd/src/ejabberd_receiver.erl +++ b/apps/ejabberd/src/ejabberd_receiver.erl @@ -216,9 +216,10 @@ handle_info({Tag, _TCPSocket, Data}, #state{socket = Socket, c2s_pid = C2SPid, sock_mod = SockMod} = State) - when (Tag == tcp) or (Tag == ssl) or (Tag == ejabberd_xml) -> + when (Tag == tcp) or (Tag == ssl) -> case SockMod of ejabberd_tls -> + mongoose_metrics:update([data, xmpp, received, encrypted_size], size(Data)), case ejabberd_tls:recv_data(Socket, Data) of {ok, TLSData} -> {noreply, process_data(TLSData, State), @@ -227,6 +228,7 @@ handle_info({Tag, _TCPSocket, Data}, {stop, normal, State} end; ejabberd_zlib -> + mongoose_metrics:update([data, xmpp, received, compressed_size], size(Data)), case ejabberd_zlib:recv_data(Socket, Data) of {ok, ZlibData} -> {noreply, process_data(ZlibData, State), @@ -334,8 +336,10 @@ process_data(Data, shaper_state = ShaperState, c2s_pid = C2SPid} = State) -> ?DEBUG("Received XML on stream = \"~s\"", [Data]), + Size = size(Data), + mongoose_metrics:update([data, xmpp, received, xml_stanza_size], Size), XMLStreamState1 = xml_stream:parse(XMLStreamState, Data), - {NewShaperState, Pause} = shaper:update(ShaperState, size(Data)), + {NewShaperState, Pause} = shaper:update(ShaperState, Size), if C2SPid == undefined -> ok; diff --git a/apps/ejabberd/src/ejabberd_socket.erl b/apps/ejabberd/src/ejabberd_socket.erl index 7dd7407644c..955a949b0d4 100644 --- a/apps/ejabberd/src/ejabberd_socket.erl +++ b/apps/ejabberd/src/ejabberd_socket.erl @@ -33,7 +33,6 @@ connect/4, starttls/2, starttls/3, - compress/1, compress/3, reset_stream/1, send/2, @@ -171,16 +170,6 @@ starttls(SocketData, TLSOpts, Data) -> send(SocketData, Data), SocketData#socket_state{socket = TLSSocket, sockmod = ejabberd_tls}. - --spec compress(socket_state()) -> socket_state(). -compress(SocketData) -> - {ok, ZlibSocket} = ejabberd_zlib:enable_zlib( - SocketData#socket_state.sockmod, - SocketData#socket_state.socket), - ejabberd_receiver:compress(SocketData#socket_state.receiver, ZlibSocket), - SocketData#socket_state{socket = ZlibSocket, sockmod = ejabberd_zlib}. - - -spec compress(socket_state(), integer(), _) -> socket_state(). compress(SocketData, InflateSizeLimit, Data) -> {ok, ZlibSocket} = ejabberd_zlib:enable_zlib( diff --git a/apps/ejabberd/src/ejabberd_sup.erl b/apps/ejabberd/src/ejabberd_sup.erl index 6298338ac01..53c95ea4fff 100644 --- a/apps/ejabberd/src/ejabberd_sup.erl +++ b/apps/ejabberd/src/ejabberd_sup.erl @@ -169,6 +169,10 @@ init([]) -> brutal_kill, worker, [mod_muc_iq]}, + MAM = + {mod_mam_sup, + {mod_mam_sup, start_link, []}, + permanent, infinity, supervisor, [mod_mam_sup]}, ShaperSpecs = shaper_srv:child_specs(), {ok, {{one_for_one, 10, 1}, @@ -190,4 +194,5 @@ init([]) -> IQSupervisor, STUNSupervisor, Listener, - MucIQ]}}. + MucIQ, + MAM]}}. diff --git a/apps/ejabberd/src/ejabberd_tls.erl b/apps/ejabberd/src/ejabberd_tls.erl index 9cdd20911c7..693f12ac480 100644 --- a/apps/ejabberd/src/ejabberd_tls.erl +++ b/apps/ejabberd/src/ejabberd_tls.erl @@ -149,6 +149,7 @@ send(#tlssock{tcpsock = TCPSocket, tlsport = Port} = TLSSock, Packet) -> %?PRINT("OUT: ~p~n", [{TCPSocket, lists:flatten(Packet)}]), case port_control(Port, ?GET_ENCRYPTED_OUTPUT, []) of <<0, Out/binary>> -> + mongoose_metrics:update([data, xmpp, sent, encrypted_size], size(Out)), gen_tcp:send(TCPSocket, Out); <<1, Error/binary>> -> {error, binary_to_list(Error)} diff --git a/apps/ejabberd/src/ejabberd_zlib.erl b/apps/ejabberd/src/ejabberd_zlib.erl index 49ae6083f6b..cda17e91cd0 100644 --- a/apps/ejabberd/src/ejabberd_zlib.erl +++ b/apps/ejabberd/src/ejabberd_zlib.erl @@ -95,6 +95,7 @@ send(#zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port}, Packet) -> case port_control(Port, ?DEFLATE, Packet) of <<0, Out/binary>> -> + mongoose_metrics:update([data, xmpp, sent, compressed_size], size(Out)), SockMod:send(Socket, Out); <<1, Error/binary>> -> {error, erlang:binary_to_existing_atom(Error, utf8)}; diff --git a/apps/ejabberd/src/gen_mod.erl b/apps/ejabberd/src/gen_mod.erl index de8d6f37c85..637120db015 100644 --- a/apps/ejabberd/src/gen_mod.erl +++ b/apps/ejabberd/src/gen_mod.erl @@ -30,6 +30,7 @@ -export([start/0, start_module/3, start_backend_module/2, + start_backend_module/3, stop_module/2, stop_module_keep_config/2, reload_module/3, @@ -44,6 +45,7 @@ loaded_modules_with_opts/1, get_hosts/2, get_module_proc/2, + backend_code/3, is_loaded/2]). -include("ejabberd.hrl"). @@ -107,24 +109,74 @@ start_module(Host, Module, Opts0) -> -spec start_backend_module(module(), list()) -> no_return(). start_backend_module(Module, Opts) -> - ModuleStr = atom_to_list(Module), - BackendModuleStr = ModuleStr ++ "_backend", + start_backend_module(Module, Opts, []). + +start_backend_module(Module, Opts, TrackedFuncs) -> Backend = gen_mod:get_opt(backend, Opts, mnesia), - {Mod, Code} = dynamic_compile:from_string(backend_code(ModuleStr, Backend)), - code:load_binary(Mod, BackendModuleStr ++ ".erl", Code). - --spec backend_code(string(), atom()) -> string(). -backend_code(Module, Backend) when is_atom(Backend) -> - BackendModule = Module ++ "_backend", - lists:flatten( - ["-module(",BackendModule,"). - -export([backend/0]). - - -spec backend() -> atom(). - backend() ->", - Module,"_", - atom_to_list(Backend), - ".\n"]). + {BackendModuleStr, CodeString} = backend_code(Module, Backend, TrackedFuncs), + {Mod, Code} = dynamic_compile:from_string(CodeString), + code:load_binary(Mod, BackendModuleStr ++ ".erl", Code), + ensure_backend_metrics(Module, TrackedFuncs). + +-spec backend_code(string(), atom(), list()) -> string(). +backend_code(Module, Backend, TrackedFuncs) when is_atom(Backend) -> + Callbacks = Module:behaviour_info(callbacks), + ModuleStr = atom_to_list(Module), + BackendModuleName = ModuleStr ++ "_backend", + RealBackendModule = ModuleStr++"_"++atom_to_list(Backend), + BehaviourExports = [generate_export(F, A) || {F, A} <- Callbacks], + + BehaviourImpl = [generate_fun(Module, RealBackendModule, F, A, TrackedFuncs) || {F, A} <- Callbacks], + Code = lists:flatten( + ["-module(", BackendModuleName,").\n", + "-export([backend/0]).\n", + BehaviourExports, + + + "-spec backend() -> atom().\n", + "backend() ->", RealBackendModule,".\n", + BehaviourImpl + ]), + {BackendModuleName, Code}. + +generate_export(F, A) -> + "-export(["++atom_to_list(F)++"/"++integer_to_list(A)++"]).\n". + +generate_fun(BaseModule, RealBackendModule, F, A, TrackedFuncs) -> + Args = string:join(["A"++integer_to_list(I) || I <- lists:seq(1, A)], ", "), + IsTracked = lists:member(F, TrackedFuncs), + [fun_header(F, Args)," ->\n", + generate_fun_body(IsTracked, BaseModule, RealBackendModule, F, Args)]. + +fun_header(F, Args) -> + [atom_to_list(F),"(",Args,")"]. + +-define(METRIC(Module, Op), [backends, Module, Op]). + +generate_fun_body(false, _, RealBackendModule, F, Args) -> + [" ",RealBackendModule,":",fun_header(F, Args),".\n"]; +generate_fun_body(true, BaseModule, RealBackendModule, F, Args) -> + FS = atom_to_list(F), +%% returned is the following +%% {Time, Result} = timer:tc(Backend, F, Args), +%% mongoose_metrics:update(?METRIC(Backend, F), Time), +%% Result. + [" {Time, Result} = timer:tc(",RealBackendModule,", ",FS,", [",Args,"]),\n", + " mongoose_metrics:update(", + io_lib:format("~p", [?METRIC(BaseModule, F)]), + ", Time),\n", + " Result.\n"]. + +ensure_backend_metrics(Module, Ops) -> + EnsureFun = fun(Op) -> + case exometer:info(?METRIC(Module, Op), type) of + undefined -> + exometer:new(?METRIC(Module, Op), histogram); + _ -> + ok + end + end, + lists:foreach(EnsureFun, Ops). -spec is_app_running(_) -> boolean(). is_app_running(AppName) -> diff --git a/apps/ejabberd/src/mod_last.erl b/apps/ejabberd/src/mod_last.erl index cc51e57639f..9dd8b4b910f 100644 --- a/apps/ejabberd/src/mod_last.erl +++ b/apps/ejabberd/src/mod_last.erl @@ -46,7 +46,7 @@ -include("mod_privacy.hrl"). -include("mod_last.hrl"). --define(BACKEND, (mod_last_backend:backend())). +-define(BACKEND, mod_last_backend). %% ------------------------------------------------------------------ %% Backend callbacks @@ -82,7 +82,7 @@ start(Host, Opts) -> IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue), - gen_mod:start_backend_module(?MODULE, Opts), + gen_mod:start_backend_module(?MODULE, Opts, [get_last, set_last_info]), ?BACKEND:init(Host, Opts), gen_iq_handler:add_iq_handler(ejabberd_local, Host, diff --git a/apps/ejabberd/src/mod_mam_muc_odbc_async_pool_writer.erl b/apps/ejabberd/src/mod_mam_muc_odbc_async_pool_writer.erl index 24a574a8683..1be5164f3b8 100644 --- a/apps/ejabberd/src/mod_mam_muc_odbc_async_pool_writer.erl +++ b/apps/ejabberd/src/mod_mam_muc_odbc_async_pool_writer.erl @@ -157,14 +157,14 @@ start_worker(WriterProc, N, Host) -> 5000, worker, [mod_mam_muc_odbc_async_writer]}, - supervisor:start_child(ejabberd_sup, WriterChildSpec). + supervisor:start_child(mod_mam_sup, WriterChildSpec). -spec stop_worker(atom()) -> 'ok' | {'error','not_found' | 'restarting' | 'running' | 'simple_one_for_one'}. stop_worker(Proc) -> - supervisor:terminate_child(ejabberd_sup, Proc), - supervisor:delete_child(ejabberd_sup, Proc). + supervisor:terminate_child(mod_mam_sup, Proc), + supervisor:delete_child(mod_mam_sup, Proc). -spec start_link(atom(),_,_) -> 'ignore' | {'error',_} | {'ok',pid()}. @@ -374,6 +374,8 @@ init([Host, N]) -> %%-------------------------------------------------------------------- -spec handle_call('wait_flushing', _, state()) -> {'noreply', state()} | {'reply','ok',state()}. +handle_call(get_connection, _From, State=#state{conn = Conn}) -> + {reply, Conn, State}; handle_call(wait_flushing, _From, State=#state{acc=[]}) -> {reply, ok, State}; handle_call(wait_flushing, From, diff --git a/apps/ejabberd/src/mod_mam_muc_odbc_async_writer.erl b/apps/ejabberd/src/mod_mam_muc_odbc_async_writer.erl index e9c143932fe..fe43a33b7e6 100644 --- a/apps/ejabberd/src/mod_mam_muc_odbc_async_writer.erl +++ b/apps/ejabberd/src/mod_mam_muc_odbc_async_writer.erl @@ -219,15 +219,15 @@ is_recent_entries_required(_End, _Now) -> | {'ok','undefined' | pid(),_}. start_server(Host) -> WriterProc = srv_name(Host), - supervisor:start_child(ejabberd_sup, writer_child_spec(WriterProc, Host)). + supervisor:start_child(mod_mam_sup, writer_child_spec(WriterProc, Host)). -spec stop_server(ejabberd:server()) -> 'ok' | {'error','not_found' | 'restarting' | 'running' | 'simple_one_for_one'}. stop_server(Host) -> Proc = srv_name(Host), - supervisor:terminate_child(ejabberd_sup, Proc), - supervisor:delete_child(ejabberd_sup, Proc). + supervisor:terminate_child(mod_mam_sup, Proc), + supervisor:delete_child(mod_mam_sup, Proc). writer_child_spec(WriterProc, Host) -> {WriterProc, @@ -282,6 +282,8 @@ init([Host]) -> %%-------------------------------------------------------------------- -spec handle_call('wait_flushing',_, state()) -> {'noreply',state()} | {'reply','ok',state()}. +handle_call(get_connection, _From, State=#state{conn = Conn}) -> + {reply, Conn, State}; handle_call(wait_flushing, _From, State=#state{acc=[]}) -> {reply, ok, State}; handle_call(wait_flushing, From, State=#state{subscribers=Subs}) -> diff --git a/apps/ejabberd/src/mod_mam_odbc_async_pool_writer.erl b/apps/ejabberd/src/mod_mam_odbc_async_pool_writer.erl index e3d597b1963..d6055e1be46 100644 --- a/apps/ejabberd/src/mod_mam_odbc_async_pool_writer.erl +++ b/apps/ejabberd/src/mod_mam_odbc_async_pool_writer.erl @@ -175,11 +175,11 @@ start_worker(WriterProc, N, Host) -> 5000, worker, [mod_mam_odbc_async_writer]}, - supervisor:start_child(ejabberd_sup, WriterChildSpec). + supervisor:start_child(mod_mam_sup, WriterChildSpec). stop_worker(Proc) -> - supervisor:terminate_child(ejabberd_sup, Proc), - supervisor:delete_child(ejabberd_sup, Proc). + supervisor:terminate_child(mod_mam_sup, Proc), + supervisor:delete_child(mod_mam_sup, Proc). start_link(ProcName, N, Host) -> @@ -370,6 +370,8 @@ init([Host, N]) -> %% {stop, Reason, State} %% Description: Handling call messages %%-------------------------------------------------------------------- +handle_call(get_connection, _From, State=#state{conn = Conn}) -> + {reply, Conn, State}; handle_call(wait_flushing, _From, State=#state{acc=[]}) -> {reply, ok, State}; handle_call(wait_flushing, From, diff --git a/apps/ejabberd/src/mod_mam_odbc_async_writer.erl b/apps/ejabberd/src/mod_mam_odbc_async_writer.erl index b562a50ffa9..3320a51fbbb 100644 --- a/apps/ejabberd/src/mod_mam_odbc_async_writer.erl +++ b/apps/ejabberd/src/mod_mam_odbc_async_writer.erl @@ -255,7 +255,6 @@ is_recent_entries_required(End, Now) when is_integer(End) -> is_recent_entries_required(_End, _Now) -> true. - %%==================================================================== %% Internal functions %%==================================================================== @@ -264,7 +263,7 @@ is_recent_entries_required(_End, _Now) -> | {'ok','undefined' | pid()} | {'ok','undefined' | pid(),_}. start_server(Host) -> WriterProc = srv_name(Host), - supervisor:start_child(ejabberd_sup, writer_child_spec(WriterProc, Host)). + supervisor:start_child(mod_mam_sup, writer_child_spec(WriterProc, Host)). -spec stop_server(ejabberd:server()) -> 'ok' @@ -333,6 +332,8 @@ init([Host]) -> %%-------------------------------------------------------------------- -spec handle_call('wait_flushing', _, state()) -> {'noreply',state()} | {'reply','ok',state()}. +handle_call(get_connection, _From, State=#state{conn = Conn}) -> + {reply, Conn, State}; handle_call(wait_flushing, _From, State=#state{acc=[]}) -> {reply, ok, State}; handle_call(wait_flushing, From, State=#state{subscribers=Subs}) -> diff --git a/apps/ejabberd/src/mod_mam_sup.erl b/apps/ejabberd/src/mod_mam_sup.erl new file mode 100644 index 00000000000..52ab6ae02d9 --- /dev/null +++ b/apps/ejabberd/src/mod_mam_sup.erl @@ -0,0 +1,76 @@ +%%============================================================================== +%% Copyright 2014 Erlang Solutions Ltd. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%============================================================================== +-module(mod_mam_sup). + + +-behaviour(supervisor). + +%% API +-export([start_link/0]). + +%% Supervisor callbacks +-export([init/1]). + +-define(SERVER, ?MODULE). + +%%%=================================================================== +%%% API functions +%%%=================================================================== + +%%-------------------------------------------------------------------- +%% @doc +%% Starts the supervisor +%% +%% @end +%%-------------------------------------------------------------------- +-spec(start_link() -> + {ok, Pid :: pid()} | ignore | {error, Reason :: term()}). +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +%%%=================================================================== +%%% Supervisor callbacks +%%%=================================================================== + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Whenever a supervisor is started using supervisor:start_link/[2,3], +%% this function is called by the new process to find out about +%% restart strategy, maximum restart frequency and child +%% specifications. +%% +%% @end +%%-------------------------------------------------------------------- +-spec(init(Args :: term()) -> + {ok, {SupFlags :: {RestartStrategy :: supervisor:strategy(), + MaxR :: non_neg_integer(), MaxT :: non_neg_integer()}, + [ChildSpec :: supervisor:child_spec()] + }} | + ignore | + {error, Reason :: term()}). +init([]) -> + RestartStrategy = one_for_one, + MaxRestarts = 1000, + MaxSecondsBetweenRestarts = 3600, + + SupFlags = {RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts}, + + {ok, {SupFlags, []}}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== diff --git a/apps/ejabberd/src/mod_offline.erl b/apps/ejabberd/src/mod_offline.erl index 760c2a7c46f..64dd52977b4 100644 --- a/apps/ejabberd/src/mod_offline.erl +++ b/apps/ejabberd/src/mod_offline.erl @@ -57,7 +57,7 @@ %% default value for the maximum number of user messages -define(MAX_USER_MESSAGES, infinity). --define(BACKEND, (mod_offline_backend:backend())). +-define(BACKEND, mod_offline_backend). -record(state, {host, access_max_user_messages}). @@ -67,7 +67,23 @@ -callback init(Host, Opts) -> ok when Host :: binary(), Opts :: list(). - +-callback pop_messages(LUser, LServer) -> Result when + LUser :: ejabberd:luser(), + LServer :: ejabberd:lserver(), + Result :: term(). +-callback write_messages(LUser, LServer, Msgs, MaxOfflineMsgs) -> Result when + LUser :: ejabberd:luser(), + LServer :: ejabberd:lserver(), + Msgs :: list(), + MaxOfflineMsgs :: integer(), + Result :: term(). +-callback remove_expired_messages(Host) -> Result when + Host :: ejabberd:lserver(), + Result :: term(). +-callback remove_old_messages(Host, Days) -> Result when + Host :: ejabberd:lserver(), + Days :: integer(), + Result :: term(). -callback remove_user(LUser, LServer) -> ok when LUser :: binary(), LServer :: binary(). @@ -78,7 +94,7 @@ start(Host, Opts) -> AccessMaxOfflineMsgs = gen_mod:get_opt(access_max_user_messages, Opts, max_user_offline_messages), - gen_mod:start_backend_module(?MODULE, Opts), + gen_mod:start_backend_module(?MODULE, Opts, [pop_messages, write_messages]), ?BACKEND:init(Host, Opts), start_worker(Host, AccessMaxOfflineMsgs), ejabberd_hooks:add(offline_message_hook, Host, diff --git a/apps/ejabberd/src/mod_privacy.erl b/apps/ejabberd/src/mod_privacy.erl index 79ec4b7842a..1be1661741f 100644 --- a/apps/ejabberd/src/mod_privacy.erl +++ b/apps/ejabberd/src/mod_privacy.erl @@ -42,7 +42,7 @@ -include("jlib.hrl"). -include("mod_privacy.hrl"). --define(BACKEND, (mod_privacy_backend:backend())). +-define(BACKEND, mod_privacy_backend). -export_type([userlist/0]). @@ -114,7 +114,10 @@ %% ------------------------------------------------------------------ start(Host, Opts) -> - gen_mod:start_backend_module(?MODULE, Opts), + gen_mod:start_backend_module(?MODULE, Opts, [get_privacy_list,get_list_names, + set_default_list, forget_default_list, + remove_privacy_list, replace_privacy_list, + get_default_list]), ?BACKEND:init(Host, Opts), IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue), ejabberd_hooks:add(privacy_iq_get, Host, diff --git a/apps/ejabberd/src/mod_privacy_odbc.erl b/apps/ejabberd/src/mod_privacy_odbc.erl index cd7754dddf4..05e77990d90 100644 --- a/apps/ejabberd/src/mod_privacy_odbc.erl +++ b/apps/ejabberd/src/mod_privacy_odbc.erl @@ -351,4 +351,3 @@ sql_del_privacy_lists(LUser, LServer) -> Username = ejabberd_odbc:escape(LUser), Server = ejabberd_odbc:escape(LServer), odbc_queries:del_privacy_lists(LServer, Server, Username). - diff --git a/apps/ejabberd/src/mod_private.erl b/apps/ejabberd/src/mod_private.erl index 7f0c8f74855..f09f1cacb79 100644 --- a/apps/ejabberd/src/mod_private.erl +++ b/apps/ejabberd/src/mod_private.erl @@ -37,7 +37,7 @@ -include("ejabberd.hrl"). -include("jlib.hrl"). --define(BACKEND, (mod_private_backend:backend())). +-define(BACKEND, mod_private_backend). %% ------------------------------------------------------------------ %% Backend callbacks @@ -71,7 +71,7 @@ %% gen_mod callbacks start(Host, Opts) -> - gen_mod:start_backend_module(?MODULE, Opts), + gen_mod:start_backend_module(?MODULE, Opts, [multi_get_data, multi_set_data]), ?BACKEND:init(Host, Opts), IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue), ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 50), diff --git a/apps/ejabberd/src/mod_roster.erl b/apps/ejabberd/src/mod_roster.erl index bb27f431056..6289e2a8821 100644 --- a/apps/ejabberd/src/mod_roster.erl +++ b/apps/ejabberd/src/mod_roster.erl @@ -64,12 +64,87 @@ -type roster() :: #roster{}. --define(BACKEND, (mod_roster_backend:backend())). +-callback init(Host, Opts) -> ok when + Host :: ejabberd:server(), + Opts :: list(). +-callback read_roster_version(LUser, LServer) -> Result when + LUser :: ejabberd:luser(), + LServer :: ejabberd:lserver(), + Result :: term(). +-callback write_roster_version(LUser, LServer, InTransaction, Ver) -> Result when + LUser :: ejabberd:luser(), + LServer :: ejabberd:lserver(), + InTransaction :: boolean(), + Ver :: binary(), + Result :: term(). +-callback get_roster(LUser, LServer) -> Result when + LUser :: ejabberd:luser(), + LServer :: ejabberd:lserver(), + Result :: term(). +-callback get_roster_by_jid_t(LUser, LServer, LJid) -> Result when + LUser :: ejabberd:luser(), + LServer :: ejabberd:lserver(), + LJid :: ejabberd:simple_bare_jid(), + Result :: term(). +-callback get_subscription_lists(Acc, LUser, LServer) -> Result when + Acc :: term(), + LUser :: ejabberd:luser(), + LServer :: ejabberd:lserver(), + Result :: term(). +-callback roster_subscribe_t(LUser, LServer, LJid, SJid) -> Result when + LUser :: ejabberd:luser(), + LServer :: ejabberd:lserver(), + LJid :: ejabberd:simple_bare_jid(), + SJid :: roster(), + Result :: term(). +-callback get_roster_by_jid_with_groups_t(LUser, LServer, LJid) -> Result when + LUser :: ejabberd:luser(), + LServer :: ejabberd:lserver(), + LJid :: ejabberd:simple_bare_jid(), + Result :: term(). +-callback remove_user(LUser, LServer) -> Result when + LUser :: ejabberd:luser(), + LServer :: ejabberd:lserver(), + Result :: term(). +-callback update_roster_t(LUser, LServer, LJid, Item) -> Result when + LUser :: ejabberd:luser(), + LServer :: ejabberd:lserver(), + LJid :: ejabberd:simple_bare_jid(), + Item :: roster(), + Result :: term(). +-callback del_roster_t(LUser, LServer, LJid) -> Result when + LUser :: ejabberd:luser(), + LServer :: ejabberd:lserver(), + LJid :: ejabberd:simple_bare_jid(), + Result :: term(). +-callback read_subscription_and_groups(LUser, LServer, LJid) -> Result when + LUser :: ejabberd:luser(), + LServer :: ejabberd:lserver(), + LJid :: ejabberd:simple_bare_jid(), + Result :: term(). + +-callback raw_to_record(LServer, Item) -> Result when + LServer :: ejabberd:lserver(), + Item :: term(), + Result :: error | roster(). + + +-define(BACKEND, mod_roster_backend). start(Host, Opts) -> IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue), - - gen_mod:start_backend_module(?MODULE, Opts), + TrackedFuns = [read_roster_version, + write_roster_version, + get_roster, + get_roster_by_jid_t, + get_subscription_lists, + roster_subscribe_t, + get_roster_by_jid_with_groups_t, + update_roster_t, + del_roster_t, + read_subscription_and_groups + ], + gen_mod:start_backend_module(?MODULE, Opts, TrackedFuns), ?BACKEND:init(Host, Opts), ejabberd_hooks:add(roster_get, Host, diff --git a/apps/ejabberd/src/mod_roster_mnesia.erl b/apps/ejabberd/src/mod_roster_mnesia.erl index 207f796d0da..1523c58bfe6 100644 --- a/apps/ejabberd/src/mod_roster_mnesia.erl +++ b/apps/ejabberd/src/mod_roster_mnesia.erl @@ -13,6 +13,8 @@ -include("mod_roster.hrl"). -include("jlib.hrl"). +-behaviour(mod_roster). + %% API -export([init/2, read_roster_version/2, diff --git a/apps/ejabberd/src/mod_roster_odbc.erl b/apps/ejabberd/src/mod_roster_odbc.erl index cef4ed74e21..a1c096c882b 100644 --- a/apps/ejabberd/src/mod_roster_odbc.erl +++ b/apps/ejabberd/src/mod_roster_odbc.erl @@ -14,6 +14,8 @@ -include("mod_roster.hrl"). -include("jlib.hrl"). +-behaviour(mod_roster). + %% API -export([init/2, read_roster_version/2, diff --git a/apps/ejabberd/src/mod_vcard.erl b/apps/ejabberd/src/mod_vcard.erl index 6b5b3199527..3b118ab014b 100644 --- a/apps/ejabberd/src/mod_vcard.erl +++ b/apps/ejabberd/src/mod_vcard.erl @@ -55,7 +55,7 @@ -export([config_change/4]). -define(PROCNAME, ejabberd_mod_vcard). --define(BACKEND, (mod_vcard_backend:backend())). +-define(BACKEND, mod_vcard_backend). -record(state,{search :: boolean(), host :: binary(), @@ -100,7 +100,7 @@ %% gen_mod callbacks %%-------------------------------------------------------------------- start(VHost, Opts) -> - gen_mod:start_backend_module(?MODULE, Opts), + gen_mod:start_backend_module(?MODULE, Opts, [set_vcard, get_vcard, search]), Proc = gen_mod:get_module_proc(VHost,?PROCNAME), ChildSpec = {Proc, {?MODULE, start_link, [VHost,Opts]}, transient, 1000, worker, [?MODULE]}, diff --git a/apps/ejabberd/src/mongoose_metrics.erl b/apps/ejabberd/src/mongoose_metrics.erl index fc7ada92a1e..5a19541233e 100644 --- a/apps/ejabberd/src/mongoose_metrics.erl +++ b/apps/ejabberd/src/mongoose_metrics.erl @@ -19,6 +19,13 @@ %% API -export([update/2, + start_graphite_reporter/1, + start_graphite_reporter/2, + start_host_metrics_subscriptions/3, + start_vm_metrics_subscriptions/2, + start_global_metrics_subscriptions/2, + start_data_metrics_subscriptions/2, + start_backend_metrics_subscriptions/2, get_metric_value/1, get_metric_values/1, get_host_metric_names/1, @@ -28,12 +35,45 @@ create_global_metrics/0, create_generic_hook_metric/2, increment_generic_hook_metric/2, + get_odbc_data_stats/0, + get_odbc_mam_async_stats/0, + get_dist_data_stats/0, remove_host_metrics/1, remove_all_metrics/0]). -spec update({term(), term()}, term()) -> no_return(). +update(Name, Change) when is_tuple(Name)-> + update(tuple_to_list(Name), Change); update(Name, Change) -> - exometer:update(tuple_to_list(Name), Change). + exometer:update(Name, Change). + +start_graphite_reporter(GraphiteHost) -> + start_graphite_reporter(GraphiteHost, []). +start_graphite_reporter(GraphiteHost, Opts) -> + GraphiteOpts = [{prefix, "exometer." ++ atom_to_list(node())}, + {host, GraphiteHost}] + ++ merge_opts(Opts), + case exometer_report:add_reporter(exometer_report_graphite, GraphiteOpts) of + ok -> + {ok, exometer_report_graphite}; + Error -> + Error + end. + +start_host_metrics_subscriptions(Reporter, Host, Interval) -> + do_start_metrics_subscriptions(check_reporter(Reporter), Interval, [Host]). + +start_vm_metrics_subscriptions(Reporter, Interval) -> + do_start_vm_metrics_subscriptions(check_reporter(Reporter), Interval). + +start_global_metrics_subscriptions(Reporter, Interval) -> + do_start_global_metrics_subscriptions(check_reporter(Reporter), Interval). + +start_data_metrics_subscriptions(Reporter, Interval) -> + do_start_metrics_subscriptions(check_reporter(Reporter), Interval, [data]). + +start_backend_metrics_subscriptions(Reporter, Interval) -> + do_start_metrics_subscriptions(check_reporter(Reporter), Interval, [backends]). get_host_metric_names(Host) -> [MetricName || {[_Host, MetricName | _], _, _} <- exometer:find_entries([Host])]. @@ -42,11 +82,16 @@ get_global_metric_names() -> get_host_metric_names(global). get_metric_value({Host, Name}) -> - exometer:get_value([Host, Name]). + get_metric_value([Host, Name]); +get_metric_value(Metric) -> + exometer:get_value(Metric). +get_metric_values(Metric) when is_list(Metric) -> + exometer:get_values(Metric); get_metric_values(Host) -> exometer:get_values([Host]). + get_aggregated_values(Metric) -> exometer:aggregate([{{['_',Metric],'_','_'},[],[true]}], [one, count, value]). @@ -74,6 +119,77 @@ do_increment_generic_hook_metric({_, skip}) -> do_increment_generic_hook_metric(MetricName) -> update(MetricName, 1). +get_dist_data_stats() -> + DistStats = [inet_stats(Port) || {_, Port} <- erlang:system_info(dist_ctrl)], + [{connections, length(DistStats)} | merge_stats(DistStats)]. + +get_odbc_data_stats() -> + RegularODBCWorkers = [ejabberd_odbc_sup:get_pids(Host) || Host <- ?MYHOSTS], + get_odbc_stats(lists:flatten(RegularODBCWorkers)). + +get_odbc_mam_async_stats() -> + %% MAM async ODBC workers are organized differently... + MamAsynODBCWorkers = [catch element(2, gen_server:call(Pid, get_connection, 1000)) || {_, Pid, worker, _} <- supervisor:which_children(mod_mam_sup)], + get_odbc_stats(MamAsynODBCWorkers). + +get_odbc_stats(ODBCWorkers) -> + ODBCConnections = [catch ejabberd_odbc:get_db_info(Pid) || Pid <- ODBCWorkers], + Ports = [get_port_from_odbc_connection(Conn) || Conn <- ODBCConnections], + PortStats = [inet_stats(Port) || Port <- lists:flatten(Ports)], + [{workers, length(ODBCConnections)} | merge_stats(PortStats)]. +%% + +get_port_from_odbc_connection({ok, DbType, Pid}) when DbType =:= mysql; DbType =:= pgsql -> + %% Pid of mysql_conn process + {links, [MySQLRecv]} = erlang:process_info(Pid, links), + %% Port is hold by mysql_recv process which is linked to the mysql_conn + {links, Links} = erlang:process_info(MySQLRecv, links), + [Port || Port <- Links, is_port(Port)]; +get_port_from_odbc_connection({ok, odbc, Pid}) -> + {links, Links} = erlang:process_info(Pid, links), + [Port || Port <- Links, is_port(Port), {name, "tcp_inet"} == erlang:port_info(Port, name)]; +get_port_from_odbc_connection(_) -> + undefined. + +-define (INET_STATS, [recv_oct, + recv_cnt, + recv_max, + send_oct, + send_max, + send_cnt, + send_pend + ]). +-define(EMPTY_INET_STATS, [{recv_oct,0}, + {recv_cnt,0}, + {recv_max,0}, + {send_oct,0}, + {send_max,0}, + {send_cnt,0}, + {send_pend,0} + ]). + +merge_stats(Stats) -> + OrdDict = lists:foldl(fun(Stat, Acc) -> + StatDict = orddict:from_list(Stat), + orddict:merge(fun merge_stats_fun/3, Acc, StatDict) + end, orddict:from_list(?EMPTY_INET_STATS), Stats), + + orddict:to_list(OrdDict). + +merge_stats_fun(recv_max, V1, V2) -> + erlang:max(V1, V2); +merge_stats_fun(send_max, V1, V2) -> + erlang:max(V1, V2); +merge_stats_fun(_, V1, V2) -> + V1 + V2. + + +inet_stats(Port) when is_port(Port) -> + {ok, Stats} = inet:getstat(Port, ?INET_STATS), + Stats; +inet_stats(_) -> + ?EMPTY_INET_STATS. + remove_host_metrics(Host) -> lists:foreach(fun remove_metric/1, exometer:find_entries([Host])). @@ -134,7 +250,10 @@ create_metrics(Host) -> get_general_counters(Host)), lists:foreach(fun(Name) -> ensure_metric(Name, counter) end, - get_total_counters(Host)). + get_total_counters(Host)), + + lists:foreach(fun(Name) -> ensure_metric(Name, histogram) end, + get_histograms(Host)). ensure_metric({Host, Metric}, Type) -> case exometer:info([Host, Metric], type) of @@ -207,7 +326,7 @@ metrics_hooks(Op, Host) -> -spec get_general_counters(ejabberd:server()) -> [{ejabberd:server(), atom()}]. get_general_counters(Host) -> - [{Host, Counter} || Counter <- ?GENERAL_COUNTERS]. + get_counters(Host, ?GENERAL_COUNTERS). -define (TOTAL_COUNTERS, [ sessionCount @@ -217,18 +336,113 @@ get_general_counters(Host) -> -spec get_total_counters(ejabberd:server()) -> [{ejabberd:server(),'sessionCount'}]. get_total_counters(Host) -> - [{Host, Counter} || Counter <- ?TOTAL_COUNTERS]. + get_counters(Host, ?TOTAL_COUNTERS). +-define (HISTOGRAMS, [ + mam_archive_time, + mam_lookup_time + +]). + +get_histograms(Host) -> + get_counters(Host, ?HISTOGRAMS). + +-define(EX_EVAL_SINGLE_VALUE, {[{l, [{t, [value, {v, 'Value'}]}]}],[value]}). -define(GLOBAL_COUNTERS, [{[global, totalSessionCount], - {function, ejabberd_sm, get_total_sessions_number, [], value, []}}, + {function, ejabberd_sm, get_total_sessions_number, [], + eval, ?EX_EVAL_SINGLE_VALUE}}, {[global, uniqueSessionCount], - {function, ejabberd_sm, get_unique_sessions_number, [], value, []}}, + {function, ejabberd_sm, get_unique_sessions_number, [], + eval, ?EX_EVAL_SINGLE_VALUE}}, {[global, nodeSessionCount], - {function, ejabberd_sm, get_node_sessions_number, [], value, []}} + {function, ejabberd_sm, get_node_sessions_number, [], + eval, ?EX_EVAL_SINGLE_VALUE}} ] ). +-define(GLOBAL_HISTOGRAMS, [[data, xmpp, received, encrypted_size], + [data, xmpp, received, compressed_size], + [data, xmpp, received, xml_stanza_size], + [data, xmpp, sent, encrypted_size], + [data, xmpp, sent, compressed_size], + [data, xmpp, sent, xml_stanza_size]]). + +-define(DATA_FUN_METRICS, + [{[data, odbc, regular], + {function, mongoose_metrics, get_odbc_data_stats, [], proplist, [workers | ?INET_STATS]}}, + {[data, odbc, mam_async], + {function, mongoose_metrics, get_odbc_mam_async_stats, [], proplist, [workers | ?INET_STATS]}}, + {[data, dist], + {function, mongoose_metrics, get_dist_data_stats, [], proplist, [connections | ?INET_STATS]}}]). + create_global_metrics() -> + lists:foreach(fun({Metric, FunSpec, DataPoints}) -> + FunSpecTuple = list_to_tuple(FunSpec ++ [DataPoints]), + exometer:new(Metric, FunSpecTuple) + end, get_vm_stats()), + lists:foreach(fun({Metric, Spec}) -> exometer:new(Metric, Spec) end, + ?GLOBAL_COUNTERS), + create_data_metrics(). + +create_data_metrics() -> + lists:foreach(fun(Metric) -> exometer:new(Metric, histogram) end, + ?GLOBAL_HISTOGRAMS), lists:foreach(fun({Metric, Spec}) -> exometer:new(Metric, Spec) end, - ?GLOBAL_COUNTERS). + ?DATA_FUN_METRICS). + + +get_vm_stats() -> + [{[erlang, system_info], [function, erlang, system_info, ['$dp'], value], + [port_count, port_limit, process_count, process_limit, ets_limit]}, + {[erlang, memory], [function, erlang, memory, ['$dp'], value], + [total, processes_used, atom_used, binary, ets, system]}]. + +get_counters(Host, Counters) -> + [{Host, Counter} || Counter <- Counters]. + +check_reporter(Reporter) -> + Reporters = exometer_report:list_reporters(), + case lists:keyfind(Reporter, 1, Reporters) of + {Reporter, _} -> + {ok, Reporter}; + _ -> + {error, {no_such_reporter}} + end. + + +do_start_vm_metrics_subscriptions({ok, Reporter}, Interval) -> + [exometer_report:subscribe(Reporter, Metric, DataPoints, Interval) + || {Metric, _, DataPoints} <- get_vm_stats()]; +do_start_vm_metrics_subscriptions(Error, _) -> + Error. + +do_start_global_metrics_subscriptions({ok, Reporter}, Interval) -> + [exometer_report:subscribe(Reporter, Metric, default, Interval) + || {Metric, _} <- ?GLOBAL_COUNTERS]; +do_start_global_metrics_subscriptions(Error, _) -> + Error. + +do_start_metrics_subscriptions({ok, Reporter}, Interval, MetricPrefix) -> + [subscribe_metric(Reporter, Metric, Interval) + || Metric <- exometer:find_entries(MetricPrefix)]; +do_start_metrics_subscriptions(Error, _, _) -> + Error. + + +subscribe_metric(Reporter, {Name, counter, _}, Interval) -> + exometer_report:subscribe(Reporter, Name, [value], Interval); +subscribe_metric(Reporter, {Name, histogram, _}, Interval) -> + exometer_report:subscribe(Reporter, Name, [min, mean, max, median, 95, 99, 999], Interval); +subscribe_metric(Reporter, {Name, _, _}, Interval) -> + exometer_report:subscribe(Reporter, Name, default, Interval). + +merge_opts(Opts) -> + Defaults = [{connect_timeout, 5000}, + {port, 2003}, + {api_key, ""}], + + MergeFun = fun(_, _, V2) -> V2 end, + orddict:to_list(orddict:merge(MergeFun, + orddict:from_list(Defaults), + orddict:from_list(Opts))). \ No newline at end of file diff --git a/apps/ejabberd/src/xml_stream.erl b/apps/ejabberd/src/xml_stream.erl index 755fa7183ed..ed299660b9a 100644 --- a/apps/ejabberd/src/xml_stream.erl +++ b/apps/ejabberd/src/xml_stream.erl @@ -119,10 +119,7 @@ parse(#xml_stream_state{callback_pid = CallbackPid, stack = Stack, size = Size, maxsize = MaxSize} = State, Str) -> - StrSize = if - is_list(Str) -> length(Str); - is_binary(Str) -> size(Str) - end, + StrSize = size(Str), Res = port_control(Port, ?PARSE_COMMAND, Str), {NewStack, NewSize} = lists:foldl( diff --git a/rel/reltool.config.script b/rel/reltool.config.script index 731d58c7250..4c6fc951b2a 100644 --- a/rel/reltool.config.script +++ b/rel/reltool.config.script @@ -60,7 +60,7 @@ IncludeApps = lists:map(fun(App) -> {app, App, [{incl_cond, include}]} end, Apps [{sys, [ {lib_dirs, ["../apps", "../deps"]}, {incl_cond, exclude}, - {rel, "mongooseim", "", [mongoose | AppsToRun]}, + {rel, "mongooseim", "", [mongooseim | AppsToRun]}, {rel, "start_clean", "", [kernel,stdlib]}, {boot_rel, "mongooseim"}, {profile, embedded}, @@ -68,7 +68,7 @@ IncludeApps = lists:map(fun(App) -> {app, App, [{incl_cond, include}]} end, Apps {excl_sys_filters, ["^bin/.*", "^erts.*/bin/(dialyzer|typer)"]}, - {app, mongoose, [{incl_cond, include}, {lib_dir, ".."}]} + {app, mongooseim, [{incl_cond, include}, {lib_dir, ".."}]} ] ++ IncludeApps}, diff --git a/src/mongooseim.erl b/src/mongooseim.erl new file mode 100644 index 00000000000..b6235fa64d1 --- /dev/null +++ b/src/mongooseim.erl @@ -0,0 +1,24 @@ +%%============================================================================== +%% Copyright 2014 Erlang Solutions Ltd. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%============================================================================== +-module(mongooseim). + + +%% API +-export([start/0]). + +start() -> + application:start(mongooseim), + ejabberd:start(). diff --git a/tools/summarise-ct-results b/tools/summarise-ct-results index 70886b67c32..9f0af9173bd 100755 --- a/tools/summarise-ct-results +++ b/tools/summarise-ct-results @@ -21,5 +21,5 @@ main(Directories) -> true -> erlang:halt(100404); _ -> - erlang:halt(Failed) + erlang:halt(Failed + AutoSkipped) end.