diff options
Diffstat (limited to 'lib/observer/src')
23 files changed, 992 insertions, 473 deletions
diff --git a/lib/observer/src/Makefile b/lib/observer/src/Makefile index dd7831fa2b..ff2bcbdb99 100644 --- a/lib/observer/src/Makefile +++ b/lib/observer/src/Makefile @@ -92,7 +92,7 @@ EXAMPLE_FILES= multitrace.erl TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) $(APP_TARGET) $(APPUP_TARGET) PRIVDIR= ../priv -WEBTOOLFILES= $(PRIVDIR)/crashdump_viewer.tool $(PRIVDIR)/erlang_observer.png +PNGFILES= $(PRIVDIR)/erlang_observer.png BINDIR= $(PRIVDIR)/bin ifeq ($(findstring win32,$(TARGET)),win32) WIN32_EXECUTABLES= $(BINDIR)/etop.bat $(BINDIR)/cdv.bat @@ -103,10 +103,6 @@ EXECUTABLES= \ $(BINDIR)/etop \ $(BINDIR)/cdv \ $(WIN32_EXECUTABLES) -CDVDIR= $(PRIVDIR)/crashdump_viewer -GIF_FILES= \ - $(CDVDIR)/collapsd.gif \ - $(CDVDIR)/exploded.gif APP_FILE= observer.app @@ -163,9 +159,7 @@ release_spec: opt $(INSTALL_DATA) $(TARGET_FILES) "$(RELSYSDIR)/ebin" $(INSTALL_DIR) "$(RELSYSDIR)/priv/bin" $(INSTALL_SCRIPT) $(EXECUTABLES) "$(RELSYSDIR)/priv/bin" - $(INSTALL_DIR) "$(RELSYSDIR)/priv/crashdump_viewer" - $(INSTALL_DATA) $(WEBTOOLFILES) "$(RELSYSDIR)/priv" - $(INSTALL_DATA) $(GIF_FILES) "$(RELSYSDIR)/priv/crashdump_viewer" + $(INSTALL_DATA) $(PNGFILES) "$(RELSYSDIR)/priv" release_docs_spec: diff --git a/lib/observer/src/cdv_bin_cb.erl b/lib/observer/src/cdv_bin_cb.erl index 0cea1fdcf0..5472d36a6f 100644 --- a/lib/observer/src/cdv_bin_cb.erl +++ b/lib/observer/src/cdv_bin_cb.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2016. All Rights Reserved. +%% Copyright Ericsson AB 2013-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -58,7 +58,7 @@ binary_to_term_fun(Bin) -> try binary_to_term(Bin) of Term -> plain_html(io_lib:format("~p",[Term])) catch error:badarg -> - Warning = "This binary can not be coverted to an Erlang term", + Warning = "This binary can not be converted to an Erlang term", observer_html_lib:warning(Warning) end end. diff --git a/lib/observer/src/cdv_detail_wx.erl b/lib/observer/src/cdv_detail_wx.erl index 44f121f359..27057fd27f 100644 --- a/lib/observer/src/cdv_detail_wx.erl +++ b/lib/observer/src/cdv_detail_wx.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2016. All Rights Reserved. +%% Copyright Ericsson AB 2013-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -55,7 +55,7 @@ init([Id, Data, ParentFrame, Callback, Parent]) -> end, {stop,normal}; {info,Info} -> - observer_lib:display_info_dialog(Info), + observer_lib:display_info_dialog(ParentFrame,Info), {stop,normal} end. diff --git a/lib/observer/src/cdv_ets_cb.erl b/lib/observer/src/cdv_ets_cb.erl index 52a90b093b..a652729ed3 100644 --- a/lib/observer/src/cdv_ets_cb.erl +++ b/lib/observer/src/cdv_ets_cb.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2016. All Rights Reserved. +%% Copyright Ericsson AB 2013-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -30,34 +30,26 @@ -include("crashdump_viewer.hrl"). %% Defines --define(COL_ID, 0). --define(COL_NAME, ?COL_ID+1). --define(COL_SLOT, ?COL_NAME+1). --define(COL_OWNER, ?COL_SLOT+1). --define(COL_BUCK, ?COL_OWNER+1). --define(COL_OBJ, ?COL_BUCK+1). +-define(COL_NAME, 0). +-define(COL_IS_NAMED, ?COL_NAME+1). +-define(COL_OWNER, ?COL_IS_NAMED+1). +-define(COL_OBJ, ?COL_OWNER+1). -define(COL_MEM, ?COL_OBJ+1). --define(COL_TYPE, ?COL_MEM+1). %% Callbacks for cdv_virtual_list_wx -col_to_elem(id) -> col_to_elem(?COL_ID); -col_to_elem(?COL_ID) -> #ets_table.id; +col_to_elem(id) -> col_to_elem(?COL_NAME); +col_to_elem(?COL_IS_NAMED) -> #ets_table.is_named; col_to_elem(?COL_NAME) -> #ets_table.name; -col_to_elem(?COL_SLOT) -> #ets_table.slot; col_to_elem(?COL_OWNER) -> #ets_table.pid; -col_to_elem(?COL_TYPE) -> #ets_table.data_type; -col_to_elem(?COL_BUCK) -> #ets_table.buckets; col_to_elem(?COL_OBJ) -> #ets_table.size; col_to_elem(?COL_MEM) -> #ets_table.memory. col_spec() -> - [{"Id", ?wxLIST_FORMAT_LEFT, 200}, - {"Name", ?wxLIST_FORMAT_LEFT, 200}, - {"Slot", ?wxLIST_FORMAT_RIGHT, 50}, + [{"Name", ?wxLIST_FORMAT_LEFT, 200}, + {"Is Named", ?wxLIST_FORMAT_CENTRE, 70}, {"Owner", ?wxLIST_FORMAT_CENTRE, 120}, {"Objects", ?wxLIST_FORMAT_RIGHT, 80}, {"Memory", ?wxLIST_FORMAT_RIGHT, 80} -% {"Type", ?wxLIST_FORMAT_LEFT, 50} ]. get_info(Owner) -> @@ -73,7 +65,7 @@ get_details(Id, Data) -> {ok,{"Table:" ++ Id,Proplist,""}}. get_detail_cols(all) -> - {[{ets, ?COL_ID}, {process, ?COL_OWNER}],true}; + {[{ets, ?COL_NAME}, {process, ?COL_OWNER}],true}; get_detail_cols(_W) -> {[],true}. diff --git a/lib/observer/src/cdv_mem_cb.erl b/lib/observer/src/cdv_mem_cb.erl index ba972d6963..abeddc7335 100644 --- a/lib/observer/src/cdv_mem_cb.erl +++ b/lib/observer/src/cdv_mem_cb.erl @@ -77,6 +77,10 @@ fix_alloc([{Title,Columns,Data}|Tables]) -> fix_alloc(Tables)]; fix_alloc([{Title,[{_,V}|_]=Data}|Tables]) -> fix_alloc([{Title,lists:duplicate(length(V),[]),Data}|Tables]); +fix_alloc([{"",[]}|Tables]) -> % no name and no data, probably truncated dump + fix_alloc(Tables); +fix_alloc([{Title,[]=Data}|Tables]) -> % no data, probably truncated dump + fix_alloc([{Title,[],Data}|Tables]); fix_alloc([]) -> []. diff --git a/lib/observer/src/cdv_wx.erl b/lib/observer/src/cdv_wx.erl index 2587a6e64e..1e3fb6289e 100644 --- a/lib/observer/src/cdv_wx.erl +++ b/lib/observer/src/cdv_wx.erl @@ -17,7 +17,7 @@ %% %% %CopyrightEnd% -module(cdv_wx). --compile(export_all). + -behaviour(wx_object). -export([start/1]). diff --git a/lib/observer/src/crashdump_viewer.erl b/lib/observer/src/crashdump_viewer.erl index 9268dac180..8d70f5e2c8 100644 --- a/lib/observer/src/crashdump_viewer.erl +++ b/lib/observer/src/crashdump_viewer.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2016. All Rights Reserved. +%% Copyright Ericsson AB 2003-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -90,6 +90,7 @@ %% All possible tags - use macros in order to avoid misspelling in the code +-define(abort,abort). -define(allocated_areas,allocated_areas). -define(allocator,allocator). -define(atoms,atoms). @@ -321,8 +322,16 @@ handle_call(general_info,_From,State=#state{file=File}) -> NumAtoms = GenInfo#general_info.num_atoms, WS = parse_vsn_str(GenInfo#general_info.system_vsn,4), TW = case get(truncated) of - true -> ["WARNING: The crash dump is truncated. " - "Some information might be missing."]; + true -> + case get(truncated_reason) of + undefined -> + ["WARNING: The crash dump is truncated. " + "Some information might be missing."]; + Reason -> + ["WARNING: The crash dump is truncated " + "("++Reason++"). " + "Some information might be missing."] + end; false -> [] end, ets:insert(cdv_reg_proc_table, @@ -515,8 +524,15 @@ truncated_warning([Tag|Tags]) -> false -> truncated_warning(Tags) end. truncated_warning() -> - ["WARNING: The crash dump is truncated here. " - "Some information might be missing."]. + case get(truncated_reason) of + undefined -> + ["WARNING: The crash dump is truncated here. " + "Some information might be missing."]; + Reason -> + ["WARNING: The crash dump is truncated here " + "("++Reason++"). " + "Some information might be missing."] + end. truncated_here(Tag) -> case get(truncated) of @@ -692,6 +708,7 @@ val(Fd, NoExist) -> {eof,[]} -> NoExist; [] -> NoExist; {eof,Val} -> Val; + "=abort:"++_ -> NoExist; Val -> Val end. @@ -787,7 +804,7 @@ do_read_file(File) -> ?erl_crash_dump -> reset_index_table(), insert_index(Tag,Id,N1+1), - put(last_tag,{Tag,""}), + put_last_tag(Tag,""), indexify(Fd,Rest,N1), end_progress(), check_if_truncated(), @@ -831,7 +848,7 @@ indexify(Fd,Bin,N) -> <<_:Pos/binary,TagAndRest/binary>> = Bin, {Tag,Id,Rest,N1} = tag(Fd,TagAndRest,N+Pos), insert_index(Tag,Id,N1+1), % +1 to get past newline - put(last_tag,{Tag,Id}), + put_last_tag(Tag,Id), indexify(Fd,Rest,N1); nomatch -> case progress_read(Fd) of @@ -911,7 +928,10 @@ general_info(File) -> WholeLine -> WholeLine end, - GI = get_general_info(Fd,#general_info{created=Created}), + {Slogan,SysVsn} = get_slogan_and_sysvsn(Fd,[]), + GI = get_general_info(Fd,#general_info{created=Created, + slogan=Slogan, + system_vsn=SysVsn}), {MemTot,MemMax} = case lookup_index(?memory) of @@ -965,12 +985,20 @@ general_info(File) -> mem_max=MemMax, instr_info=InstrInfo}. +get_slogan_and_sysvsn(Fd,Acc) -> + case val(Fd,eof) of + "Slogan: " ++ SloganPart when Acc==[] -> + get_slogan_and_sysvsn(Fd,[SloganPart]); + "System version: " ++ SystemVsn -> + {lists:append(lists:reverse(Acc)),SystemVsn}; + eof -> + {lists:append(lists:reverse(Acc)),"-1"}; + SloganPart -> + get_slogan_and_sysvsn(Fd,[[$\n|SloganPart]|Acc]) + end. + get_general_info(Fd,GenInfo) -> case line_head(Fd) of - "Slogan" -> - get_general_info(Fd,GenInfo#general_info{slogan=val(Fd)}); - "System version" -> - get_general_info(Fd,GenInfo#general_info{system_vsn=val(Fd)}); "Compiled" -> get_general_info(Fd,GenInfo#general_info{compile_time=val(Fd)}); "Taints" -> @@ -1174,7 +1202,11 @@ parse_link_list("{from,"++Str,Links,Monitors,MonitoredBy) -> parse_link_list(", "++Rest,Links,Monitors,MonitoredBy) -> parse_link_list(Rest,Links,Monitors,MonitoredBy); parse_link_list([],Links,Monitors,MonitoredBy) -> - {lists:reverse(Links),lists:reverse(Monitors),lists:reverse(MonitoredBy)}. + {lists:reverse(Links),lists:reverse(Monitors),lists:reverse(MonitoredBy)}; +parse_link_list(Unexpected,Links,Monitors,MonitoredBy) -> + io:format("WARNING: found unexpected data in link list:~n~s~n",[Unexpected]), + parse_link_list([],Links,Monitors,MonitoredBy). + parse_port(Str) -> {Port,Rest} = parse_link(Str,[]), @@ -1523,10 +1555,14 @@ split_pid_list_no_space([],[],Pids) -> %% Page with external ets tables get_ets_tables(File,Pid,WS) -> ParseFun = fun(Fd,Id) -> - get_etsinfo(Fd,#ets_table{pid=list_to_pid(Id)},WS) + ET = get_etsinfo(Fd,#ets_table{pid=list_to_pid(Id)},WS), + ET#ets_table{is_named=tab_is_named(ET)} end, lookup_and_parse_index(File,{?ets,Pid},ParseFun,"ets"). +tab_is_named(#ets_table{id=Name,name=Name}) -> "yes"; +tab_is_named(#ets_table{}) -> "no". + get_etsinfo(Fd,EtsTable = #ets_table{details=Ds},WS) -> case line_head(Fd) of "Slot" -> @@ -1813,16 +1849,16 @@ main_modinfo(_Fd,LM,_LineHead) -> all_modinfo(Fd,LM,LineHead) -> case LineHead of "Current attributes" -> - Str = hex_to_str(val(Fd)), + Str = hex_to_str(val(Fd,"")), LM#loaded_mod{current_attrib=Str}; "Current compilation info" -> - Str = hex_to_str(val(Fd)), + Str = hex_to_str(val(Fd,"")), LM#loaded_mod{current_comp_info=Str}; "Old attributes" -> - Str = hex_to_str(val(Fd)), + Str = hex_to_str(val(Fd,"")), LM#loaded_mod{old_attrib=Str}; "Old compilation info" -> - Str = hex_to_str(val(Fd)), + Str = hex_to_str(val(Fd,"")), LM#loaded_mod{old_comp_info=Str}; Other -> unexpected(Fd,Other,"loaded modules info"), @@ -1848,7 +1884,12 @@ hex_to_term([],Acc) -> Bin}; Term -> Term - end. + end; +hex_to_term(Rest,Acc) -> + {"WARNING: The term is probably truncated!", + "I can not convert hex to term.", + Rest,list_to_binary(lists:reverse(Acc))}. + hex_to_dec("F") -> 15; hex_to_dec("E") -> 14; @@ -2159,7 +2200,8 @@ sort_allocator_types([{Name,Data}|Allocators],Acc,DoTotal) -> Type = case string:tokens(Name,"[]") of [T,_Id] -> T; - [Name] -> Name + [Name] -> Name; + Other -> Other end, TypeData = proplists:get_value(Type,Acc,[]), {NewTypeData,NewDoTotal} = sort_type_data(Type,Data,TypeData,DoTotal), @@ -2686,6 +2728,7 @@ count_index(Tag) -> %%----------------------------------------------------------------- %% Convert tags read from crashdump to atoms used as first part of key %% in cdv_dump_index_table +tag_to_atom("abort") -> ?abort; tag_to_atom("allocated_areas") -> ?allocated_areas; tag_to_atom("allocator") -> ?allocator; tag_to_atom("atoms") -> ?atoms; @@ -2720,6 +2763,14 @@ tag_to_atom(UnknownTag) -> list_to_atom(UnknownTag). %%%----------------------------------------------------------------- +%%% Store last tag for use when truncated, and reason if aborted +put_last_tag(?abort,Reason) -> + %% Don't overwrite the real last tag + put(truncated_reason,Reason); +put_last_tag(Tag,Id) -> + put(last_tag,{Tag,Id}). + +%%%----------------------------------------------------------------- %%% Fetch next chunk from crashdump file lookup_and_parse_index(File,What,ParseFun,Str) when is_list(File) -> Indices = lookup_index(What), @@ -2815,7 +2866,16 @@ collect(Pids,Acc) -> update_progress(), collect(Pids,Acc); {'DOWN', _Ref, process, Pid, {pmap_done,Result}} -> - collect(lists:delete(Pid,Pids),[Result|Acc]) + collect(lists:delete(Pid,Pids),[Result|Acc]); + {'DOWN', _Ref, process, Pid, _Error} -> + Warning = + "WARNING: an error occured while parsing data.\n" ++ + case get(truncated) of + true -> "This might be because the dump is truncated.\n"; + false -> "" + end, + io:format(Warning), + collect(lists:delete(Pid,Pids),Acc) end. %%%----------------------------------------------------------------- diff --git a/lib/observer/src/crashdump_viewer.hrl b/lib/observer/src/crashdump_viewer.hrl index a08659efd6..6a93a089fd 100644 --- a/lib/observer/src/crashdump_viewer.hrl +++ b/lib/observer/src/crashdump_viewer.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2016. All Rights Reserved. +%% Copyright Ericsson AB 2003-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -118,6 +118,7 @@ slot, id, name, + is_named, data_type="hash", buckets="-", size, diff --git a/lib/observer/src/etop.erl b/lib/observer/src/etop.erl index fcb900960b..f0990f1f25 100644 --- a/lib/observer/src/etop.erl +++ b/lib/observer/src/etop.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2002-2016. All Rights Reserved. +%% Copyright Ericsson AB 2002-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ -export([start/0, start/1, config/2, stop/0, dump/1, help/0]). %% Internal -export([update/1]). --export([loadinfo/1, meminfo/2, getopt/2]). +-export([loadinfo/2, meminfo/2, getopt/2]). -include("etop.hrl"). -include("etop_defs.hrl"). @@ -81,7 +81,7 @@ check_runtime_config(accumulate,A) when A=:=true; A=:=false -> ok; check_runtime_config(_Key,_Value) -> error. dump(File) -> - case file:open(File,[write]) of + case file:open(File,[write,{encoding,utf8}]) of {ok,Fd} -> etop_server ! {dump,Fd}; Error -> Error end. @@ -161,7 +161,7 @@ data_handler(Reader, Opts) -> {'EXIT', EPid, Reason} when EPid == Opts#opts.out_proc -> case Reason of normal -> ok; - _ -> io:format("Output server crashed: ~p~n",[Reason]) + _ -> io:format("Output server crashed: ~tp~n",[Reason]) end, stop(Opts), out_proc_stopped; @@ -180,10 +180,16 @@ stop(Opts) -> end, unregister(etop_server). -update(#opts{store=Store,node=Node,tracing=Tracing}=Opts) -> +update(#opts{store=Store,node=Node,tracing=Tracing,intv=Interval}=Opts) -> Pid = spawn_link(Node,observer_backend,etop_collect,[self()]), Info = receive {Pid,I} -> I - after 1000 -> exit(connection_lost) + after Interval -> + %% Took more than the update interval to fetch + %% data. Either the connection is lost or the + %% fetching took too long... + io:format("Timeout when waiting for process info from " + "node ~p; exiting~n", [Node]), + exit(timeout) end, #etop_info{procinfo=ProcInfo} = Info, ProcInfo1 = @@ -319,18 +325,18 @@ output(graphical) -> exit({deprecated, "Use observer instead"}); output(text) -> etop_txt. -loadinfo(SysI) -> +loadinfo(SysI,Prev) -> #etop_info{n_procs = Procs, run_queue = RQ, now = Now, wall_clock = WC, runtime = RT} = SysI, - Cpu = calculate_cpu_utilization(WC,RT), + Cpu = calculate_cpu_utilization(WC,RT,Prev#etop_info.runtime), Clock = io_lib:format("~2.2.0w:~2.2.0w:~2.2.0w", tuple_to_list(element(2,calendar:now_to_datetime(Now)))), {Cpu,Procs,RQ,Clock}. -calculate_cpu_utilization({_,WC},{_,RT}) -> +calculate_cpu_utilization({_,WC},{_,RT},_) -> %% Old version of observer_backend, using statistics(wall_clock) %% and statistics(runtime) case {WC,RT} of @@ -341,15 +347,23 @@ calculate_cpu_utilization({_,WC},{_,RT}) -> _ -> round(100*RT/WC) end; -calculate_cpu_utilization(_,undefined) -> +calculate_cpu_utilization(_,undefined,_) -> %% First time collecting - no cpu utilization has been measured %% since scheduler_wall_time flag is not yet on 0; -calculate_cpu_utilization(_,RTInfo) -> +calculate_cpu_utilization(WC,RTInfo,undefined) -> + %% Second time collecting - RTInfo shows scheduler_wall_time since + %% flag was set to true. Faking previous values by setting + %% everything to zero. + ZeroRT = [{Id,0,0} || {Id,_,_} <- RTInfo], + calculate_cpu_utilization(WC,RTInfo,ZeroRT); +calculate_cpu_utilization(_,RTInfo,PrevRTInfo) -> %% New version of observer_backend, using statistics(scheduler_wall_time) - Sum = lists:foldl(fun({_,A,T},{AAcc,TAcc}) -> {A+AAcc,T+TAcc} end, + Sum = lists:foldl(fun({{_, A0, T0}, {_, A1, T1}},{AAcc,TAcc}) -> + {(A1 - A0)+AAcc,(T1 - T0)+TAcc} + end, {0,0}, - RTInfo), + lists:zip(PrevRTInfo,RTInfo)), case Sum of {0,0} -> 0; diff --git a/lib/observer/src/etop_txt.erl b/lib/observer/src/etop_txt.erl index 3b4c176478..183641119a 100644 --- a/lib/observer/src/etop_txt.erl +++ b/lib/observer/src/etop_txt.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2002-2016. All Rights Reserved. +%% Copyright Ericsson AB 2002-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -22,35 +22,34 @@ %%-compile(export_all). -export([init/1,stop/1]). --export([do_update/3]). +-export([do_update/4]). -include("etop.hrl"). -include("etop_defs.hrl"). --import(etop,[loadinfo/1,meminfo/2]). - --define(PROCFORM,"~-15w~-20s~8w~8w~8w~8w ~-20s~n"). +-import(etop,[loadinfo/2,meminfo/2]). stop(Pid) -> Pid ! stop. init(Config) -> - loop(Config). + loop(#etop_info{},Config). -loop(Config) -> - Info = do_update(Config), +loop(Prev,Config) -> + Info = do_update(Prev,Config), receive stop -> stopped; - {dump,Fd} -> do_update(Fd,Info,Config), loop(Config); - {config,_,Config1} -> loop(Config1) - after Config#opts.intv -> loop(Config) + {dump,Fd} -> do_update(Fd,Info,Prev,Config), loop(Info,Config); + {config,_,Config1} -> loop(Info,Config1) + after Config#opts.intv -> loop(Info,Config) end. -do_update(Config) -> +do_update(Prev,Config) -> Info = etop:update(Config), - do_update(standard_io,Info,Config). + do_update(standard_io,Info,Prev,Config). -do_update(Fd,Info,Config) -> - {Cpu,NProcs,RQ,Clock} = loadinfo(Info), +do_update(Fd,Info,Prev,Config) -> + Encoding = encoding(Fd), + {Cpu,NProcs,RQ,Clock} = loadinfo(Info,Prev), io:nl(Fd), writedoubleline(Fd), case Info#etop_info.memi of @@ -73,7 +72,7 @@ do_update(Fd,Info,Config) -> io:nl(Fd), writepinfo_header(Fd), writesingleline(Fd), - writepinfo(Fd,Info#etop_info.procinfo), + writepinfo(Fd,Info#etop_info.procinfo,Encoding), writedoubleline(Fd), io:nl(Fd), Info. @@ -93,19 +92,37 @@ writepinfo(Fd,[#etop_proc_info{pid=Pid, runtime=Time, cf=MFA, mq=MQ} - |T]) -> - io:fwrite(Fd,?PROCFORM,[Pid,to_list(Name),Time,Reds,Mem,MQ,formatmfa(MFA)]), - writepinfo(Fd,T); -writepinfo(_Fd,[]) -> + |T], + Encoding) -> + io:fwrite(Fd,proc_format(Encoding), + [Pid,to_list(Name,Encoding),Time,Reds,Mem,MQ, + formatmfa(MFA,Encoding)]), + writepinfo(Fd,T,Encoding); +writepinfo(_Fd,[],_) -> ok. -formatmfa({M, F, A}) -> +formatmfa({M, F, A},latin1) -> io_lib:format("~w:~w/~w",[M, F, A]); -formatmfa(Other) -> +formatmfa({M, F, A},_) -> + io_lib:format("~w:~tw/~w",[M, F, A]); +formatmfa(Other,_) -> %% E.g. when running hipe - the current_function for some %% processes will be 'undefined' io_lib:format("~w",[Other]). -to_list(Name) when is_atom(Name) -> atom_to_list(Name); -to_list({_M,_F,_A}=MFA) -> formatmfa(MFA). +to_list(Name,_) when is_atom(Name) -> atom_to_list(Name); +to_list({_M,_F,_A}=MFA,Encoding) -> formatmfa(MFA,Encoding). + +encoding(Device) -> + case io:getopts(Device) of + List when is_list(List) -> + proplists:get_value(encoding,List,latin1); + _ -> + latin1 + end. + +proc_format(latin1) -> + "~-15w~-20s~8w~8w~8w~8w ~-20s~n"; +proc_format(_) -> + "~-15w~-20ts~8w~8w~8w~8w ~-20ts~n". diff --git a/lib/observer/src/observer_alloc_wx.erl b/lib/observer/src/observer_alloc_wx.erl index 77609b11ce..ef425f0874 100644 --- a/lib/observer/src/observer_alloc_wx.erl +++ b/lib/observer/src/observer_alloc_wx.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2015. All Rights Reserved. +%% Copyright Ericsson AB 2015-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ %% %CopyrightEnd% -module(observer_alloc_wx). --export([start_link/2]). +-export([start_link/3]). %% wx_object callbacks -export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, @@ -36,6 +36,7 @@ wins, mem, samples, + max, panel, paint, appmon, @@ -48,10 +49,10 @@ [make_win/4, setup_graph_drawing/1, refresh_panel/4, interval_dialog/2, add_data/5, precalc/4]). -start_link(Notebook, Parent) -> - wx_object:start_link(?MODULE, [Notebook, Parent], []). +start_link(Notebook, Parent, Config) -> + wx_object:start_link(?MODULE, [Notebook, Parent, Config], []). -init([Notebook, Parent]) -> +init([Notebook, Parent, Config]) -> try TopP = wxPanel:new(Notebook), Main = wxBoxSizer:new(?wxVERTICAL), @@ -74,7 +75,8 @@ init([Notebook, Parent]) -> wins = Windows, mem = MemWin, paint = PaintInfo, - time = setup_time() + time = setup_time(Config), + max = #{} } } catch _:Err -> @@ -82,9 +84,11 @@ init([Notebook, Parent]) -> {stop, Err} end. -setup_time() -> - Freq = 1, - #ti{fetch=Freq, disp=?DISP_FREQ/Freq}. +setup_time(Config) -> + Freq = maps:get(fetch, Config, 1), + #ti{disp=?DISP_FREQ/Freq, + fetch=Freq, + secs=maps:get(secs, Config, ?DISP_SECONDS)}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% handle_event(#wx{id=?ID_REFRESH_INTERVAL, event=#wxCommand{type=command_menu_selected}}, @@ -115,6 +119,10 @@ handle_sync_event(#wx{obj=Panel, event = #wxPaint{}},_, refresh_panel(Active, Win, Ti, Paint), ok. %%%%%%%%%% +handle_call(get_config, _, #state{time=Ti}=State) -> + #ti{fetch=Fetch, secs=Range} = Ti, + {reply, #{fetch=>Fetch, secs=>Range}, State}; + handle_call(Event, From, _State) -> error({unhandled_call, Event, From}). @@ -126,16 +134,17 @@ handle_info({Key, {promise_reply, {badrpc, _}}}, #state{async=Key} = State) -> {noreply, State#state{active=false, appmon=undefined}}; handle_info({Key, {promise_reply, SysInfo}}, - #state{async=Key, panel=_Panel, samples=Data, active=Active, wins=Wins0, - time=#ti{tick=Tick, disp=Disp0}=Ti} = S0) -> + #state{async=Key, samples=Data, max=Max0, + active=Active, wins=Wins0, time=#ti{tick=Tick, disp=Disp0}=Ti} = S0) -> Disp = trunc(Disp0), Next = max(Tick - Disp, 0), erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, Next}), Info = alloc_info(SysInfo), + Max = lists:foldl(fun calc_max/2, Max0, Info), {Wins, Samples} = add_data(Info, Data, Wins0, Ti, Active), - S1 = S0#state{time=Ti#ti{tick=Next}, wins=Wins, samples=Samples, async=undefined}, + S1 = S0#state{time=Ti#ti{tick=Next}, wins=Wins, samples=Samples, max=Max, async=undefined}, if Active -> - update_alloc(S0, Info), + update_alloc(S0, Info, Max), State = precalc(S1), {noreply, State}; true -> @@ -185,27 +194,40 @@ code_change(_, _, State) -> %%%%%%%%%% restart_fetcher(Node, #state{panel=Panel, wins=Wins0, time=Ti} = State) -> - SysInfo = observer_wx:try_rpc(Node, observer_backend, sys_info, []), - Info = alloc_info(SysInfo), - {Wins, Samples} = add_data(Info, {0, queue:new()}, Wins0, Ti, true), - erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, 0}), - wxWindow:refresh(Panel), - precalc(State#state{active=true, appmon=Node, time=Ti#ti{tick=0}, - wins=Wins, samples=Samples}). + case rpc:call(Node, observer_backend, sys_info, []) of + {badrpc, _} -> State; + SysInfo -> + Info = alloc_info(SysInfo), + Max = lists:foldl(fun calc_max/2, #{}, Info), + {Wins, Samples} = add_data(Info, {0, queue:new()}, Wins0, Ti, true), + erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, 0}), + wxWindow:refresh(Panel), + precalc(State#state{active=true, appmon=Node, time=Ti#ti{tick=0}, + wins=Wins, samples=Samples, max=Max}) + end. precalc(#state{samples=Data0, paint=Paint, time=Ti, wins=Wins0}=State) -> Wins = [precalc(Ti, Data0, Paint, Win) || Win <- Wins0], State#state{wins=Wins}. +calc_max({Name, _, Cs}, Max0) -> + case maps:get(Name, Max0, 0) of + Value when Value < Cs -> + Max0#{Name=>Cs}; + _V -> + Max0 + end. -update_alloc(#state{mem=Grid}, Fields) -> +update_alloc(#state{mem=Grid}, Fields, Max) -> wxWindow:freeze(Grid), - Max = wxListCtrl:getItemCount(Grid), + Last = wxListCtrl:getItemCount(Grid), Update = fun({Name, BS, CS}, Row) -> - (Row >= Max) andalso wxListCtrl:insertItem(Grid, Row, ""), + (Row >= Last) andalso wxListCtrl:insertItem(Grid, Row, ""), + MaxV = maps:get(Name, Max, CS), wxListCtrl:setItem(Grid, Row, 0, observer_lib:to_str(Name)), wxListCtrl:setItem(Grid, Row, 1, observer_lib:to_str(BS div 1024)), wxListCtrl:setItem(Grid, Row, 2, observer_lib:to_str(CS div 1024)), + wxListCtrl:setItem(Grid, Row, 3, observer_lib:to_str(MaxV div 1024)), Row + 1 end, wx:foldl(Update, 0, Fields), @@ -269,7 +291,9 @@ create_mem_info(Parent) -> end, ListItems = [{"Allocator Type", ?wxLIST_FORMAT_LEFT, 200}, {"Block size (kB)", ?wxLIST_FORMAT_RIGHT, 150}, - {"Carrier size (kB)",?wxLIST_FORMAT_RIGHT, 150}], + {"Carrier size (kB)",?wxLIST_FORMAT_RIGHT, 150}, + {"Max Carrier size (kB)",?wxLIST_FORMAT_RIGHT, 150} + ], lists:foldl(AddListEntry, 0, ListItems), wxListItem:destroy(Li), diff --git a/lib/observer/src/observer_app_wx.erl b/lib/observer/src/observer_app_wx.erl index 936b2783e2..bc4f1fe117 100644 --- a/lib/observer/src/observer_app_wx.erl +++ b/lib/observer/src/observer_app_wx.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2016. All Rights Reserved. +%% Copyright Ericsson AB 2011-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ %% %CopyrightEnd% -module(observer_app_wx). --export([start_link/2]). +-export([start_link/3]). %% wx_object callbacks -export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, @@ -73,10 +73,10 @@ -define(wxGC, wxGraphicsContext). -start_link(Notebook, Parent) -> - wx_object:start_link(?MODULE, [Notebook, Parent], []). +start_link(Notebook, Parent, Config) -> + wx_object:start_link(?MODULE, [Notebook, Parent, Config], []). -init([Notebook, Parent]) -> +init([Notebook, Parent, _Config]) -> Panel = wxPanel:new(Notebook, [{size, wxWindow:getClientSize(Notebook)}, {winid, 1} ]), @@ -191,8 +191,8 @@ handle_event(#wx{event=#wxMouse{type=Type, x=X0, y=Y0}}, end; handle_event(#wx{event=#wxCommand{type=command_menu_selected}}, - State = #state{sel=undefined}) -> - observer_lib:display_info_dialog("Select process first"), + State = #state{panel=Panel,sel=undefined}) -> + observer_lib:display_info_dialog(Panel,"Select process first"), {noreply, State}; handle_event(#wx{id=?ID_PROC_INFO, event=#wxCommand{type=command_menu_selected}}, @@ -205,7 +205,7 @@ handle_event(#wx{id=?ID_PROC_MSG, event=#wxCommand{type=command_menu_selected}}, case observer_lib:user_term(Panel, "Enter message", "") of cancel -> ok; {ok, Term} -> Pid ! Term; - {error, Error} -> observer_lib:display_info_dialog(Error) + {error, Error} -> observer_lib:display_info_dialog(Panel,Error) end, {noreply, State}; @@ -214,7 +214,7 @@ handle_event(#wx{id=?ID_PROC_KILL, event=#wxCommand{type=command_menu_selected}} case observer_lib:user_term(Panel, "Enter Exit Reason", "kill") of cancel -> ok; {ok, Term} -> exit(Pid, Term); - {error, Error} -> observer_lib:display_info_dialog(Error) + {error, Error} -> observer_lib:display_info_dialog(Panel,Error) end, {noreply, State}; @@ -258,6 +258,8 @@ handle_sync_event(#wx{event = #wxPaint{}},_, destroy_gc(GC), ok. %%%%%%%%%% +handle_call(get_config, _, State) -> + {reply, #{}, State}; handle_call(Event, From, _State) -> error({unhandled_call, Event, From}). diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl index 7c1337025f..c7ee294719 100644 --- a/lib/observer/src/observer_lib.erl +++ b/lib/observer/src/observer_lib.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2016. All Rights Reserved. +%% Copyright Ericsson AB 2011-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -20,12 +20,12 @@ -module(observer_lib). -export([get_wx_parent/1, - display_info_dialog/1, display_yes_no_dialog/1, + display_info_dialog/2, display_yes_no_dialog/1, display_progress_dialog/2, destroy_progress_dialog/0, wait_for_progress/0, report_progress/1, user_term/3, user_term_multiline/3, - interval_dialog/4, start_timer/1, stop_timer/1, - display_info/2, fill_info/2, update_info/2, to_str/1, + interval_dialog/4, start_timer/1, start_timer/2, stop_timer/1, timer_config/1, + display_info/2, display_info/3, fill_info/2, update_info/2, to_str/1, create_menus/3, create_menu_item/3, create_attrs/0, set_listctrl_col_size/2, @@ -90,6 +90,12 @@ stop_timer(Timer = {true, _}) -> Timer; stop_timer(Timer = {_, Intv}) -> setup_timer(false, Timer), {true, Intv}. + +start_timer(#{interval:=Intv}, _Def) -> + setup_timer(true, {false, Intv}); +start_timer(_, Def) -> + setup_timer(true, {false, Def}). + start_timer(Intv) when is_integer(Intv) -> setup_timer(true, {true, Intv}); start_timer(Timer) -> @@ -105,10 +111,15 @@ setup_timer(Bool, {Timer, Old}) -> timer:cancel(Timer), setup_timer(Bool, {false, Old}). -display_info_dialog(Str) -> - display_info_dialog("",Str). -display_info_dialog(Title,Str) -> - Dlg = wxMessageDialog:new(wx:null(), Str, [{caption,Title}]), +timer_config({_, Interval}) -> + #{interval=>Interval}; +timer_config(#{}=Config) -> + Config. + +display_info_dialog(Parent,Str) -> + display_info_dialog(Parent,"",Str). +display_info_dialog(Parent,Title,Str) -> + Dlg = wxMessageDialog:new(Parent, Str, [{caption,Title}]), wxMessageDialog:showModal(Dlg), wxMessageDialog:destroy(Dlg), ok. @@ -124,6 +135,11 @@ display_info(Frame, Info) -> Panel = wxPanel:new(Frame), wxWindow:setBackgroundStyle(Panel, ?wxBG_STYLE_SYSTEM), Sizer = wxBoxSizer:new(?wxVERTICAL), + InfoFs = display_info(Panel, Sizer, Info), + wxWindow:setSizerAndFit(Panel, Sizer), + {Panel, Sizer, InfoFs}. + +display_info(Panel, Sizer, Info) -> wxSizer:addSpacer(Sizer, 5), Add = fun(BoxInfo) -> case create_box(Panel, BoxInfo) of @@ -136,9 +152,7 @@ display_info(Frame, Info) -> [] end end, - InfoFs = [Add(I) || I <- Info], - wxWindow:setSizerAndFit(Panel, Sizer), - {Panel, Sizer, InfoFs}. + [Add(I) || I <- Info]. fill_info([{dynamic, Key}|Rest], Data) when is_atom(Key); is_function(Key) -> @@ -159,13 +173,13 @@ fill_info([{Str,Attrib,Key}|Rest], Data) when is_atom(Key); is_function(Key) -> Value -> [{Str,Attrib,Value} | fill_info(Rest, Data)] end; fill_info([{Str, {Format, Key}}|Rest], Data) - when is_atom(Key); is_function(Key), is_atom(Format) -> + when is_atom(Key); is_function(Key) -> case get_value(Key, Data) of undefined -> [undefined | fill_info(Rest, Data)]; Value -> [{Str, {Format, Value}} | fill_info(Rest, Data)] end; fill_info([{Str, Attrib, {Format, Key}}|Rest], Data) - when is_atom(Key); is_function(Key), is_atom(Format) -> + when is_atom(Key); is_function(Key) -> case get_value(Key, Data) of undefined -> [undefined | fill_info(Rest, Data)]; Value -> [{Str, Attrib, {Format, Value}} | fill_info(Rest, Data)] @@ -238,6 +252,8 @@ to_str({bytes, B}) -> KB > 0 -> integer_to_list(KB) ++ " kB"; true -> integer_to_list(B) ++ " B" end; +to_str({{words,WSz}, Sz}) -> + to_str({bytes, WSz*Sz}); to_str({time_ms, MS}) -> S = MS div 1000, Min = S div 60, @@ -254,6 +270,11 @@ to_str({func, {F,A}}) when is_atom(F), is_integer(A) -> lists:concat([F, "/", A]); to_str({func, {F,'_'}}) when is_atom(F) -> atom_to_list(F); +to_str({inet, Addr}) -> + case inet:ntoa(Addr) of + {error,einval} -> to_str(Addr); + AddrStr -> AddrStr + end; to_str({{format,Fun},Value}) when is_function(Fun) -> Fun(Value); to_str({A, B}) when is_atom(A), is_atom(B) -> @@ -453,14 +474,16 @@ create_box(Parent, Data) -> link_entry(Panel,Value); _ -> Value = to_str(Value0), - case length(Value) > 100 of - true -> - Shown = lists:sublist(Value, 80), + case string:sub_word(lists:sublist(Value, 80),1,$\n) of + Value -> + %% Short string, no newlines - show all + wxStaticText:new(Panel, ?wxID_ANY, Value); + Shown -> + %% Long or with newlines, + %% use tooltip to show all TCtrl = wxStaticText:new(Panel, ?wxID_ANY, [Shown,"..."]), wxWindow:setToolTip(TCtrl,wxToolTip:new(Value)), - TCtrl; - false -> - wxStaticText:new(Panel, ?wxID_ANY, Value) + TCtrl end end, wxSizer:add(Line, 10, 0), % space of size 10 horisontally @@ -613,12 +636,12 @@ user_term_multiline(Parent, Title, Default) -> parse_string(Str) -> try - Tokens = case erl_scan:string(Str) of + Tokens = case erl_scan:string(Str, 1, [text]) of {ok, Ts, _} -> Ts; {error, {_SLine, SMod, SError}, _} -> throw(io_lib:format("~s", [SMod:format_error(SError)])) end, - case erl_parse:parse_term(Tokens) of + case lib:extended_parse_term(Tokens) of {error, {_PLine, PMod, PError}} -> throw(io_lib:format("~s", [PMod:format_error(PError)])); Res -> Res @@ -714,7 +737,7 @@ progress_loop(Title,PD,Caller) -> if is_list(Reason) -> Reason; true -> file:format_error(Reason) end, - display_info_dialog("Crashdump Viewer Error",FailMsg), + display_info_dialog(PD,"Crashdump Viewer Error",FailMsg), Caller ! error, unregister(?progress_handler), unlink(Caller); diff --git a/lib/observer/src/observer_perf_wx.erl b/lib/observer/src/observer_perf_wx.erl index 1010a6af4c..fcc51310c8 100644 --- a/lib/observer/src/observer_perf_wx.erl +++ b/lib/observer/src/observer_perf_wx.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2012-2016. All Rights Reserved. +%% Copyright Ericsson AB 2012-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ %% %CopyrightEnd% -module(observer_perf_wx). --export([start_link/2]). +-export([start_link/3]). %% wx_object callbacks -export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, @@ -55,12 +55,12 @@ -define(wxGC, wxGraphicsContext). --record(paint, {font, small, pen, pen2, pens, usegc = false}). +-record(paint, {font, small, pen, pen2, pens, dot_pens, usegc = false}). -start_link(Notebook, Parent) -> - wx_object:start_link(?MODULE, [Notebook, Parent], []). +start_link(Notebook, Parent, Config) -> + wx_object:start_link(?MODULE, [Notebook, Parent, Config], []). -init([Notebook, Parent]) -> +init([Notebook, Parent, Config]) -> try Panel = wxPanel:new(Notebook), Main = wxBoxSizer:new(?wxVERTICAL), @@ -81,7 +81,9 @@ init([Notebook, Parent]) -> panel =Panel, wins = Windows, paint=PaintInfo, - samples=reset_data() + samples=reset_data(), + time=#ti{fetch=maps:get(fetch, Config, ?FETCH_DATA), + secs=maps:get(secs, Config, ?DISP_SECONDS)} }, {Panel, State0} catch _:Err -> @@ -124,13 +126,17 @@ setup_graph_drawing(Panels) -> {F, SF} end, BlackPen = wxPen:new({0,0,0}, [{width, 1}]), - Pens = [wxPen:new(Col, [{width, 1}]) || Col <- tuple_to_list(colors())], + Pens = [wxPen:new(Col, [{width, 1}, {style, ?wxSOLID}]) + || Col <- tuple_to_list(colors())], + DotPens = [wxPen:new(Col, [{width, 1}, {style, ?wxDOT}]) + || Col <- tuple_to_list(colors())], #paint{usegc = UseGC, font = Font, small = SmallFont, pen = ?wxGREY_PEN, pen2 = BlackPen, - pens = list_to_tuple(Pens) + pens = list_to_tuple(Pens), + dot_pens = list_to_tuple(DotPens) }. @@ -173,6 +179,10 @@ refresh_panel(Active, #win{name=_Id, panel=Panel}=Win, Ti, #paint{usegc=UseGC}=P destroy_gc(GC). %%%%%%%%%% +handle_call(get_config, _, #state{time=Ti}=State) -> + #ti{fetch=Fetch, secs=Range} = Ti, + {reply, #{fetch=>Fetch, secs=>Range}, State}; + handle_call(Event, From, _State) -> error({unhandled_call, Event, From}). @@ -181,17 +191,17 @@ handle_cast(Event, _State) -> %%%%%%%%%% handle_info({stats, 1, _, _, _} = Stats, #state{panel=Panel, samples=Data, active=Active, wins=Wins0, - time=#ti{tick=Tick, disp=Disp0}=Ti} = State0) -> + appmon=Node, time=#ti{tick=Tick, disp=Disp0}=Ti} = State0) -> if Active -> Disp = trunc(Disp0), Next = max(Tick - Disp, 0), erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, Next}), - {Wins, Samples} = add_data(Stats, Data, Wins0, Ti, Active), + {Wins, Samples} = add_data(Stats, Data, Wins0, Ti, Active, Node), State = precalc(State0#state{time=Ti#ti{tick=Next}, wins=Wins, samples=Samples}), wxWindow:refresh(Panel), {noreply, State}; true -> - {Wins1, Samples} = add_data(Stats, Data, Wins0, Ti, Active), + {Wins1, Samples} = add_data(Stats, Data, Wins0, Ti, Active, Node), Wins = [W#win{max=undefined} || W <- Wins1], {noreply, State0#state{samples=Samples, wins=Wins, time=Ti#ti{tick=0}}} end; @@ -206,7 +216,7 @@ handle_info({refresh, Seq}, #state{panel=Panel, time=#ti{tick=Seq, disp=DispF}=T handle_info({refresh, _}, State) -> {noreply, State}; -handle_info({active, Node}, #state{parent=Parent, panel=Panel, appmon=Old, time=_Ti} = State) -> +handle_info({active, Node}, #state{parent=Parent, panel=Panel, appmon=Old} = State) -> create_menus(Parent, []), try Node = node(Old), @@ -235,39 +245,50 @@ terminate(_Event, #state{appmon=Pid}) -> code_change(_, _, State) -> State. -restart_fetcher(Node, #state{appmon=Old, panel=Panel, time=#ti{fetch=Freq}=Ti}=State) -> +restart_fetcher(Node, #state{appmon=Old, panel=Panel, time=#ti{fetch=Freq}=Ti, wins=Wins0}=State) -> catch Old ! exit, Me = self(), Pid = spawn_link(Node, observer_backend, fetch_stats, [Me, round(1000/Freq)]), wxWindow:refresh(Panel), - precalc(State#state{active=true, appmon=Pid, samples=reset_data(), time=Ti#ti{tick=0}}). + Wins = [W#win{state=undefined} || W <- Wins0], + precalc(State#state{active=true, appmon=Pid, samples=reset_data(), + wins=Wins, time=Ti#ti{tick=0}}). reset_data() -> {0, queue:new()}. -add_data(Stats, {N, Q0}, Wins, #ti{fetch=Fetch, secs=Secs}, Active) when N > (Secs*Fetch+1) -> +add_data(Stats, Q, Wins, Ti, Active) -> + add_data(Stats, Q, Wins, Ti, Active, ignore). + +add_data(Stats, {N, Q0}, Wins, #ti{fetch=Fetch, secs=Secs}, Active, Node) + when N > (Secs*Fetch+1) -> {{value, Drop}, Q} = queue:out(Q0), - add_data_1(Wins, Stats, N, {Drop,Q}, Active); -add_data(Stats, {N, Q}, Wins, _, Active) -> - add_data_1(Wins, Stats, N+1, {empty, Q}, Active). + add_data_1(Wins, Stats, N, {Drop,Q}, Active, Node); +add_data(Stats, {N, Q}, Wins, _, Active, Node) -> + add_data_1(Wins, Stats, N+1, {empty, Q}, Active, Node). -add_data_1([#win{state={_,St}}|_]=Wins0, Last, N, {Drop, Q}, Active) +add_data_1([#win{state={_,St}}|_]=Wins0, Last, N, {Drop, Q}, Active, Node) when St /= undefined -> - {Wins, Stat} = - lists:mapfoldl(fun(Win0, Entry) -> - {Win1,Stat} = add_data_2(Win0, Last, Entry), - case Active of - true -> - Win = add_data_3(Win1, N, Drop, Stat, Q), - {Win, Stat}; - false -> - {Win1, Stat} - end - end, #{}, Wins0), - {Wins, {N,queue:in(Stat#{}, Q)}}; -add_data_1(Wins, Stats, 1, {_, Q}, _) -> - {[Win#win{state=init_data(Id, Stats), - info = info(Id, Stats)} + try + {Wins, Stat} = + lists:mapfoldl(fun(Win0, Entry) -> + {Win1,Stat} = add_data_2(Win0, Last, Entry), + case Active of + true -> + Win = add_data_3(Win1, N, Drop, Stat, Q), + {Win, Stat}; + false -> + {Win1, Stat} + end + end, #{}, Wins0), + {Wins, {N,queue:in(Stat#{}, Q)}} + catch no_scheduler_change -> + {[Win#win{state=init_data(Id, Last), info=info(Id, Last, Node)} + || #win{name=Id}=Win <- Wins0], {0,queue:new()}} + end; + +add_data_1(Wins, Stats, 1, {_, Q}, _, Node) -> + {[Win#win{state=init_data(Id, Stats), info=info(Id, Stats, Node)} || #win{name=Id}=Win <- Wins], {0,Q}}. add_data_2(#win{name=Id, state=S0}=Win, Stats, Map) -> @@ -373,16 +394,24 @@ lmax(MState, Values, State) -> init_data(runq, {stats, _, T0, _, _}) -> {mk_max(),lists:sort(T0)}; init_data(io, {stats, _, _, {{_,In0}, {_,Out0}}, _}) -> {mk_max(), {In0,Out0}}; -init_data(memory, _) -> {mk_max(), info(memory, undefined)}; -init_data(alloc, _) -> {mk_max(), ok}; -init_data(utilz, _) -> {mk_max(), ok}. - -info(runq, {stats, _, T0, _, _}) -> lists:seq(1, length(T0)); -info(memory, _) -> [total, processes, atom, binary, code, ets]; -info(io, _) -> [input, output]; -info(alloc, First) -> [Type || {Type, _, _} <- First]; -info(utilz, First) -> [Type || {Type, _, _} <- First]; -info(_, []) -> []. +init_data(memory, _) -> {mk_max(), info(memory, undefined, undefined)}; +init_data(alloc, _) -> {mk_max(), unused}; +init_data(utilz, _) -> {mk_max(), unused}. + +info(runq, {stats, _, T0, _, _}, Node) -> + Dirty = get_dirty_cpu(Node), + {lists:seq(1, length(T0)-Dirty), Dirty}; +info(memory, _, _) -> [total, processes, atom, binary, code, ets]; +info(io, _, _) -> [input, output]; +info(alloc, First, _) -> [Type || {Type, _, _} <- First]; +info(utilz, First, _) -> [Type || {Type, _, _} <- First]; +info(_, [], _) -> []. + +get_dirty_cpu(Node) -> + case rpc:call(node(Node), erlang, system_info, [dirty_cpu_schedulers]) of + {badrpc,_R} -> 0; + N -> N + end. collect_data(runq, {stats, _, T0, _, _}, {Max,S0}) -> S1 = lists:sort(T0), @@ -401,15 +430,16 @@ collect_data(memory, {stats, _, _, _, MemInfo}, {Max, MemTypes}) -> collect_data(alloc, MemInfo, Max) -> Vs = [Carrier || {_Type,_Block,Carrier} <- MemInfo], Sample = list_to_tuple(Vs), - {Sample, lmax(Max, Vs, Sample)}; + {Sample, {lmax(Max, Vs, Sample),unused}}; collect_data(utilz, MemInfo, Max) -> Vs = [round(100*Block/Carrier) || {_Type,Block,Carrier} <- MemInfo], Sample = list_to_tuple(Vs), - {Sample, lmax(Max,Vs,Sample)}. + {Sample, {lmax(Max,Vs,Sample),unused}}. calc_delta([{Id, WN, TN}|Ss], [{Id, WP, TP}|Ps]) -> [100*(WN-WP) div (TN-TP)|calc_delta(Ss, Ps)]; -calc_delta([], []) -> []. +calc_delta([], []) -> []; +calc_delta(_, _) -> throw(no_scheduler_change). precalc(#state{samples=Data0, paint=Paint, time=Ti, wins=Wins0}=State) -> Wins = [precalc(Ti, Data0, Paint, Win) || Win <- Wins0], @@ -461,9 +491,10 @@ window_geom({W,H}, {_, Max, _Unit, MaxUnit}, %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -draw_win(DC, #win{no_samples=Samples, geom=#{scale:={WS,HS}}, graphs=Graphs, max={_,Max,_,_}}=Win, +draw_win(DC, #win{name=Name, no_samples=Samples, geom=#{scale:={WS,HS}}, + graphs=Graphs, max={_,Max,_,_}, info=Info}=Win, #ti{tick=Tick, fetch=FetchFreq, secs=Secs, disp=DispFreq}=Ti, - Paint=#paint{pens=Pens}) when Samples >= 2, Graphs =/= [] -> + Paint=#paint{pens=Pens, dot_pens=Dots}) when Samples >= 2, Graphs =/= [] -> %% Draw graphs {X0,Y0,DrawBs} = draw_borders(DC, Ti, Win, Paint), Offset = Tick / DispFreq, @@ -473,14 +504,23 @@ draw_win(DC, #win{no_samples=Samples, geom=#{scale:={WS,HS}}, graphs=Graphs, max end, Start = X0 + (max(Secs*FetchFreq+Full-Samples, 0) - Offset)*WS, Last = Secs*FetchFreq*WS+X0, + Dirty = case {Name, Info} of + {runq, {_, DCpu}} -> DCpu; + _ -> 0 + end, + NoGraphs = length(Graphs), + NoCpu = NoGraphs - Dirty, Draw = fun(Lines0, N) -> - setPen(DC, element(1+ ((N-1) rem tuple_size(Pens)), Pens)), + case Dirty > 0 andalso N > NoCpu of + true -> setPen(DC, element(1+ ((N-NoCpu-1) rem tuple_size(Dots)), Dots)); + false -> setPen(DC, element(1+ ((N-1) rem tuple_size(Pens)), Pens)) + end, Order = lists:reverse(Lines0), [{_,Y}|Lines] = translate(Order, {Start, Y0}, 0, WS, {X0,Max*HS,Last}, []), strokeLines(DC, [{Last,Y}|Lines]), N-1 end, - lists:foldl(Draw, length(Graphs), Graphs), + lists:foldl(Draw, NoGraphs, Graphs), DrawBs(), ok; @@ -645,11 +685,17 @@ draw_borders(DC, #ti{secs=Secs, fetch=FetchFreq}, case Type of runq -> + {TextInfo, DirtyCpus} = Info, drawText(DC, "Scheduler Utilization (%) ", TopTextX, ?BH), TN0 = Text(TopTextX, BottomTextY, "Scheduler: ", 0), - lists:foldl(fun(Id, Pos0) -> - Text(Pos0, BottomTextY, integer_to_list(Id), Id) - end, TN0, Info); + Id = fun(Id, Pos0) -> + Text(Pos0, BottomTextY, integer_to_list(Id), Id) + end, + TN1 = lists:foldl(Id, TN0, TextInfo), + TN2 = Text(TN1, BottomTextY, "Dirty cpu: ", 0), + TN3 = lists:foldl(Id, TN2, lists:seq(1, DirtyCpus)), + _ = Text(TN3, BottomTextY, "(dotted)", 0), + ok; memory -> drawText(DC, "Memory Usage " ++ Unit, TopTextX,?BH), lists:foldl(fun(MType, {PenId, Pos0}) -> @@ -738,10 +784,10 @@ calc_max1(Max) -> end. colors() -> - {{240, 100, 100}, {100, 240, 100}, {100, 100, 240}, - {220, 220, 80}, {100, 240, 240}, {240, 100, 240}, - {100, 25, 25}, {25, 100, 25}, {25, 25, 100}, - {120, 120, 0}, {25, 100, 100}, {100, 50, 100} + {{240, 100, 100}, {0, 128, 0}, {25, 45, 170}, {255, 165, 0}, + {220, 220, 40}, {100, 240, 240},{240, 100, 240}, {160, 40, 40}, + {100, 100, 240}, {140, 140, 0}, {25, 200, 100}, {120, 25, 240}, + {255, 140, 163}, {25, 120, 120}, {120, 25, 120}, {110, 90, 60} }. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/observer/src/observer_port_wx.erl b/lib/observer/src/observer_port_wx.erl index 3b788642cc..8339267659 100644 --- a/lib/observer/src/observer_port_wx.erl +++ b/lib/observer/src/observer_port_wx.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2014. All Rights Reserved. +%% Copyright Ericsson AB 2011-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ %% %CopyrightEnd% -module(observer_port_wx). --export([start_link/2]). +-export([start_link/3]). %% wx_object callbacks -export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, @@ -52,7 +52,13 @@ slot, id_str, links, - monitors}). + monitors, + monitored_by, + parallelism, + locking, + queue_size, + memory, + inet}). -record(opt, {sort_key=2, sort_incr=true @@ -63,7 +69,7 @@ parent, grid, panel, - node=node(), + node={node(),true}, opt=#opt{}, right_clicked_port, ports, @@ -71,10 +77,10 @@ open_wins=[] }). -start_link(Notebook, Parent) -> - wx_object:start_link(?MODULE, [Notebook, Parent], []). +start_link(Notebook, Parent, Config) -> + wx_object:start_link(?MODULE, [Notebook, Parent, Config], []). -init([Notebook, Parent]) -> +init([Notebook, Parent, Config]) -> Panel = wxPanel:new(Notebook), Sizer = wxBoxSizer:new(?wxVERTICAL), Style = ?wxLC_REPORT bor ?wxLC_HRULES, @@ -104,12 +110,12 @@ init([Notebook, Parent]) -> wxListCtrl:connect(Grid, size, [{skip, true}]), wxWindow:setFocus(Grid), - {Panel, #state{grid=Grid, parent=Parent, panel=Panel, timer={false, 10}}}. + {Panel, #state{grid=Grid, parent=Parent, panel=Panel, timer=Config}}. handle_event(#wx{id=?ID_REFRESH}, State = #state{node=Node, grid=Grid, opt=Opt}) -> Ports0 = get_ports(Node), - Ports = update_grid(Grid, Opt, Ports0), + Ports = update_grid(Grid, sel(State), Opt, Ports0), {noreply, State#state{ports=Ports}}; handle_event(#wx{obj=Obj, event=#wxClose{}}, #state{open_wins=Opened} = State) -> @@ -128,7 +134,7 @@ handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}}, NewKey -> Opt0#opt{sort_key=NewKey} end, Ports0 = get_ports(Node), - Ports = update_grid(Grid, Opt, Ports0), + Ports = update_grid(Grid, sel(State), Opt, Ports0), wxWindow:setFocus(Grid), {noreply, State#state{opt=Opt, ports=Ports}}; @@ -254,6 +260,9 @@ handle_event(Event, _State) -> handle_sync_event(_Event, _Obj, _State) -> ok. +handle_call(get_config, _, #state{timer=Timer}=State) -> + {reply, observer_lib:timer_config(Timer), State}; + handle_call(Event, From, _State) -> error({unhandled_call, Event, From}). @@ -261,10 +270,39 @@ handle_cast(Event, _State) -> error({unhandled_cast, Event}). handle_info({portinfo_open, PortIdStr}, - State = #state{grid=Grid, ports=Ports, open_wins=Opened}) -> - Port = lists:keyfind(PortIdStr,#port.id_str,Ports), - NewOpened = display_port_info(Grid, Port, Opened), - {noreply, State#state{open_wins = NewOpened}}; + State = #state{node={ActiveNodeName,ActiveAvailable}, grid=Grid, + opt=Opt, open_wins=Opened}) -> + NodeName = node(list_to_port(PortIdStr)), + Available = + case NodeName of + ActiveNodeName -> + ActiveAvailable; + _ -> + portinfo_available(NodeName) + end, + if Available -> + Ports0 = get_ports({NodeName,Available}), + Port = lists:keyfind(PortIdStr, #port.id_str, Ports0), + NewOpened = + case Port of + false -> + self() ! {error,"No such port: " ++ PortIdStr}, + Opened; + _ -> + display_port_info(Grid, Port, Opened) + end, + Ports = + case NodeName of + ActiveNodeName -> + update_grid(Grid, sel(State), Opt, Ports0); + _ -> + State#state.ports + end, + {noreply, State#state{ports=Ports, open_wins=NewOpened}}; + true -> + popup_unavailable_info(NodeName), + {noreply, State} + end; handle_info(refresh_interval, State = #state{node=Node, grid=Grid, opt=Opt, ports=OldPorts}) -> @@ -273,25 +311,35 @@ handle_info(refresh_interval, State = #state{node=Node, grid=Grid, opt=Opt, %% no change {noreply, State}; Ports0 -> - Ports = update_grid(Grid, Opt, Ports0), + Ports = update_grid(Grid, sel(State), Opt, Ports0), {noreply, State#state{ports=Ports}} end; -handle_info({active, Node}, State = #state{parent=Parent, grid=Grid, opt=Opt, - timer=Timer0}) -> - Ports0 = get_ports(Node), - Ports = update_grid(Grid, Opt, Ports0), +handle_info({active, NodeName}, State = #state{parent=Parent, grid=Grid, opt=Opt, + timer=Timer0}) -> + Available = portinfo_available(NodeName), + Available orelse popup_unavailable_info(NodeName), + Ports0 = get_ports({NodeName,Available}), + Ports = update_grid(Grid, sel(State), Opt, Ports0), wxWindow:setFocus(Grid), create_menus(Parent), - Timer = observer_lib:start_timer(Timer0), - {noreply, State#state{node=Node, ports=Ports, timer=Timer}}; + Timer = observer_lib:start_timer(Timer0, 10), + {noreply, State#state{node={NodeName,Available}, ports=Ports, timer=Timer}}; handle_info(not_active, State = #state{timer = Timer0}) -> Timer = observer_lib:stop_timer(Timer0), {noreply, State#state{timer=Timer}}; -handle_info({error, Error}, State) -> - handle_error(Error), +handle_info({info, {port_info_not_available,NodeName}}, + State = #state{panel=Panel}) -> + Str = io_lib:format("Can not fetch port info from ~p.~n" + "Too old OTP version.",[NodeName]), + observer_lib:display_info_dialog(Panel, Str), + {noreply, State}; + +handle_info({error, Error}, #state{panel=Panel} = State) -> + Str = io_lib:format("ERROR: ~s~n",[Error]), + observer_lib:display_info_dialog(Panel, Str), {noreply, State}; handle_info(_Event, State) -> @@ -322,16 +370,18 @@ create_menus(Parent) -> ], observer_wx:create_menus(Parent, MenuEntries). -get_ports(Node) -> - case get_ports2(Node) of +get_ports({_NodeName,false}) -> + []; +get_ports({NodeName,true}) -> + case get_ports2(NodeName) of Error = {error, _} -> self() ! Error, []; Res -> Res end. -get_ports2(Node) -> - case rpc:call(Node, observer_backend, get_port_list, []) of +get_ports2(NodeName) -> + case rpc:call(NodeName, observer_backend, get_port_list, []) of {badrpc, Error} -> {error, Error}; Error = {error, _} -> @@ -358,7 +408,13 @@ list_to_portrec(PL) -> links = proplists:get_value(links, PL, []), name = proplists:get_value(registered_name, PL, []), monitors = proplists:get_value(monitors, PL, []), - controls = proplists:get_value(name, PL)}. + monitored_by = proplists:get_value(monitored_by, PL, []), + controls = proplists:get_value(name, PL), + parallelism = proplists:get_value(parallelism, PL), + locking = proplists:get_value(locking, PL), + queue_size = proplists:get_value(queue_size, PL, 0), + memory = proplists:get_value(memory, PL, 0), + inet = proplists:get_value(inet, PL, [])}. portrec_to_list(#port{id = Id, slot = Slot, @@ -366,14 +422,26 @@ portrec_to_list(#port{id = Id, links = Links, name = Name, monitors = Monitors, - controls = Controls}) -> + monitored_by = MonitoredBy, + controls = Controls, + parallelism = Parallelism, + locking = Locking, + queue_size = QueueSize, + memory = Memory, + inet = Inet}) -> [{id,Id}, {slot,Slot}, {connected,Connected}, {links,Links}, {name,Name}, {monitors,Monitors}, - {controls,Controls}]. + {monitored_by,MonitoredBy}, + {controls,Controls}, + {parallelism,Parallelism}, + {locking,Locking}, + {queue_size,QueueSize}, + {memory,Memory} | + Inet]. display_port_info(Parent, PortRec, Opened) -> PortIdStr = PortRec#port.id_str, @@ -391,43 +459,95 @@ do_display_port_info(Parent0, PortRec) -> Title = "Port Info: " ++ PortRec#port.id_str, Frame = wxMiniFrame:new(Parent, ?wxID_ANY, Title, [{style, ?wxSYSTEM_MENU bor ?wxCAPTION - bor ?wxCLOSE_BOX bor ?wxRESIZE_BORDER}]), - + bor ?wxCLOSE_BOX bor ?wxRESIZE_BORDER}, + {size,{600,400}}]), + ScrolledWin = wxScrolledWindow:new(Frame,[{style,?wxHSCROLL bor ?wxVSCROLL}]), + wxScrolledWindow:enableScrolling(ScrolledWin,true,true), + wxScrolledWindow:setScrollbars(ScrolledWin,20,20,0,0), + Sizer = wxBoxSizer:new(?wxVERTICAL), + wxWindow:setSizer(ScrolledWin,Sizer), Port = portrec_to_list(PortRec), Fields0 = port_info_fields(Port), - {_FPanel, _Sizer, _UpFields} = observer_lib:display_info(Frame, Fields0), + _UpFields = observer_lib:display_info(ScrolledWin, Sizer, Fields0), wxFrame:center(Frame), wxFrame:connect(Frame, close_window, [{skip, true}]), wxFrame:show(Frame), Frame. -port_info_fields(Port) -> + +port_info_fields(Port0) -> + {InetStruct,Port} = inet_extra_fields(Port0), Struct = [{"Overview", - [{"Name", name}, + [{"Registered Name", name}, {"Connected", {click,connected}}, {"Slot", slot}, - {"Controls", controls}]}, + {"Controls", controls}, + {"Parallelism", parallelism}, + {"Locking", locking}, + {"Queue Size", {bytes,queue_size}}, + {"Memory", {bytes,memory}}]}, {scroll_boxes, [{"Links",1,{click,links}}, - {"Monitors",1,{click,filter_monitor_info()}}]}], + {"Monitors",1,{click,filter_monitor_info()}}, + {"Monitored by",1,{click,monitored_by}}]} | InetStruct], observer_lib:fill_info(Struct, Port). +inet_extra_fields(Port) -> + Statistics = proplists:get_value(statistics,Port,[]), + Options = proplists:get_value(options,Port,[]), + Struct = + case proplists:get_value(controls,Port) of + Inet when Inet=="tcp_inet"; Inet=="udp_inet"; Inet=="sctp_inet" -> + [{"Inet", + [{"Local Address", {inet,local_address}}, + {"Local Port Number", local_port}, + {"Remote Address", {inet,remote_address}}, + {"Remote Port Number", remote_port}]}, + {"Statistics", + [stat_name_and_unit(Key) || {Key,_} <- Statistics]}, + {"Options", + [{atom_to_list(Key),Key} || {Key,_} <- Options]}]; + _ -> + [] + end, + Port1 = lists:keydelete(statistics,1,Port), + Port2 = lists:keydelete(options,1,Port1), + {Struct,Port2 ++ Statistics ++ Options}. + +stat_name_and_unit(recv_avg) -> + {"Average package size received", {bytes,recv_avg}}; +stat_name_and_unit(recv_cnt) -> + {"Number of packets received", recv_cnt}; +stat_name_and_unit(recv_dvi) -> + {"Average packet size deviation received", {bytes,recv_dvi}}; +stat_name_and_unit(recv_max) -> + {"Largest packet received", {bytes,recv_max}}; +stat_name_and_unit(recv_oct) -> + {"Total received", {bytes,recv_oct}}; +stat_name_and_unit(send_avg) -> + {"Average packet size sent", {bytes, send_avg}}; +stat_name_and_unit(send_cnt) -> + {"Number of packets sent", send_cnt}; +stat_name_and_unit(send_max) -> + {"Largest packet sent", {bytes, send_max}}; +stat_name_and_unit(send_oct) -> + {"Total sent", {bytes, send_oct}}; +stat_name_and_unit(send_pend) -> + {"Data waiting to be sent from driver", {bytes,send_pend}}; +stat_name_and_unit(Key) -> + {atom_to_list(Key), Key}. + filter_monitor_info() -> fun(Data) -> Ms = proplists:get_value(monitors, Data), [Pid || {process, Pid} <- Ms] end. - -handle_error(Foo) -> - Str = io_lib:format("ERROR: ~s~n",[Foo]), - observer_lib:display_info_dialog(Str). - -update_grid(Grid, Opt, Ports) -> - wx:batch(fun() -> update_grid2(Grid, Opt, Ports) end). -update_grid2(Grid, #opt{sort_key=Sort,sort_incr=Dir}, Ports) -> +update_grid(Grid, Sel, Opt, Ports) -> + wx:batch(fun() -> update_grid2(Grid, Sel, Opt, Ports) end). +update_grid2(Grid, Sel, #opt{sort_key=Sort,sort_incr=Dir}, Ports) -> wxListCtrl:deleteAllItems(Grid), Update = fun(#port{id = Id, @@ -447,6 +567,12 @@ update_grid2(Grid, #opt{sort_key=Sort,sort_incr=Dir}, Ports) -> observer_lib:to_str(Val)) end, [{0,Id},{1,Connected},{2,Name},{3,Ctrl},{4,Slot}]), + case lists:member(Id, Sel) of + true -> + wxListCtrl:setItemState(Grid, Row, 16#FFFF, ?wxLIST_STATE_SELECTED); + false -> + wxListCtrl:setItemState(Grid, Row, 0, ?wxLIST_STATE_SELECTED) + end, Row + 1 end, PortInfo = case Dir of @@ -456,6 +582,8 @@ update_grid2(Grid, #opt{sort_key=Sort,sort_incr=Dir}, Ports) -> lists:foldl(Update, 0, PortInfo), PortInfo. +sel(#state{grid=Grid, ports=Ports}) -> + [Id || #port{id=Id} <- get_selected_items(Grid, Ports)]. get_selected_items(Grid, Data) -> get_indecies(get_selected_items(Grid, -1, []), Data). @@ -477,3 +605,15 @@ get_indecies(Rest = [_|_], I, [_|T]) -> get_indecies(Rest, I+1, T); get_indecies(_, _, _) -> []. + +portinfo_available(NodeName) -> + _ = rpc:call(NodeName, code, ensure_loaded, [observer_backend]), + case rpc:call(NodeName, erlang, function_exported, + [observer_backend, get_port_list, 0]) of + true -> true; + false -> false + end. + +popup_unavailable_info(NodeName) -> + self() ! {info, {port_info_not_available, NodeName}}, + ok. diff --git a/lib/observer/src/observer_pro_wx.erl b/lib/observer/src/observer_pro_wx.erl index ee6829b847..3083297f31 100644 --- a/lib/observer/src/observer_pro_wx.erl +++ b/lib/observer/src/observer_pro_wx.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2016. All Rights Reserved. +%% Copyright Ericsson AB 2011-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ -behaviour(wx_object). --export([start_link/2]). +-export([start_link/3]). %% wx_object callbacks -export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, @@ -67,12 +67,14 @@ -record(holder, {parent, info, - etop, + next=[], sort=#sort{}, accum=[], + next_accum=[], attrs, node, - backend_pid + backend_pid, + old_backend=false }). -record(state, {parent, @@ -86,18 +88,19 @@ right_clicked_pid, holder}). -start_link(Notebook, Parent) -> - wx_object:start_link(?MODULE, [Notebook, Parent], []). +start_link(Notebook, Parent, Config) -> + wx_object:start_link(?MODULE, [Notebook, Parent, Config], []). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -init([Notebook, Parent]) -> +init([Notebook, Parent, Config]) -> Attrs = observer_lib:create_attrs(), Self = self(), - Holder = spawn_link(fun() -> init_table_holder(Self, Attrs) end), - {ProPanel, State} = setup(Notebook, Parent, Holder), + Acc = maps:get(acc, Config, false), + Holder = spawn_link(fun() -> init_table_holder(Self, Acc, Attrs) end), + {ProPanel, State} = setup(Notebook, Parent, Holder, Config), {ProPanel, State#state{holder=Holder}}. -setup(Notebook, Parent, Holder) -> +setup(Notebook, Parent, Holder, Config) -> ProPanel = wxPanel:new(Notebook, []), Grid = create_list_box(ProPanel, Holder), @@ -113,7 +116,7 @@ setup(Notebook, Parent, Holder) -> panel=ProPanel, parent_notebook=Notebook, holder=Holder, - timer={false, 10} + timer=Config }, {ProPanel, State}. @@ -225,7 +228,7 @@ handle_info({holder_updated, Count}, State0=#state{grid=Grid}) -> wxListCtrl:setItemCount(Grid, Count), Count > 0 andalso wxListCtrl:refreshItems(Grid, 0, Count-1), - + observer_wx:set_status(io_lib:format("Number of Processes: ~w", [Count])), {noreply, State}; handle_info(refresh_interval, #state{holder=Holder}=State) -> @@ -246,7 +249,7 @@ handle_info({active, Node}, #state{holder=Holder, timer=Timer, parent=Parent}=State) -> create_pro_menu(Parent, Holder), Holder ! {change_node, Node}, - {noreply, State#state{timer=observer_lib:start_timer(Timer)}}; + {noreply, State#state{timer=observer_lib:start_timer(Timer, 10)}}; handle_info(not_active, #state{timer=Timer0}=State) -> Timer = observer_lib:stop_timer(Timer0), @@ -264,11 +267,15 @@ terminate(_Reason, #state{holder=Holder}) -> code_change(_, _, State) -> {ok, State}. +handle_call(get_config, _, #state{holder=Holder, timer=Timer}=State) -> + Conf = observer_lib:timer_config(Timer), + Accum = call(Holder, {get_accum, self()}), + {reply, Conf#{acc=>Accum}, State}; + handle_call(Msg, _From, State) -> io:format("~p:~p: Unhandled call ~p~n",[?MODULE, ?LINE, Msg]), {reply, ok, State}. - handle_cast(Msg, State) -> io:format("~p:~p: Unhandled cast ~p~n", [?MODULE, ?LINE, Msg]), {noreply, State}. @@ -453,18 +460,23 @@ rm_selected(_, [], [], AccIds, AccPids) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%TABLE HOLDER%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -init_table_holder(Parent, Attrs) -> - Backend = spawn_link(node(), observer_backend,etop_collect,[self()]), +init_table_holder(Parent, Accum0, Attrs) -> + process_flag(trap_exit, true), + Backend = spawn_link(node(), observer_backend, procs_info, [self()]), + Accum = case Accum0 of + true -> true; + false -> [] + end, table_holder(#holder{parent=Parent, - etop=#etop_info{}, info=array:new(), node=node(), backend_pid=Backend, - attrs=Attrs + attrs=Attrs, + accum=Accum }). table_holder(#holder{info=Info, attrs=Attrs, - node=Node, backend_pid=Backend}=S0) -> + node=Node, backend_pid=Backend, old_backend=Old}=S0) -> receive {get_row, From, Row, Col} -> get_row(From, Row, Col, Info), @@ -472,14 +484,25 @@ table_holder(#holder{info=Info, attrs=Attrs, {get_attr, From, Row} -> get_attr(From, Row, Attrs), table_holder(S0); + {procs_info, Backend, Procs} -> + State = handle_update(Procs, S0), + table_holder(State); + {'EXIT', Backend, normal} when Old =:= false -> + S1 = update_complete(S0), + table_holder(S1#holder{backend_pid=undefined}); {Backend, EtopInfo=#etop_info{}} -> - State = handle_update(EtopInfo, S0), + State = handle_update_old(EtopInfo, S0), table_holder(State#holder{backend_pid=undefined}); refresh when is_pid(Backend)-> table_holder(S0); %% Already updating refresh -> - Pid = spawn_link(Node,observer_backend,etop_collect,[self()]), - table_holder(S0#holder{backend_pid=Pid}); + Pid = case Old of + true -> + spawn_link(Node, observer_backend, etop_collect, [self()]); + false -> + spawn_link(Node, observer_backend, procs_info, [self()]) + end, + table_holder(S0#holder{backend_pid=Pid}); {change_sort, Col} -> State = change_sort(Col, S0), table_holder(State); @@ -492,7 +515,6 @@ table_holder(#holder{info=Info, attrs=Attrs, {get_name_or_pid, From, Indices} -> get_name_or_pid(From, Indices, Info), table_holder(S0); - {get_node, From} -> From ! {self(), Node}, table_holder(S0); @@ -501,30 +523,50 @@ table_holder(#holder{info=Info, attrs=Attrs, true -> table_holder(S0); false -> - self() ! refresh, - table_holder(S0#holder{node=NewNode}) - end; + _ = rpc:call(NewNode, code, ensure_loaded, [observer_backend]), + case rpc:call(NewNode, erlang, function_exported, + [observer_backend,procs_info, 1]) of + true -> + self() ! refresh, + table_holder(S0#holder{node=NewNode, old_backend=false}); + false -> + self() ! refresh, + table_holder(S0#holder{node=NewNode, old_backend=true}); + _ -> + table_holder(S0) + end + end; {accum, Bool} -> table_holder(change_accum(Bool,S0)); {get_accum, From} -> From ! {self(), S0#holder.accum == true}, table_holder(S0); {dump, Fd} -> - EtopInfo = (S0#holder.etop)#etop_info{procinfo=array:to_list(Info)}, - etop_txt:do_update(Fd, EtopInfo, #opts{node=Node}), - file:close(Fd), - table_holder(S0); + Collector = spawn_link(Node, observer_backend, etop_collect,[self()]), + receive + {Collector, EtopInfo=#etop_info{}} -> + etop_txt:do_update(Fd, EtopInfo, #etop_info{}, #opts{node=Node}), + file:close(Fd), + table_holder(S0); + {'EXIT', Collector, _} -> + table_holder(S0) + end; stop -> ok; - What -> - io:format("Table holder got ~p~n",[What]), + {'EXIT', Backend, normal} -> + table_holder(S0); + {'EXIT', Backend, _Reason} -> + %% Node crashed will be noticed soon.. + table_holder(S0#holder{backend_pid=undefined}); + _What -> + %% io:format("~p: Table holder got ~p~n",[?MODULE, _What]), table_holder(S0) end. change_sort(Col, S0=#holder{parent=Parent, info=Data, sort=Sort0}) -> {Sort, ProcInfo}=sort(Col, Sort0, Data), Parent ! {holder_updated, array:size(Data)}, - S0#holder{info=ProcInfo, sort=Sort}. + S0#holder{info=array:from_list(ProcInfo), sort=Sort}. change_accum(true, S0) -> S0#holder{accum=true}; @@ -532,23 +574,45 @@ change_accum(false, S0=#holder{info=Info}) -> self() ! refresh, S0#holder{accum=lists:sort(array:to_list(Info))}. -handle_update(EI=#etop_info{procinfo=ProcInfo0}, - S0=#holder{parent=Parent, sort=Sort=#sort{sort_key=KeyField}}) -> - {ProcInfo1, S1} = accum(ProcInfo0, S0), +handle_update_old(#etop_info{procinfo=ProcInfo0}, + S0=#holder{parent=Parent, sort=Sort=#sort{sort_key=KeyField}}) -> + {ProcInfo1, Accum} = accum(ProcInfo0, S0), {_SO, ProcInfo} = sort(KeyField, Sort#sort{sort_key=undefined}, ProcInfo1), - Parent ! {holder_updated, array:size(ProcInfo)}, - S1#holder{info=ProcInfo, etop=EI#etop_info{procinfo=[]}}. + Info = array:from_list(ProcInfo), + Parent ! {holder_updated, array:size(Info)}, + S0#holder{info=Info, accum=Accum}. + +handle_update(ProcInfo0, S0=#holder{next=Next, sort=#sort{sort_key=KeyField}}) -> + {ProcInfo1, Accum} = accum(ProcInfo0, S0), + Sort = sort_fun(KeyField, true), + Merge = merge_fun(KeyField), + Merged = Merge(Sort(ProcInfo1), Next), + case Accum of + true -> S0#holder{next=Merged}; + _List -> S0#holder{next=Merged, next_accum=Accum} + end. -accum(ProcInfo, State=#holder{accum=true}) -> - {ProcInfo, State}; -accum(ProcInfo0, State=#holder{accum=Previous}) -> +update_complete(#holder{parent=Parent, sort=#sort{sort_incr=Incr}, + next=ProcInfo, accum=Accum, next_accum=NextAccum}=S0) -> + Info = case Incr of + true -> array:from_list(ProcInfo); + false -> array:from_list(lists:reverse(ProcInfo)) + end, + Parent ! {holder_updated, array:size(Info)}, + S0#holder{info=Info, accum= Accum =:= true orelse NextAccum, + next=[], next_accum=[]}. + +accum(ProcInfo, #holder{accum=true}) -> + {ProcInfo, true}; +accum(ProcInfo0, #holder{accum=Previous, next_accum=Next}) -> + Accum = [{Pid, Reds} || #etop_proc_info{pid=Pid, reds=Reds} <- ProcInfo0], ProcInfo = lists:sort(ProcInfo0), - {accum2(ProcInfo,Previous,[]), State#holder{accum=ProcInfo}}. + {accum2(ProcInfo,Previous,[]), lists:merge(lists:sort(Accum), Next)}. -accum2([PI=#etop_proc_info{pid=Pid, reds=Reds, runtime=RT}|PIs], - [#etop_proc_info{pid=Pid, reds=OldReds, runtime=OldRT}|Old], Acc) -> - accum2(PIs, Old, [PI#etop_proc_info{reds=Reds-OldReds, runtime=RT-OldRT}|Acc]); -accum2(PIs=[#etop_proc_info{pid=Pid}|_], [#etop_proc_info{pid=OldPid}|Old], Acc) +accum2([PI=#etop_proc_info{pid=Pid, reds=Reds}|PIs], + [{Pid, OldReds}|Old], Acc) -> + accum2(PIs, Old, [PI#etop_proc_info{reds=Reds-OldReds}|Acc]); +accum2(PIs=[#etop_proc_info{pid=Pid}|_], [{OldPid,_}|Old], Acc) when Pid > OldPid -> accum2(PIs, Old, Acc); accum2([PI|PIs], Old, Acc) -> @@ -559,14 +623,52 @@ sort(Col, Opt, Table) when not is_list(Table) -> sort(Col,Opt,array:to_list(Table)); sort(Col, Opt=#sort{sort_key=Col, sort_incr=Bool}, Table) -> - {Opt#sort{sort_incr=not Bool}, - array:from_list(lists:reverse(Table))}; -sort(Col, S=#sort{sort_incr=true}, Table) -> - {S#sort{sort_key=Col}, - array:from_list(lists:keysort(col_to_element(Col), Table))}; -sort(Col, S=#sort{sort_incr=false}, Table) -> - {S#sort{sort_key=Col}, - array:from_list(lists:reverse(lists:keysort(col_to_element(Col), Table)))}. + {Opt#sort{sort_incr=not Bool},lists:reverse(Table)}; +sort(Col, S=#sort{sort_incr=Incr}, Table) -> + Sort = sort_fun(Col, Incr), + {S#sort{sort_key=Col}, Sort(Table)}. + +sort_fun(?COL_NAME, true) -> + fun(Table) -> lists:sort(fun sort_name/2, Table) end; +sort_fun(?COL_NAME, false) -> + fun(Table) -> lists:sort(fun sort_name_rev/2, Table) end; +sort_fun(Col, true) -> + N = col_to_element(Col), + fun(Table) -> lists:keysort(N, Table) end; +sort_fun(Col, false) -> + N = col_to_element(Col), + fun(Table) -> lists:reverse(lists:keysort(N, Table)) end. + +merge_fun(?COL_NAME) -> + fun(A,B) -> lists:merge(fun sort_name/2, A, B) end; +merge_fun(Col) -> + KeyField = col_to_element(Col), + fun(A,B) -> lists:keymerge(KeyField, A, B) end. + + +sort_name(#etop_proc_info{name={_,_,_}=A}, #etop_proc_info{name={_,_,_}=B}) -> + A =< B; +sort_name(#etop_proc_info{name=A}, #etop_proc_info{name=B}) + when is_atom(A), is_atom(B) -> + A =< B; +sort_name(#etop_proc_info{name=Reg}, #etop_proc_info{name={M,_F,_A}}) + when is_atom(Reg) -> + Reg < M; +sort_name(#etop_proc_info{name={M,_,_}}, #etop_proc_info{name=Reg}) + when is_atom(Reg) -> + M < Reg. + +sort_name_rev(#etop_proc_info{name={_,_,_}=A}, #etop_proc_info{name={_,_,_}=B}) -> + A >= B; +sort_name_rev(#etop_proc_info{name=A}, #etop_proc_info{name=B}) + when is_atom(A), is_atom(B) -> + A >= B; +sort_name_rev(#etop_proc_info{name=Reg}, #etop_proc_info{name={M,_F,_A}}) + when is_atom(Reg) -> + Reg >= M; +sort_name_rev(#etop_proc_info{name={M,_,_}}, #etop_proc_info{name=Reg}) + when is_atom(Reg) -> + M >= Reg. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/observer/src/observer_procinfo.erl b/lib/observer/src/observer_procinfo.erl index 620979dcc9..10decd8b62 100644 --- a/lib/observer/src/observer_procinfo.erl +++ b/lib/observer/src/observer_procinfo.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2016. All Rights Reserved. +%% Copyright Ericsson AB 2011-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -92,7 +92,7 @@ init([Pid, ParentFrame, Parent]) -> observer_wx:return_to_localnode(ParentFrame, node(Pid)), {stop, badrpc}; process_undefined -> - observer_lib:display_info_dialog("No such alive process"), + observer_lib:display_info_dialog(ParentFrame,"No such alive process"), {stop, normal} end. @@ -198,10 +198,11 @@ code_change(_, _, State) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% init_process_page(Panel, Pid) -> - Fields0 = process_info_fields(Pid), + WSz = observer_wx:try_rpc(node(Pid), erlang, system_info,[wordsize]), + Fields0 = process_info_fields(Pid, WSz), {FPanel, _, UpFields} = observer_lib:display_info(Panel, Fields0), {FPanel, fun() -> - Fields = process_info_fields(Pid), + Fields = process_info_fields(Pid, WSz), observer_lib:update_info(UpFields, Fields) end}. @@ -359,7 +360,7 @@ create_menus(MenuBar) -> {"View", [#create_menu{id=?REFRESH, text="Refresh\tCtrl-R"}]}], observer_lib:create_menus(Menus, MenuBar, new_window). -process_info_fields(Pid) -> +process_info_fields(Pid, WSz) -> Struct = [{"Overview", [{"Initial Call", initial_call}, {"Current Function", current_function}, @@ -383,10 +384,10 @@ process_info_fields(Pid) -> {"Monitored by", {click, monitored_by}}]}, {"Memory and Garbage Collection", right, [{"Memory", {bytes, memory}}, - {"Stack and Heaps", {bytes, total_heap_size}}, - {"Heap Size", {bytes, heap_size}}, - {"Stack Size", {bytes, stack_size}}, - {"GC Min Heap Size", {bytes, get_gc_info(min_heap_size)}}, + {"Stack and Heaps", {{words,WSz}, total_heap_size}}, + {"Heap Size", {{words,WSz}, heap_size}}, + {"Stack Size", {{words,WSz}, stack_size}}, + {"GC Min Heap Size", {{words,WSz}, get_gc_info(min_heap_size)}}, {"GC FullSweep After", get_gc_info(fullsweep_after)} ]}], case observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, item_list()]) of @@ -434,7 +435,7 @@ get_gc_info(Arg) -> filter_monitor_info() -> fun(Data) -> Ms = proplists:get_value(monitors, Data), - [Pid || {process, Pid} <- Ms] + [Id || {_Type, Id} <- Ms] % Type is process or port end. stringify_bins(Data) -> diff --git a/lib/observer/src/observer_sys_wx.erl b/lib/observer/src/observer_sys_wx.erl index fa824995f7..db86c05bed 100644 --- a/lib/observer/src/observer_sys_wx.erl +++ b/lib/observer/src/observer_sys_wx.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2016. All Rights Reserved. +%% Copyright Ericsson AB 2011-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ -behaviour(wx_object). --export([start_link/2]). +-export([start_link/3]). %% wx_object callbacks -export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, handle_event/2, handle_cast/2]). @@ -41,12 +41,12 @@ fields, timer}). -start_link(Notebook, Parent) -> - wx_object:start_link(?MODULE, [Notebook, Parent], []). +start_link(Notebook, Parent, Config) -> + wx_object:start_link(?MODULE, [Notebook, Parent, Config], []). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -init([Notebook, Parent]) -> +init([Notebook, Parent, Config]) -> SysInfo = observer_backend:sys_info(), {Sys, Mem, Cpu, Stats} = info_fields(), Panel = wxPanel:new(Notebook), @@ -69,7 +69,7 @@ init([Notebook, Parent]) -> wxSizer:add(Sizer, HSizer1, [{flag, ?wxEXPAND bor BorderFlags bor ?wxBOTTOM}, {proportion, 0}, {border, 5}]), wxPanel:setSizer(Panel, Sizer), - Timer = observer_lib:start_timer(10), + Timer = observer_lib:start_timer(Config, 10), {Panel, #sys_wx_state{parent=Parent, parent_notebook=Notebook, panel=Panel, sizer=Sizer, @@ -167,6 +167,9 @@ terminate(_Reason, _State) -> code_change(_, _, State) -> {ok, State}. +handle_call(get_config, _, #sys_wx_state{timer=Timer}=State) -> + {reply, observer_lib:timer_config(Timer), State}; + handle_call(Msg, _From, State) -> io:format("~p~p: Unhandled Call ~p~n",[?MODULE, ?LINE, Msg]), {reply, ok, State}. diff --git a/lib/observer/src/observer_trace_wx.erl b/lib/observer/src/observer_trace_wx.erl index af90e2100c..b960c61ff0 100644 --- a/lib/observer/src/observer_trace_wx.erl +++ b/lib/observer/src/observer_trace_wx.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2016. All Rights Reserved. +%% Copyright Ericsson AB 2011-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ -module(observer_trace_wx). --export([start_link/2, add_processes/1, add_ports/1]). +-export([start_link/3, add_processes/1, add_ports/1]). -export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, handle_event/2, handle_cast/2]). @@ -88,8 +88,8 @@ -record(titem, {id, opts}). -start_link(Notebook, ParentPid) -> - wx_object:start_link(?MODULE, [Notebook, ParentPid], []). +start_link(Notebook, ParentPid, Config) -> + wx_object:start_link(?MODULE, [Notebook, ParentPid, Config], []). add_processes(Pids) when is_list(Pids) -> wx_object:cast(observer_wx:get_tracer(), {add_processes, Pids}). @@ -99,10 +99,10 @@ add_ports(Ports) when is_list(Ports) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -init([Notebook, ParentPid]) -> - wx:batch(fun() -> create_window(Notebook, ParentPid) end). +init([Notebook, ParentPid, Config]) -> + wx:batch(fun() -> create_window(Notebook, ParentPid, Config) end). -create_window(Notebook, ParentPid) -> +create_window(Notebook, ParentPid, Config) -> %% Create the window Panel = wxPanel:new(Notebook, [{size, wxWindow:getClientSize(Notebook)}]), Sizer = wxBoxSizer:new(?wxVERTICAL), @@ -130,11 +130,16 @@ create_window(Notebook, ParentPid) -> wxSizer:add(Sizer, Buttons, [{flag, ?wxLEFT bor ?wxRIGHT bor ?wxDOWN}, {border, 5}, {proportion,0}]), wxWindow:setSizer(Panel, Sizer), + MS = parse_ms(maps:get(match_specs, Config, []), default_matchspecs()), {Panel, #state{parent=ParentPid, panel=Panel, n_view=NodeView, proc_view=ProcessView, port_view=PortView, m_view=ModView, f_view=FuncView, toggle_button = ToggleButton, - match_specs=default_matchspecs()}}. + output=maps:get(output, Config, []), + def_proc_flags=maps:get(procflags, Config, []), + def_port_flags=maps:get(portflags, Config, []), + match_specs=MS + }}. default_matchspecs() -> [{Key,default_matchspecs(Key)} || Key <- [funcs,send,'receive']]. @@ -397,27 +402,19 @@ handle_event(#wx{id=?LOG_SAVE, userData=TCtrl}, #state{panel=Panel} = State) -> {noreply, State}; handle_event(#wx{id = ?SAVE_TRACEOPTS}, - #state{panel = Panel, - def_proc_flags = ProcFlags, - def_port_flags = PortFlags, - match_specs = MatchSpecs, - tpatterns = TracePatterns, - output = Output - } = State) -> + #state{panel = Panel} = State) -> Dialog = wxFileDialog:new(Panel, [{style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}]), case wxFileDialog:showModal(Dialog) of ?wxID_OK -> Path = wxFileDialog:getPath(Dialog), - write_file(Panel, Path, - ProcFlags, PortFlags, MatchSpecs, Output, - dict:to_list(TracePatterns) - ); + write_file(Panel, Path, get_config(State)); _ -> ok end, wxDialog:destroy(Dialog), {noreply, State}; + handle_event(#wx{id = ?LOAD_TRACEOPTS}, #state{panel = Panel} = State) -> Dialog = wxFileDialog:new(Panel, [{style, ?wxFD_FILE_MUST_EXIST}]), State2 = case wxFileDialog:showModal(Dialog) of @@ -690,6 +687,10 @@ handle_event(#wx{id=ID, event = What}, State) -> {noreply, State}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +handle_call(get_config, _, State) -> + Config0 = get_config(State), + Config = lists:keydelete(trace_p, 1, Config0), + {reply, maps:from_list(Config), State}; handle_call(Msg, From, _State) -> error({unhandled_call, Msg, From}). @@ -1101,26 +1102,38 @@ ftup(Trace, Index, Size) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -write_file(Frame, Filename, ProcFlags, PortFlags, MatchSpecs, Output, TPs) -> +get_config(#state{def_proc_flags = ProcFlags, + def_port_flags = PortFlags, + match_specs = MatchSpecs0, + tpatterns = TracePatterns, + output = Output}) -> MSToList = fun(#match_spec{name=Id, term=T, func=F}) -> [{name,Id},{term,T},{func,F}] end, - MSTermList = [{ms,Key,[MSToList(MS) || MS <- MSs]} || - {Key,MSs} <- MatchSpecs], + MatchSpecs = [{ms,Key,[MSToList(MS) || MS <- MSs]} || + {Key,MSs} <- MatchSpecs0], TPToTuple = fun(#tpattern{fa={F,A}, ms=Ms}) -> - {F,A,MSToList(Ms)} + {F,A,MSToList(Ms)} end, ModuleTermList = [{tp, Module, [TPToTuple(FTP) || FTP <- FTPs]} || - {Module,FTPs} <- TPs], - + {Module,FTPs} <- dict:to_list(TracePatterns)], + [{procflags,ProcFlags}, + {portflags,PortFlags}, + {match_specs,MatchSpecs}, + {output,Output}, + {trace_p,ModuleTermList}]. + +write_file(Frame, Filename, Config) -> Str = ["%%%\n%%% This file is generated by Observer\n", "%%%\n%%% DO NOT EDIT!\n%%%\n", - [io_lib:format("~p.~n",[MSTerm]) || MSTerm <- MSTermList], - io_lib:format("~p.~n",[{procflags,ProcFlags}]), - io_lib:format("~p.~n",[{portflags,PortFlags}]), - io_lib:format("~p.~n",[{output,Output}]), - [io_lib:format("~p.~n",[ModuleTerm]) || ModuleTerm <- ModuleTermList] + [io_lib:format("~p.~n",[MSTerm]) || + MSTerm <- proplists:get_value(match_specs, Config)], + io_lib:format("~p.~n",[lists:keyfind(procflags, 1, Config)]), + io_lib:format("~p.~n",[lists:keyfind(portflags, 1, Config)]), + io_lib:format("~p.~n",[lists:keyfind(output, 1, Config)]), + [io_lib:format("~p.~n",[ModuleTerm]) || + ModuleTerm <- proplists:get_value(trace_p, Config)] ], case file:write_file(Filename, list_to_binary(Str)) of diff --git a/lib/observer/src/observer_tv_table.erl b/lib/observer/src/observer_tv_table.erl index 75e6919642..789e060cfb 100644 --- a/lib/observer/src/observer_tv_table.erl +++ b/lib/observer/src/observer_tv_table.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2016. All Rights Reserved. +%% Copyright Ericsson AB 2011-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -233,9 +233,22 @@ handle_event(#wx{id=?ID_REFRESH},State = #state{pid=Pid}) -> {noreply, State}; handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}}, - State = #state{pid=Pid}) -> + State = #state{pid=Pid, grid=Grid, selected=OldSel}) -> + SelObj = case OldSel of + undefined -> undefined; + _ -> get_row(Pid, OldSel, term) + end, Pid ! {sort, Col+1}, - {noreply, State}; + case SelObj =/= undefined andalso search(Pid, SelObj, -1, true, term) of + false when is_integer(OldSel) -> + wxListCtrl:setItemState(Grid, OldSel, 0, ?wxLIST_STATE_SELECTED), + {noreply, State#state{selected=undefined}}; + false -> + {noreply, State#state{selected=undefined}}; + Row -> + wxListCtrl:setItemState(Grid, Row, 16#FFFF, ?wxLIST_STATE_SELECTED), + {noreply, State#state{selected=Row}} + end; handle_event(#wx{event=#wxSize{size={W,_}}}, State=#state{grid=Grid}) -> observer_lib:set_listctrl_col_size(Grid, W), @@ -607,6 +620,17 @@ keysort(Col, Table) -> end, lists:sort(Sort, Table). +search([Term, -1, true, term], S=#holder{parent=Parent, table=Table}) -> + Search = fun(Idx, [Tuple|_]) -> + Tuple =:= Term andalso throw(Idx), + Tuple + end, + try array:map(Search, Table) of + _ -> Parent ! {self(), false} + catch Index -> + Parent ! {self(), Index} + end, + S; search([Str, Row, Dir0, CaseSens], S=#holder{parent=Parent, n=N, table=Table}) -> Opt = case CaseSens of @@ -642,6 +666,8 @@ get_row(From, Row, Col, Table) -> From ! {self(), format(Object)}; [Object|_] when Col =:= all_multiline -> From ! {self(), io_lib:format("~p", [Object])}; + [Object|_] when Col =:= term -> + From ! {self(), Object}; [Object|_] when tuple_size(Object) >= Col -> From ! {self(), format(element(Col, Object))}; _ -> diff --git a/lib/observer/src/observer_tv_wx.erl b/lib/observer/src/observer_tv_wx.erl index 59f6443551..9564bdfa1c 100644 --- a/lib/observer/src/observer_tv_wx.erl +++ b/lib/observer/src/observer_tv_wx.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2016. All Rights Reserved. +%% Copyright Ericsson AB 2011-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ %% %CopyrightEnd% -module(observer_tv_wx). --export([start_link/2, display_table_info/4]). +-export([start_link/3, display_table_info/4]). %% wx_object callbacks -export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, @@ -58,10 +58,10 @@ timer }). -start_link(Notebook, Parent) -> - wx_object:start_link(?MODULE, [Notebook, Parent], []). +start_link(Notebook, Parent, Config) -> + wx_object:start_link(?MODULE, [Notebook, Parent, Config], []). -init([Notebook, Parent]) -> +init([Notebook, Parent, Config]) -> Panel = wxPanel:new(Notebook), Sizer = wxBoxSizer:new(?wxVERTICAL), Style = ?wxLC_REPORT bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES, @@ -78,11 +78,11 @@ init([Notebook, Parent]) -> Col + 1 end, ListItems = [{"Table Name", ?wxLIST_FORMAT_LEFT, 200}, - {"Table Id", ?wxLIST_FORMAT_RIGHT, 100}, {"Objects", ?wxLIST_FORMAT_RIGHT, 100}, {"Size (kB)", ?wxLIST_FORMAT_RIGHT, 100}, {"Owner Pid", ?wxLIST_FORMAT_CENTER, 150}, - {"Owner Name", ?wxLIST_FORMAT_LEFT, 200} + {"Owner Name", ?wxLIST_FORMAT_LEFT, 200}, + {"Table Id", ?wxLIST_FORMAT_LEFT, 250} ], lists:foldl(AddListEntry, 0, ListItems), wxListItem:destroy(Li), @@ -94,25 +94,31 @@ init([Notebook, Parent]) -> wxListCtrl:connect(Grid, size, [{skip, true}]), wxWindow:setFocus(Grid), - {Panel, #state{grid=Grid, parent=Parent, panel=Panel, timer={false, 10}}}. + {Panel, #state{grid=Grid, parent=Parent, panel=Panel, + timer=Config, + opt=#opt{type=maps:get(type, Config, ets), + sys_hidden=maps:get(sys_hidden, Config, true), + unread_hidden=maps:get(unread_hidden, Config, true)} + }}. handle_event(#wx{id=?ID_REFRESH}, State = #state{node=Node, grid=Grid, opt=Opt}) -> Tables = get_tables(Node, Opt), - Tabs = update_grid(Grid, Opt, Tables), - {noreply, State#state{tabs=Tabs}}; + {Tabs,Sel} = update_grid(Grid, sel(State), Opt, Tables), + Sel =/= undefined andalso wxListCtrl:ensureVisible(Grid, Sel), + {noreply, State#state{tabs=Tabs, selected=Sel}}; handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}}, State = #state{node=Node, grid=Grid, opt=Opt0=#opt{sort_key=Key, sort_incr=Bool}}) -> - Opt = case Col+2 of + Opt = case col2key(Col) of Key -> Opt0#opt{sort_incr=not Bool}; NewKey -> Opt0#opt{sort_key=NewKey} end, Tables = get_tables(Node, Opt), - Tabs = update_grid(Grid, Opt, Tables), + {Tabs,Sel} = update_grid(Grid, sel(State), Opt, Tables), wxWindow:setFocus(Grid), - {noreply, State#state{opt=Opt, tabs=Tabs}}; + {noreply, State#state{opt=Opt, tabs=Tabs, selected=Sel}}; handle_event(#wx{id=Id}, State = #state{node=Node, grid=Grid, opt=Opt0}) when Id >= ?ID_ETS, Id =< ?ID_SYSTEM_TABLES -> @@ -124,12 +130,14 @@ handle_event(#wx{id=Id}, State = #state{node=Node, grid=Grid, opt=Opt0}) end, case get_tables2(Node, Opt) of Error = {error, _} -> + Id =:= ?ID_MNESIA andalso + wxMenuBar:check(observer_wx:get_menubar(), ?ID_ETS, true), self() ! Error, {noreply, State}; Tables -> - Tabs = update_grid(Grid, Opt, Tables), + {Tabs, Sel} = update_grid(Grid, sel(State), Opt, Tables), wxWindow:setFocus(Grid), - {noreply, State#state{opt=Opt, tabs=Tabs}} + {noreply, State#state{opt=Opt, tabs=Tabs, selected=Sel}} end; handle_event(#wx{event=#wxSize{size={W,_}}}, State=#state{grid=Grid}) -> @@ -200,6 +208,12 @@ handle_event(Event, _State) -> handle_sync_event(_Event, _Obj, _State) -> ok. +handle_call(get_config, _, #state{timer=Timer, opt=Opt}=State) -> + #opt{type=Type, sys_hidden=Sys, unread_hidden=Unread} = Opt, + Conf0 = observer_lib:timer_config(Timer), + Conf = Conf0#{type=>Type, sys_hidden=>Sys, unread_hidden=>Unread}, + {reply, Conf, State}; + handle_call(Event, From, _State) -> error({unhandled_call, Event, From}). @@ -213,26 +227,38 @@ handle_info(refresh_interval, State = #state{node=Node, grid=Grid, opt=Opt, %% no change {noreply, State}; Tables -> - Tabs = update_grid(Grid, Opt, Tables), - {noreply, State#state{tabs=Tabs}} + {Tabs, Sel} = update_grid(Grid, sel(State), Opt, Tables), + Sel =/= undefined andalso wxListCtrl:ensureVisible(Grid, Sel), + {noreply, State#state{tabs=Tabs, selected=Sel}} end; -handle_info({active, Node}, State = #state{parent=Parent, grid=Grid, opt=Opt, +handle_info({active, Node}, State = #state{parent=Parent, grid=Grid, opt=Opt0, timer=Timer0}) -> - Tables = get_tables(Node, Opt), - Tabs = update_grid(Grid, Opt, Tables), + {Tables, Opt} = case Opt0#opt.type =:= mnesia andalso get_tables2(Node, Opt0) of + Ts when is_list(Ts) -> + {Ts, Opt0}; + _ -> % false or error getting mnesia tables + Opt1 = Opt0#opt{type=ets}, + {get_tables(Node, Opt1), Opt1} + end, + {Tabs,Sel} = update_grid(Grid, sel(State), Opt, Tables), wxWindow:setFocus(Grid), create_menus(Parent, Opt), - Timer = observer_lib:start_timer(Timer0), - {noreply, State#state{node=Node, tabs=Tabs, timer=Timer}}; + Timer = observer_lib:start_timer(Timer0, 10), + {noreply, State#state{node=Node, tabs=Tabs, timer=Timer, opt=Opt, selected=Sel}}; handle_info(not_active, State = #state{timer = Timer0}) -> Timer = observer_lib:stop_timer(Timer0), {noreply, State#state{timer=Timer}}; -handle_info({error, Error}, State) -> - handle_error(Error), - {noreply, State}; +handle_info({error, Error}, #state{panel=Panel,opt=Opt}=State) -> + Str = io_lib:format("ERROR: ~s~n",[Error]), + observer_lib:display_info_dialog(Panel,Str), + case Opt#opt.type of + mnesia -> wxMenuBar:check(observer_wx:get_menubar(), ?ID_ETS, true); + _ -> ok + end, + {noreply, State#state{opt=Opt#opt{type=ets}}}; handle_info(_Event, State) -> {noreply, State}. @@ -283,6 +309,13 @@ get_tables2(Node, #opt{type=Type, sys_hidden=Sys, unread_hidden=Unread}) -> [list_to_tabrec(Tab) || Tab <- Result] end. +col2key(0) -> #tab.name; +col2key(1) -> #tab.size; +col2key(2) -> #tab.memory; +col2key(3) -> #tab.owner; +col2key(4) -> #tab.reg_name; +col2key(5) -> #tab.id. + list_to_tabrec(PL) -> #tab{name = proplists:get_value(name, PL), id = proplists:get_value(id, PL, ignore), @@ -353,17 +386,15 @@ list_to_strings([A]) -> integer_to_list(A); list_to_strings([A|B]) -> integer_to_list(A) ++ " ," ++ list_to_strings(B). -handle_error(Foo) -> - Str = io_lib:format("ERROR: ~s~n",[Foo]), - observer_lib:display_info_dialog(Str). +update_grid(Grid, Selected, Opt, Tables) -> + wx:batch(fun() -> update_grid2(Grid, Selected, Opt, Tables) end). -update_grid(Grid, Opt, Tables) -> - wx:batch(fun() -> update_grid2(Grid, Opt, Tables) end). -update_grid2(Grid, #opt{sort_key=Sort,sort_incr=Dir}, Tables) -> +update_grid2(Grid, {SelName,SelId}, #opt{sort_key=Sort,sort_incr=Dir}, Tables) -> wxListCtrl:deleteAllItems(Grid), Update = fun(#tab{name = Name, id = Id, owner = Owner, size = Size, memory = Memory, - protection = Protection, reg_name = RegName}, Row) -> + protection = Protection, reg_name = RegName}, + {Row, Sel}) -> _Item = wxListCtrl:insertItem(Grid, Row, ""), if (Row rem 2) =:= 0 -> wxListCtrl:setItemBackgroundColour(Grid, Row, ?BG_EVEN); @@ -378,13 +409,26 @@ update_grid2(Grid, #opt{sort_key=Sort,sort_incr=Dir}, Tables) -> ({Col, Val}) -> wxListCtrl:setItem(Grid, Row, Col, observer_lib:to_str(Val)) end, - [{0,Name}, {1,Id}, {2,Size}, {3, Memory div 1024}, - {4,Owner}, {5,RegName}]), - Row + 1 + [{0,Name}, {1,Size}, {2, Memory div 1024}, + {3,Owner}, {4,RegName}, {5,Id}]), + if SelName =:= Name, SelId =:= Id -> + wxListCtrl:setItemState(Grid, Row, 16#FFFF, ?wxLIST_STATE_SELECTED), + {Row+1, Row}; + true -> + wxListCtrl:setItemState(Grid, Row, 0, ?wxLIST_STATE_SELECTED), + {Row+1, Sel} + end end, ProcInfo = case Dir of false -> lists:reverse(lists:keysort(Sort, Tables)); true -> lists:keysort(Sort, Tables) end, - lists:foldl(Update, 0, ProcInfo), - ProcInfo. + {_, Sel} = lists:foldl(Update, {0, undefined}, ProcInfo), + {ProcInfo, Sel}. + +sel(#state{selected=Sel, tabs=Tabs}) -> + try lists:nth(Sel+1, Tabs) of + #tab{name=Name, id=Id} -> {Name, Id} + catch _:_ -> + {undefined, undefined} + end. diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl index 301bb4b32f..9b9e80f479 100644 --- a/lib/observer/src/observer_wx.erl +++ b/lib/observer/src/observer_wx.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2016. All Rights Reserved. +%% Copyright Ericsson AB 2011-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ -behaviour(wx_object). -export([start/0, stop/0]). --export([create_menus/2, get_attrib/1, get_tracer/0, get_active_node/0, +-export([create_menus/2, get_attrib/1, get_tracer/0, get_active_node/0, get_menubar/0, set_status/1, create_txt_dialog/4, try_rpc/4, return_to_localnode/2]). -export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3, @@ -54,20 +54,14 @@ status_bar, notebook, main_panel, - pro_panel, - port_panel, - tv_panel, - sys_panel, - trace_panel, - app_panel, - perf_panel, - allc_panel, + panels, active_tab, node, nodes, prev_node="", log = false, - reply_to=false + reply_to=false, + config }). start() -> @@ -94,6 +88,9 @@ get_tracer() -> get_active_node() -> wx_object:call(observer, get_active_node). +get_menubar() -> + wx_object:call(observer, get_menubar). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% init(_Args) -> @@ -115,6 +112,10 @@ init(_Args) -> setup(#state{frame = Frame} = State) -> %% Setup Menubar & Menus + Config = load_config(), + Cnf = fun(Who) -> + proplists:get_value(Who, Config, #{}) + end, MenuBar = wxMenuBar:new(), {Nodes, NodeMenus} = get_nodes(), @@ -128,7 +129,7 @@ setup(#state{frame = Frame} = State) -> Notebook = wxNotebook:new(Panel, ?ID_NOTEBOOK, [{style, ?wxBK_DEFAULT}]), %% System Panel - SysPanel = observer_sys_wx:start_link(Notebook, self()), + SysPanel = observer_sys_wx:start_link(Notebook, self(), Cnf(sys_panel)), wxNotebook:addPage(Notebook, SysPanel, "System", []), %% Setup sizer create early to get it when window shows @@ -142,43 +143,45 @@ setup(#state{frame = Frame} = State) -> wxFrame:setTitle(Frame, atom_to_list(node())), wxStatusBar:setStatusText(StatusBar, atom_to_list(node())), - wxNotebook:connect(Notebook, command_notebook_page_changing), - wxFrame:connect(Frame, close_window, [{skip, true}]), + wxNotebook:connect(Notebook, command_notebook_page_changed, + [{skip, true}, {id, ?ID_NOTEBOOK}]), + wxFrame:connect(Frame, close_window, []), wxMenu:connect(Frame, command_menu_selected), wxFrame:show(Frame), %% Freeze and thaw is buggy currently - DoFreeze = [?wxMAJOR_VERSION,?wxMINOR_VERSION] < [2,9], + DoFreeze = [?wxMAJOR_VERSION,?wxMINOR_VERSION] < [2,9] + orelse element(1, os:type()) =:= win32, DoFreeze andalso wxWindow:freeze(Panel), %% I postpone the creation of the other tabs so they can query/use %% the window size %% Perf Viewer Panel - PerfPanel = observer_perf_wx:start_link(Notebook, self()), + PerfPanel = observer_perf_wx:start_link(Notebook, self(), Cnf(perf_panel)), wxNotebook:addPage(Notebook, PerfPanel, "Load Charts", []), %% Memory Allocator Viewer Panel - AllcPanel = observer_alloc_wx:start_link(Notebook, self()), + AllcPanel = observer_alloc_wx:start_link(Notebook, self(), Cnf(allc_panel)), wxNotebook:addPage(Notebook, AllcPanel, ?ALLOC_STR, []), %% App Viewer Panel - AppPanel = observer_app_wx:start_link(Notebook, self()), + AppPanel = observer_app_wx:start_link(Notebook, self(), Cnf(app_panel)), wxNotebook:addPage(Notebook, AppPanel, "Applications", []), %% Process Panel - ProPanel = observer_pro_wx:start_link(Notebook, self()), + ProPanel = observer_pro_wx:start_link(Notebook, self(), Cnf(pro_panel)), wxNotebook:addPage(Notebook, ProPanel, "Processes", []), %% Port Panel - PortPanel = observer_port_wx:start_link(Notebook, self()), + PortPanel = observer_port_wx:start_link(Notebook, self(), Cnf(port_panel)), wxNotebook:addPage(Notebook, PortPanel, "Ports", []), %% Table Viewer Panel - TVPanel = observer_tv_wx:start_link(Notebook, self()), + TVPanel = observer_tv_wx:start_link(Notebook, self(), Cnf(tv_panel)), wxNotebook:addPage(Notebook, TVPanel, "Table Viewer", []), %% Trace Viewer Panel - TracePanel = observer_trace_wx:start_link(Notebook, self()), + TracePanel = observer_trace_wx:start_link(Notebook, self(), Cnf(trace_panel)), wxNotebook:addPage(Notebook, TracePanel, ?TRACE_STR, []), %% Force redraw (windows needs it) @@ -190,19 +193,21 @@ setup(#state{frame = Frame} = State) -> SysPid = wx_object:get_pid(SysPanel), SysPid ! {active, node()}, + Panels = [{sys_panel, SysPanel, "System"}, %% In order + {perf_panel, PerfPanel, "Load Charts"}, + {allc_panel, AllcPanel, ?ALLOC_STR}, + {app_panel, AppPanel, "Applications"}, + {pro_panel, ProPanel, "Processes"}, + {port_panel, PortPanel, "Ports"}, + {tv_panel, TVPanel, "Table Viewer"}, + {trace_panel, TracePanel, ?TRACE_STR}], + UpdState = State#state{main_panel = Panel, notebook = Notebook, menubar = MenuBar, status_bar = StatusBar, - sys_panel = SysPanel, - pro_panel = ProPanel, - port_panel = PortPanel, - tv_panel = TVPanel, - trace_panel = TracePanel, - app_panel = AppPanel, - perf_panel = PerfPanel, - allc_panel = AllcPanel, active_tab = SysPid, + panels = Panels, node = node(), nodes = Nodes }, @@ -225,11 +230,14 @@ setup(#state{frame = Frame} = State) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%Callbacks -handle_event(#wx{event=#wxNotebook{type=command_notebook_page_changing}}, - #state{active_tab=Previous, node=Node} = State) -> - case get_active_pid(State) of - Previous -> {noreply, State}; +handle_event(#wx{event=#wxNotebook{type=command_notebook_page_changed, nSel=Next}}, + #state{active_tab=Previous, node=Node, panels=Panels, status_bar=SB} = State) -> + {_, Obj, _} = lists:nth(Next+1, Panels), + case wx_object:get_pid(Obj) of + Previous -> + {noreply, State}; Pid -> + wxStatusBar:setStatusText(SB, ""), Previous ! not_active, Pid ! {active, Node}, {noreply, State#state{active_tab=Pid}} @@ -359,8 +367,7 @@ handle_event(#wx{id = Id, event = #wxCommand{type = command_menu_selected}}, end, {noreply, change_node_view(Node, LState)}; -handle_event(Event, State) -> - Pid = get_active_pid(State), +handle_event(Event, #state{active_tab=Pid} = State) -> Pid ! Event, {noreply, State}. @@ -385,12 +392,16 @@ handle_call({create_menus, TabMenus}, _From, handle_call({get_attrib, Attrib}, _From, State) -> {reply, get(Attrib), State}; -handle_call(get_tracer, _From, State=#state{trace_panel=TraceP}) -> +handle_call(get_tracer, _From, State=#state{panels=Panels}) -> + {_, TraceP, _} = lists:keyfind(trace_panel, 1, Panels), {reply, TraceP, State}; handle_call(get_active_node, _From, State=#state{node=Node}) -> {reply, Node, State}; +handle_call(get_menubar, _From, State=#state{menubar=MenuBar}) -> + {reply, MenuBar, State}; + handle_call(stop, From, State) -> stop_servers(State), {noreply, State#state{reply_to=From}}; @@ -418,9 +429,7 @@ handle_info({nodedown, Node}, create_txt_dialog(Frame, Msg, "Node down", ?wxICON_EXCLAMATION), {noreply, State3}; -handle_info({open_link, Id0}, State = #state{pro_panel=ProcViewer, - port_panel=PortViewer, - frame=Frame}) -> +handle_info({open_link, Id0}, State = #state{panels=Panels,frame=Frame}) -> Id = case Id0 of [_|_] -> try list_to_pid(Id0) catch _:_ -> Id0 end; _ -> Id0 @@ -428,8 +437,10 @@ handle_info({open_link, Id0}, State = #state{pro_panel=ProcViewer, %% Forward to process tab case Id of Pid when is_pid(Pid) -> + {pro_panel, ProcViewer, _} = lists:keyfind(pro_panel, 1, Panels), wx_object:get_pid(ProcViewer) ! {procinfo_open, Pid}; "#Port" ++ _ = Port -> + {port_panel, PortViewer, _} = lists:keyfind(port_panel, 1, Panels), wx_object:get_pid(PortViewer) ! {portinfo_open, Port}; _ -> Msg = io_lib:format("Information about ~p is not available or implemented",[Id]), @@ -459,15 +470,13 @@ handle_info({stop, Me}, State) when Me =:= self() -> handle_info(_Info, State) -> {noreply, State}. -stop_servers(#state{node=Node, log=LogOn, sys_panel=Sys, pro_panel=Procs, tv_panel=TVs, - trace_panel=Trace, app_panel=Apps, perf_panel=Perfs, - allc_panel=Alloc} = _State) -> +stop_servers(#state{node=Node, log=LogOn, panels=Panels} = _State) -> LogOn andalso rpc:block_call(Node, rb, stop, []), Me = self(), - Tabs = [Sys, Procs, TVs, Trace, Apps, Perfs, Alloc], + save_config(Panels), Stop = fun() -> try - _ = [wx_object:stop(Panel) || Panel <- Tabs], + _ = [wx_object:stop(Panel) || {_, Panel, _} <- Panels], ok catch _:_ -> ok end, @@ -484,6 +493,27 @@ terminate(_Reason, #state{frame = Frame, reply_to=From}) -> end, ok. +load_config() -> + case file:consult(config_file()) of + {ok, Config} -> Config; + _ -> [] + end. + +save_config(Panels) -> + Configs = [{Name, wx_object:call(Panel, get_config)} || {Name, Panel, _} <- Panels], + File = config_file(), + case filelib:ensure_dir(File) of + ok -> + Format = [io_lib:format("~p.~n",[Conf]) || Conf <- Configs], + _ = file:write_file(File, Format); + _ -> + ignore + end. + +config_file() -> + Dir = filename:basedir(user_config, "erl_observer"), + filename:join(Dir, "config.txt"). + code_change(_, _, State) -> {ok, State}. @@ -543,8 +573,7 @@ connect2(NodeName, Opts, Cookie) -> {error, net_kernel, Reason} end. -change_node_view(Node, State) -> - Tab = get_active_pid(State), +change_node_view(Node, #state{active_tab=Tab} = State) -> Tab ! not_active, Tab ! {active, Node}, StatusText = ["Observer - " | atom_to_list(Node)], @@ -556,37 +585,13 @@ check_page_title(Notebook) -> Selection = wxNotebook:getSelection(Notebook), wxNotebook:getPageText(Notebook, Selection). -get_active_pid(#state{notebook=Notebook, pro_panel=Pro, sys_panel=Sys, - tv_panel=Tv, trace_panel=Trace, app_panel=App, - perf_panel=Perf, allc_panel=Alloc, port_panel=Port - }) -> - Panel = case check_page_title(Notebook) of - "Processes" -> Pro; - "Ports" -> Port; - "System" -> Sys; - "Table Viewer" -> Tv; - ?TRACE_STR -> Trace; - "Load Charts" -> Perf; - "Applications" -> App; - ?ALLOC_STR -> Alloc - end, - wx_object:get_pid(Panel). - -pid2panel(Pid, #state{pro_panel=Pro, sys_panel=Sys, - tv_panel=Tv, trace_panel=Trace, app_panel=App, - perf_panel=Perf, allc_panel=Alloc}) -> - case Pid of - Pro -> "Processes"; - Sys -> "System"; - Tv -> "Table Viewer" ; - Trace -> ?TRACE_STR; - Perf -> "Load Charts"; - App -> "Applications"; - Alloc -> ?ALLOC_STR; - _ -> "unknown" +pid2panel(Pid, #state{panels=Panels}) -> + PanelPids = [{Name, wx_object:get_pid(Obj)} || {Name, Obj, _} <- Panels], + case lists:keyfind(Pid, 2, PanelPids) of + false -> "unknown"; + {Name,_} -> Name end. - create_connect_dialog(ping, #state{frame = Frame, prev_node=Prev}) -> Dialog = wxTextEntryDialog:new(Frame, "Connect to node", [{value, Prev}]), case wxDialog:showModal(Dialog) of @@ -629,7 +634,8 @@ create_connect_dialog(connect, #state{frame = Frame}) -> wxWindow:setSizerAndFit(Dialog, VSizer), wxSizer:setSizeHints(VSizer, Dialog), - CookiePath = filename:join(os:getenv("HOME"), ".erlang.cookie"), + {ok,[[HomeDir]]} = init:get_argument(home), + CookiePath = filename:join(HomeDir, ".erlang.cookie"), DefaultCookie = case filelib:is_file(CookiePath) of true -> {ok, Bin} = file:read_file(CookiePath), diff --git a/lib/observer/src/ttb.erl b/lib/observer/src/ttb.erl index ac6c4572eb..09b0bc6710 100644 --- a/lib/observer/src/ttb.erl +++ b/lib/observer/src/ttb.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2002-2016. All Rights Reserved. +%% Copyright Ericsson AB 2002-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -78,6 +78,11 @@ do_tracer(Nodes0,PI,Client,Traci) -> do_tracer(Clients,PI,Traci) -> Shell = proplists:get_value(shell, Traci, false), + IpPortSpec = + case proplists:get_value(queue_size, Traci) of + undefined -> 0; + QS -> {0,QS} + end, DefShell = fun(Trace) -> dbg:dhandler(Trace, standard_io) end, {ClientSucc,Succ} = lists:foldl( @@ -98,7 +103,7 @@ do_tracer(Clients,PI,Traci) -> [_,H] = string:tokens(atom_to_list(N),"@"), H end, - case catch dbg:tracer(N,port,dbg:trace_port(ip,0)) of + case catch dbg:tracer(N,port,dbg:trace_port(ip,IpPortSpec)) of {ok,N} -> {ok,Port} = dbg:trace_port_control(N,get_listen_port), {ok,T} = dbg:get_tracer(N), @@ -160,6 +165,8 @@ opt([{resume,MSec}|O],{PI,Client,Traci}) -> opt(O,{PI,Client,[{resume, {true, MSec}}|Traci]}); opt([{flush,MSec}|O],{PI,Client,Traci}) -> opt(O,{PI,Client,[{flush, MSec}|Traci]}); +opt([{queue_size,QueueSize}|O],{PI,Client,Traci}) -> + opt(O,{PI,Client,[{queue_size,QueueSize}|Traci]}); opt([],Opt) -> ensure_opt(Opt). @@ -384,16 +391,16 @@ run_config(ConfigFile,N) -> print_func(M,F,A) -> Args = arg_list(A,[]), - io:format("~w:~w(~s) ->~n",[M,F,Args]). + io:format("~w:~tw(~ts) ->~n",[M,F,Args]). print_result(R) -> - io:format("~p~n~n",[R]). + io:format("~tp~n~n",[R]). arg_list([],[]) -> ""; arg_list([A1],Acc) -> - Acc++io_lib:format("~w",[A1]); + Acc++io_lib:format("~tw",[A1]); arg_list([A1|A],Acc) -> - arg_list(A,Acc++io_lib:format("~w,",[A1])). + arg_list(A,Acc++io_lib:format("~tw,",[A1])). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1110,7 +1117,7 @@ get_fd(Out) -> Out; _file -> file:delete(Out), - case file:open(Out,[append]) of + case file:open(Out,[append,{encoding,utf8}]) of {ok,Fd} -> Fd; Error -> exit(Error) end |