%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2008-2009. 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%
%%
%%
%% @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.