diff options
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 |