diff options
Diffstat (limited to 'lib/observer/src/crashdump_viewer.erl')
-rw-r--r-- | lib/observer/src/crashdump_viewer.erl | 2566 |
1 files changed, 2566 insertions, 0 deletions
diff --git a/lib/observer/src/crashdump_viewer.erl b/lib/observer/src/crashdump_viewer.erl new file mode 100644 index 0000000000..b323d86ea4 --- /dev/null +++ b/lib/observer/src/crashdump_viewer.erl @@ -0,0 +1,2566 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2003-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(crashdump_viewer). + +%% +%% 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. +%% +%% 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 and end positions for +%% this item-information. All tags start with a "=" at the beginning of +%% a line. +%% +%% Process state +%% ------------- +%% file: The name of the crashdump currently viewed. +%% 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. +%% sorted: atom(), 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]). + +%% 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/2, + proc_details/2, + ports/2, + ets_tables/2, + timers/2, + fun_table/2, + atoms/2, + dist_info/2, + loaded_modules/2, + loaded_mod_details/2, + memory/2, + allocated_areas/2, + allocator_info/2, + hash_tables/2, + index_tables/2, + sort_procs/2, + expand/2, + expand_binary/2, + expand_memory/2, + next/2]). + + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +%% Debug support +-export([debug/1,stop_debug/0]). + +-include("crashdump_viewer.hrl"). +-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(initial_proc_record(Pid), + #proc{pid=Pid, + %% msg_q_len, reds and stack_heap are integers because it must + %% be possible to sort on them. All other fields are strings + msg_q_len=0,reds=0,stack_heap=0, + %% for old dumps start_time, parent and number of heap frament + %% does not exist + start_time="unknown", + parent="unknown", + num_heap_frag="unknown", + %% current_func can be both "current function" and + %% "last scheduled in for" + current_func={"Current Function",?space}, + %% stack_dump, message queue and dictionaries should only be + %% displayed as a link to "Expand" (if dump is from OTP R9B + %% or newer) + _=?space}). + +-record(state,{file,procs_summary,sorted,shared_heap=false, + wordsize=4,num_atoms="unknown",binaries,bg_status}). + +%%%----------------------------------------------------------------- +%%% Debugging +%% Start tracing with +%% debug(Functions). +%% Functions = local | global | FunctionList +%% FunctionList = [Function] +%% Function = {FunctionName,Arity} | FunctionName +debug(F) -> + ttb:tracer(all,[{file,"cdv"}]), % tracing all nodes + ttb:p(all,[call,timestamp]), + MS = [{'_',[],[{return_trace},{message,{caller}}]}], + tp(F,MS), + ttb:ctp(?MODULE,stop_debug), % don't want tracing of the stop_debug func + ok. +tp([{M,F,A}|T],MS) -> % mod:func/arity + ttb:tpl(M,F,A,MS), + tp(T,MS); +tp([{M,F}|T],MS) -> % mod:func + ttb:tpl(M,F,MS), + tp(T,MS); +tp([M|T],MS) -> % mod + ttb:tp(M,MS), % only exported + tp(T,MS); +tp([],_MS) -> + ok. +stop_debug() -> + ttb:stop([format]). + +%%%----------------------------------------------------------------- +%%% User API +start() -> + webtool:start(), + receive after 1000 -> ok end, + webtool:start_tools([],"app=crashdump_viewer"), + receive after 1000 -> ok end, + ok. + +stop() -> + webtool:stop_tools([],"app=crashdump_viewer"), + webtool:stop(). + +%%%----------------------------------------------------------------- +%%% 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_link() -> + case whereis(?SERVER) of + undefined -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []); + Pid -> + {ok,Pid} + 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}). + +%%%----------------------------------------------------------------- +%%% The following functions are called when menu items are clicked. +general_info(_Env,_Input) -> + call(general_info). +processes(_Env,_Input) -> + call(procs_summary). +ports(_Env,Input) -> % this is also called when a link to a port is clicked + call({ports,Input}). +ets_tables(_Env,Input) -> + call({ets_tables,Input}). +timers(_Env,Input) -> + call({timers,Input}). +fun_table(_Env,_Input) -> + call(funs). +atoms(_Env,_Input) -> + call(atoms). +dist_info(_Env,_Input) -> + call(dist_info). +loaded_modules(_Env,_Input) -> + call(loaded_mods). +loaded_mod_details(_Env,Input) -> + call({loaded_mod_details,Input}). +memory(_Env,_Input) -> + call(memory). +allocated_areas(_Env,_Input) -> + call(allocated_areas). +allocator_info(_Env,_Input) -> + call(allocator_info). +hash_tables(_Env,_Input) -> + call(hash_tables). +index_tables(_Env,_Input) -> + 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(_Env,Input) -> + call({sort_procs,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}). + +%%%----------------------------------------------------------------- +%%% Called when "<< xxx bytes>>" link in a stack dump, message queue or +%%% dictionary is clicked. +expand_binary(_Env,Input) -> + call({expand_binary,Input}). + +%%%----------------------------------------------------------------- +%%% Called when the "Next" link under atoms is clicked. +next(_Env,Input) -> + call({next,Input}). + +%%%----------------------------------------------------------------- +%%% Called on regular intervals while waiting for a dump to be read +redirect(_Env,_Input) -> + call(redirect). + +%%==================================================================== +%% Server functions +%%==================================================================== + +%%-------------------------------------------------------------------- +%% Function: init/1 +%% Description: Initiates the server +%% Returns: {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%%-------------------------------------------------------------------- +init([]) -> + ets:new(cdv_menu_table,[set,named_table,{keypos,#menu_item.index},public]), + ets:new(cdv_dump_index_table,[bag,named_table,public]), + {ok, #state{}}. + +%%-------------------------------------------------------------------- +%% Function: handle_call/3 +%% Description: Handling call messages +%% Returns: {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | (terminate/2 is called) +%% {stop, Reason, State} (terminate/2 is called) +%%-------------------------------------------------------------------- +handle_call(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,File0} = get_value("path",httpd:parse_query(Input)), + File = + case File0 of + [$"|FileAndSome] -> + %% Opera adds \"\" around the filename! + [$"|Elif] = lists:reverse(FileAndSome), + lists:reverse(Elif); + _ -> + File0 + end, + 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}) -> + GenInfo = general_info(File), + NumAtoms = GenInfo#general_info.num_atoms, + {WS,SH} = parse_vsn_str(GenInfo#general_info.system_vsn,4,false), + Reply = crashdump_viewer_html:general_info(GenInfo), + {reply,Reply,State#state{shared_heap=SH,wordsize=WS,num_atoms=NumAtoms}}; +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), + Fd = open(File), + pos_bof(Fd,Pos), + {Bin,_Line} = get_binary(val(Fd)), + close(Fd), + Reply=crashdump_viewer_html:expanded_binary(io_lib:format("~p",[Bin])), + {reply,Reply,State}; +handle_call({next,Input},_From,State=#state{file=File}) -> + [{"pos",Pos},{"num",N},{"start",Start},{"what",What}] = + httpd:parse_query(Input), + Tags = related_tags(What), + TW = truncated_warning(Tags), + Next = get_next(File,list_to_integer(Pos),list_to_integer(N), + list_to_integer(Start),What), + Reply = crashdump_viewer_html:next(Next,TW), + {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,_From,State=#state{file=File,shared_heap=SH}) -> + ProcsSummary = + case State#state.procs_summary of + undefined -> procs_summary(File); + PS -> PS + end, + TW = truncated_warning(["=proc"]), + Reply = crashdump_viewer_html:procs_summary("pid",ProcsSummary,TW,SH), + {reply,Reply,State#state{procs_summary=ProcsSummary,sorted="pid"}}; +handle_call({sort_procs,Input}, _From, State=#state{shared_heap=SH}) -> + {ok,Sort} = get_value("sort",httpd:parse_query(Input)), + {ProcsSummary,Sorted} = do_sort_procs(Sort, + State#state.procs_summary, + State#state.sorted), + TW = truncated_warning(["=proc"]), + Reply = crashdump_viewer_html:procs_summary(Sort,ProcsSummary,TW,SH), + {reply,Reply,State#state{sorted=Sorted}}; +handle_call({proc_details,Input},_From,State=#state{file=File,shared_heap=SH}) -> + {ok,Pid} = get_value("pid",httpd:parse_query(Input)), + Reply = + case get_proc_details(File,Pid) 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) + end, + {reply, Reply, State}; +handle_call({ports,Input},_From,State=#state{file=File}) -> + Reply = + case get_value("port",httpd:parse_query(Input)) of + {ok,P} -> + Id = [$#|P], + case get_port(File,Id) of + {ok,PortInfo} -> + TW = truncated_warning([{"=port",Id}]), + crashdump_viewer_html:ports(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) + end; + error -> % no port identity in Input - get all ports + Ports=get_ports(File), + TW = truncated_warning(["=port"]), + crashdump_viewer_html:ports("Port Information",Ports,TW) + end, + {reply,Reply,State}; +handle_call({ets_tables,Input},_From,State=#state{file=File,wordsize=WS}) -> + {Pid,Heading,InternalEts} = + case get_value("pid",httpd:parse_query(Input)) of + {ok,P} -> + {P,["ETS Tables for Process ",P],[]}; + error -> + I = get_internal_ets_tables(File,WS), + {'_',"ETS Table Information",I} + end, + EtsTables = get_ets_tables(File,Pid,WS), + TW = truncated_warning(["=ets"]), + Reply = crashdump_viewer_html:ets_tables(Heading,EtsTables,InternalEts,TW), + {reply,Reply,State}; +handle_call({timers,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 -> {'_',"Timer Information"} + end, + Timers=get_timers(File,Pid), + TW = truncated_warning(["=timer"]), + Reply = crashdump_viewer_html:timers(Heading,Timers,TW), + {reply,Reply,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), + {reply,Reply,State}; +handle_call(loaded_mods,_From,State=#state{file=File}) -> + LoadedMods=loaded_mods(File), + TW = truncated_warning(["=mod"]), + Reply = crashdump_viewer_html:loaded_mods(LoadedMods,TW), + {reply,Reply,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), + TW = truncated_warning([{"=mod",Mod}]), + Reply = crashdump_viewer_html:loaded_mod_details(ModInfo,TW), + {reply,Reply,State}; +handle_call(funs,_From,State=#state{file=File}) -> + Funs=funs(File), + TW = truncated_warning(["=fun"]), + Reply = crashdump_viewer_html:funs(Funs,TW), + {reply,Reply,State}; +handle_call(atoms,_From,State=#state{file=File,num_atoms=Num}) -> + Atoms=atoms(File), + TW = truncated_warning(["=atoms","=num_atoms"]), + Reply = crashdump_viewer_html:atoms(Atoms,Num,TW), + {reply,Reply,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}; +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}; +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}; +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}; +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}. + + + +%%-------------------------------------------------------------------- +%% Function: handle_cast/2 +%% Description: Handling cast messages +%% Returns: {noreply, State} | +%% {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}}. + +%%-------------------------------------------------------------------- +%% Function: handle_info/2 +%% Description: Handling all non call/cast messages +%% Returns: {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%%-------------------------------------------------------------------- +handle_info(_Info, State) -> + {noreply, State}. + +%%-------------------------------------------------------------------- +%% Function: terminate/2 +%% Description: Shutdown the server +%% Returns: any (ignored by gen_server) +%%-------------------------------------------------------------------- +terminate(_Reason, _State) -> + ok. + +%%-------------------------------------------------------------------- +%% Func: code_change/3 +%% Purpose: Convert process state when code is changed +%% Returns: {ok, NewState} +%%-------------------------------------------------------------------- +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +call(Request) -> + gen_server:call(?SERVER,Request,?call_timeout). + +cast(Msg) -> + gen_server:cast(?SERVER,Msg). + +unexpected(_Fd,{eof,_LastLine},_Where) -> + ok; % truncated file +unexpected(Fd,{part,What},Where) -> + skip_rest_of_line(Fd), + io:format("WARNING: Found unexpected line in ~s:~n~s ...~n",[Where,What]); +unexpected(_Fd,What,Where) -> + io:format("WARNING: Found unexpected line in ~s:~n~s~n",[Where,What]). + +truncated_warning([]) -> + []; +truncated_warning([Tag|Tags]) -> + case truncated_here(Tag) of + true -> truncated_warning(); + false -> truncated_warning(Tags) + end. +truncated_warning() -> + ["WARNING: The crash dump is truncated here. " + "Some information might be missing."]. + +truncated_here(Tag) -> + case get(truncated) of + true -> + case get(last_tag) of + Tag -> % Tag == {TagType,Id} + true; + {Tag,_Id} -> + true; + _LastTag -> + truncated_earlier(Tag) + end; + false -> + false + end. + + +%% Check if the dump was truncated with the same tag, but earlier id. +%% Eg if this is {"=proc","<0.30.0>"}, we should warn if the dump was +%% truncated in {"=proc","<0.29.0>"} or earlier +truncated_earlier({"=proc",Pid}) -> + compare_pid(Pid,get(truncated_proc)); +truncated_earlier(_Tag) -> + false. + +compare_pid("<"++Id,"<"++OtherId) -> + 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. +close(Fd) -> + erase(chunk), + file:close(Fd). +pos_bof(Fd,Pos) -> + reset_chunk(), + file:position(Fd,{bof,Pos}). + +get_chunk(Fd) -> + case erase(chunk) of + undefined -> + case read(Fd) of + eof -> + put_pos(Fd), + eof; + Other -> + Other + end; + Bin -> + {ok,Bin} + end. + +read(Fd) -> + file:read(Fd,?chunk_size). + +put_chunk(Fd,Bin) -> + {ok,Pos0} = file:position(Fd,cur), + Pos = Pos0 - byte_size(Bin), + put(chunk,Bin), + put(pos,Pos). + +put_pos(Fd) -> + {ok,Pos} = file:position(Fd,cur), + put(pos,Pos). + +reset_chunk() -> + erase(chunk), + erase(pos). + +line_head(Fd) -> + case get_chunk(Fd) of + {ok,Bin} -> line_head(Fd,Bin,[],0); + eof -> {eof,[]} + end. +line_head(Fd,Bin,Acc,?max_line_size) -> + put_chunk(Fd,Bin), + {part,lists:reverse(Acc)}; +line_head(Fd,<<$\n:8,Bin/binary>>,Acc,_N) -> + put_chunk(Fd,Bin), + lists:reverse(Acc); +line_head(Fd,<<$::8,$\r:8,$\n:8,Bin/binary>>,Acc,_N) -> + put_chunk(Fd,Bin), + lists:reverse(Acc); +line_head(Fd,<<$::8,$\r:8>>,Acc,N) -> + case get_chunk(Fd) of + {ok,Bin} -> line_head(Fd,<<$:,Bin/binary>>,Acc,N); + eof -> {eof,lists:reverse(Acc)} + end; +line_head(Fd,<<$::8>>,Acc,N) -> + case get_chunk(Fd) of + {ok,Bin} -> line_head(Fd,<<$:,Bin/binary>>,Acc,N); + eof -> {eof,lists:reverse(Acc)} + end; +line_head(Fd,<<$::8,Space:8,Bin/binary>>,Acc,_N) when Space=:=$ ;Space=:=$\n -> + put_chunk(Fd,Bin), + lists:reverse(Acc); +line_head(Fd,<<$::8,Bin/binary>>,Acc,_N) -> + put_chunk(Fd,Bin), + lists:reverse(Acc); +line_head(Fd,<<$\r:8,Bin/binary>>,Acc,N) -> + line_head(Fd,Bin,Acc,N+1); +line_head(Fd,<<Char:8,Bin/binary>>,Acc,N) -> + line_head(Fd,Bin,[Char|Acc],N+1); +line_head(Fd,<<>>,Acc,N) -> + case get_chunk(Fd) of + {ok,Bin} -> line_head(Fd,Bin,Acc,N); + eof -> {eof,lists:reverse(Acc)} + end. + +skip_rest_of_line(Fd) -> + case get_chunk(Fd) of + {ok,Bin} -> skip(Fd,Bin); + eof -> ok + end. +skip(Fd,<<$\n:8,Bin/binary>>) -> + put_chunk(Fd,Bin), + ok; +skip(Fd,<<_Char:8,Bin/binary>>) -> + skip(Fd,Bin); +skip(Fd,<<>>) -> + case get_chunk(Fd) of + {ok,Bin} -> skip(Fd,Bin); + eof -> ok + end. + + +val(Fd) -> + case get_rest_of_line(Fd) of + {eof,[]} -> "-1"; + [] -> "-1"; + {eof,Val} -> Val; + Val -> Val + end. + +get_rest_of_line(Fd) -> + case get_chunk(Fd) of + {ok,Bin} -> get_rest_of_line_1(Fd, Bin, []); + eof -> {eof,[]} + end. + +get_rest_of_line_1(Fd, <<$\n:8,Bin/binary>>, Acc) -> + put_chunk(Fd, Bin), + lists:reverse(Acc); +get_rest_of_line_1(Fd, <<$\r:8,Rest/binary>>, Acc) -> + get_rest_of_line_1(Fd, Rest, Acc); +get_rest_of_line_1(Fd, <<Char:8,Rest/binary>>, Acc) -> + get_rest_of_line_1(Fd, Rest, [Char|Acc]); +get_rest_of_line_1(Fd, <<>>, Acc) -> + case get_chunk(Fd) of + {ok,Bin} -> get_rest_of_line_1(Fd, Bin, Acc); + eof -> {eof,lists:reverse(Acc)} + end. + +count_rest_of_line(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}; + 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) -> + 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) -> + case get_chunk(Fd) of + {ok,Bin} -> count_rest_of_tag(Fd,Bin,N); + eof -> N + end. + +split(Str) -> + split($ ,Str,[]). +split(Char,Str) -> + split(Char,Str,[]). +split(Char,[Char|Str],Acc) -> % match Char + {lists:reverse(Acc),Str}; +split(_Char,[$\r,$\n|Str],Acc) -> % new line + {lists:reverse(Acc),Str}; +split(_Char,[$\n|Str],Acc) -> % new line + {lists:reverse(Acc),Str}; +split(Char,[H|T],Acc) -> + split(Char,T,[H|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) -> + 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; + [_Char|Rest] -> + parse_vsn_str(Rest,WS,SH) + 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(0, {"./ets_tables","ETS tables"}, 0), + 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) -> + case file:read_file_info(File) of + {ok,#file_info{type=regular,access=FileA}} when FileA=:=read; + FileA=:=read_write -> + Fd = open(File), + case read(Fd) of + {ok,<<$=:8,TagAndRest/binary>>} -> + {Tag,Id,Rest,N1} = tag(Fd,TagAndRest,1), + case Tag of + "=erl_crash_dump" -> + ets:delete_all_objects(cdv_dump_index_table), + ets:insert(cdv_dump_index_table,{Tag,Id,N1+1}), + put(last_tag,{Tag,""}), + Status = background_status(processing,File), + background_status(Status), + indexify(Fd,Rest,N1), + check_if_truncated(), + initial_menu(), + Binaries = read_binaries(Fd), + R = crashdump_viewer_html:start_page(), + close(Fd), + background_done({R,File,Binaries}); + _Other -> + R = crashdump_viewer_html:error( + "~s is not an Erlang crash dump~n", + [File]), + close(Fd), + background_done({R,undefined,undefined}) + end; + {ok,<<"<Erlang crash dump>",_Rest/binary>>} -> + %% old version - no longer supported + R = crashdump_viewer_html:error( + "The crashdump ~s is in the pre-R10B format, " + "which is no longer supported.~n", + [File]), + close(Fd), + background_done({R,undefined,undefined}); + _Other -> + R = crashdump_viewer_html:error( + "~s is not an Erlang crash dump~n", + [File]), + close(Fd), + background_done({R,undefined,undefined}) + end; + _other -> + R = crashdump_viewer_html:error("~s is not an Erlang crash dump~n", + [File]), + background_done({R,undefined,undefined}) + end. + +indexify(Fd,<<"\n=",TagAndRest/binary>>,N) -> + {Tag,Id,Rest,N1} = tag(Fd,TagAndRest,N+2), + ets:insert(cdv_dump_index_table,{Tag,Id,N1+1}), % +1 to get past newline + put(last_tag,{Tag,Id}), + indexify(Fd,Rest,N1); +indexify(Fd,<<>>,N) -> + case read(Fd) of + {ok,Chunk} when is_binary(Chunk) -> + indexify(Fd,Chunk,N); + eof -> + eof + end; +indexify(Fd,<<$\n>>,N) -> + %% This clause is needed in case the chunk ends with a newline and + %% the next chunk starts with a tag (i.e. "\n=....") + case read(Fd) of + {ok,Chunk} when is_binary(Chunk) -> + indexify(Fd,<<$\n,Chunk/binary>>,N); + eof -> + eof + end; +indexify(Fd,<<_Char:8,Rest/binary>>,N) -> + indexify(Fd,Rest,N+1). + +tag(Fd,Bin,N) -> + tag(Fd,Bin,N,[],[],tag). +tag(_Fd,<<$\n:8,_/binary>>=Rest,N,Gat,Di,_Now) -> + {[$=|lists:reverse(Gat)],lists:reverse(Di),Rest,N}; +tag(Fd,<<$\r:8,Rest/binary>>,N,Gat,Di,Now) -> + tag(Fd,Rest,N+1,Gat,Di,Now); +tag(Fd,<<$::8,IdAndRest/binary>>,N,Gat,Di,tag) -> + tag(Fd,IdAndRest,N+1,Gat,Di,id); +tag(Fd,<<Char:8,Rest/binary>>,N,Gat,Di,tag) -> + tag(Fd,Rest,N+1,[Char|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 + {ok,Chunk} when is_binary(Chunk) -> + tag(Fd,Chunk,N,Gat,Di,Now); + eof -> + {[$=|lists:reverse(Gat)],lists:reverse(Di),<<>>,N} + end. + +check_if_truncated() -> + case get(last_tag) of + {"=end",_} -> + put(truncated,false), + put(truncated_proc,false); + TruncatedTag -> + put(truncated,true), + find_truncated_proc(TruncatedTag) + end. + +find_truncated_proc({"=atom",_Id}) -> + put(truncated_proc,false); +find_truncated_proc({Tag,Pid}) -> + case is_proc_tag(Tag) of + true -> + put(truncated_proc,Pid); + false -> + %% This means that the dump is truncated between "=proc" and + %% "=proc_heap" => memory info is missing for all procs. + put(truncated_proc,"<0.0.0>") + end. + +is_proc_tag(Tag) when Tag=="=proc"; + Tag=="=proc_dictionary"; + Tag=="=proc_messages"; + Tag=="=proc_dictionary"; + Tag=="=debug_proc_dictionary"; + Tag=="=proc_stack"; + Tag=="=proc_heap" -> + true; +is_proc_tag(_) -> + false. + +related_tags("Atoms") -> + ["=atoms","=num_atoms"]. + +%%% 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) -> + [{"=erl_crash_dump",_Id,Start}] = + ets:lookup(cdv_dump_index_table,"=erl_crash_dump"), + Fd = open(File), + pos_bof(Fd,Start), + Created = case get_rest_of_line(Fd) of + {eof,SomeOfLine} -> SomeOfLine; + WholeLine -> WholeLine + end, + + GI0 = get_general_info(Fd,#general_info{created=Created,_=?space}), + GI = case GI0#general_info.num_atoms of + ?space -> GI0#general_info{num_atoms=get_num_atoms(Fd)}; + _ -> GI0 + end, + + {MemTot,MemMax} = + case ets:lookup(cdv_dump_index_table,"=memory") of + [{"=memory",_,MemStart}] -> + pos_bof(Fd,MemStart), + Memory = get_meminfo(Fd,[]), + Tot = case lists:keysearch("total",1,Memory) of + {value,{_,T}} -> T; + false -> "" + end, + Max = case lists:keysearch("maximum",1,Memory) of + {value,{_,M}} -> M; + false -> "" + end, + {Tot,Max}; + _ -> + {"",""} + end, + + close(Fd), + {NumProcs,NumEts,NumFuns} = count(), + NodeName = + case ets:lookup(cdv_dump_index_table,"=node") of + [{"=node",N,_Start}] -> + N; + [] -> + case ets:lookup(cdv_dump_index_table,"=no_distribution") of + [_] -> "nonode@nohost"; + [] -> "unknown" + end + end, + + InstrInfo = + case ets:member(cdv_dump_index_table,"=old_instr_data") of + true -> + old_instr_data; + false -> + case ets:member(cdv_dump_index_table,"=instr_data") of + true -> + instr_data; + false -> + false + end + end, + GI#general_info{node_name=NodeName, + num_procs=integer_to_list(NumProcs), + num_ets=integer_to_list(NumEts), + num_fun=integer_to_list(NumFuns), + mem_tot=MemTot, + mem_max=MemMax, + instr_info=InstrInfo}. + +get_general_info(Fd,GenInfo) -> + case line_head(Fd) of + "Slogan" -> + get_general_info(Fd,GenInfo#general_info{slogan=val(Fd)}); + "System version" -> + get_general_info(Fd,GenInfo#general_info{system_vsn=val(Fd)}); + "Compiled" -> + get_general_info(Fd,GenInfo#general_info{compile_time=val(Fd)}); + "Atoms" -> + get_general_info(Fd,GenInfo#general_info{num_atoms=val(Fd)}); + "=" ++ _next_tag -> + GenInfo; + Other -> + unexpected(Fd,Other,"general information"), + GenInfo + end. + +get_num_atoms(Fd) -> + case ets:match(cdv_dump_index_table,{"=hash_table","atom_tab",'$1'}) 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 ets:lookup(cdv_dump_index_table,"=num_atoms") of + [] -> + ?space; + [{"=num_atoms",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() -> + {ets:select_count(cdv_dump_index_table,count_ms("=proc")), + ets:select_count(cdv_dump_index_table,count_ms("=ets")), + ets:select_count(cdv_dump_index_table,count_ms("=fun"))}. + +count_ms(Tag) -> + [{{Tag,'_','_'},[],[true]}]. + + +procs_summary(File) -> + AllProcs = ets:lookup(cdv_dump_index_table,"=proc"), + Fd = open(File), + R = lists:map(fun({"=proc",Pid,Start}) -> + pos_bof(Fd,Start), + get_procinfo(Fd,fun main_procinfo/4, + ?initial_proc_record(Pid)) + end, + AllProcs), + close(Fd), + R. + +get_proc_details(File,Pid) -> + DumpVsn = ets:lookup_element(cdv_dump_index_table,"=erl_crash_dump",2), + case ets:match(cdv_dump_index_table,{"=proc",Pid,'$1'}) of + [[Start]] -> + Fd = open(File), + pos_bof(Fd,Start), + Proc0 = + case DumpVsn of + "0.0" -> + %% Old version (translated) + ?initial_proc_record(Pid); + _ -> + (?initial_proc_record(Pid))#proc{ + 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)} + end, + Proc = get_procinfo(Fd,fun all_procinfo/4,Proc0), + close(Fd), + {ok,Proc}; + _ -> + 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 ets:select_count(cdv_dump_index_table,[{{Tag,Key,'_'},[],[true]}]) 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 + end. + +get_procinfo(Fd,Fun,Proc) -> + 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}); + "Name" -> + get_procinfo(Fd,Fun,Proc#proc{name=val(Fd)}); + "Spawned as" -> + IF = val(Fd), + case Proc#proc.name of + ?space -> + get_procinfo(Fd,Fun,Proc#proc{name=IF,init_func=IF}); + _ -> + get_procinfo(Fd,Fun,Proc#proc{init_func=IF}) + end; + "Spawned by" -> + case val(Fd) of + "[]" -> + get_procinfo(Fd,Fun,Proc); + Parent -> + get_procinfo(Fd,Fun,Proc#proc{parent=Parent}) + end; + "Started" -> + get_procinfo(Fd,Fun,Proc#proc{start_time=val(Fd)}); + "Last scheduled in for" -> + get_procinfo(Fd,Fun,Proc#proc{current_func= + {"Last scheduled in for", + val(Fd)}}); + "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))}); + "Number of heap fragments" -> + get_procinfo(Fd,Fun,Proc#proc{num_heap_frag=val(Fd)}); + "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))}); + "OldHeap" -> + get_procinfo(Fd,Fun,Proc#proc{old_heap=val(Fd)}); + "Heap unused" -> + get_procinfo(Fd,Fun,Proc#proc{heap_unused=val(Fd)}); + "OldHeap unused" -> + get_procinfo(Fd,Fun,Proc#proc{old_heap_unused=val(Fd)}); + "New heap start" -> + get_procinfo(Fd,Fun,Proc#proc{new_heap_start=val(Fd)}); + "New heap top" -> + get_procinfo(Fd,Fun,Proc#proc{new_heap_top=val(Fd)}); + "Stack top" -> + get_procinfo(Fd,Fun,Proc#proc{stack_top=val(Fd)}); + "Stack end" -> + get_procinfo(Fd,Fun,Proc#proc{stack_end=val(Fd)}); + "Old heap start" -> + get_procinfo(Fd,Fun,Proc#proc{old_heap_start=val(Fd)}); + "Old heap top" -> + get_procinfo(Fd,Fun,Proc#proc{old_heap_top=val(Fd)}); + "Old heap end" -> + get_procinfo(Fd,Fun,Proc#proc{old_heap_end=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)}); + "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); + "Link list" -> + get_procinfo(Fd,Fun,Proc#proc{links=val(Fd)}); + "Program counter" -> + get_procinfo(Fd,Fun,Proc#proc{prog_count=val(Fd)}); + "CP" -> + get_procinfo(Fd,Fun,Proc#proc{cp=val(Fd)}); + "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); + "=" ++ _next_tag -> + %% DumpVsn=0.1 or newer: No stack dump here + Proc; + Other -> + unexpected(Fd,Other,"process info"), + get_procinfo(Fd,Fun,Proc) + 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}}. + +maybe_other_node(File,Id) -> + Channel = + case split($.,Id) of + {"<" ++ N, _Rest} -> + N; + {"#Port<" ++ N, _Rest} -> + N + end, + Ms = ets:fun2ms( + fun({Tag,Id,Start}) when Tag=:="=visible_node", Id=:=Channel -> + {"Visible Node",Start}; + ({Tag,Id,Start}) when Tag=:="=hidden_node", Id=:=Channel -> + {"Hidden Node",Start}; + ({Tag,Id,Start}) when Tag=:="=not_connected", Id=:=Channel -> + {"Not Connected Node",Start} + 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} + end. + +expand_memory(File,What,Pid,Binaries) -> + Fd = open(File), + 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, + erase(fd), + close(Fd), + Expanded. + +%%% +%%% Read binaries. +%%% +read_binaries(Fd) -> + AllBinaries = ets:match(cdv_dump_index_table,{"=binary",'$1','$2'}), + 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. + + + +%%% +%%% Read top level section. +%%% + +read_stack_dump(Fd,Pid,Dict) -> + case ets:match(cdv_dump_index_table,{"=proc_stack",Pid,'$1'}) of + [[Start]] -> + pos_bof(Fd,Start), + read_stack_dump1(Fd,Dict,[]); + [] -> + [] + end. +read_stack_dump1(Fd,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]) + end. + +parse_top(Line0, D) -> + {Label,Line1} = get_label(Line0), + {Term,Line,D} = parse_term(Line1, D), + [] = skip_blanks(Line), + {Label,Term}. + +%%% +%%% Read message queue. +%%% + +read_messages(Fd,Pid,Dict) -> + case ets:match(cdv_dump_index_table,{"=proc_messages",Pid,'$1'}) of + [[Start]] -> + pos_bof(Fd,Start), + read_messages1(Fd,Dict,[]); + [] -> + [] + end. +read_messages1(Fd,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]) + end. + +parse_message(Line0, D) -> + {Msg,":"++Line1,_} = parse_term(Line0, D), + {Token,Line,_} = parse_term(Line1, D), + [] = skip_blanks(Line), + {Msg,Token}. + +%%% +%%% Read process dictionary +%%% + +read_dictionary(Fd,Tag,Pid,Dict) -> + case ets:match(cdv_dump_index_table,{Tag,Pid,'$1'}) of + [[Start]] -> + pos_bof(Fd,Start), + read_dictionary1(Fd,Dict,[]); + [] -> + [] + end. +read_dictionary1(Fd,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]) + end. + +parse_dictionary(Line0, D) -> + {Entry,Line,_} = parse_term(Line0, D), + [] = skip_blanks(Line), + Entry. + +%%% +%%% Read heap data. +%%% + +read_heap(Fd,Pid,Dict0) -> + case ets:match(cdv_dump_index_table,{"=proc_heap",Pid,'$2'}) of + [[Pos]] -> + pos_bof(Fd,Pos), + read_heap(Dict0); + [] -> + Dict0 + end. + +read_heap(Dict0) -> + %% This function is never called if the dump is truncated in "=proc_heap:Pid" + case get(fd) of + end_of_heap -> + Dict0; + Fd -> + case val(Fd) of + "=" ++ _next_tag -> + put(fd, end_of_heap), + Dict0; + Line -> + Dict = parse(Line,Dict0), + read_heap(Dict) + end + end. + +parse(Line0, Dict0) -> + {Addr,":"++Line1} = get_hex(Line0), + {_Term,Line,Dict} = parse_heap_term(Line1, Addr, Dict0), + [] = skip_blanks(Line), + Dict. + + +do_sort_procs("state",Procs,"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,"pid") -> + {lists:reverse(Procs),"rpid"}; +do_sort_procs("pid",Procs,_) -> + {Procs,"pid"}; +do_sort_procs("msg_q_len",Procs,"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,"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,"mem") -> + {lists:keysort(#proc.stack_heap,Procs),"rmem"}; +do_sort_procs("mem",Procs,_) -> + {lists:reverse(lists:keysort(#proc.stack_heap,Procs)),"mem"}; +do_sort_procs("init_func",Procs,"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,"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,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. + + +get_port(File,Port) -> + case ets:match(cdv_dump_index_table,{"=port",Port,'$1'}) of + [[Start]] -> + Fd = open(File), + R = get_portinfo(Fd,Port,Start), + 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 + end. + +get_ports(File) -> + Ports = ets:lookup(cdv_dump_index_table,"=port"), + Fd = open(File), + R = lists:map(fun({"=port",Id,Start}) -> get_portinfo(Fd,Id,Start) end, + Ports), + close(Fd), + R. + + +get_portinfo(Fd,Id,Start) -> + pos_bof(Fd,Start), + get_portinfo(Fd,#port{id=Id,_=?space}). + +get_portinfo(Fd,Port) -> + case line_head(Fd) of + "Slot" -> + get_portinfo(Fd,Port#port{slot=val(Fd)}); + "Connected" -> + get_portinfo(Fd,Port#port{connected=val(Fd)}); + "Links" -> + get_portinfo(Fd,Port#port{links=val(Fd)}); + "Port controls linked-in driver" -> + get_portinfo(Fd,Port#port{controls=["Linked in driver: " | + val(Fd)]}); + "Port controls external process" -> + get_portinfo(Fd,Port#port{controls=["External proc: " | val(Fd)]}); + "Port is a file" -> + get_portinfo(Fd,Port#port{controls=["File: "| val(Fd)]}); + "Port is UNIX fd not opened by emulator" -> + get_portinfo(Fd,Port#port{ + controls=["UNIX fd not opened by emulator: "| + val(Fd)]}); + "=" ++ _next_tag -> + Port; + Other -> + unexpected(Fd,Other,"port info"), + Port + end. + +get_ets_tables(File,Pid,WS) -> + EtsTables = ets:match_object(cdv_dump_index_table,{"=ets",Pid,'_'}), + Fd = open(File), + R = lists:map(fun({"=ets",P,Start}) -> + get_etsinfo(Fd,P,Start,WS) + end, + EtsTables), + close(Fd), + R. + +get_internal_ets_tables(File,WS) -> + InternalEts = ets:match_object(cdv_dump_index_table, + {"=internal_ets",'_','_'}), + Fd = open(File), + R = lists:map(fun({"=internal_ets",Descr,Start}) -> + {Descr,get_etsinfo(Fd,undefined,Start,WS)} + end, + InternalEts), + close(Fd), + R. + +get_etsinfo(Fd,Pid,Start,WS) -> + pos_bof(Fd,Start), + get_etsinfo(Fd,#ets_table{pid=Pid,type="hash",_=?space},WS). + +get_etsinfo(Fd,EtsTable,WS) -> + case line_head(Fd) of + "Slot" -> + get_etsinfo(Fd,EtsTable#ets_table{slot=val(Fd)},WS); + "Table" -> + get_etsinfo(Fd,EtsTable#ets_table{id=val(Fd)},WS); + "Name" -> + get_etsinfo(Fd,EtsTable#ets_table{name=val(Fd)},WS); + "Ordered set (AVL tree), Elements" -> + 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); + "Objects" -> + get_etsinfo(Fd,EtsTable#ets_table{size=val(Fd)},WS); + "Words" -> + Words = list_to_integer(val(Fd)), + Bytes = + case Words of + -1 -> "-1"; % probably truncated + _ -> integer_to_list(Words * WS) + end, + get_etsinfo(Fd,EtsTable#ets_table{memory=Bytes},WS); + "=" ++ _next_tag -> + EtsTable; + Other -> + unexpected(Fd,Other,"ETS info"), + EtsTable + end. + +get_timers(File,Pid) -> + Timers = ets:match_object(cdv_dump_index_table,{"=timer",Pid,'$1'}), + Fd = open(File), + R = lists:map(fun({"=timer",P,Start}) -> + get_timerinfo(Fd,P,Start) + end, + Timers), + close(Fd), + R. + +get_timerinfo(Fd,Pid,Start) -> + pos_bof(Fd,Start), + get_timerinfo(Fd,#timer{pid=Pid,_=?space}). + +get_timerinfo(Fd,Timer) -> + case line_head(Fd) of + "Message" -> + get_timerinfo(Fd,Timer#timer{msg=val(Fd)}); + "Time left" -> + get_timerinfo(Fd,Timer#timer{time=val(Fd)}); + "=" ++ _next_tag -> + Timer; + Other -> + unexpected(Fd,Other,"timer info"), + Timer + end. + +nods(File) -> + case ets:lookup(cdv_dump_index_table,"=no_distribution") of + [] -> + V = ets:lookup(cdv_dump_index_table,"=visible_node"), + H = ets:lookup(cdv_dump_index_table,"=hidden_node"), + N = ets:lookup(cdv_dump_index_table,"=not_connected"), + Fd = open(File), + Visible = lists:map( + fun({"=visible_node",Channel,Start}) -> + get_nodeinfo(Fd,Channel,Start) + end, + V), + Hidden = lists:map( + fun({"=hidden_node",Channel,Start}) -> + get_nodeinfo(Fd,Channel,Start) + end, + H), + NotConnected = lists:map( + fun({"=not_connected",Channel,Start}) -> + get_nodeinfo(Fd,Channel,Start) + end, + N), + close(Fd), + {Visible,Hidden,NotConnected}; + [_] -> + no_distribution + end. + +get_nodeinfo(Fd,Channel,Start) -> + pos_bof(Fd,Start), + get_nodeinfo(Fd,#nod{channel=Channel,_=?space}). + +get_nodeinfo(Fd,Nod) -> + case line_head(Fd) of + "Name" -> + get_nodeinfo(Fd,Nod#nod{name=val(Fd)}); + "Controller" -> + get_nodeinfo(Fd,Nod#nod{controller=val(Fd)}); + "Creation" -> + get_nodeinfo(Fd,Nod#nod{creation=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]}); + "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]}); + "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]}); + "Error" -> + get_nodeinfo(Fd,Nod#nod{error=val(Fd)}); + "=" ++ _next_tag -> + Nod; + Other -> + unexpected(Fd,Other,"node info"), + Nod + end. + +loaded_mods(File) -> + case ets:lookup(cdv_dump_index_table,"=loaded_modules") of + [{"=loaded_modules",_,StartTotal}] -> + Fd = open(File), + pos_bof(Fd,StartTotal), + {CC,OC} = get_loaded_mod_totals(Fd,{"unknown","unknown"}), + + Mods = ets:lookup(cdv_dump_index_table,"=mod"), + LM = lists:map(fun({"=mod",M,Start}) -> + pos_bof(Fd,Start), + InitLM = #loaded_mod{mod=M,_=?space}, + get_loaded_mod_info(Fd,InitLM, + fun main_modinfo/3) + end, + Mods), + close(Fd), + {CC,OC,LM}; + [] -> + {"unknown","unknown",[]} + end. + +get_loaded_mod_totals(Fd,{CC,OC}) -> + case line_head(Fd) of + "Current code" -> + get_loaded_mod_totals(Fd,{val(Fd),OC}); + "Old code" -> + get_loaded_mod_totals(Fd,{CC,val(Fd)}); + "=" ++ _next_tag -> + {CC,OC}; + Other -> + unexpected(Fd,Other,"loaded modules info"), + {CC,OC} % truncated file + end. + +get_loaded_mod_details(File,Mod) -> + [[Start]] = ets:match(cdv_dump_index_table,{"=mod",Mod,'$1'}), + Fd = open(File), + pos_bof(Fd,Start), + InitLM = #loaded_mod{mod=Mod,old_size="No old code exists", + _="No information available"}, + ModInfo = get_loaded_mod_info(Fd,InitLM,fun all_modinfo/3), + close(Fd), + ModInfo. + +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); + "Old size" -> + get_loaded_mod_info(Fd,LM#loaded_mod{old_size=val(Fd)},Fun); + "=" ++ _next_tag -> + LM; + {eof,_} -> + LM; % truncated file + Other -> + LM1 = Fun(Fd,LM,Other), + get_loaded_mod_info(Fd,LM1,Fun) + end. + +main_modinfo(_Fd,LM,_LineHead) -> + LM. +all_modinfo(Fd,LM,LineHead) -> + case LineHead of + "Current attributes" -> + Str = hex_to_str(val(Fd)), + LM#loaded_mod{current_attrib=Str}; + "Current compilation info" -> + Str = hex_to_str(val(Fd)), + LM#loaded_mod{current_comp_info=Str}; + "Old attributes" -> + Str = hex_to_str(val(Fd)), + LM#loaded_mod{old_attrib=Str}; + "Old compilation info" -> + Str = hex_to_str(val(Fd)), + LM#loaded_mod{old_comp_info=Str}; + Other -> + unexpected(Fd,Other,"loaded modules info"), + LM + end. + + +hex_to_str(Hex) -> + Term = hex_to_term(Hex,[]), + io_lib:format("~p~n",[Term]). + +hex_to_term([X,Y|Hex],Acc) -> + MS = hex_to_dec([X]), + LS = hex_to_dec([Y]), + Z = 16*MS+LS, + hex_to_term(Hex,[Z|Acc]); +hex_to_term([],Acc) -> + Bin = list_to_binary(lists:reverse(Acc)), + case catch binary_to_term(Bin) of + {'EXIT',_Reason} -> + {"WARNING: The term is probably truncated!", + "I can not do binary_to_term.", + Bin}; + Term -> + Term + end. + +hex_to_dec("F") -> 15; +hex_to_dec("E") -> 14; +hex_to_dec("D") -> 13; +hex_to_dec("C") -> 12; +hex_to_dec("B") -> 11; +hex_to_dec("A") -> 10; +hex_to_dec(N) -> list_to_integer(N). + + + +funs(File) -> + case ets:lookup(cdv_dump_index_table,"=fun") of + [] -> + []; + AllFuns -> + Fd = open(File), + R = lists:map(fun({"=fun",_,Start}) -> + get_funinfo(Fd,Start) + end, + AllFuns), + close(Fd), + R + end. + +get_funinfo(Fd,Start) -> + pos_bof(Fd,Start), + get_funinfo1(Fd,#fu{_=?space}). + +get_funinfo1(Fd,Fu) -> + case line_head(Fd) of + "Module" -> + get_funinfo1(Fd,Fu#fu{module=val(Fd)}); + "Uniq" -> + get_funinfo1(Fd,Fu#fu{uniq=val(Fd)}); + "Index" -> + get_funinfo1(Fd,Fu#fu{index=val(Fd)}); + "Address" -> + get_funinfo1(Fd,Fu#fu{address=val(Fd)}); + "Native_address" -> + get_funinfo1(Fd,Fu#fu{native_address=val(Fd)}); + "Refc" -> + get_funinfo1(Fd,Fu#fu{refc=val(Fd)}); + "=" ++ _next_tag -> + Fu; + Other -> + unexpected(Fd,Other,"fun info"), + Fu + end. + +atoms(File) -> + case ets:lookup(cdv_dump_index_table,"=atoms") of + [{_atoms,_Id,Start}] -> + Fd = open(File), + pos_bof(Fd,Start), + R = case get_n_lines_of_tag(Fd,100) of + {all,N,Lines} -> + {n_lines,1,N,"Atoms",Lines}; + {part,100,Lines} -> + {n_lines,1,100,"Atoms",Lines,get(pos)}; + empty -> + [] + end, + close(Fd), + R; + _ -> + [] + end. + +memory(File) -> + case ets:lookup(cdv_dump_index_table,"=memory") of + [{"=memory",_,Start}] -> + Fd = open(File), + pos_bof(Fd,Start), + R = get_meminfo(Fd,[]), + close(Fd), + R; + _ -> + [] + end. + +get_meminfo(Fd,Acc) -> + case line_head(Fd) of + "=" ++ _next_tag -> + lists:reverse(Acc); + {eof,_last_line} -> + lists:reverse(Acc); + Key -> + get_meminfo(Fd,[{Key,val(Fd)}|Acc]) + end. + +allocated_areas(File) -> + case ets:lookup(cdv_dump_index_table,"=allocated_areas") of + [{"=allocated_areas",_,Start}] -> + Fd = open(File), + pos_bof(Fd,Start), + R = get_allocareainfo(Fd,[]), + close(Fd), + R; + _ -> + [] + end. + +get_allocareainfo(Fd,Acc) -> + case line_head(Fd) of + "=" ++ _next_tag -> + lists:reverse(Acc); + {eof,_last_line} -> + lists:reverse(Acc); + Key -> + Val = val(Fd), + AllocInfo = + case split(Val) of + {Alloc,[]} -> + {Key,Alloc,?space}; + {Alloc,Used} -> + {Key,Alloc,Used} + end, + get_allocareainfo(Fd,[AllocInfo|Acc]) + end. + +allocator_info(File) -> + case ets:lookup(cdv_dump_index_table,"=allocator") of + [] -> + []; + AllAllocators -> + Fd = open(File), + R = lists:map(fun({"=allocator",Heading,Start}) -> + {Heading,get_allocatorinfo(Fd,Start)} + end, + AllAllocators), + close(Fd), + R + end. + +get_allocatorinfo(Fd,Start) -> + pos_bof(Fd,Start), + get_allocatorinfo1(Fd,[]). + +get_allocatorinfo1(Fd,Acc) -> + case line_head(Fd) of + "=" ++ _next_tag -> + lists:reverse(Acc); + {eof,_last_line} -> + lists:reverse(Acc); + Key -> + Values = get_all_vals(val(Fd),[]), + get_allocatorinfo1(Fd,[{Key,Values}|Acc]) + end. + +get_all_vals([$ |Rest],Acc) -> + [lists:reverse(Acc)|get_all_vals(Rest,[])]; +get_all_vals([],Acc) -> + [lists:reverse(Acc)]; +get_all_vals([Char|Rest],Acc) -> + get_all_vals(Rest,[Char|Acc]). + + +hash_tables(File) -> + case ets:lookup(cdv_dump_index_table,"=hash_table") of + [] -> + []; + AllHashTables -> + Fd = open(File), + R = lists:map(fun({"=hash_table",Name,Start}) -> + get_hashtableinfo(Fd,Name,Start) + end, + AllHashTables), + close(Fd), + R + end. + +get_hashtableinfo(Fd,Name,Start) -> + pos_bof(Fd,Start), + get_hashtableinfo1(Fd,#hash_table{name=Name,_=?space}). + +get_hashtableinfo1(Fd,HashTable) -> + case line_head(Fd) of + "size" -> + get_hashtableinfo1(Fd,HashTable#hash_table{size=val(Fd)}); + "used" -> + get_hashtableinfo1(Fd,HashTable#hash_table{used=val(Fd)}); + "objs" -> + get_hashtableinfo1(Fd,HashTable#hash_table{objs=val(Fd)}); + "depth" -> + get_hashtableinfo1(Fd,HashTable#hash_table{depth=val(Fd)}); + "=" ++ _next_tag -> + HashTable; + Other -> + unexpected(Fd,Other,"hash table information"), + HashTable + end. + +index_tables(File) -> + case ets:lookup(cdv_dump_index_table,"=index_table") of + [] -> + []; + AllIndexTables -> + Fd = open(File), + R = lists:map(fun({"=index_table",Name,Start}) -> + get_indextableinfo(Fd,Name,Start) + end, + AllIndexTables), + close(Fd), + R + end. + +get_indextableinfo(Fd,Name,Start) -> + pos_bof(Fd,Start), + get_indextableinfo1(Fd,#index_table{name=Name,_=?space}). + +get_indextableinfo1(Fd,IndexTable) -> + case line_head(Fd) of + "size" -> + get_indextableinfo1(Fd,IndexTable#index_table{size=val(Fd)}); + "used" -> + get_indextableinfo1(Fd,IndexTable#index_table{used=val(Fd)}); + "limit" -> + get_indextableinfo1(Fd,IndexTable#index_table{limit=val(Fd)}); + "rate" -> + get_indextableinfo1(Fd,IndexTable#index_table{rate=val(Fd)}); + "=" ++ _next_tag -> + IndexTable; + Other -> + unexpected(Fd,Other,"index table information"), + IndexTable + end. + + + + + +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. + + +get_next(File,Pos,N0,Start,What) -> + Fd = open(File), + pos_bof(Fd,Pos), + R = case get_n_lines_of_tag(Fd,N0) of + {all,N,Lines} -> + {n_lines,Start,N,What,Lines}; + {part,N,Lines} -> + {n_lines,Start,N,What,Lines,get(pos)} + 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), + Term = [H|T], + D = gb_trees:insert(Addr, Term, D2), + {Term,Line,D}; +parse_heap_term([$t|Line0], Addr, D) -> %Tuple + {N,":"++Line} = get_hex(Line0), + parse_tuple(N, Line, Addr, D, []); +parse_heap_term([$F|Line0], Addr, 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. + {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 + {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). + 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. + {Pid0,Line} = get_id(Line0), + Pid = "#CDVPid"++Pid0, + D = gb_trees:insert(Addr, Pid, D0), + {Pid,Line,D}; +parse_heap_term([$p|Line0], Addr, D0) -> % External Port. + {Port0,Line} = get_id(Line0), + 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. + {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. + {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), + {Sz,Line} = get_hex(Line2), + 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'; + 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), + {Sz,Line} = get_hex(Line2), + 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'; + none -> '#CDVNonexistingBinary' + end, + D = gb_trees:insert(Addr, Term, D0), + {Term,Line,D}. + + +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 + {Term,[$,|Line],D} when N > 1 -> + parse_tuple(N-1, Line, Addr, D, [Term|Acc]); + {Term,Line,D}-> + parse_tuple(N-1, Line, Addr, D, [Term|Acc]) + end. + +parse_term([$H|Line0], D) -> %Pointer to heap term. + {Ptr,Line} = get_hex(Line0), + deref_ptr(Ptr, Line, D); +parse_term([$N|Line], D) -> %[] (nil). + {[],Line,D}; +parse_term([$I|Line0], D) -> %Small. + {Int,Line} = string:to_integer(Line0), + {Int,Line,D}; +parse_term([$A|_]=Line, D) -> %Atom. + parse_atom(Line, D); +parse_term([$P|Line0], D) -> %Pid. + {Pid,Line} = get_id(Line0), + {"#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. + Str = lists:reverse(skip_blanks(lists:reverse(Str0))), + {Str,[],D}; +parse_term([$D|Line0], D) -> %DistExternal + try + {AttabSize,":"++Line1} = get_hex(Line0), + {Attab, "E"++Line2} = parse_atom_translation_table(AttabSize, Line1, []), + {Bin,Line3} = get_binary(Line2), + {try + erts_debug:dist_ext_to_term(Attab, Bin) + catch + error:_ -> '<invalid-distribution-message>' + end, + Line3, + D} + catch + error:_ -> + {'#CDVBadDistExt', skip_dist_ext(Line0), D} + end. + +skip_dist_ext(Line) -> + skip_dist_ext(lists:reverse(Line), []). + +skip_dist_ext([], SeqTraceToken) -> + SeqTraceToken; +skip_dist_ext([$:| _], SeqTraceToken) -> + [$:|SeqTraceToken]; +skip_dist_ext([C|Cs], KeptCs) -> + skip_dist_ext(Cs, [C|KeptCs]). + +parse_atom([$A|Line0], D) -> + {N,":"++Line1} = get_hex(Line0), + {Chars, Line} = get_chars(N, Line1), + {list_to_atom(Chars), Line, D}. + +parse_atom_translation_table(0, Line0, As) -> + {list_to_tuple(lists:reverse(As)), Line0}; +parse_atom_translation_table(N, Line0, As) -> + {A, Line1, _} = parse_atom(Line0, []), + parse_atom_translation_table(N-1, Line1, [A|As]). + + + +deref_ptr(Ptr, Line, D0) -> + case gb_trees:lookup(Ptr, D0) of + {value,Term} -> + {Term,Line,D0}; + none -> + case get(fd) of + end_of_heap -> + {['#CDVIncompleteHeap'],Line,D0}; + Fd -> + case val(Fd) of + "="++_ -> + put(fd, end_of_heap), + deref_ptr(Ptr, Line, D0); + L -> + D = parse(L, D0), + deref_ptr(Ptr, Line, D) + end + end + end. + +get_hex(L) -> + get_hex_1(L, 0). + +get_hex_1([H|T]=L, Acc) -> + case get_hex_digit(H) of + none -> {Acc,L}; + Digit -> get_hex_1(T, (Acc bsl 4) bor Digit) + end; +get_hex_1([], Acc) -> {Acc,[]}. + +get_hex_digit(C) when $0 =< C, C =< $9 -> C-$0; +get_hex_digit(C) when $a =< C, C =< $f -> C-$a+10; +get_hex_digit(C) when $A =< C, C =< $F -> C-$A+10; +get_hex_digit(_) -> none. + +skip_blanks([$\s|T]) -> + skip_blanks(T); +skip_blanks([$\r|T]) -> + skip_blanks(T); +skip_blanks([$\n|T]) -> + skip_blanks(T); +skip_blanks([$\t|T]) -> + skip_blanks(T); +skip_blanks(T) -> T. + +get_chars(N, Line) -> + get_chars(N, Line, []). + +get_chars(0, Line, Acc) -> + {lists:reverse(Acc),Line}; +get_chars(N, [H|T], Acc) -> + get_chars(N-1, T, [H|Acc]). + +get_id(Line) -> + get_id(Line, []). + +get_id([$>|Line], Acc) -> + {lists:reverse(Acc, [$>]),Line}; +get_id([H|T], Acc) -> + get_id(T, [H|Acc]). + +get_label(L) -> + get_label(L, []). + +get_label([$:|Line], Acc) -> + Label = lists:reverse(Acc), + case get_hex(Label) of + {Int,[]} -> + {Int,Line}; + _ -> + {list_to_atom(Label),Line} + end; +get_label([H|T], Acc) -> + get_label(T, [H|Acc]). + +get_binary(Line0) -> + {N,":"++Line} = get_hex(Line0), + get_binary(N, Line, []). + +get_binary(0, Line, Acc) -> + {list_to_binary(lists:reverse(Acc)),Line}; +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) -> + {'#CDVTruncatedBinary',[]}. + +cdvbin(Sz,Pos) -> + "#CDVBin<"++integer_to_list(Sz)++","++integer_to_list(Pos)++">". |