aboutsummaryrefslogtreecommitdiffstats
path: root/lib/percept/src/percept.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/percept/src/percept.erl')
-rw-r--r--lib/percept/src/percept.erl337
1 files changed, 337 insertions, 0 deletions
diff --git a/lib/percept/src/percept.erl b/lib/percept/src/percept.erl
new file mode 100644
index 0000000000..af1a920efd
--- /dev/null
+++ b/lib/percept/src/percept.erl
@@ -0,0 +1,337 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-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 - Erlang Concurrency Profiling Tool
+%%
+%% This module provides the user interface for the application.
+%%
+
+-module(percept).
+-behaviour(application).
+-export([
+ profile/1,
+ profile/2,
+ profile/3,
+ stop_profile/0,
+ start_webserver/0,
+ start_webserver/1,
+ stop_webserver/0,
+ stop_webserver/1,
+ analyze/1,
+ % Application behaviour
+ start/2,
+ stop/1]).
+
+
+-include("percept.hrl").
+
+%%==========================================================================
+%%
+%% Type definitions
+%%
+%%==========================================================================
+
+%% @type percept_option() = procs | ports | exclusive
+
+-type(percept_option() :: 'procs' | 'ports' | 'exclusive' | 'scheduler').
+
+%%==========================================================================
+%%
+%% Application callback functions
+%%
+%%==========================================================================
+
+%% @spec start(Type, Args) -> {started, Hostname, Port} | {error, Reason}
+%% @doc none
+%% @hidden
+
+start(_Type, _Args) ->
+ %% start web browser service
+ start_webserver(0).
+
+%% @spec stop(State) -> ok
+%% @doc none
+%% @hidden
+
+stop(_State) ->
+ %% stop web browser service
+ stop_webserver(0).
+
+%%==========================================================================
+%%
+%% Interface functions
+%%
+%%==========================================================================
+
+%% @spec profile(Filename::string()) -> {ok, Port} | {already_started, Port}
+%% @see percept_profile
+
+%% profiling
+
+-spec(profile/1 :: (Filename :: string()) ->
+ {'ok', port()} | {'already_started', port()}).
+
+profile(Filename) ->
+ percept_profile:start(Filename, [procs]).
+
+%% @spec profile(Filename::string(), [percept_option()]) -> {ok, Port} | {already_started, Port}
+%% @see percept_profile
+
+-spec(profile/2 :: (
+ Filename :: string(),
+ Options :: [percept_option()]) ->
+ {'ok', port()} | {'already_started', port()}).
+
+profile(Filename, Options) ->
+ percept_profile:start(Filename, Options).
+
+%% @spec profile(Filename::string(), MFA::mfa(), [percept_option()]) -> ok | {already_started, Port} | {error, not_started}
+%% @see percept_profile
+
+-spec(profile/3 :: (
+ Filename :: string(),
+ Entry :: {atom(), atom(), list()},
+ Options :: [percept_option()]) ->
+ 'ok' | {'already_started', port()} | {'error', 'not_started'}).
+
+profile(Filename, MFA, Options) ->
+ percept_profile:start(Filename, MFA, Options).
+
+-spec(stop_profile/0 :: () -> 'ok' | {'error', 'not_started'}).
+
+%% @spec stop_profile() -> ok | {'error', 'not_started'}
+%% @see percept_profile
+
+stop_profile() ->
+ percept_profile:stop().
+
+%% @spec analyze(string()) -> ok | {error, Reason}
+%% @doc Analyze file.
+
+-spec(analyze/1 :: (Filename :: string()) ->
+ 'ok' | {'error', any()}).
+
+analyze(Filename) ->
+ case percept_db:start() of
+ {started, DB} ->
+ parse_and_insert(Filename,DB);
+ {restarted, DB} ->
+ parse_and_insert(Filename,DB)
+ end.
+
+%% @spec start_webserver() -> {started, Hostname, Port} | {error, Reason}
+%% Hostname = string()
+%% Port = integer()
+%% Reason = term()
+%% @doc Starts webserver.
+
+-spec(start_webserver/0 :: () ->
+ {'started', string(), pos_integer()} |
+ {'error', any()}).
+
+start_webserver() ->
+ start_webserver(0).
+
+%% @spec start_webserver(integer()) -> {started, Hostname, AssignedPort} | {error, Reason}
+%% Hostname = string()
+%% AssignedPort = integer()
+%% Reason = term()
+%% @doc Starts webserver. If port number is 0, an available port number will
+%% be assigned by inets.
+
+-spec(start_webserver/1 :: (Port :: non_neg_integer()) ->
+ {'started', string(), pos_integer()} |
+ {'error', any()}).
+
+start_webserver(Port) when is_integer(Port) ->
+ application:load(percept),
+ case whereis(percept_httpd) of
+ undefined ->
+ {ok, Config} = get_webserver_config("percept", Port),
+ inets:start(),
+ case inets:start(httpd, Config) of
+ {ok, Pid} ->
+ AssignedPort = find_service_port_from_pid(inets:services_info(), Pid),
+ {ok, Host} = inet:gethostname(),
+ %% workaround until inets can get me a service from a name.
+ Mem = spawn(fun() -> service_memory({Pid,AssignedPort,Host}) end),
+ register(percept_httpd, Mem),
+ {started, Host, AssignedPort};
+ {error, Reason} ->
+ {error, {inets, Reason}}
+ end;
+ _ ->
+ {error, already_started}
+ end.
+
+%% @spec stop_webserver() -> ok | {error, not_started}
+%% @doc Stops webserver.
+
+stop_webserver() ->
+ case whereis(percept_httpd) of
+ undefined ->
+ {error, not_started};
+ Pid ->
+ Pid ! {self(), get_port},
+ receive Port -> ok end,
+ Pid ! quit,
+ stop_webserver(Port)
+ end.
+
+%% @spec stop_webserver(integer()) -> ok | {error, not_started}
+%% @doc Stops webserver of the given port.
+%% @hidden
+
+stop_webserver(Port) ->
+ case find_service_pid_from_port(inets:services_info(), Port) of
+ undefined ->
+ {error, not_started};
+ Pid ->
+ inets:stop(httpd, Pid)
+ end.
+
+%%==========================================================================
+%%
+%% Auxiliary functions
+%%
+%%==========================================================================
+
+%% parse_and_insert
+
+parse_and_insert(Filename, DB) ->
+ io:format("Parsing: ~p ~n", [Filename]),
+ T0 = erlang:now(),
+ Pid = dbg:trace_client(file, Filename, mk_trace_parser(self())),
+ Ref = erlang:monitor(process, Pid),
+ parse_and_insert_loop(Filename, Pid, Ref, DB, T0).
+
+parse_and_insert_loop(Filename, Pid, Ref, DB, T0) ->
+ receive
+ {'DOWN',Ref,process, Pid, noproc} ->
+ io:format("Incorrect file or malformed trace file: ~p~n", [Filename]),
+ {error, file};
+ {parse_complete, {Pid, Count}} ->
+ receive {'DOWN', Ref, process, Pid, normal} -> ok after 0 -> ok end,
+ DB ! {action, consolidate},
+ T1 = erlang:now(),
+ io:format("Parsed ~p entries in ~p s.~n", [Count, ?seconds(T1, T0)]),
+ io:format(" ~p created processes.~n", [length(percept_db:select({information, procs}))]),
+ io:format(" ~p opened ports.~n", [length(percept_db:select({information, ports}))]),
+ ok;
+ {'DOWN',Ref, process, Pid, normal} -> parse_and_insert_loop(Filename, Pid, Ref, DB, T0);
+ {'DOWN',Ref, process, Pid, Reason} -> {error, Reason}
+ end.
+
+mk_trace_parser(Pid) ->
+ {fun trace_parser/2, {0, Pid}}.
+
+trace_parser(end_of_trace, {Count, Pid}) ->
+ Pid ! {parse_complete, {self(),Count}},
+ receive
+ {ack, Pid} ->
+ ok
+ end;
+trace_parser(Trace, {Count, Pid}) ->
+ percept_db:insert(Trace),
+ {Count + 1, Pid}.
+
+find_service_pid_from_port([], _) ->
+ undefined;
+find_service_pid_from_port([{_, Pid, Options} | Services], Port) ->
+ case lists:keysearch(port, 1, Options) of
+ false ->
+ find_service_pid_from_port(Services, Port);
+ {value, {port, Port}} ->
+ Pid
+ end.
+
+find_service_port_from_pid([], _) ->
+ undefined;
+find_service_port_from_pid([{_, Pid, Options} | _], Pid) ->
+ case lists:keysearch(port, 1, Options) of
+ false ->
+ undefined;
+ {value, {port, Port}} ->
+ Port
+ end;
+find_service_port_from_pid([{_, _, _} | Services], Pid) ->
+ find_service_port_from_pid(Services, Pid).
+
+%% service memory
+
+service_memory({Pid, Port, Host}) ->
+ receive
+ quit ->
+ ok;
+ {Reply, get_port} ->
+ Reply ! Port,
+ service_memory({Pid, Port, Host});
+ {Reply, get_host} ->
+ Reply ! Host,
+ service_memory({Pid, Port, Host});
+ {Reply, get_pid} ->
+ Reply ! Pid,
+ service_memory({Pid, Port, Host})
+ end.
+
+% Create config data for the webserver
+
+get_webserver_config(Servername, Port) when is_list(Servername), is_integer(Port) ->
+ Path = code:priv_dir(percept),
+ Root = filename:join([Path, "server_root"]),
+ MimeTypesFile = filename:join([Root,"conf","mime.types"]),
+ {ok, MimeTypes} = httpd_conf:load_mime_types(MimeTypesFile),
+ Config = [
+ % Roots
+ {server_root, Root},
+ {document_root,filename:join([Root, "htdocs"])},
+
+ % Aliases
+ {eval_script_alias,{"/eval",[io]}},
+ {erl_script_alias,{"/cgi-bin",[percept_graph,percept_html,io]}},
+ {script_alias,{"/cgi-bin/", filename:join([Root, "cgi-bin"])}},
+ {alias,{"/javascript/",filename:join([Root, "scripts"]) ++ "/"}},
+ {alias,{"/images/", filename:join([Root, "images"]) ++ "/"}},
+ {alias,{"/css/", filename:join([Root, "css"]) ++ "/"}},
+
+ % Logs
+ %{transfer_log, filename:join([Path, "logs", "transfer.log"])},
+ %{error_log, filename:join([Path, "logs", "error.log"])},
+
+ % Configs
+ {default_type,"text/plain"},
+ {directory_index,["index.html"]},
+ {mime_types, MimeTypes},
+ {modules,[mod_alias,
+ mod_esi,
+ mod_actions,
+ mod_cgi,
+ mod_include,
+ mod_dir,
+ mod_get,
+ mod_head
+ % mod_log,
+ % mod_disk_log
+ ]},
+ {com_type,ip_comm},
+ {server_name, Servername},
+ {bind_address, any},
+ {port, Port}],
+ {ok, Config}.