\n"].
br() ->
" \n".
%% In all the following, "<" is changed to "<" and ">" is changed to ">"
href_proc_port(Text) ->
href_proc_port(Text,[]).
href_proc_port([$#,$R,$e,$f,$<|T],Acc) ->
%% No links to refs
href_proc_port(T,[$;,$t,$l,$&,$f,$e,$R,$#|Acc]);
href_proc_port([$#,$F,$u,$n,$<|T],Acc) ->
%% No links to funs
href_proc_port(T,[$;,$t,$l,$&,$n,$u,$F,$#|Acc]);
href_proc_port([$#,$P,$o,$r,$t,$<|T],Acc) ->
{[$#|Port]=HashPort,Rest} = to_gt(T,[$;,$t,$l,$&,$t,$r,$o,$P,$#]),
href_proc_port(Rest,[href("TARGET=\"main\"",
["./port?port=",Port],HashPort)|Acc]);
href_proc_port([$<,$<|T],Acc) ->
%% No links to binaries
href_proc_port(T,[$;,$t,$l,$&,$;,$t,$l,$&|Acc]);
href_proc_port([$<,C|T],Acc) when $0 =< C, C =< $9 ->
%% Pid
{Pid,Rest} = to_gt(T,[C,$;,$t,$l,$&]),
href_proc_port(Rest,[href("TARGET=\"main\"",
["./proc_details?pid=",Pid],Pid)|Acc]);
href_proc_port([$",$#,$C,$D,$V,$B,$i,$n,$<|T],Acc) ->
%% Binary written by crashdump_viewer:parse_heap_term(...)
{SizeAndPos,[$"|Rest]} = split($>,T),
{Size,Pos} = split($,,SizeAndPos),
href_proc_port(Rest,[href("TARGET=\"expanded\"",
["./expand_binary?pos=",Pos],
["<< ",Size," bytes >>"]) | Acc]);
href_proc_port([$",$#,$C,$D,$V,$P,$o,$r,$t,$<|T],Acc) ->
%% Port written by crashdump_viewer:parse_term(...)
{[$#|Port]=HashPort,[$"|Rest]} = to_gt(T,[$;,$t,$l,$&,$t,$r,$o,$P,$#]),
href_proc_port(Rest,[href("TARGET=\"main\"",
["./port?port=",Port],HashPort)|Acc]);
href_proc_port([$",$#,$C,$D,$V,$P,$i,$d,$<|T],Acc) ->
%% Pid written by crashdump_viewer:parse_term(...)
{Pid,[$"|Rest]} = to_gt(T,[$;,$t,$l,$&]),
href_proc_port(Rest,[href("TARGET=\"main\"",
["./proc_details?pid=",Pid],Pid)|Acc]);
href_proc_port([$',$#,$C,$D,$V,$I,$n,$c,$o,$m,$p,$l,$e,$t,$e,$H,$e,$a,$p,$'|T],
Acc)->
%% The heap is incomplete! Written by crashdump_viewer:deref_pts(...)
IH = lists:reverse(
lists:flatten(
"...(Incomplete Heap)")),
href_proc_port(T,IH++Acc);
href_proc_port([$',$#,$C,$D,$V,$T,$r,$u,$n,$c,$a,$t,$e,$d,$B,$i,$n,$a,$r,$y,$'
|T], Acc)->
%% A binary which is truncated! Written by
%% crashdump_viewer:parse_heap_term(...)
IH = lists:reverse(
lists:flatten(
"<<...(Truncated Binary)>>"
"")),
href_proc_port(T,IH++Acc);
href_proc_port([$',$#,$C,$D,$V,$N,$o,$n,$e,$x,$i,$s,$t,$i,$n,$g,$B,$i,$n,$a,$r,
$y,$'|T], Acc)->
%% A binary which could not be found in the dump! Written by
%% crashdump_viewer:parse_heap_term(...)
IH = lists:reverse(
lists:flatten(
"<<...(Nonexisting Binary)>>"
"")),
href_proc_port(T,IH++Acc);
href_proc_port([$<|T],Acc) ->
href_proc_port(T,[$;,$t,$l,$&|Acc]);
href_proc_port([$>|T],Acc) ->
href_proc_port(T,[$;,$t,$g,$&|Acc]);
href_proc_port([H|T],Acc) ->
href_proc_port(T,[H|Acc]);
href_proc_port([],Acc) ->
lists:reverse(Acc).
to_gt(Str,Acc) ->
{Match,Rest} = to_gt_noreverse(Str,Acc),
{lists:reverse(Match),Rest}.
to_gt_noreverse([$>|T],Acc) ->
{[$;,$t,$g,$&|Acc],T};
to_gt_noreverse([H|T],Acc) ->
to_gt_noreverse(T,[H|Acc]);
to_gt_noreverse([],Acc) ->
{Acc,[]}.
split(Char,Str) ->
split(Char,Str,[]).
split(Char,[Char|Str],Acc) -> % match Char
{lists:reverse(Acc),Str};
split(Char,[H|T],Acc) ->
split(Char,T,[H|Acc]).
warn([]) ->
[];
warn(Warning) ->
font("COLOR=\"#FF0000\"",p([Warning,br(),br()])).
heading(Heading,HelpMarker) ->
[font("SIZE=+2",b(Heading)),?space,?space,help(HelpMarker)].
help(HelpMarker) ->
[href("TARGET=doc",
["/crashdump_doc/crashdump_help.html#",HelpMarker],
"Help"),
br(),br()].
%%%-----------------------------------------------------------------
%%% This function pretty formats a string which contains erlang
%%% terms (e.g. the message queue).
%%% In all the following, "<" is changed to "<" and ">" is changed to ">"
pretty_format(In) ->
case catch scan(In,[],initial,[]) of
{'EXIT',_Reason} ->
%% Probably a truncated file, so the erlang term is not complete
[font("COLOR=\"#FF0000\"","(This term might be truncated)"),
href_proc_port(lists:flatten(In))];
{[R],_,Insrt} ->
InsrtString = lists:flatten(io_lib:format("~p",[R])),
lists:flatten(replace_insrt(lists:reverse(InsrtString),Insrt,[]))
end.
%% Finish term
scan(In,Acc,list,Insrt) when hd(In)==$] ->
{lists:reverse(Acc),tl(In),Insrt};
scan(In,Acc,tuple,Insrt) when hd(In)==$} ->
{list_to_tuple(lists:reverse(Acc)),tl(In),Insrt};
scan(In,Acc,atom,Insrt) when In==[];hd(In)==$,;hd(In)==$];hd(In)==$} ->
{list_to_atom(lists:reverse(Acc)),In,Insrt};
scan(In,Acc,float,Insrt) when In==[];hd(In)==$,;hd(In)==$];hd(In)==$} ->
{list_to_float(lists:reverse(Acc)),In,Insrt};
scan(In,Acc,integer,Insrt) when In==[];hd(In)==$,;hd(In)==$];hd(In)==$} ->
{list_to_integer(lists:reverse(Acc)),In,Insrt};
scan([$"|In],Acc,string,Insrt) when In==[];hd(In)==$,;hd(In)==$];hd(In)==$} ->
{lists:reverse(Acc),In,Insrt};
scan([$>|In],Acc,special,Insrt) when In==[];hd(In)==$,;hd(In)==$];hd(In)==$} ->
%% pid, ref, port, fun
{lists:reverse([$;,$t,$g,$&|Acc]),In,Insrt};
scan([$}|In],Acc,special,Insrt) when In==[];hd(In)==$,;hd(In)==$];hd(In)==$} ->
%% bignum integer, e.g. #integer(2) = {2452,4324}
{lists:reverse([$}|Acc]),In,Insrt};
scan([$,|In],Acc,Cur,Insrt) when Cur/=string,Cur/=special ->
scan(In,Acc,Cur,Insrt);
%% In the middle of an atom
scan([$'|In],Acc,Cur,Insrt) when Cur==atom ->
%% all $' are removed. They are added again by list_to_atom,
%% so if we don't remove them we will get two of them.
scan(In,Acc,Cur,Insrt);
%% A $. in the middle of an integer - turn to float
scan([C|T],Acc,integer,Insrt) when C==$. ->
scan(T,[C|Acc],float,Insrt);
%% In the middle of an atom, integer, float or string
scan([$<|T],Acc,Cur,Insrt) when Cur==atom;Cur==string;Cur==special ->
scan(T,[$;,$t,$l,$&|Acc],Cur,Insrt);
scan([$>|T],Acc,Cur,Insrt) when Cur==atom;Cur==string ->
scan(T,[$;,$t,$g,$&|Acc],Cur,Insrt);
scan([C|T],Acc,Cur,Insrt) when Cur==atom;Cur==integer;Cur==float;Cur==string;Cur==special ->
scan(T,[C|Acc],Cur,Insrt);
%% Start list
scan([$[|T],Acc,Cur,Insrt0) ->
{L,Rest,Insrt} = scan(T,[],list,Insrt0),
scan(Rest,[L|Acc],Cur,Insrt);
%% Star tuple
scan([${|T],Acc,Cur,Insrt0) ->
{Tuple,Rest,Insrt} = scan(T,[],tuple,Insrt0),
scan(Rest,[Tuple|Acc],Cur,Insrt);
%% Star string
scan([$"|T],Acc,Cur,Insrt0) ->
{String,Rest,Insrt} = scan(T,[],string,Insrt0),
scan(Rest,[String|Acc],Cur,Insrt);
%% Start atom
scan([$'|T],Acc,Cur,Insrt0) ->
%% all $' are removed. They are added again by list_to_atom,
%% so if we don't remove them we will get two of them.
{Atom,Rest,Insrt} = scan(T,[],atom,Insrt0),
scan(Rest,[Atom|Acc],Cur,Insrt);
scan([C|T],Acc,Cur,Insrt0) when C>=$A,C=<$Z;C>=$a,C=<$z;C==$'->
{Atom,Rest,Insrt} = scan(T,[C],atom,Insrt0),
scan(Rest,[Atom|Acc],Cur,Insrt);
%% Start integer or float
scan([C|T],Acc,Cur,Insrt0) when C>=$0,C=<$9;C==$- ->
{Num,Rest,Insrt} = scan(T,[C],integer,Insrt0), % can later change to float
scan(Rest,[Num|Acc],Cur,Insrt);
%% Start Pid/Port/Ref/Fun/Binary
scan([$<|T],Acc,Cur,Insrt0) ->
{Special,Rest,Insrt} = scan(T,[$;,$t,$l,$&],special,Insrt0),
scan(Rest,['$insrt'|Acc],Cur,[Special|Insrt]);
scan([$#|T],Acc,Cur,Insrt0) ->
{Special,Rest,Insrt} = scan(T,[$#],special,Insrt0),
scan(Rest,['$insrt'|Acc],Cur,[Special|Insrt]);
%% done
scan([],Acc,initial,Insrt) ->
{Acc,[],Insrt}.
replace_insrt("'trsni$'"++Rest,[H|T],Acc) -> % the list is reversed here!
Special =
case H of
"<<" ++ _Binary ->
H;
"<" ++ _Pid ->
href("TARGET=\"main\"",["./proc_details?pid=",H],H);
"#Port<" ++ Port ->
href("TARGET=\"main\"",["./port?port=","Port<"++Port],H);
"#" ++ _other ->
H
end,
replace_insrt(Rest,T,[Special|Acc]);
replace_insrt([H|T],Insrt,Acc) ->
replace_insrt(T,Insrt,[H|Acc]);
replace_insrt([],[],Acc) ->
Acc.
%%%-----------------------------------------------------------------
%%% Create a page with one table by delivering chunk by chunk to
%%% inets. crashdump_viewer first calls chunk_page/5 once, then
%%% chunk/3 multiple times until all data is delivered.
chunk_page(processes,SessionId,TW,{Sorted,SharedHeap},FirstChunk) ->
Columns = procs_summary_table_head(Sorted,SharedHeap),
chunk_page(SessionId, "Process Information", TW, FirstChunk,
"processes", Columns, fun procs_summary_table/1);
chunk_page(ports,SessionId,TW,_,FirstChunk) ->
chunk_page(SessionId, "Port Information", TW, FirstChunk,
"ports", port_table_head(), fun ports_table/1);
chunk_page(ets_tables,SessionId,TW,Heading,FirstChunk) ->
Columns = ["Owner",
"Slot",
"Id",
"Name",
"Type",
"Buckets",
"Objects",
"Memory (bytes)"],
chunk_page(SessionId, Heading, TW, FirstChunk,
"ets_tables", Columns, fun ets_tables_table/1);
chunk_page(timers,SessionId,TW,Heading,FirstChunk) ->
chunk_page(SessionId, Heading, TW, FirstChunk, "timers",
["Owner","Message","Time left"], fun timers_table/1);
chunk_page(loaded_mods,SessionId,TW,{CC,OC},FirstChunk) ->
TotalsInfo = p([b("Current code: "),CC," bytes",br(),
b("Old code: "),OC," bytes"]),
Columns = ["Module","Current size (bytes)","Old size (bytes)"],
chunk_page(SessionId, "Loaded Modules Information", TW, FirstChunk,
"loaded_modules", TotalsInfo,Columns, fun loaded_mods_table/1);
chunk_page(funs,SessionId, TW, _, FirstChunk) ->
Columns = ["Module",
"Uniq",
"Index",
"Address",
"Native_address",
"Refc"],
chunk_page(SessionId, "Fun Information", TW, FirstChunk,
"funs", Columns, fun funs_table/1).
chunk_page(SessionId,Heading,TW,FirstChunk,Type,TableColumns,TableFun) ->
chunk_page(SessionId,Heading,TW,FirstChunk,Type,[],TableColumns,TableFun).
chunk_page(SessionId,Heading,TW,done,Type,_TotalsInfo,_TableColumns,_TableFun) ->
no_info_found(SessionId,Heading,TW,Type);
chunk_page(SessionId,Heading,TW,FirstChunk,Type,TotalsInfo,TableColumns,TableFun) ->
deliver_first(SessionId,[start_html_page(Heading),
heading(Heading,Type),
warn(TW),
TotalsInfo,
start_visible_table(TableColumns)]),
chunk(SessionId,FirstChunk,TableFun),
TableFun.
no_info_found(SessionId, Heading, TW, Type) ->
Info = ["No ", Type, " were found\n"],
deliver_first(SessionId,[start_html_page(Heading),
h1(Heading),
warn(TW),
Info,
stop_html_page()]).
chunk(SessionId, done, _TableFun) ->
deliver(SessionId,[stop_table(),stop_html_page()]);
chunk(SessionId, Items, TableFun) ->
deliver(SessionId, [lists:map(TableFun, Items),
stop_table(), %! Will produce an empty table at the end
start_visible_table()]). % of the page :(
%%%-----------------------------------------------------------------
%%% Deliver part of a page to inets
%%% The first part, which includes the HTTP header, must always be
%%% delivered as a string (i.e. no binaries). The rest of the page is
%%% better delivered as binaries in order to avoid data copying.
deliver_first(SessionId,String) ->
mod_esi:deliver(SessionId,String).
deliver(SessionId,IoList) ->
mod_esi:deliver(SessionId,[list_to_binary(IoList)]).
%%%-----------------------------------------------------------------
%%% Page specific stuff for chunk pages
procs_summary_table_head(Sorted,SharedHeap) ->
MemHeading =
if SharedHeap ->
"Stack";
true ->
"Stack+heap"
end,
[procs_summary_table_head("pid","Pid",Sorted),
procs_summary_table_head("name_func","Name/Spawned as",Sorted),
procs_summary_table_head("state","State",Sorted),
procs_summary_table_head("reds","Reductions",Sorted),
procs_summary_table_head("mem",MemHeading,Sorted),
procs_summary_table_head("msg_q_len","MsgQ Length",Sorted)].
procs_summary_table_head(_,Text,no_sort) ->
Text;
procs_summary_table_head(Sorted,Text,Sorted) ->
%% Mark the sorted column (bigger and italic)
font("SIZE=\"+1\"",em(href("./sort_procs?sort="++Sorted,Text)));
procs_summary_table_head(SortOn,Text,_Sorted) ->
href("./sort_procs?sort="++SortOn,Text).
procs_summary_table(Proc) ->
#proc{pid=Pid,name=Name,state=State,
reds=Reds,stack_heap=Mem0,msg_q_len=MsgQLen}=Proc,
Mem = case Mem0 of
-1 -> "unknown";
_ -> integer_to_list(Mem0)
end,
tr(
[td(href(["./proc_details?pid=",Pid],Pid)),
td(Name),
td(State),
td("ALIGN=right",integer_to_list(Reds)),
td("ALIGN=right",Mem),
td("ALIGN=right",integer_to_list(MsgQLen))]).
port_table_head() ->
["Id","Slot","Connected","Links","Name","Monitors","Controls"].
ports_table(Port) ->
#port{id=Id,slot=Slot,connected=Connected,links=Links,name=Name,
monitors=Monitors,controls=Controls}=Port,
tr(
[td(Id),
td("ALIGN=right",Slot),
td(href_proc_port(Connected)),
td(href_proc_port(Links)),
td(Name),
td(href_proc_port(Monitors)),
td(Controls)]).
ets_tables_table(EtsTable) ->
#ets_table{pid=Pid,slot=Slot,id=Id,name=Name,type=Type,
buckets=Buckets,size=Size,memory=Memory} = EtsTable,
tr(
[td(href_proc_port(Pid)),
td(Slot),
td(Id),
td(Name),
td(Type),
td("ALIGN=right",Buckets),
td("ALIGN=right",Size),
td("ALIGN=right",Memory)]).
timers_table(Timer) ->
#timer{pid=Pid,msg=Msg,time=Time}=Timer,
tr(
[td(href_proc_port(Pid)),
td(Msg),
td("ALIGN=right",Time)]).
loaded_mods_table(#loaded_mod{mod=Mod,current_size=CS,old_size=OS}) ->
tr([td(href(["loaded_mod_details?mod=",Mod],Mod)),
td("ALIGN=right",CS),
td("ALIGN=right",OS)]).
funs_table(Fu) ->
#fu{module=Module,uniq=Uniq,index=Index,address=Address,
native_address=NativeAddress,refc=Refc}=Fu,
tr(
[td(Module),
td("ALIGN=right",Uniq),
td("ALIGN=right",Index),
td(Address),
td(NativeAddress),
td("ALIGN=right",Refc)]).