diff options
author | HÃ¥kan Mattsson <[email protected]> | 2010-02-03 08:59:06 +0000 |
---|---|---|
committer | Erlang/OTP <[email protected]> | 2010-02-03 08:59:06 +0000 |
commit | 43f3482adf5eee657e5ba922733dfff6600c4e14 (patch) | |
tree | 7ea7b32a171de1a7690102c403a8a946e8a382a8 /lib/et/src | |
parent | 768da5a5f6312496b9b8a09cca5ea1d6b89a2c1c (diff) | |
download | otp-43f3482adf5eee657e5ba922733dfff6600c4e14.tar.gz otp-43f3482adf5eee657e5ba922733dfff6600c4e14.tar.bz2 otp-43f3482adf5eee657e5ba922733dfff6600c4e14.zip |
OTP-8058 The GUI parts are rewritten to use wxWidgets. Thanks Olle
Mattsson!
For the time being it is still possible to use the old GS based
version of the tool, but it is deprecated. The wxWidgets based
version is started by default.
A new tutorial has been added to the documentation. It is based
on Jayson Vantuyl's article
http://souja.net/2009/04/making-sense-of-erlangs-event-tracer.htm
l.
The functions et:trace_me/4 and et:trace_me/5 has been introduced
in order to replace the deprecated functions et:report_event/4
and et:report_event/5. Hopefully the new names makes it a little
more obvious what the intended usage of the functions are.
A print function has been added to the GUI, in order to enable
printing of sequence charts.
More functionality for hiding unwanted events has been added to
the GUI.
The max_events, hide_unknown and display_mode configuration
parameters to et_viewer is not used any more. Now the event cache
in the Viewer only contains those events that actually are
displayed in the GUI.
Diffstat (limited to 'lib/et/src')
-rw-r--r-- | lib/et/src/Makefile | 17 | ||||
-rw-r--r-- | lib/et/src/et.app.src | 17 | ||||
-rw-r--r-- | lib/et/src/et.erl | 38 | ||||
-rw-r--r-- | lib/et/src/et_collector.erl | 330 | ||||
-rw-r--r-- | lib/et/src/et_gs_contents_viewer.erl (renamed from lib/et/src/et_contents_viewer.erl) | 86 | ||||
-rw-r--r-- | lib/et/src/et_gs_viewer.erl | 1483 | ||||
-rw-r--r-- | lib/et/src/et_internal.hrl | 13 | ||||
-rw-r--r-- | lib/et/src/et_selector.erl | 76 | ||||
-rw-r--r-- | lib/et/src/et_viewer.erl | 1492 | ||||
-rw-r--r-- | lib/et/src/et_wx_contents_viewer.erl | 700 | ||||
-rw-r--r-- | lib/et/src/et_wx_viewer.erl | 2124 | ||||
-rw-r--r-- | lib/et/src/modules.mk | 17 |
12 files changed, 4724 insertions, 1669 deletions
diff --git a/lib/et/src/Makefile b/lib/et/src/Makefile index c590852625..bb6632ee91 100644 --- a/lib/et/src/Makefile +++ b/lib/et/src/Makefile @@ -1,19 +1,19 @@ # # %CopyrightBegin% -# -# Copyright Ericsson AB 2000-2009. All Rights Reserved. -# +# +# Copyright Ericsson AB 2000-2010. 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% # @@ -127,9 +127,16 @@ $(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_gs_contents_viewer.$(EMULATOR): et_gs_contents_viewer.erl ../include/et.hrl et_internal.hrl +$(EBIN)/et_wx_contents_viewer.$(EMULATOR): et_wx_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 +$(EBIN)/et_gs_viewer.$(EMULATOR): et_gs_viewer.erl ../include/et.hrl et_internal.hrl + +$(EBIN)/et_wx_viewer.$(EMULATOR): et_wx_viewer.erl ../include/et.hrl et_internal.hrl + diff --git a/lib/et/src/et.app.src b/lib/et/src/et.app.src index 0c7bef7c3d..dc22ce4223 100644 --- a/lib/et/src/et.app.src +++ b/lib/et/src/et.app.src @@ -1,20 +1,20 @@ %% This is an -*- erlang -*- file. %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2002-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2002-2010. 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, @@ -25,8 +25,13 @@ et, et_collector, et_contents_viewer, + et_gs_contents_viewer, + et_gs_viewer, et_selector, - et_viewer + et_viewer, + et_viewer, + et_wx_contents_viewer, + et_wx_viewer ]}, {registered, [et_collector]}, {applications, [stdlib, kernel]}, diff --git a/lib/et/src/et.erl b/lib/et/src/et.erl index 9c0a7f8f49..e2cd8564c3 100644 --- a/lib/et/src/et.erl +++ b/lib/et/src/et.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2000-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2000-2010. 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% %% %%---------------------------------------------------------------------- @@ -84,13 +84,15 @@ -module(et). -export([ - phone_home/4, report_event/4, - phone_home/5, report_event/5 + trace_me/4, phone_home/4, report_event/4, + trace_me/5, phone_home/5, report_event/5 ]). %%---------------------------------------------------------------------- %% Reports an event, such as a message %% +%% trace_me(DetailLevel, FromTo, Label, Contents) -> hopefully_traced +%% trace_me(DetailLevel, From, To, Label, Contents) -> hopefully_traced %% 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 @@ -123,18 +125,28 @@ %% Other events (termed actions) may be undirected and only have one actor. %%---------------------------------------------------------------------- +trace_me(DetailLevel, FromTo, Label, Contents) + when is_integer(DetailLevel) -> + ?MODULE:trace_me(DetailLevel, FromTo, FromTo, Label, Contents). + +trace_me(DetailLevel, _From, _To, _Label, _Contents) + when is_integer(DetailLevel) -> + hopefully_traced. + phone_home(DetailLevel, FromTo, Label, Contents) -> %% N.B External call - ?MODULE:report_event(DetailLevel, FromTo, FromTo, Label, Contents). + ?MODULE:trace_me(DetailLevel, FromTo, FromTo, Label, Contents). phone_home(DetailLevel, From, To, Label, Contents) -> %% N.B External call - ?MODULE:report_event(DetailLevel, From, To, Label, Contents). + ?MODULE:trace_me(DetailLevel, From, To, Label, Contents). report_event(DetailLevel, FromTo, Label, Contents) -> %% N.B External call - ?MODULE:report_event(DetailLevel, FromTo, FromTo, Label, Contents). + ?MODULE:trace_me(DetailLevel, FromTo, FromTo, Label, Contents). + +report_event(DetailLevel, From, To, Label, Contents) + when is_integer(DetailLevel) -> + %% N.B External call + ?MODULE:trace_me(DetailLevel, From, To, 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 index ea23c188f7..289537541d 100644 --- a/lib/et/src/et_collector.erl +++ b/lib/et/src/et_collector.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2000-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2000-2010. 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% %% %%---------------------------------------------------------------------- @@ -36,6 +36,7 @@ iterate/3, iterate/5, + lookup/2, start_trace_client/3, start_trace_port/1, @@ -45,6 +46,7 @@ get_global_pid/0, %% get_table_handle/1, + get_table_size/1, change_pattern/2, make_key/2, @@ -59,9 +61,12 @@ -export([init/1,terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2]). +-include("et_internal.hrl"). -include("../include/et.hrl"). -record(state, {parent_pid, + auto_shutdown, % Optionally shutdown when the last subscriber dies + event_tab_size, event_tab, dict_tab, event_order, @@ -102,7 +107,7 @@ %% 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 +%% predefined key {filter, all} 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. %% @@ -126,7 +131,7 @@ %% option() = %% {parent_pid, pid()} | %% {event_order, event_order()} | -%% {dict_insert, {filter, collector}, collector_fun()} | +%% {dict_insert, {filter, all}, collector_fun()} | %% {dict_insert, {filter, event_filter_name()}, event_filter_fun()} | %% {dict_insert, {subscriber, pid()}, dict_val()} | %% {dict_insert, dict_key(), dict_val()} | @@ -159,19 +164,16 @@ 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() -> + {ok, S, Dict2, Clients} -> + Res = + case S#state.trace_global of + false -> + gen_server:start_link(?MODULE, [S, Dict2], []); + true -> + gen_server:start_link({global, ?MODULE}, ?MODULE, [S, Dict2], []) + end, + case Res of + {ok, Pid} when S#state.parent_pid =/= self() -> unlink(Pid), start_clients(Pid, Clients); {ok,Pid} -> @@ -185,6 +187,7 @@ start_link(Options) -> default_state() -> #state{parent_pid = self(), + auto_shutdown = false, event_order = trace_ts, subscribers = [], trace_global = false, @@ -196,28 +199,30 @@ default_state() -> 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}, + Default = {dict_insert, {filter, ?DEFAULT_FILTER_NAME}, 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 -> + {parent_pid, Parent} when Parent =:= undefined -> parse_opt(T, S#state{parent_pid = Parent}, Dict, Clients); - {parent_pid, Parent} when pid(Parent) -> + {parent_pid, Parent} when is_pid(Parent) -> parse_opt(T, S#state{parent_pid = Parent}, Dict, Clients); - {event_order, Order} when Order == trace_ts -> + {auto_shutdown, Bool} when Bool =:= true; Bool =:= false -> + parse_opt(T, S#state{auto_shutdown = Bool}, 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 -> + {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) -> + is_atom(Name), is_function(Fun) -> parse_opt(T, S, Dict ++ [H], Clients); true -> {error, {bad_option, H}} end; {dict_insert, {subscriber, Pid}, _Val} -> if - pid(Pid) -> + is_pid(Pid) -> parse_opt(T, S, Dict ++ [H], Clients); true -> {error, {bad_option, H}} @@ -228,17 +233,17 @@ parse_opt([H | T], S, Dict, Clients) -> parse_opt(T, S, Dict ++ [H], Clients); {trace_client, Client = {_, _}} -> parse_opt(T, S, Dict, Clients ++ [Client]); - {trace_global, Bool} when Bool == false -> + {trace_global, Bool} when Bool =:= false -> parse_opt(T, S#state{trace_global = Bool}, Dict, Clients); - {trace_global, Bool} when Bool == true -> + {trace_global, Bool} when Bool =:= true -> parse_opt(T, S#state{trace_global = Bool}, Dict, Clients); - {trace_pattern, {Mod, _} = Pattern} when atom(Mod) -> + {trace_pattern, {Mod, _} = Pattern} when is_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) -> + {trace_port, Port} when is_integer(Port) -> parse_opt(T, S#state{trace_port = Port}, Dict, Clients); - {trace_max_queue, MaxQueue} when integer(MaxQueue) -> + {trace_max_queue, MaxQueue} when is_integer(MaxQueue) -> parse_opt(T, S#state{trace_port = MaxQueue}, Dict, Clients); Bad -> {error, {bad_option, Bad}} @@ -352,19 +357,19 @@ do_load_event_file(Fun, Fd, Cont, Acc, FileName, BadBytes) -> %% Returns: {ok, Continuation} | exit(Reason) %%---------------------------------------------------------------------- -report(CollectorPid, TraceOrEvent) when pid(CollectorPid) -> +report(CollectorPid, TraceOrEvent) when is_pid(CollectorPid) -> case get_table_handle(CollectorPid) of - {ok, TH} when record(TH, table_handle) -> + {ok, TH} when is_record(TH, table_handle) -> report(TH, TraceOrEvent); {error, Reason} -> exit(Reason) end; -report(TH, TraceOrEvent) when record(TH, table_handle) -> +report(TH, TraceOrEvent) when is_record(TH, table_handle) -> Fun = TH#table_handle.filter, case Fun(TraceOrEvent) of false -> {ok, TH}; - true when record(TraceOrEvent, event) -> + true when is_record(TraceOrEvent, event) -> Key = make_key(TH, TraceOrEvent), case catch ets:insert(TH#table_handle.event_tab, {Key, TraceOrEvent}) of true -> @@ -373,7 +378,7 @@ report(TH, TraceOrEvent) when record(TH, table_handle) -> %% Refresh the report handle and try again report(TH#table_handle.collector_pid, TraceOrEvent) end; - {true, Event} when record(Event, event) -> + {true, Event} when is_record(Event, event) -> Key = make_key(TH, Event), case catch ets:insert(TH#table_handle.event_tab, {Key, Event}) of true -> @@ -401,7 +406,7 @@ report(TH, TraceOrEvent) when record(TH, table_handle) -> report(TH#table_handle.collector_pid, TraceOrEvent) end end; -report(TH, end_of_trace) when record(TH, table_handle) -> +report(TH, end_of_trace) when is_record(TH, table_handle) -> {ok, TH}; report(_, Bad) -> exit({bad_event, Bad}). @@ -410,7 +415,7 @@ 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) -> + when is_integer(DetailLevel), DetailLevel >= 0, DetailLevel =< 100 -> TS= erlang:now(), E = #event{detail_level = DetailLevel, trace_ts = TS, @@ -431,32 +436,38 @@ report_event(CollectorPid, DetailLevel, From, To, Label, Contents) %% Key = record(event_ts) | record(trace_ts) %%---------------------------------------------------------------------- -make_key(TH, Stuff) when record(TH, table_handle) -> +make_key(TH, Stuff) when is_record(TH, table_handle) -> make_key(TH#table_handle.event_order, Stuff); make_key(trace_ts, Stuff) -> if - record(Stuff, event) -> + is_record(Stuff, event) -> #event{trace_ts = R, event_ts = P} = Stuff, #trace_ts{trace_ts = R, event_ts = P}; - record(Stuff, trace_ts) -> + is_record(Stuff, trace_ts) -> Stuff; - record(Stuff, event_ts) -> + is_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) -> + is_record(Stuff, event) -> #event{trace_ts = R, event_ts = P} = Stuff, #event_ts{trace_ts = R, event_ts = P}; - record(Stuff, event_ts) -> + is_record(Stuff, event_ts) -> Stuff; - record(Stuff, trace_ts) -> + is_record(Stuff, trace_ts) -> #trace_ts{trace_ts = R, event_ts = P} = Stuff, #event_ts{trace_ts = R, event_ts = P} end. %%---------------------------------------------------------------------- +%%---------------------------------------------------------------------- + +get_table_size(CollectorPid) when is_pid(CollectorPid) -> + call(CollectorPid, get_table_size). + +%%---------------------------------------------------------------------- %% get_table_handle(CollectorPid) -> Handle %% %% Return a table handle @@ -465,7 +476,7 @@ make_key(event_ts, Stuff) -> %% Handle = record(table_handle) %%---------------------------------------------------------------------- -get_table_handle(CollectorPid) when pid(CollectorPid) -> +get_table_handle(CollectorPid) when is_pid(CollectorPid) -> call(CollectorPid, get_table_handle). %%---------------------------------------------------------------------- @@ -480,7 +491,7 @@ get_table_handle(CollectorPid) when pid(CollectorPid) -> get_global_pid() -> case global:whereis_name(?MODULE) of - CollectorPid when pid(CollectorPid) -> + CollectorPid when is_pid(CollectorPid) -> CollectorPid; undefined -> exit(global_collector_not_started) @@ -505,7 +516,7 @@ change_pattern(CollectorPid, RawPattern) -> call(CollectorPid, {change_pattern, Pattern}). %%---------------------------------------------------------------------- -%% dict_insert(CollectorPid, {filter, collector}, FilterFun) -> ok +%% dict_insert(CollectorPid, {filter, all}, FilterFun) -> ok %% dict_insert(CollectorPid, {subscriber, SubscriberPid}, Void) -> ok %% dict_insert(CollectorPid, Key, Val) -> ok %% @@ -532,14 +543,14 @@ change_pattern(CollectorPid, RawPattern) -> dict_insert(CollectorPid, Key = {filter, Name}, Fun) -> if - atom(Name), function(Fun) -> + is_atom(Name), is_function(Fun) -> call(CollectorPid, {dict_insert, Key, Fun}); true -> exit({badarg, Key}) end; dict_insert(CollectorPid, Key = {subscriber, Pid}, Val) -> if - pid(Pid) -> + is_pid(Pid) -> call(CollectorPid, {dict_insert, Key, Val}); true -> exit({badarg, Key}) @@ -626,9 +637,9 @@ multicast(CollectorPid, Msg) -> %% Pid = dbg_trace_client_pid() %%---------------------------------------------------------------------- -start_trace_client(CollectorPid, Type, FileName) when Type == event_file -> +start_trace_client(CollectorPid, Type, FileName) when Type =:= event_file -> load_event_file(CollectorPid, FileName); -start_trace_client(CollectorPid, Type, FileName) when Type == file -> +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, @@ -658,9 +669,9 @@ start_trace_client(CollectorPid, Type, Parameters) -> {trace_client_pid, Pid}. trace_spec_wrapper(EventFun, EndFun, EventInitialAcc) - when function(EventFun), function(EndFun) -> + when is_function(EventFun), is_function(EndFun) -> {fun(Trace, Acc) -> - case Trace == end_of_trace of + case Trace =:= end_of_trace of true -> EndFun(Acc); false -> EventFun(Trace, Acc) end @@ -702,24 +713,24 @@ iterate(Handle, Prev, Limit) -> %% Acc = NewAcc = term() %%---------------------------------------------------------------------- -iterate(_, _, Limit, _, Acc) when Limit == 0 -> +iterate(_, _, Limit, _, Acc) when Limit =:= 0 -> Acc; -iterate(CollectorPid, Prev, Limit, Fun, Acc) when pid(CollectorPid) -> +iterate(CollectorPid, Prev, Limit, Fun, Acc) when is_pid(CollectorPid) -> case get_table_handle(CollectorPid) of - {ok, TH} when record(TH, table_handle) -> + {ok, TH} when is_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) -> +iterate(TH, Prev, Limit, Fun, Acc) when is_record(TH, table_handle) -> if - Limit == infinity -> + Limit =:= infinity -> next_iterate(TH, Prev, Limit, Fun, Acc); - integer(Limit), Limit > 0 -> + is_integer(Limit), Limit > 0 -> next_iterate(TH, Prev, Limit, Fun, Acc); - Limit == '-infinity' -> + Limit =:= '-infinity' -> prev_iterate(TH, Prev, Limit, Fun, Acc); - integer(Limit), Limit < 0 -> + is_integer(Limit), Limit < 0 -> prev_iterate(TH, Prev, Limit, Fun, Acc) end. @@ -793,7 +804,7 @@ prev_iterate(TH, Prev, Limit, Fun, Acc) -> lookup_and_apply(TH, Prev, Next, Limit, 1, Fun, Acc) end. -lookup_and_apply(TH, _Prev, Next, Limit, Incr, Fun, _Acc) when Fun == undefined -> +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) -> @@ -801,17 +812,33 @@ lookup_and_apply(TH, Prev, Next, Limit, Incr, Fun, Acc) -> 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) -> + E when is_record(E, event) -> Acc2 = Fun(E, Acc), Limit2 = incr(Limit, Incr), iterate(TH, Next, Limit2, Fun, Acc2) end. +lookup(CollectorPid, Key) when is_pid(CollectorPid) -> + case get_table_handle(CollectorPid) of + {ok, TH} when is_record(TH, table_handle) -> + lookup(TH, Key); + {error, Reason} -> + {error, Reason} + end; +lookup(TH, Key) when is_record(TH, table_handle) -> + Tab = TH#table_handle.event_tab, + case catch ets:lookup_element(Tab, Key, 2) of + {'EXIT', _} -> + {error, enoent}; + E when is_record(E, event) -> + {ok, E} + end. + incr(Val, Incr) -> if - Val == infinity -> Val; - Val == '-infinity' -> Val; - integer(Val) -> Val + Incr + Val =:= infinity -> Val; + Val =:= '-infinity' -> Val; + is_integer(Val) -> Val + Incr end. %%---------------------------------------------------------------------- @@ -824,13 +851,18 @@ incr(Val, Incr) -> %% table_handle() = record(table_handle) %%---------------------------------------------------------------------- -clear_table(CollectorPid) when pid(CollectorPid) -> +clear_table(CollectorPid) when is_pid(CollectorPid) -> call(CollectorPid, clear_table); -clear_table(TH) when record(TH, table_handle) -> +clear_table(TH) when is_record(TH, table_handle) -> clear_table(TH#table_handle.collector_pid). call(CollectorPid, Request) -> - gen_server:call(CollectorPid, Request, infinity). + try + gen_server:call(CollectorPid, Request, infinity) + catch + exit:{noproc,_} -> + {error, no_collector} + end. %%%---------------------------------------------------------------------- %%% Callback functions from gen_server @@ -849,7 +881,7 @@ init([InitialS, Dict]) -> case InitialS#state.parent_pid of undefined -> ignore; - Pid when pid(Pid) -> + Pid when is_pid(Pid) -> link(Pid) end, Funs = [fun init_tables/1, @@ -860,7 +892,7 @@ init([InitialS, Dict]) -> 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}. + S#state{event_tab = EventTab, dict_tab = DictTab, event_tab_size = 0}. init_global(S) -> case S#state.trace_global of @@ -889,44 +921,53 @@ init_global(S) -> handle_call({multicast, Msg}, _From, S) -> do_multicast(S#state.subscribers, Msg), - {reply, ok, S}; + reply(ok, S); handle_call(Msg = {dict_insert, _Key, _Val}, _From, S) -> S2 = do_dict_insert(Msg, S), - {reply, ok, S2}; + reply(ok, S2); handle_call(Msg = {dict_delete, _Key}, _From, S) -> - S2 = do_dict_delete(Msg, S), - {reply, ok, S2}; - + try + S2 = do_dict_delete(Msg, S), + reply(ok, S2) + catch + throw:{stop, R} -> + opt_unlink(S#state.parent_pid), + {stop, R, S} + end; handle_call({dict_lookup, Key}, _From, S) -> Reply = ets:lookup(S#state.dict_tab, Key), - {reply, Reply, S}; + 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}; + reply([], S); Matching -> - {reply, Matching, S} + reply(Matching, S) end; handle_call(get_table_handle, _From, S) -> - [{_, TableFilter}] = ets:lookup(S#state.dict_tab, {filter, collector}), + [{_, TableFilter}] = ets:lookup(S#state.dict_tab, {filter, ?DEFAULT_FILTER_NAME}), TH = #table_handle{collector_pid = self(), event_tab = S#state.event_tab, event_order = S#state.event_order, filter = TableFilter}, - {reply, {ok, TH}, S}; + reply({ok, TH}, S); + +handle_call(get_table_size, _From, S) -> + Size = ets:info(S#state.event_tab, size), + reply({ok, Size}, S); handle_call(close, _From, S) -> case S#state.file of undefined -> - {reply, {error, file_not_open}, S}; + reply({error, file_not_open}, S); F -> Reply = disk_log:close(F#file.desc), S2 = S#state{file = undefined}, - {reply, Reply, S2} + reply(Reply, S2) end; handle_call({save_event_file, FileName, Options}, _From, S) -> Default = #file{name = FileName, @@ -934,7 +975,7 @@ handle_call({save_event_file, FileName, Options}, _From, S) -> file_opt = write, table_opt = keep}, case parse_file_options(Default, Options) of - {ok, F} when record(F, file) -> + {ok, F} when is_record(F, file) -> case file_open(F) of {ok, Fd} -> F2 = F#file{desc = Fd}, @@ -966,16 +1007,16 @@ handle_call({save_event_file, FileName, Options}, _From, S) -> end, case F2#file.table_opt of keep -> - {reply, Reply2, S3}; + reply(Reply2, S3); clear -> S4 = do_clear_table(S3), - {reply, Reply2, S4} + reply(Reply2, S4) end; {error, Reason} -> - {reply, {error, {file_open, Reason}}, S} + reply({error, {file_open, Reason}}, S) end; {error, Reason} -> - {reply, {error, Reason}, S} + reply({error, Reason}, S) end; handle_call({change_pattern, Pattern}, _From, S) -> @@ -983,11 +1024,11 @@ handle_call({change_pattern, Pattern}, _From, S) -> rpc:multicall(Ns, et_selector, change_pattern, [Pattern]), Reply = {old_pattern, S#state.trace_pattern}, S2 = S#state{trace_pattern = Pattern}, - {reply, Reply, S2}; + reply(Reply, S2); handle_call(clear_table, _From, S) -> S2 = do_clear_table(S), - {reply, ok, S2}; + reply(ok, S2); handle_call(stop, _From, S) -> do_multicast(S#state.subscribers, close), @@ -999,7 +1040,7 @@ handle_call(stop, _From, 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}. + reply({error, {bad_request, Request}}, S). %%---------------------------------------------------------------------- %% Func: handle_cast/2 @@ -1011,7 +1052,7 @@ handle_call(Request, From, S) -> handle_cast(Msg, S) -> ok = error_logger:format("~p(~p): handle_cast(~p, ~p)~n", [?MODULE, self(), Msg, S]), - {noreply, S}. + noreply(S). %%---------------------------------------------------------------------- %% Func: handle_info/2 @@ -1020,54 +1061,67 @@ handle_cast(Msg, S) -> %% {stop, Reason, State} (terminate/2 is called) %%---------------------------------------------------------------------- +handle_info(timeout, S) -> + S2 = check_size(S), + noreply(S2); 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-> + S2 = listen_on_trace_port(Node, Port, S), + noreply(S2); + {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}; + 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}; + 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} + noreply(S2) end; handle_info({nodedown, Node}, S) -> - {noreply, S#state{trace_nodes = S#state.trace_nodes -- [Node]}}; + noreply(S#state{trace_nodes = S#state.trace_nodes -- [Node]}); handle_info({register_trace_client, Pid}, S) -> link(Pid), - {noreply, S}; + noreply(S); -handle_info({'EXIT', Pid, Reason}, S) when Pid == S#state.parent_pid -> +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, +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}; + true when Reason =:= shutdown -> + try + S2 = do_dict_delete({dict_delete, {subscriber, Pid}}, S), + noreply(S2) + catch + throw:{stop, R} -> + opt_unlink(S#state.parent_pid), + {stop, R, S} + end; + true -> + opt_unlink(S#state.parent_pid), + {stop, Reason, S}; false -> ok = error_logger:format("~p(~p): handle_info(~p, ~p)~n", [?MODULE, self(), Info, S]), - {noreply, 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}. + noreply(S). listen_on_trace_port(Node, Port, S) -> [_Name, Host] = string:tokens(atom_to_list(Node), [$@]), @@ -1075,20 +1129,17 @@ listen_on_trace_port(Node, Port, S) -> {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-> + S#state{trace_nodes = [Node | S#state.trace_nodes], + trace_port = Port + 1}; + {'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}; + S#state{trace_port = Port + 1}; {'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} + S#state{trace_port = Port + 1} end. %%---------------------------------------------------------------------- @@ -1120,7 +1171,7 @@ do_clear_table(S) -> 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) -> +do_dict_insert(Msg = {dict_insert, Key = {subscriber, Pid}, Val}, S) when is_pid(Pid) -> OldSubscribers = S#state.subscribers, NewSubscribers = case lists:member(Pid, OldSubscribers) of @@ -1133,6 +1184,8 @@ do_dict_insert(Msg = {dict_insert, Key = {subscriber, Pid}, Val}, S) when pid(Pi [Pid | OldSubscribers] end, do_multicast(NewSubscribers, Msg), + Size = ets:info(S#state.event_tab, size), + do_multicast(NewSubscribers, {more_events, Size}), ets:insert(S#state.dict_tab, {Key, Val}), S#state{subscribers = NewSubscribers}; do_dict_insert(Msg = {dict_insert, Key, Val}, S) -> @@ -1147,11 +1200,18 @@ do_dict_delete(Msg = {dict_delete, Key = {subscriber, Pid}}, S) -> case lists:member(Pid, OldSubscribers) of true -> unlink(Pid), - S#state{subscribers = OldSubscribers -- [Pid]}; + S2 = S#state{subscribers = OldSubscribers -- [Pid]}, + if + S2#state.auto_shutdown, + S2#state.subscribers =:= [] -> + throw({stop, shutdown}); + true -> + S2 + end; false -> S end; -do_dict_delete({dict_delete, {filter, collector}}, S) -> +do_dict_delete({dict_delete, {filter, ?DEFAULT_FILTER_NAME}}, S) -> S; do_dict_delete(Msg = {dict_delete, Key}, S) -> do_multicast(S#state.subscribers, Msg), @@ -1202,3 +1262,33 @@ do_multicast([Pid | Pids], Msg) -> do_multicast(Pids, Msg); do_multicast([], _Msg) -> ok. + +opt_unlink(Pid) -> + if + Pid =:= undefined -> + ignore; + true -> + unlink(Pid) + end. + +reply(Reply, #state{subscribers = []} = S) -> + {reply, Reply, S}; +reply(Reply, S) -> + {reply, Reply, S, 500}. + +noreply(#state{subscribers = []} = S) -> + {noreply, S}; +noreply(S) -> + {noreply, S, 500}. + +check_size(S) -> + Size = ets:info(S#state.event_tab, size), + if + Size =:= S#state.event_tab_size -> + S; + true -> + %% Tell the subscribers that more events are available + Msg = {more_events, Size}, + do_multicast(S#state.subscribers, Msg), + S#state{event_tab_size = Size} + end. diff --git a/lib/et/src/et_contents_viewer.erl b/lib/et/src/et_gs_contents_viewer.erl index 29ca93ca64..f6a87bd608 100644 --- a/lib/et/src/et_contents_viewer.erl +++ b/lib/et/src/et_gs_contents_viewer.erl @@ -1,26 +1,26 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2000-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2000-2010. 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). +-module(et_gs_contents_viewer). -behaviour(gen_server). @@ -75,7 +75,7 @@ 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() -> + {ok, ContentsPid} when S#state.parent_pid =/= self() -> unlink(ContentsPid), {ok, ContentsPid}; Other -> @@ -88,8 +88,8 @@ start_link(Options) -> default_state() -> #state{parent_pid = self(), viewer_pid = undefined, - active_filter = collector, - filters = [#filter{name = collector, function = fun(E) -> E end}], + active_filter = ?DEFAULT_FILTER_NAME, + filters = [?DEFAULT_FILTER], width = 600, height = 300}. @@ -97,11 +97,11 @@ parse_opt([], S) -> Name = S#state.active_filter, Filters = S#state.filters, if - S#state.event == undefined -> + S#state.event =:= undefined -> {error, {badarg, no_event}}; - atom(Name) -> + is_atom(Name) -> case lists:keysearch(Name, #filter.name, Filters) of - {value, F} when record(F, filter) -> + {value, F} when is_record(F, filter) -> {ok, S#state{active_filter = Name}}; false -> {error, {badarg, {no_such_filter, Name, Filters}}} @@ -109,27 +109,27 @@ parse_opt([], S) -> end; parse_opt([H | T], S) -> case H of - {parent_pid, ParentPid} when pid(ParentPid) -> + {parent_pid, ParentPid} when is_pid(ParentPid) -> parse_opt(T, S#state{parent_pid = ParentPid}); - {viewer_pid, ViewerPid} when pid(ViewerPid) -> + {viewer_pid, ViewerPid} when is_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) -> + {event, Event} when is_record(Event, event) -> parse_opt(T, S#state{event = Event}); - {active_filter, Name} when atom(Name) -> + {active_filter, Name} when is_atom(Name) -> parse_opt(T, S#state{active_filter = Name}); - F when record(F, filter), - atom(F#filter.name), - function(F#filter.function) -> + F when is_record(F, filter), + is_atom(F#filter.name), + is_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 -> + {width, Width} when is_integer(Width), Width > 0 -> parse_opt(T, S#state{width = Width}); - {height, Height} when integer(Height), Height > 0 -> + {height, Height} when is_integer(Height), Height > 0 -> parse_opt(T, S#state{height = Height}); Bad -> {error, {bad_option, Bad}} @@ -164,7 +164,7 @@ call(ContentsPid, Request) -> %% {stop, Reason} %%---------------------------------------------------------------------- -init([S]) when record(S, state) -> +init([S]) when is_record(S, state) -> process_flag(trap_exit, true), S2 = create_window(S), {ok, S2}. @@ -223,11 +223,11 @@ handle_info({gs, Button, click, Data, _Other}, S) -> FileName = ["et_contents_viewer_", now_to_string(TimeStamp), ".save"], file:write_file(lists:flatten(FileName), Bin), {noreply, S}; - _PopupMenuItem when record(Data, filter) -> + _PopupMenuItem when is_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() -> + {ok, Pid} when S#state.parent_pid =/= self() -> unlink(Pid), {noreply, S}; _ -> @@ -312,11 +312,11 @@ handle_info({gs, _Obj, keypress, _, [KeySym, _Keycode, _Shift, _Control | _]}, S {noreply, S}; 0 -> - case lists:keysearch(collector, #filter.name, S#state.filters) of - {value, F} when record(F, filter) -> + case lists:keysearch(?DEFAULT_FILTER_NAME, #filter.name, S#state.filters) of + {value, F} when is_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() -> + {ok, Pid} when S#state.parent_pid =/= self() -> unlink(Pid); _ -> ignore @@ -325,12 +325,12 @@ handle_info({gs, _Obj, keypress, _, [KeySym, _Keycode, _Shift, _Control | _]}, S ignore end, {noreply, S}; - Int when integer(Int), Int > 0, Int =< 9 -> + Int when is_integer(Int), Int > 0, Int =< 9 -> case catch lists:nth(Int, S#state.filters) of - F when record(F, filter) -> + F when is_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() -> + {ok, Pid} when S#state.parent_pid =/= self() -> unlink(Pid); _ -> ignore @@ -356,7 +356,7 @@ handle_info({gs, _Obj, configure, [], [W, H | _]}, S) -> {noreply, S2}; handle_info({'EXIT', Pid, Reason}, S) -> if - Pid == S#state.parent_pid -> + Pid =:= S#state.parent_pid -> unlink(Pid), {stop, Reason, S}; true -> @@ -428,7 +428,7 @@ create_filter_menu(Bar, 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-> + Item = fun(F, N) when F#filter.name =:= ?DEFAULT_FILTER_NAME-> Label = lists:concat([pad_string(F#filter.name, 20), "(0)"]), gs:menuitem(Menu, [{label, {text, Label}}, {data, F}]), N + 1; @@ -449,9 +449,9 @@ create_hide_menu(Bar, S) -> From = E#event.from, To = E#event.to, if - S#state.viewer_pid == undefined -> + S#state.viewer_pid =:= undefined -> ignore; - From == To -> + 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)"}}]), @@ -483,9 +483,9 @@ create_search_menu(Bar, S) -> {bg, lightblue}, {enable, false}]), gs:menuitem(Menu, [{itemtype, separator}]), if - S#state.viewer_pid == undefined -> + S#state.viewer_pid =:= undefined -> S; - From == To -> + From =:= To -> Key = et_collector:make_key(S#state.event_order, E), ModeS = {search_actors, forward, Key, [From]}, ModeR = {search_actors, reverse, Key, [From]}, @@ -508,7 +508,7 @@ config_editor(Editor, S) -> case catch FilterFun(Event) of true -> do_config_editor(Editor, Event, lightblue, S#state.event_order); - {true, Event2} when record(Event2, event) -> + {true, Event2} when is_record(Event2, event) -> do_config_editor(Editor, Event2, lightblue, S#state.event_order); false -> do_config_editor(Editor, Event, red, S#state.event_order); @@ -536,7 +536,7 @@ term_to_string(Term) -> end. now_to_string({Mega, Sec, Micro} = Now) - when integer(Mega), integer(Sec), integer(Micro) -> + when is_integer(Mega), is_integer(Sec), is_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) -> @@ -548,14 +548,14 @@ event_to_string(Event, TsKey) -> 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 + 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 + case ReportedTs =:= ParsedTs of true -> ["\nPARSED: ", now_to_string(ParsedTs)]; false -> @@ -571,9 +571,9 @@ event_to_string(Event, TsKey) -> "\nCONTENTS:\n\n", term_to_string(Event#event.contents)], lists:flatten(Deep). -pad_string(Atom, MinLen) when atom(Atom) -> +pad_string(Atom, MinLen) when is_atom(Atom) -> pad_string(atom_to_list(Atom), MinLen); -pad_string(String, MinLen) when integer(MinLen), MinLen >= 0 -> +pad_string(String, MinLen) when is_integer(MinLen), MinLen >= 0 -> Len = length(String), case Len >= MinLen of true -> @@ -584,7 +584,7 @@ pad_string(String, MinLen) when integer(MinLen), MinLen >= 0 -> send_viewer_event(S, Event) -> case S#state.viewer_pid of - ViewerPid when pid(ViewerPid) -> + ViewerPid when is_pid(ViewerPid) -> ViewerPid ! {et, Event}; undefined -> ignore diff --git a/lib/et/src/et_gs_viewer.erl b/lib/et/src/et_gs_viewer.erl new file mode 100644 index 0000000000..7235269aff --- /dev/null +++ b/lib/et/src/et_gs_viewer.erl @@ -0,0 +1,1483 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2010. 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_gs_viewer). + +-behaviour(gen_server). + +%% External exports +-export([start_link/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 +%%%---------------------------------------------------------------------- + +start_link(Options) -> + case parse_opt(Options, default_state(), []) of + {ok, S, CollectorOpt} -> + case S#state.collector_pid of + CollectorPid when is_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 = ?DEFAULT_FILTER_NAME, + filters = [?DEFAULT_FILTER], + 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 is_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 is_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 is_integer(Scale), Scale > 0 -> + parse_opt(T, S#state{scale = Scale}, CollectorOpt); + {width, W} when is_integer(W), W > 0 -> + parse_opt(T, S#state{width = W, canvas_width = W}, CollectorOpt); + {height, WH} when is_integer(WH), WH > 0 -> + parse_opt(T, S#state{height = WH, canvas_height = WH}, CollectorOpt); + {collector_pid, Pid} when is_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 is_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 + is_atom(Name), is_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 + is_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 is_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 is_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 is_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 is_list(Actors), Dir =:= forward -> + parse_opt(T, S#state{display_mode = Mode}, CollectorOpt); + {display_mode, Mode = {search_actors, Dir, _Key, Actors}} when is_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 is_atom(Name), is_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 is_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. + +%%%---------------------------------------------------------------------- +%%% Callback functions from gen_server +%%%---------------------------------------------------------------------- + +%%---------------------------------------------------------------------- +%% Func: init/1 +%% Returns: {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%%---------------------------------------------------------------------- + +init([S]) when is_record(S, state) -> + process_flag(trap_exit, true), + InitialTimeout = 0, + case S#state.parent_pid of + undefined -> + ignore; + Pid when is_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, {more_events, _Size}}, S) -> + noreply(S); +handle_info({et, {insert_actors, ActorNames}}, S) when is_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 is_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, is_integer(hd(Other)), + hd(Other) >= ?detail_level_min, + hd(Other) =< ?detail_level_max -> + S2 = S#state{detail_level = hd(Other)}, + noreply(S2); + _PopupMenuItem when is_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(?DEFAULT_FILTER_NAME, #filter.name, S#state.filters) of + {value, F} when is_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 is_integer(Int), Int > 0, Int =< 9 -> + case catch lists:nth(Int, S#state.filters) of + F when is_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 is_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 is_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 is_record(E, event), is_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 + is_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_gs_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 is_atom(Atom) -> + pad_string(atom_to_list(Atom), MinLen); +pad_string(String, MinLen) when is_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 is_list(List) -> + {length(List), [], List}. diff --git a/lib/et/src/et_internal.hrl b/lib/et/src/et_internal.hrl index b6f84f5b4b..1feb161ef1 100644 --- a/lib/et/src/et_internal.hrl +++ b/lib/et/src/et_internal.hrl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2002-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2002-2010. 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% %% %%---------------------------------------------------------------------- @@ -21,3 +21,6 @@ %%---------------------------------------------------------------------- -record(filter, {name, function}). + +-define(DEFAULT_FILTER_NAME, all). +-define(DEFAULT_FILTER, #filter{name = ?DEFAULT_FILTER_NAME, function = fun(E) -> E end}). diff --git a/lib/et/src/et_selector.erl b/lib/et/src/et_selector.erl index 845359622d..66f5723bad 100644 --- a/lib/et/src/et_selector.erl +++ b/lib/et/src/et_selector.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2001-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2001-2010. 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% %% %%---------------------------------------------------------------------- @@ -41,14 +41,14 @@ %% %% 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) +%% min - minimum level of tracing (ignore calls to trace_me/4,5) +%% max - maximum level of tracing (all calls to trace_me/4,5) %% integer() - explicit detail level of tracing %%---------------------------------------------------------------------- make_pattern(undefined) -> {undefined, undefined}; -make_pattern({Mod, Pattern}) when atom(Mod) -> +make_pattern({Mod, Pattern}) when is_atom(Mod) -> case Pattern of min -> {Mod, []}; @@ -57,7 +57,7 @@ make_pattern({Mod, Pattern}) when atom(Mod) -> Body = [], Cond = [], {Mod, [{Head, Cond, Body}]}; - DetailLevel when integer(DetailLevel) -> + DetailLevel when is_integer(DetailLevel) -> Head = ['$1', '_', '_', '_', '_'], Body = [], Cond = [{ '<', '$1', DetailLevel}], @@ -80,28 +80,31 @@ make_pattern({Mod, Pattern}) when atom(Mod) -> %% 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 +%% Min detail level deactivates tracing of calls to trace_me/4,5 %% -%% Max detail level activates tracing of all calls to report_event/4,5 +%% Max detail level activates tracing of all calls to trace_me/4,5 %% %% integer(X) detail level activates tracing of all calls to -%% report_event/4,5 whose detail level argument is lesser than X. +%% trace_me/4,5 whose detail level argument is lesser than X. %% -%% An empty match spec deactivates tracing of calls to report_event/4,5 +%% An empty match spec deactivates tracing of calls to trace_me/4,5 %% -%% Other match specs activates tracing of calls to report_event/4,5 +%% Other match specs activates tracing of calls to trace_me/4,5 %% accordlingly with erlang:trace_pattern/2. %%---------------------------------------------------------------------- -change_pattern({Mod, Pattern}) when atom(Mod) -> - MFA = {Mod, report_event, 5}, +change_pattern({Mod, Pattern}) when is_atom(Mod) -> + MFA = {Mod, trace_me, 5}, case Pattern of undefined -> ignore; [] -> + error_to_exit(old_ctp(MFA)), error_to_exit(dbg:ctp(MFA)), error_to_exit(dbg:p(all, clear)); - List when list(List) -> + List when is_list(List) -> + error_to_exit(old_ctp(MFA)), + error_to_exit(old_tp(MFA, Pattern)), error_to_exit(dbg:ctp(MFA)), error_to_exit(dbg:tp(MFA, Pattern)), error_to_exit(dbg:p(all, [call, timestamp])); @@ -110,6 +113,18 @@ change_pattern({Mod, Pattern}) when atom(Mod) -> end, ok. +old_ctp({Mod, _Fun, Args}) -> + case Mod of + et -> ignore; + _ -> dbg:ctp({Mod, report_event, Args}) + end. + +old_tp({Mod, _Fun, Args}, Pattern) -> + case Mod of + et -> ignore; + _ -> dbg:tp({Mod, report_event, Args}, Pattern) + end. + error_to_exit({error, Reason}) -> exit(Reason); error_to_exit({ok, Res}) -> @@ -148,7 +163,7 @@ error_to_exit({ok, Res}) -> %% 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. +%% See et:trace_me/4 and et:trace_me/5 for details. %% %% Returns: %% @@ -161,7 +176,7 @@ error_to_exit({ok, Res}) -> %% should be dropped %%---------------------------------------------------------------------- -parse_event(_Mod, E) when record(E, event) -> +parse_event(_Mod, E) when is_record(E, event) -> true; parse_event(Mod, Trace) -> ParsedTS = erlang:now(), @@ -293,6 +308,14 @@ parse_event(Mod, Trace, ParsedTS, ReportedTS, From, Label, Contents) -> {msg, Msg}]}}; call -> case Contents of + [{M, trace_me, [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 [{M, report_event, [UserDetailLevel, UserFrom, UserTo, UserLabel, UserContents]}] when M == Mod, Mod /= undefined -> {true, #event{detail_level = UserDetailLevel, trace_ts = ReportedTS, @@ -358,6 +381,21 @@ parse_event(Mod, Trace, ParsedTS, ReportedTS, From, Label, Contents) -> {to, From}, {mfa, MFA}, {return, ReturnValue}]}}; + exception_from -> + DetailLevel = 54, + [MFA, Exception] = 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}, + {exception, Exception}]}}; spawn -> DetailLevel = 25, [NewPid, MFA] = Contents, diff --git a/lib/et/src/et_viewer.erl b/lib/et/src/et_viewer.erl index ede2c401eb..d9bd01f8d0 100644 --- a/lib/et/src/et_viewer.erl +++ b/lib/et/src/et_viewer.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2000-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2000-2010. 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% %% %%---------------------------------------------------------------------- @@ -22,66 +22,23 @@ -module(et_viewer). --behaviour(gen_server). %% External exports -export([file/1, start/0, start/1, + start/2, start_link/1, + start_link/2, + open_event/2, 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 @@ -99,7 +56,7 @@ %%---------------------------------------------------------------------- file(FileName) -> - start_link([{trace_client, {file, FileName}}]). + start_link([{trace_client, {file, FileName}}], default). %%---------------------------------------------------------------------- %% start() -> ok @@ -113,14 +70,19 @@ file(FileName) -> %%---------------------------------------------------------------------- start() -> - start([{trace_global, true}]). + start([{trace_global, true}], default). %%---------------------------------------------------------------------- %% start(Options) -> {ok, ViewerPid} | {error, Reason} %%---------------------------------------------------------------------- +start(GUI) when GUI =:= wx; GUI =:= gs; GUI =:= default -> + start_link([{trace_global, true}], GUI); start(Options) -> - start_link([{parent_pid, undefined} | Options]). + start_link([{parent_pid, undefined} | Options], default). + +start(Options, GUI) -> + start_link([{parent_pid, undefined} | Options], GUI). %%---------------------------------------------------------------------- %% start_link(Options) -> {ok, ViewerPid} | {error, Reason} @@ -177,216 +139,31 @@ start(Options) -> %% 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); +start_link(GUI) when GUI =:= wx; GUI =:= gs; GUI =:= default -> + start_link([{trace_global, true}], GUI); +start_link(Options) -> + start_link(Options, default). - Bad -> - {error, {bad_option, Bad}} - end; -parse_opt(BadList, _S, _CollectorOpt) -> - {error, {bad_option_list, BadList}}. +start_link(Options, GUI) -> + case GUI of + wx -> + et_wx_viewer:start_link(Options); + gs -> + et_gs_viewer:start_link(Options); + default -> + start_link(Options, which_gui()) + end. -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. +which_gui() -> + try + wx:new(), + wx:destroy(), + wx + catch _:_ -> + gs + end. -%%---------------------------------------------------------------------- -%% 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). @@ -402,1201 +179,14 @@ get_collector_pid(ViewerPid) -> 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}. +open_event(ViewerPid, N) -> + call(ViewerPid, {open_event, N}). %%---------------------------------------------------------------------- -%% 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. +call(ViewerPid, Request) -> + gen_server:call(ViewerPid, Request, infinity). -list_to_queue(List) when list(List) -> - {length(List), [], List}. diff --git a/lib/et/src/et_wx_contents_viewer.erl b/lib/et/src/et_wx_contents_viewer.erl new file mode 100644 index 0000000000..8a8d9ef1ee --- /dev/null +++ b/lib/et/src/et_wx_contents_viewer.erl @@ -0,0 +1,700 @@ +%% +%% %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_wx_contents_viewer). + +-behaviour(wx_object). + +%% 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, + handle_event/2]). + +-include("../include/et.hrl"). +-include("et_internal.hrl"). +-include_lib("wx/include/wx.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: Frame object + frame, % GUI: Frame object + panel, % GUI: Panel object + width, % GUI: Window width + height, + editor, + menu_data, % GUI: Window height + wx_debug, % GUI: WX debug level + trap_exit}). % trap_exit process flag + +%%%---------------------------------------------------------------------- +%%% Client side +%%%---------------------------------------------------------------------- + +%%---------------------------------------------------------------------- +%% start_link(Options) -> {ok, ContentsPid} | {error, Reason} +%% +%% Start a 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} -> + try + WxRef = wx_object:start_link(?MODULE, [S], []), + Pid = wx_object:get_pid(WxRef), + if + S#state.parent_pid =/= self() -> + unlink(Pid); + true -> + ignore + end, + {ok, Pid} + catch + error:Reason -> + {error, {'EXIT', Reason, erlang:get_stacktrace()}} + end; + {error, Reason} -> + {error, Reason} + end. + +default_state() -> + #state{parent_pid = self(), + viewer_pid = undefined, + active_filter = ?DEFAULT_FILTER_NAME, + filters = [?DEFAULT_FILTER], + width = 600, + height = 300, + wx_debug = 0, + trap_exit = true}. + +parse_opt([], S) -> + Name = S#state.active_filter, + Filters = S#state.filters, + if + S#state.event =:= undefined -> + {error, {badarg, no_event}}; + is_atom(Name) -> + case lists:keysearch(Name, #filter.name, Filters) of + {value, F} when is_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 is_pid(ParentPid); ParentPid =:= undefined -> + parse_opt(T, S#state{parent_pid = ParentPid}); + {viewer_pid, ViewerPid} when is_pid(ViewerPid) -> + parse_opt(T, S#state{viewer_pid = ViewerPid}); + {wx_debug, Level} -> + parse_opt(T, S#state{wx_debug = Level}); + {trap_exit, Bool} when Bool =:= true; Bool =:= false-> + parse_opt(T, S#state{trap_exit = Bool}); + {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 is_record(Event, event) -> + parse_opt(T, S#state{event = Event}); + {active_filter, Name} when is_atom(Name) -> + parse_opt(T, S#state{active_filter = Name}); + F when is_record(F, filter), + is_atom(F#filter.name), + is_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 is_integer(Width), Width > 0 -> + parse_opt(T, S#state{width = Width}); + {height, Height} when is_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) when is_pid(ContentsPid) -> + Type = process, + MonitorRef = erlang:monitor(Type, ContentsPid), + ContentsPid ! {stop, self()}, + receive + {'DOWN', MonitorRef, Type, ContentsPid, shutdown} -> + ok; + {'DOWN', MonitorRef, Type, ContentsPid, Reason} -> + {error, Reason} + end. + +%% call(Frame, Request) -> +%% wx_object:call(Frame, Request, infinity). + +%%%---------------------------------------------------------------------- +%%% Callback functions from gen_server +%%%---------------------------------------------------------------------- + +%%---------------------------------------------------------------------- +%% Func: init/1 +%% Returns: {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%%---------------------------------------------------------------------- + +init([S]) when is_record(S, state) -> + process_flag(trap_exit, S#state.trap_exit), + case S#state.parent_pid of + undefined -> ok; + ParentPid -> link(ParentPid) + end, + wx:debug(S#state.wx_debug), + S2 = create_window(S), + {S2#state.frame, 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(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_event/2 +%% Returns: {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%%---------------------------------------------------------------------- + +handle_event(#wx{id = Id, + event = #wxCommand{type = command_menu_selected}}, + S) -> + case proplists:get_value(Id, S#state.menu_data) of + undefined -> + ignore; + Data when is_record(Data, filter) -> + F = Data, + ChildState= S#state{active_filter = F#filter.name}, + case wx_object:start_link(?MODULE, [ChildState], []) of + {ok, Pid} when S#state.parent_pid =/= self() -> + unlink(Pid); + _ -> + ignore + end; + {hide, Actors} -> + send_viewer_event(S, {delete_actors, Actors}); + {show, Actors} -> + send_viewer_event(S, {insert_actors, Actors}); + {mode, Mode} -> + send_viewer_event(S, {mode, Mode}); + Nyi -> + ok = error_logger:format("~p: click ~p ignored (nyi)~n", + [?MODULE, Nyi]) + end, + case Id of + ?wxID_EXIT -> + wxFrame:destroy(S#state.frame), + opt_unlink(S#state.parent_pid), + {stop, shutdown, S}; + ?wxID_SAVE -> + Event = S#state.event, + TimeStamp = + case S#state.event_order of + trace_ts -> Event#event.trace_ts; + event_ts -> Event#event.event_ts + end, + FileName = lists:flatten(["et_contents_viewer_", now_to_string(TimeStamp), ".txt"]), + Style = ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT, + Msg = "Select a file to the events to", + case select_file(S#state.frame, Msg, filename:absname(FileName), Style) of + {ok, FileName2} -> + Bin = list_to_binary(event_to_string(Event, S#state.event_order)), + file:write_file(FileName2, Bin); + cancel -> + ok + end, + {noreply, S}; + ?wxID_PRINT -> + Html = wxHtmlEasyPrinting:new([{parentWindow, S#state.win}]), + Text = "<pre>" ++ wxTextCtrl:getValue(S#state.editor) ++ "</pre>", + wxHtmlEasyPrinting:previewText(Html, Text), + {noreply, S}; + _ -> + {noreply, S} + end; +handle_event(#wx{event = #wxKey{rawCode = KeyCode}}, S) -> + case KeyCode of + $c -> + wxFrame:destroy(S#state.frame), + opt_unlink(S#state.parent_pid), + {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(?DEFAULT_FILTER_NAME, #filter.name, S#state.filters) of + {value, F} when is_record(F, filter) -> + ChildState= S#state{active_filter = F#filter.name}, + case wx_object:start_link(?MODULE, [ChildState], []) of + {ok, Pid} when S#state.parent_pid =/= self() -> + unlink(Pid); + _ -> + ignore + end; + false -> + ignore + end, + {noreply, S}; + Int when is_integer(Int), Int > $0, Int =< $9 -> + case catch lists:nth(Int-$0, S#state.filters) of + F when is_record(F, filter) -> + ChildState= S#state{active_filter = F#filter.name}, + case wx_object:start_link(?MODULE, [ChildState], []) of + {ok, Pid} when S#state.parent_pid =/= self() -> + unlink(Pid); + _ -> + ignore + end; + {'EXIT', _} -> + ignore + end, + {noreply, S}; + + _ -> + io:format("~p: ignored: ~p~n", [?MODULE, KeyCode]), + {noreply, S} + end; +handle_event(#wx{event = #wxClose{}}, S) -> + opt_unlink(S#state.parent_pid), + {stop, shutdown, S}; +handle_event(#wx{event = #wxSize{size = {W, H}}}, S) -> + S2 = S#state{width = W, height = H}, + {noreply, S2}; +handle_event(Wx = #wx{}, S) -> + io:format("~p got an unexpected event: ~p\n", [self(), Wx]), + {noreply, S}. + +%%---------------------------------------------------------------------- +%% Func: handle_info/2 +%% Returns: {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%%---------------------------------------------------------------------- + +handle_info({stop, _From}, S) -> + wxFrame:destroy(S#state.frame), + opt_unlink(S#state.parent_pid), + {stop, shutdown, S}; +handle_info({'EXIT', Pid, Reason}, S) -> + if + Pid =:= S#state.parent_pid -> + wxFrame:destroy(S#state.frame), + opt_unlink(S#state.parent_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 +%%%---------------------------------------------------------------------- + +opt_unlink(Pid) -> + if + Pid =:= undefined -> + ignore; + true -> + unlink(Pid) + end. + +create_window(S) -> + H = S#state.height, + W = S#state.width, + Name = S#state.active_filter, + Title = lists:concat([?MODULE, " (filter: ", Name, ")"]), + WinOpt = [{size, {W,H}}], + Frame = wxFrame:new(wx:null(), ?wxID_ANY, Title, WinOpt), + wxFrame:createStatusBar(Frame), + + Panel = wxPanel:new(Frame, []), + Bar = wxMenuBar:new(), + wxFrame:setMenuBar(Frame,Bar), + create_file_menu(Bar), + Editor = wxTextCtrl:new(Panel, ?wxID_ANY, [{style, 0 + bor ?wxDEFAULT + bor ?wxTE_MULTILINE + bor ?wxTE_READONLY + bor ?wxTE_DONTWRAP}]), + Font = wxFont:new(10, ?wxFONTFAMILY_TELETYPE, ?wxNORMAL, ?wxNORMAL,[]), + TextAttr = wxTextAttr:new(?wxBLACK, [{font, Font}]), + wxTextCtrl:setDefaultStyle(Editor, TextAttr), + Sizer = wxBoxSizer:new(?wxHORIZONTAL), + wxSizer:add(Sizer, Editor, [{flag, ?wxEXPAND}, {proportion, 1}]), + FilteredEvent = config_editor(Editor, S), + S2 = S#state{win = Frame, panel = Panel, filtered_event = FilteredEvent}, + HideData = create_hide_menu(Bar, S2), + SearchData = create_search_menu(Bar, S2), + FilterData = create_filter_menu(Bar, S#state.filters), + wxFrame:connect(Frame, command_menu_selected, []), + wxFrame:connect(Frame, key_up), + wxFrame:connect(Frame, close_window, [{skip,true}]), + wxFrame:setFocus(Frame), + wxPanel:setSizer(Panel, Sizer), + wxFrame:show(Frame), + S2#state{menu_data = HideData++SearchData++FilterData, editor = Editor, frame = Frame}. + +menuitem(Menu, Id, Text, UserData) -> + Item = wxMenu:append(Menu, Id, Text), + {wxMenuItem:getId(Item), UserData}. + +create_file_menu(Bar) -> + Menu = wxMenu:new([]), + wxMenu:append(Menu, ?wxID_SAVE, "Save"), + wxMenu:append(Menu, ?wxID_PRINT,"Print"), + wxMenu:appendSeparator(Menu), + wxMenu:append(Menu, ?wxID_EXIT, "Close"), + wxMenuBar:append(Bar, Menu, "File"). + +create_filter_menu(Bar, Filters) -> + Menu = wxMenu:new([]), + wxMenuItem:enable(wxMenu:append(Menu, ?wxID_ANY, "Select Filter"), [{enable, false}]), + wxMenu:appendSeparator(Menu), + Item = fun(F, {N,Acc}) when F#filter.name =:= ?DEFAULT_FILTER_NAME-> + Label = lists:concat([pad_string(F#filter.name, 20, $\ , right), "(0)"]), + MenuItem = menuitem(Menu, ?wxID_ANY, Label, F), + {N + 1, [MenuItem|Acc]}; + (F, {N, Acc}) -> + Name = F#filter.name, + Label = lists:concat([pad_string(Name, 20, $\ , right), "(", N, ")"]), + MenuItem = menuitem(Menu, ?wxID_ANY, Label, F), + {N + 1, [MenuItem|Acc]} + end, + Filters2 = lists:keysort(#filter.name, Filters), + {_,MenuData} = lists:foldl(Item, {1, []}, Filters2), + wxMenuBar:append(Bar, Menu, "Filters"), + MenuData. + +create_hide_menu(Bar, S) -> + Menu = wxMenu:new([]), + E = S#state.filtered_event, + From = E#event.from, + To = E#event.to, + MenuData = + if + S#state.viewer_pid =:= undefined -> + ignore; + From =:= To -> + wxMenuItem:enable(wxMenu:append(Menu, ?wxID_ANY, "Hide actor in Viewer "), + [{enable, false}]), + wxMenu:appendSeparator(Menu), + Hide = menuitem(Menu, ?wxID_ANY, "From=To (f|t|b)", {hide, [From]}), + wxMenu:appendSeparator(Menu), + wxMenuItem:enable(wxMenu:append(Menu, ?wxID_ANY, "Show actor in Viewer "), + [{enable, false}]), + wxMenu:appendSeparator(Menu), + Show = menuitem(Menu, ?wxID_ANY, "From=To (F|T|B)", {show, [From]}), + [Show,Hide]; + true -> + wxMenuItem:enable(wxMenu:append(Menu, ?wxID_ANY, "Hide actor in Viewer "), + [{enable, false}]), + wxMenu:appendSeparator(Menu), + Hide = [menuitem(Menu, ?wxID_ANY, "From (f)", {hide, [From]}), + menuitem(Menu, ?wxID_ANY, "To (t)", {hide, [To]}), + menuitem(Menu, ?wxID_ANY, "Both (b)", {hide, [From, To]})], + wxMenu:appendSeparator(Menu), + wxMenuItem:enable(wxMenu:append(Menu, ?wxID_ANY, "Show actor in Viewer "), + [{enable, false}]), + wxMenu:appendSeparator(Menu), + Show = [menuitem(Menu, ?wxID_ANY, "From (F)", {show, [From]}), + menuitem(Menu, ?wxID_ANY, "To (T)", {show, [To]}), + menuitem(Menu, ?wxID_ANY, "Both (B)", {show, [From, To]})], + Show++Hide + end, + wxMenuBar:append(Bar, Menu, "Hide"), + MenuData. + +create_search_menu(Bar, S) -> + Menu = wxMenu:new([]), + E = S#state.filtered_event, + From = E#event.from, + To = E#event.to, + wxMenuItem:enable(wxMenu:append(Menu, ?wxID_ANY, "Search in Viewer "), + [{enable, false}]), + wxMenu:appendSeparator(Menu), + MenuData = + if + S#state.viewer_pid =:= undefined -> + [menuitem(Menu, ?wxID_ANY, "Abort search. Display all (a)", {mode, all})]; + From =:= To -> + Key = et_collector:make_key(S#state.event_order, E), + ModeS = {search_actors, forward, Key, [From]}, + ModeR = {search_actors, reverse, Key, [From]}, + [menuitem(Menu, ?wxID_ANY, "Forward from this event (s)", {mode, ModeS}), + menuitem(Menu, ?wxID_ANY, "Reverse from this event (r)", {mode, ModeR}), + menuitem(Menu, ?wxID_ANY, "Abort search. Display all (a)", {mode, all})]; + 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]}, + [menuitem(Menu, ?wxID_ANY, "Forward from this event (s)", {mode, ModeS}), + menuitem(Menu, ?wxID_ANY, "Reverse from this event (r)", {mode, ModeR}), + menuitem(Menu, ?wxID_ANY, "Abort search. Display all (a)", {mode, all})] + end, + wxMenuBar:append(Bar, Menu, "Search"), + MenuData. + +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 is_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), + wxTextCtrl:appendText(Editor, String), + 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 is_integer(Mega), is_integer(Sec), is_integer(Micro) -> + {{Y, Mo, D}, {H, Mi, S}} = calendar:now_to_universal_time(Now), + lists:concat([Y, "-", + pad_string(Mo, 2, $0, left), "-", + pad_string(D, 2, $0, left), + "T", + pad_string(H, 2, $0, left), ":", + pad_string(Mi, 2, $0, left), ":", + pad_string(S, 2, $0, left), ".", + 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(Int, MinLen, Char, Dir) when is_integer(Int) -> + pad_string(integer_to_list(Int), MinLen, Char, Dir); +pad_string(Atom, MinLen, Char, Dir) when is_atom(Atom) -> + pad_string(atom_to_list(Atom), MinLen, Char, Dir); +pad_string(String, MinLen, Char, Dir) when is_integer(MinLen), MinLen >= 0 -> + Len = length(String), + case {Len >= MinLen, Dir} of + {true, _} -> + String; + {false, right} -> + String ++ lists:duplicate(MinLen - Len, Char); + {false, left} -> + lists:duplicate(MinLen - Len, Char) ++ String + end. + +send_viewer_event(S, Event) -> + case S#state.viewer_pid of + ViewerPid when is_pid(ViewerPid) -> + ViewerPid ! {et, Event}; + undefined -> + ignore + end. + +select_file(Frame, Message, DefaultFile, Style) -> + Dialog = wxFileDialog:new(Frame, + [{message, Message}, + {defaultDir, filename:dirname(DefaultFile)}, + {defaultFile, filename:basename(DefaultFile)}, + {style, Style}]), + Choice = + case wxMessageDialog:showModal(Dialog) of + ?wxID_CANCEL -> cancel; + ?wxID_OK -> {ok, wxFileDialog:getPath(Dialog)} + end, + wxFileDialog:destroy(Dialog), + Choice. diff --git a/lib/et/src/et_wx_viewer.erl b/lib/et/src/et_wx_viewer.erl new file mode 100644 index 0000000000..1e2677e216 --- /dev/null +++ b/lib/et/src/et_wx_viewer.erl @@ -0,0 +1,2124 @@ +%% +%% %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_wx_viewer). + +-behaviour(gen_server). + +%% External exports +-export([start_link/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"). +-include_lib("wx/include/wx.hrl"). + +-define(unknown, "UNKNOWN"). +-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). + +-record(state, + {parent_pid, % Pid of parent process + auto_shutdown, % Shutdown collector when last subscriber dies + 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 + filter_menu, + pending_actor, % Pending actor - move or toggle + first_event, % Key of first event (regardless of visibility) + last_event, % Key of last event (regardless of visibility) + events_per_page, % Maximum number of shown events + events, % Queue containg all event keys (regardless of visibility) + n_events, % Number of events available in the collector + max_actors, % Maximum number of shown actors + actors, % List of known actors + refresh_needed, % Refresh is needed in order to show all actors + detail_level, % Show only events with lesser detail level + hide_actions, % Hide/show events where to == from actor (bool) + hide_actors, % Hide/show events with unknown actor (bool) + display_all, + context, % display | print + title, % GUI: Window title + frame, % 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 + normal_font, % GUI: Font to be used on text labels + bold_font, % GUI: Font to be used on text labels + pen, + brush, + print_psdd, + print_d, + canvas_width, % GUI: Canvas width + canvas_height, % GUI: Canvas height + canvas, % GUI: Canvas object + canvas_sizer, + scroll_bar, % GUI: Canvas scroll bar + y_pos, % GUI: Current y position on canvas + menu_data, + checkbox_data, + hide_actions_box, + hide_actors_box, + status_bar, + event_file, + wx_debug, % GUI: WX debug level + trap_exit}). % trap_exit process flag + + +-record(actor, {name, string, include, exclude}). +-record(e, {pos, key, event}). + +%%%---------------------------------------------------------------------- +%%% Client side +%%%---------------------------------------------------------------------- + +start_link(Options) -> + case parse_opt(Options, default_state(), []) of + {ok, S, CollectorOpt} -> + case S#state.collector_pid of + CollectorPid when is_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([{auto_shutdown, true} | 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, + n_events = 0, + detail_level = ?detail_level_max, + active_filter = ?DEFAULT_FILTER_NAME, + filters = [?DEFAULT_FILTER], + event_order = trace_ts, + events_per_page = 100, + first_event = first, + last_event = first, + events = queue_new(), + max_actors = 5, + actors = [create_actor(?unknown)], + pending_actor = ?unknown, + hide_actions = false, + hide_actors = false, + display_all = true, + context = display, + refresh_needed = false, + scale = 2, + canvas_height = 0, + canvas_width = 0, + width = 800, + height = 600, + event_file = filename:absname("et_viewer.etrace"), + wx_debug = 0, + trap_exit = true}. + +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 is_pid(Parent); Parent =:= undefined -> + parse_opt(T, S#state{parent_pid = Parent}, CollectorOpt); + {wx_debug, Level} -> + parse_opt(T, S#state{wx_debug = Level}, CollectorOpt); + {trap_exit, Bool} when Bool =:= true; Bool =:= false-> + parse_opt(T, S#state{trap_exit = Bool}, CollectorOpt); + {title, Title} -> + parse_opt(T, S#state{title = name_to_string(Title)}, CollectorOpt); + {detail_level, Level} when is_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); + {scale, Scale} when is_integer(Scale), Scale > 0 -> + parse_opt(T, S#state{scale = Scale}, CollectorOpt); + {width, W} when is_integer(W), W > 0 -> + parse_opt(T, S#state{width = W, canvas_width = W}, CollectorOpt); + {height, WH} when is_integer(WH), WH > 0 -> + parse_opt(T, S#state{height = WH, canvas_height = WH}, CollectorOpt); + {collector_pid, Pid} when is_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 is_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 + is_atom(Name), is_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 + is_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} -> + %% Kept for backward compatibility + parse_opt(T, S, CollectorOpt); + {max_actors, Max} when is_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 is_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); + {include, ActorNames} when is_list(ActorNames) -> + Actors = [opt_create_actor(Name, include, S#state.actors) || Name <- ActorNames], + parse_opt(T, S#state{actors = Actors}, CollectorOpt); + {exclude, ActorNames} when is_list(ActorNames) -> + Actors = [opt_create_actor(Name, exclude, S#state.actors) || Name <- ActorNames], + parse_opt(T, S#state{actors = Actors}, CollectorOpt); + {first_event, _FirstKey} -> + %% NYI + parse_opt(T, S, CollectorOpt); + {hide_actors, Bool} when Bool =:= true; Bool =:= false -> + parse_opt(T, S#state{hide_actors = Bool}, CollectorOpt); + {hide_actions, Bool} when Bool =:= true; Bool =:= false -> + parse_opt(T, S#state{hide_actions = Bool}, CollectorOpt); + {hide_unknown, Bool} when Bool =:= true; Bool =:= false -> + %% Kept for backward compatibility + parse_opt(T, S, CollectorOpt); + {display_mode, _Mode} -> + %% Kept for backward compatibility + parse_opt(T, S, 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 is_atom(Name), is_function(Fun) -> + F = #filter{name = Name, function = Fun}, + Filters = lists:keydelete(Name, #filter.name, S#state.filters), + Filters2 = lists:keysort(#filter.name, [F | Filters]), + S2 = create_filter_menu(S, S#state.active_filter, Filters2), + S2#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 is_atom(Name), Name =/= S#state.active_filter -> + Filters = lists:keydelete(Name, #filter.name, S#state.filters), + S2 = create_filter_menu(S, S#state.active_filter, Filters), + S2#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. + +%%%---------------------------------------------------------------------- +%%% Callback functions from gen_server +%%%---------------------------------------------------------------------- + +%%---------------------------------------------------------------------- +%% Func: init/1 +%% Returns: {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%%---------------------------------------------------------------------- + +init([S]) when is_record(S, state) -> + process_flag(trap_exit, S#state.trap_exit), + case S#state.parent_pid of + undefined -> ok; + ParentPid -> link(ParentPid) + end, + wx:new(), + wx:debug(S#state.wx_debug), + et_collector:dict_insert(S#state.collector_pid, + {subscriber, self()}, + ?MODULE), + S2 = create_main_window(S), + EventsPerPage = events_per_page(S2, S2#state.height), + S3 = revert_main_window(S2#state{events_per_page = EventsPerPage}), + Timeout = timeout(S3), + {ok, S3, Timeout}. + +%%---------------------------------------------------------------------- +%% 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) -> + wxFrame:destroy(S#state.frame), + opt_unlink(S#state.parent_pid), + {stop, shutdown, ok, S}; +handle_call({open_event, N}, _From, S) when is_integer(N), N > 0-> + Reply = do_open_event(S, N), + reply(Reply, 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, {more_events, N}}, S) -> + %% io:format("more events: ~p \n", [N]), + S4 = + if + N =:= S#state.n_events -> + S; + true -> + Missing = S#state.events_per_page - queue_length(S#state.events), + if + Missing =:= 0 -> + update_scroll_bar(S#state{n_events = N}); + Missing > 0 -> + OldEvents = queue_to_list(S#state.events), + {S2, NewEvents} = + collect_more_events(S#state{n_events = N}, + S#state.last_event, + Missing), + S3 = replace_events(S2, OldEvents ++ NewEvents), + refresh_main_window(S3) + end + end, + noreply(S4); +handle_info({et, {insert_actors, ActorNames}}, S) when is_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 is_list(ActorNames)-> + Fun = fun(N, Actors) when N =:= ?unknown -> + Actors; + (N, Actors) -> + lists:keydelete(N, #actor.name, Actors) + end, + Actors = lists:foldl(Fun, S#state.actors, ActorNames), + S2 = refresh_main_window(S#state{actors = Actors}), + 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 = revert_main_window(S), + noreply(S2); +handle_info({et, {display_mode, _Mode}}, S) -> + %% Kept for backward compatibility + noreply(S); +handle_info({et, close}, S) -> + wxFrame:destroy(S#state.frame), + opt_unlink(S#state.parent_pid), + {stop, shutdown, S}; +handle_info(#wx{id=?wxID_HELP}, S) -> + HelpString = + "Vertical scroll:\n" + "\tUse mouse wheel and up/down arrows to scroll little.\n" + "\tUse page up/down and home/end buttons to scroll more.\n\n" + "Display details of an event:\n" + "\tLeft mouse click on the event label or the arrow.\n\n" + "Highlight actor (toggle):\n" + "\tLeft mouse click on the actor name tag.\n" + "\tThe actor name will be enclosed in square brackets [].\n\n" + "Exclude actor (toggle):\n" + "\tRight mouse click on the actor name tag.\n" + "\tThe actor name will be enclosed in round brackets ().\n\n" + "Move actor:\n" + "\tLeft mouse button drag and drop on actor name tag.\n\n" + "Display all (reset settings for hidden and/or highlighted actors):\n" + "\tPress the 'a' button.", + Dialog = + wxMessageDialog:new(S#state.frame, HelpString, + [{style, 0 + bor ?wxOK + bor ?wxICON_INFORMATION + bor ?wxSTAY_ON_TOP}, + {caption, "Help"}]), + wxMessageDialog:showModal(Dialog), + noreply(S); +handle_info(#wx{id=Id, event = #wxCommand{type = command_menu_selected}}, S=#state{filter_menu = {_,Data}}) -> + CollectorPid = S#state.collector_pid, + case get_value(Id, 3, S#state.menu_data) of + close -> + wxFrame:destroy(S#state.frame), + opt_unlink(S#state.parent_pid), + {stop, shutdown, S}; + 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 = revert_main_window(S), + noreply(S2); + {display_mode, _Mode} -> + %% Kept for backward compatibility + noreply(S); + display_all -> + S2 = display_all(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 -> + Style = ?wxFD_OPEN bor ?wxFD_OVERWRITE_PROMPT, + Msg = "Select a file to load events from", + S2 = + case select_file(S#state.frame, Msg, S#state.event_file, Style) of + {ok, NewFile} -> + et_collector:start_trace_client(CollectorPid, event_file, NewFile), + S#state{event_file = NewFile}; + cancel -> + S + end, + noreply(S2); + save_all -> + Style = ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT, + Msg = "Select a file to save events to", + S2 = + case select_file(S#state.frame, Msg, S#state.event_file, Style) of + {ok, NewFile} -> + et_collector:save_event_file(CollectorPid, NewFile, [existing, write, keep]), + S#state{event_file = NewFile}; + cancel -> + S + end, + noreply(S2); + print_setup -> + S2 = print_setup(S), + noreply(S2); + print_one_page = Scope -> + S2 = print(S, Scope), + noreply(S2); + print_all_pages = Scope -> + S2 = print(S, Scope), + noreply(S2); + {open_viewer, Scale} -> + Actors = [A#actor.name || A <- S#state.actors], + open_viewer(Scale, S#state.active_filter, Actors, S), + noreply(S); + + _ -> + case get_value(Id, 3, Data) of + {data, F=#filter{}, Scale} -> + open_viewer(S#state.scale+Scale, F#filter.name, [?unknown], S); + {data, F=#filter{}} -> + open_viewer(S#state.scale, F#filter.name, [?unknown], S); + false -> + ok + end, + noreply(S) + end; +handle_info(#wx{event = #wxCommand{type = command_slider_updated, commandInt = Level}}, S) -> + if + Level >= ?detail_level_min, + Level =< ?detail_level_max -> + S2 = S#state{detail_level = Level}, + S3 = revert_main_window(S2), + noreply(S3); + + true -> + noreply(S) + end; +handle_info(#wx{id = Id, event = #wxCommand{type = command_checkbox_clicked, commandInt = Int}}, S) -> + case get_value(Id, 2, S#state.checkbox_data) of + hide_actions -> + case Int of + 1 -> + S2 = S#state{hide_actions = true}, + S3 = revert_main_window(S2), + noreply(S3); + 0 -> + S2 = S#state{hide_actions = false}, + S3 = revert_main_window(S2), + noreply(S3) + end; + hide_actors -> + case Int of + 1 -> + S2 = S#state{hide_actors = true}, + S3 = revert_main_window(S2), + noreply(S3); + 0 -> + S2 = S#state{hide_actors = false}, + S3 = revert_main_window(S2), + noreply(S3) + end; + false -> + noreply(S) + end; +handle_info(#wx{event = #wxMouse{type = left_down, x = X, y = Y}}, S) -> + S3 = + case y_to_n(Y, S) of + actor -> + %% Actor click + case S#state.actors of + [] -> + S; + Actors -> + N = x_to_n(X, S), + A = lists:nth(N, Actors), + S#state{pending_actor = A} + end; + {event, N} -> + %% Event click + do_open_event(S, N), + S + end, + noreply(S3); +handle_info(#wx{event = #wxMouse{type = left_up}}, S) when S#state.pending_actor =:= undefined -> + noreply(S); +handle_info(#wx{event = #wxMouse{type = left_up, x = X, y = Y}}, S) -> + S3 = + case y_to_n(Y, S) of + actor -> + %% Actor click + case S#state.actors of + [] -> + S; + Actors -> + N = x_to_n(X, S), + A = lists:nth(N, Actors), + Pending = S#state.pending_actor, + if + A#actor.name =:= Pending#actor.name -> + %% Toggle include actor + A2 = A#actor{include = not A#actor.include}, + %% io:format("include ~p: ~p -> ~p\n", + %% [A#actor.name, A#actor.include, A2#actor.include]), + Actors2 = lists:keyreplace(A#actor.name, #actor.name, Actors, A2), + DisplayAll = not lists:keymember(true, #actor.include, Actors2), + S2 = S#state{actors = Actors2, display_all = DisplayAll}, + revert_main_window(S2); + true -> + move_actor(Pending, A, Actors, S) + end + end; + {event, _N} -> + %% Event click ignored + S + end, + noreply(S3#state{pending_actor = undefined}); +handle_info(#wx{event = #wxMouse{type = right_up, x = X, y = Y}}, S) -> + S3 = + case y_to_n(Y, S) of + actor -> + %% Actor click + case S#state.actors of + [] -> + S; + Actors -> + %% Toggle exclude actor + N = x_to_n(X, S), + A = lists:nth(N, Actors), + A2 = A#actor{exclude = not A#actor.exclude}, + Actors2 = lists:keyreplace(A#actor.name, #actor.name, Actors, A2), + S2 = S#state{actors = Actors2}, + revert_main_window(S2) + end; + {event, _N} -> + %% Event click ignored + S + end, + noreply(S3#state{pending_actor = undefined}); +handle_info(#wx{event = #wxKey{keyCode = KeyCode, shiftDown = SD}}, S) -> + case KeyCode of + $C when SD =:= true -> + close_all(S); + $c -> + close_all_others(S); + ?WXK_HOME -> + S2 = scroll_first(S), + noreply(S2); + ?WXK_END -> + S2 = scroll_last(S), + noreply(S2); + ?WXK_UP -> + S2 = scroll_up(S), + noreply(S2); + ?WXK_DOWN -> + S2 = scroll_down(S), + noreply(S2); + ?WXK_PAGEUP -> + S2 = scroll_prev(S), + noreply(S2); + ?WXK_PAGEDOWN -> + S2 = scroll_next(S), + noreply(S2); + $F when SD =:= true -> + et_collector:multicast(S#state.collector_pid, first), + noreply(S); + $F -> + S2 = scroll_first(S), + noreply(S2); + $P when SD =:= true -> + et_collector:multicast(S#state.collector_pid, prev), + noreply(S); + $P -> + S2 = scroll_prev(S), + noreply(S2); + $N when SD =:= true -> + et_collector:multicast(S#state.collector_pid, next), + noreply(S); + $N -> + S2 = scroll_next(S), + noreply(S2); + $L when SD =:= true -> + et_collector:multicast(S#state.collector_pid, last), + noreply(S); + $L -> + S2 = scroll_last(S), + noreply(S2); + $R when SD =:= true -> + et_collector:multicast(S#state.collector_pid, refresh), + noreply(S); + $R -> + S2 = revert_main_window(S), + noreply(S2); + $A -> + S2 = display_all(S), + noreply(S2); + $= -> + Scale = S#state.scale, + Actors = [A#actor.name || A <- S#state.actors], + open_viewer(Scale, S#state.active_filter, Actors, S), + noreply(S); + Int when Int =:= $+; Int =:= ?WXK_NUMPAD_ADD -> + Scale = S#state.scale + 1, + Actors = [A#actor.name || A <- S#state.actors], + open_viewer(Scale, S#state.active_filter, Actors, S), + noreply(S); + Int when Int =:= $-; Int =:= ?WXK_NUMPAD_SUBTRACT -> + case S#state.scale of + 1 -> + ignore; + 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(?DEFAULT_FILTER_NAME, #filter.name, S#state.filters) of + {value, F} when is_record(F, filter) -> + open_viewer(S#state.scale, F#filter.name, [?unknown], S); + false -> + ok + end, + noreply(S); + Int when is_integer(Int), Int > $0, Int =< $9 -> + case catch lists:nth(Int-$0, S#state.filters) of + F when is_record(F, filter) -> + open_viewer(S#state.scale, F#filter.name, [?unknown], S); + {'EXIT', _} -> + ok + end, + noreply(S); + + _ -> + noreply(S) + end; +handle_info(#wx{event = #wxScroll{type = scroll_changed}} = Wx, S) -> + get_latest_scroll(Wx), + Pos = wxScrollBar:getThumbPosition(S#state.scroll_bar), + {_, LineTopY, LineBotY} = calc_y(S), + Range = LineBotY - LineTopY, + N = round(S#state.n_events * Pos / Range), + Diff = + case N - event_pos(S) of + D when D < 0 -> D - 1; + D -> D + 1 + end, + S2 = scroll_changed(S, Diff), + noreply(S2); +handle_info(timeout, S) -> + noreply(S); +handle_info({'EXIT', Pid, Reason}, S) -> + if + Pid =:= S#state.collector_pid -> + io:format("collector died: ~p\n\n", [Reason]), + wxFrame:destroy(S#state.frame), + {stop, Reason, S}; + Pid =:= S#state.parent_pid -> + wxFrame:destroy(S#state.frame), + {stop, Reason, S}; + true -> + noreply(S) + end; +handle_info(#wx{event = #wxClose{}}, S) -> + opt_unlink(S#state.parent_pid), + {stop, shutdown, S}; +handle_info(#wx{event = #wxMouse{type = mousewheel, wheelRotation = Rot}}, S) when Rot > 0 -> + S2 = scroll_up(S), + noreply(S2); +handle_info(#wx{event = #wxMouse{type = mousewheel, wheelRotation = Rot}}, S) when Rot < 0 -> + S2 = scroll_down(S), + noreply(S2); +handle_info(#wx{event = #wxSize{size = {OldW, OldH}}} = Wx, S) -> + #wx{event = #wxSize{type = size, size = {W, H}}} = get_latest_resize(Wx), + S2 = S#state{width = W, height = H, canvas_width = W, canvas_height = H}, + EventsPerPage = events_per_page(S, H), + Diff = EventsPerPage - S#state.events_per_page, + S6 = + if + OldW =:= W, OldH =:= H, S2#state.events_per_page =:= EventsPerPage -> + S2; + Diff =:= 0 -> + refresh_main_window(S2); + Diff > 0 -> + OldEvents = queue_to_list(S2#state.events), + {S3, NewEvents} = collect_more_events(S2, S2#state.last_event, Diff), + S4 = S3#state{events_per_page = EventsPerPage}, + S5 = replace_events(S4, OldEvents ++ NewEvents), + refresh_main_window(S5); + Diff < 0 -> + OldEvents = queue_to_list(S2#state.events), + RevEvents = delete_n(lists:reverse(OldEvents), abs(Diff)), + S3 = S2#state{events_per_page = EventsPerPage}, + S4 = replace_events(S3, lists:reverse(RevEvents)), + refresh_main_window(S4) + end, + noreply(S6); +handle_info(#wx{event = #wxFocus{}}, S) -> + wxWindow:setFocus(S#state.canvas), % Get keyboard focus + noreply(S); +handle_info(#wx{event = #wxMouse{type = enter_window}}, S) -> + wxWindow:setFocus(S#state.canvas), % Get keyboard focus + noreply(S); +handle_info(#wx{event = #wxPaint{}}, S) -> + S2 = refresh_main_window(S), + noreply(S2); +handle_info(#wx{event = #wxMouse{type = T, x=X,y=Y}}, S) -> + io:format("~p ~p\n", [T, {X,Y}]), + noreply(S); +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 stuff +%%%---------------------------------------------------------------------- + +reply(Reply, S) -> + Timeout = timeout(S), + {reply, Reply, S, Timeout}. + +noreply(S) -> + Timeout = timeout(S), + {noreply, S, Timeout}. + +timeout(_S) -> + infinity. + +scroll_first(S) -> + EventsPerPage = S#state.events_per_page, + {S2, NewEvents} = + collect_more_events(S, first, EventsPerPage), + S3 = + case NewEvents of + [] -> + S2; + [FirstE | _] -> + S2#state{first_event = FirstE} + end, + S4 = replace_events(S3, NewEvents), + refresh_main_window(S4). + +scroll_last(S) -> + case collect_more_events(S, last, -1) of + {_, []} -> + scroll_first(S); + {S2, NewEvents} -> + [FirstE | _] = NewEvents, + S3 = replace_events(S2#state{first_event = FirstE}, NewEvents), + refresh_main_window(S3) + end. + +scroll_prev(S) -> + scroll_up(S, S#state.events_per_page). + +scroll_next(S) -> + scroll_down(S, S#state.events_per_page). + +scroll_up(S) -> + scroll_up(S, calc_scroll(S)). + +scroll_up(S, Expected) -> + N = queue_length(S#state.events), + EventsPerPage = S#state.events_per_page, + Expected2 = adjust_expected(Expected, N, EventsPerPage), + OldEvents = queue_to_list(S#state.events), + case collect_more_events(S, S#state.first_event, -Expected2) of + {_, []} -> + S; + {S2, NewEvents} -> + NewN = length(NewEvents), + if + N + NewN > EventsPerPage -> + RevAllEvents = lists:reverse(OldEvents, lists:reverse(NewEvents)), + TooMany = N + NewN - EventsPerPage, + case delete_n(RevAllEvents, TooMany) of + [] -> + S; + [LastE | _] = RevEvents -> + Events = lists:reverse(RevEvents), + S3 = replace_events(S2#state{last_event = LastE}, Events), + refresh_main_window(S3) + end; + true -> + Events = NewEvents ++ OldEvents, + LastE = lists:last(Events), + S3 = replace_events(S2#state{last_event = LastE}, Events), + refresh_main_window(S3) + end + end. + +scroll_down(S) -> + scroll_down(S, calc_scroll(S)). + +scroll_down(S, Expected) -> + N = queue_length(S#state.events), + EventsPerPage = S#state.events_per_page, + Expected2 = adjust_expected(Expected, N, EventsPerPage), + OldEvents = queue_to_list(S#state.events), + case collect_more_events(S, S#state.last_event, Expected2) of + {_, []} -> + case collect_more_events(S, S#state.first_event, N - EventsPerPage) of + {_, []} -> + S; + {S2, NewEvents} -> + Events = NewEvents ++ OldEvents, + [FirstE | _] = Events, + S3 = replace_events(S2#state{first_event = FirstE}, Events), + refresh_main_window(S3) + end; + {S2, NewEvents} -> + AllEvents = OldEvents ++ NewEvents, + case delete_n(AllEvents, length(NewEvents)) of + [] -> + scroll_first(S); + Events -> + [FirstE | _] = Events, + S3 = replace_events(S2#state{first_event = FirstE}, Events), + refresh_main_window(S3) + end + end. + +scroll_changed(S, Expected) -> + if + Expected =:= 0 -> + refresh_main_window(S); + Expected < 0 -> + %% Up + OldPos = event_pos(S), + NewPos = lists:max([OldPos + Expected, 0]), + case S#state.first_event of + #e{key = Key, pos = OldPos} -> + jump_up(S, Key, OldPos, NewPos); + first -> + scroll_first(S); + last -> + scroll_last(S) + end; + true -> + %% Down + OldPos = event_pos(S), + NewPos = lists:min([OldPos + Expected, S#state.n_events]), + case S#state.first_event of + #e{key = Key, pos = OldPos} -> + jump_down(S, Key, OldPos, NewPos); + first = Key -> + jump_down(S, Key, 0, NewPos); + last -> + scroll_last(S) + end + end. + +jump_up(S, OldKey, OldPos, NewPos) -> + Try = NewPos - OldPos, + Order = S#state.event_order, + Fun = fun(Event, #e{pos = P}) when P >= NewPos -> + Key = et_collector:make_key(Order, Event), + #e{event = Event, key = Key, pos = P - 1}; + (_, Acc) -> + Acc + end, + PrevE = et_collector:iterate(S#state.collector_pid, + OldKey, + Try, + Fun, + #e{key = OldKey, pos = OldPos}), + case collect_more_events(S, PrevE, S#state.events_per_page) of + {_, []} -> + S; + {S2, Events} -> + [FirstE | _] = Events, + S3 = replace_events(S2#state{first_event = FirstE}, Events), + refresh_main_window(S3) + end. + +jump_down(S, OldKey, OldPos, NewPos) -> + Try = NewPos - OldPos, + Order = S#state.event_order, + Fun = fun(Event, #e{pos = P}) when P < NewPos -> + Key = et_collector:make_key(Order, Event), + #e{event = Event, key = Key, pos = P + 1}; + (_, Acc) -> + Acc + end, + PrevE = et_collector:iterate(S#state.collector_pid, + OldKey, + Try, + Fun, + #e{key = OldKey, pos = OldPos}), + case collect_more_events(S, PrevE, S#state.events_per_page) of + {_, []} -> + S; + {S2, Events} -> + [FirstE | _] = Events, + S3 = replace_events(S2#state{first_event = FirstE}, Events), + refresh_main_window(S3) + end. + +adjust_expected(Expected, N, EventsPerPage) -> + if + N < EventsPerPage -> + EventsPerPage - N; + Expected < EventsPerPage -> + Expected; + true -> + EventsPerPage + end. + +calc_scroll(S) -> + lists:max([S#state.events_per_page div 3, 1]). + +revert_main_window(S) -> + {S2, Events} = revert(S), + S3 = replace_events(S2, Events), + refresh_main_window(S3). + +revert(S) -> + EventsPerPage = S#state.events_per_page, + %% Find previous event + case collect_more_events(S, S#state.first_event, -1) of + {_, []} -> + collect_more_events(S, first, EventsPerPage); + {S2, [_PrevEvent]} -> + collect_more_events(S, S2#state.first_event, EventsPerPage) + end. + +delete_n(List, 0) -> + List; +delete_n([], _) -> + []; +delete_n([_ | Tail], N) when N > 0 -> + delete_n(Tail, N - 1). + +pick_n(Rest, 0, Acc) -> + {lists:reverse(Acc), Rest}; +pick_n([], _N, Acc) -> + {lists:reverse(Acc), []}; +pick_n([Head | Tail], N, Acc) when N > 0 -> + pick_n(Tail, N - 1, [Head | Acc]). + +close_all(S) -> + close_all_others(S), + wxFrame:destroy(S#state.frame), + opt_unlink(S#state.parent_pid), + {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). + +opt_unlink(Pid) -> + if + Pid =:= undefined -> + ignore; + true -> + unlink(Pid) + end. + +%%%---------------------------------------------------------------------- +%%% 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}, + {detail_level, S#state.detail_level}, + {active_filter, FilterName}, + {event_order, S#state.event_order}, + {first_event, S#state.first_event}, + {max_actors, S#state.max_actors}, + {hide_actions, S#state.hide_actions}, + {hide_actors, S#state.hide_actors}, + {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) -> + {NormalFont, BoldFont} = select_fonts(S#state.scale), + 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, + Frame = wxFrame:new(wx:null(), + ?wxID_ANY, + Title ++ " (filter: " ++ Name ++ ")", + [{size, {S#state.width, S#state.height}}]), + StatusBar = wxFrame:createStatusBar(Frame), + + Panel = wxPanel:new(Frame, []), + Bar = wxMenuBar:new(), + wxFrame:setMenuBar(Frame,Bar), + MainSizer = wxBoxSizer:new(?wxVERTICAL), + + MenuData = lists:flatten([create_file_menu(Bar), + create_viewer_menu(Bar), + create_collector_menu(Bar)]), + FilterMenu = wxMenu:new([]), + S2 = create_filter_menu(S#state{filter_menu = {FilterMenu,[]}}, + S#state.active_filter, + S#state.filters), + wxMenuBar:append(Bar, FilterMenu, "Filters and scaling"), + create_help_menu(Bar), + + OptSizer = wxBoxSizer:new(?wxHORIZONTAL), + CheckSizer = wxBoxSizer:new(?wxVERTICAL), + HideActions = wxCheckBox:new(Panel, ?wxID_ANY, "Hide From=To"), + wxCheckBox:setValue(HideActions, S#state.hide_actions), + HideActors = wxCheckBox:new(Panel, ?wxID_ANY, "Hide (excluded actors)"), + wxCheckBox:setValue(HideActors, S#state.hide_actors), + CheckBoxData = [{wxCheckBox:getId(HideActions), hide_actions}, + {wxCheckBox:getId(HideActors), hide_actors}], + wxPanel:connect(Panel, command_checkbox_clicked), + wxSizer:add(CheckSizer, HideActions), + wxSizer:add(CheckSizer,HideActors), + wxSizer:add(OptSizer, CheckSizer, [{border, 10}, {flag, ?wxALL}]), + DetailLevelBox = wxStaticBoxSizer:new(?wxHORIZONTAL, + Panel, + [{label, "Detail level"}]), + DetailLevel = wxSlider:new(Panel, ?wxID_ANY, + S#state.detail_level, + ?detail_level_min, + ?detail_level_max, + [{style, ?wxSL_LABELS}, + {size, {200,-1}}]), + wxStatusBar:setStatusText(StatusBar, where_text(S)), + wxFrame:connect(Frame, command_slider_updated), + wxSizer:add(DetailLevelBox, DetailLevel), + wxSizer:add(OptSizer, DetailLevelBox, [{border, 10}, {flag, ?wxALL}]), + wxSizer:addStretchSpacer(OptSizer), + wxSizer:add(MainSizer, OptSizer), + wxSizer:add(MainSizer, + wxStaticLine:new(Panel, [{style, ?wxLI_HORIZONTAL}]), + [{flag, ?wxEXPAND}]), + + CanvasSizer = wxBoxSizer:new(?wxHORIZONTAL), + Canvas = wxPanel:new(Panel, []), + {CanvasW,CanvasH} = wxPanel:getSize(Canvas), + ScrollBar = wxScrollBar:new(Panel, ?wxID_ANY, [{style, ?wxSB_VERTICAL}]), + + wxSizer:add(CanvasSizer, Canvas, [{flag, ?wxEXPAND}, {proportion, 1}]), + wxSizer:add(CanvasSizer, ScrollBar, [{flag, ?wxEXPAND}]), + wxSizer:add(MainSizer, CanvasSizer, [{flag, ?wxEXPAND}, {proportion, 1}]), + wxPanel:connect(Canvas, left_down), + wxPanel:connect(Canvas, left_up), + wxPanel:connect(Canvas, right_up), + wxPanel:connect(Canvas, size), + wxPanel:connect(Canvas, paint), + wxPanel:connect(Canvas, key_down), + wxPanel:connect(Canvas, kill_focus), + wxPanel:connect(Canvas, enter_window, [{skip, true}]), + wxFrame:connect(Frame, command_menu_selected), + wxFrame:connect(Frame, close_window), + wxFrame:connect(ScrollBar, scroll_changed), + wxPanel:setSize(Panel, {S#state.width, S#state.height}), + wxPanel:setSizer(Panel, MainSizer), + wxFrame:show(Frame), + wxPanel:setFocus(Canvas), + wxPanel:connect(Canvas, mousewheel), + + S3 = S2#state{title = Title, + frame = Frame, packer = Panel, + normal_font = NormalFont, bold_font = BoldFont, + canvas_width = CanvasW, canvas_height = CanvasH, + canvas = Canvas, + canvas_sizer = CanvasSizer, + scroll_bar = ScrollBar, + y_pos = ?initial_y * S#state.scale, + pen = wxPen:new(), + brush = wxBrush:new(), + print_d = undefined, + print_psdd = undefined, + menu_data = MenuData, + checkbox_data = CheckBoxData, + hide_actions_box = HideActions, + hide_actors_box = HideActors, + status_bar = StatusBar}, + DC = wxClientDC:new(Canvas), + S4 = draw_all_actors(S3, DC), + wxClientDC:destroy(DC), + S4. + +where_text(#state{n_events = N} = S) -> + Pos = event_pos(S), + lists:concat([Pos, " (", N, ")"]). + +event_pos(#state{first_event = E, events = Events, n_events = Last}) -> + case E of + #e{pos = Pos} -> + Pos; + first -> + case queue_length(Events) of + 0 -> + 0; + _ -> + 1 + end; + last -> + Last + end. + +init_printers(#state{print_d = undefined, print_psdd = undefined} = S) -> + PD = wxPrintData:new(), + PSDD = wxPageSetupDialogData:new(PD), + wxPrintData:setPaperId(PD, ?wxPAPER_A4), + wxPageSetupDialogData:setMarginTopLeft(PSDD, {15,15}), + wxPageSetupDialogData:setMarginBottomRight(PSDD, {15,15}), + S#state{print_d = PD, print_psdd = PSDD}; +init_printers(#state{} = S) -> + S. + +select_fonts(Scale) when is_integer(Scale) -> + Size = + case Scale of + 1 -> 5; + 2 -> 10; + 3 -> 14; + 4 -> 20; + S -> S*6 + end, + {wxFont:new(Size, ?wxFONTFAMILY_TELETYPE, ?wxNORMAL, ?wxNORMAL,[]), + wxFont:new(Size, ?wxFONTFAMILY_TELETYPE, ?wxNORMAL, ?wxBOLD,[])}. + +get_value(Key, Pos, TupleList) when is_list(TupleList)-> + case lists:keysearch(Key, 1, TupleList) of + {value, Tuple} when is_tuple(Tuple)-> + element(Pos, Tuple); + false -> + false + end. + +menuitem(Menu, Id, Text, UserData) -> + Item = wxMenu:append(Menu, Id, Text), + {wxMenuItem:getId(Item), Item, UserData}. + +create_file_menu(Bar) -> + Menu = wxMenu:new([]), + Data = [ + menuitem(Menu, ?wxID_ANY, "Clear all events in the Collector", clear_all), + menuitem(Menu, ?wxID_ANY, "Load events to the Collector from file", load_all), + menuitem(Menu, ?wxID_ANY, "Save all events in the Collector to file", save_all), + + menuitem(Menu, ?wxID_PRINT_SETUP, "Print setup", print_setup), + menuitem(Menu, ?wxID_ANY, "Print current page", print_one_page), + menuitem(Menu, ?wxID_PRINT, "Print all pages", print_all_pages), + + menuitem(Menu, ?wxID_ANY, "Close this Viewer", close), + menuitem(Menu, ?wxID_ANY, "Close all other Viewers, but this (c)", close_all_others), + menuitem(Menu, ?wxID_ANY, "Close all Viewers and the Collector) (C) ", close_all) + ], + wxMenu:insertSeparator(Menu, 3), + wxMenu:insertSeparator(Menu, 7), + wxMenuBar:append(Bar, Menu, "File"), + Data. + +create_viewer_menu(Bar) -> + Menu = wxMenu:new([]), + wxMenuItem:enable(wxMenu:append(Menu, ?wxID_ANY, "Scroll this Viewer"), + [{enable, false}]), + wxMenu:appendSeparator(Menu), + D1 = [ + menuitem(Menu, ?wxID_ANY, "First (f)", first), + menuitem(Menu, ?wxID_ANY, "Last (l)", last), + menuitem(Menu, ?wxID_ANY, "Prev (p)", prev), + menuitem(Menu, ?wxID_ANY, "Next (n)", next), + menuitem(Menu, ?wxID_ANY, "Refresh (r)", refresh) + ], + wxMenu:appendSeparator(Menu), + D2 = [ + menuitem(Menu, ?wxID_ANY, "Up 5 (Up)", up), + menuitem(Menu, ?wxID_ANY, "Down 5 (Down)", down) + ], + wxMenu:appendSeparator(Menu), + wxMenuItem:enable(wxMenu:append(Menu, ?wxID_ANY, "Actor visibility in this Viewer"), + [{enable, false}]), + wxMenu:appendSeparator(Menu), + D3 = [menuitem(Menu, ?wxID_ANY, "Display all actors (a)", display_all)], + wxMenuBar:append(Bar, Menu, "Viewer"), + [D1,D2,D3]. + +create_collector_menu(Bar) -> + Menu = wxMenu:new([]), + wxMenuItem:enable(wxMenu:append(Menu, ?wxID_ANY, "Scroll all Viewers"), + [{enable, false}]), + wxMenu:appendSeparator(Menu), + Data = [ + menuitem(Menu, ?wxID_ANY, "First (F)", first_all), + menuitem(Menu, ?wxID_ANY, "Last (L)", last_all), + menuitem(Menu, ?wxID_ANY, "Prev (P)", prev_all), + menuitem(Menu, ?wxID_ANY, "Next (N)", next_all), + menuitem(Menu, ?wxID_ANY, "Refresh (R)", refresh_all) + ], + wxMenuBar:append(Bar, Menu, "Collector"), + Data. + +create_filter_menu(S=#state{filter_menu = {Menu,Data}}, ActiveFilterName, Filters) -> + wx:foreach(fun({_,I,_}) -> + wxMenu:delete(Menu,I); + (I) -> + try + wxMenu:delete(Menu,I) + catch + _:Reason -> + io:format("Could not delete item: ~p, because ~p.\n", [I, Reason]) + end + end, + Data), + Item = fun(F, {N, Acc}) when F#filter.name =:= all -> + Label = lists:concat([pad_string(F#filter.name, 20), "(0)"]), + {N+1, [menuitem(Menu, ?wxID_ANY, Label, {data, F})|Acc]}; + (F, {N, Acc}) -> + Label = lists:concat([pad_string(F#filter.name, 20), "(", N, ")"]), + {N+1, [menuitem(Menu, ?wxID_ANY, Label, {data, F})|Acc]} + end, + D1 = [I1 = wxMenu:append(Menu, ?wxID_ANY, "Same Filter New Scale"), + wxMenu:appendSeparator(Menu)], + wxMenuItem:enable(I1, [{enable,false}]), + {value, Filter} = lists:keysearch(ActiveFilterName, #filter.name, Filters), + Same = lists:concat([pad_string(ActiveFilterName, 20), "(=) same scale"]), + Larger = lists:concat([pad_string(ActiveFilterName, 20), "(+) bigger scale"]), + Smaller = lists:concat([pad_string(ActiveFilterName, 20), "(-) smaller scale"]), + D2 = [ + menuitem(Menu, ?wxID_ANY, Same, {data, Filter, 0}), + menuitem(Menu, ?wxID_ANY, Smaller, {data, Filter, -1}), + menuitem(Menu, ?wxID_ANY, Larger, {data, Filter, 1}), + wxMenu:appendSeparator(Menu), + I2 = wxMenu:append(Menu, ?wxID_ANY, "New Filter Same Scale"), + wxMenu:appendSeparator(Menu) + ], + wxMenuItem:enable(I2, [{enable,false}]), + {_,D3} = lists:foldl(Item, {1,[]}, Filters), + S#state{filter_menu = {Menu, lists:flatten([D1,D2,D3])}}. + +create_help_menu(Bar) -> + Menu = wxMenu:new([]), + menuitem(Menu, ?wxID_HELP, "Info", help), + wxMenuBar:append(Bar, Menu, "Help"). + +clear_canvas(S) -> + DC = wxClientDC:new(S#state.canvas), + wxDC:clear(DC), + {CanvasW, CanvasH} = wxPanel:getSize(S#state.canvas), + wxSizer:recalcSizes(S#state.canvas_sizer), + S2 = S#state{refresh_needed = false, + y_pos = ?initial_y * S#state.scale, + canvas_width = CanvasW, + canvas_height = CanvasH, + events = queue_new()}, + S3 = draw_all_actors(S2, DC), + wxClientDC:destroy(DC), + S3. + +replace_events(S, []) -> + S#state{first_event = first, + last_event = first, + events = queue_new()}; +replace_events(S, Events) -> + Queue = lists:foldl(fun(E, Q) -> queue_in(E, Q) end, queue_new(), Events), + S#state{events = Queue}. + +refresh_main_window(S) -> + wx:batch(fun() -> + S2 = clear_canvas(S), + S3 = update_scroll_bar(S2), + display_events(S3, queue_to_list(S#state.events)) + end). + +display_events(S, []) -> + S; +display_events(S, Events) -> + DC = wxClientDC:new(S#state.canvas), + S2 = lists:foldl(fun(E, State) -> display_event(E, State, DC) end, S, Events), + wxClientDC:destroy(DC), + S2. + +collect_more_events(S, PrevKey = first, Try) -> + PrevE = #e{event = undefined, key = PrevKey, pos = 0}, + S2 = S#state{first_event = PrevE, last_event = PrevE}, + do_collect_more_events(S2, Try, PrevE, []); +collect_more_events(S, PrevKey = last, Try) -> + PrevE = #e{event = undefined, key = PrevKey, pos = S#state.n_events}, + S2 = S#state{first_event = PrevE, last_event = PrevE}, + do_collect_more_events(S2, Try, PrevE, []); +collect_more_events(S, #e{} = PrevE, Try) -> + do_collect_more_events(S, Try, PrevE, []). + +do_collect_more_events(#state{collector_pid = Collector, + event_order = Order, + active_filter = Active, + filters = Filters} = S, + Try, + PrevE, + Acc) -> + Incr = + if + Try < 0 -> -1; + true -> 1 + end, + PrevKey = PrevE#e.key, + {value, #filter{function = FilterFun}} = + lists:keysearch(Active, #filter.name, Filters), + {_S, _Incr, _Order, _Active, _FilterFun, LastE, NewEvents} = + et_collector:iterate(Collector, + PrevKey, + Try, + fun collect_event/2, + {S, Incr, Order, Active, FilterFun, PrevE, []}), + Expected = abs(Try), + Actual = length(NewEvents), + Missing = Expected - Actual, + {S2, Acc2, Try2} = + if + Try < 0 -> + {S#state{first_event = LastE}, NewEvents ++ Acc, -Missing}; + true -> + TmpEvents = lists:reverse(NewEvents), + {S#state{last_event = LastE}, Acc ++ TmpEvents, Missing} + end, + if + Missing =/= 0, PrevKey =/= LastE#e.key -> + do_collect_more_events(S2, Try2, LastE, Acc2); + true -> + {S2, Acc2} + end. + +collect_event(Event, {S, Incr, Order, Active, FilterFun, #e{pos = PrevPos}, Events}) -> + Key = et_collector:make_key(Order, Event), + E = #e{event = Event, key = Key, pos = PrevPos + Incr}, + {LastE, Events2} = + case catch FilterFun(Event) of + true -> + case is_hidden(Event#event.from, Event#event.to, S) of + true -> + {E, Events}; + false -> + {E, [E | Events]} + end; + {true, Event2} -> + Key2 = et_collector:make_key(Order, Event2), + E2 = E#e{event = Event2, key = Key2}, + case is_hidden(Event2#event.from, Event2#event.to, S) of + true -> + {E2, Events}; + false -> + {E2, [E2 | Events]} + end; + false -> + {E, Events}; + Bad -> + Contents = {bad_filter, S#state.active_filter, Bad, Event}, + Event2 = Event#event{contents = Contents, + from = bad_filter, + to = bad_filter}, + E2 = E#e{event = Event2}, + {E2, [E2 | Events]} + end, + {S, Incr, Order, Active, FilterFun, LastE, Events2}. + +display_event(#e{event = Event} = E, S, DC) + when Event#event.detail_level < S#state.detail_level -> + {FromRefresh, From} = ensure_actor(Event#event.from, S, DC), + {FromName, FromPos, S2} = From, + {ToRefresh, To} = ensure_actor(Event#event.to, S2, DC), + {ToName, ToPos, S3} = To, + S4 = + if + FromRefresh =/= false, ToRefresh =/= false -> + S3#state{refresh_needed = true, + events = queue_in(E, S3#state.events)}; + FromName =:= ToName -> + case S#state.hide_actions of + true -> + S3; + false -> + Label = name_to_string(Event#event.label), + draw_named_arrow(Label, FromName, ToName, FromPos, ToPos, E, S3, DC) + end; + true -> + Label = name_to_string(Event#event.label), + draw_named_arrow(Label, FromName, ToName, FromPos, ToPos, E, S3, DC) + end, + S4; +display_event(#e{}, S, _DC) -> + S. + +draw_named_arrow(Label, FromName, ToName, FromPos, ToPos, E, S, DC) -> + case S#state.y_pos + (?incr_y * S#state.scale) of + _ when S#state.hide_actors =:= true, FromName =:= ?unknown -> + S; + _ when S#state.hide_actors =:= true, ToName =:= ?unknown -> + S; + Y when Y > S#state.canvas_height -> + S#state{refresh_needed = true, + events = queue_in(E, S#state.events)}; + Y -> + S2 = S#state{y_pos = Y, events = queue_in(E, S#state.events)}, + S3 = draw_arrow(FromPos, ToPos, S2, DC), + draw_label(Label, FromName, ToName, FromPos, ToPos, S3, DC) + end. + +draw_arrow(Pos, Pos, S, _DC) -> + S; +draw_arrow(FromPos, ToPos, S, DC) -> + Y = S#state.y_pos, + wxPen:setColour(S#state.pen, ?wxBLACK), + wxDC:setPen(DC, S#state.pen), + wxDC:drawLine(DC, {FromPos , Y}, {ToPos, Y}), + + %% Draw arrow head + Radians = calc_angle({FromPos, Y}, {ToPos, Y}), + Len = 5, + Radians2 = Radians + 3.665191429188092, + Radians3 = Radians + 2.617993877991494, + {X3, Y3} = calc_point({ToPos, Y}, Len, Radians2), + {X4, Y4} = calc_point({ToPos, Y}, Len, Radians3), + Points = [{round(ToPos), round(Y)}, + {round(X3), round(Y3)}, + {round(X4), round(Y4)}], + wxBrush:setColour(S#state.brush, ?wxBLACK), + wxDC:setBrush(DC, S#state.brush), + wxDC:drawPolygon(DC, Points, []), + S. + + %% Calclulate angle in radians for a line between two points +calc_angle({X1, Y1}, {X2, Y2}) -> + math:atan2((Y2 - Y1), (X2 - X1)). + + %% Calc new point at a given distance and angle from another point +calc_point({X, Y}, Length, Radians) -> + X2 = round(X + Length * math:cos(Radians)), + Y2 = round(Y + Length * math:sin(Radians)), + {X2, Y2}. + +draw_label(Label, FromName, ToName, FromPos, ToPos, S, DC) -> + Color = + if + FromName =:= ?unknown, + ToName =:= ?unknown -> {2, 71, 254};% blue + FromName =:= ?unknown -> {255,126,0}; % orange + ToName =:= ?unknown -> {255,126,0}; % orange + FromPos =:= ToPos -> {2, 71, 254};% blue + true -> {227,38, 54} % red + end, + Scale = S#state.scale, + X = lists:min([FromPos, ToPos]) + (6 * Scale), + Y = S#state.y_pos, + write_text(Label, X, Y, Color, S#state.normal_font, S, DC), + S. + +draw_all_actors(S, DC) -> + Scale = S#state.scale, + Fun = fun(A, X) -> + case draw_actor(A, X, S, DC) of + true -> + X + (?incr_x * Scale); + false -> + X + end + end, + lists:foldl(Fun, ?initial_x * Scale, S#state.actors), + S. + +%% Returns: {NeedsRefreshBool, {ActorPos, NewsS, NewActors}} +ensure_actor(Name, S, DC) -> + do_ensure_actor(Name, S, S#state.actors, 0, DC). + +do_ensure_actor(Name, S, [H | _], N, _DC) when H#actor.name =:= Name -> + Pos = (?initial_x + (N * ?incr_x)) * S#state.scale, + {false, {Name, Pos, S}}; +do_ensure_actor(Name, S, [H | T], N, DC) -> + if + S#state.hide_actors, H#actor.exclude -> + do_ensure_actor(Name, S, T, N, DC); + true -> + do_ensure_actor(Name, S, T, N + 1, DC) + end; +do_ensure_actor(Name, S, [], N, DC) -> + %% 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 + is_integer(MaxActors), N > MaxActors -> + %% Failed on max_actors limit, put into unknown + %% Assume that unknown always is in actor list + ensure_actor(?unknown, S, DC); + 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, DC), + {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, DC), + {false, {Name, Pos, S#state{actors = S#state.actors ++ [A]}}} + end. + +draw_actor(A, LineX, S, DC) -> + if + S#state.hide_actors, A#actor.exclude -> + false; + true -> + Scale = S#state.scale, + TextX = LineX - (5 * Scale), + {TextY, LineTopY, LineBotY} = calc_y(S), + Color = + case A#actor.name of + ?unknown -> {255,126,0};% orange + _ -> {227,38,54} % red + end, + {String, Font} = + if + S#state.context =:= display, A#actor.exclude -> + {"(" ++ A#actor.string ++ ")", S#state.normal_font}; + S#state.context =:= display, A#actor.include -> + {"[" ++ A#actor.string ++ "]", S#state.bold_font}; + true -> + {A#actor.string, S#state.normal_font} + end, + write_text(String, TextX, TextY, Color, Font, S, DC), + wxPen:setColour(S#state.pen, Color), + wxDC:setPen(DC, S#state.pen), + wxDC:drawLines(DC, [{LineX, LineTopY}, {LineX, LineBotY}]), + true + end. + +calc_y(#state{canvas_height = Height, scale = Scale}) -> + TextY = ?initial_y * Scale, + LineTopY = round(TextY + ((?incr_y / 2) * Scale)), + LineBotY = Height, + %% LineBotY = round(Height - ((?incr_y / 2) * Scale)), + {TextY, LineTopY, LineBotY}. + +display_all(S) -> + Actors = S#state.actors, + Actors2 = [A#actor{include = false, exclude = false} || A <- Actors], + S2 = S#state{actors = Actors2, + display_all = true, + hide_actions = false, + hide_actors = false}, + wxCheckBox:setValue(S2#state.hide_actions_box, S2#state.hide_actions), + wxCheckBox:setValue(S2#state.hide_actors_box, S2#state.hide_actors), + revert_main_window(S2). + +is_hidden(A, S) -> + case S#state.display_all of + true -> + A#actor.exclude; + false -> + A#actor.exclude orelse not A#actor.include + end. + +is_hidden(From, To, S) -> + Actors = S#state.actors, + DisplayAll = S#state.display_all, + FromMatch = lists:keysearch(From, #actor.name, Actors), + ToMatch = lists:keysearch(To, #actor.name, Actors), + case {FromMatch, ToMatch} of + {false, false} -> + not DisplayAll; + {false, {value, T}} -> + is_hidden(T, S); + {{value, F}, false} -> + is_hidden(F, S); + {{value, F}, {value, T}} when DisplayAll -> + is_hidden(F, S) orelse is_hidden(T, S); + {{value, F}, {value, T}} when F#actor.include; T#actor.include -> + F#actor.exclude orelse T#actor.exclude; + {{value, _F}, {value, _T}}-> + true + 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, Color, Font, S, DC) -> + wxDC:setFont(DC, Font), + wxDC:setTextForeground(DC, Color), + wxDC:drawText(DC, Text, {X, round(Y - (?incr_y * S#state.scale / 2))-3}). + +do_open_event(S, N) -> + Events = queue_to_list(S#state.events), + S2 = S#state{events = list_to_queue(Events)}, + case catch lists:nth(N, Events) of + {'EXIT', _} -> + {error, {no_such_event, N}}; + #e{key = Key} -> + Pid = S#state.collector_pid, + Fun = fun create_contents_window/2, + Prev = et_collector:iterate(Pid, Key, -1), + {S2, Res} = + if + Prev =:= Key -> + et_collector:iterate(Pid, first, 1, Fun, {S2, []}); + true -> + et_collector:iterate(Pid, Prev, 1, Fun, {S2, []}) + end, + case Res of + [] -> + {error, no_contents_viewer_started}; + [Single] -> + Single; + Multi -> + {error, {too_many, Multi}} + end + end. + +create_contents_window(Event, {S, Res}) -> + Options = [{viewer_pid, self()}, + {event, Event}, + {event_order, S#state.event_order}, + {active_filter, S#state.active_filter}, + {wx_debug, S#state.wx_debug} + | S#state.filters], + case catch et_wx_contents_viewer:start_link(Options) of + {ok, Pid} -> + {S, [{ok, Pid} | Res]}; + {error, Reason} -> + ok = error_logger:format("~p(~p): create_contents_window(~p) ->~n ~p~n", + [?MODULE, self(), Options, Reason]), + {S, [{error, Reason} | Res]}; + Stuff -> + {S, [{error, {stuff, Stuff}} | Res]} + end. + +print_setup(S) -> + S2 = #state{print_psdd = PSDD0, print_d = PD0} = init_printers(S), + + wxPageSetupDialogData:setPrintData(PSDD0, PD0), + PSD = wxPageSetupDialog:new(S#state.frame, [{data,PSDD0}]), + wxPageSetupDialog:showModal(PSD), + + PSDD1 = wxPageSetupDialog:getPageSetupData(PSD), + PD1 = wxPageSetupDialogData:getPrintData(PSDD1), + + %% Create new objects using copy constructor + PD = wxPrintData:new(PD1), + PsDD = wxPageSetupDialogData:new(PSDD1), + wxPageSetupDialog:destroy(PSD), + wxPageSetupDialogData:destroy(PSDD0), + wxPrintData:destroy(PD0), + S2#state{print_psdd=PsDD, print_d=PD}. + +print(#state{print_d = undefined, print_psdd = undefined} = S, Scope) -> + S2 = print_setup(S), + print(S2, Scope); +print(#state{print_psdd = PSDD, print_d = PD} = S, Scope) -> + PDD = wxPrintDialogData:new(PD), + wxPrintDialogData:enablePrintToFile(PDD, true), + wxPrintDialogData:enablePageNumbers(PDD, true), + wxPrintDialogData:enableSelection(PDD, true), + Tab = ets:new(?MODULE, [public]), + GetPageInfo = + fun(This) -> + {_, _, PW, PH} = wxPrintout:getPaperRectPixels(This), + PrinterS = S#state{context = printer, + canvas_width = PW, + canvas_height = PH}, + EventsPerPage = events_per_page(PrinterS, PH), + PagedEvents = paged_events(PrinterS, Scope, EventsPerPage), + [ets:insert(Tab, PE) || PE <- PagedEvents], + ets:insert(Tab, PrinterS), + NumPages = length(PagedEvents), + {1, NumPages, 1, NumPages} + end, + HasPage = + fun(_This, Page) -> + Size = ets:info(Tab, size), + NumPages = Size - 1, + (Page >= 1) andalso (Page =< NumPages) + end, + OnPrintPage = + fun(This, Page) -> + wxPrintout:mapScreenSizeToPageMargins(This, PSDD), + [PrinterS] = ets:lookup(Tab, state), + Events = ets:lookup_element(Tab, Page, 2), + DC = wxPrintout:getDC(This), + PrinterS2 = draw_all_actors(PrinterS, DC), + PrinterS3 = PrinterS2#state{y_pos = ?initial_y * PrinterS2#state.scale}, + lists:foldl(fun(E, State) -> display_event(E, State, DC) end, + PrinterS3, + Events), + true + end, + Printout1 = wxPrintout:new("Print", OnPrintPage, + [{getPageInfo, GetPageInfo}, {hasPage, HasPage}]), + Printout2 = wxPrintout:new("Print", OnPrintPage, + [{getPageInfo, GetPageInfo}, {hasPage, HasPage}]), + Preview = wxPrintPreview:new(Printout1, [{printoutForPrinting, Printout2}, {data,PDD}]), + case wxPrintPreview:isOk(Preview) of + true -> + PF = wxPreviewFrame:new(Preview, S#state.frame, []), + wxPreviewFrame:centre(PF, [{dir, ?wxBOTH}]), + wxPreviewFrame:initialize(PF), + wxPreviewFrame:centre(PF), + wxPreviewFrame:show(PF), + OnClose = fun(_Wx, EventRef) -> ets:delete(Tab), wxEvent:skip(EventRef) end, + wxPreviewFrame:connect(PF, close_window, [{callback, OnClose}]); + false -> + io:format("Could not create preview window.\n" + "Perhaps your current printer is not set correctly?~n", []), + wxPrintPreview:destroy(Preview), + ets:delete(Tab) + end, + S. + +paged_events(S, Scope, EventsPerPage) -> + {_, Events} = + case Scope of + print_one_page -> + revert(S#state{events_per_page = EventsPerPage}); + print_all_pages -> + collect_more_events(S, first, S#state.n_events) + end, + split_list(Events, EventsPerPage). + +split_list(List, N) when is_integer(N), N > 0 -> + do_split_list(List, N, 1, []). + +do_split_list([], _N, _Page, Acc) -> + lists:reverse(Acc); +do_split_list(List, N, Page, Acc) -> + {Items, Rest} = pick_n(List, N, []), + do_split_list(Rest, N, Page + 1, [{Page, Items} | Acc]). + +get_latest_resize(#wx{obj = ObjRef, event = #wxSize{}} = Wx) -> + receive + #wx{obj = ObjRef, event = #wxSize{}} = Wx2 -> + get_latest_resize(Wx2) + after 100 -> + Wx + end. + +get_latest_scroll(#wx{obj = ObjRef, event = #wxScroll{type = scroll_changed}} = Wx) -> + receive + #wx{obj = ObjRef, event = #wxScroll{type = scroll_changed}} = Wx2 -> + get_latest_scroll(Wx2) + after 100 -> + Wx + end. + +update_scroll_bar(#state{scroll_bar = ScrollBar, + status_bar = StatusBar, + events_per_page = EventsPerPage, + n_events = N} = S) -> + Opts = [{refresh, true}], + {_, LineTopY, LineBotY} = calc_y(S), + Range = LineBotY - LineTopY, + EventPos = + case event_pos(S) of + 1 -> 0; + P -> P + end, + if + N =/= 0, + EventsPerPage =/= 0 -> + PixelsPerEvent = Range / EventsPerPage, + Share = EventsPerPage / N, + wxScrollBar:setScrollbar(ScrollBar, + trunc(EventPos * Share * PixelsPerEvent), + round(Share * Range), + Range, + round(Share * Range), + Opts); + true -> + wxScrollBar:setScrollbar(ScrollBar, + 0, + Range, + Range, + Range, + Opts) + end, + wxStatusBar:setStatusText(StatusBar, where_text(S)), + S. + +events_per_page(S, PageHeight) -> + EventsPerPage = ((PageHeight - (?initial_y * S#state.scale)) div (?incr_y * S#state.scale)), + lists:max([1, EventsPerPage]). + +select_file(Frame, Message, DefaultFile, Style) -> + Dialog = wxFileDialog:new(Frame, + [{message, Message}, + {defaultDir, filename:dirname(DefaultFile)}, + {defaultFile, filename:basename(DefaultFile)}, + {style, Style}]), + Choice = + case wxMessageDialog:showModal(Dialog) of + ?wxID_CANCEL -> cancel; + ?wxID_OK -> {ok, wxFileDialog:getPath(Dialog)} + end, + wxFileDialog:destroy(Dialog), + Choice. + +%%%---------------------------------------------------------------------- +%%% String padding of actors +%%%---------------------------------------------------------------------- + +opt_create_actor(Name, Tag, S) -> + Actors = S#state.actors, + New = + case lists:keysearch(Name, #actor.name, Actors) of + {value, Old} -> Old; + false -> create_actor(Name) + end, + case Tag of + include -> New#actor{include = true}; + exclude -> New#actor{exclude = true} + end. + +create_actor(Name) -> + String = name_to_string(Name), + %% PaddedString = pad_string(String, 8), + #actor{name = Name, string = String, include = false, exclude = false}. + +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 is_atom(Atom) -> + pad_string(atom_to_list(Atom), MinLen); +pad_string(String, MinLen) when is_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 is_list(List) -> + {length(List), [], List}. diff --git a/lib/et/src/modules.mk b/lib/et/src/modules.mk index 8e9dd1a386..8d6c0902fb 100644 --- a/lib/et/src/modules.mk +++ b/lib/et/src/modules.mk @@ -1,27 +1,30 @@ #-*-makefile-*- ; force emacs to enter makefile-mode # %CopyrightBegin% -# -# Copyright Ericsson AB 2001-2009. All Rights Reserved. -# +# +# Copyright Ericsson AB 2001-2010. 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_gs_contents_viewer \ + et_gs_viewer \ et_selector \ - et_viewer + et_viewer \ + et_wx_contents_viewer \ + et_wx_viewer HRL_FILES = \ ../include/et.hrl |