aboutsummaryrefslogtreecommitdiffstats
path: root/lib/et/src
diff options
context:
space:
mode:
authorErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
committerErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
commit84adefa331c4159d432d22840663c38f155cd4c1 (patch)
treebff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/et/src
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/et/src')
-rw-r--r--lib/et/src/Makefile135
-rw-r--r--lib/et/src/et.app.src34
-rw-r--r--lib/et/src/et.appup.src23
-rw-r--r--lib/et/src/et.erl140
-rw-r--r--lib/et/src/et_collector.erl1204
-rw-r--r--lib/et/src/et_contents_viewer.erl591
-rw-r--r--lib/et/src/et_internal.hrl23
-rw-r--r--lib/et/src/et_selector.erl529
-rw-r--r--lib/et/src/et_viewer.erl1602
-rw-r--r--lib/et/src/modules.mk32
10 files changed, 4313 insertions, 0 deletions
diff --git a/lib/et/src/Makefile b/lib/et/src/Makefile
new file mode 100644
index 0000000000..c590852625
--- /dev/null
+++ b/lib/et/src/Makefile
@@ -0,0 +1,135 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2000-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%
+#
+
+#
+include $(ERL_TOP)/make/target.mk
+
+ERL_COMPILE_FLAGS += -W +warn_unused_vars
+
+ifeq ($(TYPE),debug)
+ERL_COMPILE_FLAGS += -Ddebug
+endif
+
+# This is temporary until the et app is actually moved...
+EBIN = ../ebin
+
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
+# ----------------------------------------------------
+# Application version
+# ----------------------------------------------------
+include ../vsn.mk
+VSN=$(ET_VSN)
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+RELSYSDIR = $(RELEASE_PATH)/lib/et-$(VSN)
+
+
+# ----------------------------------------------------
+# Target Specs
+# ----------------------------------------------------
+
+include modules.mk
+
+ERL_FILES = $(MODULES:%=%.erl)
+
+TARGET_FILES = \
+ $(ERL_FILES:%.erl=$(EBIN)/%.$(EMULATOR))
+
+APP_FILE = et.app
+APP_SRC = $(APP_FILE).src
+APP_TARGET = $(EBIN)/$(APP_FILE)
+
+APPUP_FILE = et.appup
+APPUP_SRC = $(APPUP_FILE).src
+APPUP_TARGET = $(EBIN)/$(APPUP_FILE)
+
+# ----------------------------------------------------
+# FLAGS
+# ----------------------------------------------------
+ERL_COMPILE_FLAGS += -pa $(ERL_TOP)/lib/et/ebin -I../include
+
+# ----------------------------------------------------
+# Special Build Targets
+# ----------------------------------------------------
+
+# ----------------------------------------------------
+# Targets
+# ----------------------------------------------------
+
+debug:
+ @${MAKE} TYPE=debug opt
+
+opt: $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET)
+
+clean:
+ rm -f $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET)
+ rm -f errs core *~
+
+docs:
+
+
+# ----------------------------------------------------
+# Special Build Targets
+# ----------------------------------------------------
+
+$(APP_TARGET): $(APP_SRC) ../vsn.mk
+ sed -e 's;%VSN%;$(VSN);' $< > $@
+
+$(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk
+ sed -e 's;%VSN%;$(VSN);' $< > $@
+
+# ----------------------------------------------------
+# Release Target
+# ----------------------------------------------------
+
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+release_spec: opt
+ $(INSTALL_DIR) $(RELSYSDIR)/ebin
+ $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin
+ $(INSTALL_DATA) $(APP_TARGET) $(APPUP_TARGET) $(RELSYSDIR)/ebin
+ $(INSTALL_DIR) $(RELSYSDIR)/src
+ $(INSTALL_DATA) $(ERL_FILES) $(RELSYSDIR)/src
+ $(INSTALL_DATA) $(INTERNAL_HRL_FILES) $(RELSYSDIR)/src
+ $(INSTALL_DIR) $(RELSYSDIR)/include
+ $(INSTALL_DATA) $(HRL_FILES) $(RELSYSDIR)/include
+
+
+release_docs_spec:
+
+
+# ----------------------------------------------------
+# Include dependencies
+# ----------------------------------------------------
+
+$(EBIN)/et.$(EMULATOR): et.erl
+
+$(EBIN)/et_selector.$(EMULATOR): et_selector.erl ../include/et.hrl
+
+$(EBIN)/et_contents_viewer.$(EMULATOR): et_contents_viewer.erl ../include/et.hrl et_internal.hrl
+
+$(EBIN)/et_collector.$(EMULATOR): et_collector.erl ../include/et.hrl et_internal.hrl
+
+$(EBIN)/et_viewer.$(EMULATOR): et_viewer.erl ../include/et.hrl et_internal.hrl
+
+
+
diff --git a/lib/et/src/et.app.src b/lib/et/src/et.app.src
new file mode 100644
index 0000000000..0c7bef7c3d
--- /dev/null
+++ b/lib/et/src/et.app.src
@@ -0,0 +1,34 @@
+%% This is an -*- erlang -*- file.
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-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%
+
+{application, et,
+ [{description, "Event Tracer"},
+ {vsn, "%VSN%"},
+ {modules,
+ [
+ et,
+ et_collector,
+ et_contents_viewer,
+ et_selector,
+ et_viewer
+ ]},
+ {registered, [et_collector]},
+ {applications, [stdlib, kernel]},
+ {env, []}
+ ]}.
diff --git a/lib/et/src/et.appup.src b/lib/et/src/et.appup.src
new file mode 100644
index 0000000000..b6344a9387
--- /dev/null
+++ b/lib/et/src/et.appup.src
@@ -0,0 +1,23 @@
+%% This is an -*- erlang -*- file.
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-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%
+
+{"%VSN%",
+ [ ]
+}.
+
diff --git a/lib/et/src/et.erl b/lib/et/src/et.erl
new file mode 100644
index 0000000000..9c0a7f8f49
--- /dev/null
+++ b/lib/et/src/et.erl
@@ -0,0 +1,140 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2000-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%
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Main API module for Event Tracer
+%%----------------------------------------------------------------------
+%%
+%% The Event Tracer (et) uses the built-in trace mechanism in Erlang and
+%% provides tools for collection and graphical viewing of trace data.
+%%
+%% et_collector
+%%
+%% An Erlang trace client which collects and stores trace data.
+%% Provides hooks for trace data filtering and group communication
+%% between processes (such as et_viewer-processes). The trace data
+%% is preferably traced et-module calls, but may in fact be any
+%% Erlang trace data.
+%%
+%% It do also provide functionality for global control of trace
+%% pattern settings. If used, the one et_collector-process is
+%% registered globally. On all connected Erlang nodes, it starts an
+%% Erlang tracer process which sends its trace data to a local port
+%% (the port number is generated). On the node where the global
+%% et_collector is running, the corresponding Erlang trace client
+%% processes are started (one for each node), configured to
+%% transform the trace data into event records and possibly hand
+%% them over to the collector. Whenever new nodes are
+%% (dis)connected, this is monitored and new tracer/client pair of
+%% processes are automatically started and eventually the trace
+%% pattern are set on these nodes.
+%%
+%% Trace data can also be loaded from one or more files.
+%%
+%% et_viewer
+%%
+%% A graphical sequence chart tool. It is connected to a
+%% et_collector-process, which it polls regulary for more trace
+%% events to display. Before the event is displayed a user defined
+%% filter function is applied in order to skip, accept as is or
+%% transform the event. Several et_viewer-processes may share the
+%% same et_collector in order to provide different simultaneous
+%% views of the same trace data.
+%%
+%% et_contents_viewer
+%%
+%% A graphical tool which displays a detailed view of one trace
+%% event. Normally started from the et_viewer.
+%%
+%% et_selector
+%%
+%% A library module with low level functions for activation of
+%% Erlang trace patterns. It do also implement a default filter
+%% function which transforms the raw trace data into the event
+%% record data structure that is used as internal format by the rest
+%% of the application. Customized transform functions can be
+%% alternatively be used (by et_viewer, et_contents_viewer and
+%% et_collector), if needed.
+%%
+%% et
+%%
+%% A library module with a few event report functions that are
+%% intended to be invoked from other applications. The functions are
+%% extremely light weight as they do nothing besides returning an
+%% atom. These functions are specifically designed to be traced
+%% for. The global trace patterns in et_collector defaults to trace
+%% on these functions.
+%%----------------------------------------------------------------------
+
+-module(et).
+
+-export([
+ phone_home/4, report_event/4,
+ phone_home/5, report_event/5
+ ]).
+
+%%----------------------------------------------------------------------
+%% Reports an event, such as a message
+%%
+%% report_event(DetailLevel, FromTo, Label, Contents) -> hopefully_traced
+%% report_event(DetailLevel, From, To, Label, Contents) -> hopefully_traced
+%% phone_home(DetailLevel, FromTo, Label, Contents) -> hopefully_traced
+%% phone_home(DetailLevel, From, To, Label, Contents) -> hopefully_traced
+%%
+%% DetailLevel = integer(X) when X =< 0, X >= 100
+%% From = actor()
+%% To = actor()
+%% FromTo = actor()
+%% Label = atom() | string() |�term()
+%% Contents = [{Key, Value}] | term()
+%%
+%% actor() = term()
+%%
+%% These functions are intended to be invoked at strategic places
+%% in user applications in order to enable simplified tracing.
+%% The functions are extremely light weight as they do nothing
+%% besides returning an atom. These functions are designed for
+%% being traced. The global tracing mechanism in et_collector
+%% defaults to set its trace pattern to these functions.
+%%
+%% The label is intended to provide a brief summary of the event.
+%% A simple tag would do.
+%%
+%% The contents can be any term but in order to simplify
+%% post processing of the traced events, a plain list
+%% of {Key, Value} tuples is preferred.
+%%
+%% Some events, such as messages, are directed from some actor to another.
+%% Other events (termed actions) may be undirected and only have one actor.
+%%----------------------------------------------------------------------
+
+phone_home(DetailLevel, FromTo, Label, Contents) ->
+ %% N.B External call
+ ?MODULE:report_event(DetailLevel, FromTo, FromTo, Label, Contents).
+
+phone_home(DetailLevel, From, To, Label, Contents) ->
+ %% N.B External call
+ ?MODULE:report_event(DetailLevel, From, To, Label, Contents).
+
+report_event(DetailLevel, FromTo, Label, Contents) ->
+ %% N.B External call
+ ?MODULE:report_event(DetailLevel, FromTo, FromTo, Label, Contents).
+
+report_event(DetailLevel, _From, _To, _Label, _Contents)
+ when integer(DetailLevel) ->
+ hopefully_traced.
diff --git a/lib/et/src/et_collector.erl b/lib/et/src/et_collector.erl
new file mode 100644
index 0000000000..ea23c188f7
--- /dev/null
+++ b/lib/et/src/et_collector.erl
@@ -0,0 +1,1204 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2000-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%
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Collect trace events and provide a backing storage
+%% appropriate for iteration
+%%----------------------------------------------------------------------
+
+-module(et_collector).
+
+-behaviour(gen_server).
+
+%% External exports
+-export([
+ start_link/1,
+ stop/1,
+
+ report/2,
+ report_event/5,
+ report_event/6,
+
+ iterate/3,
+ iterate/5,
+
+ start_trace_client/3,
+ start_trace_port/1,
+ %% load_event_file/2,
+ save_event_file/3,
+ clear_table/1,
+
+ get_global_pid/0,
+ %% get_table_handle/1,
+ change_pattern/2,
+ make_key/2,
+
+ dict_insert/3,
+ dict_delete/2,
+ dict_lookup/2,
+ dict_match/2,
+ multicast/2
+ ]).
+
+%% gen_server callbacks
+-export([init/1,terminate/2, code_change/3,
+ handle_call/3, handle_cast/2, handle_info/2]).
+
+-include("../include/et.hrl").
+
+-record(state, {parent_pid,
+ event_tab,
+ dict_tab,
+ event_order,
+ subscribers,
+ file,
+ trace_pattern,
+ trace_port,
+ trace_max_queue,
+ trace_nodes,
+ trace_global}).
+
+-record(file, {name, desc, event_opt, file_opt, table_opt}).
+
+-record(table_handle, {collector_pid, event_tab, event_order, filter}).
+
+-record(trace_ts, {trace_ts, event_ts}).
+-record(event_ts, {event_ts, trace_ts}).
+
+%%%----------------------------------------------------------------------
+%%% Client side
+%%%----------------------------------------------------------------------
+
+%%----------------------------------------------------------------------
+%% start_link(Options) -> {ok, CollectorPid} | {error, Reason}
+%%
+%% Start a collector process
+%%
+%% The collector collects trace events and keeps them ordered by their
+%% timestamp. The timestamp may either reflect the time when the
+%% actual trace data was generated (trace_ts) or when the trace data
+%% was transformed into an event record (event_ts). If the time stamp
+%% is missing in the trace data (missing timestamp option to
+%% erlang:trace/4) the trace_ts will be set to the event_ts.
+%%
+%% Events are reported to the collector directly with the report
+%% function or indirectly via one or more trace clients. All reported
+%% events are first filtered thru the collector filter before they are
+%% stored by the collector. By replacing the default collector filter
+%% with a customized dito it is possible to allow any trace data as
+%% input. The collector filter is a dictionary entry with the
+%% predefined key {filter, collector} and the value is a fun of
+%% arity 1. See et_selector:parse_event/2 for interface details,
+%% such as which erlang:trace/1 tuples that are accepted.
+%%
+%% The collector has a built-in dictionary service. Any term may be
+%% stored as value in the dictionary and bound to a unique key. When
+%% new values are inserted with an existing key, the new values will
+%% overwrite the existing ones. Processes may subscribe on dictionary
+%% updates by using {subscriber, pid()} as dictionary key. All
+%% dictionary updates will be propagated to the subscriber processes
+%% matching the pattern {{subscriber, '_'}, '_'} where the first '_'
+%% is interpreted as a pid().
+%%
+%% In global trace mode, the collector will automatically start
+%% tracing on all connected Erlang nodes. When a node connects, a port
+%% tracer will be started on that node and a corresponding trace
+%% client on the collector node. By default the global trace pattern
+%% is 'max'.
+%%
+%% Options = [option()]
+%%
+%% option() =
+%% {parent_pid, pid()} |
+%% {event_order, event_order()} |
+%% {dict_insert, {filter, collector}, collector_fun()} |
+%% {dict_insert, {filter, event_filter_name()}, event_filter_fun()} |
+%% {dict_insert, {subscriber, pid()}, dict_val()} |
+%% {dict_insert, dict_key(), dict_val()} |
+%% {dict_delete, dict_key()} |
+%% {trace_client, trace_client()} |
+%% {trace_global, boolean()} |
+%% {trace_pattern, trace_pattern()} |
+%% {trace_port, integer()} |
+%% {trace_max_queue, integer()}
+%%
+%% event_order() = trace_ts | event_ts
+%% trace_pattern() = detail_level() | dbg_match_spec()
+%% detail_level() = min | max | integer(X) when X =< 0, X >= 100
+%% trace_client() =
+%% {event_file, file_name()} |
+%% {dbg_trace_type(), dbg_trace_parameters()}
+%% file_name() = string()
+%% collector_fun() = trace_filter_fun() | event_filter_fun()
+%% trace_filter_fun() = fun(TraceData) -> false | true | {true, NewEvent}
+%% event_filter_fun() = fun(Event) -> false | true | {true, NewEvent}
+%% event_filter_name() = atom()
+%% TraceData = erlang_trace_data()
+%% Event = NewEvent = record(event)
+%% dict_key() = term()
+%% dict_val() = term()
+%%
+%% CollectorPid = pid()
+%% Reason = term()
+%%----------------------------------------------------------------------
+
+start_link(Options) ->
+ case parse_opt(Options, default_state(), [], []) of
+ {ok, S, Dict2, Clients} when S#state.trace_global == false ->
+ case gen_server:start_link(?MODULE, [S, Dict2], []) of
+ {ok, Pid} when S#state.parent_pid /= self() ->
+ unlink(Pid),
+ start_clients(Pid, Clients);
+ {ok,Pid} ->
+ start_clients(Pid, Clients);
+ {error, Reason} ->
+ {error, Reason}
+ end;
+ {ok, S, Dict2, Clients} when S#state.trace_global == true ->
+ case gen_server:start_link({global, ?MODULE}, ?MODULE, [S, Dict2], []) of
+ {ok, Pid} when S#state.parent_pid /= self() ->
+ unlink(Pid),
+ start_clients(Pid, Clients);
+ {ok,Pid} ->
+ start_clients(Pid, Clients);
+ {error, Reason} ->
+ {error, Reason}
+ end;
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
+default_state() ->
+ #state{parent_pid = self(),
+ event_order = trace_ts,
+ subscribers = [],
+ trace_global = false,
+ trace_pattern = undefined,
+ trace_nodes = [],
+ trace_port = 4711,
+ trace_max_queue = 50}.
+
+parse_opt([], S, Dict, Clients) ->
+ {Mod, Pattern} = et_selector:make_pattern(S#state.trace_pattern),
+ Fun = fun(E) -> et_selector:parse_event(Mod, E) end,
+ Default = {dict_insert, {filter, collector}, Fun},
+ {ok, S#state{trace_pattern = {Mod, Pattern}}, [Default | Dict], Clients};
+parse_opt([H | T], S, Dict, Clients) ->
+ case H of
+ {parent_pid, Parent} when Parent == undefined ->
+ parse_opt(T, S#state{parent_pid = Parent}, Dict, Clients);
+ {parent_pid, Parent} when pid(Parent) ->
+ parse_opt(T, S#state{parent_pid = Parent}, Dict, Clients);
+ {event_order, Order} when Order == trace_ts ->
+ parse_opt(T, S#state{event_order = Order}, Dict, Clients);
+ {event_order, Order} when Order == event_ts ->
+ parse_opt(T, S#state{event_order = Order}, Dict, Clients);
+ {dict_insert, {filter, Name}, Fun} ->
+ if
+ atom(Name), function(Fun) ->
+ parse_opt(T, S, Dict ++ [H], Clients);
+ true ->
+ {error, {bad_option, H}}
+ end;
+ {dict_insert, {subscriber, Pid}, _Val} ->
+ if
+ pid(Pid) ->
+ parse_opt(T, S, Dict ++ [H], Clients);
+ true ->
+ {error, {bad_option, H}}
+ end;
+ {dict_insert, _Key, _Val} ->
+ parse_opt(T, S, Dict ++ [H], Clients);
+ {dict_delete, _Key} ->
+ parse_opt(T, S, Dict ++ [H], Clients);
+ {trace_client, Client = {_, _}} ->
+ parse_opt(T, S, Dict, Clients ++ [Client]);
+ {trace_global, Bool} when Bool == false ->
+ parse_opt(T, S#state{trace_global = Bool}, Dict, Clients);
+ {trace_global, Bool} when Bool == true ->
+ parse_opt(T, S#state{trace_global = Bool}, Dict, Clients);
+ {trace_pattern, {Mod, _} = Pattern} when atom(Mod) ->
+ parse_opt(T, S#state{trace_pattern = Pattern}, Dict, Clients);
+ {trace_pattern, undefined = Pattern} ->
+ parse_opt(T, S#state{trace_pattern = Pattern}, Dict, Clients);
+ {trace_port, Port} when integer(Port) ->
+ parse_opt(T, S#state{trace_port = Port}, Dict, Clients);
+ {trace_max_queue, MaxQueue} when integer(MaxQueue) ->
+ parse_opt(T, S#state{trace_port = MaxQueue}, Dict, Clients);
+ Bad ->
+ {error, {bad_option, Bad}}
+ end;
+parse_opt(BadList, _S, _Dict, _Clients) ->
+ {error, {bad_option_list, BadList}}.
+
+start_clients(CollectorPid, [{Type, Parameters} | T]) ->
+ start_trace_client(CollectorPid, Type, Parameters),
+ start_clients(CollectorPid, T);
+start_clients(CollectorPid, []) ->
+ {ok, CollectorPid}.
+
+%%----------------------------------------------------------------------
+%% stop(CollectorPid) -> ok
+%%
+%% Stop a collector process
+%%
+%% CollectorPid = pid()
+%%----------------------------------------------------------------------
+
+stop(CollectorPid) ->
+ call(CollectorPid, stop).
+
+%%----------------------------------------------------------------------
+%% save_event_file(CollectorPid, FileName, Options) -> ok | {error, Reason}
+%%
+%% Saves the events to a file
+%%
+%% CollectorPid = pid()
+%% FileName = string()
+%% Options = [option()]
+%% Reason = term()
+%%
+%% option() = event_option() | file_option() | table_option()
+%% event_option() = existing
+%% file_option() = write | append
+%% table_option() = keep | clear
+%%
+%% By default the currently stored events (existing) are
+%% written to a brand new file (write) and the events are
+%% kept (keep) after they have been written to the file.
+%%
+%% Instead of keeping the events after writing them to file,
+%% it is possible to remove all stored events after they
+%% have successfully written to file (clear).
+%%
+%% The options defaults to existing, write and keep.
+%%----------------------------------------------------------------------
+
+save_event_file(CollectorPid, FileName, Options) ->
+ call(CollectorPid, {save_event_file, FileName, Options}).
+
+%%----------------------------------------------------------------------
+%% load_event_file(CollectorPid, FileName) ->{ok, BadBytes} | exit(Reason)
+%%
+%% Load the event table from a file
+%%
+%% CollectorPid = pid()
+%% FileName = string()
+%% BadBytes = integer(X) where X >= 0
+%% Reason = term()
+%%----------------------------------------------------------------------
+
+load_event_file(CollectorPid, FileName) ->
+ Fd = make_ref(),
+ Args = [{file, FileName}, {name, Fd}, {repair, true}, {mode, read_only}],
+ Fun = fun(Event, {ok, TH}) -> report(TH, Event) end,
+ case disk_log:open(Args) of
+ {ok, _} ->
+ do_load_event_file(Fun, Fd, start, {ok, CollectorPid}, FileName, 0);
+ {repaired, _, _, BadBytes} ->
+ do_load_event_file(Fun, Fd, start, {ok, CollectorPid}, FileName, BadBytes);
+ {error, Reason} ->
+ exit({disk_log_open, FileName, Reason})
+ end.
+
+do_load_event_file(Fun, Fd, Cont, Acc, FileName, BadBytes) ->
+ case disk_log:chunk(Fd, Cont) of
+ eof ->
+ {ok, BadBytes};
+ {error, Reason} ->
+ exit({bad_disk_log_chunk, FileName, Reason});
+ {Cont2, Events} ->
+ Acc2 = lists:foldl(Fun, Acc, Events),
+ do_load_event_file(Fun, Fd, Cont2, Acc2, FileName, BadBytes);
+ {Cont2, Events, More} ->
+ Acc2 = lists:foldl(Fun, Acc, Events),
+ do_load_event_file(Fun, Fd, Cont2, Acc2, FileName, BadBytes + More)
+ end.
+
+%%----------------------------------------------------------------------
+%% report(Handle, TraceOrEvent)
+%%
+%% Report an event to the collector
+%%
+%% All events are filtered thru the collector filter, which
+%% optionally may transform or discard the event. The first
+%% call should use the pid of the collector process as
+%% report handle, while subsequent calls should use the
+%% table handle.
+%%
+%% Handle = Initial | Continuation
+%% Initial = collector_pid()
+%% collector_pid() = pid()
+%% Continuation = record(table_handle)
+%%
+%% TraceOrEvent = record(event) | dbg_trace_tuple() | end_of_trace
+%% Reason = term()
+%%
+%% Returns: {ok, Continuation} | exit(Reason)
+%%----------------------------------------------------------------------
+
+report(CollectorPid, TraceOrEvent) when pid(CollectorPid) ->
+ case get_table_handle(CollectorPid) of
+ {ok, TH} when record(TH, table_handle) ->
+ report(TH, TraceOrEvent);
+ {error, Reason} ->
+ exit(Reason)
+ end;
+report(TH, TraceOrEvent) when record(TH, table_handle) ->
+ Fun = TH#table_handle.filter,
+ case Fun(TraceOrEvent) of
+ false ->
+ {ok, TH};
+ true when record(TraceOrEvent, event) ->
+ Key = make_key(TH, TraceOrEvent),
+ case catch ets:insert(TH#table_handle.event_tab, {Key, TraceOrEvent}) of
+ true ->
+ {ok, TH};
+ {'EXIT', _Reason} ->
+ %% Refresh the report handle and try again
+ report(TH#table_handle.collector_pid, TraceOrEvent)
+ end;
+ {true, Event} when record(Event, event) ->
+ Key = make_key(TH, Event),
+ case catch ets:insert(TH#table_handle.event_tab, {Key, Event}) of
+ true ->
+ {ok, TH};
+ {'EXIT', _Reason} ->
+ %% Refresh the report handle and try again
+ report(TH#table_handle.collector_pid, TraceOrEvent)
+ end;
+ BadEvent ->
+ TS = erlang:now(),
+ Contents = [{trace, TraceOrEvent}, {reason, BadEvent}, {filter, Fun}],
+ Event = #event{detail_level = 0,
+ trace_ts = TS,
+ event_ts = TS,
+ from = bad_filter,
+ to = bad_filter,
+ label = bad_filter,
+ contents = Contents},
+ Key = make_key(TH, Event),
+ case catch ets:insert(TH#table_handle.event_tab, {Key, Event}) of
+ true ->
+ {ok, TH};
+ {'EXIT', _Reason} ->
+ %% Refresh the report handle and try again
+ report(TH#table_handle.collector_pid, TraceOrEvent)
+ end
+ end;
+report(TH, end_of_trace) when record(TH, table_handle) ->
+ {ok, TH};
+report(_, Bad) ->
+ exit({bad_event, Bad}).
+
+report_event(CollectorPid, DetailLevel, FromTo, Label, Contents) ->
+ report_event(CollectorPid, DetailLevel, FromTo, FromTo, Label, Contents).
+
+report_event(CollectorPid, DetailLevel, From, To, Label, Contents)
+ when integer(DetailLevel), DetailLevel >= 0, DetailLevel =< 100, list(Contents) ->
+ TS= erlang:now(),
+ E = #event{detail_level = DetailLevel,
+ trace_ts = TS,
+ event_ts = TS,
+ from = From,
+ to = To,
+ label = Label,
+ contents = Contents},
+ report(CollectorPid, E).
+
+%%----------------------------------------------------------------------
+%% make_key(Type, Stuff) -> Key
+%%
+%% Makes a key out of an event record or an old key
+%%
+%% Type = record(table_handle) | trace_ts | event_ts
+%% Stuff = record(event) | Key
+%% Key = record(event_ts) | record(trace_ts)
+%%----------------------------------------------------------------------
+
+make_key(TH, Stuff) when record(TH, table_handle) ->
+ make_key(TH#table_handle.event_order, Stuff);
+make_key(trace_ts, Stuff) ->
+ if
+ record(Stuff, event) ->
+ #event{trace_ts = R, event_ts = P} = Stuff,
+ #trace_ts{trace_ts = R, event_ts = P};
+ record(Stuff, trace_ts) ->
+ Stuff;
+ record(Stuff, event_ts) ->
+ #event_ts{trace_ts = R, event_ts = P} = Stuff,
+ #trace_ts{trace_ts = R, event_ts = P}
+ end;
+make_key(event_ts, Stuff) ->
+ if
+ record(Stuff, event) ->
+ #event{trace_ts = R, event_ts = P} = Stuff,
+ #event_ts{trace_ts = R, event_ts = P};
+ record(Stuff, event_ts) ->
+ Stuff;
+ record(Stuff, trace_ts) ->
+ #trace_ts{trace_ts = R, event_ts = P} = Stuff,
+ #event_ts{trace_ts = R, event_ts = P}
+ end.
+
+%%----------------------------------------------------------------------
+%% get_table_handle(CollectorPid) -> Handle
+%%
+%% Return a table handle
+%%
+%% CollectorPid = pid()
+%% Handle = record(table_handle)
+%%----------------------------------------------------------------------
+
+get_table_handle(CollectorPid) when pid(CollectorPid) ->
+ call(CollectorPid, get_table_handle).
+
+%%----------------------------------------------------------------------
+%% get_global_pid() -> CollectorPid | exit(Reason)
+%%
+%% Return a the identity of the globally registered collector
+%% if there is any
+%%
+%% CollectorPid = pid()
+%% Reason = term()
+%%----------------------------------------------------------------------
+
+get_global_pid() ->
+ case global:whereis_name(?MODULE) of
+ CollectorPid when pid(CollectorPid) ->
+ CollectorPid;
+ undefined ->
+ exit(global_collector_not_started)
+ end.
+
+%%----------------------------------------------------------------------
+%% change_pattern(CollectorPid, RawPattern) -> {old_pattern, TracePattern}
+%%
+%% Change active trace pattern globally on all trace nodes
+%%
+%% CollectorPid = pid()
+%% RawPattern = {report_module(), extended_dbg_match_spec()}
+%% report_module() = atom() | undefined
+%% extended_dbg_match_spec()() = detail_level() | dbg_match_spec()
+%% RawPattern = detail_level()
+%% detail_level() = min | max | integer(X) when X =< 0, X >= 100
+%% TracePattern = {report_module(), dbg_match_spec_match_spec()}
+%%----------------------------------------------------------------------
+
+change_pattern(CollectorPid, RawPattern) ->
+ Pattern = et_selector:make_pattern(RawPattern),
+ call(CollectorPid, {change_pattern, Pattern}).
+
+%%----------------------------------------------------------------------
+%% dict_insert(CollectorPid, {filter, collector}, FilterFun) -> ok
+%% dict_insert(CollectorPid, {subscriber, SubscriberPid}, Void) -> ok
+%% dict_insert(CollectorPid, Key, Val) -> ok
+%%
+%% Insert a dictionary entry
+%% and send a {et, {dict_insert, Key, Val}} tuple
+%% to all registered subscribers.
+%%
+%% If the entry is a new subscriber, it will imply that
+%% the new subscriber process first will get one message
+%% for each already stored dictionary entry, before it
+%% and all old subscribers will get this particular entry.
+%% The collector process links to and then supervises the
+%% subscriber process. If the subscriber process dies it
+%% will imply that it gets unregistered as with a normal
+%% dict_delete/2.
+%%
+%% CollectorPid = pid()
+%% FilterFun = filter_fun()
+%% SubscriberPid = pid()
+%% Void = term()
+%% Key = term()
+%% Val = term()
+%%----------------------------------------------------------------------
+
+dict_insert(CollectorPid, Key = {filter, Name}, Fun) ->
+ if
+ atom(Name), function(Fun) ->
+ call(CollectorPid, {dict_insert, Key, Fun});
+ true ->
+ exit({badarg, Key})
+ end;
+dict_insert(CollectorPid, Key = {subscriber, Pid}, Val) ->
+ if
+ pid(Pid) ->
+ call(CollectorPid, {dict_insert, Key, Val});
+ true ->
+ exit({badarg, Key})
+ end;
+dict_insert(CollectorPid, Key, Val) ->
+ call(CollectorPid, {dict_insert, Key, Val}).
+
+%%----------------------------------------------------------------------
+%% dict_lookup(CollectorPid, Key) -> [Val]
+%%
+%% Lookup a dictionary entry and return zero or one value
+%%
+%% CollectorPid = pid()
+%% Key = term()
+%% Val = term()
+%%----------------------------------------------------------------------
+
+dict_lookup(CollectorPid, Key) ->
+ call(CollectorPid, {dict_lookup, Key}).
+
+%%----------------------------------------------------------------------
+%% Ddict_delete(CollectorPid, Key) -> ok
+%%
+%% elete a dictionary entry
+%% and send a {et, {dict_delete, Key}} tuple
+%% to all registered subscribers.
+%%
+%% If the deleted entry is a registered subscriber, it will
+%% imply that the subscriber process gets is unregistered as
+%% subscriber as well as it gets it final message.
+%%
+%% dict_delete(CollectorPid, {subscriber, SubscriberPid})
+%% dict_delete(CollectorPid, Key)
+%%
+%% CollectorPid = pid()
+%% SubscriberPid = pid()
+%% Key = term()
+%%----------------------------------------------------------------------
+
+dict_delete(CollectorPid, Key) ->
+ call(CollectorPid, {dict_delete, Key}).
+
+%%----------------------------------------------------------------------
+%% dict_match(CollectorPid, Pattern) -> [Match]
+%%
+%% Match some dictionary entries
+%%
+%% CollectorPid = pid()
+%% Pattern = '_' | {key_pattern(), val_pattern()}
+%% key_pattern() = ets_match_object_pattern()
+%% val_pattern() = ets_match_object_pattern()
+%% Match = {key(), val()}
+%% key() = term()
+%% val() = term()
+%%----------------------------------------------------------------------
+
+dict_match(CollectorPid, Pattern) ->
+ call(CollectorPid, {dict_match, Pattern}).
+
+%%----------------------------------------------------------------------
+%% multicast(_CollectorPid, Msg) -> ok
+%%
+%% Sends a message to all registered subscribers
+%%
+%% CollectorPid = pid()
+%% Msg = term()
+%%----------------------------------------------------------------------
+
+multicast(_CollectorPid, Msg = {dict_insert, _Key, _Val}) ->
+ exit({badarg, Msg});
+multicast(_CollectorPid, Msg = {dict_delete, _Key}) ->
+ exit({badarg, Msg});
+multicast(CollectorPid, Msg) ->
+ call(CollectorPid, {multicast, Msg}).
+
+%%----------------------------------------------------------------------
+%% start_trace_client(CollectorPid, Type, Parameters) ->
+%% file_loaded | {trace_client_pid, pid()} | exit(Reason)
+%%
+%% Load raw Erlang trace from a file, port or process.
+%%
+%% Type = dbg_trace_client_type()
+%% Parameters = dbg_trace_client_parameters()
+%% Pid = dbg_trace_client_pid()
+%%----------------------------------------------------------------------
+
+start_trace_client(CollectorPid, Type, FileName) when Type == event_file ->
+ load_event_file(CollectorPid, FileName);
+start_trace_client(CollectorPid, Type, FileName) when Type == file ->
+ WaitFor = {make_ref(), end_of_trace},
+ EventFun = fun(E, {ReplyTo, {ok, TH}}) -> {ReplyTo, report(TH, E)} end,
+ EndFun = fun({ReplyTo, {ok, _TH}}) -> ReplyTo ! WaitFor, ReplyTo end,
+ Spec = trace_spec_wrapper(EventFun, EndFun, {self(), {ok, CollectorPid}}),
+ Pid = dbg:trace_client(Type, FileName, Spec),
+ unlink(Pid),
+ Ref = erlang:monitor(process, Pid),
+ receive
+ WaitFor ->
+ erlang:demonitor(Ref),
+ receive
+ {'DOWN', Ref, _, _, _} ->
+ file_loaded
+ after 0 ->
+ file_loaded
+ end;
+ {'DOWN', Ref, _, _, Reason} ->
+ exit(Reason)
+ end;
+start_trace_client(CollectorPid, Type, Parameters) ->
+ EventFun = fun(Event, {ok, TH}) -> report(TH, Event) end,
+ EndFun = fun(Acc) -> Acc end,
+ Spec = trace_spec_wrapper(EventFun, EndFun, {ok, CollectorPid}),
+ Pid = dbg:trace_client(Type, Parameters, Spec),
+ CollectorPid ! {register_trace_client, Pid},
+ unlink(Pid),
+ {trace_client_pid, Pid}.
+
+trace_spec_wrapper(EventFun, EndFun, EventInitialAcc)
+ when function(EventFun), function(EndFun) ->
+ {fun(Trace, Acc) ->
+ case Trace == end_of_trace of
+ true -> EndFun(Acc);
+ false -> EventFun(Trace, Acc)
+ end
+ end,
+ EventInitialAcc}.
+
+start_trace_port(Parameters) ->
+ dbg:tracer(port, dbg:trace_port(ip, Parameters)).
+
+%%----------------------------------------------------------------------
+%% iterate(Handle, Prev, Limit) ->
+%% iterate(Handle, Prev, Limit, undefined, Prev)
+%%
+%% Iterates over the currently stored events
+%%
+%% Short for iterate/5.
+%%----------------------------------------------------------------------
+
+iterate(Handle, Prev, Limit) ->
+ iterate(Handle, Prev, Limit, undefined, Prev).
+
+%%----------------------------------------------------------------------
+%% iterate(Handle, Prev, Limit, Fun, Acc) -> NewAcc
+%%
+%% Iterates over the currently stored events and apply a function for
+%% each event. The iteration may be performed forwards or backwards
+%% and may be limited to a maximum number of events (abs(Limit)).
+%%
+%% Handle = collector_pid() | table_handle()
+%% Prev = first | last | event_key()
+%% Limit = done() | forward() | backward()
+%% collector_pid() = pid()
+%% table_handle() = record(table_handle)
+%% event_key() =
+%% done() = 0
+%% forward() = infinity | integer(X) where X > 0
+%% backward() = '-infinity' | integer(X) where X < 0
+%% Fun = fun(Event, Acc) -> NewAcc
+%% Acc = NewAcc = term()
+%%----------------------------------------------------------------------
+
+iterate(_, _, Limit, _, Acc) when Limit == 0 ->
+ Acc;
+iterate(CollectorPid, Prev, Limit, Fun, Acc) when pid(CollectorPid) ->
+ case get_table_handle(CollectorPid) of
+ {ok, TH} when record(TH, table_handle) ->
+ iterate(TH, Prev, Limit, Fun, Acc);
+ {error, Reason} ->
+ exit(Reason)
+ end;
+iterate(TH, Prev, Limit, Fun, Acc) when record(TH, table_handle) ->
+ if
+ Limit == infinity ->
+ next_iterate(TH, Prev, Limit, Fun, Acc);
+ integer(Limit), Limit > 0 ->
+ next_iterate(TH, Prev, Limit, Fun, Acc);
+ Limit == '-infinity' ->
+ prev_iterate(TH, Prev, Limit, Fun, Acc);
+ integer(Limit), Limit < 0 ->
+ prev_iterate(TH, Prev, Limit, Fun, Acc)
+ end.
+
+next_iterate(TH, Prev = first, Limit, Fun, Acc) ->
+ Tab = TH#table_handle.event_tab,
+ case catch ets:first(Tab) of
+ '$end_of_table' ->
+ Acc;
+ {'EXIT', _} = Error ->
+ io:format("~p(~p): First ~p~n", [?MODULE, ?LINE, Error]),
+ iterate(TH#table_handle.collector_pid, Prev, Limit, Fun, Acc);
+ First ->
+ lookup_and_apply(TH, Prev, First, Limit, -1, Fun, Acc)
+ end;
+next_iterate(TH, Prev = last, Limit, Fun, Acc) ->
+ Tab = TH#table_handle.event_tab,
+ case catch ets:last(Tab) of
+ '$end_of_table' ->
+ Acc;
+ {'EXIT', _} = Error ->
+ io:format("~p(~p): Last ~p~n", [?MODULE, ?LINE, Error]),
+ iterate(TH#table_handle.collector_pid, Prev, Limit, Fun, Acc);
+ Last ->
+ lookup_and_apply(TH, Prev, Last, Limit, -1, Fun, Acc)
+ end;
+next_iterate(TH, Prev, Limit, Fun, Acc) ->
+ Tab = TH#table_handle.event_tab,
+ Key = make_key(TH, Prev),
+ case catch ets:next(Tab, Key) of
+ '$end_of_table' ->
+ Acc;
+ {'EXIT', _} = Error ->
+ io:format("~p(~p): Next ~p -> ~p~n", [?MODULE, ?LINE, Key, Error]),
+ iterate(TH#table_handle.collector_pid, Prev, Limit, Fun, Acc);
+ Next ->
+ lookup_and_apply(TH, Prev, Next, Limit, -1, Fun, Acc)
+ end.
+
+prev_iterate(TH, Prev = first, Limit, Fun, Acc) ->
+ Tab = TH#table_handle.event_tab,
+ case catch ets:first(Tab) of
+ '$end_of_table' ->
+ Acc;
+ {'EXIT', _} = Error ->
+ io:format("~p(~p): First ~p~n", [?MODULE, ?LINE, Error]),
+ iterate(TH#table_handle.collector_pid, Prev, Limit, Fun, Acc);
+ First ->
+ lookup_and_apply(TH, Prev, First, Limit, 1, Fun, Acc)
+ end;
+prev_iterate(TH, Prev = last, Limit, Fun, Acc) ->
+ Tab = TH#table_handle.event_tab,
+ case catch ets:last(Tab) of
+ '$end_of_table' ->
+ Acc;
+ {'EXIT', _} = Error ->
+ io:format("~p(~p): Last ~p~n", [?MODULE, ?LINE, Error]),
+ iterate(TH#table_handle.collector_pid, Prev, Limit, Fun, Acc);
+ Last ->
+ lookup_and_apply(TH, Prev, Last, Limit, 1, Fun, Acc)
+ end;
+prev_iterate(TH, Prev, Limit, Fun, Acc) ->
+ Tab = TH#table_handle.event_tab,
+ Key = make_key(TH, Prev),
+ case catch ets:prev(Tab, Key) of
+ '$end_of_table' ->
+ Acc;
+ {'EXIT', _} = Error ->
+ io:format("~p(~p): Prev ~p -> ~p~n", [?MODULE, ?LINE, Key, Error]),
+ iterate(TH#table_handle.collector_pid, Prev, Limit, Fun, Acc);
+ Next ->
+ lookup_and_apply(TH, Prev, Next, Limit, 1, Fun, Acc)
+ end.
+
+lookup_and_apply(TH, _Prev, Next, Limit, Incr, Fun, _Acc) when Fun == undefined ->
+ Limit2 = incr(Limit, Incr),
+ iterate(TH, Next, Limit2, Fun, Next);
+lookup_and_apply(TH, Prev, Next, Limit, Incr, Fun, Acc) ->
+ Tab = TH#table_handle.event_tab,
+ case catch ets:lookup_element(Tab, Next, 2) of
+ {'EXIT', _} ->
+ iterate(TH#table_handle.collector_pid, Prev, Limit, Fun, Acc);
+ E when record(E, event) ->
+ Acc2 = Fun(E, Acc),
+ Limit2 = incr(Limit, Incr),
+ iterate(TH, Next, Limit2, Fun, Acc2)
+ end.
+
+incr(Val, Incr) ->
+ if
+ Val == infinity -> Val;
+ Val == '-infinity' -> Val;
+ integer(Val) -> Val + Incr
+ end.
+
+%%----------------------------------------------------------------------
+%% clear_table(Handle) -> ok
+%%
+%% Clear the event table
+%%
+%% Handle = collector_pid() | table_handle()
+%% collector_pid() = pid()
+%% table_handle() = record(table_handle)
+%%----------------------------------------------------------------------
+
+clear_table(CollectorPid) when pid(CollectorPid) ->
+ call(CollectorPid, clear_table);
+clear_table(TH) when record(TH, table_handle) ->
+ clear_table(TH#table_handle.collector_pid).
+
+call(CollectorPid, Request) ->
+ gen_server:call(CollectorPid, Request, infinity).
+
+%%%----------------------------------------------------------------------
+%%% Callback functions from gen_server
+%%%----------------------------------------------------------------------
+
+%%----------------------------------------------------------------------
+%% Func: init/1
+%% Returns: {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%%----------------------------------------------------------------------
+
+init([InitialS, Dict]) ->
+ process_flag(trap_exit, true),
+ case InitialS#state.parent_pid of
+ undefined ->
+ ignore;
+ Pid when pid(Pid) ->
+ link(Pid)
+ end,
+ Funs = [fun init_tables/1,
+ fun init_global/1,
+ fun(S) -> lists:foldl(fun do_dict_insert/2, S, Dict) end],
+ {ok, lists:foldl(fun(F, S) -> F(S) end, InitialS, Funs)}.
+
+init_tables(S) ->
+ EventTab = ets:new(et_events, [ordered_set, {keypos, 1}, public]),
+ DictTab = ets:new(et_dict, [ordered_set, {keypos, 1}, public]),
+ S#state{event_tab = EventTab, dict_tab = DictTab}.
+
+init_global(S) ->
+ case S#state.trace_global of
+ true ->
+ EventFun = fun(Event, {ok, TH}) -> report(TH, Event) end,
+ EndFun = fun(Acc) -> Acc end,
+ Spec = trace_spec_wrapper(EventFun, EndFun, {ok, self()}),
+ dbg:tracer(process, Spec),
+ et_selector:change_pattern(S#state.trace_pattern),
+ net_kernel:monitor_nodes(true),
+ lists:foreach(fun(N) -> self() ! {nodeup, N} end, nodes()),
+ S#state{trace_nodes = [node()]};
+ false ->
+ S
+ end.
+
+%%----------------------------------------------------------------------
+%% Func: handle_call/3
+%% Returns: {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} | (terminate/2 is called)
+%% {stop, Reason, State} (terminate/2 is called)
+%%----------------------------------------------------------------------
+
+handle_call({multicast, Msg}, _From, S) ->
+ do_multicast(S#state.subscribers, Msg),
+ {reply, ok, S};
+
+handle_call(Msg = {dict_insert, _Key, _Val}, _From, S) ->
+ S2 = do_dict_insert(Msg, S),
+ {reply, ok, S2};
+
+handle_call(Msg = {dict_delete, _Key}, _From, S) ->
+ S2 = do_dict_delete(Msg, S),
+ {reply, ok, S2};
+
+handle_call({dict_lookup, Key}, _From, S) ->
+ Reply = ets:lookup(S#state.dict_tab, Key),
+ {reply, Reply, S};
+
+handle_call({dict_match, Pattern}, _From, S) ->
+ case catch ets:match_object(S#state.dict_tab, Pattern) of
+ {'EXIT', _Reason} ->
+ {reply, [], S};
+ Matching ->
+ {reply, Matching, S}
+ end;
+
+handle_call(get_table_handle, _From, S) ->
+ [{_, TableFilter}] = ets:lookup(S#state.dict_tab, {filter, collector}),
+ TH = #table_handle{collector_pid = self(),
+ event_tab = S#state.event_tab,
+ event_order = S#state.event_order,
+ filter = TableFilter},
+ {reply, {ok, TH}, S};
+
+handle_call(close, _From, S) ->
+ case S#state.file of
+ undefined ->
+ {reply, {error, file_not_open}, S};
+ F ->
+ Reply = disk_log:close(F#file.desc),
+ S2 = S#state{file = undefined},
+ {reply, Reply, S2}
+ end;
+handle_call({save_event_file, FileName, Options}, _From, S) ->
+ Default = #file{name = FileName,
+ event_opt = existing,
+ file_opt = write,
+ table_opt = keep},
+ case parse_file_options(Default, Options) of
+ {ok, F} when record(F, file) ->
+ case file_open(F) of
+ {ok, Fd} ->
+ F2 = F#file{desc = Fd},
+ {Reply2, S3} =
+ case F2#file.event_opt of
+ %% new ->
+ %% Reply = ok,
+ %% S2 = S#state{file = F},
+ %% {Reply, S2};
+ %%
+ %% insert() ->
+ %% case S2#state.file of
+ %% undefined ->
+ %% ignore;
+ %% F ->
+ %% Fd = F#file.desc,
+ %% ok = disk_log:log(Fd, Event)
+ %% end.
+ existing ->
+ Fun = fun({_, E}, A) -> ok = disk_log:log(Fd, E), A end,
+ Tab = S#state.event_tab,
+ Reply = tab_iterate(Fun, Tab, ets:first(Tab), ok),
+ disk_log:close(Fd),
+ {Reply, S}
+ %% all ->
+ %% Reply = tab_iterate(WriteFun, Tab, ok),
+ %% S2 = S#state{file = F},
+ %% {Reply, S2}
+ end,
+ case F2#file.table_opt of
+ keep ->
+ {reply, Reply2, S3};
+ clear ->
+ S4 = do_clear_table(S3),
+ {reply, Reply2, S4}
+ end;
+ {error, Reason} ->
+ {reply, {error, {file_open, Reason}}, S}
+ end;
+ {error, Reason} ->
+ {reply, {error, Reason}, S}
+ end;
+
+handle_call({change_pattern, Pattern}, _From, S) ->
+ Ns = S#state.trace_nodes,
+ rpc:multicall(Ns, et_selector, change_pattern, [Pattern]),
+ Reply = {old_pattern, S#state.trace_pattern},
+ S2 = S#state{trace_pattern = Pattern},
+ {reply, Reply, S2};
+
+handle_call(clear_table, _From, S) ->
+ S2 = do_clear_table(S),
+ {reply, ok, S2};
+
+handle_call(stop, _From, S) ->
+ do_multicast(S#state.subscribers, close),
+ case S#state.trace_global of
+ true -> rpc:multicall(S#state.trace_nodes, dbg, stop, []);
+ false -> ignore
+ end,
+ {stop, shutdown, ok, S};
+handle_call(Request, From, S) ->
+ ok = error_logger:format("~p(~p): handle_call(~p, ~p, ~p)~n",
+ [?MODULE, self(), Request, From, S]),
+ {reply, {error, {bad_request, Request}}, S}.
+
+%%----------------------------------------------------------------------
+%% Func: handle_cast/2
+%% Returns: {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State} (terminate/2 is called)
+%%----------------------------------------------------------------------
+
+handle_cast(Msg, S) ->
+ ok = error_logger:format("~p(~p): handle_cast(~p, ~p)~n",
+ [?MODULE, self(), Msg, S]),
+ {noreply, S}.
+
+%%----------------------------------------------------------------------
+%% Func: handle_info/2
+%% Returns: {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State} (terminate/2 is called)
+%%----------------------------------------------------------------------
+
+handle_info({nodeup, Node}, S) ->
+ Port = S#state.trace_port,
+ MaxQueue = S#state.trace_max_queue,
+ case rpc:call(Node, ?MODULE, start_trace_port, [{Port, MaxQueue}]) of
+ {ok, _} ->
+ listen_on_trace_port(Node, Port, S);
+ {error, Reason} when Reason == already_started->
+ ok = error_logger:format("~p(~p): producer ignored(~p:~p):~n ~p~n",
+ [?MODULE, self(), Node, Port, Reason]),
+ S2 = S#state{trace_port = Port + 1},
+ {noreply, S2};
+ {badrpc, Reason} ->
+ ok = error_logger:format("~p(~p): producer ignored(~p:~p):~n ~p~n",
+ [?MODULE, self(), Node, Port, Reason]),
+ S2 = S#state{trace_port = Port + 1},
+ {noreply, S2};
+ {error, Reason} ->
+ self() ! {nodeup, Node},
+ ok = error_logger:format("~p(~p): producer retry(~p:~p):~n ~p~n",
+ [?MODULE, self(), Node, Port, Reason]),
+ S2 = S#state{trace_port = Port + 1},
+ {noreply, S2}
+ end;
+
+handle_info({nodedown, Node}, S) ->
+ {noreply, S#state{trace_nodes = S#state.trace_nodes -- [Node]}};
+
+handle_info({register_trace_client, Pid}, S) ->
+ link(Pid),
+ {noreply, S};
+
+handle_info({'EXIT', Pid, Reason}, S) when Pid == S#state.parent_pid ->
+ {stop, Reason, S};
+handle_info(Info = {'EXIT', Pid, _Reason}, S) ->
+ OldSubscribers = S#state.subscribers,
+ case lists:member(Pid, OldSubscribers) of
+ true ->
+ S2 = do_dict_delete({dict_delete, {subscriber, Pid}}, S),
+ {noreply, S2};
+ false ->
+ ok = error_logger:format("~p(~p): handle_info(~p, ~p)~n",
+ [?MODULE, self(), Info, S]),
+ {noreply, S}
+ end;
+handle_info(Info, S) ->
+ ok = error_logger:format("~p(~p): handle_info(~p, ~p)~n",
+ [?MODULE, self(), Info, S]),
+ {noreply, S}.
+
+listen_on_trace_port(Node, Port, S) ->
+ [_Name, Host] = string:tokens(atom_to_list(Node), [$@]),
+ case catch start_trace_client(self(), ip, {Host, Port}) of
+ {trace_client_pid, RemotePid} ->
+ rpc:call(Node, et_selector, change_pattern, [S#state.trace_pattern]),
+ link(RemotePid),
+ S2 = S#state{trace_nodes = [Node | S#state.trace_nodes],
+ trace_port = Port + 1},
+ {noreply, S2};
+ {'EXIT', Reason} when Reason == already_started->
+ ok = error_logger:format("~p(~p): consumer ignored(~p:~p): ~p~n",
+ [?MODULE, self(), Node, Port, Reason]),
+ S2 = S#state{trace_port = Port + 1},
+ {noreply, S2};
+ {'EXIT', Reason} ->
+ self() ! {nodeup, Node},
+ ok = error_logger:format("~p(~p): consumer retry(~p:~p):~n ~p~n",
+ [?MODULE, self(), Node, Port, Reason]),
+ S2 = S#state{trace_port = Port + 1},
+ {noreply, S2}
+ end.
+
+%%----------------------------------------------------------------------
+%% Func: terminate/2
+%% Purpose: Shutdown the server
+%% Returns: any (ignored by gen_server)
+%%----------------------------------------------------------------------
+
+terminate(Reason, S) ->
+ Fun = fun(Pid) -> exit(Pid, Reason) end,
+ lists:foreach(Fun, S#state.subscribers).
+
+%%----------------------------------------------------------------------
+%% Func: code_change/3
+%% Purpose: Convert process state when code is changed
+%% Returns: {ok, NewState}
+%%----------------------------------------------------------------------
+
+code_change(_OldVsn, S, _Extra) ->
+ {ok, S}.
+
+%%%----------------------------------------------------------------------
+%%% Internal functions
+%%%----------------------------------------------------------------------
+
+do_clear_table(S) ->
+ OldTab = S#state.event_tab,
+ ets:delete(OldTab),
+ NewTab = ets:new(et_events, [ordered_set, {keypos, 1}, public]),
+ S#state{event_tab = NewTab}.
+
+do_dict_insert(Msg = {dict_insert, Key = {subscriber, Pid}, Val}, S) when pid(Pid) ->
+ OldSubscribers = S#state.subscribers,
+ NewSubscribers =
+ case lists:member(Pid, OldSubscribers) of
+ true ->
+ OldSubscribers;
+ false ->
+ link(Pid),
+ All = ets:match_object(S#state.dict_tab, '_'),
+ lists:foreach(fun({K, V}) -> Pid ! {et, {dict_insert, K, V}} end, All),
+ [Pid | OldSubscribers]
+ end,
+ do_multicast(NewSubscribers, Msg),
+ ets:insert(S#state.dict_tab, {Key, Val}),
+ S#state{subscribers = NewSubscribers};
+do_dict_insert(Msg = {dict_insert, Key, Val}, S) ->
+ do_multicast(S#state.subscribers, Msg),
+ ets:insert(S#state.dict_tab, {Key, Val}),
+ S.
+
+do_dict_delete(Msg = {dict_delete, Key = {subscriber, Pid}}, S) ->
+ OldSubscribers = S#state.subscribers,
+ do_multicast(OldSubscribers, Msg),
+ ets:delete(S#state.dict_tab, Key),
+ case lists:member(Pid, OldSubscribers) of
+ true ->
+ unlink(Pid),
+ S#state{subscribers = OldSubscribers -- [Pid]};
+ false ->
+ S
+ end;
+do_dict_delete({dict_delete, {filter, collector}}, S) ->
+ S;
+do_dict_delete(Msg = {dict_delete, Key}, S) ->
+ do_multicast(S#state.subscribers, Msg),
+ ets:delete(S#state.dict_tab, Key),
+ S.
+
+tab_iterate(_Fun, _Tab, '$end_of_table', Acc) ->
+ Acc;
+tab_iterate(Fun, Tab, Key, Acc) ->
+ Acc2 = lists:foldl(Fun, Acc, ets:lookup(Tab, Key)),
+ tab_iterate(Fun, Tab, ets:next(Tab, Key), Acc2).
+
+file_open(F) ->
+ Fd = make_ref(),
+ case F#file.file_opt of
+ write -> file:rename(F#file.name, F#file.name ++ ".OLD");
+ append -> ignore
+ end,
+ Args = [{file, F#file.name}, {name, Fd},
+ {repair, true}, {mode, read_write}],
+ case disk_log:open(Args) of
+ {ok, _} ->
+ {ok, Fd};
+ {repaired, _, _, BadBytes} ->
+ ok = error_logger:format("~p: Skipped ~p bad bytes in file: ~p~n",
+ [?MODULE, BadBytes, F#file.name]),
+ {ok, Fd};
+ {error,Reason} ->
+ {error,Reason}
+ end.
+
+parse_file_options(F, [H | T]) ->
+ case H of
+ existing -> parse_file_options(F#file{event_opt = existing} , T);
+ %%new -> parse_file_options(F#file{event_opt = new} , T);
+ all -> parse_file_options(F#file{event_opt = all} , T);
+ write -> parse_file_options(F#file{file_opt = write} , T);
+ append -> parse_file_options(F#file{file_opt = append} , T);
+ keep -> parse_file_options(F#file{table_opt = keep} , T);
+ clear -> parse_file_options(F#file{table_opt = clear} , T);
+ Bad -> {error, {bad_file_option, Bad}}
+ end;
+parse_file_options(F, []) ->
+ {ok, F}.
+
+do_multicast([Pid | Pids], Msg) ->
+ Pid ! {et, Msg},
+ do_multicast(Pids, Msg);
+do_multicast([], _Msg) ->
+ ok.
diff --git a/lib/et/src/et_contents_viewer.erl b/lib/et/src/et_contents_viewer.erl
new file mode 100644
index 0000000000..29ca93ca64
--- /dev/null
+++ b/lib/et/src/et_contents_viewer.erl
@@ -0,0 +1,591 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2000-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%
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Displays details of a trace event
+%%----------------------------------------------------------------------
+
+-module(et_contents_viewer).
+
+-behaviour(gen_server).
+
+%% External exports
+-export([start_link/1,
+ stop/1]).
+
+%% gen_server callbacks
+-export([init/1, terminate/2, code_change/3,
+ handle_call/3, handle_cast/2, handle_info/2]).
+
+-include("../include/et.hrl").
+-include("et_internal.hrl").
+
+-record(state, {parent_pid, % Pid of parent process
+ viewer_pid, % Pid of viewer process
+ event_order, % Field to be used as primary key
+ event, % The original event
+ filtered_event, % Event processed by active filter
+ active_filter, % Name of the active filter
+ filters, % List of possible filters
+ win, % GUI: Window object
+ packer, % GUI: Packer object
+ width, % GUI: Window width
+ height}). % GUI: Window height
+
+%%%----------------------------------------------------------------------
+%%% Client side
+%%%----------------------------------------------------------------------
+
+%%----------------------------------------------------------------------
+%% start_link(Options) -> {ok, ContentsPid} | {error, Reason}
+%%
+%% Start an viewer for the event contents as window in GS
+%%
+%% Options = [option()]
+%%
+%% option() =
+%%
+%% {parent_pid, pid()} | % Pid of parent process
+%% {viewer_pid, pid()} | % Pid of viewer process
+%% {event_order, event_order()} | % Field to be used as primary key
+%% {active_filter, atom()} | % Name of the active filter
+%% {filter, atom(), fun()} % A named filter fun
+%%
+%% event_order() = 'trace_ts' | 'event_ts'
+%% ContentsPid = pid()
+%% Reason = term()
+%%----------------------------------------------------------------------
+
+start_link(Options) ->
+ case parse_opt(Options, default_state()) of
+ {ok, S} ->
+ case gen_server:start_link(?MODULE, [S], []) of
+ {ok, ContentsPid} when S#state.parent_pid /= self() ->
+ unlink(ContentsPid),
+ {ok, ContentsPid};
+ Other ->
+ Other
+ end;
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
+default_state() ->
+ #state{parent_pid = self(),
+ viewer_pid = undefined,
+ active_filter = collector,
+ filters = [#filter{name = collector, function = fun(E) -> E end}],
+ width = 600,
+ height = 300}.
+
+parse_opt([], S) ->
+ Name = S#state.active_filter,
+ Filters = S#state.filters,
+ if
+ S#state.event == undefined ->
+ {error, {badarg, no_event}};
+ atom(Name) ->
+ case lists:keysearch(Name, #filter.name, Filters) of
+ {value, F} when record(F, filter) ->
+ {ok, S#state{active_filter = Name}};
+ false ->
+ {error, {badarg, {no_such_filter, Name, Filters}}}
+ end
+ end;
+parse_opt([H | T], S) ->
+ case H of
+ {parent_pid, ParentPid} when pid(ParentPid) ->
+ parse_opt(T, S#state{parent_pid = ParentPid});
+ {viewer_pid, ViewerPid} when pid(ViewerPid) ->
+ parse_opt(T, S#state{viewer_pid = ViewerPid});
+ {event_order, trace_ts} ->
+ parse_opt(T, S#state{event_order = trace_ts});
+ {event_order, event_ts} ->
+ parse_opt(T, S#state{event_order = event_ts});
+ {event, Event} when record(Event, event) ->
+ parse_opt(T, S#state{event = Event});
+ {active_filter, Name} when atom(Name) ->
+ parse_opt(T, S#state{active_filter = Name});
+ F when record(F, filter),
+ atom(F#filter.name),
+ function(F#filter.function) ->
+ Filters = lists:keydelete(F#filter.name, #filter.name, S#state.filters),
+ Filters2 = lists:keysort(#filter.name, [F | Filters]),
+ parse_opt(T, S#state{filters = Filters2});
+ {width, Width} when integer(Width), Width > 0 ->
+ parse_opt(T, S#state{width = Width});
+ {height, Height} when integer(Height), Height > 0 ->
+ parse_opt(T, S#state{height = Height});
+ Bad ->
+ {error, {bad_option, Bad}}
+ end;
+parse_opt(BadList, _S) ->
+ {error, {bad_option_list, BadList}}.
+
+%%----------------------------------------------------------------------
+%% stop(ContentsPid) -> ok
+%%
+%% Stops a contents viewer process
+%%
+%% ContentsPid = pid()
+%%----------------------------------------------------------------------
+
+stop(ContentsPid) ->
+ unlink(ContentsPid),
+ call(ContentsPid, stop).
+
+call(ContentsPid, Request) ->
+ gen_server:call(ContentsPid, Request, infinity).
+
+%%%----------------------------------------------------------------------
+%%% Callback functions from gen_server
+%%%----------------------------------------------------------------------
+
+%%----------------------------------------------------------------------
+%% Func: init/1
+%% Returns: {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%%----------------------------------------------------------------------
+
+init([S]) when record(S, state) ->
+ process_flag(trap_exit, true),
+ S2 = create_window(S),
+ {ok, S2}.
+
+%%----------------------------------------------------------------------
+%% Func: handle_call/3
+%% Returns: {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} | (terminate/2 is called)
+%% {stop, Reason, State} (terminate/2 is called)
+%%----------------------------------------------------------------------
+
+handle_call(stop, _From, S) ->
+ unlink(S#state.parent_pid),
+ {stop, shutdown, ok, S};
+handle_call(Request, From, S) ->
+ ok = error_logger:format("~p(~p): handle_call(~p, ~p, ~p)~n",
+ [?MODULE, self(), Request, From, S]),
+ Reply = {error, {bad_request, Request}},
+ {reply, Reply, S}.
+
+%%----------------------------------------------------------------------
+%% Func: handle_cast/2
+%% Returns: {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State} (terminate/2 is called)
+%%----------------------------------------------------------------------
+
+handle_cast(Msg, S) ->
+ ok = error_logger:format("~p(~p): handle_cast(~p, ~p)~n",
+ [?MODULE, self(), Msg, S]),
+ {noreply, S}.
+
+%%----------------------------------------------------------------------
+%% Func: handle_info/2
+%% Returns: {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State} (terminate/2 is called)
+%%----------------------------------------------------------------------
+
+handle_info({gs, Button, click, Data, _Other}, S) ->
+ case Button of
+ close ->
+ gs:destroy(S#state.win),
+ {stop, normal, S};
+ save ->
+ Event = S#state.event,
+ Bin = list_to_binary(event_to_string(Event, S#state.event_order)),
+ TimeStamp =
+ case S#state.event_order of
+ trace_ts -> Event#event.trace_ts;
+ event_ts -> Event#event.event_ts
+ end,
+ FileName = ["et_contents_viewer_", now_to_string(TimeStamp), ".save"],
+ file:write_file(lists:flatten(FileName), Bin),
+ {noreply, S};
+ _PopupMenuItem when record(Data, filter) ->
+ F = Data,
+ ChildState= S#state{active_filter = F#filter.name},
+ case gen_server:start_link(?MODULE, [ChildState], []) of
+ {ok, Pid} when S#state.parent_pid /= self() ->
+ unlink(Pid),
+ {noreply, S};
+ _ ->
+ {noreply, S}
+ end;
+ {hide, Actors} ->
+ send_viewer_event(S, {delete_actors, Actors}),
+ {noreply, S};
+ {show, Actors} ->
+ send_viewer_event(S, {insert_actors, Actors}),
+ {noreply, S};
+ {mode, Mode} ->
+ send_viewer_event(S, {mode, Mode}),
+ {noreply, S};
+ Nyi ->
+ ok = error_logger:format("~p: click ~p ignored (nyi)~n",
+ [?MODULE, Nyi]),
+ {noreply, S}
+ end;
+handle_info({gs, _Obj, destroy,_, _}, S) ->
+ unlink(S#state.parent_pid),
+ gs:destroy(S#state.win),
+ {stop, normal, S};
+handle_info({gs, _Obj, keypress, _, [KeySym, _Keycode, _Shift, _Control | _]}, S) ->
+ case KeySym of
+ 'c' ->
+ gs:destroy(S#state.win),
+ {stop, normal, S};
+
+ 'f' ->
+ E = S#state.filtered_event,
+ From = E#event.from,
+ send_viewer_event(S, {delete_actors, [From]}),
+ {noreply, S};
+ 't' ->
+ E = S#state.filtered_event,
+ To = E#event.to,
+ send_viewer_event(S, {delete_actors, [To]}),
+ {noreply, S};
+ 'b' ->
+ E = S#state.filtered_event,
+ From = E#event.from,
+ To = E#event.to,
+ send_viewer_event(S, {delete_actors, [From, To]}),
+ {noreply, S};
+
+ 'F' ->
+ E = S#state.filtered_event,
+ From = E#event.from,
+ send_viewer_event(S, {insert_actors, [From]}),
+ {noreply, S};
+ 'T' ->
+ E = S#state.filtered_event,
+ To = E#event.to,
+ send_viewer_event(S, {insert_actors, [To]}),
+ {noreply, S};
+ 'B' ->
+ E = S#state.filtered_event,
+ From = E#event.from,
+ To = E#event.to,
+ send_viewer_event(S, {insert_actors, [From, To]}),
+ {noreply, S};
+
+ 's' ->
+ E = S#state.filtered_event,
+ From = E#event.from,
+ To = E#event.to,
+ First = et_collector:make_key(S#state.event_order, E),
+ Mode = {search_actors, forward, First, [From, To]},
+ send_viewer_event(S, {mode, Mode}),
+ {noreply, S};
+ 'r' ->
+ E = S#state.filtered_event,
+ From = E#event.from,
+ To = E#event.to,
+ First = et_collector:make_key(S#state.event_order, E),
+ Mode = {search_actors, reverse, First, [From, To]},
+ send_viewer_event(S, {mode, Mode}),
+ {noreply, S};
+ 'a' ->
+ send_viewer_event(S, {mode, all}),
+ {noreply, S};
+
+ 0 ->
+ case lists:keysearch(collector, #filter.name, S#state.filters) of
+ {value, F} when record(F, filter) ->
+ ChildState= S#state{active_filter = F#filter.name},
+ case gen_server:start_link(?MODULE, [ChildState], []) of
+ {ok, Pid} when S#state.parent_pid /= self() ->
+ unlink(Pid);
+ _ ->
+ ignore
+ end;
+ false ->
+ ignore
+ end,
+ {noreply, S};
+ Int when integer(Int), Int > 0, Int =< 9 ->
+ case catch lists:nth(Int, S#state.filters) of
+ F when record(F, filter) ->
+ ChildState= S#state{active_filter = F#filter.name},
+ case gen_server:start_link(?MODULE, [ChildState], []) of
+ {ok, Pid} when S#state.parent_pid /= self() ->
+ unlink(Pid);
+ _ ->
+ ignore
+ end;
+ {'EXIT', _} ->
+ ignore
+ end,
+ {noreply, S};
+
+ 'Shift_L' ->
+ {noreply, S};
+ 'Shift_R' ->
+ {noreply, S};
+ 'Caps_Lock' ->
+ {noreply, S};
+ _ ->
+ io:format("~p: ignored: ~p~n", [?MODULE, KeySym]),
+ {noreply, S}
+ end;
+handle_info({gs, _Obj, configure, [], [W, H | _]}, S) ->
+ gs:config(S#state.packer, [{width, W},{height, H}]),
+ S2 = S#state{width = W, height = H},
+ {noreply, S2};
+handle_info({'EXIT', Pid, Reason}, S) ->
+ if
+ Pid == S#state.parent_pid ->
+ unlink(Pid),
+ {stop, Reason, S};
+ true ->
+ {noreply, S}
+ end;
+handle_info(Info, S) ->
+ ok = error_logger:format("~p(~p): handle_info(~p, ~p)~n",
+ [?MODULE, self(), Info, S]),
+ {noreply, S}.
+
+%%----------------------------------------------------------------------
+%% Func: terminate/2
+%% Purpose: Shutdown the server
+%% Returns: any (ignored by gen_server)
+%%----------------------------------------------------------------------
+
+terminate(_Reason, _S) ->
+ ignore.
+
+%%----------------------------------------------------------------------
+%% Func: code_change/3
+%% Purpose: Convert process state when code is changed
+%% Returns: {ok, NewState}
+%%----------------------------------------------------------------------
+
+code_change(_OldVsn, S, _Extra) ->
+ {ok, S}.
+
+%%%----------------------------------------------------------------------
+%%% Handle graphics
+%%%----------------------------------------------------------------------
+
+create_window(S) ->
+ H = S#state.height,
+ W = S#state.width,
+ Name = S#state.active_filter,
+ Title = lists:concat([?MODULE, " (filter: ", Name, ")"]),
+ WinOpt = [{title, Title}, {configure, true},
+ {width, W}, {height, H}],
+ GS = gs:start(),
+ Win = gs:window(GS, WinOpt),
+ Bar = gs:menubar(Win, []),
+ create_file_menu(Bar),
+ PackerOpt = [{packer_x, [{stretch, 1}]},
+ {packer_y, [{stretch, 1}, {fixed, 25}]},
+ {x, 0}, {y, 25}],
+ Packer = gs:frame(Win, PackerOpt),
+ EditorOpt = [{pack_xy, {1, 1}}, {vscroll, right}, {hscroll, bottom},
+ {wrap, none},
+ {bg, lightblue}, {font, {courier, 12}}],
+ Editor = gs:editor(Packer, EditorOpt),
+ FilteredEvent = config_editor(Editor, S),
+ S2 = S#state{win = Win, packer = Packer, filtered_event = FilteredEvent},
+ create_hide_menu(Bar, S2),
+ create_search_menu(Bar, S2),
+ create_filter_menu(Bar, S#state.filters),
+ gs:config(Packer, [{width, W}, {height, H}]),
+ gs:config(Win, [{map,true}, {keypress, true}]),
+ S2.
+
+create_file_menu(Bar) ->
+ Button = gs:menubutton(Bar, [{label, {text, "File"}}]),
+ Menu = gs:menu(Button, []),
+ gs:menuitem(close, Menu, [{label, {text,"Close (c)"}}]),
+ gs:menuitem(save, Menu, [{label, {text,"Save"}}]).
+
+create_filter_menu(Bar, Filters) ->
+ Button = gs:menubutton(Bar, [{label, {text, "Filters"}}]),
+ Menu = gs:menu(Button, []),
+ gs:menuitem(Menu, [{label, {text, "Select Filter"}}, {bg, lightblue}, {enable, false}]),
+ gs:menuitem(Menu, [{itemtype, separator}]),
+ Item = fun(F, N) when F#filter.name == collector->
+ Label = lists:concat([pad_string(F#filter.name, 20), "(0)"]),
+ gs:menuitem(Menu, [{label, {text, Label}}, {data, F}]),
+ N + 1;
+ (F, N) ->
+ Name = F#filter.name,
+ Label = lists:concat([pad_string(Name, 20), "(", N, ")"]),
+ gs:menuitem(Menu, [{label, {text, Label}}, {data, F}]),
+ N + 1
+ end,
+ Filters2 = lists:keysort(#filter.name, Filters),
+ lists:foldl(Item, 1, Filters2),
+ Menu.
+
+create_hide_menu(Bar, S) ->
+ Button = gs:menubutton(Bar, [{label, {text, "Hide"}}]),
+ Menu = gs:menu(Button, []),
+ E = S#state.filtered_event,
+ From = E#event.from,
+ To = E#event.to,
+ if
+ S#state.viewer_pid == undefined ->
+ ignore;
+ From == To ->
+ gs:menuitem(Menu, [{label, {text, "Hide actor in Viewer "}}, {bg, lightblue}, {enable, false}]),
+ gs:menuitem(Menu, [{itemtype, separator}]),
+ gs:menuitem({hide, [From]}, Menu, [{label, {text,"From=To (f|t|b)"}}]),
+ gs:menuitem(Menu, [{itemtype, separator}]),
+ gs:menuitem(Menu, [{label, {text, "Show actor in Viewer "}}, {bg, lightblue}, {enable, false}]),
+ gs:menuitem(Menu, [{itemtype, separator}]),
+ gs:menuitem({show, [From]}, Menu, [{label, {text,"From=To (F|T|B)"}}]);
+ true ->
+ gs:menuitem(Menu, [{label, {text, "Hide actor in Viewer "}}, {bg, lightblue}, {enable, false}]),
+ gs:menuitem(Menu, [{itemtype, separator}]),
+ gs:menuitem({hide, [From]}, Menu, [{label, {text,"From (f)"}}]),
+ gs:menuitem({hide, [To]}, Menu, [{label, {text,"To (t)"}}]),
+ gs:menuitem({hide, [From, To]}, Menu, [{label, {text,"Both (b)"}}]),
+ gs:menuitem(Menu, [{itemtype, separator}]),
+ gs:menuitem(Menu, [{label, {text, "Show actor in Viewer "}}, {bg, lightblue}, {enable, false}]),
+ gs:menuitem(Menu, [{itemtype, separator}]),
+ gs:menuitem({show, [From]}, Menu, [{label, {text,"From (F)"}}]),
+ gs:menuitem({show, [To]}, Menu, [{label, {text,"To (T)"}}]),
+ gs:menuitem({show, [From, To]}, Menu, [{label, {text,"Both (B)"}}])
+ end.
+
+create_search_menu(Bar, S) ->
+ Button = gs:menubutton(Bar, [{label, {text, "Search"}}]),
+ Menu = gs:menu(Button, []),
+ E = S#state.filtered_event,
+ From = E#event.from,
+ To = E#event.to,
+ gs:menuitem(Menu, [{label, {text, "Search in Viewer "}},
+ {bg, lightblue}, {enable, false}]),
+ gs:menuitem(Menu, [{itemtype, separator}]),
+ if
+ S#state.viewer_pid == undefined ->
+ S;
+ From == To ->
+ Key = et_collector:make_key(S#state.event_order, E),
+ ModeS = {search_actors, forward, Key, [From]},
+ ModeR = {search_actors, reverse, Key, [From]},
+ gs:menuitem({mode, ModeS}, Menu, [{label, {text,"Forward from this event (s)"}}]),
+ gs:menuitem({mode, ModeR}, Menu, [{label, {text,"Reverse from this event (r)"}}]);
+ true ->
+ Key = et_collector:make_key(S#state.event_order, E),
+ ModeS = {search_actors, forward, Key, [From, To]},
+ ModeR = {search_actors, reverse, Key, [From, To]},
+ gs:menuitem({mode, ModeS}, Menu, [{label, {text,"Forward from this event (s)"}}]),
+ gs:menuitem({mode, ModeR}, Menu, [{label, {text,"Reverse from this event (r)"}}])
+ end,
+ gs:menuitem({mode, all}, Menu, [{label, {text,"Abort search. Display all (a)"}}]).
+
+config_editor(Editor, S) ->
+ Event = S#state.event,
+ Name = S#state.active_filter,
+ {value, F} = lists:keysearch(Name, #filter.name, S#state.filters),
+ FilterFun = F#filter.function,
+ case catch FilterFun(Event) of
+ true ->
+ do_config_editor(Editor, Event, lightblue, S#state.event_order);
+ {true, Event2} when record(Event2, event) ->
+ do_config_editor(Editor, Event2, lightblue, S#state.event_order);
+ false ->
+ do_config_editor(Editor, Event, red, S#state.event_order);
+ Bad ->
+ Contents = {bad_filter, Name, Bad},
+ BadEvent = Event#event{contents = Contents},
+ do_config_editor(Editor, BadEvent, red, S#state.event_order)
+ end.
+
+do_config_editor(Editor, Event, Colour, TsKey) ->
+ String = event_to_string(Event, TsKey),
+ gs:config(Editor, {insert, {'end', String}}),
+ gs:config(Editor, {enable, false}),
+ gs:config(Editor, {bg, Colour}),
+ Event.
+
+%%%----------------------------------------------------------------------
+%%% String handling
+%%%----------------------------------------------------------------------
+
+term_to_string(Term) ->
+ case catch io_lib:format("~s", [Term]) of
+ {'EXIT', _} -> io_lib:format("~p", [Term]);
+ GoodString -> GoodString
+ end.
+
+now_to_string({Mega, Sec, Micro} = Now)
+ when integer(Mega), integer(Sec), integer(Micro) ->
+ {{Y, Mo, D}, {H, Mi, S}} = calendar:now_to_universal_time(Now),
+ lists:concat([Y, "-", Mo, "-", D, " ", H, ".", Mi, ".", S, ".", Micro]);
+now_to_string(Other) ->
+ term_to_string(Other).
+
+event_to_string(Event, TsKey) ->
+ ReportedTs = Event#event.trace_ts,
+ ParsedTs = Event#event.event_ts,
+ Deep =
+ ["DETAIL LEVEL: ", term_to_string(Event#event.detail_level),
+ "\nLABEL: ", term_to_string(Event#event.label),
+ case Event#event.from == Event#event.to of
+ true ->
+ ["\nACTOR: ", term_to_string(Event#event.from)];
+ false ->
+ ["\nFROM: ", term_to_string(Event#event.from),
+ "\nTO: ", term_to_string(Event#event.to)]
+ end,
+ case ReportedTs == ParsedTs of
+ true ->
+ ["\nPARSED: ", now_to_string(ParsedTs)];
+ false ->
+ case TsKey of
+ trace_ts ->
+ ["\nTRACE_TS: ", now_to_string(ReportedTs),
+ "\nEVENT_TS: ", now_to_string(ParsedTs)];
+ event_ts ->
+ ["\nEVENT_TS: ", now_to_string(ParsedTs),
+ "\nTRACE_TS: ", now_to_string(ReportedTs)]
+ end
+ end,
+ "\nCONTENTS:\n\n", term_to_string(Event#event.contents)],
+ lists:flatten(Deep).
+
+pad_string(Atom, MinLen) when atom(Atom) ->
+ pad_string(atom_to_list(Atom), MinLen);
+pad_string(String, MinLen) when integer(MinLen), MinLen >= 0 ->
+ Len = length(String),
+ case Len >= MinLen of
+ true ->
+ String;
+ false ->
+ String ++ lists:duplicate(MinLen - Len, $ )
+ end.
+
+send_viewer_event(S, Event) ->
+ case S#state.viewer_pid of
+ ViewerPid when pid(ViewerPid) ->
+ ViewerPid ! {et, Event};
+ undefined ->
+ ignore
+ end.
diff --git a/lib/et/src/et_internal.hrl b/lib/et/src/et_internal.hrl
new file mode 100644
index 0000000000..b6f84f5b4b
--- /dev/null
+++ b/lib/et/src/et_internal.hrl
@@ -0,0 +1,23 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-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%
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Definition of internal data structures
+%%----------------------------------------------------------------------
+
+-record(filter, {name, function}).
diff --git a/lib/et/src/et_selector.erl b/lib/et/src/et_selector.erl
new file mode 100644
index 0000000000..845359622d
--- /dev/null
+++ b/lib/et/src/et_selector.erl
@@ -0,0 +1,529 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2001-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%
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Define event transforms and trace patterns
+%%----------------------------------------------------------------------
+
+-module(et_selector).
+
+-export([
+ make_pattern/1,
+ change_pattern/1,
+ parse_event/2
+ ]).
+
+-include("../include/et.hrl").
+
+%%----------------------------------------------------------------------
+%% make_pattern(RawPattern) -> TracePattern
+%%
+%% Makes a trace pattern suitable to feed change_pattern/1
+%% See also erlang:trace_pattern/2 for more info about its match_spec()
+%%
+%% RawPattern = detail_level()
+%% TracePattern = erlang_trace_pattern_match_spec()
+%%
+%% detail_level() = min | max | integer(X) when X =< 0, X >= 100
+%%
+%% min - minimum level of tracing (ignore calls to report_event/4,5)
+%% max - maximum level of tracing (all calls to report_event/4,5)
+%% integer() - explicit detail level of tracing
+%%----------------------------------------------------------------------
+
+make_pattern(undefined) ->
+ {undefined, undefined};
+make_pattern({Mod, Pattern}) when atom(Mod) ->
+ case Pattern of
+ min ->
+ {Mod, []};
+ max ->
+ Head = ['$1', '_', '_', '_', '_'],
+ Body = [],
+ Cond = [],
+ {Mod, [{Head, Cond, Body}]};
+ DetailLevel when integer(DetailLevel) ->
+ Head = ['$1', '_', '_', '_', '_'],
+ Body = [],
+ Cond = [{ '<', '$1', DetailLevel}],
+ {Mod, [{Head, Cond, Body}]};
+ undefined ->
+ {Mod, undefined};
+ _ ->
+ exit({bad_pattern, Pattern})
+ end.
+
+%%----------------------------------------------------------------------
+%% change_pattern(Pattern) -> ok
+%%
+%% Activates/deactivates tracing by changing the current trace pattern
+%%
+%% Pattern = detail_level() |
+%% empty_match_spec() |
+%% erlang_trace_pattern_match_spec()
+%%
+%% detail_level() = min | max | integer(X) when X =<0, X >= 100
+%% empty_match_spec() = []
+%%
+%% Min detail level deactivates tracing of calls to report_event/4,5
+%%
+%% Max detail level activates tracing of all calls to report_event/4,5
+%%
+%% integer(X) detail level activates tracing of all calls to
+%% report_event/4,5 whose detail level argument is lesser than X.
+%%
+%% An empty match spec deactivates tracing of calls to report_event/4,5
+%%
+%% Other match specs activates tracing of calls to report_event/4,5
+%% accordlingly with erlang:trace_pattern/2.
+%%----------------------------------------------------------------------
+
+change_pattern({Mod, Pattern}) when atom(Mod) ->
+ MFA = {Mod, report_event, 5},
+ case Pattern of
+ undefined ->
+ ignore;
+ [] ->
+ error_to_exit(dbg:ctp(MFA)),
+ error_to_exit(dbg:p(all, clear));
+ List when list(List) ->
+ error_to_exit(dbg:ctp(MFA)),
+ error_to_exit(dbg:tp(MFA, Pattern)),
+ error_to_exit(dbg:p(all, [call, timestamp]));
+ Other ->
+ change_pattern(make_pattern({Mod, Other}))
+ end,
+ ok.
+
+error_to_exit({error, Reason}) ->
+ exit(Reason);
+error_to_exit({ok, Res}) ->
+ Res.
+
+%%----------------------------------------------------------------------
+%% parse_event(Mod, ValidTraceData) -> false | true | {true, Event}
+%%
+%% Transforms trace data and makes an event record out of it
+%%
+%% ValidTraceData = erlang_trace_data() | record(event)
+%% Mod = module_name() | undefined
+%% module_name() = atom()
+%%
+%% erlang_trace_data() =
+%%
+%% {trace, Pid, Label, Info} |
+%% {trace, Pid, Label, Info, Extra} |
+%% {trace_ts, Pid, Label, Info, ReportedTS} |
+%% {trace_ts, Pid, Label, Info, Extra, ReportedTS} |
+%% {seq_trace, Label, Info} |
+%% {seq_trace, Label, Info, ReportedTS} |
+%% {drop, NumberOfDroppedItems}
+%%
+%% See erlang:trace/3 for more info about the semantics of
+%% the trace data.
+%%
+%% An event record consists of the following fields:
+%%
+%% detail_level - Noise has a high level as opposed to essentials.
+%% trace_ts - Time when the trace was generated.
+%% Same as event_ts if omitted in trace data.
+%% event_ts - Time when the event record was created.
+%% from - From actor, such as sender of a message.
+%% to - To actor, such as receiver of message.
+%% label - Label intended to provide a brief event summary.
+%% contents - All nitty gritty details of the event.
+%%
+%% See et:report_event/4 and et:report_event/5 for details.
+%%
+%% Returns:
+%%
+%% {true, Event} - where Event is an #event{} record representing the
+%% trace data
+%% true - means that the trace data already is an event
+%% record and that it is valid as it is.
+%% No transformation is needed.
+%% false - means that the trace data is uninteresting and
+%% should be dropped
+%%----------------------------------------------------------------------
+
+parse_event(_Mod, E) when record(E, event) ->
+ true;
+parse_event(Mod, Trace) ->
+ ParsedTS = erlang:now(),
+ case Trace of
+ {trace, Pid, Label, Info} ->
+ parse_event(Mod, Trace, ParsedTS, ParsedTS, Pid, Label, [Info]);
+ {trace, Pid, Label, Info, Extra} ->
+ parse_event(Mod, Trace, ParsedTS, ParsedTS, Pid, Label, [Info, Extra]);
+ {trace_ts, Pid, Label, Info, ReportedTS} ->
+ parse_event(Mod, Trace, ParsedTS, ReportedTS, Pid, Label, [Info]);
+ {trace_ts, Pid, Label, Info, Extra, ReportedTS} ->
+ parse_event(Mod, Trace, ParsedTS, ReportedTS, Pid, Label, [Info, Extra]);
+ {seq_trace, Label, Info} ->
+ parse_seq_event(Trace, ParsedTS, ParsedTS, Label, Info);
+ {seq_trace, Label, Info, ReportedTS} ->
+ parse_seq_event(Trace, ParsedTS, ReportedTS, Label, Info);
+ {drop, NumberOfDroppedItems} ->
+ DetailLevel = 20,
+ {true, #event{detail_level = DetailLevel,
+ trace_ts = ParsedTS,
+ event_ts = ParsedTS,
+ from = undefined,
+ to = undefined,
+ label = drop,
+ contents = [{label, drop},
+ {detail_level, DetailLevel},
+ {from, undefined},
+ {to, undefined},
+ {drop, NumberOfDroppedItems}]}};
+ _ ->
+ error_logger:format("~p(~p): Ignoring unknown trace type -> ~p~n~n",
+ [?MODULE, ?LINE, Trace]),
+ false
+ end.
+
+parse_seq_event(Trace, ParsedTS, ReportedTS, Label, Info) ->
+ case Info of
+ {send, Serial, From, To, Msg} ->
+ DetailLevel = 15,
+ {true, #event{detail_level = DetailLevel,
+ trace_ts = ReportedTS,
+ event_ts = ParsedTS,
+ from = From,
+ to = To,
+ label = {seq_send, Label},
+ contents = [{label, {seq_send, Label}},
+ {detail_level, DetailLevel},
+ {from, From},
+ {to, To},
+ {serial, Serial},
+ {msg, Msg}]}};
+ {'receive', Serial, From, To, Msg} ->
+ DetailLevel = 10,
+ {true, #event{detail_level = DetailLevel,
+ trace_ts = ReportedTS,
+ event_ts = ParsedTS,
+ from = From,
+ to = To,
+ label = {seq_receive, Label},
+ contents = [{label, {seq_receive, Label}},
+ {detail_level, DetailLevel},
+ {from, From},
+ {to, To},
+ {serial, Serial},
+ {msg, Msg}]}};
+ {print, Serial, From, _, UserInfo} ->
+ DetailLevel = 5,
+ {true, #event{detail_level = DetailLevel,
+ trace_ts = ReportedTS,
+ event_ts = ParsedTS,
+ from = From,
+ to = From,
+ label = {seq_print, Label},
+ contents = [{label, {seq_print, Label}},
+ {detail_level, DetailLevel},
+ {from, From},
+ {to, From},
+ {serial, Serial},
+ {user_info, UserInfo}]}};
+ _ ->
+ error_logger:format("~p(~p): Ignoring unknown trace type -> ~p~n~n",
+ [?MODULE, ?LINE, Trace]),
+ false
+ end.
+
+parse_event(Mod, Trace, ParsedTS, ReportedTS, From, Label, Contents) ->
+ case Label of
+ 'receive' ->
+ DetailLevel = 35,
+ [Msg] = Contents,
+ {true, #event{detail_level = DetailLevel,
+ trace_ts = ReportedTS,
+ event_ts = ParsedTS,
+ from = From,
+ to = From,
+ label = Label,
+ contents = [{label, Label},
+ {detail_level, DetailLevel},
+ {from, From},
+ {to, From},
+ {msg, Msg}]}};
+ send ->
+ DetailLevel = 40,
+ [Msg, To] = Contents,
+ {true, #event{detail_level = DetailLevel,
+ trace_ts = ReportedTS,
+ event_ts = ParsedTS,
+ from = From,
+ to = To,
+ label = Label,
+ contents = [{label, Label},
+ {detail_level, DetailLevel},
+ {from, From},
+ {to, To},
+ {msg, Msg}]}};
+ send_to_non_existing_process ->
+ DetailLevel = 40,
+ [Msg, To] = Contents,
+ {true, #event{detail_level = DetailLevel,
+ trace_ts = ReportedTS,
+ event_ts = ParsedTS,
+ from = From,
+ to = To,
+ label = Label,
+ contents = [{label, Label},
+ {detail_level, DetailLevel},
+ {from, From},
+ {to, To},
+ {msg, Msg}]}};
+ call ->
+ case Contents of
+ [{M, report_event, [UserDetailLevel, UserFrom, UserTo, UserLabel, UserContents]}] when M == Mod, Mod /= undefined ->
+ {true, #event{detail_level = UserDetailLevel,
+ trace_ts = ReportedTS,
+ event_ts = ParsedTS,
+ from = UserFrom,
+ to = UserTo,
+ label = UserLabel,
+ contents = UserContents}}; % Term
+ [MFA] ->
+ DetailLevel = 45,
+ {true, #event{detail_level = DetailLevel,
+ trace_ts = ReportedTS,
+ event_ts = ParsedTS,
+ from = From,
+ to = From,
+ label = Label,
+ contents = [{label, Label},
+ {detail_level, DetailLevel},
+ {from, From},
+ {to, From},
+ {mfa, MFA}]}};
+ [MFA, PamResult] ->
+ DetailLevel = 45,
+ {true, #event{detail_level = DetailLevel,
+ trace_ts = ReportedTS,
+ event_ts = ParsedTS,
+ from = From,
+ to = From,
+ label = Label,
+ contents = [{label, Label},
+ {detail_level, DetailLevel},
+ {from, From},
+ {to, From},
+ {mfa, MFA},
+ {pam_result, PamResult}]}}
+ end;
+ return_to ->
+ DetailLevel = 50,
+ [MFA] = Contents,
+ {true, #event{detail_level = DetailLevel,
+ trace_ts = ReportedTS,
+ event_ts = ParsedTS,
+ from = From,
+ to = From,
+ label = Label,
+ contents = [{label, Label},
+ {detail_level, DetailLevel},
+ {from, From},
+ {to, From},
+ {mfa, MFA}]}};
+ return_from ->
+ DetailLevel = 52,
+ [MFA, ReturnValue] = Contents,
+ {true, #event{detail_level = DetailLevel,
+ trace_ts = ReportedTS,
+ event_ts = ParsedTS,
+ from = From,
+ to = From,
+ label = Label,
+ contents = [{label, Label},
+ {detail_level, DetailLevel},
+ {from, From},
+ {to, From},
+ {mfa, MFA},
+ {return, ReturnValue}]}};
+ spawn ->
+ DetailLevel = 25,
+ [NewPid, MFA] = Contents,
+ {true, #event{detail_level = DetailLevel,
+ trace_ts = ReportedTS,
+ event_ts = ParsedTS,
+ from = From,
+ to = NewPid,
+ label = Label,
+ contents = [{label, Label},
+ {detail_level, DetailLevel},
+ {from, From},
+ {to, NewPid},
+ {mfa, MFA}]}}; % MFA | Term
+ exit ->
+ DetailLevel = 30,
+ [Reason] = Contents,
+ {true, #event{detail_level = DetailLevel,
+ trace_ts = ReportedTS,
+ event_ts = ParsedTS,
+ from = From,
+ to = From,
+ label = Label,
+ contents = [{label, Label},
+ {detail_level, DetailLevel},
+ {from, From},
+ {to, From},
+ {reason, Reason}]}};
+ link ->
+ DetailLevel = 55,
+ [LinkTo] = Contents,
+ {true, #event{detail_level = DetailLevel,
+ trace_ts = ReportedTS,
+ event_ts = ParsedTS,
+ from = From,
+ to = LinkTo,
+ label = Label,
+ contents = [{label, Label},
+ {detail_level, DetailLevel},
+ {from, From},
+ {to, LinkTo}]}};
+ unlink ->
+ DetailLevel = 60,
+ [UnlinkFrom] = Contents,
+ {true, #event{detail_level = DetailLevel,
+ trace_ts = ReportedTS,
+ event_ts = ParsedTS,
+ from = From,
+ to = UnlinkFrom,
+ label = Label,
+ contents = [{label, Label},
+ {detail_level, DetailLevel},
+ {from, From},
+ {to, UnlinkFrom}]}};
+ getting_linked ->
+ DetailLevel = 65,
+ [LinkTo] = Contents,
+ {true, #event{detail_level = DetailLevel,
+ trace_ts = ReportedTS,
+ event_ts = ParsedTS,
+ from = From,
+ to = LinkTo,
+ label = Label,
+ contents = [{label, Label},
+ {detail_level, DetailLevel},
+ {from, From},
+ {to, LinkTo}]}};
+ getting_unlinked ->
+ DetailLevel = 67,
+ [UnlinkFrom] = Contents,
+ {true, #event{detail_level = DetailLevel,
+ trace_ts = ReportedTS,
+ event_ts = ParsedTS,
+ from = From,
+ to = UnlinkFrom,
+ label = Label,
+ contents = [{label, Label},
+ {detail_level, DetailLevel},
+ {from, From},
+ {to, UnlinkFrom}]}};
+ register ->
+ DetailLevel = 70,
+ [Name] = Contents,
+ {true, #event{detail_level = DetailLevel,
+ trace_ts = ReportedTS,
+ event_ts = ParsedTS,
+ from = From,
+ to = From,
+ label = Label,
+ contents = [{label, Label},
+ {detail_level, DetailLevel},
+ {from, From},
+ {to, From},
+ {name, Name}]}};
+ unregister ->
+ DetailLevel = 75,
+ [Name] = Contents,
+ {true, #event{detail_level = DetailLevel,
+ trace_ts = ReportedTS,
+ event_ts = ParsedTS,
+ from = From,
+ to = From,
+ label = Label,
+ contents = [{label, Label},
+ {detail_level, DetailLevel},
+ {from, From},
+ {to, From},
+ {name, Name}]}};
+ in ->
+ DetailLevel = 90,
+ [MFA] = Contents,
+ {true, #event{detail_level = DetailLevel,
+ trace_ts = ReportedTS,
+ event_ts = ParsedTS,
+ from = From,
+ to = From,
+ label = Label,
+ contents = [{label, Label},
+ {detail_level, DetailLevel},
+ {from, From},
+ {to, From},
+ {mfa, MFA}]}}; % MFA | 0
+ out ->
+ DetailLevel = 95,
+ [MFA] = Contents,
+ {true, #event{detail_level = DetailLevel,
+ trace_ts = ReportedTS,
+ event_ts = ParsedTS,
+ from = From,
+ to = From,
+ label = Label,
+ contents = [{label, Label},
+ {detail_level, DetailLevel},
+ {from, From},
+ {to, From},
+ {mfa, MFA}]}}; % MFA | 0
+ gc_start ->
+ DetailLevel = 80,
+ [GcKeyValueList] = Contents,
+ {true, #event{detail_level = DetailLevel,
+ trace_ts = ReportedTS,
+ event_ts = ParsedTS,
+ from = From,
+ to = From,
+ label = Label,
+ contents = [{label, Label},
+ {detail_level, DetailLevel},
+ {from, From},
+ {to, From},
+ {gc_items, GcKeyValueList}]}};
+ gc_end ->
+ DetailLevel = 85,
+ [GcKeyValueList] = Contents,
+ {true, #event{detail_level = DetailLevel,
+ trace_ts = ReportedTS,
+ event_ts = ParsedTS,
+ from = From,
+ to = From,
+ label = Label,
+ contents = [{label, Label},
+ {detail_level, DetailLevel},
+ {from, From},
+ {to, From},
+ {gc_items, GcKeyValueList}]}};
+ _ ->
+ error_logger:format("~p(~p): Ignoring unknown trace type -> ~p~n~n",
+ [?MODULE, ?LINE, Trace]),
+ false
+ end.
diff --git a/lib/et/src/et_viewer.erl b/lib/et/src/et_viewer.erl
new file mode 100644
index 0000000000..ede2c401eb
--- /dev/null
+++ b/lib/et/src/et_viewer.erl
@@ -0,0 +1,1602 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2000-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%
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Displays a sequence chart for trace events (messages/actions)
+%%----------------------------------------------------------------------
+
+-module(et_viewer).
+
+-behaviour(gen_server).
+
+%% External exports
+-export([file/1,
+ start/0,
+ start/1,
+ start_link/1,
+ stop/1,
+ get_collector_pid/1]).
+
+%% gen_server callbacks
+-export([init/1, terminate/2, code_change/3,
+ handle_call/3, handle_cast/2, handle_info/2]).
+
+-include("../include/et.hrl").
+-include("et_internal.hrl").
+
+-define(unknown, "UNKNOWN").
+
+-record(state,
+ {parent_pid, % Pid of parent process
+ collector_pid, % Pid of collector process
+ event_order, % Field to be used as primary key
+ trace_pattern, % Collector trace pattern
+ active_filter, % Name of the active filter
+ filters, % List of possible filters
+ selected_actor, % Actor selected by user
+ first_event, % Key of first event (regardless of visibility)
+ last_event, % Key of last event (regardless of visibility)
+ max_events, % Maximum number of shown events
+ events, % Queue containg all event keys (regardless of visibility)
+ max_actors, % Maximum number of shown actors
+ actors, % List of known actors
+ refresh_needed, % Refresh is needed in order to show all actors
+ display_mode, % Display all or only matching actors
+ detail_level, % Show only events with lesser detail level
+ hide_actions, % Hide/show events where to == from actor (bool)
+ hide_unknown, % Hide/show events with unknown actor (bool)
+ is_suspended, % Suspend viewer updates (bool)
+ title, % GUI: Window title
+ win, % GUI: Window object
+ menubar, % GUI: Menu bar object
+ packer, % GUI: Packer object
+ width, % GUI: Window width
+ height, % GUI: Window height
+ scale, % GUI: Scaling factor on canvas
+ font, % GUI: Font to be used on text labels
+ canvas_width, % GUI: Canvas width
+ canvas_height, % GUI: Canvas height
+ canvas, % GUI: Canvas object
+ y_pos}). % GUI: Current y position on canvas
+
+-record(actor, {name, string}).
+
+-define(initial_x, 10).
+-define(incr_x, 60).
+-define(initial_y, 15).
+-define(incr_y, 15).
+-define(detail_level_min, 0).
+-define(detail_level_max, 100).
+
+%%%----------------------------------------------------------------------
+%%% Client side
+%%%----------------------------------------------------------------------
+
+%%----------------------------------------------------------------------
+%% file(FileName) -> {ok, ViewerPid} | {error, Reason}
+%%
+%% Start a new event viewer and a corresponding collector
+%% and load them with trace events from a trace file.
+%%
+%% FileName() = string()
+%% ViewerPid = pid()
+%% Reason = term()
+%%----------------------------------------------------------------------
+
+file(FileName) ->
+ start_link([{trace_client, {file, FileName}}]).
+
+%%----------------------------------------------------------------------
+%% start() -> ok
+%%
+%% Simplified start of a sequence chart viewer with
+%% global tracing activated.
+%%
+%% Convenient to be used from the command line
+%% (erl -s et_viewer) as both the viewer and collector
+%% processes are unlinked from the calling process.
+%%----------------------------------------------------------------------
+
+start() ->
+ start([{trace_global, true}]).
+
+%%----------------------------------------------------------------------
+%% start(Options) -> {ok, ViewerPid} | {error, Reason}
+%%----------------------------------------------------------------------
+
+start(Options) ->
+ start_link([{parent_pid, undefined} | Options]).
+
+%%----------------------------------------------------------------------
+%% start_link(Options) -> {ok, ViewerPid} | {error, Reason}
+%%
+%% Start a sequence chart viewer for trace events (messages/actions)
+%%
+%% Options = [option() | collector_option()]
+%%
+%% option() =
+%% {parent_pid, extended_pid()} |
+%% {title, term()} |
+%% {detail_level, detail_level()} |
+%% {is_suspended, boolean()} |
+%% {scale, integer()} |
+%% {width, integer()} |
+%% {height, integer()} |
+%% {collector_pid, extended_pid()} |
+%% {event_order, event_order()} |
+%% {active_filter, atom()} |
+%% {max_events, extended_integer()} |
+%% {max_actors, extended_integer()} |
+%% {trace_global, et_collector_trace_global()} |
+%% {trace_pattern, et_collector_trace_pattern()} |
+%% {trace_port, et_collector_trace_port()} |
+%% {trace_max_queue, et_collector_trace_max_queue()} |
+%% {trace_client, et_collector_trace_client()} |
+%% {dict_insert, {filter, filter_name()}, event_filter_fun()} |
+%% {dict_insert, et_collector_dict_key(), et_collector_dict_val()} |
+%% {dict_delete, {filter, filter_name()}} |
+%% {dict_delete, et_collector_dict_key()} |
+%% {actors, actors()} |
+%% {first_event, first_key()} |
+%% {hide_unknown, boolean()} |
+%% {hide_actions, boolean()} |
+%% {display_mode, display_mode()}
+%%
+%% extended_pid() = pid() | undefined
+%% detail_level() = min | max | integer(X) when X >=0, X =< 100
+%% event_order() = trace_ts | event_ts
+%% extended_integer() = integer() | infinity
+%% display_mode() = all | {search_actors, direction(), first_key(), actors()}
+%% direction() = forward | reverse
+%% first_key() = event_key()
+%% actors() = [term()]
+%%
+%% filter_name() = atom()
+%% filter_fun() = fun(Event) -> false | true | {true, NewEvent}
+%% Event = NewEvent = record(event)
+%%
+%% ViewerPid = pid()
+%% Reason = term()
+%%
+%% A filter_fun() takes an event record as sole argument
+%% and returns false | true | {true, NewEvent}.
+%%----------------------------------------------------------------------
+
+start_link(Options) ->
+ case parse_opt(Options, default_state(), []) of
+ {ok, S, CollectorOpt} ->
+ case S#state.collector_pid of
+ CollectorPid when pid(CollectorPid) ->
+ case gen_server:start_link(?MODULE, [S], []) of
+ {ok, Pid} when S#state.parent_pid /= self() ->
+ unlink(Pid),
+ {ok, Pid};
+ Other ->
+ Other
+ end;
+ undefined ->
+ case et_collector:start_link(CollectorOpt) of
+ {ok, CollectorPid} ->
+ S2 = S#state{collector_pid = CollectorPid},
+ case gen_server:start_link(?MODULE, [S2], []) of
+ {ok, Pid} when S#state.parent_pid /= self() ->
+ unlink(Pid),
+ {ok, Pid};
+ Other ->
+ Other
+ end;
+ {error, Reason} ->
+ {error, {et_collector, Reason}}
+ end
+ end;
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
+default_state() ->
+ #state{parent_pid = self(),
+ collector_pid = undefined,
+ detail_level = ?detail_level_max,
+ active_filter = collector,
+ filters = [#filter{name = collector, function = fun(E) -> E end}],
+ event_order = trace_ts,
+ is_suspended = false,
+ max_events = 100,
+ first_event = first,
+ last_event = first,
+ events = queue_new(),
+ max_actors = 5,
+ actors = [create_actor(?unknown)],
+ selected_actor = ?unknown,
+ hide_actions = false,
+ hide_unknown = false,
+ refresh_needed = false,
+ display_mode = all,
+ scale = 2,
+ canvas_height = 0,
+ canvas_width = 0,
+ width = 800,
+ height = 600}.
+
+parse_opt([], S, CollectorOpt) ->
+ {ok, S, [{parent_pid, S#state.parent_pid} | CollectorOpt]};
+parse_opt([H | T], S, CollectorOpt) ->
+ case H of
+ {parent_pid, Parent} when Parent == undefined ->
+ CollectorOpt2 = [H | CollectorOpt],
+ parse_opt(T, S#state{parent_pid = Parent}, CollectorOpt2);
+ {parent_pid, Parent} when pid(Parent) ->
+ CollectorOpt2 = [H | CollectorOpt],
+ parse_opt(T, S#state{parent_pid = Parent}, CollectorOpt2);
+ {title, Title} ->
+ parse_opt(T, S#state{title = name_to_string(Title)}, CollectorOpt);
+ {detail_level, Level} when integer(Level),
+ Level >= ?detail_level_min,
+ Level =< ?detail_level_max ->
+ parse_opt(T, S#state{detail_level = Level}, CollectorOpt);
+ {detail_level, max} ->
+ parse_opt(T, S#state{detail_level = ?detail_level_max}, CollectorOpt);
+ {detail_level, min} ->
+ parse_opt(T, S#state{detail_level = ?detail_level_min}, CollectorOpt);
+ {is_suspended, true} ->
+ parse_opt(T, S#state{is_suspended = true}, CollectorOpt);
+ {is_suspended, false} ->
+ parse_opt(T, S#state{is_suspended = false}, CollectorOpt);
+ {scale, Scale} when integer(Scale), Scale > 0 ->
+ parse_opt(T, S#state{scale = Scale}, CollectorOpt);
+ {width, W} when integer(W), W > 0 ->
+ parse_opt(T, S#state{width = W, canvas_width = W}, CollectorOpt);
+ {height, WH} when integer(WH), WH > 0 ->
+ parse_opt(T, S#state{height = WH, canvas_height = WH}, CollectorOpt);
+ {collector_pid, Pid} when pid(Pid) ->
+ parse_opt(T, S#state{collector_pid = Pid}, CollectorOpt);
+ {collector_pid, undefined} ->
+ parse_opt(T, S#state{collector_pid = undefined}, CollectorOpt);
+ {active_filter, Name} when atom(Name) ->
+ parse_opt(T, S#state{active_filter = Name}, CollectorOpt);
+ {event_order, trace_ts} -> %% BUGBUG: Verify event_order with collector
+ CollectorOpt2 = [H | CollectorOpt],
+ parse_opt(T, S#state{event_order = trace_ts}, CollectorOpt2);
+ {event_order, event_ts} -> %% BUGBUG: Verify event_order with collector
+ CollectorOpt2 = [H | CollectorOpt],
+ parse_opt(T, S#state{event_order = event_ts}, CollectorOpt2);
+ {trace_port, _Port} ->
+ CollectorOpt2 = [H | CollectorOpt],
+ parse_opt(T, S, CollectorOpt2);
+ {trace_max_queue, _Queue} ->
+ CollectorOpt2 = [H | CollectorOpt],
+ parse_opt(T, S, CollectorOpt2);
+ {trace_pattern, _Pattern} ->
+ CollectorOpt2 = [H | CollectorOpt],
+ parse_opt(T, S, CollectorOpt2);
+ {trace_global, _Boolean} ->
+ CollectorOpt2 = [H | CollectorOpt],
+ parse_opt(T, S, CollectorOpt2);
+ {trace_client, _Client} ->
+ CollectorOpt2 = [H | CollectorOpt],
+ parse_opt(T, S, CollectorOpt2);
+ {dict_insert, {filter, Name}, Fun} ->
+ if
+ atom(Name), function(Fun) ->
+ F = #filter{name = Name, function = Fun},
+ Filters = lists:keydelete(Name, #filter.name, S#state.filters),
+ CollectorOpt2 = [H | CollectorOpt],
+ parse_opt(T, S#state{filters = Filters ++ [F]}, CollectorOpt2);
+ true ->
+ {error, {bad_option, H}}
+ end;
+ {dict_insert, {subscriber, Pid}, _Val} ->
+ if
+ pid(Pid) ->
+ CollectorOpt2 = [H | CollectorOpt],
+ parse_opt(T, S, CollectorOpt2);
+ true ->
+ {error, {bad_option, H}}
+ end;
+ {dict_insert, _Key, _Val} ->
+ CollectorOpt2 = [H | CollectorOpt],
+ parse_opt(T, S, CollectorOpt2);
+ {dict_delete, {filter, Name}} ->
+ Filters = lists:keydelete(Name, #filter.name, S#state.filters),
+ CollectorOpt2 = [H | CollectorOpt],
+ parse_opt(T, S#state{filters = Filters}, CollectorOpt2);
+ {dict_delete, _Key} ->
+ CollectorOpt2 = [H | CollectorOpt],
+ parse_opt(T, S, CollectorOpt2);
+ {max_events, Max} when integer(Max), Max > 0->
+ parse_opt(T, S#state{max_events = Max}, CollectorOpt);
+ {max_events, Max} when Max == infinity ->
+ parse_opt(T, S#state{max_events = Max}, CollectorOpt);
+ {max_actors, Max} when integer(Max), Max >= 0->
+ parse_opt(T, S#state{max_actors = Max}, CollectorOpt);
+ {max_actors, Max} when Max == infinity ->
+ parse_opt(T, S#state{max_actors = Max}, CollectorOpt);
+ {actors, ActorNames} when list(ActorNames) ->
+ ActorNames2 =
+ case lists:member(?unknown, ActorNames) of
+ false -> [?unknown | ActorNames];
+ true -> ActorNames
+ end,
+ Actors = [create_actor(Name) || Name <- ActorNames2],
+ parse_opt(T, S#state{actors = Actors}, CollectorOpt);
+ {first_event, First} ->
+ parse_opt(T, S#state{first_event = First}, CollectorOpt);
+ {hide_unknown, Bool} when Bool == false ->
+ parse_opt(T, S#state{hide_unknown = Bool}, CollectorOpt);
+ {hide_unknown, Bool} when Bool == true ->
+ parse_opt(T, S#state{hide_unknown = Bool}, CollectorOpt);
+ {hide_actions, Bool} when Bool == false ->
+ parse_opt(T, S#state{hide_actions = Bool}, CollectorOpt);
+ {hide_actions, Bool} when Bool == true ->
+ parse_opt(T, S#state{hide_actions = Bool}, CollectorOpt);
+ {display_mode, Mode = all} ->
+ parse_opt(T, S#state{display_mode = Mode}, CollectorOpt);
+ {display_mode, Mode = {search_actors, Dir, _Key, Actors}} when list(Actors), Dir == forward ->
+ parse_opt(T, S#state{display_mode = Mode}, CollectorOpt);
+ {display_mode, Mode = {search_actors, Dir, _Key, Actors}} when list(Actors), Dir == reverse ->
+ parse_opt(T, S#state{display_mode = Mode}, CollectorOpt);
+
+ Bad ->
+ {error, {bad_option, Bad}}
+ end;
+parse_opt(BadList, _S, _CollectorOpt) ->
+ {error, {bad_option_list, BadList}}.
+
+do_dict_insert({filter, Name}, Fun, S) when atom(Name), function(Fun) ->
+ F = #filter{name = Name, function = Fun},
+ Filters = lists:keydelete(Name, #filter.name, S#state.filters),
+ Filters2 = lists:keysort(#filter.name, [F | Filters]),
+ gs:destroy(filter_menu),
+ create_filter_menu(S#state.active_filter, Filters2),
+ S#state{filters = Filters2};
+do_dict_insert(_Key, _Val, S) ->
+ %% ok = error_logger:format("~p(~p): handle_info({et, {dict_insert, ~p, ~p}})~n",
+ %% [?MODULE, self(), Key, Val]),
+ S.
+
+do_dict_delete({filter, Name}, S) when atom(Name), Name /= S#state.active_filter ->
+ Filters = lists:keydelete(Name, #filter.name, S#state.filters),
+ gs:destroy(filter_menu),
+ create_filter_menu(S#state.active_filter, Filters),
+ S#state{filters = Filters};
+do_dict_delete(_Key, S) ->
+ %% ok = error_logger:format("~p(~p): handle_info({et, {dict_delete, ~p}})~n",
+ %% [?MODULE, self(), Key]),
+ S.
+
+%%----------------------------------------------------------------------
+%% get_collector_pid(ViewerPid) -> CollectorPid
+%%
+%% Returns the identifier of the collector process
+%%
+%% ViewerPid = pid()
+%% CollectorPid = pid()
+%%----------------------------------------------------------------------
+
+get_collector_pid(ViewerPid) ->
+ call(ViewerPid, get_collector_pid).
+
+%%----------------------------------------------------------------------
+%% stop(ViewerPid) -> ok
+%%
+%% Stops a viewer
+%%
+%% ViewerPid = pid()
+%%----------------------------------------------------------------------
+
+stop(ViewerPid) ->
+ call(ViewerPid, stop).
+
+call(ViewerPid, Request) ->
+ gen_server:call(ViewerPid, Request, infinity).
+
+%%%----------------------------------------------------------------------
+%%% Callback functions from gen_server
+%%%----------------------------------------------------------------------
+
+%%----------------------------------------------------------------------
+%% Func: init/1
+%% Returns: {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%%----------------------------------------------------------------------
+
+init([S]) when record(S, state) ->
+ process_flag(trap_exit, true),
+ InitialTimeout = 0,
+ case S#state.parent_pid of
+ undefined ->
+ ignore;
+ Pid when pid(Pid) ->
+ link(Pid)
+ end,
+ et_collector:dict_insert(S#state.collector_pid,
+ {subscriber, self()},
+ ?MODULE),
+ {ok, create_main_window(S), InitialTimeout}.
+
+%%----------------------------------------------------------------------
+%% Func: handle_call/3
+%% Returns: {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} | (terminate/2 is called)
+%% {stop, Reason, State} (terminate/2 is called)
+%%----------------------------------------------------------------------
+
+handle_call(get_collector_pid, _From, S) ->
+ Reply = S#state.collector_pid,
+ reply(Reply, S);
+handle_call(stop, _From, S) ->
+ gs:destroy(S#state.win),
+ {stop, shutdown, ok, S};
+handle_call(Request, From, S) ->
+ ok = error_logger:format("~p(~p): handle_call(~p, ~p, ~p)~n",
+ [?MODULE, self(), Request, From, S]),
+ Reply = {error, {bad_request, Request}},
+ reply(Reply, S).
+
+%%----------------------------------------------------------------------
+%% Func: handle_cast/2
+%% Returns: {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State} (terminate/2 is called)
+%%----------------------------------------------------------------------
+
+handle_cast(Msg, S) ->
+ ok = error_logger:format("~p(~p): handle_cast(~p, ~p)~n",
+ [?MODULE, self(), Msg, S]),
+ noreply(S).
+
+%%----------------------------------------------------------------------
+%% Func: handle_info/2
+%% Returns: {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State} (terminate/2 is called)
+%%----------------------------------------------------------------------
+
+handle_info({et, {insert_actors, ActorNames}}, S) when list(ActorNames) ->
+ Fun = fun(N, Actors) ->
+ case lists:keymember(N, #actor.name, Actors) of
+ true -> Actors;
+ false -> Actors ++ [create_actor(N)]
+ end
+ end,
+ Actors = lists:foldl(Fun, S#state.actors, ActorNames),
+ S2 = refresh_main_window(S#state{actors = Actors}),
+ noreply(S2);
+handle_info({et, {delete_actors, ActorNames}}, S) when list(ActorNames)->
+ Fun = fun(N, Actors) when N == ?unknown ->
+ Actors;
+ (N, Actors) ->
+ lists:keydelete(N, #actor.name, Actors)
+ end,
+ New = lists:foldl(Fun, S#state.actors, ActorNames),
+ S2 = refresh_main_window(S#state{actors = New}),
+ noreply(S2);
+handle_info({et, {dict_insert, Key, Val}}, S) ->
+ S2 = do_dict_insert(Key, Val, S),
+ noreply(S2);
+handle_info({et, {dict_delete, Key}}, S) ->
+ S2 = do_dict_delete(Key, S),
+ noreply(S2);
+handle_info({et, first}, S) ->
+ S2 = scroll_first(S),
+ noreply(S2);
+handle_info({et, prev}, S) ->
+ S2 = scroll_prev(S),
+ noreply(S2);
+handle_info({et, next}, S) ->
+ S2 = scroll_next(S),
+ noreply(S2);
+handle_info({et, last}, S) ->
+ S2 = scroll_last(S),
+ noreply(S2);
+handle_info({et, refresh}, S) ->
+ S2 = refresh_main_window(S),
+ noreply(S2);
+handle_info({et, {display_mode, Mode}}, S) ->
+ S2 = change_display_mode(Mode, S),
+ noreply(S2);
+handle_info({et, close}, S) ->
+ gs:destroy(S#state.win),
+ {stop, shutdown, S};
+handle_info({gs, Button, click, Data, Other} = Click, S) ->
+ CollectorPid = S#state.collector_pid,
+ case Button of
+ close ->
+ gs:destroy(S#state.win),
+ {stop, shutdown, S};
+ suspended ->
+ case Other of
+ [_Text, _Group, Bool | _] when Bool == true ->
+ S2 = do_suspend(S),
+ noreply(S2);
+ [_Text, _Group, Bool | _] when Bool == false ->
+ S2 = do_resume(S),
+ noreply(S2);
+ _ ->
+ click_error(Click, S),
+ noreply(S)
+ end;
+ hide_actions ->
+ case Other of
+ [_Text, _Group, Bool | _] when Bool == true ->
+ S2 = refresh_main_window(S#state{hide_actions = Bool}),
+ noreply(S2);
+ [_Text, _Group, Bool | _] when Bool == false ->
+ S2 = refresh_main_window(S#state{hide_actions = Bool}),
+ noreply(S2);
+ _ ->
+ click_error(Click, S),
+ noreply(S)
+ end;
+ hide_unknown ->
+ case Other of
+ [_Text, _Group, Bool | _] when Bool == true ->
+ S2 = refresh_main_window(S#state{hide_unknown = Bool}),
+ noreply(S2);
+ [_Text, _Group, Bool | _] when Bool == false ->
+ S2 = refresh_main_window(S#state{hide_unknown = Bool}),
+ noreply(S2);
+ _ ->
+ click_error(Click, S),
+ noreply(S)
+ end;
+ up ->
+ S2 = scroll_up(S),
+ noreply(S2);
+ down ->
+ S2 = scroll_down(S),
+ noreply(S2);
+ first ->
+ S2 = scroll_first(S),
+ noreply(S2);
+ prev ->
+ S2 = scroll_prev(S),
+ noreply(S2);
+ next ->
+ S2 = scroll_next(S),
+ noreply(S2);
+ last ->
+ S2 = scroll_last(S),
+ noreply(S2);
+ refresh ->
+ S2 = refresh_main_window(S),
+ noreply(S2);
+ {display_mode, Mode} ->
+ S2 = change_display_mode(Mode, S),
+ noreply(S2);
+ close_all ->
+ close_all(S);
+ close_all_others ->
+ close_all_others(S);
+ first_all ->
+ et_collector:multicast(CollectorPid, first),
+ noreply(S);
+ prev_all ->
+ et_collector:multicast(CollectorPid, prev),
+ noreply(S);
+ next_all ->
+ et_collector:multicast(CollectorPid, next),
+ noreply(S);
+ last_all ->
+ et_collector:multicast(CollectorPid, last),
+ noreply(S);
+ refresh_all ->
+ et_collector:multicast(CollectorPid, refresh),
+ noreply(S);
+ clear_all ->
+ et_collector:clear_table(CollectorPid),
+ et_collector:multicast(CollectorPid, refresh),
+ noreply(S);
+ load_all ->
+ et_collector:start_trace_client(CollectorPid, event_file, "et_viewer.log"),
+ noreply(S);
+ save_all ->
+ et_collector:save_event_file(CollectorPid,
+ "et_viewer.log",
+ [existing, write, keep]),
+ noreply(S);
+ {open_viewer, Scale} ->
+ Actors = [A#actor.name || A <- S#state.actors],
+ open_viewer(Scale, S#state.active_filter, Actors, S),
+ noreply(S);
+ _Level when Data == detail_level, integer(hd(Other)),
+ hd(Other) >= ?detail_level_min,
+ hd(Other) =< ?detail_level_max ->
+ S2 = S#state{detail_level = hd(Other)},
+ noreply(S2);
+ _PopupMenuItem when record(Data, filter) ->
+ open_viewer(S#state.scale, Data#filter.name, [?unknown], S),
+ noreply(S);
+ _ ->
+ click_error(Click, S),
+ noreply(S)
+ end;
+handle_info({gs, _Obj, destroy,_, _}, S) ->
+ gs:destroy(S#state.win),
+ {stop, shutdown, S};
+handle_info({gs, _Obj, buttonpress, _, [_Button, X, Y | _]}, S) ->
+ S3 =
+ case y_to_n(Y, S) of
+ actor ->
+ %% Actor click
+ case S#state.actors of
+ [] ->
+ S;
+ _ ->
+ N = x_to_n(X, S),
+ A = lists:nth(N, S#state.actors),
+ S#state{selected_actor = A}
+ end;
+ {event, N} ->
+ %% Event click
+ List = queue_to_list(S#state.events),
+ S2 = S#state{events = list_to_queue(List)},
+
+ Key = lists:nth(N, List),
+ Pid = S#state.collector_pid,
+ Fun = fun create_contents_window/2,
+ case et_collector:iterate(Pid, Key, -1) of
+ Prev when Prev == Key ->
+ et_collector:iterate(Pid, first, 1, Fun, S2);
+ Prev ->
+ et_collector:iterate(Pid, Prev, 1, Fun, S2)
+ end
+ end,
+ noreply(S3);
+handle_info({gs, _Obj, buttonrelease, _, [_Button, X, Y | _]}, S) ->
+ S2 =
+ case y_to_n(Y, S) of
+ actor ->
+ %% Actor click
+ case S#state.actors of
+ [] ->
+ S;
+ Actors ->
+ N = x_to_n(X, S),
+ New = lists:nth(N, S#state.actors),
+ Old = S#state.selected_actor,
+ case New#actor.name == Old#actor.name of
+ true ->
+ A = S#state.selected_actor,
+ toggle_search_for_actor(A#actor.name, S);
+ false ->
+ move_actor(Old, New, Actors, S)
+ end
+ end;
+ {event, _N} ->
+ %% Event click ignored
+ S
+ end,
+ noreply(S2);
+handle_info({gs, _Obj, keypress, _, [KeySym, _Keycode, _Shift, _Control | _]} = Key, S) ->
+ case KeySym of
+ 'c' ->
+ close_all_others(S);
+ 'C' ->
+ close_all(S);
+ 'Up' ->
+ S2 = scroll_up(S),
+ noreply(S2);
+ 'Down' ->
+ S2 = scroll_down(S),
+ noreply(S2);
+ 'f' ->
+ S2 = scroll_first(S),
+ noreply(S2);
+ 'p' ->
+ S2 = scroll_prev(S),
+ noreply(S2);
+ 'Prior' ->
+ S2 = scroll_prev(S),
+ noreply(S2);
+ 'n' ->
+ S2 = scroll_next(S),
+ noreply(S2);
+ 'Next' ->
+ S2 = scroll_next(S),
+ noreply(S2);
+ 'l' ->
+ S2 = scroll_last(S),
+ noreply(S2);
+ 'r' ->
+ S2 = refresh_main_window(S),
+ noreply(S2);
+ 'F' ->
+ et_collector:multicast(S#state.collector_pid, first),
+ noreply(S);
+ 'P' ->
+ et_collector:multicast(S#state.collector_pid, prev),
+ noreply(S);
+ 'N' ->
+ et_collector:multicast(S#state.collector_pid, next),
+ noreply(S);
+ 'L' ->
+ et_collector:multicast(S#state.collector_pid, last),
+ noreply(S);
+ 'R' ->
+ et_collector:multicast(S#state.collector_pid, refresh),
+ noreply(S);
+
+ 'a' ->
+ S2 = S#state{display_mode = all},
+ S3 = refresh_main_window(S2),
+ noreply(S3);
+
+ 'equal' ->
+ Scale = S#state.scale,
+ Actors = [A#actor.name || A <- S#state.actors],
+ open_viewer(Scale, S#state.active_filter, Actors, S),
+ noreply(S);
+ 'plus' ->
+ Scale = S#state.scale + 1,
+ Actors = [A#actor.name || A <- S#state.actors],
+ open_viewer(Scale, S#state.active_filter, Actors, S),
+ noreply(S);
+ 'minus' ->
+ case S#state.scale of
+ 1 ->
+ gs:config(S#state.canvas, beep);
+ Scale ->
+ Actors = [A#actor.name || A <- S#state.actors],
+ open_viewer(Scale - 1, S#state.active_filter, Actors, S)
+ end,
+ noreply(S);
+ 0 ->
+ case lists:keysearch(collector, #filter.name, S#state.filters) of
+ {value, F} when record(F, filter) ->
+ open_viewer(S#state.scale, F#filter.name, [?unknown], S);
+ false ->
+ gs:config(S#state.canvas, beep)
+ end,
+ noreply(S);
+ Int when integer(Int), Int > 0, Int =< 9 ->
+ case catch lists:nth(Int, S#state.filters) of
+ F when record(F, filter) ->
+ open_viewer(S#state.scale, F#filter.name, [?unknown], S);
+ {'EXIT', _} ->
+ gs:config(S#state.canvas, beep)
+ end,
+ noreply(S);
+
+ 'Shift_L' ->
+ noreply(S);
+ 'Shift_R' ->
+ noreply(S);
+ 'Caps_Lock' ->
+ noreply(S);
+
+ _ ->
+ click_error(Key, S),
+ noreply(S)
+ end;
+handle_info({gs, _Obj,configure, [], [W, H | _]}, S) ->
+ gs:config(S#state.packer, [{width, W}, {height, H}]),
+ S2 = S#state{width = W, height = H},
+ noreply(S2);
+handle_info(timeout, S) ->
+ Try =
+ case S#state.display_mode of
+ {search_actors, reverse, _, _} ->
+ -10;
+ _ ->
+ 10
+ end,
+ if
+ S#state.is_suspended == true ->
+ {noreply, S, infinity};
+ S#state.max_events == infinity ->
+ display_more_events(Try, S);
+ true ->
+ Needed = S#state.max_events - queue_length(S#state.events),
+ if
+ Needed =< 0 -> {noreply, S, infinity};
+ Needed > 10 -> display_more_events(Try, S);
+ Needed =< 10 -> display_more_events(Needed, S)
+ end
+ end;
+
+handle_info({'EXIT', Pid, Reason}, S) ->
+ if
+ Pid == S#state.collector_pid ->
+ unlink(Pid),
+ gs:destroy(S#state.win),
+ {stop, Reason, S};
+ Pid == S#state.parent_pid ->
+ unlink(Pid),
+ gs:destroy(S#state.win),
+ {stop, Reason, S};
+ true ->
+ noreply(S)
+ end;
+handle_info(Info, S) ->
+ ok = error_logger:format("~p(~p): handle_info(~p, ~p)~n",
+ [?MODULE, self(), Info, S]),
+ noreply(S).
+
+%%----------------------------------------------------------------------
+%% Func: terminate/2
+%% Purpose: Shutdown the server
+%% Returns: any (ignored by gen_server)
+%%----------------------------------------------------------------------
+
+terminate(_Reason, _S) ->
+ ignore.
+
+%%----------------------------------------------------------------------
+%% Func: code_change/3
+%% Purpose: Convert process state when code is changed
+%% Returns: {ok, NewState}
+%%----------------------------------------------------------------------
+
+code_change(_OldVsn, S, _Extra) ->
+ {ok, S}.
+
+%%%----------------------------------------------------------------------
+%%% Handle suspend/resume
+%%%----------------------------------------------------------------------
+
+reply(Reply, S) ->
+ case queue_length(S#state.events) of
+ _ when S#state.is_suspended == true ->
+ {reply, Reply, S, infinity};
+ _ when S#state.max_events == infinity ->
+ {reply, Reply, S, 500};
+ N when N >= S#state.max_events ->
+ {reply, Reply, S, infinity};
+ _ ->
+ {reply, Reply, S, 0}
+ end.
+
+noreply(S) ->
+ case queue_length(S#state.events) of
+ _ when S#state.is_suspended == true ->
+ {noreply, S, infinity};
+ _ when S#state.max_events == infinity ->
+ {noreply, S, 500};
+ N when N >= S#state.max_events ->
+ {noreply, S, infinity};
+ _ ->
+ {noreply, S, 0}
+ end.
+
+do_suspend(S) ->
+ config_suspend(S#state{is_suspended = true}).
+
+do_resume(S) ->
+ config_suspend(S#state{is_suspended = false}).
+
+config_suspend(S) ->
+ Suspended = S#state.is_suspended,
+ gs:config(refresh, [{enable, not Suspended}]),
+ gs:config(refresh_all, [{enable, not Suspended}]),
+ gs:config(clear_all, [{enable, not Suspended}]),
+ S.
+
+refresh_main_window(S) ->
+ Pid = S#state.collector_pid,
+ Key = S#state.first_event,
+ case et_collector:iterate(Pid, Key, -1) of
+ Prev when Prev == Key ->
+ scroll_first(S);
+ _Prev ->
+ S2 = S#state{last_event = S#state.first_event},
+ clear_canvas(S2)
+ end.
+
+scroll_first(S) ->
+ S2 = S#state{first_event = first, last_event = first},
+ clear_canvas(S2).
+
+scroll_prev(S) ->
+ Try =
+ case S#state.max_events of
+ infinity -> -10;
+ Max -> -Max
+ end,
+ Key = et_collector:iterate(S#state.collector_pid, S#state.first_event, Try),
+ S2 = S#state{first_event = Key, last_event = Key},
+ clear_canvas(S2).
+
+scroll_next(S) ->
+ S2 = S#state{first_event = S#state.last_event},
+ clear_canvas(S2).
+
+scroll_up(S) ->
+ Key = et_collector:iterate(S#state.collector_pid, S#state.first_event, -5),
+ S2 = S#state{first_event = Key, last_event = Key},
+ clear_canvas(S2).
+
+scroll_down(S) ->
+ Key = et_collector:iterate(S#state.collector_pid, S#state.first_event, 5),
+ S2 = S#state{first_event = Key, last_event = Key},
+ clear_canvas(S2).
+
+scroll_last(S) ->
+ S2 = S#state{first_event = last, last_event = last},
+ clear_canvas(S2).
+
+change_display_mode(Mode, S) ->
+ case Mode of
+ all ->
+ S2 = S#state{display_mode = Mode},
+ refresh_main_window(S2);
+ {search_actors, _Dir, _Key, []} ->
+ S2 = S#state{display_mode = all},
+ refresh_main_window(S2);
+ {search_actors, _Dir, Key, Actors} when list(Actors) ->
+ Pid = S#state.collector_pid,
+ Prev = et_collector:iterate(Pid, Key, -1),
+ S2 = S#state{first_event = Prev,
+ last_event = Prev,
+ display_mode = Mode},
+ clear_canvas(S2)
+ end.
+
+close_all(S) ->
+ et_collector:multicast(S#state.collector_pid, close),
+ timer:sleep(timer:seconds(1)),
+ spawn(et_collector, stop, [S#state.collector_pid]),
+ gs:destroy(S#state.win),
+ {stop, shutdown, S}.
+
+close_all_others(S) ->
+ Fun =
+ fun({{subscriber, Pid}, _}) ->
+ if
+ Pid == self() ->
+ ignore;
+ true ->
+ unlink(Pid),
+ Pid ! {et, close}
+ end
+ end,
+ All = et_collector:dict_match(S#state.collector_pid,
+ {{subscriber, '_'}, '_'}),
+ lists:foreach(Fun, All),
+ noreply(S).
+
+click_error(Click, S) ->
+ gs:config(S#state.canvas, beep),
+ io:format("~p: ignored: ~p~n", [?MODULE, Click]).
+
+%%%----------------------------------------------------------------------
+%%% Clone viewer
+%%%----------------------------------------------------------------------
+
+open_viewer(Scale, FilterName, Actors, S) ->
+ Filters = [{dict_insert, {filter, F#filter.name}, F#filter.function}
+ || F <- S#state.filters],
+ Options =
+ [{parent_pid, S#state.parent_pid},
+ {title, S#state.title},
+ {collector_pid, S#state.collector_pid},
+ {is_suspended, S#state.is_suspended},
+ {detail_level, S#state.detail_level},
+ {active_filter, FilterName},
+ {event_order, S#state.event_order},
+ {first_event, S#state.first_event},
+ {max_events, S#state.max_events},
+ {max_actors, S#state.max_actors},
+ {hide_actions, S#state.hide_actions},
+ {hide_unknown, S#state.hide_unknown},
+ {is_suspended, S#state.is_suspended},
+ {actors, Actors},
+ {scale, Scale},
+ {width, S#state.width},
+ {height, S#state.height} | Filters],
+ case start_link(Options) of
+ {ok, ViewerPid} ->
+ unlink(ViewerPid),
+ ok;
+ {error, Reason} ->
+ ok = error_logger:format("~p: Failed to start a new window: ~p~n",
+ [?MODULE, Reason])
+ end.
+
+%%%----------------------------------------------------------------------
+%%% Handle graphics
+%%%----------------------------------------------------------------------
+
+create_main_window(S) ->
+ Font = select_font(S#state.scale),
+ GS = gs:start(),
+ Name = name_to_string(S#state.active_filter),
+ Title = case S#state.title of
+ undefined -> atom_to_list(?MODULE);
+ Explicit -> name_to_string(Explicit)
+ end,
+ WinOpt = [{title, Title ++ " (filter: " ++ Name ++ ")"},
+ {configure, true},
+ {width, S#state.width},
+ {height, S#state.height}],
+ Win = gs:window(GS, WinOpt),
+ Bar = gs:menubar(Win, []),
+
+ create_file_menu(Bar),
+ create_viewer_menu(Bar),
+ create_collector_menu(Bar),
+ gs:menubutton(filter_button, Bar, [{label, {text, "Filter"}}]),
+ create_filter_menu(S#state.active_filter, S#state.filters),
+ create_help_menu(Bar),
+
+ config_suspend(S),
+
+ PackerOpt = [{packer_x, [{fixed, 5}, {fixed, 40}, {fixed, 40},
+ {stretch, 1}, {fixed, 5}]},
+ {packer_y, [{fixed, 30}, {fixed, 30},
+ {stretch, 1}, {fixed, 30}]},
+ {x, 0}, {y, 30}],
+ Packer = gs:frame(Win, PackerOpt),
+ gs:checkbutton(suspended, Packer, [{label,{text,"Freeze"}},
+ {x, 10}, {y, 0},
+ {width, 120}, {align, w},
+ {select, S#state.is_suspended}]),
+ gs:checkbutton(hide_actions, Packer, [{label,{text,"Hide From=To"}},
+ {x, 10}, {y, 20},
+ {width, 120}, {align, w},
+ {select, S#state.hide_actions}]),
+ gs:checkbutton(hide_unknown, Packer, [{label,{text,"Hide Unknown"}},
+ {x, 10}, {y, 40},
+ {width, 120}, {align, w},
+ {select, S#state.hide_unknown}]),
+ gs:scale(Packer, [{text,"Detail Level"},
+ {range, {?detail_level_min, ?detail_level_max}},
+ {orient, horizontal},
+ {x, 150}, {y, 0}, {height, 65}, {width, 200},
+ {pos, S#state.detail_level}, {data, detail_level}]),
+ CanvasW = calc_canvas_width(S),
+ CanvasH = calc_canvas_height(S),
+ CanOpt = [{pack_xy, {{2, 4}, 3}}, {vscroll, right}, {hscroll, bottom},
+ {scrollregion, {2, 2, CanvasW, CanvasH}}],
+ Canvas = gs:canvas(Packer, CanOpt),
+ gs:config(Canvas, [{buttonpress, true}, {buttonrelease, true}]),
+ gs:config(Packer, [{width, S#state.width}, {height, S#state.height}]),
+ gs:config(Win, [{map, true}, {keypress, true}]),
+ S2 = S#state{title = Title,
+ win = Win, font = Font, packer = Packer,
+ canvas_width = CanvasW, canvas_height = CanvasH,
+ canvas = Canvas,
+ y_pos = ?initial_y * S#state.scale},
+ draw_all_actors(S2).
+
+select_font(Scale) when integer(Scale) ->
+ case Scale of
+ 1 -> {courier, 7};
+ 2 -> {courier, 10};
+ 3 -> {courier, 12};
+ 4 -> {courier, 14};
+ S -> {courier, S * 4}
+ end.
+
+create_file_menu(Bar) ->
+ Button = gs:menubutton(Bar, [{label, {text, "File"}}]),
+ Menu = gs:menu(Button, []),
+ gs:menuitem(close_all, Menu, [{label, {text, "Close Collector and all Viewers (C) "}}]),
+ gs:menuitem(close_all_others, Menu, [{label, {text, "Close other Viewers, but keep Collector (c)"}}]),
+ gs:menuitem(close, Menu, [{label, {text, "Close this Viewer, but keep Collector"}}]),
+ gs:menuitem(Menu, [{itemtype, separator}]),
+
+ gs:menuitem(clear_all, Menu, [{label, {text, "Clear Collector"}}]),
+ gs:menuitem(load_all, Menu, [{label, {text, "Load Collector from the file \"et_viewer.log\""}}]),
+ gs:menuitem(save_all, Menu, [{label, {text, "Save Collector to the file \"et_viewer.log\""}}]).
+
+create_viewer_menu(Bar) ->
+ Button = gs:menubutton(Bar, [{label, {text, "Viewer"}}]),
+ Menu = gs:menu(Button, []),
+ gs:menuitem(Menu, [{label, {text, "Scroll this Viewer"}}, {bg, lightblue}, {enable,false}]),
+ gs:menuitem(Menu, [{itemtype, separator}]),
+ gs:menuitem(first, Menu, [{label, {text, "First (f)"}}]),
+ gs:menuitem(prev, Menu, [{label, {text, "Prev (p)"}}]),
+ gs:menuitem(next, Menu, [{label, {text, "Next (n)"}}]),
+ gs:menuitem(last, Menu, [{label, {text, "Last (l)"}}]),
+ gs:menuitem(refresh, Menu, [{label, {text, "Refresh (r)"}}]),
+ gs:menuitem(Menu, [{itemtype, separator}]),
+ gs:menuitem(up, Menu, [{label, {text, "Up 5 (Up)"}}]),
+ gs:menuitem(down, Menu, [{label, {text, "Down 5 (Down)"}}]),
+ gs:menuitem(Menu, [{itemtype, separator}]),
+ gs:menuitem(Menu, [{label, {text, "Search in this Viewer"}}, {bg, lightblue}, {enable,false}]),
+ gs:menuitem(Menu, [{itemtype, separator}]),
+ gs:menuitem({mode, all}, Menu, [{label, {text, "Abort search. Display all (a)"}}]).
+
+create_collector_menu(Bar) ->
+ Button = gs:menubutton(Bar, [{label, {text, "Collector"}}]),
+ Menu = gs:menu(Button, []),
+ gs:menuitem(Menu, [{label, {text, "Scroll all Viewers"}}, {bg, lightblue}, {enable,false}]),
+ gs:menuitem(Menu, [{itemtype, separator}]),
+ gs:menuitem(first_all, Menu, [{label, {text, "First (F)"}}]),
+ gs:menuitem(prev_all, Menu, [{label, {text, "Prev (P)"}}]),
+ gs:menuitem(next_all, Menu, [{label, {text, "Next (N)"}}]),
+ gs:menuitem(last_all, Menu, [{label, {text, "Last (L)"}}]),
+ gs:menuitem(refresh_all, Menu, [{label, {text, "Refresh (R)"}}]).
+
+create_filter_menu(ActiveFilterName, Filters) ->
+ Menu = gs:menu(filter_menu, filter_button, []),
+ Item = fun(F, N) when F#filter.name == collector ->
+ Label = lists:concat([pad_string(F#filter.name, 20), "(0)"]),
+ gs:menuitem(Menu, [{label, {text, Label}}, {data, F}]),
+ N + 1;
+ (F, N) ->
+ Label = lists:concat([pad_string(F#filter.name, 20), "(", N, ")"]),
+ gs:menuitem(Menu, [{label, {text, Label}}, {data, F}]),
+ N + 1
+ end,
+ gs:menuitem(Menu, [{label, {text, "Same Filter New Scale"}}, {bg, lightblue}, {enable,false}]),
+ gs:menuitem(Menu, [{itemtype, separator}]),
+ {value, Filter} = lists:keysearch(ActiveFilterName, #filter.name, Filters),
+ Same = lists:concat([pad_string(ActiveFilterName, 20), "(=)"]),
+ Larger = lists:concat([pad_string(ActiveFilterName, 20), "(+)"]),
+ Smaller = lists:concat([pad_string(ActiveFilterName, 20), "(-)"]),
+ gs:menuitem(Menu, [{label, {text, Same}}, {data, Filter}]),
+ gs:menuitem(Menu, [{label, {text, Smaller}}, {data, Filter}]),
+ gs:menuitem(Menu, [{label, {text, Larger}}, {data, Filter}]),
+ gs:menuitem(Menu, [{itemtype, separator}]),
+ gs:menuitem(Menu, [{label, {text, "New Filter Same Scale"}}, {bg, lightblue}, {enable,false}]),
+ gs:menuitem(Menu, [{itemtype, separator}]),
+ lists:foldl(Item, 1, Filters).
+
+create_help_menu(Bar) ->
+ Button = gs:menubutton(Bar, [{label, {text, "Help"}}]),
+ Menu = gs:menu(Button, []),
+ gs:menuitem(Menu, [{label, {text, "Display details of an event"}},
+ {bg, lightblue}, {enable,false}]),
+ gs:menuitem(Menu, [{label, {text, " Single click on the name tag or the arrow (Mouse-1)"}},
+ {enable,false}]),
+ gs:menuitem(Menu, [{itemtype, separator}]),
+ gs:menuitem(Menu, [{label, {text, "Toggle actor search"}},
+ {bg, lightblue}, {enable,false}]),
+ gs:menuitem(Menu, [{label, {text, " Single click on the name tag (Mouse-1)"}},
+ {enable,false}]),
+ gs:menuitem(Menu, [{itemtype, separator}]),
+ gs:menuitem(Menu, [{label, {text, "Move actor"}},
+ {bg, lightblue}, {enable,false}]),
+ gs:menuitem(Menu, [{label, {text, " se drag and drop on name tag (Mouse-1)"}},
+ {enable,false}]).
+
+clear_canvas(S) ->
+ gs:destroy(S#state.canvas),
+ CanvasW = calc_canvas_width(S),
+ CanvasH = calc_canvas_height(S),
+ CanOpt = [{pack_xy, {{2, 4}, 3}}, {vscroll, right}, {hscroll, bottom},
+ {scrollregion, {2, 2, CanvasW, CanvasH}}],
+ Canvas = gs:canvas(S#state.packer, CanOpt),
+ gs:config(S#state.packer, [{width, S#state.width}, {height, S#state.height}]),
+ gs:config(Canvas, [{buttonpress, true}, {buttonrelease, true}]),
+ S2 = S#state{refresh_needed = false,
+ y_pos = ?initial_y * S#state.scale,
+ canvas = Canvas,
+ canvas_width = CanvasW,
+ canvas_height = CanvasH,
+ events = queue_new()},
+ draw_all_actors(S2).
+
+calc_canvas_width(S) ->
+ Min = calc_min_actors(S),
+ CanvasW = ((2 * ?initial_x) + (Min * ?incr_x)) * S#state.scale,
+ lists:max([CanvasW, S#state.width - (15 * S#state.scale), S#state.canvas_width]).
+
+calc_canvas_height(S) ->
+ Min = calc_min_events(S),
+ CanvasH = ((2 * ?initial_y) + (Min * ?incr_y)) * S#state.scale,
+ lists:max([CanvasH, S#state.height - (4 * 30), S#state.canvas_height]).
+
+calc_min_actors(S) ->
+ Max = S#state.max_actors,
+ N = length(S#state.actors),
+ if
+ Max == infinity ->
+ N * 2;
+ Max < N ->
+ N;
+ true ->
+ Max
+ end.
+
+calc_min_events(S) ->
+ Max = S#state.max_events,
+ N = queue_length(S#state.events),
+ if
+ Max == infinity ->
+ N * 2;
+ Max < N ->
+ N;
+ true ->
+ Max
+ end.
+
+display_more_events(Try, S) ->
+ Name = S#state.active_filter,
+ {value, F} = lists:keysearch(Name, #filter.name, S#state.filters),
+ FilterFun = F#filter.function,
+ Fun = fun(Event, State) ->
+ case catch FilterFun(Event) of
+ true ->
+ State2 = ensure_key(Event, State),
+ opt_display_event(Event, State2);
+ {true, Event2} ->
+ State2 = ensure_key(Event2, State),
+ opt_display_event(Event2, State2);
+ false ->
+ ensure_key(Event, State);
+ Bad ->
+ Contents = {bad_filter, Name, Bad, Event},
+ Event2 = Event#event{contents = Contents,
+ from = bad_filter,
+ to = bad_filter},
+ State2 = ensure_key(Event2, State),
+ opt_display_event(Event2, State2)
+ end
+ end,
+ Pid = S#state.collector_pid,
+ S2 = et_collector:iterate(Pid, S#state.last_event, Try, Fun, S),
+ case queue_length(S2#state.events) - queue_length(S#state.events) of
+ Diff when Diff == Try ->
+ %% Got as much as requested, look for more
+ %% io:format("Done: ~p~n", [{Try, Diff}]),
+ {noreply, S2, 0};
+ _Diff when S2#state.first_event == S#state.first_event,
+ S2#state.last_event == S#state.last_event ->
+ %% Got lesser than requested, wait a while before looking for more
+ %% io:format("More: ~p~n", [{Try, Diff}]),
+ {noreply, S2, 500};
+ _Diff ->
+ %% Got lesser than requested, look for more
+ %% io:format("More2: ~p~n", [{Try, Diff}]),
+ {noreply, S2, 0}
+ end.
+
+ensure_key(E, S) when record(E, event), record(S, state) ->
+ Key = et_collector:make_key(S#state.event_order, E),
+ case S#state.first_event of
+ first ->
+ S#state{first_event = Key, last_event = Key};
+ last ->
+ S#state{first_event = Key, last_event = Key};
+ _ ->
+ S#state{last_event = Key}
+ end.
+
+opt_display_event(E, S) ->
+ case S#state.display_mode of
+ all ->
+ display_event(E, S);
+ {search_actors, _Dir, _FirstKey, Actors} ->
+ %% Key = S#state.last_event,
+ From = select_actor_name(E#event.from, S),
+ case lists:member(From, Actors) of
+ true ->
+ display_event(E, S);
+ false ->
+ To = select_actor_name(E#event.to, S),
+ case lists:member(To, Actors) of
+ true ->
+ display_event(E, S);
+ false ->
+ S
+ end
+ end
+ end.
+
+select_actor_name(Name, S) ->
+ case lists:keymember(Name, #actor.name, S#state.actors) of
+ true -> Name;
+ false -> ?unknown
+ end.
+
+display_event(E, S) when E#event.detail_level < S#state.detail_level ->
+ {FromRefresh, From} = ensure_actor(E#event.from, S),
+ {FromName, FromPos, S2} = From,
+ {ToRefresh, To} = ensure_actor(E#event.to, S2),
+ {ToName, ToPos, S3} = To,
+ if
+ FromRefresh /= false, ToRefresh /= false ->
+ Key = S#state.last_event,
+ refresh_beep(S),
+ S3#state{refresh_needed = true,
+ events = queue_in(Key, S3#state.events)};
+ FromName == ToName ->
+ case S#state.hide_actions of
+ true ->
+ S3;
+ false ->
+ Label = name_to_string(E#event.label),
+ draw_named_arrow(Label, FromName, FromPos, ToName, ToPos, S3)
+ end;
+ true ->
+ Label = name_to_string(E#event.label),
+ draw_named_arrow(Label, FromName, FromPos, ToName, ToPos, S3)
+ end;
+display_event(_, S) ->
+ S.
+
+draw_named_arrow(Label, FromName, FromPos, ToName, ToPos, S) ->
+ Key = S#state.last_event,
+ case S#state.y_pos + (?incr_y * S#state.scale) of
+ _ when S#state.hide_unknown == true, FromName == ?unknown ->
+ S;
+ _ when S#state.hide_unknown == true, ToName == ?unknown ->
+ S;
+ Y when Y > S#state.canvas_height ->
+ refresh_beep(S),
+ S#state{refresh_needed = true,
+ events = queue_in(Key, S#state.events)};
+ Y ->
+ S2 = S#state{y_pos = Y, events = queue_in(Key, S#state.events)},
+ S3 = draw_arrow(FromPos, ToPos, S2),
+ draw_label(Label, FromName, ToName, FromPos, ToPos, S3)
+ end.
+
+refresh_beep(S) ->
+ case S#state.refresh_needed of
+ false ->
+ gs:config(S#state.canvas, beep),
+ gs:config(S#state.canvas, beep),
+ gs:config(S#state.canvas, beep);
+ true ->
+ ignore
+ end.
+
+draw_arrow(Pos, Pos, S) ->
+ S;
+draw_arrow(FromPos, ToPos, S) ->
+ Y = S#state.y_pos,
+ CanOpts = [{coords, [{FromPos , Y}, {ToPos, Y}]},
+ {arrow, last},{width, 1}, {fg, black}],
+ gs:line(S#state.canvas, CanOpts),
+ S.
+
+draw_label(Label, FromName, ToName, FromPos, ToPos, S) ->
+ Colour =
+ if
+ FromName == ?unknown,
+ ToName == ?unknown -> blue; %turquoise;
+ FromName == ?unknown -> orange;
+ ToName == ?unknown -> orange;
+ FromPos == ToPos -> blue;
+ true -> red
+ end,
+ Scale = S#state.scale,
+ X = lists:min([FromPos, ToPos]) + (6 * Scale),
+ Y = S#state.y_pos,
+ write_text(Label, X, Y, Colour, S),
+ S.
+
+draw_all_actors(State) ->
+ Scale = State#state.scale,
+ Fun = fun(A, X) ->
+ draw_actor(A, X, State),
+ X + (?incr_x * Scale)
+ end,
+ lists:foldl(Fun, ?initial_x * Scale, State#state.actors),
+ State.
+
+%% Returns: {NeedsRefreshBool, {ActorPos, NewsS, NewActors}}
+ensure_actor(Name, S) ->
+ do_ensure_actor(Name, S, S#state.actors, 0).
+
+do_ensure_actor(Name, S, [H | _], N) when H#actor.name == Name ->
+ Pos = (?initial_x + (N * ?incr_x)) * S#state.scale,
+ {false, {Name, Pos, S}};
+do_ensure_actor(Name, S, [_ | T], N) ->
+ do_ensure_actor(Name, S, T, N + 1);
+do_ensure_actor(Name, S, [], N) ->
+ %% A brand new actor, let's see if it does fit
+ Pos = (?initial_x + (N * ?incr_x)) * S#state.scale,
+ MaxActors = S#state.max_actors,
+ if
+ integer(MaxActors), N > MaxActors ->
+ %% Failed on max_actors limit, put into unknown
+ %% Assume that unknown always is in actor list
+ ensure_actor(?unknown, S);
+ Pos > (S#state.canvas_width - ((?initial_x - 15) * S#state.scale)) ->
+ %% New actor does not fit in canvas, refresh needed
+ A = create_actor(Name),
+ draw_actor(A, Pos, S),
+ {true, {Name, Pos, S#state{actors = S#state.actors ++ [A]}}};
+ true ->
+ %% New actor fits in canvas. Draw the new actor.
+ A = create_actor(Name),
+ draw_actor(A, Pos, S),
+ {false, {Name, Pos, S#state{actors = S#state.actors ++ [A]}}}
+ end.
+
+draw_actor(A, LineX, S) ->
+ Scale = S#state.scale,
+ TextX = LineX - (5 * Scale),
+ TextY = ?initial_y * Scale,
+ LineTopY = TextY + ((?incr_y / 2) * Scale),
+ LineBotY = S#state.canvas_height - ((?incr_y / 2) * Scale),
+ Colour = case A#actor.name of
+ ?unknown -> orange;
+ _ -> red
+ end,
+ write_text(A#actor.string, TextX, TextY, Colour, S),
+ LineOpt = [{coords, [{LineX, LineTopY}, {LineX, LineBotY}]},
+ {width, 1}, {fg, Colour}],
+ gs:line(S#state.canvas, LineOpt).
+
+toggle_search_for_actor(ActorName,S) ->
+ case S#state.display_mode of
+ all ->
+ io:format("~p: search for: ~p ++ ~p~n", [?MODULE, [], [ActorName]]),
+ %% Search for this actor
+ Key = S#state.first_event,
+ Actors = [ActorName],
+ Mode = {search_actors, forward, Key, Actors},
+ change_display_mode(Mode, S);
+ {search_actors, Dir, Key, Actors}->
+ Actors2 =
+ case lists:member(ActorName, Actors) of
+ true ->
+ io:format("~p: search for: ~p -- ~p~n", [?MODULE, Actors, [ActorName]]),
+ %% Remove actor from search list
+ Actors -- [ActorName];
+ false ->
+ io:format("~p: search for: ~p ++ ~p~n", [?MODULE, Actors, [ActorName]]),
+ %% Add actor from search list
+ [ActorName | Actors]
+ end,
+ Mode2 = {search_actors, Dir, Key, Actors2},
+ change_display_mode(Mode2, S)
+ end.
+
+move_actor(From, To, Actors, S) ->
+ Pos = #actor.name,
+ ToName = To#actor.name,
+ FromName = From#actor.name,
+ ToIx = actor_index(ToName, Pos, Actors),
+ FromIx = actor_index(FromName, Pos, Actors),
+ if
+ FromIx /= 0, ToIx /= 0, ToIx > FromIx ->
+ Actors2 = lists:keydelete(FromName, Pos, Actors),
+ Actors3 = insert_actor_after(From, To, Actors2),
+ S2 = S#state{actors = Actors3},
+ refresh_main_window(S2);
+ FromIx /= 0, ToIx /= 0 ->
+ Actors2 = lists:keydelete(FromName, Pos, Actors),
+ Actors3 = insert_actor_before(From, To, Actors2),
+ S2 = S#state{actors = Actors3},
+ refresh_main_window(S2);
+ true ->
+ %% Ignore
+ S
+ end.
+
+insert_actor_after(From, To, [H | T]) ->
+ case To#actor.name == H#actor.name of
+ true -> [H, From | T];
+ false -> [H | insert_actor_after(From, To, T)]
+ end;
+insert_actor_after(_From, _To, []) ->
+ [].
+
+insert_actor_before(From, To, [H | T]) ->
+ case To#actor.name == H#actor.name of
+ true -> [From, H | T];
+ false -> [H | insert_actor_before(From, To, T)]
+ end;
+insert_actor_before(_From, _To, []) ->
+ [].
+
+actor_index(_Key, _Pos, []) ->
+ 0;
+actor_index(Key, Pos, [H | T]) ->
+ case Key == element(Pos, H) of
+ false -> actor_index(Key, Pos, T) + 1;
+ true -> 1
+ end.
+
+y_to_n(Y, S) ->
+ Y2 = ((Y / S#state.scale) - ?initial_y + (?incr_y / 2)),
+ N = round(Y2 / ?incr_y - 0.2),
+ MaxN = queue_length(S#state.events),
+ if
+ N =< 0 -> actor;
+ N > MaxN -> actor;
+ true -> {event, N}
+ end.
+
+x_to_n(X, S) ->
+ Scale = S#state.scale,
+ Len = length(S#state.actors),
+ X2 = X - (?initial_x * Scale),
+ N = X2 / (?incr_x * Scale),
+ N2 = trunc(N + 1.5),
+ if
+ N2 > Len -> Len;
+ N2 < 1 -> 1;
+ true -> N2
+ end.
+
+write_text(Text, X, Y, Colour, S) ->
+ Opt = [{coords, [{X, Y - (?incr_y * S#state.scale / 2)}]},
+ {font, S#state.font}, {fg, Colour}, {text, Text}],
+ gs:text(S#state.canvas, Opt).
+
+create_contents_window(Event, S) ->
+ Options = [{viewer_pid, self()},
+ {event, Event},
+ {event_order, S#state.event_order},
+ {active_filter, S#state.active_filter}
+ | S#state.filters],
+ case et_contents_viewer:start_link(Options) of
+ {ok, _Pid} ->
+ S;
+ {error, Reason} ->
+ ok = error_logger:format("~p(~p): create_contents_window(~p) ->~n ~p~n",
+ [?MODULE, self(), Options, Reason]),
+ S
+ end.
+
+%%%----------------------------------------------------------------------
+%%% String padding of actors
+%%%----------------------------------------------------------------------
+
+create_actor(Name) ->
+ String = name_to_string(Name),
+ PaddedString = pad_string(String, 8),
+ #actor{name = Name, string = PaddedString}.
+
+name_to_string(Name) ->
+ case catch io_lib:format("~s", [Name]) of
+ {'EXIT', _} -> lists:flatten(io_lib:format("~w", [Name]));
+ GoodString -> lists:flatten(GoodString)
+ end.
+
+pad_string(Atom, MinLen) when atom(Atom) ->
+ pad_string(atom_to_list(Atom), MinLen);
+pad_string(String, MinLen) when integer(MinLen), MinLen >= 0 ->
+ Len = length(String),
+ case Len >= MinLen of
+ true ->
+ String;
+ false ->
+ String ++ lists:duplicate(MinLen - Len, $ )
+ end.
+
+%%%----------------------------------------------------------------------
+%%% Queue management
+%%%----------------------------------------------------------------------
+
+queue_new() ->
+ {0, [], []}.
+
+queue_in(X, {Size, In, Out}) ->
+ {Size + 1, [X | In], Out}.
+
+%% queue_out(Q) ->
+%% case Q of
+%% {Size, In, [H | Out]} -> {{value, H}, {Size - 1, In, Out}};
+%% {Size, [], []} -> {empty, {Size, [], []}};
+%% {Size, In, _} -> queue_out({Size, [], lists:reverse(In)})
+%% end.
+
+queue_to_list({_Size, [], Out}) ->
+ Out;
+queue_to_list({_Size, In, Out}) ->
+ Out ++ lists:reverse(In).
+
+queue_length({Size, _In, _Out}) ->
+ Size.
+
+list_to_queue(List) when list(List) ->
+ {length(List), [], List}.
diff --git a/lib/et/src/modules.mk b/lib/et/src/modules.mk
new file mode 100644
index 0000000000..8e9dd1a386
--- /dev/null
+++ b/lib/et/src/modules.mk
@@ -0,0 +1,32 @@
+#-*-makefile-*- ; force emacs to enter makefile-mode
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2001-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%
+
+MODULES = \
+ et \
+ et_collector \
+ et_contents_viewer \
+ et_selector \
+ et_viewer
+
+HRL_FILES = \
+ ../include/et.hrl
+
+INTERNAL_HRL_FILES = \
+ et_internal.hrl
+
+