% Copyright (C) 2003-2019 Olivier Boudeville
%
% This file is part of the Ceylan-Traces library.
%
% This library is free software: you can redistribute it and/or modify
% it under the terms of the GNU Lesser General Public License or
% the GNU General Public License, as they are published by the Free Software
% Foundation, either version 3 of these Licenses, or (at your option)
% any later version.
% You can also redistribute it and/or modify it under the terms of the
% Mozilla Public License, version 1.1 or later.
%
% This library is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
% GNU Lesser General Public License and the GNU General Public License
% for more details.
%
% You should have received a copy of the GNU Lesser General Public
% License, of the GNU General Public License and of the Mozilla Public License
% along with this library.
% If not, see and
% .
%
% Author: Olivier Boudeville [olivier (dot) boudeville (at) esperide (dot) com]
% Creation date: July 1, 2007.
%
-module(class_TraceSupervisor).
-define( class_description, "Trace supervisor; this version relies on the "
"advanced traces, often monitored thanks to LogMX (http://logmx.com) "
"to track the default execution trace file, expected to be locally "
"available on disk." ).
% Determines what are the direct mother classes of this class (if any):
-define( superclasses, [] ).
% Describes the class-specific attributes:
-define( class_attributes, [
{ trace_filename, file_utils:file_path(),
"the name of the file where traces are to be stored (ex: *.traces)" },
{ trace_type, trace_type(),
"the type of traces to be written (ex: advanced_traces)" },
{ trace_aggregator_pid, aggregator_pid(),
"the PID of the supervised trace aggregator" } ] ).
-type supervisor_pid() :: pid().
-type supervisor_outcome() :: 'no_trace_supervisor_wanted' | supervisor_pid().
-export_type([ supervisor_pid/0, supervisor_outcome/0 ]).
% Allows to define WOOPER base variables and methods for that class:
-include("wooper.hrl").
% For TraceExtension:
-include("traces.hrl").
% For the default trace filename:
-include("class_TraceAggregator.hrl").
-define( LogPrefix, "[Trace Supervisor]" ).
% Use global:registered_names() to check supervisor presence.
%-define( LogOutput( Message, Format ),
% trace_utils:debug_fmt( Message, Format ) ).
-define( LogOutput(Message,Format), void ).
% Total width (expressed as a number of characters) of a line of log, in text
% mode (text_traces).
%
-define( TextWidth, 110 ).
% Shorthand:
-type aggregator_pid() :: class_TraceAggregator:aggregator_pid().
% Constructs a new trace supervisor:
%
% - {TraceFilename,TraceType,TraceAggregatorPid}:
%
% - TraceFilename is the name of the file where traces should be read from
%
% - TraceType the type of traces to expect (ex: advanced_traces, text_traces)
%
% - MaybeTraceAggregatorPid is the PID of the trace aggregator to wait for, or
% 'undefined'
%
% - MonitorNow tells whether the supervision should begin immediately (if true)
% or only when the monitor method is called (if false)
%
% - Blocking tells whether the monitoring should be non-blocking (if equal to
% 'none'); otherwise the monitoring should be blocking, and this Blocking
% parameter should be the PID of the caller to be notified. This parameter has a
% meaning iff MonitorNow is true
%
-spec construct( wooper:state(), { file_utils:file_name(),
traces:trace_supervision_type(), maybe( aggregator_pid() ) },
boolean(), 'none' | pid() ) -> wooper:state().
construct( State, { TraceFilename, TraceType, MaybeTraceAggregatorPid },
MonitorNow, Blocking ) ->
trace_utils:debug_fmt( "~s Creating a trace supervisor, whose PID is ~w "
"(trace filename: '~s', trace type: '~s', "
"monitor now: ~w, blocking: ~w",
[ ?LogPrefix, self(), TraceFilename, TraceType,
MonitorNow, Blocking ] ),
NewState = setAttributes( State, [
{ trace_filename, TraceFilename },
{ trace_type, TraceType },
{ trace_aggregator_pid, MaybeTraceAggregatorPid } ] ),
case MaybeTraceAggregatorPid of
AggPid when is_pid( AggPid ) ->
% We have a PID, avoid the race condition that could happen if the
% trace viewer (ex: LogMX) was launched before a first trace was
% written by the aggregator in the trace file:
%trace_utils:trace(
% "(trace supervisor waiting for the trace aggregator)" ),
AggPid ! { requestReadyNotification, [], self() },
receive
{ wooper_result, trace_file_ready } ->
%trace_utils:trace( "Trace aggregator answered." ),
ok
end;
undefined ->
%trace_utils:trace(
% "(trace supervisor not waiting any trace aggregator)" ),
% Supposedly no race condition is to be feared here:
ok
end,
EndState = case MonitorNow of
true ->
case Blocking of
CallerPid when is_pid( CallerPid ) ->
% Pattern-match the result of in-place invocation:
% ('monitor_ok' used to be temporarily replaced by '_' due
% to the LogMX issue with java_security_PrivilegedAction)
%
case executeRequest( NewState, blocking_monitor ) of
{ RequestState, monitor_ok } ->
% Sends back to the caller:
CallerPid ! { wooper_result, monitor_ok },
self() ! delete,
RequestState;
{ AnyState, monitor_failed } ->
% If needing to ignore a non-significant error from
% the supervision tool:
%
CallerPid ! { wooper_result, monitor_ok },
self() ! delete,
AnyState
%throw( blocking_monitoring_failed )
end;
none ->
% Non-blocking, handled after the constructor:
self() ! monitor,
NewState
end;
false ->
NewState
end,
%trace_utils:debug_fmt( "~s Supervisor created.", [ ?LogPrefix ] ),
EndState.
% Methods section.
% Triggers a non-blocking supervision (trace monitoring).
% Will return immediately.
%
-spec monitor( wooper:state() ) -> const_oneway_return().
monitor( State ) ->
case ?getAttr(trace_type) of
{ text_traces, pdf } ->
trace_utils:info_fmt( "~s Supervisor has nothing to monitor, "
"as the PDF trace report will be generated only on "
"execution termination.", [ ?LogPrefix ] ),
wooper:const_return();
_Other ->
{ Command, ActualFilename } = get_viewer_settings( State ),
case file_utils:is_existing_file( ActualFilename ) of
true ->
ok;
false ->
trace_utils:error_fmt( "class_TraceSupervisor:monitor "
"unable to find trace file '~s'.",
[ ActualFilename ] ),
throw( { trace_file_not_found, ActualFilename } )
end,
trace_utils:info_fmt( "~s Supervisor will monitor file '~s' now, "
"with '~s'.", [ ?LogPrefix, ActualFilename, Command ] ),
Cmd = Command ++ " '" ++ ActualFilename ++ "'",
% Non-blocking (command must be found in the PATH):
system_utils:run_background_executable( Cmd ),
wooper:const_return()
end.
% Triggers a blocking supervision (trace monitoring).
%
% Will block until the viewer window is closed by the user.
%
-spec blocking_monitor( wooper:state() ) ->
const_request_return( 'monitor_ok' ).
blocking_monitor( State ) ->
case ?getAttr(trace_type) of
{ text_traces, pdf } ->
trace_utils:info_fmt( "~s Supervisor has nothing to monitor, "
"as the PDF trace report will be generated only on "
"execution termination.", [ ?LogPrefix ] ),
wooper:const_return_result( monitor_ok );
_Other ->
{ Command, ActualFilename } = get_viewer_settings( State ),
case file_utils:is_existing_file( ActualFilename ) of
true ->
ok;
false ->
trace_utils:error_fmt(
"class_TraceSupervisor:blocking_monitor "
"unable to find trace file '~s'.", [ ActualFilename ] ),
throw( { trace_file_not_found, ActualFilename } )
end,
trace_utils:info_fmt( "~s Supervisor will monitor file '~s' now "
"with '~s', blocking until the user closes the viewer window.",
[ ?LogPrefix, ActualFilename, Command ] ),
% Blocking:
case system_utils:run_executable(
Command ++ " '" ++ ActualFilename ++ "'" ) of
{ _ExitStatus=0, _Output } ->
trace_utils:info_fmt(
"~s Supervisor ended monitoring of '~s'.",
[ ?LogPrefix, ActualFilename ] ),
wooper:const_return_result( monitor_ok );
{ ExitStatus, ErrorOutput } ->
trace_utils:error_fmt(
"The monitoring of trace supervisor failed "
"(error ~B): '~s'.", [ ExitStatus, ErrorOutput ] ),
% Must not be a blocking error:
%wooper:const_return_result( monitor_failed )
%throw( trace_supervision_failed )
wooper:const_return_result( monitor_ok )
end
end.
% Static section.
% Creates the trace supervisor with default settings regarding trace filename,
% start mode (immediate here, not deferred) and trace type (advanced ones here,
% not text based), with no PID specified for the trace aggregator, and blocks
% until closed.
%
% See create/5 for a more in-depth explanation of the parameters.
%
-spec create() -> static_return( supervisor_pid() ).
create() ->
SupervisorPid = create( _Blocking=true ),
wooper:return_static( SupervisorPid ).
% Creates the trace supervisor, then blocks iff Blocking is true, with default
% settings regarding trace filename, start mode (immediate here, not deferred)
% and trace type (advanced ones here, not text based), with no PID specified for
% the trace aggregator.
%
% See create/5 for a more in-depth explanation of the parameters.
%
-spec create( boolean() ) -> static_return( supervisor_pid() ).
create( Blocking ) ->
SupervisorPid = create( Blocking, ?trace_aggregator_filename ),
wooper:return_static( SupervisorPid ).
% Creates the trace supervisor, then blocks iff Blocking is true, with default
% settings regarding start mode (immediate here, not deferred) and trace type
% (advanced ones here, not text based), with no PID specified for the trace
% aggregator.
%
% See create/5 for a more in-depth explanation of the parameters.
%
create( Blocking, TraceFilename ) ->
SupervisorPid = create( Blocking, TraceFilename, _TraceType=advanced_traces,
_TraceAggregatorPid=undefined ),
wooper:return_static( SupervisorPid ).
% Creates the trace supervisor, then blocks iff Blocking is true, with default
% settings regarding start mode (immediate here, not deferred).
%
% See create/5 for a more in-depth explanation of the parameters.
%
-spec create( boolean(), file_utils:file_name(),
traces:trace_supervision_type(), maybe( aggregator_pid() ) ) ->
static_return( supervisor_pid() ).
create( Blocking, TraceFilename, TraceType, TraceAggregatorPid ) ->
SupervisorPid = create( Blocking, _MonitorNow=true, TraceFilename,
TraceType, TraceAggregatorPid ),
wooper:return_static( SupervisorPid ).
% Creates a trace supervisor:
%
% - Blocking tells whether the monitoring should be blocking (if true) or not
% (the supervisor tool is then launched in the background)
%
% - MonitorNow tells whether the monitoring should start immediately or only
% when a monitor/blocking_monitor method is called
%
% - TraceFilename the trace file to monitor
%
% - TraceType the expected type of the traces (ex: advanced_traces, text_traces)
%
% - MaybeTraceAggregatorPid is either the PID of the trace aggregator, or the
% 'undefined' atom
%
% Returns either the PID of the created supervisor or, if blocking (hence the
% supervisor being dead by design when this creation returns), 'undefined'.
%
-spec create( boolean(), boolean(), file_utils:file_name(),
traces:trace_supervision_type(), maybe( aggregator_pid() ) ) ->
static_return( maybe( supervisor_pid() ) ).
create( Blocking, MonitorNow, TraceFilename, TraceType,
MaybeTraceAggregatorPid ) ->
BlockingParam = case Blocking of
true ->
self() ;
false ->
none
end,
SupervisorPid = new_link( { TraceFilename, TraceType,
MaybeTraceAggregatorPid },
MonitorNow, BlockingParam ),
MaybeSupervisorPid = case Blocking of
% Then by design the process is terminated:
true ->
undefined;
false ->
SupervisorPid
end,
wooper:return_static( MaybeSupervisorPid ).
% Inits a trace supervisor; especially useful when the trace supervisor cannot
% be created at the same time as the trace aggregator (ex: if the trace filename
% is to change at runtime).
%
% Use the --batch option (ex: erl --batch, or with the make system 'make
% MY_TARGET CMD_LINE_OPT="--batch") to disable the use of the trace supervisor.
%
-spec init( file_utils:file_name(), traces:trace_supervision_type(),
aggregator_pid() ) -> static_return( supervisor_outcome() ).
init( TraceFilename, TraceType, TraceAggregatorPid ) ->
SupOutcome = init( TraceFilename, TraceType, TraceAggregatorPid,
_BlockingSupervisor=true ),
wooper:return_static( SupOutcome ).
% Inits a trace supervisor; especially useful when the trace supervisor cannot
% be created at the same time as the trace aggregator (ex: if the trace filename
% is to change at runtime).
%
% Use the --batch option (ex: erl --batch, or with the make system 'make
% MY_TARGET CMD_LINE_OPT="--batch") to disable the use of the trace supervisor.
%
-spec init( file_utils:file_name(), traces:trace_supervision_type(),
aggregator_pid(), boolean() ) -> static_return( supervisor_outcome() ).
init( TraceFilename, TraceType, TraceAggregatorPid, BlockingSupervisor ) ->
%trace_utils:trace_fmt( "Initializing trace supervisor for file '~s' and "
% "trace type ~p.", [ TraceFilename, TraceType ] ),
% By default (with no specific option) a synchronous supervisor is wanted
% (wait for its launch to complete):
case executable_utils:is_batch() of
true ->
% Option specified to disable the supervisor:
trace_utils:info_fmt( "Application trace file is '~s'; no "
"interactive supervision requested.",
[ TraceFilename ] ),
wooper:return_static( no_trace_supervisor_wanted );
false ->
% Default: a trace supervisor is used.
%trace_utils:info( "Supervisor enabled." ),
SupervisorPid = create( BlockingSupervisor, TraceFilename,
TraceType, TraceAggregatorPid ),
%trace_utils:debug( "Waiting for trace supervisor to be closed." )
wooper:return_static( SupervisorPid )
end.
% Waits, usually at the end of a test, for any trace supervisor to be closed by
% the user.
%
-spec wait_for() -> static_void_return().
wait_for() ->
case executable_utils:is_batch() of
true ->
% No supervisor was launched.
% Let live the system for some time instead:
system_utils:await_output_completion();
false ->
actual_wait_for()
end,
wooper:return_static_void().
% (helper)
-spec actual_wait_for() -> void().
actual_wait_for() ->
% A supervisor must be waited for here:
trace_utils:info(
"(waiting for the user to stop the trace supervision)" ),
receive
{ wooper_result, monitor_ok } ->
%trace_utils:info(
% "Notification received from supervisor." ),
% Not {test,app}_info, as used in both contexts:
class_TraceEmitter:send_standalone( info,
"Traces successfully monitored." )
end.
% Helper section.
% Returns the path of the tool and the corresponding file that should be used to
% monitor traces.
%
% (helper)
%
-spec get_viewer_settings( wooper:state() ) ->
{ file_utils:path(), file_utils:file_name() }.
get_viewer_settings( State ) ->
Filename = ?getAttr(trace_filename),
case ?getAttr(trace_type) of
advanced_traces ->
{ executable_utils:get_default_trace_viewer_path(), Filename };
{ text_traces, text_only } ->
{ executable_utils:get_default_wide_text_viewer_path( ?TextWidth ),
Filename };
{ text_traces, pdf } ->
PdfTargetFilename = file_utils:replace_extension( Filename,
?TraceExtension, ".pdf" ),
{ executable_utils:get_default_pdf_viewer_path(),
PdfTargetFilename }
end.