diff options
Diffstat (limited to 'lib/observer')
-rw-r--r-- | lib/observer/doc/src/notes.xml | 119 | ||||
-rw-r--r-- | lib/observer/src/etop_gui.erl | 7 | ||||
-rw-r--r-- | lib/observer/src/observer_lib.erl | 36 | ||||
-rw-r--r-- | lib/observer/src/observer_procinfo.erl | 101 | ||||
-rw-r--r-- | lib/observer/src/observer_tv_table.erl | 109 | ||||
-rw-r--r-- | lib/observer/src/observer_tv_wx.erl | 4 | ||||
-rw-r--r-- | lib/observer/test/etop_SUITE.erl | 13 | ||||
-rw-r--r-- | lib/observer/vsn.mk | 2 |
8 files changed, 315 insertions, 76 deletions
diff --git a/lib/observer/doc/src/notes.xml b/lib/observer/doc/src/notes.xml index baa1354268..a9554a08f4 100644 --- a/lib/observer/doc/src/notes.xml +++ b/lib/observer/doc/src/notes.xml @@ -31,6 +31,125 @@ <p>This document describes the changes made to the Observer application.</p> +<section><title>Observer 1.0</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + The following bugs in <c>ttb</c> have been corrected:</p> + <p> + <list> <item><c>ttb:tracer/2</c> would earlier crash when + trying to set up tracing for a diskless node to wrap + files, i.e. when option + <c>{file,{local,{wrap,Filename,Size,Count}}}</c> was + used.</item> <item><c>ttb:stop([fetch])</c> would + sometimes silently fail if multiple nodes with different + current working directories were traced.</item> + <item><c>ttb:stop([fetch])</c> would crash if the tracer + was started with option + <c>{file,{local,Filename}}</c></item> <item>A deadlock + would sometimes occur due to an information printout from + the <c>ttb_control</c> process when <c>ttb</c> was + stopped.</item> </list></p> + <p> + Own Id: OTP-9431</p> + </item> + <item> + <p> + The file trace port to which the IP trace client relays + all traces from diskless nodes was not flushed and closed + properly on ttb:stop. This has been corrected.</p> + <p> + Own Id: OTP-9665</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + A new GUI for Observer. Integrating pman, etop, appmon + and tv into observer with tracing facilities.</p> + <p> + Own Id: OTP-4779</p> + </item> + <item> + <p> + The following new features are added to <c>ttb</c>:</p> + <p> + <list> <item>A one-command trace setup is added, + <c>ttb:start_trace/4</c>.</item> <item>The following new + options are added to <c>ttb:tracer/2</c>: <list> + <item><em><c>shell</c></em> - Show trace messages on the + console in real time</item> <item><em><c>timer</c></em> - + Time constrained tracing</item> + <item><em><c>overload</c></em> - Overload + protection</item> <item><em><c>flush</c></em> - Flush + file trace port buffers with given frequency</item> + <item><em><c>resume</c></em> - Automatically resume + tracing after node restart</item> </list> </item> <item> + A new shortcut is added for common tracer settings + similar to using the <c>dbg</c> module directly, + <c>ttb:tracer(shell | dbg)</c>. </item> <item> Some + shortcuts are added for commonly used match + specifications in <c>ttb:tp</c> and <c>ttb:tpl</c>. + </item> <item> The <c>Options</c> argument to functions + <c>ttb:tracer</c>, <c>ttb:write_config</c>, + <c>ttb:stop</c> and <c>ttb:format</c> may now be one + single option instead of a list. </item> <item> The + history buffer of the last trace is now always + automatically dumped to the file <c>ttb_last_config</c> + when <c>ttb:stop</c> is called. </item> <item> The + following new options are added to <c>ttb:stop/1</c>: + <list> <item><em><c>fetch_dir</c></em> - Specify where to + store fetched logs</item> + <item><em><c>{format,FormatOpts}</c></em> - Specify + options to use when formatting the fetched logs</item> + <item><em><c>return_fetch_dir</c></em> - Indicate that + the return value from <c>ttb:stop/1</c> should include + the name of the directory where the fetched logs are + stored</item> </list> </item> <item> The option + <c>disable_sort</c> is added to <c>ttb:format/2</c>. When + this option is used, trace messages from different logs + are not merged according to timestamps, but just appended + one log after the other. </item> </list></p> + <p> + Own Id: OTP-9403</p> + </item> + <item> + <p> + The following non backwards compatible changes are done + in <c>ttb</c>:</p> + <p> + <list> <item> When setting up trace with ttb, the + 'timestamp' trace flag will now always be set. </item> + <item> The 'fetch' option to ttb:stop/1 is removed since + it is now default behavior that trace logs are fetched + when stopping ttb. Fetching can be disabled with the + 'nofetch' option to ttb:stop/1. </item> <item> The name + of the upload directory is changed from + ttb_upload-Timestamp to ttb_upload_FileName-Timestamp. + </item> <item> To format the output using 'et', you now + need to provide the option {handler,ttb:get_et_handler()} + instead of {handler,et}. </item> <item> When formatting a + trace log, the handler state was earlier reset after each + trace file, this is now changed so the handler state is + passed not only from one trace message to the next in the + same file, but also from one file to the next. </item> + </list></p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-9430</p> + </item> + </list> + </section> + +</section> + <section><title>Observer 0.9.10</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/observer/src/etop_gui.erl b/lib/observer/src/etop_gui.erl index ff1b8078ad..9248d67344 100644 --- a/lib/observer/src/etop_gui.erl +++ b/lib/observer/src/etop_gui.erl @@ -17,6 +17,13 @@ %% %CopyrightEnd% %% -module(etop_gui). +-compile([{nowarn_deprecated_function,{gs,config,2}}, + {nowarn_deprecated_function,{gs,create,3}}, + {nowarn_deprecated_function,{gs,create,4}}, + {nowarn_deprecated_function,{gs,destroy,1}}, + {nowarn_deprecated_function,{gs,read,2}}, + {nowarn_deprecated_function,{gs,start,0}}]). + -author('[email protected]'). -export([init/1,stop/1]). diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl index 967baa5c7a..5260861497 100644 --- a/lib/observer/src/observer_lib.erl +++ b/lib/observer/src/observer_lib.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011. All Rights Reserved. +%% Copyright Ericsson AB 2011-2012. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -339,17 +339,37 @@ user_term(Parent, Title, Default) -> ?wxID_OK -> Str = wxTextEntryDialog:getValue(Dialog), wxTextEntryDialog:destroy(Dialog), - parse_string(Str); + parse_string(ensure_last_is_dot(Str)); ?wxID_CANCEL -> - wxTextEntryDialog:destroy(Dialog) + wxTextEntryDialog:destroy(Dialog), + cancel end. parse_string(Str) -> try - {ok, Tokens, _} = erl_scan:string(Str), - erl_parse:parse_term(Tokens) - catch _:{badmatch, {error, {_, _, Err}}} -> - {error, ["Parse error: ", Err]}; - _Err -> + Tokens = case erl_scan:string(Str) 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 + {error, {_PLine, PMod, PError}} -> + throw(io_lib:format("~s", [PMod:format_error(PError)])); + Res -> Res + end + catch + throw:ErrStr -> + {error, ErrStr}; + _:_Err -> {error, ["Syntax error in: ", Str]} end. + +ensure_last_is_dot([]) -> + "."; +ensure_last_is_dot(String) -> + case lists:last(String) =:= $. of + true -> + String; + false -> + String ++ "." + end. diff --git a/lib/observer/src/observer_procinfo.erl b/lib/observer/src/observer_procinfo.erl index a4c5914c49..ec08d3aff1 100644 --- a/lib/observer/src/observer_procinfo.erl +++ b/lib/observer/src/observer_procinfo.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011. All Rights Reserved. +%% Copyright Ericsson AB 2011-2012. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -124,9 +124,14 @@ code_change(_, _, State) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% init_process_page(Panel, Pid) -> - Fields = process_info_fields(Pid), - {FPanel, _, UpFields} = observer_lib:display_info(Panel, Fields), - {FPanel, fun() -> observer_lib:update_info(UpFields, process_info_fields(Pid)) end}. + Fields0 = process_info_fields(Pid), + {FPanel, _, UpFields} = observer_lib:display_info(Panel, Fields0), + {FPanel, fun() -> case process_info_fields(Pid) of + Fields when is_list(Fields) -> + observer_lib:update_info(UpFields, Fields); + _ -> ok + end + end}. init_text_page(Parent) -> Style = ?wxTE_MULTILINE bor ?wxTE_RICH2 bor ?wxTE_READONLY, @@ -144,16 +149,20 @@ init_message_page(Parent, Pid) -> Number+1} end, Update = fun() -> - {messages,RawMessages} = - observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, messages]), - {Messages,_} = lists:mapfoldl(Format, 1, RawMessages), - Last = wxTextCtrl:getLastPosition(Text), - wxTextCtrl:remove(Text, 0, Last), - case Messages =:= [] of - true -> - wxTextCtrl:writeText(Text, "No messages"); - false -> - wxTextCtrl:writeText(Text, Messages) + case observer_wx:try_rpc(node(Pid), erlang, process_info, + [Pid, messages]) + of + {messages,RawMessages} -> + {Messages,_} = lists:mapfoldl(Format, 1, RawMessages), + Last = wxTextCtrl:getLastPosition(Text), + wxTextCtrl:remove(Text, 0, Last), + case Messages =:= [] of + true -> + wxTextCtrl:writeText(Text, "No messages"); + false -> + wxTextCtrl:writeText(Text, Messages) + end; + _ -> ok end end, Update(), @@ -162,12 +171,15 @@ init_message_page(Parent, Pid) -> init_dict_page(Parent, Pid) -> Text = init_text_page(Parent), Update = fun() -> - {dictionary,RawDict} = - observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, dictionary]), - Dict = [io_lib:format("~-20.w ~p~n", [K, V]) || {K, V} <- RawDict], - Last = wxTextCtrl:getLastPosition(Text), - wxTextCtrl:remove(Text, 0, Last), - wxTextCtrl:writeText(Text, Dict) + case observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, dictionary]) + of + {dictionary,RawDict} -> + Dict = [io_lib:format("~-20.w ~p~n", [K, V]) || {K, V} <- RawDict], + Last = wxTextCtrl:getLastPosition(Text), + wxTextCtrl:remove(Text, 0, Last), + wxTextCtrl:writeText(Text, Dict); + _ -> ok + end end, Update(), {Text, Update}. @@ -183,24 +195,29 @@ init_stack_page(Parent, Pid) -> wxListCtrl:setColumnWidth(LCtrl, 1, 300), wxListItem:destroy(Li), Update = fun() -> - {current_stacktrace,RawBt} = - observer_wx:try_rpc(node(Pid), erlang, process_info, - [Pid, current_stacktrace]), - wxListCtrl:deleteAllItems(LCtrl), - wx:foldl(fun({M, F, A, Info}, Row) -> - _Item = wxListCtrl:insertItem(LCtrl, Row, ""), - ?EVEN(Row) andalso - wxListCtrl:setItemBackgroundColour(LCtrl, Row, ?BG_EVEN), - wxListCtrl:setItem(LCtrl, Row, 0, observer_lib:to_str({M,F,A})), - FileLine = case Info of - [{file,File},{line,Line}] -> - io_lib:format("~s:~w", [File,Line]); - _ -> - [] - end, - wxListCtrl:setItem(LCtrl, Row, 1, FileLine), - Row+1 - end, 0, RawBt) + case observer_wx:try_rpc(node(Pid), erlang, process_info, + [Pid, current_stacktrace]) + of + {current_stacktrace,RawBt} -> + observer_wx:try_rpc(node(Pid), erlang, process_info, + [Pid, current_stacktrace]), + wxListCtrl:deleteAllItems(LCtrl), + wx:foldl(fun({M, F, A, Info}, Row) -> + _Item = wxListCtrl:insertItem(LCtrl, Row, ""), + ?EVEN(Row) andalso + wxListCtrl:setItemBackgroundColour(LCtrl, Row, ?BG_EVEN), + wxListCtrl:setItem(LCtrl, Row, 0, observer_lib:to_str({M,F,A})), + FileLine = case Info of + [{file,File},{line,Line}] -> + io_lib:format("~s:~w", [File,Line]); + _ -> + [] + end, + wxListCtrl:setItem(LCtrl, Row, 1, FileLine), + Row+1 + end, 0, RawBt); + _ -> ok + end end, Resize = fun(#wx{event=#wxSize{size={W,_}}},Ev) -> wxEvent:skip(Ev), @@ -216,7 +233,6 @@ create_menus(MenuBar) -> observer_lib:create_menus(Menus, MenuBar, new_window). process_info_fields(Pid) -> - RawInfo = observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, item_list()]), Struct = [{"Overview", [{"Initial Call", initial_call}, {"Current Function", current_function}, @@ -246,7 +262,12 @@ process_info_fields(Pid) -> {"GC Min Heap Size", {bytes, get_gc_info(min_heap_size)}}, {"GC FullSweep After", get_gc_info(fullsweep_after)} ]}], - observer_lib:fill_info(Struct, RawInfo). + case observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, item_list()]) of + RawInfo when is_list(RawInfo) -> + observer_lib:fill_info(Struct, RawInfo); + _ -> + ok + end. item_list() -> [ %% backtrace, diff --git a/lib/observer/src/observer_tv_table.erl b/lib/observer/src/observer_tv_table.erl index 31d5f3d632..f432173f57 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. All Rights Reserved. +%% Copyright Ericsson AB 2011-2012. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -58,7 +58,7 @@ source, tab, attrs, - timer + timer={false, 30} }). -record(opt, @@ -268,8 +268,8 @@ handle_event(#wx{id=?ID_DELETE}, handle_event(#wx{id=?wxID_CLOSE}, State) -> {stop, normal, State}; -handle_event(Help = #wx{id=?wxID_HELP}, State = #state{parent=Parent}) -> - Parent ! Help, +handle_event(Help = #wx{id=?wxID_HELP}, State) -> + observer ! Help, {noreply, State}; handle_event(#wx{id=?GOTO_ENTRY, event=#wxCommand{cmdString=Str}}, @@ -374,40 +374,51 @@ handle_event(#wx{id=?ID_REFRESH_INTERVAL}, Timer = observer_lib:interval_dialog(Grid, Timer0, 10, 5*60), {noreply, State#state{timer=Timer}}; -handle_event(Event, State) -> - io:format("~p:~p, handle event ~p\n", [?MODULE, ?LINE, Event]), +handle_event(_Event, State) -> + %io:format("~p:~p, handle event ~p\n", [?MODULE, ?LINE, Event]), {noreply, State}. -handle_sync_event(Event, _Obj, _State) -> - io:format("~p:~p, handle sync_event ~p\n", [?MODULE, ?LINE, Event]), +handle_sync_event(_Event, _Obj, _State) -> + %io:format("~p:~p, handle sync_event ~p\n", [?MODULE, ?LINE, Event]), ok. -handle_call(Event, From, State) -> - io:format("~p:~p, handle call (~p) ~p\n", [?MODULE, ?LINE, From, Event]), +handle_call(_Event, _From, State) -> + %io:format("~p:~p, handle call (~p) ~p\n", [?MODULE, ?LINE, From, Event]), {noreply, State}. -handle_cast(Event, State) -> - io:format("~p:~p, handle cast ~p\n", [?MODULE, ?LINE, Event]), +handle_cast(_Event, State) -> + %io:format("~p:~p, handle cast ~p\n", [?MODULE, ?LINE, Event]), {noreply, State}. handle_info({no_rows, N}, State = #state{grid=Grid, status=StatusBar}) -> wxListCtrl:setItemCount(Grid, N), wxStatusBar:setStatusText(StatusBar, io_lib:format("Objects: ~w",[N])), {noreply, State}; + handle_info({new_cols, New}, State = #state{grid=Grid, columns=Cols0}) -> Cols = add_columns(Grid, Cols0, New), {noreply, State#state{columns=Cols}}; + handle_info({refresh, Min, Max}, State = #state{grid=Grid}) -> wxListCtrl:refreshItems(Grid, Min, Max), {noreply, State}; + +handle_info(refresh_interval, State = #state{pid=Pid}) -> + Pid ! refresh, + {noreply, State}; + handle_info({error, Error}, State = #state{frame=Frame}) -> - Dlg = wxMessageDialog:new(Frame, Error), + ErrorStr = + try io_lib:format("~ts", [Error]), Error + catch _:_ -> io_lib:format("~p", [Error]) + end, + Dlg = wxMessageDialog:new(Frame, ErrorStr), wxMessageDialog:showModal(Dlg), wxMessageDialog:destroy(Dlg), {noreply, State}; -handle_info(Event, State) -> - io:format("~p:~p, handle info ~p\n", [?MODULE, ?LINE, Event]), +handle_info(_Event, State) -> + %% io:format("~p:~p, handle info ~p\n", [?MODULE, ?LINE, _Event]), {noreply, State}. terminate(_Event, #state{pid=Pid, attrs=Attrs}) -> @@ -588,19 +599,22 @@ search([Str, Row, Dir0, CaseSens], true -> []; false -> [caseless] end, - {ok, Re} = re:compile(Str, Opt), Dir = case Dir0 of true -> 1; false -> -1 end, - Res = search(Row, Dir, Re, Table), + Res = case re:compile(Str, Opt) of + {ok, Re} -> + search(Row, Dir, Re, Table); + {error, _} -> false + end, Parent ! {self(), Res}, S#holder{search=Res}. search(Row, Dir, Re, Table) -> Res = try lists:nth(Row+1, Table) of Term -> - Str = io_lib:format("~w", [Term]), + Str = format(Term), re:run(Str, Re) catch _:_ -> no_more end, @@ -613,9 +627,9 @@ search(Row, Dir, Re, Table) -> get_row(From, Row, Col, Table) -> case lists:nth(Row+1, Table) of [Object|_] when Col =:= all -> - From ! {self(), io_lib:format("~w", [Object])}; + From ! {self(), format(Object)}; [Object|_] when tuple_size(Object) >= Col -> - From ! {self(), io_lib:format("~w", [element(Col, Object)])}; + From ! {self(), format(element(Col, Object))}; _ -> From ! {self(), ""} end. @@ -724,3 +738,58 @@ key_pos(Node, ets, TabId) -> KeyPos = rpc:call(Node, ets, info, [TabId, keypos]), is_integer(KeyPos) orelse throw(node_or_table_down), KeyPos. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +format(Tuple) when is_tuple(Tuple) -> + [${ |format_tuple(Tuple, 1, tuple_size(Tuple))]; +format(List) when is_list(List) -> + format_list(List); +format(Bin) when is_binary(Bin), byte_size(Bin) > 100 -> + io_lib:format("<<#Bin:~w>>", [byte_size(Bin)]); +format(Float) when is_float(Float) -> + io_lib:format("~.3g", [Float]); +format(Term) -> + io_lib:format("~w", [Term]). + +format_tuple(Tuple, I, Max) when I < Max -> + [format(element(I, Tuple)), $,|format_tuple(Tuple, I+1, Max)]; +format_tuple(Tuple, Max, Max) -> + [format(element(Max, Tuple)), $}]; +format_tuple(_Tuple, 1, 0) -> + [$}]. + +format_list([]) -> "[]"; +format_list(List) -> + case printable_list(List) of + true -> io_lib:format("\"~ts\"", [List]); + false -> [$[ | make_list(List)] + end. + +make_list([Last]) -> + [format(Last), $]]; +make_list([Head|Tail]) -> + [format(Head), $,|make_list(Tail)]. + +%% printable_list([Char]) -> bool() +%% Return true if CharList is a list of printable characters, else +%% false. + +printable_list([C|Cs]) when is_integer(C), C >= $ , C =< 255 -> + printable_list(Cs); +printable_list([$\n|Cs]) -> + printable_list(Cs); +printable_list([$\r|Cs]) -> + printable_list(Cs); +printable_list([$\t|Cs]) -> + printable_list(Cs); +printable_list([$\v|Cs]) -> + printable_list(Cs); +printable_list([$\b|Cs]) -> + printable_list(Cs); +printable_list([$\f|Cs]) -> + printable_list(Cs); +printable_list([$\e|Cs]) -> + printable_list(Cs); +printable_list([]) -> true; +printable_list(_Other) -> false. %Everything else is false diff --git a/lib/observer/src/observer_tv_wx.erl b/lib/observer/src/observer_tv_wx.erl index ad3e8c14ab..b276965f83 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. All Rights Reserved. +%% Copyright Ericsson AB 2011-2012. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -313,7 +313,7 @@ display_table_info(Parent0, Node, Source, Table) -> list_to_strings([]) -> "None"; list_to_strings([A]) -> integer_to_list(A); -list_to_strings([A,B]) -> +list_to_strings([A|B]) -> integer_to_list(A) ++ " ," ++ list_to_strings(B). handle_error(Foo) -> diff --git a/lib/observer/test/etop_SUITE.erl b/lib/observer/test/etop_SUITE.erl index a0782ea809..a277453620 100644 --- a/lib/observer/test/etop_SUITE.erl +++ b/lib/observer/test/etop_SUITE.erl @@ -57,11 +57,14 @@ end_per_group(_GroupName, Config) -> Config. -text(suite) -> - []; -text(doc) -> - ["Start etop with text presentation"]; -text(Config) when is_list(Config) -> +%% Start etop with text presentation +text(_) -> + case test_server:is_native(lists) of + true -> {skip,"Native libs -- tracing does not work"}; + false -> text() + end. + +text() -> ?line {ok,Node} = ?t:start_node(node2,peer,[]), %% Must spawn this process, else the test case will never end. diff --git a/lib/observer/vsn.mk b/lib/observer/vsn.mk index 76e2f591fa..fa104ede01 100644 --- a/lib/observer/vsn.mk +++ b/lib/observer/vsn.mk @@ -1 +1 @@ -OBSERVER_VSN = 0.9.10 +OBSERVER_VSN = 1.0 |