\n"].
br() ->
" \n".
%% In all the following, "<" is changed to "<" and ">" is changed to ">"
href_proc_port(Text) ->
href_proc_port(Text,true).
href_proc_port(Text,LinkToBin) ->
href_proc_port(Text,[],LinkToBin).
href_proc_port("#Ref<"++T,Acc,LTB) ->
%% No links to refs
href_proc_port(T,["#Ref<"|Acc],LTB);
href_proc_port("#Fun<"++T,Acc,LTB) ->
%% No links to funs
href_proc_port(T,["#Fun<"|Acc],LTB);
href_proc_port("#Port<"++T,Acc,LTB) ->
{Port0,Rest} = split($>,T),
Port = "#Port<"++Port0 ++ ">",
href_proc_port(Rest,[href(Port,Port)|Acc],LTB);
href_proc_port("<<"++T,Acc,LTB) ->
%% No links to binaries
href_proc_port(T,["<<"|Acc],LTB);
href_proc_port("<"++([C|_]=T),Acc,LTB) when $0 =< C, C =< $9 ->
%% Pid
{Pid0,Rest} = split($>,T),
Pid = "<" ++ Pid0 ++ ">",
href_proc_port(Rest,[href(Pid,Pid)|Acc],LTB);
href_proc_port("['#CDVBin'"++T,Acc,LTB) ->
%% Binary written by crashdump_viewer:parse_heap_term(...)
{OffsetSizePos,Rest} = split($],T),
BinStr =
case string:tokens(OffsetSizePos,",.|") of
[Offset,Size,Pos] ->
Id = {list_to_integer(Offset),10,list_to_integer(Pos)},
{ok,PreviewBin} = crashdump_viewer:expand_binary(Id),
PreviewBytes = binary_to_list(PreviewBin),
PreviewStr = ["<<",
[integer_to_list(X)++"," || X <- PreviewBytes],
"...(",
observer_lib:to_str({bytes,Size}),
")>>"],
if LTB ->
href("TARGET=\"expanded\"",
["#Binary?offset="++Offset++
"&size="++Size++
"&pos="++Pos],
PreviewStr);
true ->
PreviewStr
end;
_ ->
"<< ... >>"
end,
href_proc_port(Rest,[BinStr|Acc],LTB);
href_proc_port("['#CDVPort'"++T,Acc,LTB) ->
%% Port written by crashdump_viewer:parse_term(...)
{Port0,Rest} = split($],T),
PortStr=
case string:tokens(Port0,",.|") of
[X,Y] ->
Port = "#Port<"++X++"."++Y++">",
href(Port,Port);
Ns ->
"#Port<" ++ string:join(Ns,".") ++"...>"
end,
href_proc_port(Rest,[PortStr|Acc],LTB);
href_proc_port("['#CDVPid'"++T,Acc,LTB) ->
%% Pid written by crashdump_viewer:parse_term(...)
{Pid0,Rest} = split($],T),
PidStr =
case string:tokens(Pid0,",.|") of
[X,Y,Z] ->
Pid = "<"++X++"."++Y++"."++Z++">",
href(Pid,Pid);
Ns ->
"<" ++ string:join(Ns,".") ++ "...>"
end,
href_proc_port(Rest,[PidStr|Acc],LTB);
href_proc_port("'#CDVIncompleteHeap'"++T,Acc,LTB)->
%% The heap is incomplete! Written by crashdump_viewer:deref_pts(...)
IH = lists:reverse(
lists:flatten(
"...(Incomplete Heap)")),
href_proc_port(T,IH++Acc,LTB);
href_proc_port("'#CDVTruncatedBinary'"++T,Acc,LTB)->
%% 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,LTB);
href_proc_port("'#CDVNonexistingBinary'"++T,Acc,LTB)->
%% 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,LTB);
href_proc_port("<"++T,Acc,LTB) ->
href_proc_port(T,["<"|Acc],LTB);
href_proc_port(">"++T,Acc,LTB) ->
href_proc_port(T,[">"|Acc],LTB);
href_proc_port([H|T],Acc,LTB) ->
href_proc_port(T,[H|Acc],LTB);
href_proc_port([],Acc,_) ->
lists:reverse(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,DumpVsn},FirstChunk) ->
Columns = procs_summary_table_head(Sorted,SharedHeap,DumpVsn),
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,DumpVsn) ->
MemHeading =
if DumpVsn>=?r16b01_dump_vsn ->
"Memory (bytes)";
true ->
if SharedHeap ->
"Stack";
true ->
"Stack+heap"
end
end,
[procs_summary_table_head1("pid","Pid",Sorted),
procs_summary_table_head1("name_func","Name/Spawned as",Sorted),
procs_summary_table_head1("state","State",Sorted),
procs_summary_table_head1("reds","Reductions",Sorted),
procs_summary_table_head1("mem",MemHeading,Sorted),
procs_summary_table_head1("msg_q_len","MsgQ Length",Sorted)].
procs_summary_table_head1(_,Text,no_sort) ->
Text;
procs_summary_table_head1(Sorted,Text,Sorted) ->
%% Mark the sorted column (bigger and italic)
font("SIZE=\"+1\"",em(href("./sort_procs?sort="++Sorted,Text)));
procs_summary_table_head1(SortOn,Text,_Sorted) ->
href("./sort_procs?sort="++SortOn,Text).
procs_summary_table(Proc) ->
#proc{pid=Pid,name=Name,state=State,
reds=Reds,stack_heap=Stack,memory=Memory,msg_q_len=MsgQLen}=Proc,
Mem =
case Memory of
undefined -> % assuming pre-R16B01
case Stack of
-1 -> "unknown";
_ -> integer_to_list(Stack)
end;
_ ->
integer_to_list(Memory)
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=",http_uri:encode(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)])
.