diff options
author | Siri Hansen <[email protected]> | 2014-01-28 15:48:11 +0100 |
---|---|---|
committer | Siri Hansen <[email protected]> | 2014-01-28 15:48:11 +0100 |
commit | f65764907faba7ea6ca1a6bb266b6c6612e06b7b (patch) | |
tree | f228f7ae616ee86fb4f0ff5a7d6643f608fde471 /lib/observer/src/crashdump_viewer.erl | |
parent | 10dcd146040728fab222fe325dde9328ab506d15 (diff) | |
parent | a0d7557b1f84e71f79af5f2d32caf2ce994adb4e (diff) | |
download | otp-f65764907faba7ea6ca1a6bb266b6c6612e06b7b.tar.gz otp-f65764907faba7ea6ca1a6bb266b6c6612e06b7b.tar.bz2 otp-f65764907faba7ea6ca1a6bb266b6c6612e06b7b.zip |
Merge branch 'siri/wx-cdv/OTP-11179'
* siri/wx-cdv/OTP-11179:
observer: cosmetic gui tweaks
observer: Fix progress dialog creation
observer: renamed crashdump_viewer files and fixed makefiles
observer: improve wx version of crashdump_viewer
observer: Use crashdump_viewer's term viewer to display large terms and binaries
observer: Fix memory and scheduler info and handle missing fields
observer: Optimize row lookups
observer: improve wx version of crashdump_viewer
observer: Consolidate the view of process information
observer: add wx version of crashdump_viewer
Diffstat (limited to 'lib/observer/src/crashdump_viewer.erl')
-rw-r--r-- | lib/observer/src/crashdump_viewer.erl | 2129 |
1 files changed, 855 insertions, 1274 deletions
diff --git a/lib/observer/src/crashdump_viewer.erl b/lib/observer/src/crashdump_viewer.erl index e7d71c581e..a17efbccb0 100644 --- a/lib/observer/src/crashdump_viewer.erl +++ b/lib/observer/src/crashdump_viewer.erl @@ -20,80 +20,53 @@ %% %% This module is the main module in the crashdump viewer. It implements -%% the server started by webtool and the API for the crashdump viewer tool. -%% -%% All functions in the API except configData/0 and start_link/0 are -%% called from HTML pages via erl_scheme (mod_esi). +%% the server backend for the crashdump viewer tool. %% %% Tables %% ------ -%% cdv_menu_table: This table holds the menu which is presented in the left -%% frame of the crashdump viewer page. Each element in the table represents -%% one meny item, and the state of the item indicates if it is presently -%% visible or not. -%% -%% cdv_dump_index_table: This table holds all tags read from the crashdump. -%% Each tag indicates where the information about a specific item starts. -%% The table entry for a tag includes the start position for this -%% item-information. All tags start with a "=" at the beginning of -%% a line. +%% cdv_dump_index_table: This table holds all tags read from the +%% crashdump. Each tag indicates where the information about a +%% specific item starts. The table entry for a tag includes the start +%% position for this item-information. In a crash dump file, all tags +%% start with a "=" at the beginning of a line. %% %% Process state %% ------------- %% file: The name of the crashdump currently viewed. %% dump_vsn: The version number of the crashdump -%% procs_summary: Process summary represented by a list of -%% #proc records. This is used for efficiency reasons when sorting the -%% process summary table instead of reading all processes from the -%% dump again. Note that if the dump contains more than -%% ?max_sort_process_num processes, the sort functionality is not -%% available, and the procs_summary field in the state will have the -%% value 'too_many'. -%% sorted: string(), indicated what item was last sorted in process summary. -%% This is needed so reverse sorting can be done. -%% shared_heap: 'true' if crashdump comes from a system running shared heap, -%% else 'false'. %% wordsize: 4 | 8, the number of bytes in a word. %% binaries: a gb_tree containing binaries or links to binaries in the dump %% %% User API --export([start/0,stop/0,script_start/0,script_start/1]). - -%% Webtool API --export([configData/0, - start_link/0]). --export([start_page/2, - read_file_frame/2, - read_file/2, - redirect/2, - filename_frame/2, - menu_frame/2, - initial_info_frame/2, - toggle/2, - general_info/2, - processes/3, - proc_details/2, - port/2, - ports/3, - ets_tables/3, - internal_ets_tables/2, - timers/3, - fun_table/3, - atoms/3, - dist_info/2, - loaded_modules/3, - loaded_mod_details/2, - memory/2, - allocated_areas/2, - allocator_info/2, - hash_tables/2, - index_tables/2, - sort_procs/3, - expand/2, - expand_binary/2, - expand_memory/2]). - +-export([start/0,start/1,stop/0,script_start/0,script_start/1]). + +%% GUI API +-export([start_link/0]). +-export([read_file/1, + general_info/0, + processes/0, + proc_details/1, + port/1, + ports/0, + ets_tables/1, + internal_ets_tables/0, + timers/1, + funs/0, + atoms/0, + dist_info/0, + node_info/1, + loaded_modules/0, + loaded_mod_details/1, + memory/0, + allocated_areas/0, + allocator_info/0, + hash_tables/0, + index_tables/0, + expand_binary/1]). + +%% Library function +-export([to_proplist/2, to_value_list/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, @@ -106,28 +79,11 @@ -include_lib("kernel/include/file.hrl"). -include_lib("stdlib/include/ms_transform.hrl"). --define(START_PAGE,"/cdv_erl/crashdump_viewer/start_page"). --define(READ_FILE_PAGE,"/cdv_erl/crashdump_viewer/read_file?path="). -define(SERVER, crashdump_viewer_server). -define(call_timeout,3600000). -define(chunk_size,1000). % number of bytes read from crashdump at a time -define(max_line_size,100). % max number of bytes (i.e. characters) the % line_head/1 function can return --define(max_display_size,500). % max number of bytes that will be directly - % displayed. If e.g. msg_q is longer than - % this, it must be explicitly expanded. --define(max_display_binary_size,50). % max size of a binary that will be - % directly displayed. --define(max_sort_process_num,10000). % Max number of processes that allows - % sorting. If more than this number of - % processes exist, they will be displayed - % in the order they are found in the log. --define(items_chunk_size,?max_sort_process_num). % Number of items per chunk - % when page of many items - % is displayed, e.g. processes, - % timers, funs... - % Must be equal to - % ?max_sort_process_num! -define(not_available,"N/A"). @@ -136,7 +92,6 @@ -define(allocator,allocator). -define(atoms,atoms). -define(binary,binary). --define(debug_proc_dictionary,debug_proc_dictionary). -define(ende,ende). -define(erl_crash_dump,erl_crash_dump). -define(ets,ets). @@ -152,7 +107,6 @@ -define(no_distribution,no_distribution). -define(node,node). -define(not_connected,not_connected). --define(num_atoms,num_atoms). -define(old_instr_data,old_instr_data). -define(port,port). -define(proc,proc). @@ -164,8 +118,7 @@ -define(visible_node,visible_node). --record(state,{file,dump_vsn,procs_summary,sorted,shared_heap=false, - wordsize=4,num_atoms="unknown",binaries,bg_status}). +-record(state,{file,dump_vsn,wordsize=4,num_atoms="unknown",binaries}). %%%----------------------------------------------------------------- %%% Debugging @@ -198,133 +151,72 @@ stop_debug() -> %%%----------------------------------------------------------------- %%% User API start() -> - webtool:start(), - receive after 1000 -> ok end, - webtool:start_tools([],"app=crashdump_viewer"), - receive after 1000 -> ok end, - ok. + start(undefined). +start(File) -> + cdv_wx:start(File). stop() -> - webtool:stop_tools([],"app=crashdump_viewer"), - webtool:stop(). + case whereis(?SERVER) of + undefined -> + ok; + Pid -> + Ref = erlang:monitor(process,Pid), + cast(stop), + receive {'DOWN', Ref, process, Pid, _} -> ok end + end. %%%----------------------------------------------------------------- %%% Start crashdump_viewer via the cdv script located in %%% $OBSERVER_PRIV_DIR/bin script_start() -> - usage(). -script_start([File]) -> - DefaultBrowser = - case os:type() of - {win32,_} -> iexplore; - {unix,darwin} -> open; - _ -> firefox - end, - script_start([File,DefaultBrowser]); -script_start([FileAtom,Browser]) -> + do_script_start(fun() -> start() end), + erlang:halt(). +script_start([FileAtom]) -> File = atom_to_list(FileAtom), case filelib:is_regular(File) of true -> - io:format("Starting crashdump_viewer...\n"), - start(), - io:format("Reading crashdump..."), - read_file(File), - redirect([],[]), - io:format("done\n"), - start_browser(Browser); + do_script_start(fun() -> start(File) end); false -> io:format("cdv error: the given file does not exist\n"), usage() - end. - -start_browser(Browser) -> - PortStr = integer_to_list(gen_server:call(web_tool,get_port)), - Url = "http://localhost:" ++ PortStr ++ ?START_PAGE, - {OSType,_} = os:type(), - case Browser of - none -> - ok; - iexplore when OSType == win32-> - io:format("Starting internet explorer...\n"), - {ok,R} = win32reg:open(""), - Key="\\local_machine\\SOFTWARE\\Microsoft\\IE Setup\\Setup", - win32reg:change_key(R,Key), - {ok,Val} = win32reg:value(R,"Path"), - IExplore=filename:join(win32reg:expand(Val),"iexplore.exe"), - os:cmd("\"" ++ IExplore ++ "\" " ++ Url); - _ when OSType == win32 -> - io:format("Starting ~w...\n",[Browser]), - os:cmd("\"" ++ atom_to_list(Browser) ++ "\" " ++ Url); - B when B==firefox; B==mozilla -> - io:format("Sending URL to ~w...",[Browser]), - BStr = atom_to_list(Browser), - SendCmd = BStr ++ " -raise -remote \'openUrl(" ++ Url ++ ")\'", - Port = open_port({spawn,SendCmd},[exit_status]), - receive - {Port,{exit_status,0}} -> - io:format("done\n"); - {Port,{exit_status,_Error}} -> - io:format(" not running, starting ~w...\n",[Browser]), - os:cmd(BStr ++ " " ++ Url) - after 5000 -> - io:format(" failed, starting ~w...\n",[Browser]), - erlang:port_close(Port), - os:cmd(BStr ++ " " ++ Url) - end; - _ -> - io:format("Starting ~w...\n",[Browser]), - os:cmd(atom_to_list(Browser) ++ " " ++ Url) end, - ok. + erlang:halt(); +script_start(_) -> + usage(), + erlang:halt(). + +do_script_start(StartFun) -> + process_flag(trap_exit,true), + case StartFun() of + ok -> + case whereis(cdv_wx) of + Pid when is_pid(Pid) -> + link(Pid), + receive + {'EXIT', Pid, normal} -> + ok; + {'EXIT', Pid, Reason} -> + io:format("\ncdv crash: ~p\n",[Reason]) + end; + _ -> + io:format("\ncdv crash: ~p\n",[unknown_reason]) + end; + Error -> + io:format("\ncdv start failed: ~p\n",[Error]) + end. usage() -> io:format( - "\nusage: cdv file [ browser ]\n" + "usage: cdv [file]\n" "\tThe \'file\' must be an existing erlang crash dump.\n" - "\tDefault browser is \'iexplore\' (Internet Explorer) on Windows,\n" - "\t\'open\' on Mac OS X, or else \'firefox\'.\n", + "\tIf omitted a file dialog will be opened.\n", []). - - - -%%%----------------------------------------------------------------- -%%% Return config data used by webtool -configData() -> - Dir = filename:join(code:priv_dir(observer),"crashdump_viewer"), - {crashdump_viewer, - [{web_data,{"CrashDumpViewer",?START_PAGE}}, - {alias,{"/crashdump_viewer",Dir}}, - {alias,{"/crashdump_erts_doc",erts_docdir()}}, - {alias,{"/crashdump_doc",cdv_docdir()}}, - {alias,{erl_alias,"/cdv_erl",[?MODULE]}}, - {start,{child,{{local,?SERVER}, - {?MODULE,start_link,[]}, - permanent,100,worker,[?MODULE]}}} - ]}. - -erts_docdir() -> - ErtsVsn = erlang:system_info(version), - RootDir = code:root_dir(), - VsnErtsDir = filename:join(RootDir,"erts-"++ErtsVsn), - DocDir = filename:join(["doc","html"]), - case filelib:is_dir(VsnErtsDir) of - true -> - filename:join(VsnErtsDir,DocDir); - false -> - %% So this can be run in clearcase - filename:join([RootDir,"erts",DocDir]) - end. - -cdv_docdir() -> - ObserverDir = code:lib_dir(observer), - filename:join([ObserverDir,"doc","html"]). - %%==================================================================== %% External functions %%==================================================================== %%%-------------------------------------------------------------------- -%%% Start the server +%%% Start the server - called by cdv_wx start_link() -> case whereis(?SERVER) of undefined -> @@ -334,119 +226,63 @@ start_link() -> end. %%%----------------------------------------------------------------- -%%% If crashdump_viewer is just started, show welcome frame. Else -%%% show menu and general_info -start_page(_Env,_Input) -> - call(start_page). - -%%%----------------------------------------------------------------- -%%% Display the form for entering the file name for the crashdump -%%% to view. -read_file_frame(_Env,_Input) -> - crashdump_viewer_html:read_file_frame(). - -%%%----------------------------------------------------------------- -%%% Called when the 'ok' button is clicked after entering the dump -%%% file name. -read_file(_Env,Input) -> - call({read_file,Input}). - -%%%----------------------------------------------------------------- -%%% The topmost frame of the main page. Called when a crashdump is -%%% loaded. -filename_frame(_Env,_Input) -> - call(filename_frame). - -%%%----------------------------------------------------------------- -%%% The initial information frame. Called when a crashdump is loaded. -initial_info_frame(_Env,_Input) -> - call(initial_info_frame). - -%%%----------------------------------------------------------------- -%%% The left frame of the main page. Called when a crashdump is -%%% loaded. -menu_frame(_Env,_Input) -> - crashdump_viewer_html:menu_frame(). - -%%%----------------------------------------------------------------- -%%% Called when the collapsed or exploded picture in the menu is -%%% clicked. -toggle(_Env,Input) -> - call({toggle,Input}). +%%% Called by cdv_wx +read_file(File) -> + cast({read_file,File}). %%%----------------------------------------------------------------- -%%% The following functions are called when menu items are clicked. -general_info(_Env,_Input) -> +%%% The following functions are called when the different tabs are +%%% created +general_info() -> call(general_info). -processes(SessionId,_Env,_Input) -> - call({procs_summary,SessionId}). -ports(SessionId,_Env,_Input) -> - call({ports,SessionId}). -ets_tables(SessionId,_Env,Input) -> - call({ets_tables,SessionId,Input}). -internal_ets_tables(_Env,_Input) -> +processes() -> + call(procs_summary). +ports() -> + call(ports). +ets_tables(Owner) -> + call({ets_tables,Owner}). +internal_ets_tables() -> call(internal_ets_tables). -timers(SessionId,_Env,Input) -> - call({timers,SessionId,Input}). -fun_table(SessionId,_Env,_Input) -> - call({funs,SessionId}). -atoms(SessionId,_Env,_Input) -> - call({atoms,SessionId}). -dist_info(_Env,_Input) -> +timers(Owner) -> + call({timers,Owner}). +funs() -> + call(funs). +atoms() -> + call(atoms). +dist_info() -> call(dist_info). -loaded_modules(SessionId,_Env,_Input) -> - call({loaded_mods,SessionId}). -loaded_mod_details(_Env,Input) -> - call({loaded_mod_details,Input}). -memory(_Env,_Input) -> +node_info(Channel) -> + call({node_info,Channel}). +loaded_modules() -> + call(loaded_mods). +loaded_mod_details(Mod) -> + call({loaded_mod_details,Mod}). +memory() -> call(memory). -allocated_areas(_Env,_Input) -> +allocated_areas() -> call(allocated_areas). -allocator_info(_Env,_Input) -> +allocator_info() -> call(allocator_info). -hash_tables(_Env,_Input) -> +hash_tables() -> call(hash_tables). -index_tables(_Env,_Input) -> +index_tables() -> call(index_tables). %%%----------------------------------------------------------------- %%% Called when a link to a process (Pid) is clicked. -proc_details(_Env,Input) -> - call({proc_details,Input}). - -%%%----------------------------------------------------------------- -%%% Called when one of the headings in the process summary table are -%%% clicked. It sorts the processes by the clicked heading. -sort_procs(SessionId,_Env,Input) -> - call({sort_procs,SessionId,Input}). +proc_details(Pid) -> + call({proc_details,Pid}). %%%----------------------------------------------------------------- %%% Called when a link to a port is clicked. -port(_Env,Input) -> - call({port,Input}). - -%%%----------------------------------------------------------------- -%%% Called when the "Expand" link in a call stack (Last Calls) is -%%% clicked. -expand(_Env,Input) -> - call({expand,Input}). - -%%%----------------------------------------------------------------- -%%% Called when the "Expand" link in a stack dump, message queue or -%%% dictionary is clicked. -expand_memory(_Env,Input) -> - call({expand_memory,Input}). +port(Id) -> + call({port,Id}). %%%----------------------------------------------------------------- -%%% Called when "<< xxx bytes>>" link in a stack dump, message queue or -%%% dictionary is clicked. -expand_binary(_Env,Input) -> - call({expand_binary,Input}). - -%%%----------------------------------------------------------------- -%%% Called on regular intervals while waiting for a dump to be read -redirect(_Env,_Input) -> - call(redirect). +%%% Called when "<< xxx bytes>>" link is clicket to open a new window +%%% displaying the whole binary. +expand_binary(Pos) -> + call({expand_binary,Pos}). %%==================================================================== %% Server functions @@ -461,7 +297,6 @@ redirect(_Env,_Input) -> %% {stop, Reason} %%-------------------------------------------------------------------- init([]) -> - ets:new(cdv_menu_table,[set,named_table,{keypos,#menu_item.index},public]), ets:new(cdv_dump_index_table,[ordered_set,named_table,public]), {ok, #state{}}. @@ -475,223 +310,125 @@ init([]) -> %% {stop, Reason, Reply, State} | (terminate/2 is called) %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- -handle_call(start_page,_From,State=#state{file=undefined,bg_status=undefined})-> - Reply = crashdump_viewer_html:welcome(), - {reply,Reply,State}; -handle_call(start_page, _From, State=#state{file=undefined,bg_status={done,Page}}) -> - {reply,Page,State}; -handle_call(start_page, _From, State=#state{file=undefined,bg_status=Status}) -> - Reply = crashdump_viewer_html:redirect(Status), - {reply,Reply,State}; -handle_call(start_page, _From, State) -> - Reply = crashdump_viewer_html:start_page(), - {reply,Reply,State}; -handle_call({read_file,Input}, _From, _State) -> - {ok,File} = get_value("path",httpd:parse_query(Input)), - spawn_link(fun() -> read_file(File) end), - Status = background_status(reading,File), - Reply = crashdump_viewer_html:redirect(Status), - {reply, Reply, #state{bg_status=Status}}; -handle_call(redirect,_From, State=#state{bg_status={done,Page}}) -> - {reply, Page, State#state{bg_status=undefined}}; -handle_call(redirect,_From, State=#state{bg_status=Status}) -> - Reply = crashdump_viewer_html:redirect(Status), - {reply, Reply, State}; -handle_call(filename_frame,_From,State=#state{file=File}) -> - Reply = crashdump_viewer_html:filename_frame(File), - {reply,Reply,State}; -handle_call(initial_info_frame,_From,State=#state{file=File}) -> +handle_call(general_info,_From,State=#state{file=File}) -> GenInfo = general_info(File), - [{DumpVsn,_}] = lookup_index(?erl_crash_dump), NumAtoms = GenInfo#general_info.num_atoms, - {WS,SH} = parse_vsn_str(GenInfo#general_info.system_vsn,4,false), - NumProcs = list_to_integer(GenInfo#general_info.num_procs), - ProcsSummary = - if NumProcs > ?max_sort_process_num -> too_many; - true -> State#state.procs_summary - end, - NewState = State#state{dump_vsn=[list_to_integer(L) || - L<-string:tokens(DumpVsn,".")], - shared_heap=SH, - wordsize=WS, - num_atoms=NumAtoms, - procs_summary=ProcsSummary}, - Reply = crashdump_viewer_html:general_info(GenInfo), - {reply,Reply,NewState}; -handle_call({toggle,Input},_From,State) -> - {ok,Index} = get_value("index",httpd:parse_query(Input)), - do_toggle(list_to_integer(Index)), - Reply = crashdump_viewer_html:menu_frame(), - {reply,Reply,State}; -handle_call({expand,Input},_From,State=#state{file=File}) -> - [{"pos",Pos},{"size",Size},{"what",What},{"truncated",Truncated}] = - httpd:parse_query(Input), - Expanded = get_expanded(File,list_to_integer(Pos),list_to_integer(Size)), - TruncText = if Truncated=="true" -> "WARNING: This term is truncated!\n\n"; - true -> "" - end, - Reply = - case {Truncated,What} of - {_,"LastCalls"} -> - LastCalls = replace_all($ ,$\n,Expanded,[]), - crashdump_viewer_html:info_page(What,[TruncText,LastCalls]); - {_,"StackDump"} -> - crashdump_viewer_html:info_page(What,[TruncText,Expanded]); - {"false",_} -> - crashdump_viewer_html:pretty_info_page(What,Expanded); - {"true",_} -> - crashdump_viewer_html:info_page(What,[TruncText,Expanded]) - end, - {reply,Reply,State}; -handle_call({expand_memory,Input},_From,State=#state{file=File,binaries=B}) -> - [{"pid",Pid},{"what",What}] = httpd:parse_query(Input), - Reply = - case truncated_warning([{?proc,Pid}]) of - [] -> - Expanded = expand_memory(File,What,Pid,B), - crashdump_viewer_html:expanded_memory(What,Expanded); - _TW -> - Info = - "The crashdump is truncated in the middle of this " - "process' memory information, so this information " - "can not be extracted.", - crashdump_viewer_html:info_page(What,Info) - end, - {reply,Reply,State}; -handle_call({expand_binary,Input},_From,State=#state{file=File}) -> - [{"pos",Pos0}] = httpd:parse_query(Input), - Pos = list_to_integer(Pos0), + 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."]; + false -> [] + end, + {reply,{ok,GenInfo,TW},State#state{wordsize=WS, num_atoms=NumAtoms}}; +handle_call({expand_binary,{Offset,Size,Pos}},_From,State=#state{file=File}) -> Fd = open(File), pos_bof(Fd,Pos), - {Bin,_Line} = get_binary(val(Fd)), + {Bin,_Line} = get_binary(Offset,Size,val(Fd)), close(Fd), - Reply=crashdump_viewer_html:expanded_binary(io_lib:format("~p",[Bin])), - {reply,Reply,State}; -handle_call(general_info,_From,State=#state{file=File}) -> - GenInfo=general_info(File), - Reply = crashdump_viewer_html:general_info(GenInfo), - {reply,Reply,State}; -handle_call({procs_summary,SessionId},_From,State) -> - TW = truncated_warning([?proc]), - NewState = procs_summary(SessionId,TW,"pid",State#state{sorted=undefined}), - {reply,ok,NewState}; -handle_call({sort_procs,SessionId,Input}, _From, State) -> - {ok,Sort} = get_value("sort",httpd:parse_query(Input)), + {reply,{ok,Bin},State}; +handle_call(procs_summary,_From,State=#state{file=File,wordsize=WS}) -> TW = truncated_warning([?proc]), - NewState = procs_summary(SessionId,TW,Sort,State), - {reply,ok,NewState}; -handle_call({proc_details,Input},_From,State=#state{file=File,shared_heap=SH}) -> - {ok,Pid} = get_value("pid",httpd:parse_query(Input)), + Procs = procs_summary(File,WS), + {reply,{ok,Procs,TW},State}; +handle_call({proc_details,Pid},_From, + State=#state{file=File,wordsize=WS,dump_vsn=DumpVsn,binaries=B})-> Reply = - case get_proc_details(File,Pid,State#state.dump_vsn) of - {ok,Proc} -> - TW = truncated_warning([{?proc,Pid}]), - crashdump_viewer_html:proc_details(Pid,Proc,TW,SH); - {other_node,Node} -> - TW = truncated_warning([?visible_node, - ?hidden_node, - ?not_connected]), - crashdump_viewer_html:nods(Node,TW); - not_found -> - crashdump_viewer_html:info_page(["Could not find process: ", - Pid],?space) + case get_proc_details(File,Pid,WS,DumpVsn,B) of + {ok,Proc,TW} -> + {ok,Proc,TW}; + Other -> + {error,Other} end, {reply, Reply, State}; -handle_call({port,Input},_From,State=#state{file=File}) -> - {ok,P} = get_value("port",httpd:parse_query(Input)), - Id = [$#|P], +handle_call({port,Id},_From,State=#state{file=File}) -> Reply = case get_port(File,Id) of {ok,PortInfo} -> TW = truncated_warning([{?port,Id}]), - crashdump_viewer_html:port(Id,PortInfo,TW); - {other_node,Node} -> - TW = truncated_warning([?visible_node, - ?hidden_node, - ?not_connected]), - crashdump_viewer_html:nods(Node,TW); - not_found -> - crashdump_viewer_html:info_page( - ["Could not find port: ",Id],?space) + {ok,PortInfo,TW}; + Other -> + {error,Other} end, {reply,Reply,State}; -handle_call({ports,SessionId},_From,State=#state{file=File}) -> +handle_call(ports,_From,State=#state{file=File}) -> TW = truncated_warning([?port]), - get_ports(SessionId,File,TW), - {reply,ok,State}; -handle_call({ets_tables,SessionId,Input},_From,State=#state{file=File,wordsize=WS}) -> - {Pid,Heading} = - case get_value("pid",httpd:parse_query(Input)) of - {ok,P} -> - {P,["ETS Tables for Process ",P]}; - error -> - {'$2',"ETS Table Information"} + Ports = get_ports(File), + {reply,{ok,Ports,TW},State}; +handle_call({ets_tables,Pid0},_From,State=#state{file=File,wordsize=WS}) -> + Pid = + case Pid0 of + all -> '$2'; + _ -> Pid0 end, TW = truncated_warning([?ets]), - get_ets_tables(SessionId,File,Heading,TW,Pid,WS), - {reply,ok,State}; + Ets = get_ets_tables(File,Pid,WS), + {reply,{ok,Ets,TW},State}; handle_call(internal_ets_tables,_From,State=#state{file=File,wordsize=WS}) -> InternalEts = get_internal_ets_tables(File,WS), TW = truncated_warning([?internal_ets]), - Reply = crashdump_viewer_html:internal_ets_tables(InternalEts,TW), - {reply,Reply,State}; -handle_call({timers,SessionId,Input},_From,State=#state{file=File}) -> - {Pid,Heading} = - case get_value("pid",httpd:parse_query(Input)) of - {ok,P} -> {P,["Timers for Process ",P]}; - error -> {'$2',"Timer Information"} + {reply,{ok,InternalEts,TW},State}; +handle_call({timers,Pid0},_From,State=#state{file=File}) -> + Pid = + case Pid0 of + all -> '$2'; + _ -> Pid0 end, TW = truncated_warning([?timer]), - get_timers(SessionId,File,Heading,TW,Pid), - {reply,ok,State}; + Timers = get_timers(File,Pid), + {reply,{ok,Timers,TW},State}; handle_call(dist_info,_From,State=#state{file=File}) -> - Nods=nods(File), TW = truncated_warning([?visible_node,?hidden_node,?not_connected]), - Reply = crashdump_viewer_html:nods(Nods,TW), + Nods=nods(File), + {reply,{ok,Nods,TW},State}; +handle_call({node_info,Channel},_From,State=#state{file=File}) -> + Reply = + case get_node(File,Channel) of + {ok,Nod} -> + TW = truncated_warning([?visible_node, + ?hidden_node, + ?not_connected]), + {ok,Nod,TW}; + {error,Other} -> + {error,Other} + end, {reply,Reply,State}; -handle_call({loaded_mods,SessionId},_From,State=#state{file=File}) -> +handle_call(loaded_mods,_From,State=#state{file=File}) -> TW = truncated_warning([?mod]), - loaded_mods(SessionId,File,TW), - {reply,ok,State}; -handle_call({loaded_mod_details,Input},_From,State=#state{file=File}) -> - {ok,Mod} = get_value("mod",httpd:parse_query(Input)), - ModInfo = get_loaded_mod_details(File,Mod), + {_CC,_OC,Mods} = loaded_mods(File), + {reply,{ok,Mods,TW},State}; +handle_call({loaded_mod_details,Mod},_From,State=#state{file=File}) -> TW = truncated_warning([{?mod,Mod}]), - Reply = crashdump_viewer_html:loaded_mod_details(ModInfo,TW), - {reply,Reply,State}; -handle_call({funs,SessionId},_From,State=#state{file=File}) -> + ModInfo = get_loaded_mod_details(File,Mod), + {reply,{ok,ModInfo,TW},State}; +handle_call(funs,_From,State=#state{file=File}) -> TW = truncated_warning([?fu]), - funs(SessionId,File,TW), - {reply,ok,State}; -handle_call({atoms,SessionId},_From,State=#state{file=File,num_atoms=Num}) -> - TW = truncated_warning([?atoms,?num_atoms]), - atoms(SessionId,File,TW,Num), - {reply,ok,State}; + Funs = funs(File), + {reply,{ok,Funs,TW},State}; +handle_call(atoms,_From,State=#state{file=File,num_atoms=NumAtoms0}) -> + TW = truncated_warning([?atoms]), + NumAtoms = try list_to_integer(NumAtoms0) catch error:badarg -> -1 end, + Atoms = atoms(File,NumAtoms), + {reply,{ok,Atoms,TW},State}; handle_call(memory,_From,State=#state{file=File}) -> Memory=memory(File), TW = truncated_warning([?memory]), - Reply = crashdump_viewer_html:memory(Memory,TW), - {reply,Reply,State}; + {reply,{ok,Memory,TW},State}; handle_call(allocated_areas,_From,State=#state{file=File}) -> AllocatedAreas=allocated_areas(File), TW = truncated_warning([?allocated_areas]), - Reply = crashdump_viewer_html:allocated_areas(AllocatedAreas,TW), - {reply,Reply,State}; + {reply,{ok,AllocatedAreas,TW},State}; handle_call(allocator_info,_From,State=#state{file=File}) -> SlAlloc=allocator_info(File), TW = truncated_warning([?allocator]), - Reply = crashdump_viewer_html:allocator_info(SlAlloc,TW), - {reply,Reply,State}; + {reply,{ok,SlAlloc,TW},State}; handle_call(hash_tables,_From,State=#state{file=File}) -> HashTables=hash_tables(File), TW = truncated_warning([?hash_table,?index_table]), - Reply = crashdump_viewer_html:hash_tables(HashTables,TW), - {reply,Reply,State}; + {reply,{ok,HashTables,TW},State}; handle_call(index_tables,_From,State=#state{file=File}) -> IndexTables=index_tables(File), TW = truncated_warning([?hash_table,?index_table]), - Reply = crashdump_viewer_html:index_tables(IndexTables,TW), - {reply,Reply,State}. + {reply,{ok,IndexTables,TW},State}. @@ -702,11 +439,18 @@ handle_call(index_tables,_From,State=#state{file=File}) -> %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- -handle_cast({background_done,{Page,File,Binaries},Dict}, State) -> - lists:foreach(fun({Key,Val}) -> put(Key,Val) end, Dict), - {noreply, State#state{file=File,binaries=Binaries,bg_status={done,Page}}}; -handle_cast({background_status,Status}, State) -> - {noreply, State#state{bg_status=Status}}. +handle_cast({read_file,File}, _State) -> + case do_read_file(File) of + {ok,Binaries,DumpVsn} -> + observer_lib:report_progress({ok,done}), + {noreply, #state{file=File,binaries=Binaries,dump_vsn=DumpVsn}}; + Error -> + end_progress(Error), + {noreply, #state{}} + end; +handle_cast(stop,State) -> + {stop,normal,State}. + %%-------------------------------------------------------------------- %% Function: handle_info/2 @@ -791,24 +535,6 @@ compare_pid("<"++Id,"<"++OtherId) -> compare_pid(_,_) -> false. -background_status(Action,File) -> - SizeInfo = filesizeinfo(File), - background_status(Action,File,SizeInfo). - -background_status(processing,File,SizeInfo) -> - "Processing " ++ File ++ SizeInfo; -background_status(reading,File,SizeInfo) -> - "Reading file " ++ File ++ SizeInfo. - -filesizeinfo(File) -> - case file:read_file_info(File) of - {ok,#file_info{size=Size}} -> - " (" ++ integer_to_list(Size) ++ " bytes)"; - _X -> - "" - end. - - open(File) -> {ok,Fd} = file:open(File,[read,read_ahead,raw,binary]), Fd. @@ -861,6 +587,18 @@ get_chunk(Fd) -> {ok,Bin} end. +%% Read and report progress +progress_read(Fd) -> + {R,Bytes} = + case read(Fd) of + {ok,Bin} -> + {{ok,Bin},byte_size(Bin)}; + Other -> + {Other,0} + end, + update_progress(Bytes), + R. + read(Fd) -> file:read(Fd,?chunk_size). @@ -962,73 +700,30 @@ get_rest_of_line_1(Fd, <<>>, Acc) -> eof -> {eof,lists:reverse(Acc)} end. -count_rest_of_line(Fd) -> +get_lines_to_empty(Fd) -> case get_chunk(Fd) of - {ok,Bin} -> count_rest_of_line(Fd,Bin,0); - eof -> {eof,0} - end. -count_rest_of_line(Fd,<<$\n:8,Bin/binary>>,N) -> - put_chunk(Fd,Bin), - N; -count_rest_of_line(Fd,<<$\r:8,Bin/binary>>,N) -> - count_rest_of_line(Fd,Bin,N); -count_rest_of_line(Fd,<<_Char:8,Bin/binary>>,N) -> - count_rest_of_line(Fd,Bin,N+1); -count_rest_of_line(Fd,<<>>,N) -> - case get_chunk(Fd) of - {ok,Bin} -> count_rest_of_line(Fd,Bin,N); - eof -> {eof,N} - end. - -get_n_lines_of_tag(Fd,N) -> - case get_chunk(Fd) of - {ok,Bin} -> - {AllOrPart,Rest,Lines} = get_n_lines_of_tag(Fd,N,Bin,[]), - {AllOrPart,N-Rest,Lines}; + {ok,Bin} -> + get_lines_to_empty(Fd,Bin,[],[]); eof -> - empty - end. -get_n_lines_of_tag(Fd,N,<<"\n=",_/binary>>=Bin,Acc) -> - put_chunk(Fd,Bin), - {all,N-1,lists:reverse(Acc)}; -get_n_lines_of_tag(Fd,0,Bin,Acc) -> - put_chunk(Fd,Bin), - {part,0,lists:reverse(Acc)}; -get_n_lines_of_tag(Fd,N,<<$\n:8,Bin/binary>>,Acc) -> - get_n_lines_of_tag(Fd,N-1,Bin,[$\n|Acc]); -get_n_lines_of_tag(Fd,N,<<$\r:8,Bin/binary>>,Acc) -> - get_n_lines_of_tag(Fd,N,Bin,Acc); -get_n_lines_of_tag(Fd,N,<<Char:8,Bin/binary>>,Acc) -> - get_n_lines_of_tag(Fd,N,Bin,[Char|Acc]); -get_n_lines_of_tag(Fd,N,<<>>,Acc) -> - case get_chunk(Fd) of - {ok,Bin} -> - get_n_lines_of_tag(Fd,N,Bin,Acc); - eof -> - case Acc of - [$\n|_] -> - {all,N,lists:reverse(Acc)}; - _ -> - {all,N-1,lists:reverse(Acc)} - end - end. - -count_rest_of_tag(Fd) -> - case get_chunk(Fd) of - {ok,Bin} -> count_rest_of_tag(Fd,Bin,0); - eof -> 0 + [] end. -count_rest_of_tag(Fd,<<"\n=",Bin/binary>>,N) -> +get_lines_to_empty(Fd,<<$\n:8,Bin/binary>>,[],Lines) -> put_chunk(Fd,Bin), - N; -count_rest_of_tag(Fd,<<$\r:8,Bin/binary>>,N) -> - count_rest_of_tag(Fd,Bin,N); -count_rest_of_tag(Fd,<<_Char:8,Bin/binary>>,N) -> - count_rest_of_tag(Fd,Bin,N+1); -count_rest_of_tag(Fd,<<>>,N) -> + lists:reverse(Lines); +get_lines_to_empty(Fd,<<$\n:8,Bin/binary>>,Acc,Lines) -> + get_lines_to_empty(Fd,Bin,[],[lists:reverse(Acc)|Lines]); +get_lines_to_empty(Fd,<<$\r:8,Bin/binary>>,Acc,Lines) -> + get_lines_to_empty(Fd,Bin,Acc,Lines); +get_lines_to_empty(Fd,<<$\s:8,Bin/binary>>,[],Lines) -> + get_lines_to_empty(Fd,Bin,[],Lines); +get_lines_to_empty(Fd,<<Char:8,Bin/binary>>,Acc,Lines) -> + get_lines_to_empty(Fd,Bin,[Char|Acc],Lines); +get_lines_to_empty(Fd,<<>>,Acc,Lines) -> case get_chunk(Fd) of - {ok,Bin} -> count_rest_of_tag(Fd,Bin,N); - eof -> N + {ok,Bin} -> + get_lines_to_empty(Fd,Bin,Acc,Lines); + eof -> + lists:reverse(Lines,[lists:reverse(Acc)]) end. split(Str) -> @@ -1046,150 +741,32 @@ split(Char,[H|T],Acc) -> split(_Char,[],Acc) -> {lists:reverse(Acc),[]}. -size_or_term(Fd) -> - size_or_term(Fd,get(pos)). -size_or_term(Fd,Pos) -> - case count_rest_of_line(Fd) of - {eof,Size} -> - {size,true,Size,Pos}; - Size when Size > ?max_display_size -> - {size,false,Size,Pos}; - _Size -> - {ok,Pos} = pos_bof(Fd,Pos), - val(Fd) - end. - %%%----------------------------------------------------------------- %%% -get_value(Key,List) -> - case lists:keysearch(Key,1,List) of - {value,{Key,Value}} -> {ok,Value}; - false -> error - end. - -parse_vsn_str([],WS,false) -> - %% If the log is translated, crashdump_translate might have written - %% shared_heap=true in dictionary. - case erase(shared_heap) of - true -> {WS,true}; - _ -> {WS,false} - end; -parse_vsn_str([],WS,SH) -> - {WS,SH}; -parse_vsn_str(Str,WS,SH) -> +parse_vsn_str([],WS) -> + WS; +parse_vsn_str(Str,WS) -> case Str of - "[64-bit]" ++ Rest -> - case SH of - false -> - parse_vsn_str(Rest,8,false); - _ -> - {8,SH} - end; - "[shared heap]" ++ Rest -> - case WS of - 4 -> - parse_vsn_str(Rest,WS,true); - _ -> - {WS,true} - end; + "[64-bit]" ++ _Rest -> + 8; [_Char|Rest] -> - parse_vsn_str(Rest,WS,SH) + parse_vsn_str(Rest,WS) end. %%%----------------------------------------------------------------- -%%% -initial_menu() -> - insert_items( - [menu_item(0, {"./general_info","General information"},0), - menu_item(0, {"./processes","Processes"}, 0), - menu_item(0, {"./ports","Ports"}, 0), - menu_item(2, "ETS tables", 0), - menu_item(0, {"./ets_tables","ETS tables"}, 1), - menu_item(0, {"./internal_ets_tables","Internal ETS tables"}, 1), - menu_item(0, {"./timers","Timers"}, 0), - menu_item(0, {"./fun_table","Fun table"}, 0), - menu_item(0, {"./atoms","Atoms"}, 0), - menu_item(0, {"./dist_info","Distribution information"}, 0), - menu_item(0, {"./loaded_modules","Loaded modules"}, 0), - menu_item(2, "Internal Tables", 0), - menu_item(0, {"./hash_tables","Hash tables"}, 1), - menu_item(0, {"./index_tables","Index tables"}, 1), - menu_item(3, "Memory information", 0), - menu_item(0, {"./memory","Memory"}, 1), - menu_item(0, {"./allocated_areas","Allocated areas"}, 1), - menu_item(0, {"./allocator_info","Allocator information"}, 1), - menu_item(2, "Documentation", 0), - menu_item(0, {"/crashdump_doc/crashdump_help.html", - "Crashdump Viewer help"}, 1,"doc"), - menu_item(0, {"/crashdump_erts_doc/crash_dump.html", - "How to interpret Erlang crashdumps"}, 1,"doc")]). - -menu_item(Children,Text,Depth) -> - menu_item(Children,Text,Depth,"main"). -menu_item(Children,Text,Depth,Target) -> - #menu_item{picture=get_pic(Children), - text=Text, - depth=Depth, - children=Children, - state=if Depth==0 -> true; true -> false end, - target=Target}. - -insert_items(Items) -> - insert_items(Items,1). -insert_items([Item|Items],Index) -> - ets:insert(cdv_menu_table,Item#menu_item{index=Index}), - insert_items(Items,Index+1); -insert_items([],_) -> - ok. - -get_pic(0) -> - ""; -get_pic(_) -> - "/crashdump_viewer/collapsd.gif". - -do_toggle(Index) -> - [Item]= ets:lookup(cdv_menu_table,Index), - case toggle_children(Index,Index+Item#menu_item.children, - Item#menu_item.depth+1,undefined) of - true -> - ets:insert(cdv_menu_table, - Item#menu_item{picture= - "/crashdump_viewer/exploded.gif"}); - false -> - ets:insert(cdv_menu_table, - Item#menu_item{picture= - "/crashdump_viewer/collapsd.gif"}) - end. - -toggle_children(Index,Max,_Depth,ToggleState) when Index>Max-> - ToggleState; -toggle_children(Index,Max,Depth,ToggleState) -> - case ets:lookup(cdv_menu_table,Index+1) of - [#menu_item{depth=Depth}=Child] -> - NewState = not Child#menu_item.state, - ets:insert(cdv_menu_table,Child#menu_item{state=NewState}), - toggle_children(Index+1,Max,Depth,NewState); - _ -> - toggle_children(Index+1,Max,Depth,ToggleState) - end. - -%%%----------------------------------------------------------------- %%% Traverse crash dump and insert index in table for each heading -%%% -%%% This function is executed in a background process in order to -%%% avoid a timeout in the web browser. The browser displays "Please -%%% wait..." while this is going on. %%% -%%% Variable written to process dictionary in this function are copied -%%% to the crashdump_viewer_server when the function is completed (see -%%% background_done/1). -read_file(File) -> +%%% Progress is reported during the time and MUST be checked with +%%% crashdump_viewer:get_progress/0 until it returns {ok,done}. +do_read_file(File) -> case file:read_file_info(File) of - {ok,#file_info{type=regular,access=FileA}} when FileA=:=read; - FileA=:=read_write -> + {ok,#file_info{type=regular, + access=FileA, + size=Size}} when FileA=:=read; FileA=:=read_write -> Fd = open(File), - case read(Fd) of + init_progress("Reading file",Size), + case progress_read(Fd) of {ok,<<$=:8,TagAndRest/binary>>} -> {Tag,Id,Rest,N1} = tag(Fd,TagAndRest,1), case Tag of @@ -1197,41 +774,40 @@ read_file(File) -> reset_index_table(), insert_index(Tag,Id,N1+1), put(last_tag,{Tag,""}), - Status = background_status(processing,File), - background_status(Status), indexify(Fd,Rest,N1), + end_progress(), check_if_truncated(), - initial_menu(), - Binaries = read_binaries(Fd), - R = crashdump_viewer_html:start_page(), + [{DumpVsn0,_}] = lookup_index(?erl_crash_dump), + DumpVsn = [list_to_integer(L) || + L<-string:tokens(DumpVsn0,".")], + Binaries = read_binaries(Fd,DumpVsn), close(Fd), - background_done({R,File,Binaries}); + {ok,Binaries,DumpVsn}; _Other -> - R = crashdump_viewer_html:error( + R = io_lib:format( "~s is not an Erlang crash dump~n", [File]), close(Fd), - background_done({R,undefined,undefined}) + {error,R} end; {ok,<<"<Erlang crash dump>",_Rest/binary>>} -> %% old version - no longer supported - R = crashdump_viewer_html:error( + R = io_lib:format( "The crashdump ~s is in the pre-R10B format, " "which is no longer supported.~n", - [File]), + [File]), close(Fd), - background_done({R,undefined,undefined}); + {error,R}; _Other -> - R = crashdump_viewer_html:error( + R = io_lib:format( "~s is not an Erlang crash dump~n", [File]), close(Fd), - background_done({R,undefined,undefined}) + {error,R} end; _other -> - R = crashdump_viewer_html:error("~s is not an Erlang crash dump~n", - [File]), - background_done({R,undefined,undefined}) + R = io_lib:format("~s is not an Erlang crash dump~n",[File]), + {error,R} end. indexify(Fd,Bin,N) -> @@ -1244,7 +820,7 @@ indexify(Fd,Bin,N) -> put(last_tag,{Tag,Id}), indexify(Fd,Rest,N1); nomatch -> - case read(Fd) of + case progress_read(Fd) of {ok,Chunk0} when is_binary(Chunk0) -> {Chunk,N1} = case binary:last(Bin) of @@ -1272,7 +848,7 @@ tag(Fd,<<Char:8,Rest/binary>>,N,Gat,Di,tag) -> tag(Fd,<<Char:8,Rest/binary>>,N,Gat,Di,id) -> tag(Fd,Rest,N+1,Gat,[Char|Di],id); tag(Fd,<<>>,N,Gat,Di,Now) -> - case read(Fd) of + case progress_read(Fd) of {ok,Chunk} when is_binary(Chunk) -> tag(Fd,Chunk,N,Gat,Di,Now); eof -> @@ -1304,21 +880,12 @@ find_truncated_proc({Tag,Pid}) -> is_proc_tag(Tag) when Tag==?proc; Tag==?proc_dictionary; Tag==?proc_messages; - Tag==?debug_proc_dictionary; Tag==?proc_stack; Tag==?proc_heap -> true; is_proc_tag(_) -> false. -%%% Inform the crashdump_viewer_server that a background job is completed. -background_done(Result) -> - Dict = get(), - cast({background_done,Result,Dict}). - -background_status(Status) -> - cast({background_status,Status}). - %%%----------------------------------------------------------------- %%% Functions for reading information from the dump general_info(File) -> @@ -1330,22 +897,18 @@ general_info(File) -> WholeLine -> WholeLine end, - GI0 = get_general_info(Fd,#general_info{created=Created}), - GI = case GI0#general_info.num_atoms of - ?space -> GI0#general_info{num_atoms=get_num_atoms(Fd)}; - _ -> GI0 - end, + GI = get_general_info(Fd,#general_info{created=Created}), {MemTot,MemMax} = case lookup_index(?memory) of [{_,MemStart}] -> pos_bof(Fd,MemStart), Memory = get_meminfo(Fd,[]), - Tot = case lists:keysearch("total",1,Memory) of + Tot = case lists:keysearch(total,1,Memory) of {value,{_,T}} -> T; false -> "" end, - Max = case lists:keysearch("maximum",1,Memory) of + Max = case lists:keysearch(maximum,1,Memory) of {value,{_,M}} -> M; false -> "" end, @@ -1408,269 +971,210 @@ get_general_info(Fd,GenInfo) -> GenInfo end. -get_num_atoms(Fd) -> - case lookup_index(?hash_table,"atom_tab") of - [{_,Pos}] -> - pos_bof(Fd,Pos), - skip_rest_of_line(Fd), % size - skip_rest_of_line(Fd), % used - case line_head(Fd) of - "objs" -> - val(Fd); - _1 -> - get_num_atoms2() - end; - [] -> - get_num_atoms2() - end. -get_num_atoms2() -> - case lookup_index(?num_atoms) of - [] -> - ?space; - [{NA,_Pos}] -> - %% If dump is translated this will exist - case get(truncated) of - true -> - [NA," (visible in dump)"]; % might be more - false -> - NA - end - end. - count() -> {count_index(?proc),count_index(?ets),count_index(?fu),count_index(?timer)}. %%----------------------------------------------------------------- %% Page with all processes -%% -%% If there are less than ?max_sort_process_num processes in the dump, -%% we will store the list of processes in the server state in order to -%% allow sorting according to the different columns of the -%% table. Since ?max_sort_process_num=:=?items_chunk_size, there will -%% never be more than one chunk in this case. -%% -%% If there are more than ?max_sort_process_num processes in the dump, -%% no sorting will be allowed, and the processes must be read (chunk -%% by chunk) from the file each time the page is opened. This is to -%% avoid really big data in the server state. -procs_summary(SessionId,TW,_,State=#state{procs_summary=too_many}) -> - chunk_page(SessionId,State#state.file,TW,?proc,processes, - {no_sort,State#state.shared_heap,State#state.dump_vsn}, - procs_summary_parsefun()), - State; -procs_summary(SessionId,TW,SortOn,State) -> - ProcsSummary = - case State#state.procs_summary of - undefined -> % first time - read from file - Fd = open(State#state.file), - {PS,_}=lookup_and_parse_index_chunk(first_chunk_pointer(?proc), - Fd,procs_summary_parsefun()), - close(Fd), - PS; - PS -> - PS - end, - {SortedPS,NewSorted} = do_sort_procs(SortOn,ProcsSummary,State), - HtmlInfo = - crashdump_viewer_html:chunk_page(processes,SessionId,TW, - {SortOn, - State#state.shared_heap, - State#state.dump_vsn}, - SortedPS), - crashdump_viewer_html:chunk(SessionId,done,HtmlInfo), - State#state{procs_summary=ProcsSummary,sorted=NewSorted}. - -procs_summary_parsefun() -> - fun(Fd,Pid) -> - get_procinfo(Fd,fun main_procinfo/4,#proc{pid=Pid}) - end. +procs_summary(File,WS) -> + ParseFun = fun(Fd,Pid) -> + Proc = get_procinfo(Fd,fun main_procinfo/5, + #proc{pid=list_to_pid(Pid)},WS), + case Proc#proc.memory of + undefined -> Proc#proc{memory=Proc#proc.stack_heap}; + _ -> Proc + end + end, + lookup_and_parse_index(File,?proc,ParseFun,"processes"). %%----------------------------------------------------------------- %% Page with one process -get_proc_details(File,Pid,DumpVsn) -> +get_proc_details(File,Pid,WS,DumpVsn,Binaries) -> case lookup_index(?proc,Pid) of [{_,Start}] -> Fd = open(File), - pos_bof(Fd,Start), - Proc0 = - case DumpVsn of - [0,0] -> - %% Old version (translated) - #proc{pid=Pid}; - _ -> - #proc{pid=Pid, - stack_dump=if_exist(?proc_stack,Pid), - msg_q=if_exist(?proc_messages,Pid), - dict=if_exist(?proc_dictionary,Pid), - debug_dict=if_exist(?debug_proc_dictionary,Pid)} + {{Stack,MsgQ,Dict},TW} = + case truncated_warning([{?proc,Pid}]) of + [] -> + {expand_memory(Fd,Pid,DumpVsn,Binaries),[]}; + TW0 -> + {{[],[],[]},TW0} end, - Proc = get_procinfo(Fd,fun all_procinfo/4,Proc0), + pos_bof(Fd,Start), + Proc0 = #proc{pid=Pid,stack_dump=Stack,msg_q=MsgQ,dict=Dict}, + Proc = get_procinfo(Fd,fun all_procinfo/5,Proc0,WS), close(Fd), - {ok,Proc}; + {ok,Proc,TW}; _ -> - case maybe_other_node(File,Pid) of - {other_node,Type,Node} -> - Info = "The process you are searching for was residing on " - "a remote node. No process information is available. " - "Information about the remote node is show below.", - {other_node,{Type,Info,Node}}; - not_found -> - not_found - end - end. - -if_exist(Tag,Key) -> - case count_index(Tag,Key) of - 0 -> - Tag1 = - case is_proc_tag(Tag) of - true -> ?proc; - false -> Tag - end, - case truncated_here({Tag1,Key}) of - true -> truncated; - false -> ?space - end; - _ -> - expand + maybe_other_node(Pid) end. -get_procinfo(Fd,Fun,Proc) -> +get_procinfo(Fd,Fun,Proc,WS) -> case line_head(Fd) of "State" -> State = case val(Fd) of "Garbing" -> "Garbing\n(limited info)"; State0 -> State0 end, - get_procinfo(Fd,Fun,Proc#proc{state=State}); + get_procinfo(Fd,Fun,Proc#proc{state=State},WS); "Name" -> - get_procinfo(Fd,Fun,Proc#proc{name=val(Fd)}); + get_procinfo(Fd,Fun,Proc#proc{name=val(Fd)},WS); "Spawned as" -> IF = val(Fd), case Proc#proc.name of - ?space -> - get_procinfo(Fd,Fun,Proc#proc{name=IF,init_func=IF}); + undefined -> + get_procinfo(Fd,Fun,Proc#proc{name=IF,init_func=IF},WS); _ -> - get_procinfo(Fd,Fun,Proc#proc{init_func=IF}) + get_procinfo(Fd,Fun,Proc#proc{init_func=IF},WS) end; + "Message queue length" -> + %% stored as integer so we can sort on it + get_procinfo(Fd,Fun,Proc#proc{msg_q_len=list_to_integer(val(Fd))},WS); + "Reductions" -> + %% stored as integer so we can sort on it + get_procinfo(Fd,Fun,Proc#proc{reds=list_to_integer(val(Fd))},WS); + "Stack+heap" -> + %% stored as integer so we can sort on it + get_procinfo(Fd,Fun,Proc#proc{stack_heap= + list_to_integer(val(Fd))*WS},WS); + "Memory" -> + %% stored as integer so we can sort on it + get_procinfo(Fd,Fun,Proc#proc{memory=list_to_integer(val(Fd))},WS); + {eof,_} -> + Proc; % truncated file + Other -> + Fun(Fd,Fun,Proc,WS,Other) + end. + +main_procinfo(Fd,Fun,Proc,WS,LineHead) -> + case LineHead of + "=" ++ _next_tag -> + Proc; + "arity = " ++ _ -> + %%! Temporary workaround + get_procinfo(Fd,Fun,Proc,WS); + _Other -> + skip_rest_of_line(Fd), + get_procinfo(Fd,Fun,Proc,WS) + end. +all_procinfo(Fd,Fun,Proc,WS,LineHead) -> + case LineHead of + %% - START - moved from get_procinfo - "Spawned by" -> case val(Fd) of "[]" -> - get_procinfo(Fd,Fun,Proc); + get_procinfo(Fd,Fun,Proc,WS); Parent -> - get_procinfo(Fd,Fun,Proc#proc{parent=Parent}) + get_procinfo(Fd,Fun,Proc#proc{parent=Parent},WS) end; "Started" -> - get_procinfo(Fd,Fun,Proc#proc{start_time=val(Fd)}); + get_procinfo(Fd,Fun,Proc#proc{start_time=val(Fd)},WS); "Last scheduled in for" -> get_procinfo(Fd,Fun,Proc#proc{current_func= {"Last scheduled in for", - val(Fd)}}); + val(Fd)}},WS); "Current call" -> get_procinfo(Fd,Fun,Proc#proc{current_func={"Current call", - val(Fd)}}); - "Message queue length" -> - %% stored as integer so we can sort on it - get_procinfo(Fd,Fun,Proc#proc{msg_q_len=list_to_integer(val(Fd))}); - "Reductions" -> - %% stored as integer so we can sort on it - get_procinfo(Fd,Fun,Proc#proc{reds=list_to_integer(val(Fd))}); + val(Fd)}},WS); "Number of heap fragments" -> - get_procinfo(Fd,Fun,Proc#proc{num_heap_frag=val(Fd)}); + get_procinfo(Fd,Fun,Proc#proc{num_heap_frag=val(Fd)},WS); "Heap fragment data" -> - get_procinfo(Fd,Fun,Proc#proc{heap_frag_data=val(Fd)}); - Stack when Stack=:="Stack+heap"; Stack=:="Stack" -> - %% stored as integer so we can sort on it - get_procinfo(Fd,Fun,Proc#proc{stack_heap= - list_to_integer(val(Fd))}); + get_procinfo(Fd,Fun,Proc#proc{heap_frag_data=val(Fd)},WS); "OldHeap" -> - get_procinfo(Fd,Fun,Proc#proc{old_heap=val(Fd)}); + Bytes = list_to_integer(val(Fd))*WS, + get_procinfo(Fd,Fun,Proc#proc{old_heap=Bytes},WS); "Heap unused" -> - get_procinfo(Fd,Fun,Proc#proc{heap_unused=val(Fd)}); + Bytes = list_to_integer(val(Fd))*WS, + get_procinfo(Fd,Fun,Proc#proc{heap_unused=Bytes},WS); "OldHeap unused" -> - get_procinfo(Fd,Fun,Proc#proc{old_heap_unused=val(Fd)}); + Bytes = list_to_integer(val(Fd))*WS, + get_procinfo(Fd,Fun,Proc#proc{old_heap_unused=Bytes},WS); "New heap start" -> - get_procinfo(Fd,Fun,Proc#proc{new_heap_start=val(Fd)}); + get_procinfo(Fd,Fun,Proc#proc{new_heap_start=val(Fd)},WS); "New heap top" -> - get_procinfo(Fd,Fun,Proc#proc{new_heap_top=val(Fd)}); + get_procinfo(Fd,Fun,Proc#proc{new_heap_top=val(Fd)},WS); "Stack top" -> - get_procinfo(Fd,Fun,Proc#proc{stack_top=val(Fd)}); + get_procinfo(Fd,Fun,Proc#proc{stack_top=val(Fd)},WS); "Stack end" -> - get_procinfo(Fd,Fun,Proc#proc{stack_end=val(Fd)}); + get_procinfo(Fd,Fun,Proc#proc{stack_end=val(Fd)},WS); "Old heap start" -> - get_procinfo(Fd,Fun,Proc#proc{old_heap_start=val(Fd)}); + get_procinfo(Fd,Fun,Proc#proc{old_heap_start=val(Fd)},WS); "Old heap top" -> - get_procinfo(Fd,Fun,Proc#proc{old_heap_top=val(Fd)}); + get_procinfo(Fd,Fun,Proc#proc{old_heap_top=val(Fd)},WS); "Old heap end" -> - get_procinfo(Fd,Fun,Proc#proc{old_heap_end=val(Fd)}); - "Memory" -> - %% stored as integer so we can sort on it - get_procinfo(Fd,Fun,Proc#proc{memory=list_to_integer(val(Fd))}); - {eof,_} -> - Proc; % truncated file - Other -> - Fun(Fd,Fun,Proc,Other) - end. - -main_procinfo(Fd,Fun,Proc,LineHead) -> - case LineHead of - "Stack dump" -> - %% This is the last element in older dumps (DumpVsn=0.0) - Proc; - "=" ++ _next_tag -> - %% DumpVsn=0.1 or newer: No stack dump here - Proc; - "arity = " ++ _ -> - %%! Temporary workaround - get_procinfo(Fd,Fun,Proc); - _Other -> - skip_rest_of_line(Fd), - get_procinfo(Fd,Fun,Proc) - end. -all_procinfo(Fd,Fun,Proc,LineHead) -> - case LineHead of - "Message queue" -> - get_procinfo(Fd,Fun,Proc#proc{msg_q=size_or_term(Fd)}); + get_procinfo(Fd,Fun,Proc#proc{old_heap_end=val(Fd)},WS); + %% - END - moved from get_procinfo - "Last calls" -> - R = case size_or_term(Fd) of - SizeThing when is_tuple(SizeThing) -> - Proc#proc{last_calls=SizeThing}; - Term -> - Proc#proc{last_calls=replace_all($ ,$\n,Term,[])} - end, - get_procinfo(Fd,Fun,R); + get_procinfo(Fd,Fun,Proc#proc{last_calls=get_lines_to_empty(Fd)},WS); "Link list" -> - get_procinfo(Fd,Fun,Proc#proc{links=val(Fd)}); + {Links,Monitors,MonitoredBy} = parse_link_list(val(Fd),[],[],[]), + get_procinfo(Fd,Fun,Proc#proc{links=Links, + monitors=Monitors, + mon_by=MonitoredBy},WS); "Program counter" -> - get_procinfo(Fd,Fun,Proc#proc{prog_count=val(Fd)}); + get_procinfo(Fd,Fun,Proc#proc{prog_count=val(Fd)},WS); "CP" -> - get_procinfo(Fd,Fun,Proc#proc{cp=val(Fd)}); + get_procinfo(Fd,Fun,Proc#proc{cp=val(Fd)},WS); "arity = " ++ Arity -> %%! Temporary workaround - get_procinfo(Fd,Fun,Proc#proc{arity=Arity--"\r\n"}); - "Dictionary" -> - get_procinfo(Fd,Fun,Proc#proc{dict=size_or_term(Fd)}); - "$Dictionary" -> - get_procinfo(Fd,Fun,Proc#proc{debug_dict=size_or_term(Fd)}); - "Stack dump" -> - %% This is the last element in older dumps (DumpVsn=0.0) - get_stack_dump(Fd,Proc); + get_procinfo(Fd,Fun,Proc#proc{arity=Arity--"\r\n"},WS); "=" ++ _next_tag -> - %% DumpVsn=0.1 or newer: No stack dump here Proc; Other -> unexpected(Fd,Other,"process info"), - get_procinfo(Fd,Fun,Proc) + get_procinfo(Fd,Fun,Proc,WS) + end. + +parse_link_list([SB|Str],Links,Monitors,MonitoredBy) when SB==$[; SB==$] -> + parse_link_list(Str,Links,Monitors,MonitoredBy); +parse_link_list("#Port"++_=Str,Links,Monitors,MonitoredBy) -> + {Link,Rest} = parse_port(Str), + parse_link_list(Rest,[Link|Links],Monitors,MonitoredBy); +parse_link_list("<"++_=Str,Links,Monitors,MonitoredBy) -> + {Link,Rest} = parse_pid(Str), + parse_link_list(Rest,[Link|Links],Monitors,MonitoredBy); +parse_link_list("{to,"++Str,Links,Monitors,MonitoredBy) -> + {Mon,Rest} = parse_monitor(Str), + parse_link_list(Rest,Links,[Mon|Monitors],MonitoredBy); +parse_link_list("{from,"++Str,Links,Monitors,MonitoredBy) -> + {Mon,Rest} = parse_monitor(Str), + parse_link_list(Rest,Links,Monitors,[Mon|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)}. + +parse_port(Str) -> + {Port,Rest} = parse_link(Str,[]), + {{Port,Port},Rest}. + +parse_pid(Str) -> + {Pid,Rest} = parse_link(Str,[]), + {{Pid,Pid},Rest}. + +parse_monitor(Str) -> + case parse_link(Str,[]) of + {Pid,","++Rest1} -> + case parse_link(Rest1,[]) of + {Ref,"}"++Rest2} -> + {{Pid,Pid++" ("++Ref++")"},Rest2}; + {Ref,[]} -> + {{Pid,Pid++" ("++Ref++")"},[]} + end; + {Pid,[]} -> + {{Pid,Pid++" (unknown_ref)"},[]} end. -get_stack_dump(Fd,Proc) -> - %% Always show stackdump as "Expand" link - Pos = get(pos), - Size = count_rest_of_tag(Fd), - Proc#proc{stack_dump={size,true,Size,Pos}}. +parse_link(">"++Rest,Acc) -> + {lists:reverse(Acc,">"),Rest}; +parse_link([H|T],Acc) -> + parse_link(T,[H|Acc]); +parse_link([],Acc) -> + %% truncated + {lists:reverse(Acc),[]}. -maybe_other_node(File,Id) -> +maybe_other_node(Id) -> Channel = case split($.,Id) of {"<" ++ N, _Rest} -> @@ -1688,99 +1192,77 @@ maybe_other_node(File,Id) -> end), case ets:select(cdv_dump_index_table,Ms) of - [] -> + [] -> not_found; - [{Type,Pos}] -> - Fd = open(File), - NodeInfo = get_nodeinfo(Fd,Channel,Pos), - close(Fd), - {other_node,Type,NodeInfo} + [_] -> + {other_node,Channel} end. -expand_memory(File,What,Pid,Binaries) -> - Fd = open(File), +expand_memory(Fd,Pid,DumpVsn,Binaries) -> + BinAddrAdj = get_bin_addr_adj(DumpVsn), put(fd,Fd), - Dict = read_heap(Fd,Pid,Binaries), - Expanded = - case What of - "StackDump" -> read_stack_dump(Fd,Pid,Dict); - "MsgQueue" -> read_messages(Fd,Pid,Dict); - "Dictionary" -> read_dictionary(Fd,?proc_dictionary,Pid,Dict); - "DebugDictionary" -> read_dictionary(Fd,?debug_proc_dictionary,Pid,Dict) - end, + Dict = read_heap(Fd,Pid,BinAddrAdj,Binaries), + Expanded = {read_stack_dump(Fd,Pid,BinAddrAdj,Dict), + read_messages(Fd,Pid,BinAddrAdj,Dict), + read_dictionary(Fd,Pid,BinAddrAdj,Dict)}, erase(fd), - close(Fd), Expanded. - + +%%%----------------------------------------------------------------- +%%% This is a workaround for a bug in dump versions prior to 0.3: +%%% Addresses were truncated to 32 bits. This could cause binaries to +%%% get the same address as heap terms in the dump. To work around it +%%% we always store binaries on very high addresses in the gb_tree. +get_bin_addr_adj(DumpVsn) when DumpVsn < [0,3] -> + 16#f bsl 64; +get_bin_addr_adj(_) -> + 0. + %%% %%% Read binaries. %%% -read_binaries(Fd) -> +read_binaries(Fd,DumpVsn) -> AllBinaries = lookup_index(?binary), - read_binaries(Fd,AllBinaries, gb_trees:empty()). - -read_binaries(Fd,[{Addr0,Pos}|Bins],Dict0) -> - pos_bof(Fd,Pos), - {Addr,_} = get_hex(Addr0), - Dict = - case line_head(Fd) of - {eof,_} -> - gb_trees:enter(Addr,'#CDVTruncatedBinary',Dict0); - Size0 -> - {Size,_} = get_hex(Size0), - if Size > ?max_display_binary_size -> - gb_trees:enter(Addr,{'#CDVTooBig',binary,Pos},Dict0); - true -> - pos_bof(Fd,Pos), - Line = val(Fd), - parse_binary(Addr,Line,Dict0) - end - end, - read_binaries(Fd,Bins,Dict); -read_binaries(_Fd,[],Dict) -> - Dict. - -parse_binary(Addr, Line0, Dict) -> - case get_hex(Line0) of - {N,":"++Line1} -> - {Bin,Line} = get_binary(N, Line1, []), - [] = skip_blanks(Line), - gb_trees:enter(Addr, Bin, Dict); - {_N,[]} -> - %% If the dump is truncated before the ':' in this line, then - %% line_head/1 might not discover it (if a \n has been inserted - %% somehow???) - gb_trees:enter(Addr,'#CDVTruncatedBinary',Dict) - end. - - + AddrAdj = get_bin_addr_adj(DumpVsn), + Fun = fun({Addr0,Pos},Dict0) -> + pos_bof(Fd,Pos), + {HexAddr,_} = get_hex(Addr0), + Addr = HexAddr bor AddrAdj, + Bin = + case line_head(Fd) of + {eof,_} -> '#CDVTruncatedBinary'; + _Size -> {'#CDVBin',Pos} + end, + gb_trees:enter(Addr,Bin,Dict0) + end, + progress_foldl("Processing binaries",Fun,gb_trees:empty(),AllBinaries). %%% %%% Read top level section. %%% -read_stack_dump(Fd,Pid,Dict) -> +read_stack_dump(Fd,Pid,BinAddrAdj,Dict) -> case lookup_index(?proc_stack,Pid) of [{_,Start}] -> pos_bof(Fd,Start), - read_stack_dump1(Fd,Dict,[]); + read_stack_dump1(Fd,BinAddrAdj,Dict,[]); [] -> [] end. -read_stack_dump1(Fd,Dict,Acc) -> +read_stack_dump1(Fd,BinAddrAdj,Dict,Acc) -> %% This function is never called if the dump is truncated in {?proc_heap,Pid} case val(Fd) of "=" ++ _next_tag -> lists:reverse(Acc); Line -> - Stack = parse_top(Line,Dict), - read_stack_dump1(Fd,Dict,[Stack|Acc]) + Stack = parse_top(Line,BinAddrAdj,Dict), + read_stack_dump1(Fd,BinAddrAdj,Dict,[Stack|Acc]) end. -parse_top(Line0, D) -> +parse_top(Line0, BinAddrAdj, D) -> {Label,Line1} = get_label(Line0), - {Term,Line,D} = parse_term(Line1, D), + {Term,Line,D} = parse_term(Line1, BinAddrAdj, D), [] = skip_blanks(Line), {Label,Term}. @@ -1788,27 +1270,27 @@ parse_top(Line0, D) -> %%% Read message queue. %%% -read_messages(Fd,Pid,Dict) -> +read_messages(Fd,Pid,BinAddrAdj,Dict) -> case lookup_index(?proc_messages,Pid) of [{_,Start}] -> pos_bof(Fd,Start), - read_messages1(Fd,Dict,[]); + read_messages1(Fd,BinAddrAdj,Dict,[]); [] -> [] end. -read_messages1(Fd,Dict,Acc) -> +read_messages1(Fd,BinAddrAdj,Dict,Acc) -> %% This function is never called if the dump is truncated in {?proc_heap,Pid} case val(Fd) of "=" ++ _next_tag -> lists:reverse(Acc); Line -> - Msg = parse_message(Line,Dict), - read_messages1(Fd,Dict,[Msg|Acc]) + Msg = parse_message(Line,BinAddrAdj,Dict), + read_messages1(Fd,BinAddrAdj,Dict,[Msg|Acc]) end. -parse_message(Line0, D) -> - {Msg,":"++Line1,_} = parse_term(Line0, D), - {Token,Line,_} = parse_term(Line1, D), +parse_message(Line0, BinAddrAdj, D) -> + {Msg,":"++Line1,_} = parse_term(Line0, BinAddrAdj, D), + {Token,Line,_} = parse_term(Line1, BinAddrAdj, D), [] = skip_blanks(Line), {Msg,Token}. @@ -1816,26 +1298,26 @@ parse_message(Line0, D) -> %%% Read process dictionary %%% -read_dictionary(Fd,Tag,Pid,Dict) -> - case lookup_index(Tag,Pid) of +read_dictionary(Fd,Pid,BinAddrAdj,Dict) -> + case lookup_index(?proc_dictionary,Pid) of [{_,Start}] -> pos_bof(Fd,Start), - read_dictionary1(Fd,Dict,[]); + read_dictionary1(Fd,BinAddrAdj,Dict,[]); [] -> [] end. -read_dictionary1(Fd,Dict,Acc) -> +read_dictionary1(Fd,BinAddrAdj,Dict,Acc) -> %% This function is never called if the dump is truncated in {?proc_heap,Pid} case val(Fd) of "=" ++ _next_tag -> lists:reverse(Acc); Line -> - Msg = parse_dictionary(Line,Dict), - read_dictionary1(Fd,Dict,[Msg|Acc]) + Msg = parse_dictionary(Line,BinAddrAdj,Dict), + read_dictionary1(Fd,BinAddrAdj,Dict,[Msg|Acc]) end. -parse_dictionary(Line0, D) -> - {Entry,Line,_} = parse_term(Line0, D), +parse_dictionary(Line0, BinAddrAdj, D) -> + {Entry,Line,_} = parse_term(Line0, BinAddrAdj, D), [] = skip_blanks(Line), Entry. @@ -1843,16 +1325,16 @@ parse_dictionary(Line0, D) -> %%% Read heap data. %%% -read_heap(Fd,Pid,Dict0) -> +read_heap(Fd,Pid,BinAddrAdj,Dict0) -> case lookup_index(?proc_heap,Pid) of [{_,Pos}] -> pos_bof(Fd,Pos), - read_heap(Dict0); + read_heap(BinAddrAdj,Dict0); [] -> Dict0 end. -read_heap(Dict0) -> +read_heap(BinAddrAdj,Dict0) -> %% This function is never called if the dump is truncated in {?proc_heap,Pid} case get(fd) of end_of_heap -> @@ -1863,68 +1345,18 @@ read_heap(Dict0) -> put(fd, end_of_heap), Dict0; Line -> - Dict = parse(Line,Dict0), - read_heap(Dict) + Dict = parse(Line,BinAddrAdj,Dict0), + read_heap(BinAddrAdj,Dict) end end. -parse(Line0, Dict0) -> +parse(Line0, BinAddrAdj, Dict0) -> {Addr,":"++Line1} = get_hex(Line0), - {_Term,Line,Dict} = parse_heap_term(Line1, Addr, Dict0), + {_Term,Line,Dict} = parse_heap_term(Line1, Addr, BinAddrAdj, Dict0), [] = skip_blanks(Line), Dict. -do_sort_procs("state",Procs,#state{sorted="state"}) -> - {lists:reverse(lists:keysort(#proc.state,Procs)),"rstate"}; -do_sort_procs("state",Procs,_) -> - {lists:keysort(#proc.state,Procs),"state"}; -do_sort_procs("pid",Procs,#state{sorted="pid"}) -> - {lists:reverse(Procs),"rpid"}; -do_sort_procs("pid",Procs,_) -> - {Procs,"pid"}; -do_sort_procs("msg_q_len",Procs,#state{sorted="msg_q_len"}) -> - {lists:keysort(#proc.msg_q_len,Procs),"rmsg_q_len"}; -do_sort_procs("msg_q_len",Procs,_) -> - {lists:reverse(lists:keysort(#proc.msg_q_len,Procs)),"msg_q_len"}; -do_sort_procs("reds",Procs,#state{sorted="reds"}) -> - {lists:keysort(#proc.reds,Procs),"rreds"}; -do_sort_procs("reds",Procs,_) -> - {lists:reverse(lists:keysort(#proc.reds,Procs)),"reds"}; -do_sort_procs("mem",Procs,#state{sorted="mem",dump_vsn=DumpVsn}) -> - KeyPos = if DumpVsn>=?r16b01_dump_vsn -> #proc.memory; - true -> #proc.stack_heap - end, - {lists:keysort(KeyPos,Procs),"rmem"}; -do_sort_procs("mem",Procs,#state{dump_vsn=DumpVsn}) -> - KeyPos = if DumpVsn>=?r16b01_dump_vsn -> #proc.memory; - true -> #proc.stack_heap - end, - {lists:reverse(lists:keysort(KeyPos,Procs)),"mem"}; -do_sort_procs("init_func",Procs,#state{sorted="init_func"}) -> - {lists:reverse(lists:keysort(#proc.init_func,Procs)),"rinit_func"}; -do_sort_procs("init_func",Procs,_) -> - {lists:keysort(#proc.init_func,Procs),"init_func"}; -do_sort_procs("name_func",Procs,#state{sorted="name_func"}) -> - {lists:reverse(lists:keysort(#proc.name,Procs)),"rname_func"}; -do_sort_procs("name_func",Procs,_) -> - {lists:keysort(#proc.name,Procs),"name_func"}; -do_sort_procs("name",Procs,#state{sorted=Sorted}) -> - {No,Yes} = - lists:foldl(fun(P,{N,Y}) -> - case P#proc.name of - ?space -> {[P|N],Y}; - _other -> {N,[P|Y]} - end - end, - {[],[]}, - Procs), - Result = lists:keysort(#proc.name,Yes) ++ No, - case Sorted of - "name" -> {lists:reverse(Result),"rname"}; - _ -> {Result,"name"} - end. - %%----------------------------------------------------------------- %% Page with one port get_port(File,Port) -> @@ -1936,46 +1368,59 @@ get_port(File,Port) -> close(Fd), {ok,R}; [] -> - case maybe_other_node(File,Port) of - {other_node,Type,Node} -> - Info = "The port you are searching for was residing on " - "a remote node. No port information is available. " - "Information about the remote node is show below.", - {other_node,{Type,Info,Node}}; - not_found -> - not_found - end + maybe_other_node(Port) end. %%----------------------------------------------------------------- %% Page with all ports -get_ports(SessionId,File,TW) -> - ParseFun = fun(Fd,Id) -> get_portinfo(Fd,#port{id=Id}) end, - chunk_page(SessionId,File,TW,?port,ports,[],ParseFun). +get_ports(File) -> + ParseFun = fun(Fd,Id) -> get_portinfo(Fd,#port{id=port_to_tuple(Id)}) end, + lookup_and_parse_index(File,?port,ParseFun,"ports"). + +%% Converting port string to tuple to secure correct sorting. This is +%% converted back in cdv_port_cb:format/1. +port_to_tuple("#Port<"++Port) -> + [I1,I2] = string:tokens(Port,".>"), + {list_to_integer(I1),list_to_integer(I2)}. get_portinfo(Fd,Port) -> case line_head(Fd) of "Slot" -> - get_portinfo(Fd,Port#port{slot=val(Fd)}); + %% stored as integer so we can sort on it + get_portinfo(Fd,Port#port{slot=list_to_integer(val(Fd))}); "Connected" -> - get_portinfo(Fd,Port#port{connected=val(Fd)}); + %% stored as pid so we can sort on it + Connected0 = val(Fd), + Connected = + try list_to_pid(Connected0) + catch error:badarg -> Connected0 + end, + get_portinfo(Fd,Port#port{connected=Connected}); "Links" -> - get_portinfo(Fd,Port#port{links=val(Fd)}); + Pids = split_pid_list_no_space(val(Fd)), + Links = [{Pid,Pid} || Pid <- Pids], + get_portinfo(Fd,Port#port{links=Links}); "Registered as" -> get_portinfo(Fd,Port#port{name=val(Fd)}); "Monitors" -> - get_portinfo(Fd,Port#port{monitors=val(Fd)}); + Monitors0 = string:tokens(val(Fd),"()"), + Monitors = [begin + [Pid,Ref] = string:tokens(Mon,","), + {Pid,Pid++" ("++Ref++")"} + end || Mon <- Monitors0], + get_portinfo(Fd,Port#port{monitors=Monitors}); "Port controls linked-in driver" -> - get_portinfo(Fd,Port#port{controls=["Linked in driver: " | - val(Fd)]}); + Str = lists:flatten(["Linked in driver: " | val(Fd)]), + get_portinfo(Fd,Port#port{controls=Str}); "Port controls external process" -> - get_portinfo(Fd,Port#port{controls=["External proc: " | val(Fd)]}); + Str = lists:flatten(["External proc: " | val(Fd)]), + get_portinfo(Fd,Port#port{controls=Str}); "Port is a file" -> - get_portinfo(Fd,Port#port{controls=["File: "| val(Fd)]}); + Str = lists:flatten(["File: "| val(Fd)]), + get_portinfo(Fd,Port#port{controls=Str}); "Port is UNIX fd not opened by emulator" -> - get_portinfo(Fd,Port#port{ - controls=["UNIX fd not opened by emulator: "| - val(Fd)]}); + Str = lists:flatten(["UNIX fd not opened by emulator: "| val(Fd)]), + get_portinfo(Fd,Port#port{controls=Str}); "=" ++ _next_tag -> Port; Other -> @@ -1983,17 +1428,27 @@ get_portinfo(Fd,Port) -> Port end. +split_pid_list_no_space(String) -> + split_pid_list_no_space(String,[],[]). +split_pid_list_no_space([$>|Rest],Acc,Pids) -> + split_pid_list_no_space(Rest,[],[lists:reverse(Acc,[$>])|Pids]); +split_pid_list_no_space([H|T],Acc,Pids) -> + split_pid_list_no_space(T,[H|Acc],Pids); +split_pid_list_no_space([],[],Pids) -> + lists:reverse(Pids). %%----------------------------------------------------------------- %% Page with external ets tables -get_ets_tables(SessionId,File,Heading,TW,Pid,WS) -> - ParseFun = fun(Fd,Id) -> get_etsinfo(Fd,#ets_table{pid=Id},WS) end, - chunk_page(SessionId,File,TW,{?ets,Pid},ets_tables,Heading,ParseFun). +get_ets_tables(File,Pid,WS) -> + ParseFun = fun(Fd,Id) -> + get_etsinfo(Fd,#ets_table{pid=list_to_pid(Id)},WS) + end, + lookup_and_parse_index(File,{?ets,Pid},ParseFun,"ets"). get_etsinfo(Fd,EtsTable,WS) -> case line_head(Fd) of "Slot" -> - get_etsinfo(Fd,EtsTable#ets_table{slot=val(Fd)},WS); + get_etsinfo(Fd,EtsTable#ets_table{slot=list_to_integer(val(Fd))},WS); "Table" -> get_etsinfo(Fd,EtsTable#ets_table{id=val(Fd)},WS); "Name" -> @@ -2002,15 +1457,18 @@ get_etsinfo(Fd,EtsTable,WS) -> skip_rest_of_line(Fd), get_etsinfo(Fd,EtsTable#ets_table{type="tree",buckets="-"},WS); "Buckets" -> - get_etsinfo(Fd,EtsTable#ets_table{buckets=val(Fd)},WS); + %% A bug in erl_db_hash.c prints a space after the buckets + %% - need to strip the string to make list_to_integer/1 happy. + Buckets = list_to_integer(string:strip(val(Fd))), + get_etsinfo(Fd,EtsTable#ets_table{buckets=Buckets},WS); "Objects" -> - get_etsinfo(Fd,EtsTable#ets_table{size=val(Fd)},WS); + get_etsinfo(Fd,EtsTable#ets_table{size=list_to_integer(val(Fd))},WS); "Words" -> Words = list_to_integer(val(Fd)), Bytes = case Words of - -1 -> "-1"; % probably truncated - _ -> integer_to_list(Words * WS) + -1 -> -1; % probably truncated + _ -> Words * WS end, get_etsinfo(Fd,EtsTable#ets_table{memory=Bytes},WS); "=" ++ _next_tag -> @@ -2036,16 +1494,17 @@ get_internal_ets_tables(File,WS) -> %%----------------------------------------------------------------- %% Page with list of all timers -get_timers(SessionId,File,Heading,TW,Pid) -> - ParseFun = fun(Fd,Id) -> get_timerinfo_1(Fd,#timer{pid=Id}) end, - chunk_page(SessionId,File,TW,{?timer,Pid},timers,Heading,ParseFun). +get_timers(File,Pid) -> + ParseFun = fun(Fd,Id) -> get_timerinfo_1(Fd,#timer{pid=list_to_pid(Id)}) end, + lookup_and_parse_index(File,{?timer,Pid},ParseFun,"timers"). get_timerinfo_1(Fd,Timer) -> case line_head(Fd) of "Message" -> get_timerinfo_1(Fd,Timer#timer{msg=val(Fd)}); "Time left" -> - get_timerinfo_1(Fd,Timer#timer{time=val(Fd)}); + TimeLeft = list_to_integer(val(Fd) -- " ms"), + get_timerinfo_1(Fd,Timer#timer{time=TimeLeft}); "=" ++ _next_tag -> Timer; Other -> @@ -2054,6 +1513,28 @@ get_timerinfo_1(Fd,Timer) -> end. %%----------------------------------------------------------------- +%% Page with information about a node in the distribution +get_node(File,Channel) -> + Ms = ets:fun2ms( + fun({{Tag,Start},Ch}) when Tag=:=?visible_node, Ch=:=Channel -> + {visible,Start}; + ({{Tag,Start},Ch}) when Tag=:=?hidden_node, Ch=:=Channel -> + {hidden,Start}; + ({{Tag,Start},Ch}) when Tag=:=?not_connected, Ch=:=Channel -> + {not_connected,Start} + end), + + case ets:select(cdv_dump_index_table,Ms) of + [] -> + {error,not_found}; + [{Type,Pos}] -> + Fd = open(File), + NodeInfo = get_nodeinfo(Fd,Channel,Type,Pos), + close(Fd), + {ok,NodeInfo} + end. + +%%----------------------------------------------------------------- %% Page with information about the erlang distribution nods(File) -> case lookup_index(?no_distribution) of @@ -2064,28 +1545,29 @@ nods(File) -> Fd = open(File), Visible = lists:map( fun({Channel,Start}) -> - get_nodeinfo(Fd,Channel,Start) + get_nodeinfo(Fd,Channel,visible,Start) end, V), Hidden = lists:map( fun({Channel,Start}) -> - get_nodeinfo(Fd,Channel,Start) + get_nodeinfo(Fd,Channel,hidden,Start) end, H), NotConnected = lists:map( fun({Channel,Start}) -> - get_nodeinfo(Fd,Channel,Start) + get_nodeinfo(Fd,Channel,not_connected,Start) end, N), close(Fd), - {Visible,Hidden,NotConnected}; + Visible++Hidden++NotConnected; [_] -> - no_distribution + %% no_distribution + [] end. -get_nodeinfo(Fd,Channel,Start) -> +get_nodeinfo(Fd,Channel,Type,Start) -> pos_bof(Fd,Start), - get_nodeinfo(Fd,#nod{channel=Channel}). + get_nodeinfo(Fd,#nod{channel=list_to_integer(Channel),conn_type=Type}). get_nodeinfo(Fd,Nod) -> case line_head(Fd) of @@ -2094,21 +1576,27 @@ get_nodeinfo(Fd,Nod) -> "Controller" -> get_nodeinfo(Fd,Nod#nod{controller=val(Fd)}); "Creation" -> - get_nodeinfo(Fd,Nod#nod{creation=val(Fd)}); + get_nodeinfo(Fd,Nod#nod{creation=list_to_integer(val(Fd))}); "Remote link" -> Procs = val(Fd), % e.g. "<0.31.0> <4322.54.0>" - RemoteLinks = Nod#nod.remote_links, - get_nodeinfo(Fd,Nod#nod{remote_links=[split(Procs)|RemoteLinks]}); + {Local,Remote} = split(Procs), + Str = Local++" <-> "++Remote, + NewRemLinks = [{Local,Str} | Nod#nod.remote_links], + get_nodeinfo(Fd,Nod#nod{remote_links=NewRemLinks}); "Remote monitoring" -> Procs = val(Fd), % e.g. "<0.31.0> <4322.54.0>" - RemoteMon = Nod#nod.remote_mon, - get_nodeinfo(Fd,Nod#nod{remote_mon=[split(Procs)|RemoteMon]}); + {Local,Remote} = split(Procs), + Str = Local++" -> "++Remote, + NewRemMon = [{Local,Str} | Nod#nod.remote_mon], + get_nodeinfo(Fd,Nod#nod{remote_mon=NewRemMon}); "Remotely monitored by" -> Procs = val(Fd), % e.g. "<0.31.0> <4322.54.0>" - RemoteMonBy = Nod#nod.remote_mon_by, - get_nodeinfo(Fd,Nod#nod{remote_mon_by=[split(Procs)|RemoteMonBy]}); + {Local,Remote} = split(Procs), + Str = Local++" <- "++Remote, + NewRemMonBy = [{Local,Str} | Nod#nod.remote_mon_by], + get_nodeinfo(Fd,Nod#nod{remote_mon_by=NewRemMonBy}); "Error" -> - get_nodeinfo(Fd,Nod#nod{error=val(Fd)}); + get_nodeinfo(Fd,Nod#nod{error="ERROR: "++val(Fd)}); "=" ++ _next_tag -> Nod; Other -> @@ -2129,10 +1617,11 @@ get_loaded_mod_details(File,Mod) -> %%----------------------------------------------------------------- %% Page with list of all loaded modules -loaded_mods(SessionId,File,TW) -> +loaded_mods(File) -> ParseFun = fun(Fd,Id) -> - get_loaded_mod_info(Fd,#loaded_mod{mod=Id}, + get_loaded_mod_info(Fd, + #loaded_mod{mod=get_atom(list_to_binary(Id))}, fun main_modinfo/3) end, {CC,OC} = @@ -2146,7 +1635,7 @@ loaded_mods(SessionId,File,TW) -> [] -> {"unknown","unknown"} end, - chunk_page(SessionId,File,TW,?mod,loaded_mods,{CC,OC},ParseFun). + {CC,OC,lookup_and_parse_index(File,?mod,ParseFun,"modules")}. get_loaded_mod_totals(Fd,{CC,OC}) -> case line_head(Fd) of @@ -2164,9 +1653,11 @@ get_loaded_mod_totals(Fd,{CC,OC}) -> get_loaded_mod_info(Fd,LM,Fun) -> case line_head(Fd) of "Current size" -> - get_loaded_mod_info(Fd,LM#loaded_mod{current_size=val(Fd)},Fun); + CS = list_to_integer(val(Fd)), + get_loaded_mod_info(Fd,LM#loaded_mod{current_size=CS},Fun); "Old size" -> - get_loaded_mod_info(Fd,LM#loaded_mod{old_size=val(Fd)},Fun); + OS = list_to_integer(val(Fd)), + get_loaded_mod_info(Fd,LM#loaded_mod{old_size=OS},Fun); "=" ++ _next_tag -> LM; {eof,_} -> @@ -2229,24 +1720,24 @@ hex_to_dec(N) -> list_to_integer(N). %%----------------------------------------------------------------- %% Page with list of all funs -funs(SessionId,File,TW) -> +funs(File) -> ParseFun = fun(Fd,_Id) -> get_funinfo(Fd,#fu{}) end, - chunk_page(SessionId,File,TW,?fu,funs,[],ParseFun). + lookup_and_parse_index(File,?fu,ParseFun,"funs"). get_funinfo(Fd,Fu) -> case line_head(Fd) of "Module" -> get_funinfo(Fd,Fu#fu{module=val(Fd)}); "Uniq" -> - get_funinfo(Fd,Fu#fu{uniq=val(Fd)}); + get_funinfo(Fd,Fu#fu{uniq=list_to_integer(val(Fd))}); "Index" -> - get_funinfo(Fd,Fu#fu{index=val(Fd)}); + get_funinfo(Fd,Fu#fu{index=list_to_integer(val(Fd))}); "Address" -> get_funinfo(Fd,Fu#fu{address=val(Fd)}); "Native_address" -> get_funinfo(Fd,Fu#fu{native_address=val(Fd)}); "Refc" -> - get_funinfo(Fd,Fu#fu{refc=val(Fd)}); + get_funinfo(Fd,Fu#fu{refc=list_to_integer(val(Fd))}); "=" ++ _next_tag -> Fu; Other -> @@ -2256,45 +1747,54 @@ get_funinfo(Fd,Fu) -> %%----------------------------------------------------------------- %% Page with list of all atoms -atoms(SessionId,File,TW,Num) -> +atoms(File,NumAtoms) -> case lookup_index(?atoms) of [{_Id,Start}] -> Fd = open(File), pos_bof(Fd,Start), - case get_atoms(Fd,?items_chunk_size) of - {Atoms,Cont} -> - crashdump_viewer_html:atoms(SessionId,TW,Num,Atoms), - atoms_chunks(Fd,SessionId,Cont); - done -> - crashdump_viewer_html:atoms(SessionId,TW,Num,done) - end; + get_atoms(Fd,NumAtoms); _ -> - crashdump_viewer_html:atoms(SessionId,TW,Num,done) + [] end. -get_atoms(Fd,Number) -> - case get_n_lines_of_tag(Fd,Number) of - {all,_,Lines} -> - close(Fd), - {Lines,done}; - {part,_,Lines} -> - {Lines,Number}; - empty -> - close(Fd), - done +get_atoms(Fd,NumAtoms) -> + case get_chunk(Fd) of + {ok,Bin} -> + init_progress("Processing atoms",NumAtoms), + get_atoms(Fd,Bin,NumAtoms,[]); + eof -> + [] end. -atoms_chunks(_Fd,SessionId,done) -> - crashdump_viewer_html:atoms_chunk(SessionId,done); -atoms_chunks(Fd,SessionId,Number) -> - case get_atoms(Fd,Number) of - {Atoms,Cont} -> - crashdump_viewer_html:atoms_chunk(SessionId,Atoms), - atoms_chunks(Fd,SessionId,Cont); - done -> - atoms_chunks(Fd,SessionId,done) - end. +%% Atoms are written one per line in the crash dump, in creation order +%% from last to first. +get_atoms(Fd,Bin,NumAtoms,Atoms) -> + Bins = binary:split(Bin,<<"\n">>,[global]), + get_atoms1(Fd,Bins,NumAtoms,Atoms). + +get_atoms1(_Fd,[<<"=",_/binary>>|_],_N,Atoms) -> + end_progress(), + Atoms; +get_atoms1(Fd,[LastBin],N,Atoms) -> + case get_chunk(Fd) of + {ok,Bin0} -> + get_atoms(Fd,<<LastBin/binary,Bin0/binary>>,N,Atoms); + eof -> + end_progress(), + [{N,get_atom(LastBin)}|Atoms] + end; +get_atoms1(Fd,[Bin|Bins],N,Atoms) -> + update_progress(), + get_atoms1(Fd,Bins,N-1,[{N,get_atom(Bin)}|Atoms]). + +%% This ensures sorting according to first actual letter in the atom, +%% disregarding possible single quote. It is formatted back to correct +%% syntax in cdv_atom_cb:format/1 +get_atom(<<"\'",Atom/binary>>) -> + {Atom,q}; % quoted +get_atom(Atom) when is_binary(Atom) -> + {Atom,nq}. % not quoted %%----------------------------------------------------------------- %% Page with memory information @@ -2317,7 +1817,7 @@ get_meminfo(Fd,Acc) -> {eof,_last_line} -> lists:reverse(Acc); Key -> - get_meminfo(Fd,[{Key,val(Fd)}|Acc]) + get_meminfo(Fd,[{list_to_atom(Key),val(Fd)}|Acc]) end. %%----------------------------------------------------------------- @@ -2345,7 +1845,7 @@ get_allocareainfo(Fd,Acc) -> AllocInfo = case split(Val) of {Alloc,[]} -> - {Key,Alloc,?space}; + {Key,Alloc,""}; {Alloc,Used} -> {Key,Alloc,Used} end, @@ -2361,7 +1861,7 @@ allocator_info(File) -> AllAllocators -> Fd = open(File), R = lists:map(fun({Heading,Start}) -> - {Heading,get_allocatorinfo(Fd,Start)} + {Heading,get_allocatorinfo(Fd,Start)} end, AllAllocators), close(Fd), @@ -2370,17 +1870,19 @@ allocator_info(File) -> get_allocatorinfo(Fd,Start) -> pos_bof(Fd,Start), - get_allocatorinfo1(Fd,[]). + get_allocatorinfo1(Fd,[],0). -get_allocatorinfo1(Fd,Acc) -> +get_allocatorinfo1(Fd,Acc,Max) -> case line_head(Fd) of "=" ++ _next_tag -> - lists:reverse(Acc); + pad_and_reverse(Acc,Max,[]); {eof,_last_line} -> - lists:reverse(Acc); + pad_and_reverse(Acc,Max,[]); Key -> Values = get_all_vals(val(Fd),[]), - get_allocatorinfo1(Fd,[{Key,Values}|Acc]) + L = length(Values), + Max1 = if L > Max -> L; true -> Max end, + get_allocatorinfo1(Fd,[{Key,Values}|Acc],Max1) end. get_all_vals([$ |Rest],Acc) -> @@ -2390,6 +1892,16 @@ get_all_vals([],Acc) -> get_all_vals([Char|Rest],Acc) -> get_all_vals(Rest,[Char|Acc]). +%% Make sure all V have the same length by padding with "". +pad_and_reverse([{K,V}|T],Len,Rev) -> + VLen = length(V), + V1 = if VLen == Len -> V; + true -> V ++ lists:duplicate(Len-VLen,"") + end, + pad_and_reverse(T,Len,[{K,V1}|Rev]); +pad_and_reverse([],_,Rev) -> + Rev. + %% Calculate allocator summary: %% %% System totals: @@ -2473,7 +1985,8 @@ allocator_summary(Allocators) -> {TBS,TCS} -> {integer_to_list(TBS),integer_to_list(TCS)} end, - {{"Summary",["blocks size","carriers size","mseg carriers size"]}, + {"Allocator Summary", + ["blocks size","carriers size","mseg carriers size"], [{"total",[TotalBS,TotalCS,TotalMCS]} | format_allocator_summary(lists:reverse(TypeTotals))]}. @@ -2673,142 +2186,120 @@ get_indextableinfo1(Fd,IndexTable) -> IndexTable end. - - - - -%%----------------------------------------------------------------- -%% Expand a set of data which was shown in a truncated form on -get_expanded(File,Pos,Size) -> - Fd = open(File), - R = case file:pread(Fd,Pos,Size) of - {ok,Bin}-> - binary_to_list(Bin); - eof -> - ?space - end, - close(Fd), - R. - - -replace_all(From,To,[From|Rest],Acc) -> - replace_all(From,To,Rest,[To|Acc]); -replace_all(From,To,[Char|Rest],Acc) -> - replace_all(From,To,Rest,[Char|Acc]); -replace_all(_From,_To,[],Acc) -> - lists:reverse(Acc). - - %%%----------------------------------------------------------------- %%% Parse memory in crashdump version 0.1 and newer %%% -parse_heap_term([$l|Line0], Addr, D0) -> %Cons cell. - {H,"|"++Line1,D1} = parse_term(Line0, D0), - {T,Line,D2} = parse_term(Line1, D1), +parse_heap_term([$l|Line0], Addr, BinAddrAdj, D0) -> %Cons cell. + {H,"|"++Line1,D1} = parse_term(Line0, BinAddrAdj, D0), + {T,Line,D2} = parse_term(Line1, BinAddrAdj, D1), Term = [H|T], D = gb_trees:insert(Addr, Term, D2), {Term,Line,D}; -parse_heap_term([$t|Line0], Addr, D) -> %Tuple +parse_heap_term([$t|Line0], Addr, BinAddrAdj, D) -> %Tuple {N,":"++Line} = get_hex(Line0), - parse_tuple(N, Line, Addr, D, []); -parse_heap_term([$F|Line0], Addr, D0) -> %Float + parse_tuple(N, Line, Addr, BinAddrAdj, D, []); +parse_heap_term([$F|Line0], Addr, _BinAddrAdj, D0) -> %Float {N,":"++Line1} = get_hex(Line0), {Chars,Line} = get_chars(N, Line1), Term = list_to_float(Chars), D = gb_trees:insert(Addr, Term, D0), {Term,Line,D}; -parse_heap_term("B16#"++Line0, Addr, D0) -> %Positive big number. +parse_heap_term("B16#"++Line0, Addr, _BinAddrAdj, D0) -> %Positive big number. {Term,Line} = get_hex(Line0), D = gb_trees:insert(Addr, Term, D0), {Term,Line,D}; -parse_heap_term("B-16#"++Line0, Addr, D0) -> %Negative big number +parse_heap_term("B-16#"++Line0, Addr, _BinAddrAdj, D0) -> %Negative big number {Term0,Line} = get_hex(Line0), Term = -Term0, D = gb_trees:insert(Addr, Term, D0), {Term,Line,D}; -parse_heap_term("B"++Line0, Addr, D0) -> %Decimal big num (new in R10B-something). +parse_heap_term("B"++Line0, Addr, _BinAddrAdj, D0) -> %Decimal big num case string:to_integer(Line0) of {Int,Line} when is_integer(Int) -> D = gb_trees:insert(Addr, Int, D0), {Int,Line,D} end; -parse_heap_term([$P|Line0], Addr, D0) -> % External Pid. +parse_heap_term([$P|Line0], Addr, _BinAddrAdj, D0) -> % External Pid. {Pid0,Line} = get_id(Line0), - Pid = "#CDVPid"++Pid0, + Pid = ['#CDVPid'|Pid0], D = gb_trees:insert(Addr, Pid, D0), {Pid,Line,D}; -parse_heap_term([$p|Line0], Addr, D0) -> % External Port. +parse_heap_term([$p|Line0], Addr, _BinAddrAdj, D0) -> % External Port. {Port0,Line} = get_id(Line0), - Port = "#CDVPort"++Port0, + Port = ['#CDVPort'|Port0], D = gb_trees:insert(Addr, Port, D0), {Port,Line,D}; -parse_heap_term("E"++Line0, Addr, D0) -> %Term encoded in external format. +parse_heap_term("E"++Line0, Addr, _BinAddrAdj, D0) -> %Term encoded in external format. {Bin,Line} = get_binary(Line0), Term = binary_to_term(Bin), D = gb_trees:insert(Addr, Term, D0), {Term,Line,D}; -parse_heap_term("Yh"++Line0, Addr, D0) -> %Heap binary. +parse_heap_term("Yh"++Line0, Addr, _BinAddrAdj, D0) -> %Heap binary. {Term,Line} = get_binary(Line0), D = gb_trees:insert(Addr, Term, D0), {Term,Line,D}; -parse_heap_term("Yc"++Line0, Addr, D0) -> %Reference-counted binary. - {Binp,":"++Line1} = get_hex(Line0), - {First,":"++Line2} = get_hex(Line1), +parse_heap_term("Yc"++Line0, Addr, BinAddrAdj, D0) -> %Reference-counted binary. + {Binp0,":"++Line1} = get_hex(Line0), + {Offset,":"++Line2} = get_hex(Line1), {Sz,Line} = get_hex(Line2), + Binp = Binp0 bor BinAddrAdj, Term = case gb_trees:lookup(Binp, D0) of - {value,<<_:First/binary,T:Sz/binary,_/binary>>} -> T; - {value,{'#CDVTooBig',binary,Pos}} -> cdvbin(Sz,Pos); - {value,'#CDVTruncatedBinary'} -> '#CDVTruncatedBinary'; + {value,Bin} -> cdvbin(Offset,Sz,Bin); none -> '#CDVNonexistingBinary' end, D = gb_trees:insert(Addr, Term, D0), {Term,Line,D}; -parse_heap_term("Ys"++Line0, Addr, D0) -> %Sub binary. - {Binp,":"++Line1} = get_hex(Line0), - {First,":"++Line2} = get_hex(Line1), +parse_heap_term("Ys"++Line0, Addr, BinAddrAdj, D0) -> %Sub binary. + {Binp0,":"++Line1} = get_hex(Line0), + {Offset,":"++Line2} = get_hex(Line1), {Sz,Line} = get_hex(Line2), + Binp = Binp0 bor BinAddrAdj, Term = case gb_trees:lookup(Binp, D0) of - {value,<<_:First/binary,T:Sz/binary,_/binary>>} -> T; - {value,{'#CDVTooBig',binary,Pos}} -> cdvbin(Sz,Pos); - {value,'#CDVTruncatedBinary'} -> '#CDVTruncatedBinary'; + {value,Bin} -> cdvbin(Offset,Sz,Bin); + none when Binp0=/=Binp -> + %% Might it be on the heap? + case gb_trees:lookup(Binp0, D0) of + {value,Bin} -> cdvbin(Offset,Sz,Bin); + none -> '#CDVNonexistingBinary' + end; none -> '#CDVNonexistingBinary' end, D = gb_trees:insert(Addr, Term, D0), {Term,Line,D}. -parse_tuple(0, Line, Addr, D0, Acc) -> +parse_tuple(0, Line, Addr, _, D0, Acc) -> Tuple = list_to_tuple(lists:reverse(Acc)), D = gb_trees:insert(Addr, Tuple, D0), {Tuple,Line,D}; -parse_tuple(N, Line0, Addr, D0, Acc) -> - case parse_term(Line0, D0) of +parse_tuple(N, Line0, Addr, BinAddrAdj, D0, Acc) -> + case parse_term(Line0, BinAddrAdj, D0) of {Term,[$,|Line],D} when N > 1 -> - parse_tuple(N-1, Line, Addr, D, [Term|Acc]); + parse_tuple(N-1, Line, Addr, BinAddrAdj, D, [Term|Acc]); {Term,Line,D}-> - parse_tuple(N-1, Line, Addr, D, [Term|Acc]) + parse_tuple(N-1, Line, Addr, BinAddrAdj, D, [Term|Acc]) end. -parse_term([$H|Line0], D) -> %Pointer to heap term. +parse_term([$H|Line0], BinAddrAdj, D) -> %Pointer to heap term. {Ptr,Line} = get_hex(Line0), - deref_ptr(Ptr, Line, D); -parse_term([$N|Line], D) -> %[] (nil). + deref_ptr(Ptr, Line, BinAddrAdj, D); +parse_term([$N|Line], _, D) -> %[] (nil). {[],Line,D}; -parse_term([$I|Line0], D) -> %Small. +parse_term([$I|Line0], _, D) -> %Small. {Int,Line} = string:to_integer(Line0), {Int,Line,D}; -parse_term([$A|_]=Line, D) -> %Atom. +parse_term([$A|_]=Line, _, D) -> %Atom. parse_atom(Line, D); -parse_term([$P|Line0], D) -> %Pid. +parse_term([$P|Line0], _, D) -> %Pid. {Pid,Line} = get_id(Line0), - {"#CDVPid"++Pid,Line,D}; -parse_term([$p|Line0], D) -> %Port. + {['#CDVPid'|Pid],Line,D}; +parse_term([$p|Line0], _, D) -> %Port. {Port,Line} = get_id(Line0), - {"#CDVPort"++Port,Line,D}; -parse_term([$S|Str0], D) -> %Information string. + {['#CDVPort'|Port],Line,D}; +parse_term([$S|Str0], _, D) -> %Information string. Str = lists:reverse(skip_blanks(lists:reverse(Str0))), {Str,[],D}; -parse_term([$D|Line0], D) -> %DistExternal +parse_term([$D|Line0], _, D) -> %DistExternal try {AttabSize,":"++Line1} = get_hex(Line0), {Attab, "E"++Line2} = parse_atom_translation_table(AttabSize, Line1, []), @@ -2848,7 +2339,7 @@ parse_atom_translation_table(N, Line0, As) -> -deref_ptr(Ptr, Line, D0) -> +deref_ptr(Ptr, Line, BinAddrAdj, D0) -> case gb_trees:lookup(Ptr, D0) of {value,Term} -> {Term,Line,D0}; @@ -2860,10 +2351,10 @@ deref_ptr(Ptr, Line, D0) -> case val(Fd) of "="++_ -> put(fd, end_of_heap), - deref_ptr(Ptr, Line, D0); + deref_ptr(Ptr, Line, BinAddrAdj, D0); L -> - D = parse(L, D0), - deref_ptr(Ptr, Line, D) + D = parse(L, BinAddrAdj, D0), + deref_ptr(Ptr, Line, BinAddrAdj, D) end end end. @@ -2901,13 +2392,16 @@ get_chars(0, Line, Acc) -> get_chars(N, [H|T], Acc) -> get_chars(N-1, T, [H|Acc]). -get_id(Line) -> - get_id(Line, []). +get_id(Line0) -> + [$<|Line] = lists:dropwhile(fun($<) -> false; (_) -> true end,Line0), + get_id(Line, [], []). -get_id([$>|Line], Acc) -> - {lists:reverse(Acc, [$>]),Line}; -get_id([H|T], Acc) -> - get_id(T, [H|Acc]). +get_id([$>|Line], Acc, Id) -> + {lists:reverse(Id,[list_to_integer(lists:reverse(Acc))]),Line}; +get_id([$.|Line], Acc, Id) -> + get_id(Line,[],[list_to_integer(lists:reverse(Acc))|Id]); +get_id([H|T], Acc, Id) -> + get_id(T, [H|Acc], Id). get_label(L) -> get_label(L, []). @@ -2925,19 +2419,26 @@ get_label([H|T], Acc) -> get_binary(Line0) -> {N,":"++Line} = get_hex(Line0), - get_binary(N, Line, []). + do_get_binary(N, Line, []). + +get_binary(Offset,Size,Line0) -> + {_N,":"++Line} = get_hex(Line0), + do_get_binary(Size, lists:sublist(Line,(Offset*2)+1,Size*2), []). -get_binary(0, Line, Acc) -> +do_get_binary(0, Line, Acc) -> {list_to_binary(lists:reverse(Acc)),Line}; -get_binary(N, [A,B|Line], Acc) -> +do_get_binary(N, [A,B|Line], Acc) -> Byte = (get_hex_digit(A) bsl 4) bor get_hex_digit(B), - get_binary(N-1, Line, [Byte|Acc]); -get_binary(_N, [], _Acc) -> + do_get_binary(N-1, Line, [Byte|Acc]); +do_get_binary(_N, [], _Acc) -> {'#CDVTruncatedBinary',[]}. -cdvbin(Sz,Pos) -> - "#CDVBin<"++integer_to_list(Sz)++","++integer_to_list(Pos)++">". - +cdvbin(Offset,Size,{'#CDVBin',Pos}) -> + ['#CDVBin',Offset,Size,Pos]; +cdvbin(Offset,Size,['#CDVBin',_,_,Pos]) -> + ['#CDVBin',Offset,Size,Pos]; +cdvbin(_,_,'#CDVTruncatedBinary') -> + '#CDVTruncatedBinary'. %%----------------------------------------------------------------- %% Functions for accessing the cdv_dump_index_table @@ -2947,29 +2448,15 @@ reset_index_table() -> insert_index(Tag,Id,Pos) -> ets:insert(cdv_dump_index_table,{{Tag,Pos},Id}). +lookup_index({Tag,Id}) -> + lookup_index(Tag,Id); lookup_index(Tag) -> lookup_index(Tag,'$2'). lookup_index(Tag,Id) -> ets:select(cdv_dump_index_table,[{{{Tag,'$1'},Id},[],[{{Id,'$1'}}]}]). -lookup_index_chunk({'#CDVFirstChunk',Tag,Id}) -> - ets:select(cdv_dump_index_table, - [{{{Tag,'$1'},Id},[],[{{Id,'$1'}}]}], - ?items_chunk_size); -lookup_index_chunk(Cont) -> - ets:select(Cont). - -%% Create a tag which can be used instead of an ets Continuation for -%% the first call to lookup_index_chunk. -first_chunk_pointer({Tag,Id}) -> - {'#CDVFirstChunk',Tag,Id}; -first_chunk_pointer(Tag) -> - first_chunk_pointer({Tag,'$2'}). - count_index(Tag) -> ets:select_count(cdv_dump_index_table,[{{{Tag,'_'},'_'},[],[true]}]). -count_index(Tag,Id) -> - ets:select_count(cdv_dump_index_table,[{{{Tag,'_'},Id},[],[true]}]). %%----------------------------------------------------------------- @@ -2979,7 +2466,6 @@ tag_to_atom("allocated_areas") -> ?allocated_areas; tag_to_atom("allocator") -> ?allocator; tag_to_atom("atoms") -> ?atoms; tag_to_atom("binary") -> ?binary; -tag_to_atom("debug_proc_dictionary") -> ?debug_proc_dictionary; tag_to_atom("end") -> ?ende; tag_to_atom("erl_crash_dump") -> ?erl_crash_dump; tag_to_atom("ets") -> ?ets; @@ -2995,7 +2481,6 @@ tag_to_atom("mod") -> ?mod; tag_to_atom("no_distribution") -> ?no_distribution; tag_to_atom("node") -> ?node; tag_to_atom("not_connected") -> ?not_connected; -tag_to_atom("num_atoms") -> ?num_atoms; tag_to_atom("old_instr_data") -> ?old_instr_data; tag_to_atom("port") -> ?port; tag_to_atom("proc") -> ?proc; @@ -3010,37 +2495,133 @@ tag_to_atom(UnknownTag) -> list_to_atom(UnknownTag). %%%----------------------------------------------------------------- -%%% Create a page by sending chunk by chunk to crashdump_viewer_html -chunk_page(SessionId,File,TW,What,HtmlCB,HtmlExtra,ParseFun) -> +%%% Fetch next chunk from crashdump file +lookup_and_parse_index(File,What,ParseFun,Str) when is_list(File) -> + Indices = lookup_index(What), + Fun = fun(Fd,{Id,Start}) -> + pos_bof(Fd,Start), + ParseFun(Fd,Id) + end, + Report = "Processing " ++ Str, + progress_pmap(Report,File,Fun,Indices). + +%%%----------------------------------------------------------------- +%%% Convert a record to a proplist +to_proplist(Fields,Record) -> + Values = to_value_list(Record), + lists:zip(Fields,Values). + +%%%----------------------------------------------------------------- +%%% Convert a record to a simple list of field values +to_value_list(Record) -> + [_RecordName|Values] = tuple_to_list(Record), + Values. + +%%%----------------------------------------------------------------- +%%% Fold over List and report progress in percent. +%%% Report is the text to be presented in the progress dialog. +%%% Acc0 is the initial accumulator and will be passed to Fun as the +%%% second arguement, i.e. Fun = fun(Item,Acc) -> NewAcc end. +progress_foldl(Report,Fun,Acc0,List) -> + init_progress(Report, length(List)), + progress_foldl1(Fun,Acc0,List). + +progress_foldl1(Fun,Acc,[H|T]) -> + update_progress(), + progress_foldl1(Fun,Fun(H,Acc),T); +progress_foldl1(_Fun,Acc,[]) -> + end_progress(), + Acc. + + +%%%----------------------------------------------------------------- +%%% Map over List and report progress in percent. +%%% Report is the text to be presented in the progress dialog. +%%% Distribute the load over a number of processes, and File is opened +%%% on each process and passed to the Fun as first argument. +%%% I.e. Fun = fun(Fd,Item) -> ItemResult end. +progress_pmap(Report,File,Fun,List) -> + NTot = length(List), + NProcs = erlang:system_info(schedulers) * 2, + NPerProc = (NTot div NProcs) + 1, + + %% Worker processes send message to collector for each ReportInterval. + ReportInterval = (NTot div 100) + 1, + + %% Progress reporter on collector process reports 1 percent for + %% each message from worker process. + init_progress(Report,99), + + Collector = self(), + {[],Pids} = + lists:foldl( + fun(_,{L,Ps}) -> + {L1,L2} = if length(L)>=NPerProc -> lists:split(NPerProc,L); + true -> {L,[]} % last chunk + end, + P = spawn( + fun() -> + progress_map(Collector,ReportInterval,File,Fun,L1) + end), + erlang:monitor(process,P), + {L2,[P|Ps]} + end, + {List,[]}, + lists:seq(1,NProcs)), + collect(Pids,[]). + +progress_map(Collector,ReportInterval,File,Fun,List) -> Fd = open(File), - case lookup_and_parse_index_chunk(first_chunk_pointer(What),Fd,ParseFun) of - done -> - crashdump_viewer_html:chunk_page(HtmlCB,SessionId,TW,HtmlExtra,done); - {Chunk,Cont} -> - HtmlInfo = crashdump_viewer_html:chunk_page( - HtmlCB, - SessionId,TW,HtmlExtra,Chunk), - chunk_page_1(Fd,HtmlInfo,SessionId,ParseFun, - lookup_and_parse_index_chunk(Cont,Fd,ParseFun)) - end. - -chunk_page_1(_Fd,HtmlInfo,SessionId,_ParseFun,done) -> - crashdump_viewer_html:chunk(SessionId,done,HtmlInfo); -chunk_page_1(Fd,HtmlInfo,SessionId,ParseFun,{Chunk,Cont}) -> - crashdump_viewer_html:chunk(SessionId,Chunk,HtmlInfo), - chunk_page_1(Fd,HtmlInfo,SessionId,ParseFun, - lookup_and_parse_index_chunk(Cont,Fd,ParseFun)). - -lookup_and_parse_index_chunk(Pointer,Fd,ParseFun) -> - case lookup_index_chunk(Pointer) of - '$end_of_table' -> - close(Fd), - done; - {Chunk,Cont} -> - R = lists:map(fun({Id,Start}) -> - pos_bof(Fd,Start), - ParseFun(Fd,Id) - end, - Chunk), - {R,Cont} + init_progress(ReportInterval, fun(_) -> Collector ! progress end, ok), + progress_map(Fd,Fun,List,[]). +progress_map(Fd,Fun,[H|T],Acc) -> + update_progress(), + progress_map(Fd,Fun,T,[Fun(Fd,H)|Acc]); +progress_map(Fd,_Fun,[],Acc) -> + close(Fd), + exit({pmap_done,Acc}). + +collect([],Acc) -> + end_progress(), + lists:append(Acc); +collect(Pids,Acc) -> + receive + progress -> + update_progress(), + collect(Pids,Acc); + {'DOWN', _Ref, process, Pid, {pmap_done,Result}} -> + collect(lists:delete(Pid,Pids),[Result|Acc]) end. + +%%%----------------------------------------------------------------- +%%% Help functions for progress reporting + +%% Set text in progress dialog and initialize the progress counter +init_progress(Report,N) -> + observer_lib:report_progress({ok,Report}), + Interval = (N div 100) + 1, + Fun = fun(P0) -> P=P0+1,observer_lib:report_progress({ok,P}),P end, + init_progress(Interval,Fun,0). +init_progress(Interval,Fun,Acc) -> + put(progress,{Interval,Interval,Fun,Acc}), + ok. + +%% Count progress and report on given interval +update_progress() -> + update_progress(1). +update_progress(Processed) -> + do_update_progress(get(progress),Processed). + +do_update_progress({Count,Interval,Fun,Acc},Processed) when Processed>Count -> + do_update_progress({Interval,Interval,Fun,Fun(Acc)},Processed-Count); +do_update_progress({Count,Interval,Fun,Acc},Processed) -> + put(progress,{Count-Processed,Interval,Fun,Acc}), + ok. + +%% End progress reporting for this item +end_progress() -> + end_progress({ok,100}). +end_progress(Report) -> + observer_lib:report_progress(Report), + erase(progress), + ok. |