ejabberd-contrib/mod_statsdx/src/mod_statsdx.erl

1720 lines
59 KiB
Erlang

%%%----------------------------------------------------------------------
%%% File : mod_statsdx.erl
%%% Author : Badlop <badlop@ono.com>
%%% Purpose : Calculates and gathers statistics actively
%%% Created :
%%% Id : $Id: mod_statsdx.erl 1118 2011-07-11 17:16:30Z badlop $
%%%----------------------------------------------------------------------
%%%% Definitions
-module(mod_statsdx).
-author('badlop@ono.com').
-behaviour(gen_mod).
-export([start/2, loop/1, stop/1, get_statistic/2,
%% Commands
getstatsdx/1, getstatsdx/2,
get_top_users/2,
%% WebAdmin
web_menu_main/2, web_page_main/2,
web_menu_node/3, web_page_node/5,
web_menu_host/3, web_page_host/3,
%% Hooks
register_user/2, remove_user/2, user_send_packet/3,
user_send_packet_traffic/3, user_receive_packet_traffic/4,
user_login/1, user_logout/4, user_logout_sm/3]).
-include("ejabberd.hrl").
-include("ejabberd_commands.hrl").
-include("jlib.hrl").
-include("logger.hrl").
-include("mod_roster.hrl").
-include("ejabberd_http.hrl").
-include("ejabberd_web_admin.hrl").
-define(XCTB(Name, Text), ?XCT(list_to_binary(Name), list_to_binary(Text))).
-define(PROCNAME, ejabberd_mod_statsdx).
%% Copied from ejabberd_s2s.erl Used in function get_s2sconnections/1
-record(s2s, {fromto, pid, key}).
%%%==================================
%%%% Module control
start(Host, Opts) ->
Hooks = gen_mod:get_opt(hooks, Opts,
fun(O) when is_boolean(O) -> O;
(traffic) -> traffic
end, false),
%% Default value for the counters
CD = case Hooks of
true -> 0;
traffic -> 0;
false -> "disabled"
end,
ejabberd_commands:register_commands(commands()),
%% If the process that handles statistics for the server is not started yet,
%% start it now
case whereis(?PROCNAME) of
undefined ->
application:start(os_mon),
initialize_stats_server();
_ ->
ok
end,
?PROCNAME ! {initialize_stats, Host, Hooks, CD}.
stop(Host) ->
finish_stats(Host),
ejabberd_commands:unregister_commands(commands()),
case whereis(?PROCNAME) of
undefined -> ok;
_ -> ?PROCNAME ! {stop, Host}
end.
%%%==================================
%%%% Stats Server
%%% +++ TODO: why server and "server"
table_name(server) -> gen_mod:get_module_proc(<<"server">>, mod_statsdx);
table_name("server") -> gen_mod:get_module_proc(<<"server">>, mod_statsdx);
table_name(Host) -> gen_mod:get_module_proc(Host, mod_statsdx).
initialize_stats_server() ->
register(?PROCNAME, spawn(?MODULE, loop, [[]])).
loop(Hosts) ->
receive
{initialize_stats, Host, Hooks, CD} ->
case Hosts of
[] -> prepare_stats_server(CD);
_ -> ok
end,
prepare_stats_host(Host, Hooks, CD),
loop([Host | Hosts]);
{stop, Host} ->
case Hosts -- [Host] of
[] ->
finish_stats();
RemainingHosts ->
loop(RemainingHosts)
end
end.
%% Si no existe una tabla de stats del server, crearla.
%% Deberia ser creada por un proceso que solo muera cuando se detenga el ultimo mod_statsdx del servidor
prepare_stats_server(CD) ->
Table = table_name("server"),
ets:new(Table, [named_table, public]),
ets:insert(Table, {{user_login, server}, CD}),
ets:insert(Table, {{user_logout, server}, CD}),
ets:insert(Table, {{register_user, server}, CD}),
ets:insert(Table, {{remove_user, server}, CD}),
lists:foreach(
fun(E) -> ets:insert(Table, {{client, server, E}, CD}) end,
list_elem(clients, id)
),
lists:foreach(
fun(E) -> ets:insert(Table, {{conntype, server, E}, CD}) end,
list_elem(conntypes, id)
),
lists:foreach(
fun(E) -> ets:insert(Table, {{os, server, E}, CD}) end,
list_elem(oss, id)
),
ejabberd_hooks:add(webadmin_menu_main, ?MODULE, web_menu_main, 50),
ejabberd_hooks:add(webadmin_menu_node, ?MODULE, web_menu_node, 50),
ejabberd_hooks:add(webadmin_page_main, ?MODULE, web_page_main, 50),
ejabberd_hooks:add(webadmin_page_node, ?MODULE, web_page_node, 50).
prepare_stats_host(Host, Hooks, CD) ->
Table = table_name(Host),
ets:new(Table, [named_table, public]),
ets:insert(Table, {{user_login, Host}, CD}),
ets:insert(Table, {{user_logout, Host}, CD}),
ets:insert(Table, {{register_user, Host}, CD}),
ets:insert(Table, {{remove_user, Host}, CD}),
ets:insert(Table, {{send, Host, iq, in}, CD}),
ets:insert(Table, {{send, Host, iq, out}, CD}),
ets:insert(Table, {{send, Host, message, in}, CD}),
ets:insert(Table, {{send, Host, message, out}, CD}),
ets:insert(Table, {{send, Host, presence, in}, CD}),
ets:insert(Table, {{send, Host, presence, out}, CD}),
ets:insert(Table, {{recv, Host, iq, in}, CD}),
ets:insert(Table, {{recv, Host, iq, out}, CD}),
ets:insert(Table, {{recv, Host, message, in}, CD}),
ets:insert(Table, {{recv, Host, message, out}, CD}),
ets:insert(Table, {{recv, Host, presence, in}, CD}),
ets:insert(Table, {{recv, Host, presence, out}, CD}),
lists:foreach(
fun(E) -> ets:insert(Table, {{client, Host, E}, CD}) end,
list_elem(clients, id)
),
lists:foreach(
fun(E) -> ets:insert(Table, {{conntype, Host, E}, CD}) end,
list_elem(conntypes, id)
),
lists:foreach(
fun(E) -> ets:insert(Table, {{os, Host, E}, CD}) end,
list_elem(oss, id)
),
case Hooks of
true ->
ejabberd_hooks:add(register_user, Host, ?MODULE, register_user, 90),
ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 90),
ejabberd_hooks:add(user_available_hook, Host, ?MODULE, user_login, 90),
%%ejabberd_hooks:add(unset_presence_hook, Host, ?MODULE, user_logout, 90),
ejabberd_hooks:add(sm_remove_connection_hook, Host, ?MODULE, user_logout_sm, 90),
ejabberd_hooks:add(user_send_packet, Host, ?MODULE, user_send_packet, 90);
traffic ->
ejabberd_hooks:add(user_receive_packet, Host, ?MODULE, user_receive_packet_traffic, 92),
ejabberd_hooks:add(user_send_packet, Host, ?MODULE, user_send_packet_traffic, 92),
ejabberd_hooks:add(register_user, Host, ?MODULE, register_user, 90),
ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 90),
ejabberd_hooks:add(user_available_hook, Host, ?MODULE, user_login, 90),
ejabberd_hooks:add(sm_remove_connection_hook, Host, ?MODULE, user_logout_sm, 90),
ejabberd_hooks:add(user_send_packet, Host, ?MODULE, user_send_packet, 90);
false ->
ok
end,
ejabberd_hooks:add(webadmin_menu_host, Host, ?MODULE, web_menu_host, 50),
ejabberd_hooks:add(webadmin_page_host, Host, ?MODULE, web_page_host, 50).
finish_stats() ->
ejabberd_hooks:delete(webadmin_menu_main, ?MODULE, web_menu_main, 50),
ejabberd_hooks:delete(webadmin_menu_node, ?MODULE, web_menu_node, 50),
ejabberd_hooks:delete(webadmin_page_main, ?MODULE, web_page_main, 50),
ejabberd_hooks:delete(webadmin_page_node, ?MODULE, web_page_node, 50),
Table = table_name("server"),
catch ets:delete(Table).
finish_stats(Host) ->
ejabberd_hooks:delete(user_available_hook, Host, ?MODULE, user_login, 90),
%%ejabberd_hooks:delete(unset_presence_hook, Host, ?MODULE, user_logout, 90),
ejabberd_hooks:delete(sm_remove_connection_hook, Host, ?MODULE, user_logout_sm, 90),
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE, user_send_packet, 90),
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE, user_send_packet_traffic, 92),
ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE, user_receive_packet_traffic, 92),
ejabberd_hooks:delete(register_user, Host, ?MODULE, register_user, 90),
ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 90),
ejabberd_hooks:delete(webadmin_menu_host, Host, ?MODULE, web_menu_host, 50),
ejabberd_hooks:delete(webadmin_page_host, Host, ?MODULE, web_page_host, 50),
Table = table_name(Host),
catch ets:delete(Table).
%%%==================================
%%%% Hooks Handlers
register_user(_User, Server) ->
Table = table_name(Server),
ets:update_counter(Table, {register_user, Server}, 1),
ets:update_counter(Table, {register_user, server}, 1).
remove_user(_User, Server) ->
Table = table_name(Server),
ets:update_counter(Table, {remove_user, Server}, 1),
ets:update_counter(Table, {remove_user, server}, 1).
user_send_packet(FromJID, ToJID, NewEl) ->
%% Registrarse para tramitar Host/mod_stats2file
case catch binary_to_existing_atom(ToJID#jid.lresource, utf8) of
?MODULE -> received_response(FromJID, ToJID, NewEl);
_ -> ok
end.
user_send_packet_traffic(FromJID, ToJID, NewEl) ->
%% Only required for traffic stats
Host = FromJID#jid.lserver,
HostTo = ToJID#jid.lserver,
{xmlel, Type, _, _} = NewEl,
Type2 = case Type of
<<"iq">> -> iq;
<<"message">> -> message;
<<"presence">> -> presence
end,
Dest = case is_host(HostTo, Host) of
true -> in;
false -> out
end,
Table = table_name(Host),
ets:update_counter(Table, {send, Host, Type2, Dest}, 1).
%% Only required for traffic stats
user_receive_packet_traffic(_JID, From, To, FixedPacket) ->
HostFrom = From#jid.lserver,
Host = To#jid.lserver,
{xmlel, Type, _, _} = FixedPacket,
Type2 = case Type of
<<"iq">> -> iq;
<<"message">> -> message;
<<"presence">> -> presence
end,
Dest = case is_host(HostFrom, Host) of
true -> in;
false -> out
end,
Table = table_name(Host),
ets:update_counter(Table, {recv, Host, Type2, Dest}, 1).
%%%==================================
%%%% get(*
%%gett(Arg) -> get(node(), [Arg, title]).
getl(Args) -> get(node(), [Args]).
getl(Args, Host) -> get(node(), [Args, Host]).
%%get(_Node, ["", title]) -> "";
get_statistic(N, A) ->
case catch get(N, A) of
{'EXIT', R} ->
?ERROR_MSG("get_statistic error for N: ~p, A: ~p~n~p", [N, A, R]),
unknown;
Res -> Res
end.
get(global, A) -> get(node(), A);
get(_, [{"reductions", _}, title]) -> "Reductions (per minute)";
get(_, [{"reductions", I}]) -> calc_avg(element(2, statistics(reductions)), I); %+++
get(_, ["cpu_avg1", title]) -> "Average system load (1 min)";
get(N, ["cpu_avg1"]) -> rpc:call(N, cpu_sup, avg1, [])/256;
get(_, ["cpu_avg5", title]) -> "Average system load (5 min)";
get(N, ["cpu_avg5"]) -> rpc:call(N, cpu_sup, avg1, [])/256;
get(_, ["cpu_avg15", title]) -> "Average system load (15 min)";
get(N, ["cpu_avg15"]) -> rpc:call(N, cpu_sup, avg15, [])/256;
get(_, ["cpu_nprocs", title]) -> "Number of UNIX processes running on this machine";
get(N, ["cpu_nprocs"]) -> rpc:call(N, cpu_sup, nprocs, []);
get(_, ["cpu_util", title]) -> "CPU utilization";
get(N, ["cpu_util"]) -> rpc:call(N, cpu_sup, util, []);
get(_, [{"cpu_util_user", _}, title]) -> "CPU utilization - user";
get(_, [{"cpu_util_nice_user", _}, title]) -> "CPU utilization - nice_user";
get(_, [{"cpu_util_kernel", _}, title]) -> "CPU utilization - kernel";
get(_, [{"cpu_util_wait", _}, title]) -> "CPU utilization - wait";
get(_, [{"cpu_util_idle", _}, title]) -> "CPU utilization - idle";
get(_, [{"cpu_util_user", U}]) -> proplists:get_value(user, element(2, U), -1);
get(_, [{"cpu_util_nice_user", U}]) -> proplists:get_value(nice_user, element(2, U), -1);
get(_, [{"cpu_util_kernel", U}]) -> proplists:get_value(kernel, element(2, U), -1);
get(_, [{"cpu_util_wait", U}]) -> proplists:get_value(wait, element(3, U), -1);
get(_, [{"cpu_util_idle", U}]) -> proplists:get_value(idle, element(3, U), -1);
get(_, [{"client", Id}, title]) -> atom_to_list(Id);
get(_, [{"client", Id}, Host]) ->
Table = table_name(Host),
case ets:lookup(Table, {client, Host, Id}) of
[{_, C}] -> C;
[] -> 0
end;
get(_, ["client", title]) -> "Client";
get(N, ["client", Host]) ->
lists:map(
fun(Id) ->
[Id_string] = io_lib:format("~p", [Id]),
{Id_string, get(N, [{"client", Id}, Host])}
end,
lists:usort(list_elem(clients, id))
);
get(_, [{"os", Id}, title]) -> atom_to_list(Id);
get(_, [{"os", _Id}, list]) -> lists:usort(list_elem(oss, id));
get(_, [{"os", Id}, Host]) -> [{_, C}] = ets:lookup(table_name(Host), {os, Host, Id}), C;
get(_, ["os", title]) -> "Operating System";
get(N, ["os", Host]) ->
lists:map(
fun(Id) ->
[Id_string] = io_lib:format("~p", [Id]),
{Id_string, get(N, [{"os", Id}, Host])}
end,
lists:usort(list_elem(oss, id))
);
get(_, [{"conntype", Id}, title]) -> atom_to_list(Id);
get(_, [{"conntype", _Id}, list]) -> lists:usort(list_elem(conntypes, id));
get(_, [{"conntype", Id}, Host]) -> [{_, C}] = ets:lookup(table_name(Host), {conntype, Host, Id}), C;
get(_, ["conntype", title]) -> "Connection Type";
get(N, ["conntype", Host]) ->
lists:map(
fun(Id) ->
[Id_string] = io_lib:format("~p", [Id]),
{Id_string, get(N, [{"conntype", Id}, Host])}
end,
lists:usort(list_elem(conntypes, id))
);
get(_, [{"memsup_system", _}, title]) -> "Memory physical (bytes)";
get(_, [{"memsup_system", M}]) -> proplists:get_value(system_total_memory, M, -1);
get(_, [{"memsup_free", _}, title]) -> "Memory free (bytes)";
get(_, [{"memsup_free", M}]) -> proplists:get_value(free_memory, M, -1);
get(_, [{"user_login", _}, title]) -> "Logins (per minute)";
get(_, [{"user_login", I}, Host]) -> get_stat({user_login, Host}, I);
get(_, [{"user_logout", _}, title]) -> "Logouts (per minute)";
get(_, [{"user_logout", I}, Host]) -> get_stat({user_logout, Host}, I);
get(_, [{"register_user", _}, title]) -> "Accounts registered (per minute)";
get(_, [{"register_user", I}, Host]) -> get_stat({register_user, Host}, I);
get(_, [{"remove_user", _}, title]) -> "Accounts deleted (per minute)";
get(_, [{"remove_user", I}, Host]) -> get_stat({remove_user, Host}, I);
get(_, [{Table, Type, Dest, _}, title]) -> filename:flatten([Table, Type, Dest]);
get(_, [{Table, Type, Dest, I}, Host]) -> get_stat({Table, Host, Type, Dest}, I);
get(_, ["user_login", title]) -> "Logins";
get(_, ["user_login", Host]) -> get_stat({user_login, Host});
get(_, ["user_logout", title]) -> "Logouts";
get(_, ["user_logout", Host]) -> get_stat({user_logout, Host});
get(_, ["register_user", title]) -> "Accounts registered";
get(_, ["register_user", Host]) -> get_stat({register_user, Host});
get(_, ["remove_user", title]) -> "Accounts deleted";
get(_, ["remove_user", Host]) -> get_stat({remove_user, Host});
get(_, [{Table, Type, Dest}, title]) -> filename:flatten([Table, Type, Dest]);
get(_, [{Table, Type, Dest}, Host]) -> get_stat({Table, Host, Type, Dest});
get(_, ["localtime", title]) -> "Local time";
get(N, ["localtime"]) ->
localtime_to_string(rpc:call(N, erlang, localtime, []));
get(_, ["memory_total", title]) -> "Memory total allocated: processes and system";
get(N, ["memory_total"]) -> rpc:call(N, erlang, memory, [total]);
get(_, ["memory_processes", title]) -> "Memory allocated by Erlang processes";
get(N, ["memory_processes"]) -> rpc:call(N, erlang, memory, [processes]);
get(_, ["memory_processes_used", title]) -> "Memory used by Erlang processes";
get(N, ["memory_processes_used"]) -> rpc:call(N, erlang, memory, [processes_used]);
get(_, ["memory_system", title]) -> "Memory allocated by Erlang emulator but not associated to processes";
get(N, ["memory_system"]) -> rpc:call(N, erlang, memory, [system]);
get(_, ["memory_atom", title]) -> "Memory allocated for atoms";
get(N, ["memory_atom"]) -> rpc:call(N, erlang, memory, [atom]);
get(_, ["memory_atom_used", title]) -> "Memory used for atoms";
get(N, ["memory_atom_used"]) -> rpc:call(N, erlang, memory, [atom_used]);
get(_, ["memory_binary", title]) -> "Memory allocated for binaries";
get(N, ["memory_binary"]) -> rpc:call(N, erlang, memory, [binary]);
get(_, ["memory_code", title]) -> "Memory allocated for Erlang code";
get(N, ["memory_code"]) -> rpc:call(N, erlang, memory, [code]);
get(_, ["memory_ets", title]) -> "Memory allocated for ETS tables";
get(N, ["memory_ets"]) -> rpc:call(N, erlang, memory, [ets]);
get(_, ["vhost", title]) -> "Virtual host";
get(_, ["vhost", Host]) -> Host;
get(_, ["ejabberdversion", title]) -> "ejabberd version";
get(N, ["ejabberdversion"]) -> element(2, rpc:call(N, application, get_key, [ejabberd, vsn]));
get(_, ["totalerlproc", title]) -> "Total Erlang processes running";
get(N, ["totalerlproc"]) -> rpc:call(N, erlang, system_info, [process_count]);
get(_, ["operatingsystem", title]) -> "Operating System";
get(N, ["operatingsystem"]) -> {rpc:call(N, os, type, []), rpc:call(N, os, version, [])};
get(_, ["erlangmachine", title]) -> "Erlang machine";
get(N, ["erlangmachine"]) -> rpc:call(N, erlang, system_info, [system_version]);
get(_, ["erlangmachinetarget", title]) -> "Erlang machine target";
get(N, ["erlangmachinetarget"]) -> rpc:call(N, erlang, system_info, [system_architecture]);
get(_, ["maxprocallowed", title]) -> "Maximum processes allowed";
get(N, ["maxprocallowed"]) -> rpc:call(N, erlang, system_info, [process_limit]);
get(_, ["procqueue", title]) -> "Number of processes on the running queue";
get(N, ["procqueue"]) -> rpc:call(N, erlang, statistics, [run_queue]);
get(_, ["uptimehuman", title]) -> "Uptime";
get(N, ["uptimehuman"]) ->
io_lib:format("~w days ~w hours ~w minutes ~p seconds", ms_to_time(get(N, ["uptime"])));
get(_, ["lastrestart", title]) -> "Last restart";
get(N, ["lastrestart"]) ->
Now = calendar:datetime_to_gregorian_seconds(rpc:call(N, erlang, localtime, [])),
UptimeMS = get(N, ["uptime"]),
Last_restartS = trunc(Now - (UptimeMS/1000)),
Last_restart = calendar:gregorian_seconds_to_datetime(Last_restartS),
localtime_to_string(Last_restart);
get(_, ["plainusers", title]) -> "Plain users";
get(_, ["plainusers"]) -> {R, _, _} = get_connectiontype(), R;
get(_, ["tlsusers", title]) -> "TLS users";
get(_, ["tlsusers"]) -> {_, R, _} = get_connectiontype(), R;
get(_, ["sslusers", title]) -> "SSL users";
get(_, ["sslusers"]) -> {_, _, R} = get_connectiontype(), R;
get(_, ["registeredusers", title]) -> "Registered users";
get(N, ["registeredusers"]) -> rpc:call(N, mnesia, table_info, [passwd, size]);
get(_, ["registeredusers", Host]) -> length(ejabberd_auth:get_vh_registered_users(Host));
get(_, ["onlineusers", title]) -> "Online users";
get(N, ["onlineusers"]) -> rpc:call(N, mnesia, table_info, [session, size]);
get(_, ["onlineusers", Host]) -> length(ejabberd_sm:get_vh_session_list(Host));
get(_, ["httppollusers", title]) -> "HTTP-Poll users (aprox)";
get(N, ["httppollusers"]) -> rpc:call(N, mnesia, table_info, [http_poll, size]);
get(_, ["httpbindusers", title]) -> "HTTP-Bind users (aprox)";
get(N, ["httpbindusers"]) -> rpc:call(N, mnesia, table_info, [http_bind, size]);
get(_, ["s2sconnections", title]) -> "Outgoing S2S connections";
get(_, ["s2sconnections"]) -> length(get_S2SConns());
get(_, ["s2sconnections", Host]) -> get_s2sconnections(Host);
get(_, ["s2sservers", title]) -> "Outgoing S2S servers";
get(_, ["s2sservers"]) -> length(lists:usort([element(2, C) || C <- get_S2SConns()]));
get(_, ["offlinemsg", title]) -> "Offline messages";
get(N, ["offlinemsg"]) -> rpc:call(N, mnesia, table_info, [offline_msg, size]);
get(_, ["offlinemsg", Host]) -> get_offlinemsg(Host);
get(_, ["totalrosteritems", title]) -> "Total roster items";
get(N, ["totalrosteritems"]) -> rpc:call(N, mnesia, table_info, [roster, size]);
get(_, ["totalrosteritems", Host]) -> get_totalrosteritems(Host);
get(_, ["meanitemsinroster", title]) -> "Mean items in roster";
get(_, ["meanitemsinroster"]) -> get_meanitemsinroster();
get(_, ["meanitemsinroster", Host]) -> get_meanitemsinroster(Host);
get(_, ["totalmucrooms", title]) -> "Total MUC rooms";
get(_, ["totalmucrooms"]) -> ets:info(muc_online_room, size);
get(_, ["totalmucrooms", Host]) -> get_totalmucrooms(Host);
get(_, ["permmucrooms", title]) -> "Permanent MUC rooms";
get(N, ["permmucrooms"]) -> rpc:call(N, mnesia, table_info, [muc_room, size]);
get(_, ["permmucrooms", Host]) -> get_permmucrooms(Host);
get(_, ["regmucrooms", title]) -> "Users registered at MUC service";
get(N, ["regmucrooms"]) -> rpc:call(N, mnesia, table_info, [muc_registered, size]);
get(_, ["regmucrooms", Host]) -> get_regmucrooms(Host);
get(_, ["regpubsubnodes", title]) -> "Registered nodes at Pub/Sub";
get(N, ["regpubsubnodes"]) -> rpc:call(N, mnesia, table_info, [pubsub_node, size]);
get(_, ["vcards", title]) -> "Total vCards published";
get(N, ["vcards"]) -> rpc:call(N, mnesia, table_info, [vcard, size]);
get(_, ["vcards", Host]) -> get_vcards(Host);
%%get(_, ["ircconns", title]) -> "IRC connections";
%%get(_, ["ircconns"]) -> ets:info(irc_connection, size);
%%get(_, ["ircconns", Host]) -> get_irccons(Host); % This seems to crash for some people
get(_, ["uptime", title]) -> "Uptime";
get(N, ["uptime"]) -> element(1, rpc:call(N, erlang, statistics, [wall_clock]));
get(_, ["cputime", title]) -> "CPU Time";
get(N, ["cputime"]) -> element(1, rpc:call(N, erlang, statistics, [runtime]));
get(_, ["languages", title]) -> "Languages";
get(_, ["languages", Server]) -> get_languages(Server);
get(_, ["client_os", title]) -> "Client/OS";
get(_, ["client_os", Server]) -> get_client_os(Server);
get(_, ["client_conntype", title]) -> "Client/Connection Type";
get(_, ["client_conntype", Server]) -> get_client_conntype(Server);
get(N, A) ->
io:format(" ----- node: '~p', A: '~p'~n", [N, A]),
"666".
%%%==================================
%%%% get_*
get_S2SConns() -> ejabberd_s2s:dirty_get_connections().
get_tls_drv() ->
R = lists:filter(
fun(P) ->
case erlang:port_info(P, name) of
{name, "tls_drv"} -> true;
_ -> false
end
end, erlang:ports()),
length(R).
get_connections(Port) ->
R = lists:filter(
fun(P) ->
case inet:port(P) of
{ok, Port} -> true;
_ -> false
end
end, erlang:ports()),
length(R).
get_totalrosteritems(Host) ->
F = fun() ->
F2 = fun(R, {H, A}) ->
{_LUser, LServer, _LJID} = R#roster.usj,
A2 = case LServer of
H -> A+1;
_ -> A
end,
{H, A2}
end,
mnesia:foldl(F2, {Host, 0}, roster)
end,
{atomic, {Host, Res}} = mnesia:transaction(F),
Res.
%% Copied from ejabberd_sm.erl
%%-record(session, {sid, usr, us, priority}).
%%get_authusers(Host) ->
%% F = fun() ->
%% F2 = fun(R, {H, A}) ->
%% {_LUser, LServer, _LResource} = R#session.usr,
%% A2 = case LServer of
%% H -> A+1;
%% _ -> A
%% end,
%% {H, A2}
%% end,
%% mnesia:foldl(F2, {Host, 0}, session)
%% end,
%% {atomic, {Host, Res}} = mnesia:transaction(F),
%% Res.
-record(offline_msg, {us, timestamp, expire, from, to, packet}).
get_offlinemsg(Host) ->
F = fun() ->
F2 = fun(R, {H, A}) ->
{_LUser, LServer} = R#offline_msg.us,
A2 = case LServer of
H -> A+1;
_ -> A
end,
{H, A2}
end,
mnesia:foldl(F2, {Host, 0}, offline_msg)
end,
{atomic, {Host, Res}} = mnesia:transaction(F),
Res.
-record(vcard, {us, vcard}).
get_vcards(Host) ->
F = fun() ->
F2 = fun(R, {H, A}) ->
{_LUser, LServer} = R#vcard.us,
A2 = case LServer of
H -> A+1;
_ -> A
end,
{H, A2}
end,
mnesia:foldl(F2, {Host, 0}, vcard)
end,
{atomic, {Host, Res}} = mnesia:transaction(F),
Res.
get_s2sconnections(Host) ->
F = fun() ->
F2 = fun(R, {H, A}) ->
{MyServer, _Server} = R#s2s.fromto,
A2 = case MyServer of
H -> A+1;
_ -> A
end,
{H, A2}
end,
mnesia:foldl(F2, {Host, 0}, s2s)
end,
{atomic, {Host, Res}} = mnesia:transaction(F),
Res.
%%-record(irc_connection, {jid_server_host, pid}).
%%get_irccons(Host) ->
%% F2 = fun(R, {H, A}) ->
%% {From, _Server, _Host} = R#irc_connection.jid_server_host,
%% A2 = case From#jid.lserver of
%% H -> A+1;
%% _ -> A
%% end,
%% {H, A2}
%% end,
%% {Host, Res} = ets:foldl(F2, {Host, 0}, irc_connection),
%% Res.
is_host(HostBin, SubhostBin) ->
case catch binary:split(HostBin, SubhostBin) of
[_Sub, <<"">>] -> true;
_ -> false
end.
-record(muc_online_room, {name_host, pid}).
get_totalmucrooms(Host) ->
F2 = fun(R, {H, A}) ->
{_Name, MUCHost} = R#muc_online_room.name_host,
A2 = case is_host(MUCHost, H) of
true -> A+1;
false -> A
end,
{H, A2}
end,
{Host, Res} = ets:foldl(F2, {Host, 0}, muc_online_room),
Res.
-record(muc_room, {name_host, opts}).
get_permmucrooms(Host) ->
F = fun() ->
F2 = fun(R, {H, A}) ->
{_Name, MUCHost} = R#muc_room.name_host,
A2 = case is_host(MUCHost, H) of
true -> A+1;
false -> A
end,
{H, A2}
end,
mnesia:foldl(F2, {Host, 0}, muc_room)
end,
{atomic, {Host, Res}} = mnesia:transaction(F),
Res.
-record(muc_registered, {us_host, nick}).
get_regmucrooms(Host) ->
F = fun() ->
F2 = fun(R, {H, A}) ->
{_User, MUCHost} = R#muc_registered.us_host,
A2 = case is_host(MUCHost, H) of
true -> A+1;
false -> A
end,
{H, A2}
end,
mnesia:foldl(F2, {Host, 0}, muc_registered)
end,
{atomic, {Host, Res}} = mnesia:transaction(F),
Res.
get_stat(Stat) ->
Host = case Stat of
{_, H} -> H;
{_, H, _, _} -> H
end,
Table = table_name(Host),
Res = ets:lookup(Table, Stat),
[{_, C}] = Res,
C.
get_stat(Stat, Ims) ->
Host = case Stat of
{_, H} -> H;
{_, H, _, _} -> H
end,
Table = table_name(Host),
Res = ets:lookup(Table, Stat),
ets:update_counter(Table, Stat, {2,1,0,0}),
[{_, C}] = Res,
calc_avg(C, Ims).
%%C.
calc_avg(Count, TimeMS) ->
TimeMIN = TimeMS/(1000*60),
Count/TimeMIN.
%%%==================================
%%%% utilities
get_connectiontype() ->
C2 = get_connections(5222) -1,
C3 = get_connections(5223) -1,
NUplain = C2 + C3 - get_tls_drv(),
NUtls = C2 - NUplain,
NUssl = C3,
{NUplain, NUtls, NUssl}.
ms_to_time(T) ->
DMS = 24*60*60*1000,
HMS = 60*60*1000,
MMS = 60*1000,
SMS = 1000,
D = trunc(T/DMS),
H = trunc((T - (D*DMS)) / HMS),
M = trunc((T - (D*DMS) - (H*HMS)) / MMS),
S = trunc((T - (D*DMS) - (H*HMS) - (M*MMS)) / SMS),
[D, H, M, S].
%% Cuando un usuario conecta, pedirle iq:version a nombre de Host/mod_stats2file
user_login(U) ->
User = U#jid.luser,
Host = U#jid.lserver,
Resource = U#jid.lresource,
ets:update_counter(table_name("server"), {user_login, server}, 1),
ets:update_counter(table_name(Host), {user_login, Host}, 1),
request_iqversion(User, Host, Resource).
user_logout_sm(_, JID, _Data) ->
user_logout(JID#jid.luser, JID#jid.lserver, JID#jid.lresource, no_status).
%% cuando un usuario desconecta, buscar en la tabla su JID/USR y quitarlo
user_logout(User, Host, Resource, _Status) ->
TableHost = table_name(Host),
TableServer = table_name("server"),
ets:update_counter(TableServer, {user_logout, server}, 1),
ets:update_counter(TableHost, {user_logout, Host}, 1),
JID = jlib:make_jid(User, Host, Resource),
case ets:lookup(TableHost, {session, JID}) of
[{_, Client_id, OS_id, Lang, ConnType, _Client, _Version, _OS}] ->
ets:delete(TableHost, {session, JID}),
ets:update_counter(TableHost, {client, Host, Client_id}, -1),
ets:update_counter(TableServer, {client, server, Client_id}, -1),
ets:update_counter(TableHost, {os, Host, OS_id}, -1),
ets:update_counter(TableServer, {os, server, OS_id}, -1),
ets:update_counter(TableHost, {conntype, Host, ConnType}, -1),
ets:update_counter(TableServer, {conntype, server, ConnType}, -1),
update_counter_create(TableHost, {client_os, Host, Client_id, OS_id}, -1),
update_counter_create(TableServer, {client_os, server, Client_id, OS_id}, -1),
update_counter_create(TableHost, {client_conntype, Host, Client_id, ConnType}, -1),
update_counter_create(TableServer, {client_conntype, server, Client_id, ConnType}, -1),
update_counter_create(TableHost, {lang, Host, Lang}, -1),
update_counter_create(TableServer, {lang, server, Lang}, -1);
[] ->
ok
end.
request_iqversion(User, Host, Resource) ->
From = jlib:make_jid(<<"">>, Host, list_to_binary(atom_to_list(?MODULE))),
FromStr = jlib:jid_to_string(From),
To = jlib:make_jid(User, Host, Resource),
ToStr = jlib:jid_to_string(To),
Packet = {xmlel,<<"iq">>,
[{<<"from">>,FromStr}, {<<"to">>,ToStr}, {<<"type">>,<<"get">>},
{<<"id">>, list_to_binary("statsdx"++randoms:get_string())}],
[{xmlel, <<"query">>,
[{<<"xmlns">>,<<"jabber:iq:version">>}], []}]},
ejabberd_local:route(From, To, Packet).
%% cuando el virtualJID recibe una respuesta iqversion,
%% almacenar su JID/USR + client + OS en una tabla
received_response(From, _To, El) ->
try received_response(From, El)
catch
_:_ -> ok
end.
received_response(From, {xmlel, <<"iq">>, Attrs, Elc}) ->
User = From#jid.luser,
Host = From#jid.lserver,
Resource = From#jid.lresource,
<<"result">> = xml:get_attr_s(<<"type">>, Attrs),
Lang = case xml:get_attr_s(<<"xml:lang">>, Attrs) of
[] -> "unknown";
L -> binary_to_list(L)
end,
TableHost = table_name(Host),
TableServer = table_name("server"),
update_counter_create(TableHost, {lang, Host, Lang}, 1),
update_counter_create(TableServer, {lang, server, Lang}, 1),
[El] = xml:remove_cdata(Elc),
{xmlel, _, Attrs2, _Els2} = El,
?NS_VERSION = xml:get_attr_s(<<"xmlns">>, Attrs2),
Client = get_tag_cdata_subtag(El, <<"name">>),
Version = get_tag_cdata_subtag(El, <<"version">>),
OS = get_tag_cdata_subtag(El, <<"os">>),
{Client_id, OS_id} = identify(Client, OS),
ConnType = get_connection_type(User, Host, Resource),
TableHost = table_name(Host),
TableServer = table_name("server"),
ets:update_counter(TableHost, {client, Host, Client_id}, 1),
ets:update_counter(TableServer, {client, server, Client_id}, 1),
ets:update_counter(TableHost, {os, Host, OS_id}, 1),
ets:update_counter(TableServer, {os, server, OS_id}, 1),
ets:update_counter(TableHost, {conntype, Host, ConnType}, 1),
ets:update_counter(TableServer, {conntype, server, ConnType}, 1),
update_counter_create(TableHost, {client_os, Host, Client_id, OS_id}, 1),
update_counter_create(TableServer, {client_os, server, Client_id, OS_id}, 1),
update_counter_create(TableHost, {client_conntype, Host, Client_id, ConnType}, 1),
update_counter_create(TableServer, {client_conntype, server, Client_id, ConnType}, 1),
JID = jlib:make_jid(User, Host, Resource),
ets:insert(TableHost, {{session, JID}, Client_id, OS_id, Lang, ConnType, Client, Version, OS}).
get_connection_type(User, Host, Resource) ->
[_Node, {conn, Conn}, _IP] = ejabberd_sm:get_user_info(User, Host, Resource),
Conn.
update_counter_create(Table, Element, C) ->
case ets:lookup(Table, Element) of
[] -> ets:insert(Table, {Element, 1});
_ -> ets:update_counter(Table, Element, C)
end.
get_tag_cdata_subtag(E, T) ->
E2 = xml:get_subtag(E, T),
case E2 of
false -> "unknown";
_ -> binary_to_list(xml:get_tag_cdata(E2))
end.
list_elem(Type, id) ->
{_, Ids} = lists:unzip(list_elem(Type, full)),
Ids;
list_elem(clients, full) ->
[
{"Pidgin", pidgin},
{"pidgin", pidgin},
{"gaim", gaim},
{"Gajim", gajim},
{"Tkabber", tkabber},
{"Psi", psi},
{"Adium", adium},
{"Pandion", pandion},
{"Instantbird", instantbird},
{"Telepathy Gabble", 'telepathy-gabble'},
{"Swift", swift},
{"Kopete", kopete},
{"Exodus", exodus},
{"libgaim", libgaim},
{"JBother", jbother},
{"iChat", ichat},
{"imagent", messages},
{"Miranda", miranda},
{"Trillian", trillian},
{"QIP Infium", qipinfium},
{"JAJC", jajc},
{"Coccinella", coccinella},
{"Gabber", gabber},
{"BitlBee", bitlbee},
{"jabber.el", jabberel},
{"mcabber", mcabber},
{"poezio", poezio},
{"Profanity", profanity},
{"centerim", centerim},
{"Conversations", conversations},
{"yaxim", yaxim},
{"unknown", unknown}
];
list_elem(conntypes, full) ->
[
{"c2s", c2s},
{"c2s_tls", c2s_tls},
{"c2s_compressed", c2s_compressed},
{"c2s_compressed_tls", c2s_compressed_tls},
{"http_poll", http_poll},
{"http_bind", http_bind},
{"unknown", unknown}
];
list_elem(oss, full) ->
[
{"Linux", linux},
{"Win", windows},
{"Gentoo", linux},
{"Mac", mac},
{"BSD", bsd},
{"SunOS", linux},
{"Debian", linux},
{"Ubuntu", linux},
{"unknown", unknown}
].
identify(Client, OS) ->
Res = {try_match(Client, list_elem(clients, full)), try_match(OS, list_elem(oss, full))},
case Res of
{libgaim, mac} -> {adium, mac};
{adium, unknown} -> {adium, mac};
{qipinfium, unknown} -> {qipinfium, windows};
_ -> Res
end.
try_match(_E, []) -> unknown;
try_match(E, [{Str, Id} | L]) ->
case string:str(E, Str) of
0 -> try_match(E, L);
_ -> Id
end.
get_client_os(Server) ->
CO1 = ets:match(table_name(Server), {{client_os, Server, '$1', '$2'}, '$3'}),
CO2 = lists:map(
fun([Cl, Os, A3]) ->
{lists:flatten([atom_to_list(Cl), "/", atom_to_list(Os)]), A3}
end,
CO1
),
lists:keysort(1, CO2).
get_client_conntype(Server) ->
CO1 = ets:match(table_name(Server), {{client_conntype, Server, '$1', '$2'}, '$3'}),
CO2 = lists:map(
fun([Cl, Os, A3]) ->
{lists:flatten([atom_to_list(Cl), "/", atom_to_list(Os)]), A3}
end,
CO1
),
lists:keysort(1, CO2).
get_languages(Server) ->
L1 = ets:match(table_name(Server), {{lang, Server, '$1'}, '$2'}),
L2 = lists:map(
fun([La, C]) ->
{La, C}
end,
L1
),
lists:keysort(1, L2).
get_meanitemsinroster() ->
get_meanitemsinroster2(getl("totalrosteritems"), getl("registeredusers")).
get_meanitemsinroster(Host) ->
get_meanitemsinroster2(getl("totalrosteritems", Host), getl("registeredusers", Host)).
get_meanitemsinroster2(Items, Users) ->
case Users of
0 -> 0;
_ -> Items/Users
end.
localtime_to_string({{Y, Mo, D},{H, Mi, S}}) ->
lists:concat([H, ":", Mi, ":", S, " ", D, "/", Mo, "/", Y]).
%% cuando toque mostrar estadisticas
%%get_iqversion() ->
%% contar en la tabla cuantos tienen cliente: *psi*
%%buscar en la tabla iqversion
%%ok.
%%%==================================
%%%% Web Admin Menu
web_menu_main(Acc, Lang) ->
Acc ++ [{<<"statsdx">>, ?T(<<"Statistics Dx">>)}].
web_menu_node(Acc, _Node, Lang) ->
Acc ++ [{<<"statsdx">>, ?T(<<"Statistics Dx">>)}].
web_menu_host(Acc, _Host, Lang) ->
Acc ++ [{<<"statsdx">>, ?T(<<"Statistics Dx">>)}].
%%%==================================
%%%% Web Admin Page
web_page_main(_, #request{path=[<<"statsdx">>], lang = Lang} = _Request) ->
Res = [?XC(<<"h1">>, <<(?T(<<"Statistics">>))/binary, " Dx">>),
?XC(<<"h3">>, <<"Accounts">>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>, [
do_stat(global, Lang, "registeredusers")
])
]),
?XC(<<"h3">>, <<"Roster">>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>, [
do_stat(global, Lang, "totalrosteritems"),
do_stat(global, Lang, "meanitemsinroster"),
?XE(<<"tr">>,
[?XE(<<"td">>, [?CT(<<"Top rosters">>)]),
?XE(<<"td">>, [
?ACT(<<"top/roster/30">>, <<"30">>), ?C(<<", ">>),
?ACT(<<"top/roster/100">>, <<"100">>), ?C(<<", ">>),
?ACT(<<"top/roster/500">>, <<"500">>) ])]
)
])
]),
?XC(<<"h3">>, <<"Users">>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>, [
do_stat(global, Lang, "onlineusers"),
do_stat(global, Lang, "offlinemsg"),
?XE(<<"tr">>,
[?XE(<<"td">>, [?CT(<<"Top offline message queues">>) ]),
?XE(<<"td">>, [
?ACT(<<"top/offlinemsg/30">>, <<"30">>), ?C(<<", ">>),
?ACT(<<"top/offlinemsg/100">>, <<"100">>), ?C(<<", ">>),
?ACT(<<"top/offlinemsg/500">>, <<"500">>) ])]
),
do_stat(global, Lang, "vcards"),
?XE(<<"tr">>,
[?XE(<<"td">>, [?CT(<<"Top vCard sizes">>) ]),
?XE(<<"td">>, [
?ACT(<<"top/vcard/5">>, <<"5">>), ?C(<<", ">>),
?ACT(<<"top/vcard/30">>, <<"30">>), ?C(<<", ">>),
?ACT(<<"top/vcard/100">>, <<"100">>), ?C(<<", ">>),
?ACT(<<"top/vcard/500">>, <<"500">>) ])]
)
])
]),
?XC(<<"h3">>, <<"MUC">>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>, [
do_stat(global, Lang, "totalmucrooms"),
do_stat(global, Lang, "permmucrooms"),
do_stat(global, Lang, "regmucrooms")
])
]),
?XC(<<"h3">>, <<"Pub/Sub">>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>, [
do_stat(global, Lang, "regpubsubnodes")
])
]),
%%?XC("h3", "IRC"),
%%?XAE("table", [],
%% [?XE("tbody", [
%% do_stat(global, Lang, "ircconns")
%% ])
%%]),
%%?XC("h3", "Ratios"),
%%?XAE("table", [],
%% [?XE("tbody", [
%% ])
%% ]),
?XC(<<"h3">>, <<"Sessions: ", (get_stat_n("client"))/binary>>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>,
do_stat_table(global, Lang, "client", server)
)
]),
?XC(<<"h3">>, <<"Sessions: ", (get_stat_n("os"))/binary>>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>,
do_stat_table(global, Lang, "os", server)
)
]),
?XC(<<"h3">>, <<"Sessions: ", (get_stat_n("client"))/binary, "/", (get_stat_n("os"))/binary>>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>,
do_stat_table(global, Lang, "client_os", server)
)
]),
?XC(<<"h3">>, <<"Sessions: ", (get_stat_n("conntype"))/binary>>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>,
do_stat_table(global, Lang, "conntype", server)
)
]),
?XC(<<"h3">>, <<"Sessions: ", (get_stat_n("client"))/binary, "/", (get_stat_n("conntype"))/binary>>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>,
do_stat_table(global, Lang, "client_conntype", server)
)
]),
?XC(<<"h3">>, <<"Sessions: ", (get_stat_n("languages"))/binary>>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>,
do_stat_table(global, Lang, "languages", server)
)
])
],
{stop, Res};
web_page_main(_, #request{path=[<<"statsdx">>, <<"top">>, Topic, Topnumber], q = _Q, lang = Lang} = _Request) ->
Res = [?XC(<<"h1">>, <<(?T(<<"Statistics">>))/binary, " Dx">>),
case Topic of
<<"offlinemsg">> -> ?XCT(<<"h2">>, <<"Top offline message queues">>);
<<"vcard">> -> ?XCT(<<"h2">>, <<"Top vCard sizes">>);
<<"roster">> -> ?XCT(<<"h2">>, <<"Top rosters">>)
end,
?XE(<<"table">>,
[?XE(<<"thead">>, [?XE(<<"tr">>,
[?XE(<<"td">>, [?CT(<<"#">>)]),
?XE(<<"td">>, [?CT(<<"Jabber ID">>)]),
?XE(<<"td">>, [?CT(<<"Value">>)])]
)]),
?XE(<<"tbody">>, do_top_table(global, Lang, Topic, Topnumber, server))
])
],
{stop, Res};
web_page_main(_, #request{path=[<<"statsdx">> | FilterURL], q = Q, lang = Lang} = _Request) ->
Filter = parse_url_filter(FilterURL),
Sort_query = get_sort_query(Q),
FilterS = io_lib:format("~p", [Filter]),
Res = [?XC(<<"h1">>, list_to_binary(?T("Statistics") ++ " Dx222")),
?XC(<<"h2">>, list_to_binary("Sessions with: " ++ FilterS)),
?XE(<<"table">>,
[
?XE(<<"thead">>, [?XE(<<"tr">>, make_sessions_table_tr(Lang) )]),
?XE(<<"tbody">>, do_sessions_table(global, Lang, Filter, Sort_query, server))
])
],
{stop, Res};
web_page_main(Acc, _) -> Acc.
do_top_table(_Node, Lang, Topic, TopnumberBin, Host) ->
List = get_top_users(Host, jlib:binary_to_integer(TopnumberBin), Topic),
%% get_top_users(Topnumber, "roster")
{List2, _} = lists:mapfoldl(
fun({Value, UserB, ServerB}, Counter) ->
User = binary_to_list(UserB),
Server = binary_to_list(ServerB),
UserJID = User++"@"++Server,
UserJIDUrl = "/admin/server/" ++ Server ++ "/user/" ++ User ++ "/",
ValueString = integer_to_list(Value),
ValueEl = case Topic of
<<"offlinemsg">> -> {url, UserJIDUrl++"queue/", ValueString};
<<"vcard">> -> {url, UserJIDUrl++"vcard/", ValueString};
<<"roster">> -> {url, UserJIDUrl++"roster/", ValueString};
_ -> ValueString
end,
{do_table_element(Counter, Lang, UserJID, {fixed_url, UserJIDUrl}, ValueEl),
Counter+1}
end,
1,
List
),
List2.
%% Code copied from mod_muc_admin.erl
%% Returns: {normal | reverse, Integer}
get_sort_query(Q) ->
case catch get_sort_query2(Q) of
{ok, Res} -> Res;
_ -> {normal, 1}
end.
get_sort_query2(Q) ->
{value, {_, String}} = lists:keysearch("sort", 1, Q),
Integer = list_to_integer(String),
case Integer >= 0 of
true -> {ok, {normal, Integer}};
false -> {ok, {reverse, abs(Integer)}}
end.
make_sessions_table_tr(Lang) ->
Titles = [<<"Jabber ID">>,
<<"Client ID">>,
<<"OS ID">>,
<<"Lang">>,
<<"Connection">>,
<<"Client">>,
<<"Version">>,
<<"OS">>],
{Titles_TR, _} =
lists:mapfoldl(
fun(Title, Num_column) ->
NCS = list_to_binary(integer_to_list(Num_column)),
TD = ?XE(<<"td">>, [?CT(Title),
?BR,
?ACT(<<"?sort=", NCS/binary>>, <<"<">>),
?C(<<" ">>),
?ACT(<<"?sort=-", NCS/binary>>, <<">">>)]),
{TD, Num_column+1}
end,
1,
Titles),
Titles_TR.
%% @spec (Filter::string()) -> [{Class::string(), Type::string()}]
parse_url_filter(FilterURL) ->
[List] = string:tokens(FilterURL, "/"),
parse_url_filter(List, []).
parse_url_filter([Class, Type | List], Res) ->
parse_url_filter(List, Res ++ [{Class, Type}]);
parse_url_filter(_, Res) ->
Res.
web_page_node(_, Node, [<<"statsdx">>], _Query, Lang) ->
TransactionsCommited =
rpc:call(Node, mnesia, system_info, [transaction_commits]),
TransactionsAborted =
rpc:call(Node, mnesia, system_info, [transaction_failures]),
TransactionsRestarted =
rpc:call(Node, mnesia, system_info, [transaction_restarts]),
TransactionsLogged =
rpc:call(Node, mnesia, system_info, [transaction_log_writes]),
Res =
[?XC(<<"h1">>, list_to_binary(io_lib:format(?T("~p statistics"), [Node]))),
?XC(<<"h3">>, <<"Connections">>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>, [
do_stat(global, Lang, "onlineusers"),
do_stat(Node, Lang, "httppollusers"),
do_stat(Node, Lang, "httpbindusers"),
do_stat(Node, Lang, "s2sconnections"),
do_stat(Node, Lang, "s2sservers")
])
]),
?XC(<<"h3">>, <<"ejabberd">>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>, [
do_stat(Node, Lang, "ejabberdversion")
])
]),
?XC(<<"h3">>, <<"Erlang">>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>, [
do_stat(Node, Lang, "operatingsystem"),
do_stat(Node, Lang, "erlangmachine"),
do_stat(Node, Lang, "erlangmachinetarget"),
do_stat(Node, Lang, "maxprocallowed"),
do_stat(Node, Lang, "procqueue"),
do_stat(Node, Lang, "totalerlproc")
])
]),
?XC(<<"h3">>, <<"Times">>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>, [
do_stat(Node, Lang, "uptime"),
do_stat(Node, Lang, "uptimehuman"),
do_stat(Node, Lang, "lastrestart"),
do_stat(Node, Lang, "cputime")
])
]),
?XC(<<"h3">>, <<"CPU">>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>, [
do_stat(Node, Lang, "cpu_avg1"),
do_stat(Node, Lang, "cpu_avg5"),
do_stat(Node, Lang, "cpu_avg15"),
do_stat(Node, Lang, "cpu_nprocs")%,
%%do_stat(Node, Lang, "cpu_util_user"),
%%do_stat(Node, Lang, "cpu_nice_user"),
%%do_stat(Node, Lang, "cpu_kernel"),
%%do_stat(Node, Lang, "cpu_idle"),
%%do_stat(Node, Lang, "cpu_wait")
])
]),
%%?XC("h3", "RAM"),
%%?XAE("table", [],
%% [?XE("tbody", [
%% do_stat(Node, Lang, "memsup_system"),
%% do_stat(Node, Lang, "memsup_free"),
%% do_stat(Node, Lang, "reductions")
%%])
%%]),
?XC(<<"h3">>, <<"Memory (bytes)">>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>, [
do_stat(Node, Lang, "memory_total"),
do_stat(Node, Lang, "memory_processes"),
do_stat(Node, Lang, "memory_processes_used"),
do_stat(Node, Lang, "memory_system"),
do_stat(Node, Lang, "memory_atom"),
do_stat(Node, Lang, "memory_atom_used"),
do_stat(Node, Lang, "memory_binary"),
do_stat(Node, Lang, "memory_code"),
do_stat(Node, Lang, "memory_ets")
])
]),
?XC(<<"h3">>, <<"Database">>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>,
[
?XE(<<"tr">>, [?XCT(<<"td">>, <<"Transactions commited">>),
?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
list_to_binary(integer_to_list(TransactionsCommited)))]),
?XE(<<"tr">>, [?XCT(<<"td">>, <<"Transactions aborted">>),
?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
list_to_binary(integer_to_list(TransactionsAborted)))]),
?XE(<<"tr">>, [?XCT(<<"td">>, <<"Transactions restarted">>),
?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
list_to_binary(integer_to_list(TransactionsRestarted)))]),
?XE(<<"tr">>, [?XCT(<<"td">>, <<"Transactions logged">>),
?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
list_to_binary(integer_to_list(TransactionsLogged)))])
])
])],
{stop, Res};
web_page_node(Acc, _, _, _, _) -> Acc.
web_page_host(_, Host,
#request{path = [<<"statsdx">>],
lang = Lang} = _Request) ->
Res = [?XC(<<"h1">>, <<(?T(<<"Statistics">>))/binary, " Dx">>),
?XC(<<"h2">>, Host),
?XC(<<"h3">>, <<"Accounts">>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>, [
do_stat(global, Lang, "registeredusers", Host)
])
]),
?XC(<<"h3">>, <<"Roster">>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>, [
do_stat(global, Lang, "totalrosteritems", Host),
%%get_meanitemsinroster2(TotalRosterItems, RegisteredUsers)
?XE(<<"tr">>,
[?XE(<<"td">>, [?C(<<"Top rosters">>) ]),
?XE(<<"td">>, [
?ACT(<<"top/roster/30">>, <<"30">>), ?C(<<", ">>),
?ACT(<<"top/roster/100">>, <<"100">>), ?C(<<", ">>),
?ACT(<<"top/roster/500">>, <<"500">>) ])]
)
])
]),
?XC(<<"h3">>, <<"Users">>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>, [
do_stat(global, Lang, "onlineusers", Host),
%%do_stat(global, Lang, "offlinemsg", Host), %% This make take a lot of time
%%do_stat(global, Lang, "vcards", Host) %% This make take a lot of time
?XE(<<"tr">>,
[?XE(<<"td">>, [?C(<<"Top offline message queues">>)]),
?XE(<<"td">>, [
?ACT(<<"top/offlinemsg/30">>, <<"30">>), ?C(<<", ">>),
?ACT(<<"top/offlinemsg/100">>, <<"100">>), ?C(<<", ">>),
?ACT(<<"top/offlinemsg/500">>, <<"500">>) ])]
),
?XE(<<"tr">>,
[?XE(<<"td">>, [?C(<<"Top vCard sizes">>) ]),
?XE(<<"td">>, [
?ACT(<<"top/vcard/5">>, <<"5">>), ?C(<<", ">>),
?ACT(<<"top/vcard/30">>, <<"30">>), ?C(<<", ">>),
?ACT(<<"top/vcard/100">>, <<"100">>), ?C(<<", ">>),
?ACT(<<"top/vcard/500">>, <<"500">>) ])]
)
])
]),
?XC(<<"h3">>, <<"Connections">>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>, [
do_stat(global, Lang, "s2sconnections", Host)
])
]),
?XC(<<"h3">>, <<"MUC">>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>, [
do_stat(global, Lang, "totalmucrooms", Host),
do_stat(global, Lang, "permmucrooms", Host),
do_stat(global, Lang, "regmucrooms", Host)
])
]),
%%?XC("h3", "IRC"),
%%?XAE("table", [],
%% [?XE("tbody", [
%% do_stat(global, Lang, "ircconns", Host)
%% ])
%%]),
%%?XC("h3", "Pub/Sub"),
%%?XAE("table", [],
%% [?XE("tbody", [
%% do_stat(global, Lang, "regpubsubnodes", Host)
%% ])
%%]),
?XC(<<"h3">>, <<"Sessions: ", (get_stat_n("client"))/binary>>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>,
do_stat_table(global, Lang, "client", Host)
)
]),
?XC(<<"h3">>, <<"Sessions: ", (get_stat_n("os"))/binary>>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>,
do_stat_table(global, Lang, "os", Host)
)
]),
?XC(<<"h3">>, <<"Sessions: ", (get_stat_n("client"))/binary, "/", (get_stat_n("os"))/binary>>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>,
do_stat_table(global, Lang, "client_os", Host)
)
]),
?XC(<<"h3">>, <<"Sessions: ", (get_stat_n("conntype"))/binary>>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>,
do_stat_table(global, Lang, "conntype", Host)
)
]),
?XC(<<"h3">>, <<"Sessions: ", (get_stat_n("client"))/binary, "/", (get_stat_n("conntype"))/binary>>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>,
do_stat_table(global, Lang, "client_conntype", Host)
)
]),
?XC(<<"h3">>, <<"Sessions: ", (get_stat_n("languages"))/binary>>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>,
do_stat_table(global, Lang, "languages", Host)
)
]),
?XC(<<"h3">>, <<"Ratios">>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>, [
do_stat(global, Lang, "user_login", Host),
do_stat(global, Lang, "user_logout", Host),
do_stat(global, Lang, "register_user", Host),
do_stat(global, Lang, "remove_user", Host),
do_stat(global, Lang, {send, iq, in}, Host),
do_stat(global, Lang, {send, iq, out}, Host),
do_stat(global, Lang, {send, message, in}, Host),
do_stat(global, Lang, {send, message, out}, Host),
do_stat(global, Lang, {send, presence, in}, Host),
do_stat(global, Lang, {send, presence, out}, Host),
do_stat(global, Lang, {recv, iq, in}, Host),
do_stat(global, Lang, {recv, iq, out}, Host),
do_stat(global, Lang, {recv, message, in}, Host),
do_stat(global, Lang, {recv, message, out}, Host),
do_stat(global, Lang, {recv, presence, in}, Host),
do_stat(global, Lang, {recv, presence, out}, Host)
])
])
],
{stop, Res};
web_page_host(_, Host, #request{path=[<<"statsdx">>, <<"top">>, Topic, Topnumber], q = _Q, lang = Lang} = _Request) ->
Res = [?XC("h1", ?T("Statistics")++" Dx"),
case Topic of
<<"offlinemsg">> -> ?XCT(<<"h2">>, <<"Top offline message queues">>);
<<"vcard">> -> ?XCT(<<"h2">>, <<"Top vCard sizes">>);
<<"roster">> -> ?XCT(<<"h2">>, <<"Top rosters">>)
end,
?XE(<<"table">>,
[?XE(<<"thead">>, [?XE(<<"tr">>,
[?XE(<<"td">>, [?CT(<<"#">>)]),
?XE(<<"td">>, [?CT(<<"Jabber ID">>)]),
?XE(<<"td">>, [?CT(<<"Value">>)])]
)]),
?XE(<<"tbody">>, do_top_table(global, Lang, Topic, Topnumber, Host))
])
],
{stop, Res};
web_page_host(_, Host, #request{path=[<<"statsdx">> | FilterURL], q = Q,
lang = Lang} = _Request) ->
Filter = parse_url_filter(FilterURL),
Sort_query = get_sort_query(Q),
Res = [?XC("h1", ?T("Statistics")++" Dx"),
?XC("h2", "Sessions with: "++ io_lib:format("~p", [Filter])),
?XAE("table", [],
[?XE("tbody",
do_sessions_table(global, Lang, Filter, Sort_query, Host)
)
])
],
{stop, Res};
web_page_host(Acc, _, _) -> Acc.
%%%==================================
%%%% Web Admin Utils
do_table_element(Lang, L, StatLink, N) ->
do_table_element(no_counter, Lang, L, StatLink, N).
do_table_element(Counter, Lang, L, StatLink, N) ->
?XE(<<"tr">>, [
case Counter of
no_counter -> ?C(<<"">>);
_ -> ?XE(<<"td">>, [?C(integer_to_list(Counter))])
end,
case StatLink of
no_link -> ?XCT(<<"td">>, L);
{fixed_url, Fixedurl} -> ?XE(<<"td">>, [?AC(Fixedurl, L)]);
_ -> ?XE(<<"td">>, [?AC(list_to_binary(make_url(StatLink, L)), list_to_binary(L))])
end,
case N of
{url, NUrl, NName} -> ?XAE(<<"td">>, [{<<"class">>, <<"alignright">>}], [?AC(NUrl, NName)]);
N when is_list(N) -> ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], list_to_binary(N));
_ -> ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], N)
end
]).
make_url(StatLink, L) ->
List = case string:tokens(StatLink, "_") of
[Stat] ->
[Stat, L];
[Stat1, Stat2] ->
[L1, L2] = string:tokens(L, "/"),
[Stat1, L1, Stat2, L2]
end,
string:join(List, "/").
do_stat_table(global, Lang, Stat, Host) ->
Os = mod_statsdx:get_statistic(global, [Stat, Host]),
lists:map(
fun({L, N}) ->
do_table_element(Lang, L, Stat, io_lib:format("~p", [N]))
end,
lists:reverse(lists:keysort(2, Os))
).
do_sessions_table(_Node, _Lang, Filter, {Sort_direction, Sort_column}, Host) ->
Sessions = get_sessions_filtered(Filter, Host),
SessionsSorted = sort_sessions(Sort_direction, Sort_column, Sessions),
lists:map(
fun( {{session, JID}, Client_id, OS_id, LangS, ConnType, Client, Version, OS} ) ->
Lang = list_to_binary(LangS),
User = binary_to_list(JID#jid.luser),
Server = binary_to_list(JID#jid.lserver),
UserURL = "/admin/server/" ++ Server ++ "/user/" ++ User ++ "/",
?XE("tr", [
?XE(<<"td">>, [?AC(list_to_binary(UserURL), jlib:jid_to_string(JID))]),
?XCTB("td", atom_to_list(Client_id)),
?XCTB("td", atom_to_list(OS_id)),
?XCTB("td", LangS),
?XCTB("td", atom_to_list(ConnType)),
?XCTB("td", Client),
?XCTB("td", Version),
?XCTB("td", OS)
])
end,
SessionsSorted
).
%% Code copied from mod_muc_admin.erl
sort_sessions(Direction, Column, Rooms) ->
Rooms2 = lists:keysort(Column, Rooms),
case Direction of
normal -> Rooms2;
reverse -> lists:reverse(Rooms2)
end.
get_sessions_filtered(Filter, server) ->
lists:foldl(
fun(Host, Res) ->
try get_sessions_filtered(Filter, Host) of
List when is_list(List) -> List ++ Res
catch
_:_ -> Res
end
end,
[],
?MYHOSTS);
get_sessions_filtered(Filter, Host) ->
Match = case Filter of
[{<<"client">>, Client}] -> {{session, '$1'}, jlib:binary_to_atom(Client), '$2', '$3', '$4', '$5', '$6', '$7'};
[{<<"os">>, OS}] -> {{session, '$1'}, '$2', jlib:binary_to_atom(OS), '$3', '$4', '$5', '$6', '$7'};
[{<<"conntype">>, ConnType}] -> {{session, '$1'}, '$2', '$3', '$4', jlib:binary_to_atom(ConnType), '$5', '$6', '$7'};
[{<<"languages">>, Lang}] -> {{session, '$1'}, '$2', '$3', binary_to_list(Lang), '$4', '$5', '$6', '$7'};
[{<<"client">>, Client}, {<<"os">>, OS}] -> {{session, '$1'}, jlib:binary_to_atom(Client), jlib:binary_to_atom(OS), '$3', '$4', '$5', '$6', '$7'};
[{<<"client">>, Client}, {<<"conntype">>, ConnType}] -> {{session, '$1'}, jlib:binary_to_atom(Client), '$2', '$3', jlib:binary_to_atom(ConnType), '$5', '$6', '$7'};
_ -> {{session, '$1'}, '$2', '$3', '$4', '$5'}
end,
ets:match_object(table_name(Host), Match).
do_stat(Node, Lang, Stat) ->
?XE(<<"tr">>, [
?XCT(<<"td">>, get_stat_n(Stat)),
?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
get_stat_v(Node, [Stat]))]).
do_stat(Node, Lang, Stat, Host) ->
%%[Res] = get_stat_v(Node, [Stat, Host]),
%%do_table_element(Lang, get_stat_n(Stat), Res).
do_table_element(Lang, get_stat_n(Stat), no_link, get_stat_v(Node, [Stat, Host])).
%% Get a stat name
get_stat_n(Stat) ->
list_to_binary(mod_statsdx:get_statistic(foo, [Stat, title])).
%% Get a stat value
get_stat_v(Node, Stat) -> list_to_binary(get_stat_v2(mod_statsdx:get_statistic(Node, Stat))).
get_stat_v2(Value) when is_list(Value) -> Value;
get_stat_v2(Value) when is_float(Value) -> io_lib:format("~.4f", [Value]);
get_stat_v2(Value) when is_integer(Value) ->
[Str] = io_lib:format("~p", [Value]),
pretty_string_int(Str);
get_stat_v2(Value) -> io_lib:format("~p", [Value]).
%% Transform "1234567890" into "1,234,567,890"
pretty_string_int(String) ->
{_, Result} = lists:foldl(
fun(NewNumber, {3, Result}) ->
{1, [NewNumber, $, | Result]};
(NewNumber, {CountAcc, Result}) ->
{CountAcc+1, [NewNumber | Result]}
end,
{0, ""},
lists:reverse(String)),
Result.
%%%==================================
%%%% Commands
commands() ->
[
#ejabberd_commands{name = get_top_users, tags = [stats],
desc = "Get top X users with larger offlinemsg, vcard or roster.",
module = ?MODULE, function = get_top_users,
args = [{topnumber, integer}, {topic, string}],
result = {top, {list,
{user, {tuple, [
{value, integer},
{user, string},
{server, string}
]}}
}}},
#ejabberd_commands{name = getstatsdx, tags = [stats],
desc = "Get statistical value.",
module = ?MODULE, function = getstatsdx,
args = [{name, string}],
result = {stat, integer}},
#ejabberd_commands{name = getstatsdx_host, tags = [stats],
desc = "Get statistical value for this host.",
module = ?MODULE, function = getstatsdx,
args = [{name, string}, {host, string}],
result = {stat, integer}}
].
getstatsdx(Name) ->
get_statistic(global, [Name]).
getstatsdx(Name, Host) ->
get_statistic(global, [Name, Host]).
get_top_users(Number, Topic) ->
get_top_users(server, Number, Topic).
%% Returns: [{Integer, User, Server}]
get_top_users(Host, Number, <<"vcard">>) ->
get_top_users_vcard(Host, Number);
get_top_users(Host, Number, <<"offlinemsg">>) ->
get_top_users(Host, Number, offline_msg, #offline_msg.us);
get_top_users(Host, Number, <<"roster">>) ->
get_top_users(Host, Number, roster, #roster.us).
get_top_users(Host, TopX, Table, RecordUserPos) ->
F = fun() ->
F2 = fun(R, {H, Dict}) ->
{LUser, LServer} = element(RecordUserPos, R),
case H of
server ->
{Host, dict:update_counter({LUser, LServer}, 1, Dict)};
LServer ->
{Host, dict:update_counter({LUser, LServer}, 1, Dict)};
_ ->
{Host, Dict}
end
end,
mnesia:foldl(F2, {Host, dict:new()}, Table)
end,
{atomic, {Host, DictRes}} = mnesia:transaction(F),
{_, _, Result} = dict:fold(
fun({User, Server}, Num, {EntryNumber, Size, TopList}) ->
case {Num > EntryNumber, Size < TopX} of
{false, true} ->
{Num, Size+1, lists:keymerge(1, TopList, [{Num, User, Server}])};
{true, true} ->
{EntryNumber, Size+1, lists:keymerge(1, TopList, [{Num, User, Server}])};
{true, false} ->
[{NewEntryNumber, _, _} | _] = TopList2 = lists:keydelete(EntryNumber, 1, TopList),
{NewEntryNumber, Size, lists:keymerge(1, TopList2, [{Num, User, Server}])};
{false, false} ->
{EntryNumber, Size, TopList}
end
end,
{10000000000000000, 0, []},
DictRes),
lists:reverse(Result).
get_top_users_vcard(Host, Number) ->
F = fun() ->
B = fun get_users_vcard_fun/2,
{_Host, _NumSelects, _MinSize, _Sizes, Selects} = mnesia:foldl(B, {Host, Number, -1, [], []}, vcard), %+++
Selects
end,
{atomic, Result} = mnesia:transaction(F),
lists:reverse(Result).
%% Selects = [{Size, Vcard}] sorted from smaller to larger
get_users_vcard_fun(#vcard{us = {_, Host1}}, {HostReq, NumRemaining, MinSize, Sizes, Selects})
when (Host1 /= HostReq) and (HostReq /= server) ->
{HostReq, NumRemaining, MinSize, Sizes, Selects};
get_users_vcard_fun(Vcard, {HostReq, NumRemaining, MinSize, Sizes, Selects}) ->
Binary = xml:element_to_binary(Vcard#vcard.vcard),
Size = byte_size(Binary),
case {Size > MinSize, NumRemaining > 0} of
{true, true} ->
{User, Host} = Vcard#vcard.us,
Selects2 = lists:umerge(Selects, [{Size, User, Host}]),
Sizes2 = lists:umerge(Sizes, [Size]),
MinSize2 = lists:min(Sizes2),
{HostReq, NumRemaining-1, MinSize2, Sizes2, Selects2};
{true, false} ->
[_ | SelectsReduced] = Selects,
[_ | SizesReduced] = Sizes,
Sizes2 = lists:umerge(SizesReduced, [Size]),
MinSize2 = lists:min(Sizes2),
{User, Host} = Vcard#vcard.us,
Selects2 = lists:umerge(SelectsReduced, [{Size, User, Host}]),
{HostReq, NumRemaining, MinSize2, Sizes2, Selects2};
{false, _} ->
{HostReq, NumRemaining, MinSize, Sizes, Selects}
end.
%%%==================================
%%% vim: set foldmethod=marker foldmarker=%%%%,%%%=: