aboutsummaryrefslogtreecommitdiffstats
path: root/lib/observer/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/observer/src')
-rw-r--r--lib/observer/src/Makefile10
-rw-r--r--lib/observer/src/cdv_bin_cb.erl4
-rw-r--r--lib/observer/src/cdv_detail_wx.erl4
-rw-r--r--lib/observer/src/cdv_ets_cb.erl28
-rw-r--r--lib/observer/src/cdv_mem_cb.erl4
-rw-r--r--lib/observer/src/cdv_wx.erl2
-rw-r--r--lib/observer/src/crashdump_viewer.erl102
-rw-r--r--lib/observer/src/crashdump_viewer.hrl3
-rw-r--r--lib/observer/src/etop.erl40
-rw-r--r--lib/observer/src/etop_txt.erl65
-rw-r--r--lib/observer/src/observer_alloc_wx.erl72
-rw-r--r--lib/observer/src/observer_app_wx.erl20
-rw-r--r--lib/observer/src/observer_lib.erl67
-rw-r--r--lib/observer/src/observer_perf_wx.erl164
-rw-r--r--lib/observer/src/observer_port_wx.erl230
-rw-r--r--lib/observer/src/observer_pro_wx.erl210
-rw-r--r--lib/observer/src/observer_procinfo.erl21
-rw-r--r--lib/observer/src/observer_sys_wx.erl15
-rw-r--r--lib/observer/src/observer_trace_wx.erl73
-rw-r--r--lib/observer/src/observer_tv_table.erl32
-rw-r--r--lib/observer/src/observer_tv_wx.erl118
-rw-r--r--lib/observer/src/observer_wx.erl160
-rw-r--r--lib/observer/src/ttb.erl21
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