1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2003-2018. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20-module(observer_html_lib). 21 22%% 23%% This module implements the HTML generation for the crashdump 24%% viewer. No logic or states are kept by this module. 25%% 26 27-export([plain_page/2, 28 expandable_term/4, 29 warning/2]). 30 31-include("crashdump_viewer.hrl"). 32-include("observer_defs.hrl"). 33 34%%%----------------------------------------------------------------- 35%%% Display the given information as is, no heading 36%%% Empty body if no info exists. 37warning(Info, Colors0) -> 38 Colors = convert(Colors0), 39 header(body(warning_body(Info), Colors)). 40 41warning_body(Info) -> 42 [warn(Info)]. 43 44%%%----------------------------------------------------------------- 45%%% Display the given information as is, no heading 46%%% Empty body if no info exists. 47plain_page(Info, Colors0) -> 48 Colors = convert(Colors0), 49 header(body(plain_body(Info), Colors)). 50 51plain_body(Info) -> 52 [pre(href_proc_port(lists:flatten(Info)))]. 53 54%%%----------------------------------------------------------------- 55%%% Expanded memory 56expandable_term(Heading,Expanded,Tab, Colors0) -> 57 Colors = convert(Colors0), 58 header(Heading, 59 body(expandable_term_body(Heading,Expanded,Tab,Colors), 60 Colors)). 61 62expandable_term_body(Heading,[],_Tab, _) -> 63 [case Heading of 64 "MsgQueue" -> "No messages were found"; 65 "Message Queue" -> "No messages were found"; 66 "StackDump" -> "No stack dump was found"; 67 "Dictionary" -> "No dictionary was found"; 68 "ProcState" -> "Information could not be retrieved," 69 " system messages may not be handled by this process."; 70 "SaslLog" -> "No log entry was found"; 71 "Persistent Terms" -> "No persistent terms were found" 72 end]; 73expandable_term_body(Heading,Expanded,Tab, Colors) -> 74 Attr = "BORDER=0 CELLPADDING=0 CELLSPACING=1 WIDTH=100%", 75 [case Heading of 76 "MsgQueue" -> 77 table(Attr, 78 [tr( 79 [th("WIDTH=70%","Message"), 80 th("WIDTH=30%","SeqTraceToken")]) | 81 element(1, lists:mapfoldl(fun(Msg, Even) -> 82 {msgq_table(Tab, Msg, Even, Colors), 83 not Even} 84 end, 85 true, Expanded))]); 86 "Message Queue" -> 87 table(Attr, 88 [tr( 89 [th("WIDTH=10%","Id"), 90 th("WIDTH=90%","Message")]) | 91 element(1, lists:mapfoldl(fun(Msg, {Even,N}) -> 92 {msgq_table(Tab, Msg, N, Even, Colors), 93 {not Even, N+1}} 94 end, 95 {true,1}, Expanded))]); 96 "StackDump" -> 97 table(Attr, 98 [tr( 99 [th("WIDTH=20%","Label"), 100 th("WIDTH=80%","Term")]) | 101 element(1, lists:mapfoldl(fun(Entry, Even) -> 102 {stackdump_table(Tab, Entry, Even, Colors), 103 not Even} 104 end, true, Expanded))]); 105 "ProcState" -> 106 table(Attr, 107 [tr( 108 [th("WIDTH=20%","Label"), 109 th("WIDTH=80%","Information")]) | 110 element(1, lists:mapfoldl(fun(Entry, Even) -> 111 {proc_state(Tab, Entry,Even, Colors), 112 not Even} 113 end, true, Expanded))]); 114 "SaslLog" -> 115 table(Attr, 116 [tr("BGCOLOR=white",[td("ALIGN=left", pre(href_proc_port(Expanded)))])]) ; 117 _ -> 118 table(Attr, 119 [tr( 120 [th("WIDTH=30%","Key"), 121 th("WIDTH=70%","Value")]) | 122 element(1, lists:mapfoldl(fun(Entry, Even) -> 123 {dict_table(Tab, Entry, Even, Colors), 124 not Even} 125 end, true, Expanded))]) 126 end]. 127 128msgq_table(Tab,{Msg0,Token0}, Even, Colors) -> 129 Token = case Token0 of 130 [] -> ""; 131 _ -> io_lib:fwrite("~w",[Token0]) 132 end, 133 Msg = all_or_expand(Tab,Msg0), 134 tr(color(Even, Colors),[td(pre(Msg)), td(Token)]). 135 136msgq_table(Tab,Msg0, Id, Even, Colors) -> 137 Msg = all_or_expand(Tab,Msg0), 138 tr(color(Even, Colors),[td(integer_to_list(Id)), td(pre(Msg))]). 139 140stackdump_table(Tab,{Label0,Term0},Even, Colors) -> 141 Label = io_lib:format("~ts",[Label0]), 142 Term = case atom_to_list(Label0) of 143 "y" ++ _ -> 144 %% Any term is possible, including huge ones. 145 all_or_expand(Tab,Term0); 146 _ -> 147 %% Return address or catch tag. It is known to be a 148 %% flat list, shortish, possibly containing characters 149 %% greater than 255. 150 href_proc_port(Term0) 151 end, 152 tr(color(Even, Colors), [td("VALIGN=center",pre(Label)), td(pre(Term))]). 153 154dict_table(Tab,{Key0,Value0}, Even, Colors) -> 155 Key = all_or_expand(Tab,Key0), 156 Value = all_or_expand(Tab,Value0), 157 tr(color(Even, Colors), [td("VALIGN=center",pre(Key)), td(pre(Value))]). 158 159proc_state(Tab,{Key0,Value0}, Even, Colors) -> 160 Key = lists:flatten(io_lib:format("~ts",[Key0])), 161 Value = all_or_expand(Tab,Value0), 162 tr(color(Even, Colors), [td("VALIGN=center",Key), td(pre(Value))]). 163 164all_or_expand(Tab,Term) -> 165 Preview = io_lib:format("~tP",[Term,8]), 166 Check = io_lib:format("~tP",[Term,100]), 167 Exp = Preview=/=Check, 168 all_or_expand(Tab,Term,Preview,Exp). 169all_or_expand(_Tab,Term,Str,false) 170 when not is_binary(Term) -> 171 href_proc_port(lists:flatten(Str)); 172all_or_expand(Tab,Term,Preview,true) 173 when not is_binary(Term) -> 174 Key = {Key1,Key2,Key3} = {erlang:unique_integer([positive]),1,2}, 175 ets:insert(Tab,{Key,Term}), 176 [href_proc_port(lists:flatten(Preview), false), $\n, 177 href("TARGET=\"expanded\"", 178 ["#Term?key1="++integer_to_list(Key1)++ 179 "&key2="++integer_to_list(Key2)++ 180 "&key3="++integer_to_list(Key3)], 181 "Click to expand above term")]; 182all_or_expand(Tab,Bin,_PreviewStr,_Expand) 183 when is_binary(Bin) -> 184 OBSBin = observer_lib:make_obsbin(Bin,Tab), 185 Term = io_lib:format("~tp", [OBSBin]), 186 href_proc_port(lists:flatten(Term), true). 187 188color(true, #colors{even=Even}) -> "BGCOLOR="++Even; 189color(false,#colors{odd=Odd}) -> "BGCOLOR="++Odd. 190 191%%%----------------------------------------------------------------- 192%%% Internal library 193start_html() -> 194 "<HTML>\n". 195stop_html() -> 196 "</HTML>". 197start_html_body(#colors{even=Even, fg=Fg}) -> 198 "<BODY BGCOLOR=" ++ Even ++ ">\n <FONT COLOR=" ++ Fg ++ ">\n". 199stop_html_body() -> 200 "</FONT> </BODY>\n". 201 202header(Body) -> 203 header("","",Body). 204header(Title,Body) -> 205 header(Title,"",Body). 206header(Title,JavaScript,Body) -> 207 [%only_http_header(), 208 html_header(Title,JavaScript,Body)]. 209 210html_header(Title,JavaScript,Body) -> 211 [start_html(), 212 only_html_header(Title,JavaScript), 213 Body, 214 stop_html()]. 215 216only_html_header(Title,JavaScript) -> 217 ["<HEAD>\n", 218 "<TITLE>", Title, "</TITLE>\n", 219 JavaScript, 220 "</HEAD>\n"]. 221 222body(Text, Colors) -> 223 [start_html_body(Colors), 224 Text, 225 stop_html_body()]. 226 227start_table(Args) -> 228 ["<TABLE ", Args, ">\n"]. 229stop_table() -> 230 "</TABLE>\n". 231 232table(Args,Text) -> 233 [start_table(Args), Text, stop_table()]. 234tr(Text) -> 235 ["<TR>\n", Text, "\n</TR>\n"]. 236tr(Args,Text) -> 237 ["<TR ", Args, ">\n", Text, "\n</TR>\n"]. 238th(Args,Text) -> 239 ["<TH ", Args, ">\n", Text, "\n</TH>\n"]. 240td(Text) -> 241 ["<TD>", Text, "</TD>"]. 242td(Args,Text) -> 243 ["<TD ", Args, ">", Text, "</TD>"]. 244 245start_pre() -> 246 "<PRE>". 247stop_pre() -> 248 "</PRE>". 249pre(Text) -> 250 [start_pre(),Text,stop_pre()]. 251href(Link,Text) -> 252 ["<A HREF=\"",Link,"\">",Text,"</A>"]. 253href(Args,Link,Text) -> 254 ["<A HREF=\"",Link,"\" ",Args,">",Text,"</A>"]. 255font(Args,Text) -> 256 ["<FONT ",Args,">\n",Text,"\n</FONT>\n"]. 257p(Text) -> 258 ["<P>",Text,"</P>\n"]. 259br() -> 260 "<BR>\n". 261 262 263%% In all the following, "<" is changed to "<" and ">" is changed to ">" 264href_proc_port(Text) -> 265 href_proc_port(Text,true). 266href_proc_port(Text,LinkToBin) -> 267 href_proc_port(Text,[],LinkToBin). 268href_proc_port("#Ref<"++T,Acc,LTB) -> 269 %% No links to refs 270 href_proc_port(T,["#Ref<"|Acc],LTB); 271href_proc_port("#Fun<"++T,Acc,LTB) -> 272 %% No links to funs 273 href_proc_port(T,["#Fun<"|Acc],LTB); 274href_proc_port("#Port<"++T,Acc,LTB) -> 275 {Port0,Rest} = split($>,T), 276 Port = "#Port<"++Port0 ++ ">", 277 href_proc_port(Rest,[href(Port,Port)|Acc],LTB); 278href_proc_port("<<"++T,Acc,LTB) -> 279 %% No links to binaries 280 href_proc_port(T,["<<"|Acc],LTB); 281href_proc_port("<"++([C|_]=T),Acc,LTB) when $0 =< C, C =< $9 -> 282 %% Pid 283 {Pid0,Rest} = split($>,T), 284 Pid = "<" ++ Pid0 ++ ">", 285 href_proc_port(Rest,[href(Pid,Pid)|Acc],LTB); 286href_proc_port("['#CDVBin'"++T,Acc,LTB) -> 287 %% Binary written by crashdump_viewer:parse_heap_term(...) 288 href_proc_bin(cdv, T, Acc, LTB); 289href_proc_port("['#OBSBin'"++T,Acc,LTB) -> 290 %% Binary written by crashdump_viewer:parse_heap_term(...) 291 href_proc_bin(obs, T, Acc, LTB); 292href_proc_port("['#CDVPort'"++T,Acc,LTB) -> 293 %% Port written by crashdump_viewer:parse_term(...) 294 {Port0,Rest} = split($],T), 295 PortStr= 296 case string:lexemes(Port0,",.|") of 297 [X,Y] -> 298 Port = "#Port<"++X++"."++Y++">", 299 href(Port,Port); 300 Ns -> 301 "#Port<" ++ lists:join($.,Ns) ++"...>" 302 end, 303 href_proc_port(Rest,[PortStr|Acc],LTB); 304href_proc_port("['#CDVPid'"++T,Acc,LTB) -> 305 %% Pid written by crashdump_viewer:parse_term(...) 306 {Pid0,Rest} = split($],T), 307 PidStr = 308 case string:lexemes(Pid0,",.|") of 309 [X,Y,Z] -> 310 Pid = "<"++X++"."++Y++"."++Z++">", 311 href(Pid,Pid); 312 Ns -> 313 "<" ++ lists:join($.,Ns) ++ "...>" 314 end, 315 href_proc_port(Rest,[PidStr|Acc],LTB); 316href_proc_port("'#CDVIncompleteHeap'"++T,Acc,LTB)-> 317 %% The heap is incomplete! Written by crashdump_viewer:deref_pts(...) 318 IH = lists:reverse( 319 lists:flatten( 320 "<FONT COLOR=\"#FF0000\">...(Incomplete Heap)</FONT>")), 321 href_proc_port(T,IH++Acc,LTB); 322href_proc_port("'#CDVTruncatedBinary'"++T,Acc,LTB)-> 323 %% A binary which is truncated! Written by 324 %% crashdump_viewer:parse_heap_term(...) 325 IH = lists:reverse( 326 lists:flatten( 327 "<FONT COLOR=\"#FF0000\"><<...(Truncated Binary)>>" 328 "</FONT>")), 329 href_proc_port(T,IH++Acc,LTB); 330href_proc_port("'#CDVNonexistingBinary'"++T,Acc,LTB)-> 331 %% A binary which could not be found in the dump! Written by 332 %% crashdump_viewer:parse_heap_term(...) 333 IH = lists:reverse( 334 lists:flatten( 335 "<FONT COLOR=\"#FF0000\"><<...(Nonexisting Binary)>>" 336 "</FONT>")), 337 href_proc_port(T,IH++Acc,LTB); 338href_proc_port("<"++T,Acc,LTB) -> 339 href_proc_port(T,["<"|Acc],LTB); 340href_proc_port(">"++T,Acc,LTB) -> 341 href_proc_port(T,[">"|Acc],LTB); 342href_proc_port([H|T],Acc,LTB) -> 343 href_proc_port(T,[H|Acc],LTB); 344href_proc_port([],Acc,_) -> 345 lists:reverse(Acc). 346 347href_proc_bin(From, T, Acc, LTB) -> 348 {OffsetSizePos,Rest} = split($],T), 349 BinStr = 350 case string:lexemes(OffsetSizePos,",.| \n") of 351 [Offset,SizeStr,Pos] when From =:= cdv -> 352 Size = list_to_integer(SizeStr), 353 PreviewSize = min(Size,10), 354 Id = {list_to_integer(Offset),PreviewSize,list_to_integer(Pos)}, 355 case crashdump_viewer:expand_binary(Id) of 356 {ok, '#CDVTruncatedBinary'} -> 357 lists:flatten( 358 "<FONT COLOR=\"#FF0000\">" 359 "<<...(Truncated Binary)>>" 360 "</FONT>"); 361 {ok, PreviewBin} -> 362 PreviewStr = preview_string(Size, PreviewBin), 363 if LTB -> 364 href("TARGET=\"expanded\"", 365 ["#Binary?offset="++Offset++ 366 "&size="++SizeStr++ 367 "&pos="++Pos], 368 PreviewStr); 369 true -> 370 PreviewStr 371 end 372 end; 373 [PreviewIntStr,PreviewBitSizeStr,SizeStr,Md5] when From =:= obs -> 374 Size = list_to_integer(SizeStr), 375 PreviewInt = list_to_integer(PreviewIntStr), 376 PreviewBitSize = list_to_integer(PreviewBitSizeStr), 377 PreviewStr = preview_string(Size,<<PreviewInt:PreviewBitSize>>), 378 if LTB -> 379 href("TARGET=\"expanded\"", 380 ["#OBSBinary?key1="++PreviewIntStr++ 381 "&key2="++SizeStr++ 382 "&key3="++Md5], 383 PreviewStr); 384 true -> 385 PreviewStr 386 end; 387 _ -> 388 "<< ... >>" 389 end, 390 href_proc_port(Rest,[BinStr|Acc],LTB). 391 392preview_string(Size, PreviewBin) when Size > 10 -> 393 ["<<", 394 remove_lgt(io_lib:format("~tp",[PreviewBin])), 395 "...(", 396 observer_lib:to_str({bytes,Size}), 397 ")", 398 ">>"]; 399preview_string(_, PreviewBin) -> 400 ["<<", 401 remove_lgt(io_lib:format("~tp",[PreviewBin])), 402 ">>"]. 403 404remove_lgt(Deep) -> 405 remove_lgt_1(lists:flatten(Deep)). 406 407remove_lgt_1([$<,$<|Rest]) -> 408 [$>,$>|BinStr] = lists:reverse(Rest), 409 replace_lgt(lists:reverse(BinStr)); 410remove_lgt_1(TruncBin) -> 411 TruncBin. 412 413replace_lgt([$<|R]) -> 414 ["<"|replace_lgt(R)]; 415replace_lgt([$>|R]) -> 416 [">"|replace_lgt(R)]; 417replace_lgt([L=[_|_]|R]) -> 418 [replace_lgt(L)|replace_lgt(R)]; 419replace_lgt([A|R]) -> 420 [A|replace_lgt(R)]; 421replace_lgt([]) -> []. 422 423split(Char,Str) -> 424 split(Char,Str,[]). 425split(Char,[Char|Str],Acc) -> % match Char 426 {lists:reverse(Acc),Str}; 427split(Char,[H|T],Acc) -> 428 split(Char,T,[H|Acc]). 429 430warn([]) -> 431 []; 432warn(Warning) -> 433 font("COLOR=\"#FF0000\"",p([Warning,br(),br()])). 434 435convert(#colors{fg={FR,FB,FG}, even={ER,EB,EG}, odd={OR,OG,OB}}) -> 436 #colors{fg = io_lib:format("\"#~2.16.0B~2.16.0B~2.16.0B\"", [FR,FB,FG]), 437 even = io_lib:format("\"#~2.16.0B~2.16.0B~2.16.0B\"", [ER,EB,EG]), 438 odd = io_lib:format("\"#~2.16.0B~2.16.0B~2.16.0B\"", [OR,OG,OB])}. 439