%% ``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 via the world wide web 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.
%% 
%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
%% AB. All Rights Reserved.''
%% 
%%     $Id$
%% 

%% 
%% @doc Percept Collector 
%%
%%	This module provides the user interface for the percept data
%	collection (profiling).
%% 

-module(percept_profile).
-export([
	start/1, 
	start/2, 
	start/3,
	stop/0
	]).


%%==========================================================================
%%
%% 		Type definitions 
%%
%%==========================================================================

%% @type percept_option() = procs | ports | exclusive

-type(percept_option() :: 'procs' | 'ports' | 'exclusive' | 'scheduler').

%%==========================================================================
%%
%% 		Interface functions
%%
%%==========================================================================

%% @spec start(Filename::string()) -> {ok, Port} | {already_started, Port}
%% @equiv start(Filename, [procs])

-spec(start/1 :: (Filename :: string()) -> 
	{'ok', port()} | {'already_started', port()}).

start(Filename) ->
    profile_to_file(Filename, [procs]).

%% @spec start(Filename::string(), [percept_option()]) -> {ok, Port} | {already_started, Port}
%%	Port = port()
%% @doc Starts profiling with supplied options. 
%%	All events are stored in the file given by Filename. 
%%	An explicit call to stop/0 is needed to stop profiling. 

-spec(start/2 :: (
	Filename :: string(),
	Options :: [percept_option()]) ->
	{'ok', port()} | {'already_started', port()}).

start(Filename, Options) ->
    profile_to_file(Filename, Options). 

%% @spec start(string(), MFA::mfa(), [percept_option()]) -> ok | {already_started, Port} | {error, not_started}
%%	Port = port()
%% @doc Starts profiling at the entrypoint specified by the MFA. All events are collected, 
%%	this means that processes outside the scope of the entry-point are also profiled. 
%%	No explicit call to stop/0 is needed, the profiling stops when
%%	the entry function returns.

-spec(start/3 :: (
	Filename :: string(),
	Entry :: {atom(), atom(), list()},
	Options :: [percept_option()]) ->
	'ok' | {'already_started', port()} | {'error', 'not_started'}).

start(Filename, {Module, Function, Args}, Options) ->
    case whereis(percept_port) of
	undefined ->
	    profile_to_file(Filename, Options),
	    erlang:apply(Module, Function, Args),
	    stop();
	Port ->
	    {already_started, Port}
    end.

deliver_all_trace() ->
    Tracee = self(),
    Tracer = spawn(fun() -> 
	receive {Tracee, start} -> ok end,
    	Ref = erlang:trace_delivered(Tracee),
	receive {trace_delivered, Tracee, Ref} -> Tracee ! {self(), ok} end
    end),
    erlang:trace(Tracee, true, [procs, {tracer, Tracer}]),
    Tracer ! {Tracee, start},
    receive {Tracer, ok} -> ok end,
    erlang:trace(Tracee, false, [procs]),
    ok.
-spec(stop/0 :: () -> 'ok' | {'error', 'not_started'}).

%% @spec stop() -> ok | {'error', 'not_started'}
%% @doc Stops profiling.
    

stop() ->
    erlang:system_profile(undefined, [runnable_ports, runnable_procs]),
    erlang:trace(all, false, [procs, ports, timestamp]),
    deliver_all_trace(), 
    case whereis(percept_port) of
    	undefined -> 
	    {error, not_started};
	Port ->
	    erlang:port_command(Port, erlang:term_to_binary({profile_stop, erlang:now()})),
	    %% trace delivered?
	    erlang:port_close(Port),
	    ok
    end. 

%%==========================================================================
%%
%% 		Auxiliary functions 
%%
%%==========================================================================

profile_to_file(Filename, Opts) ->
    case whereis(percept_port) of 
	undefined ->
	    io:format("Starting profiling.~n", []),

	    erlang:system_flag(multi_scheduling, block),
	    Port = (dbg:trace_port(file, Filename))(),
	    % Send start time
	    erlang:port_command(Port, erlang:term_to_binary({profile_start, erlang:now()})),
	    erlang:system_flag(multi_scheduling, unblock),
		
	    %% Register Port
    	    erlang:register(percept_port, Port),
	    set_tracer(Port, Opts), 
	    {ok, Port};
	Port ->
	    io:format("Profiling already started at port ~p.~n", [Port]),
	    {already_started, Port}
    end.

%% set_tracer

set_tracer(Port, Opts) ->
    {TOpts, POpts} = parse_profile_options(Opts),
    % Setup profiling and tracing
    erlang:trace(all, true, [{tracer, Port}, timestamp | TOpts]),
    erlang:system_profile(Port, POpts).

%% parse_profile_options

parse_profile_options(Opts) ->
    parse_profile_options(Opts, {[],[]}).

parse_profile_options([], Out) ->
    Out;
parse_profile_options([Opt|Opts], {TOpts, POpts}) ->
    case Opt of
	procs ->
	    parse_profile_options(Opts, {
		[procs | TOpts], 
		[runnable_procs | POpts]
	    });
	ports ->
	    parse_profile_options(Opts, {
		[ports | TOpts], 
		[runnable_ports | POpts]
	    });
	scheduler ->
	    parse_profile_options(Opts, {
		[TOpts], 
		[scheduler | POpts]
	    });
	exclusive ->
	    parse_profile_options(Opts, {
		[TOpts], 
		[exclusive | POpts]
	    });
	_ -> 
	    parse_profile_options(Opts, {TOpts, POpts})

    end.
