%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2003-2011. 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_html). %% %% This module implements the HTML generation for the crashdump %% viewer. No logic or states are kept by this module. %% -export([welcome/0, read_file_frame/0, redirect/1, start_page/0, filename_frame/1, menu_frame/0, general_info/1, pretty_info_page/2, info_page/2, proc_details/4, expanded_memory/2, expanded_binary/1, port/3, internal_ets_tables/2, nods/2, loaded_mod_details/2, atoms/4, atoms_chunk/2, memory/2, allocated_areas/2, allocator_info/2, hash_tables/2, index_tables/2, error/2, chunk_page/5, chunk/3]). -include("crashdump_viewer.hrl"). %%%----------------------------------------------------------------- %%% Welcome frame welcome() -> header(body(welcome_body())). welcome_body() -> table( "WIDTH=100% HEIGHT=60%", [tr("VALIGN=middle", td("ALIGN=center", font("SIZE=6", ["Welcome to the Web Based",br(), "Erlang Crash Dump Analyser"]))), tr("VALIGN=middle", td("ALIGN=center", form(["name=load_new ACTION=\"./read_file_frame\""], input(["TYPE=submit VALUE=\"Load Crashdump\""]))))]). %%%----------------------------------------------------------------- %%% Present a form to enter file name of erlang crash dump read_file_frame() -> header("Read File",body(read_file_frame_body())). read_file_frame_body() -> Entry = case webtool:is_localhost() of true -> [input("TYPE=file NAME=browse SIZE=40"), input("TYPE=hidden NAME=path")]; false -> input("TYPE=text NAME=path SIZE=60") end, Form = form( "NAME=read_file_form METHOD=post ACTION= \"./read_file\"", table( "BORDER=0", [tr(td("COLSPAN=2","Enter file to analyse")), tr( [td(Entry), td("ALIGN=center", input("TYPE=submit onClick=\"path.value=browse.value;\"" "VALUE=Ok"))])])), table( "WIDTH=100% HEIGHT=60%", tr("VALIGN=middle", td("ALIGN=center",Form))). %%%----------------------------------------------------------------- %%% Display "Please wait..." while crashdump is being read redirect(Status) -> Head = [""], header("Please wait...",Head,body([Status,br(),"Please wait..."])). %%%----------------------------------------------------------------- %%% Frameset containing "filename", "menu", and "main" frames start_page() -> header("Crashdump Viewer Start Page",start_page_frameset()). start_page_frameset() -> frameset( "ROWS=\"70,*\"", [frame(["NAME=\"filename\" SRC=\"./filename_frame\""]), frameset( "COLS=\"200,*\"", [frame(["NAME=\"menu\" ", "SRC=\"/cdv_erl/crashdump_viewer/menu_frame\""]), frame("NAME=\"main\" SRC=\"./initial_info_frame\"")])]). %%%----------------------------------------------------------------- %%% Topmost frame presents the filename of the crashdump currently %%% viewed filename_frame(File) -> header("Filename",body(filename_body(File))). filename_body(File) -> p("ALIGN=center",[b("Crashdump currently viewed:"),br(),File]). %%%----------------------------------------------------------------- %%% Left frame displays the menu menu_frame() -> header("Menu", body(menu_body())). menu_body() -> [p(format_items(1,ets:info(cdv_menu_table,size),true)), p([br(), form(["name=load_new ACTION=\"./read_file_frame\" ", "TARGET=app_frame"], input("TYPE=submit VALUE=\"Load New Crashdump\""))])]. format_items(I,Max,_ParentState) when I>Max-> []; format_items(I,Max,ParentState) when I= case ets:lookup(cdv_menu_table,I) of [] -> []; [#menu_item{state=false,children=0}] -> format_items(I+1,Max,ParentState); [#menu_item{state=false,children=Children}] -> format_items(I+Children+1,Max,arentState); [Item=#menu_item{state=true,children=0}] when ParentState -> This = format_item(Item), [This|format_items(I+1,Max,ParentState)]; [Item=#menu_item{state=true,children=Children}] when ParentState -> This = format_item(Item), Ch = format_items(I+1,I+Children,true), [[This | Ch] | format_items(I+Children+1,Max,ParentState)] end. format_item(Item) -> [lists:duplicate(Item#menu_item.depth*5,?space), format_picture(Item#menu_item.index, Item#menu_item.picture, Item#menu_item.children), format_title(Item#menu_item.text,Item#menu_item.target), br()]. format_picture(_Index,Picture,0) -> img(Picture); format_picture(Index,Picture,_Children) -> href( ["./toggle?index=", integer_to_list(Index)], img(Picture)). format_title({Link,Text},Target) -> href(["TARGET=\"",Target,"\""],Link,Text); format_title(Text,_Type) -> Text. %%%----------------------------------------------------------------- %%% Display the general information general_info(GenInfo) -> Heading = "General Information", header(Heading,body(general_info_body(Heading,GenInfo))). general_info_body(Heading,GenInfo) -> TruncatedInfo = case get(truncated) of true -> p(font("SIZE=\"+1\" COLOR=\"#FF0000\"", b(["WARNING:",br(), "The crashdump is truncated",br(), "Some information might be missing",br()]))); false -> "" end, [heading(Heading,"general_info"), TruncatedInfo, table( "BORDER=4 CELLPADDING=4", [tr([th("ALIGN=left BGCOLOR=\"#8899AA\"","Slogan"), td(GenInfo#general_info.slogan)]), tr([th("ALIGN=left BGCOLOR=\"#8899AA\"","Node name"), td(GenInfo#general_info.node_name)]), tr([th("ALIGN=left BGCOLOR=\"#8899AA\"","Crashdump created on"), td(GenInfo#general_info.created)]), tr([th("ALIGN=left BGCOLOR=\"#8899AA\"","System version"), td(GenInfo#general_info.system_vsn)]), tr([th("ALIGN=left BGCOLOR=\"#8899AA\"","Compiled"), td(GenInfo#general_info.compile_time)]), tr([th("ALIGN=left BGCOLOR=\"#8899AA\"","Taints"), td(GenInfo#general_info.taints)]), case GenInfo#general_info.mem_tot of "" -> ""; MemTot -> tr([th("ALIGN=left BGCOLOR=\"#8899AA\"","Memory allocated"), td([MemTot," bytes"])]) end, case GenInfo#general_info.mem_max of "" -> ""; MemMax -> tr([th("ALIGN=left BGCOLOR=\"#8899AA\"","Memory maximum"), td([MemMax," bytes"])]) end, tr([th("ALIGN=left BGCOLOR=\"#8899AA\"","Atoms"), td(GenInfo#general_info.num_atoms)]), tr([th("ALIGN=left BGCOLOR=\"#8899AA\"","Processes"), td(GenInfo#general_info.num_procs)]), tr([th("ALIGN=left BGCOLOR=\"#8899AA\"","ETS tables"), td(GenInfo#general_info.num_ets)]), tr([th("ALIGN=left BGCOLOR=\"#8899AA\"","Timers"), td(GenInfo#general_info.num_timers)]), tr([th("ALIGN=left BGCOLOR=\"#8899AA\"","Funs"), td(GenInfo#general_info.num_fun)])]), case GenInfo#general_info.instr_info of old_instr_data -> [br(),br(), font("COLOR=\"#FF0000\"", ["Instrumentation information is found at the end of ",br(), "the dump. The information has an old format, and ",br(), "is not presented in this tool. Please read the ",br(), "crashdump manually to see this information."])]; instr_data -> [br(),br(), font("COLOR=\"#FF0000\"", ["Instrumentation information is found at the end of ",br(), "the dump. The information is not presented in this ",br(), "tool. Please read the crashdump manually to see",br(), "this information."])]; false -> [] end]. %%%----------------------------------------------------------------- %%% Display an error message error(Text,Args) -> Str = io_lib:format(Text,Args), header(body(error_body(Str))). error_body(Str) -> [h1("An error occured:"),Str,"\n"]. %%%----------------------------------------------------------------- %%% Display the given information as is info_page(Heading,Info) -> info_page(Heading,Info,[]). info_page(Heading,Info,TW) -> header(Heading,body(info_body(Heading,Info,TW))). info_body(Heading,[],TW) -> [h1(Heading), warn(TW), "No information was found\n"]; info_body(Heading,Info,TW) -> [h1(Heading), warn(TW), pre(href_proc_port(lists:flatten(Info)))]. %%%----------------------------------------------------------------- %%% Pretty print the given information pretty_info_page(Heading,Info) -> header(Heading,body(pretty_info_body(Heading,Info))). pretty_info_body(Heading,[]) -> [h1(Heading), "No information was found\n"]; pretty_info_body(Heading,Info) -> [h1(Heading), pre(pretty_format(Info))]. %%%----------------------------------------------------------------- %%% Print details for one process proc_details(Pid,Proc,TW,SharedHeap) -> Script = "\n", Heading = ["Process ", Pid], header(Heading,Script,body(proc_details_body(Heading,Proc,TW,SharedHeap))). proc_details_body(Heading,Proc,TW,SharedHeap) -> Pid = Proc#proc.pid, Name = if Proc#proc.name==Proc#proc.init_func -> ?space; true -> Proc#proc.name end, [help("processes"), warn(TW), table( "BORDER=4 COLS=4 WIDTH=\"100%\"", [tr( "BGCOLOR=\"#8899AA\"", [td("COLSPAN=4 ALIGN=center",Heading)]), tr( [td("NOWRAP=true",b("Name")), td("COLSPAN=1",Name), td("NOWRAP=true",b("Spawned as")), td("COLSPAN=1",Proc#proc.init_func)]), tr( [td("NOWRAP=true",b("State")), td("COLSPAN=1",Proc#proc.state), td("NOWRAP=true",b(element(1,Proc#proc.current_func))), td("COLSPAN=1",element(2,Proc#proc.current_func))]), tr( [td("NOWRAP=true",b("Started")), td("COLSPAN=1",Proc#proc.start_time), td("NOWRAP=true",b("Spawned by")), td("COLSPAN=1",href_proc_port(Proc#proc.parent))]), tr( [td("NOWRAP=true",b("Reductions")), td("COLSPAN=3",integer_to_list(Proc#proc.reds))]), if SharedHeap -> Stack = case Proc#proc.stack_heap of -1 -> "unknown"; S -> integer_to_list(S) end, tr( [td("NOWRAP=true",b("Stack")), td("COLSPAN=3",Stack)]); true -> [tr( [td("NOWRAP=true",b("Stack+heap")), td(integer_to_list(Proc#proc.stack_heap)), td("NOWRAP=true",b("OldHeap")), td(Proc#proc.old_heap)]), tr( [td("NOWRAP=true",b("Heap unused")), td(Proc#proc.heap_unused), td("NOWRAP=true",b("OldHeap unused")), td(Proc#proc.old_heap_unused)]), tr( [td("NOWRAP=true",b("Number of heap fragments")), td(Proc#proc.num_heap_frag), td("NOWRAP=true",b("Heap fragment data")), td(Proc#proc.heap_frag_data)])] end, case Proc#proc.new_heap_start of ?space -> ""; _ -> %% Garbing [tr( [td("NOWRAP=true",b("New heap start")), td("COLSPAN=1",Proc#proc.new_heap_start), td("NOWRAP=true",b("New heap top")), td("COLSPAN=1",Proc#proc.new_heap_top)]), tr( [td("NOWRAP=true",b("Stack top")), td("COLSPAN=1",Proc#proc.stack_top), td("NOWRAP=true",b("Stack end")), td("COLSPAN=1",Proc#proc.stack_end)]), tr( [td("NOWRAP=true",b("Old heap start")), td("COLSPAN=1",Proc#proc.old_heap_start), td("NOWRAP=true",b("Old heap top")), td("COLSPAN=1",Proc#proc.old_heap_top)]), tr( [td("NOWRAP=true",b("Old heap end")), td("COLSPAN=3",Proc#proc.old_heap_end)])] end, case Proc#proc.prog_count of ?space -> ""; _ -> [tr( [td("NOWRAP=true",b("Program counter")), td("COLSPAN=3",Proc#proc.prog_count)]), tr( [td("NOWRAP=true",b("Continuation pointer")), td("COLSPAN=3",Proc#proc.cp)]), tr( [td("NOWRAP=true",b("Arity")), td("COLSPAN=3",Proc#proc.arity)])] end, tr( [td("NOWRAP=true",b("Link list")), td("COLSPAN=3",href_proc_port(Proc#proc.links))]), tr( [td("NOWRAP=true",b("Msg queue length")), td("COLSPAN=3",integer_to_list(Proc#proc.msg_q_len))]), %% These are displayed only if data exist display_or_link_to_expand("MsgQueue",Proc#proc.msg_q,Pid), display_or_link_to_expand("Dictionary",Proc#proc.dict,Pid), display_or_link_to_expand("DebugDictionary",Proc#proc.debug_dict,Pid), display_or_link_to_expand("LastCalls",Proc#proc.last_calls,Pid), display_or_link_to_expand("StackDump",Proc#proc.stack_dump,Pid)]), p([href(["./ets_tables?pid=",Proc#proc.pid], "ETS tables owned by this process"), "     ", href(["./timers?pid=",Proc#proc.pid], "Timers owned by this process")])]. display_or_link_to_expand(Heading,Data,Pid) -> case Data of expand -> link_to_read_memory(Heading,Pid); truncated -> Text = font("COLOR=\"#FF0000\"", "The dump is truncated, no data available"), tr( [td("NOWRAP=true VALIGN=top",b(Heading)), td("COLSPAN=3",Text)]); ?space -> ""; {size,Truncated,Size,Pos} -> %% Too much data, or truncated data - %% display a link to expand it tr( [td("NOWRAP=true",b(Heading)), td("COLSPAN=3", href("TARGET=\"expanded\" onClick=popup()", ["./expand?pos=",integer_to_list(Pos), "&size=",integer_to_list(Size), "&what=",Heading, "&truncated=",atom_to_list(Truncated)], ["Expand (",integer_to_list(Size)," bytes)"]))]); _ -> %% Not too much Data - display it tr( [td("NOWRAP=true VALIGN=top",b(Heading)), td("COLSPAN=3",pre(format(Heading,Data)))]) end. link_to_read_memory(Heading,Pid) -> tr( [td("NOWRAP=true",b(Heading)), td("COLSPAN=3", href("TARGET=\"expanded\" onClick=popup()", ["./expand_memory?pid=",Pid, "&what=",Heading], ["Expand ", Heading]))]). format("LastCalls",Data) -> Data; format("StackDump",Data) -> Data; format(_Heading,Data) -> pretty_format(Data). %%%----------------------------------------------------------------- %%% Expanded memory expanded_memory(Heading,Expanded) -> header(Heading,body(expanded_memory_body(Heading,Expanded))). expanded_memory_body(Heading,[]) -> [heading(Heading,"processes"), case Heading of "MsgQueue" -> "No messages were found"; "StackDump" -> "No stack dump was found"; "Dictionary" -> "No dictionary was found"; "DebugDictionary" -> "No debug dictionary was found" end]; expanded_memory_body(Heading,Expanded) -> [heading(Heading,"processes"), case Heading of "MsgQueue" -> table( "BORDER=4 CELLPADDING=4", [tr( [th("Message"), th("SeqTraceToken")]) | lists:map(fun(Msg) -> msgq_table(Msg) end, Expanded)]); "StackDump" -> table( "BORDER=4 CELLPADDING=4", [tr( [th("Label"), th("Term")]) | lists:map(fun(Entry) -> stackdump_table(Entry) end, Expanded)]); _ -> table( "BORDER=4 CELLPADDING=4", [tr( [th("Key"), th("Value")]) | lists:map(fun(Entry) -> dict_table(Entry) end, Expanded)]) end]. msgq_table({Msg0,Token0}) -> Token = case Token0 of [] -> ?space; _ -> io_lib:fwrite("~w",[Token0]) end, Msg = href_proc_port(lists:flatten(io_lib:format("~p",[Msg0]))), tr([td(pre(Msg)), td(Token)]). stackdump_table({Label0,Term0}) -> Label = io_lib:format("~w",[Label0]), Term = href_proc_port(lists:flatten(io_lib:format("~p",[Term0]))), tr([td("VALIGN=top",Label), td(pre(Term))]). dict_table({Key0,Value0}) -> Key = href_proc_port(lists:flatten(io_lib:format("~p",[Key0]))), Value = href_proc_port(lists:flatten(io_lib:format("~p",[Value0]))), tr([td("VALIGN=top",pre(Key)), td(pre(Value))]). %%%----------------------------------------------------------------- %%% Display an expanded binary, i.e. the whole binary, not just the %%% size of it. expanded_binary(Bin) -> Heading = "Expanded binary", header(Heading,body(expanded_binary_body(Heading,Bin))). expanded_binary_body(Heading,Bin) -> [h1(Heading), pre(href_proc_port(lists:flatten(Bin))), br(),br(), href("javascript:history.go(-1)","BACK")]. %%%----------------------------------------------------------------- %%% Print info for one port port(Heading,Port,TW) -> header(Heading,body(port_body(Heading,Port,TW))). port_body(Heading,Port,TW) -> [heading(Heading,"ports"), warn(TW), table( "BORDER=4 CELLPADDING=4", [tr([th(Head) || Head <- port_table_head()]), ports_table(Port)])]. %%%----------------------------------------------------------------- %%% Print table of internal ETS tables internal_ets_tables(InternalEts,TW) -> Heading = "Internal ETS tables", header(Heading,body(internal_ets_tables_body(Heading,InternalEts,TW))). internal_ets_tables_body(Heading,[],TW) -> [h1(Heading), warn(TW), "No internal ETS tables were found\n"]; internal_ets_tables_body(Heading,InternalEts,TW) -> [heading(Heading,"internal_ets_tables"), warn(TW), table( "BORDER=4 CELLPADDING=4", [tr( [th("Description"), th("Id"), th("Name"), th("Type"), th("Buckets"), th("Objects"), th("Memory (bytes)")]) | lists:map(fun(InternalEtsTable) -> internal_ets_tables_table1(InternalEtsTable) end, InternalEts)])]. internal_ets_tables_table1({Descr,InternalEtsTable}) -> #ets_table{id=Id,name=Name,type=Type,buckets=Buckets, size=Size,memory=Memory} = InternalEtsTable, tr( [td(Descr), td(Id), td(Name), td(Type), td("ALIGN=right",Buckets), td("ALIGN=right",Size), td("ALIGN=right",Memory)]). %%%----------------------------------------------------------------- %%% Print table of nodes in distribution nods(Nods,TW) -> header("Distribution Information",body(nodes_body(Nods,TW))). nodes_body(no_distribution,_TW) -> [heading("Distribution Information","distribution_info"), "Not alive\n"]; nodes_body({Type,Info,Node},TW) when is_record(Node,nod) -> %% Display only one node - used when a pid or port on a remote %% node is clicked. [heading("Remote Node","distribution_info"), warn(TW), Info, make_nodes_table(Type,[Node])]; nodes_body({Visible,Hidden,NotConnected},TW) -> %% Display all nodes - this is the complete distribution info [heading("Distribution Information","distribution_info"), warn(TW), make_nodes_table("Visible Nodes",Visible), make_nodes_table("Hidden Nodes",Hidden), make_nodes_table("Not Connected Nodes",NotConnected)]. make_nodes_table(Text,[]) -> p(["No \"",Text,"\" were found"]); make_nodes_table(Text,Nodes) -> p(table( "BORDER=4 CELLPADDING=4", [nodes_table_heading(Text), lists:map(fun(Node) -> nodes_table_row(Node) end, Nodes)])). nodes_table_heading(Text) -> [tr("BGCOLOR=\"#8899AA\"",[th("COLSPAN=6",Text)]), tr([th("Name"), th("Channel"), th("Controller"), th("Creation(s)"), th("Links/Monitors"), th("Extra info")])]. nodes_table_row(Node) -> #nod{name=Name,channel=Channel,controller=Controller,creation=Creation, remote_links=Links,remote_mon=Mon,remote_mon_by=MonBy,error=Error}=Node, tr( [td(maybe_refcount(Name)), td("ALIGN=right",Channel), td(href_proc_port(Controller)), td("ALIGN=right",break_lines_creation(Creation)), td(format_links_and_monitors(Links,Mon,MonBy)), td(format_extra_info(Error))]). maybe_refcount(Name) -> maybe_refcount(Name, []). maybe_refcount([$ ,$( | Rest], Acc) -> [lists:reverse(Acc),br(),[$(|Rest]]; maybe_refcount([Char | Rest], Acc) -> maybe_refcount(Rest, [Char | Acc]); maybe_refcount([],Acc) -> lists:reverse(Acc). break_lines_creation(Creation) -> break_lines_creation(Creation,[]). break_lines_creation([$ ,$( | Rest1], Acc) -> {RefCount,Rest2} = to_end_par(Rest1,[$(,$ ]), [lists:reverse(Acc),RefCount,br(),break_lines_creation(Rest2)]; break_lines_creation([$ | Rest], Acc) -> [lists:reverse(Acc),br(),break_lines_creation(Rest)]; break_lines_creation([Char | Rest], Acc) -> break_lines_creation(Rest, [Char | Acc]); break_lines_creation([],Acc) -> lists:reverse(Acc). to_end_par([$),$ | Rest], Acc) -> {lists:reverse([$) | Acc]),Rest}; to_end_par([$) | Rest], Acc) -> {lists:reverse([$) | Acc]),Rest}; to_end_par([Char | Rest], Acc) -> to_end_par(Rest, [Char | Acc]); to_end_par([],Acc) -> {lists:reverse(Acc),[]}. format_links_and_monitors(?space,?space,?space) -> ?space; format_links_and_monitors(Links,Mon,MonBy) -> [format_links_and_monitors(Links," is linked to "), format_links_and_monitors(Mon," is monitoring "), format_links_and_monitors(MonBy," is monitored by ")]. format_links_and_monitors(?space,_Text) -> ""; format_links_and_monitors([{Local,Remote}|Rest],Text) -> [[href_proc_port(Local),Text,href_proc_port(Remote),br()] | format_links_and_monitors(Rest,Text)]; format_links_and_monitors([],_Text) -> []. format_extra_info(?space) -> ?space; format_extra_info(Error) -> case Error of ?space -> ""; _ -> font("COLOR=\"#FF0000\"",["ERROR: ",Error,"\n"]) end. %%%----------------------------------------------------------------- %%% Print detailed information about one module loaded_mod_details(ModInfo,TW) -> header(ModInfo#loaded_mod.mod,body(loaded_mod_details_body(ModInfo,TW))). loaded_mod_details_body(ModInfo,TW) -> #loaded_mod{mod=Mod,current_size=CS,current_attrib=CA, current_comp_info=CCI,old_size=OS, old_attrib=OA,old_comp_info=OCI} = ModInfo, [help("loaded_modules"), warn(TW), table( "BORDER=4 CELLPADDING=4", [tr(th("BGCOLOR=\"#8899AA\" COLSPAN=3", ["Module: ",Mod])), tr([td(?space),th("Current"),th("Old")]), tr([th("ALIGN=left","Size (bytes)"), td(CS), td(OS)]), tr([th("ALIGN=left","Attributes"), td(pre(CA)), td(pre(OA))]), tr([th("ALIGN=left","Compilation info"), td(pre(CCI)), td(pre(OCI))])])]. %%%----------------------------------------------------------------- %%% Print atoms atoms(SessionId,TW,Num,FirstChunk) -> Heading = "Atoms", case FirstChunk of done -> deliver_first(SessionId,[h1(Heading), warn(TW), "No atoms were found in log",br(), "Total number of atoms in node was ", Num, br()]); _ -> deliver_first(SessionId,[start_html_page(Heading), heading(Heading,"atoms"), warn(TW), "Total number of atoms in node was ", Num, br(), "The last created atom is shown first", br(), start_pre()]), atoms_chunk(SessionId,FirstChunk) end. atoms_chunk(SessionId,done) -> deliver(SessionId,[stop_pre(),stop_html_page()]); atoms_chunk(SessionId,Atoms) -> deliver(SessionId,Atoms). %%%----------------------------------------------------------------- %%% Print memory information memory(Memory,TW) -> Heading = "Memory Information", header(Heading,body(memory_body(Heading,Memory,TW))). memory_body(Heading,[],TW) -> [h1(Heading), warn(TW), "No memory information was found\n"]; memory_body(Heading,Memory,TW) -> [heading(Heading,"memory"), warn(TW), table( "BORDER=4 CELLPADDING=4", [tr("BGCOLOR=\"#8899AA\"", [th(?space), th("Bytes")]) | lists:map(fun(Entry) -> memory_table(Entry) end, Memory)])]. memory_table({Key,Value}) -> tr([th("ALIGN=left",Key),td("ALIGN=right",Value)]). %%%----------------------------------------------------------------- %%% Print allocated areas information allocated_areas(AllocatedAreas,TW) -> Heading = "Information about allocated areas", header(Heading,body(allocated_areas_body(Heading,AllocatedAreas,TW))). allocated_areas_body(Heading,[],TW) -> [h1(Heading), warn(TW), "No information was found about allocated areas\n"]; allocated_areas_body(Heading,AllocatedAreas,TW) -> [heading(Heading,"memory"), warn(TW), table( "BORDER=4 CELLPADDING=4", [tr("BGCOLOR=\"#8899AA\"", [th(?space), th("Allocated (bytes)"), th("Used (bytes)")]) | lists:map(fun(Entry) -> allocated_areas_table(Entry) end, AllocatedAreas)])]. allocated_areas_table({Key,Alloc,Used}) -> tr( [th("ALIGN=left",Key), td("ALIGN=right",Alloc), td("ALIGN=right",Used)]). %%%----------------------------------------------------------------- %%% Print allocator_info information allocator_info(Allocators,TW) -> Heading = "Allocator Information", header(Heading,body(allocator_info_body(Heading,Allocators,TW))). allocator_info_body(Heading,[],TW) -> [h1(Heading), warn(TW), "No information was found about allocators\n"]; allocator_info_body(Heading,Allocators,TW) -> [heading(Heading,"memory"), warn(TW), p(b("Sizes are in bytes")), lists:map(fun({SubTitle,Allocator}) -> [table( "BORDER=4 CELLPADDING=4", [tr("BGCOLOR=\"#8899AA\"", th("COLSPAN=10 ALIGN=left", font("SIZE=+1",SubTitle))) | lists:map( fun({Key,Values}) -> tr([th("ALIGN=left",Key) | lists:map( fun(Val) -> td("ALIGN=right",Val) end,Values)]) end, Allocator)]), br(),br()] end, Allocators)]. %%%----------------------------------------------------------------- %%% Print informatin about internal tables hash_tables(HashTables,TW) -> Heading = "Hash Table Information", header(Heading,body(hash_tables_body(Heading,HashTables,TW))). hash_tables_body(Heading,[],TW) -> [h1(Heading), warn(TW), "No hash table information was found\n"]; hash_tables_body(Heading,HashTables,TW) -> [heading(Heading,"internal_tables"), warn(TW), table( "BORDER=4 CELLPADDING=4", [tr( [th("Name"), th("Size"), th("Used"), th("Objects"), th("Depth")]) | lists:map(fun(HashTable) -> hash_tables_table(HashTable) end, HashTables)])]. hash_tables_table(HashTable) -> #hash_table{name=Name,size=Size,used=Used,objs=Objs,depth=Depth}=HashTable, tr( [td(Name), td("ALIGN=right",Size), td("ALIGN=right",Used), td("ALIGN=right",Objs), td("ALIGN=right",Depth)]). index_tables(IndexTables,TW) -> Heading = "Index Table Information", header(Heading,body(index_tables_body(Heading,IndexTables,TW))). index_tables_body(Heading,[],TW) -> [h1(Heading), warn(TW), "No index table information was found\n"]; index_tables_body(Heading,IndexTables,TW) -> [heading(Heading,"internal_tables"), warn(TW), table( "BORDER=4 CELLPADDING=4", [tr( [th("Name"), th("Size"), th("Limit"), th("Used"), th("Rate"), th("Entries")]) | lists:map(fun(IndexTable) -> index_tables_table(IndexTable) end, IndexTables)])]. index_tables_table(IndexTable) -> #index_table{name=Name,size=Size,limit=Limit,used=Used, rate=Rate,entries=Entries} = IndexTable, tr( [td(Name), td("ALIGN=right",Size), td("ALIGN=right",Limit), td("ALIGN=right",Used), td("ALIGN=right",Rate), td("ALIGN=right",Entries)]). %%%----------------------------------------------------------------- %%% Internal library start_html_page(Title) -> [only_http_header(), start_html(), only_html_header(Title), start_html_body()]. stop_html_page() -> [stop_html_body(), stop_html()]. only_http_header() -> ["Pragma:no-cache\r\n", "Content-type: text/html\r\n\r\n"]. only_html_header(Title) -> only_html_header(Title,""). only_html_header(Title,JavaScript) -> ["\n", "", Title, "\n", JavaScript, "\n"]. start_html() -> "\n". stop_html() -> "". start_html_body() -> "\n". stop_html_body() -> "\n". header(Body) -> header("","",Body). header(Title,Body) -> header(Title,"",Body). header(Title,JavaScript,Body) -> [only_http_header(), html_header(Title,JavaScript,Body)]. html_header(Title,JavaScript,Body) -> [start_html(), only_html_header(Title,JavaScript), Body, stop_html()]. body(Text) -> [start_html_body(), Text, stop_html_body()]. frameset(Args,Frames) -> ["\n", Frames, "\n\n"]. frame(Args) -> ["\n"]. start_visible_table() -> start_table("BORDER=\"4\" CELLPADDING=\"4\" WIDTH=\"100%\""). % start_table("BORDER=\"4\" CELLPADDING=\"4\""). start_visible_table(ColTitles) -> [start_visible_table(), tr([th(ColTitle) || ColTitle <- ColTitles])]. start_table(Args) -> ["\n"]. stop_table() -> "
\n". table(Args,Text) -> [start_table(Args), Text, stop_table()]. tr(Text) -> ["\n", Text, "\n\n"]. tr(Args,Text) -> ["\n", Text, "\n\n"]. th(Text) -> ["", Text, ""]. th(Args,Text) -> ["\n", Text, "\n\n"]. td(Text) -> ["", Text, ""]. td(Args,Text) -> ["", Text, ""]. b(Text) -> ["",Text,""]. em(Text) -> ["",Text,"\n"]. start_pre() -> "
".
stop_pre() ->
    "
". pre(Text) -> [start_pre(),Text,stop_pre()]. href(Link,Text) -> ["",Text,""]. href(Args,Link,Text) -> ["",Text,""]. img("") -> ""; img(Picture) -> [""]. form(Args,Text) -> ["
\n",Text,"\n
\n"]. input(Args) -> ["\n"]. h1(Text) -> ["

",Text,"

\n"]. font(Args,Text) -> ["\n",Text,"\n\n"]. p(Text) -> ["

",Text,"

\n"]. p(Args, Text) -> ["

",Text,"

\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)]).