%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1996-2010. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
-module(si_sasl_supp).
-behaviour(gen_server).
%%%---------------------------------------------------------------------------
%%% Description:
%%% This module contains the BOS specific parts of the Status Inspection Tool.
%%%---------------------------------------------------------------------------
%% user interface
-export([h/0, help/0, start_log/1, stop_log/0, abbrevs/0, pi/1, pi/2, pi/3,
pi/4, ppi/1, ppi/3, start/0, start/1, stop/0, start_link/1]).
%% intermodule exports
-export([make_pid/1, make_pid/3, process_abbrevs/0, expand_abbrev/2,
status_info/1, valid_opt/1, p/1, do_best_printout/4,
si_exec/2, handle_call/3, terminate/2]).
%% exports for use within module
-export([init/1, start_log_impl/1, pi_impl/2, ppi_impl/1]).
%% other gen_server callbacks (not used)
-export([handle_cast/2, handle_info/2, code_change/3]).
%%--------------------------------------------------
%% Table of contents
%% 1. Interface
%% 2. SI - Server
%% 3. Code
%% 4. Selectors
%%--------------------------------------------------
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 1. Interface
%% -----------------------------------------------------
h() -> print_help().
help() -> print_help().
si_exec(Fun, Args) -> call({si_exec, Fun, Args}).
start_log(FileName) ->
call({start_log, FileName}).
stop_log() ->
call(stop_log).
abbrevs() ->
io:format("~p", [process_abbrevs()]).
%%-----------------------------------------------------------------
%% All functions can be called with an option 'normal' or 'all';
%% default is 'normal'.
%%-----------------------------------------------------------------
%% Process Info that tries to determine processtype (=Module), then
%% it uses this Module:format_info to format data from status_info/1.
%%-----------------------------------------------------------------
pi(XPid) ->
si_exec({si_sasl_supp, pi_impl}, [normal, XPid]).
pi(Opt, XPid) ->
si_exec({si_sasl_supp, pi_impl}, [valid_opt(Opt), XPid]).
pi(A, B, C) when is_integer(A), is_integer(B), is_integer(C) ->
si_exec({si_sasl_supp, pi_impl}, [normal, {A, B, C}]).
pi(Opt, A, B, C) when is_integer(A), is_integer(B), is_integer(C) ->
si_exec({si_sasl_supp, pi_impl}, [valid_opt(Opt), {A, B, C}]).
%%-----------------------------------------------------------------
%% Pretty print Process_Info.
%%-----------------------------------------------------------------
ppi(XPid) ->
case whereis(si_server) of
undefined -> % You can always run ppi.
ppi_impl(XPid); % if si_server is down, use standard_io
_ ->
si_exec({si_sasl_supp, ppi_impl}, [XPid])
end.
ppi(A, B, C) ->
case whereis(si_server) of
undefined -> % You can always run ppi.
ppi_impl({A, B, C}); % if si_server is down, use standard_io
_ ->
si_exec({si_sasl_supp, ppi_impl}, [{A, B, C}])
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 2. SI - Server
%%--------------------------------------------------
-record(state, {}).
start() -> start([]).
start(Options) ->
supervisor:start_child(sasl_sup,
{si_server, {si_sasl_supp, start_link, [Options]},
temporary, brutal_kill, worker, [si_sasl_supp]}).
start_link(_Options) ->
gen_server:start_link({local, si_server}, si_sasl_supp, [], []).
stop() ->
call(stop),
supervisor:delete_child(sasl_sup, si_server).
init(Options) ->
process_flag(trap_exit, true),
start_log_impl(get_option(Options, start_log, standard_io)),
{ok, #state{}}.
%%-----------------------------------------------------------------
%% If an error occurs and we're logging to file: write the error
%% to the file.
%% Always return the error.
%% The only data held by the si_server is the device in its process dictionary.
%%-----------------------------------------------------------------
handle_call({si_exec, Fun, Args}, _From, State) ->
case catch apply(Fun, Args) of
{'EXIT', Reason} ->
print_error(get(device),
"SI internal error. Reason: ~w~n",
[Reason]),
{stop, shutdown, {internal_error, Reason}, State};
{error, Reason} ->
print_error(get(device), "~nSI error: ~w~n", [Reason]),
{reply, {error, Reason}, State};
X ->
{reply, X, State}
end;
handle_call({start_log, FileName}, _From, State) ->
start_log_impl(FileName),
{reply, ok, State};
handle_call(stop_log, _From, State) ->
start_log_impl(standard_io),
{reply, ok, State};
handle_call(stop, _From, State) ->
start_log_impl(standard_io),
{stop, normal, stopped, State}.
terminate(_Reason, _State) ->
close_device(get(device)),
ok.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
close_device(standard_io) -> ok;
close_device(Fd) -> file:close(Fd).
print_error(standard_io, _, _) -> ok;
print_error(Device, Format, Args) ->
io:format(Device, Format, Args).
get_option(Options, Key, Default) ->
case lists:keysearch(Key, 1, Options) of
{value, {_Key, Value}} -> Value;
_ -> Default
end.
open_log_file(undefined, NewFile) ->
open_log_file(NewFile);
open_log_file(standard_io, NewFile) ->
open_log_file(NewFile);
open_log_file(OldFile, NewFile) ->
file:close(OldFile),
open_log_file(NewFile).
open_log_file(standard_io) -> standard_io;
open_log_file(FileName) ->
case file:open(FileName, [write]) of
{ok, Fd} -> Fd;
Error ->
io:format("si_sasl_supp: Cannot open file '~s' (~w).~n",
[FileName, Error]),
io:format("si_sasl_supp: Using standard_io~n"),
standard_io
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 3. Code
%%--------------------------------------------------
%%-----------------------------------------------------------------
%% call(Request) -> Term
%%-----------------------------------------------------------------
call(Req) ->
gen_server:call(si_server, Req, infinity).
%%--------------------------------------------------
%% Makes a Pid of almost anything.
%% Returns: Pid|{error, Reason}
%% Fails: Never.
%%--------------------------------------------------
make_pid(A,B,C) when is_integer(A), is_integer(B), is_integer(C) ->
list_to_pid(lists:concat(["<",A,".",B,".",C,">"])).
make_pid(P) when is_pid(P) -> P;
make_pid(undefined) -> undefined;
make_pid(P) when is_atom(P) ->
case whereis(P) of
undefined ->
case expand_abbrev(P, process_abbrevs()) of
{error, Reason} -> {error, Reason};
{value, {_Abbrev, FullName}} ->
case whereis(FullName) of
undefined ->
{error, {'process not registered', P}};
Pid -> Pid
end
end;
Pid -> Pid
end;
make_pid(P) when is_list(P) -> list_to_pid(P);
make_pid({A, B, C}) -> make_pid(A, B, C);
make_pid(X) -> {error, {'can not make a pid of', X}}.
process_abbrevs() ->
[{init, init},
{fs, file_server}].
%%--------------------------------------------------
%% Args: Abbrevs is an assoc-list of {Abbrev, RealName}
%% Returns: {value, {Abbrev, FullName}}|{error, Reason}
%%--------------------------------------------------
expand_abbrev(ProcessName, Abbrevs) ->
case lists:keysearch(ProcessName, 1, Abbrevs) of
{value, {Abbrev, FullName}} ->
{value, {Abbrev, FullName}};
_ ->
case lists:keysearch(ProcessName, 2, Abbrevs) of
{value, {Abbrev, FullName}} ->
{value, {Abbrev, FullName}};
_ ->
{error, {'invalid process name', ProcessName}}
end
end.
%%-----------------------------------------------------------------
%% This is the function that actually gets the information out
%% of the agent/server/...
%% Returns: {status_info, Pid, Type, Data}
%% | {error, Reason}
%%-----------------------------------------------------------------
status_info(Pid) when is_pid(Pid) ->
case catch sys:get_status(Pid, 5000) of
{status, Pid, Type, Info} ->
{status_info, Pid, Type, Info};
_ ->
{error, {'process does not respond', Pid}}
end;
status_info(X) ->
{error, {'not a pid', X}}.
%%--------------------------------------------------
%% Implementation starts here.
%%--------------------------------------------------
start_log_impl(FileName) ->
put(device, open_log_file(get(device), FileName)).
valid_opt(all) -> all;
valid_opt(_Opt) -> normal.
print_help() ->
p("- - - - - - - - PROCESSES - - - - - - - - - "),
p("si_sasl_supp:pi([Opt,] Pid) - Formatted information about any process that"),
p(" SI recognises."),
p("si_sasl_supp:pi([Opt,] A,B,C) - Same as si_sasl_supp:pi({A, B, C})."),
p("si_sasl_supp:ppi(Pid) - Pretty formating of process_info."),
p(" Works for any process."),
p("- - - - - - - - MISC - - - - - - - - - - - "),
p("si_sasl_supp:abbrevs() - Lists valid abbreviations."),
p("si_sasl_supp:start_log(FileNname)"),
p("si_sasl_supp:stop_log()"),
p("si_sasl_supp:start() - Starts Status Inspection (the si_server)."),
p("si_sasl_supp:start([{start_log, FileName}])"),
p("si_sasl_supp:stop() - Shut down SI.").
%% Convenient shorthand
p(X) ->
io:format(lists:append(X, "~n")).
pi_impl(Opt, XPid) ->
case make_pid(XPid) of
Pid when is_pid(Pid) ->
case status_info(Pid) of
{status_info, Pid, {module, Module}, Data} ->
do_best_printout(Opt, Pid, Module, Data);
{error, Reason} ->
ppi_impl(Pid),
{error, {"can not get status info from process:",
XPid,
Reason}}
end;
{error, Reason} ->
{error, Reason}
end.
%%--------------------------------------------------
%% Is there a format_info for this process? In that case, run it!
%% Return ok|{error, Reason}
%% Fails: Never.
%%--------------------------------------------------
do_best_printout(Opt, Pid, Mod, Data) when is_pid(Pid) ->
case print_info(get(device), Pid, {Mod, format_status}, Opt, Data) of
ok -> ok;
{error, Reason} ->
ppi_impl(Pid),
{error, Reason}
end.
ppi_impl(XPid) ->
case make_pid(XPid) of
P when is_pid(P) ->
case process_info(P) of
undefined ->
{error, {'dead process', P}};
PI ->
Device = case get(device) of
undefined -> standard_io;
X -> X
end,
io:format(Device, "~nPretty Process Info~n", []),
io:format(Device, "-------------------~n", []),
io:format(Device, "~p~n", [PI])
end;
_ -> {error, {no_pid, XPid}}
end.
print_info(Device, Pid, {Module, Func}, Opt, Data) ->
case erlang:function_exported(Module, Func, 2) of
true ->
case catch apply(Module, Func, [Opt, Data]) of
Format when is_list(Format) ->
format_lib_supp:print_info(Device, 79,
add_pid_to_format(Pid, Format)),
ok;
Other -> {error, {'invalid format', Other}}
end;
_ ->
{error, {no_such_function, Module, Func}}
end.
add_pid_to_format(Pid, [{header, H} | T]) ->
[{header, H}, {data, [{"Pid", Pid}]} | T];
add_pid_to_format(Pid, List) ->
[{data, [{"Pid", Pid}]} | List].