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("~w",[Label0]), 142 Term = all_or_expand(Tab,Term0), 143 tr(color(Even, Colors), [td("VALIGN=center",pre(Label)), td(pre(Term))]). 144 145dict_table(Tab,{Key0,Value0}, Even, Colors) -> 146 Key = all_or_expand(Tab,Key0), 147 Value = all_or_expand(Tab,Value0), 148 tr(color(Even, Colors), [td("VALIGN=center",pre(Key)), td(pre(Value))]). 149 150proc_state(Tab,{Key0,Value0}, Even, Colors) -> 151 Key = lists:flatten(io_lib:format("~ts",[Key0])), 152 Value = all_or_expand(Tab,Value0), 153 tr(color(Even, Colors), [td("VALIGN=center",Key), td(pre(Value))]). 154 155all_or_expand(Tab,Term) -> 156 Preview = io_lib:format("~tP",[Term,8]), 157 Check = io_lib:format("~tP",[Term,100]), 158 Exp = Preview=/=Check, 159 all_or_expand(Tab,Term,Preview,Exp). 160all_or_expand(_Tab,Term,Str,false) 161 when not is_binary(Term) -> 162 href_proc_port(lists:flatten(Str)); 163all_or_expand(Tab,Term,Preview,true) 164 when not is_binary(Term) -> 165 Key = {Key1,Key2,Key3} = {erlang:unique_integer([positive]),1,2}, 166 ets:insert(Tab,{Key,Term}), 167 [href_proc_port(lists:flatten(Preview), false), $\n, 168 href("TARGET=\"expanded\"", 169 ["#Term?key1="++integer_to_list(Key1)++ 170 "&key2="++integer_to_list(Key2)++ 171 "&key3="++integer_to_list(Key3)], 172 "Click to expand above term")]; 173all_or_expand(Tab,Bin,_PreviewStr,_Expand) 174 when is_binary(Bin) -> 175 OBSBin = observer_lib:make_obsbin(Bin,Tab), 176 Term = io_lib:format("~tp", [OBSBin]), 177 href_proc_port(lists:flatten(Term), true). 178 179color(true, #colors{even=Even}) -> "BGCOLOR="++Even; 180color(false,#colors{odd=Odd}) -> "BGCOLOR="++Odd. 181 182%%%----------------------------------------------------------------- 183%%% Internal library 184start_html() -> 185 "<HTML>\n". 186stop_html() -> 187 "</HTML>". 188start_html_body(#colors{even=Even, fg=Fg}) -> 189 "<BODY BGCOLOR=" ++ Even ++ ">\n <FONT COLOR=" ++ Fg ++ ">\n". 190stop_html_body() -> 191 "</FONT> </BODY>\n". 192 193header(Body) -> 194 header("","",Body). 195header(Title,Body) -> 196 header(Title,"",Body). 197header(Title,JavaScript,Body) -> 198 [%only_http_header(), 199 html_header(Title,JavaScript,Body)]. 200 201html_header(Title,JavaScript,Body) -> 202 [start_html(), 203 only_html_header(Title,JavaScript), 204 Body, 205 stop_html()]. 206 207only_html_header(Title,JavaScript) -> 208 ["<HEAD>\n", 209 "<TITLE>", Title, "</TITLE>\n", 210 JavaScript, 211 "</HEAD>\n"]. 212 213body(Text, Colors) -> 214 [start_html_body(Colors), 215 Text, 216 stop_html_body()]. 217 218start_table(Args) -> 219 ["<TABLE ", Args, ">\n"]. 220stop_table() -> 221 "</TABLE>\n". 222 223table(Args,Text) -> 224 [start_table(Args), Text, stop_table()]. 225tr(Text) -> 226 ["<TR>\n", Text, "\n</TR>\n"]. 227tr(Args,Text) -> 228 ["<TR ", Args, ">\n", Text, "\n</TR>\n"]. 229th(Args,Text) -> 230 ["<TH ", Args, ">\n", Text, "\n</TH>\n"]. 231td(Text) -> 232 ["<TD>", Text, "</TD>"]. 233td(Args,Text) -> 234 ["<TD ", Args, ">", Text, "</TD>"]. 235 236start_pre() -> 237 "<PRE>". 238stop_pre() -> 239 "</PRE>". 240pre(Text) -> 241 [start_pre(),Text,stop_pre()]. 242href(Link,Text) -> 243 ["<A HREF=\"",Link,"\">",Text,"</A>"]. 244href(Args,Link,Text) -> 245 ["<A HREF=\"",Link,"\" ",Args,">",Text,"</A>"]. 246font(Args,Text) -> 247 ["<FONT ",Args,">\n",Text,"\n</FONT>\n"]. 248p(Text) -> 249 ["<P>",Text,"</P>\n"]. 250br() -> 251 "<BR>\n". 252 253 254%% In all the following, "<" is changed to "<" and ">" is changed to ">" 255href_proc_port(Text) -> 256 href_proc_port(Text,true). 257href_proc_port(Text,LinkToBin) -> 258 href_proc_port(Text,[],LinkToBin). 259href_proc_port("#Ref<"++T,Acc,LTB) -> 260 %% No links to refs 261 href_proc_port(T,["#Ref<"|Acc],LTB); 262href_proc_port("#Fun<"++T,Acc,LTB) -> 263 %% No links to funs 264 href_proc_port(T,["#Fun<"|Acc],LTB); 265href_proc_port("#Port<"++T,Acc,LTB) -> 266 {Port0,Rest} = split($>,T), 267 Port = "#Port<"++Port0 ++ ">", 268 href_proc_port(Rest,[href(Port,Port)|Acc],LTB); 269href_proc_port("<<"++T,Acc,LTB) -> 270 %% No links to binaries 271 href_proc_port(T,["<<"|Acc],LTB); 272href_proc_port("<"++([C|_]=T),Acc,LTB) when $0 =< C, C =< $9 -> 273 %% Pid 274 {Pid0,Rest} = split($>,T), 275 Pid = "<" ++ Pid0 ++ ">", 276 href_proc_port(Rest,[href(Pid,Pid)|Acc],LTB); 277href_proc_port("['#CDVBin'"++T,Acc,LTB) -> 278 %% Binary written by crashdump_viewer:parse_heap_term(...) 279 href_proc_bin(cdv, T, Acc, LTB); 280href_proc_port("['#OBSBin'"++T,Acc,LTB) -> 281 %% Binary written by crashdump_viewer:parse_heap_term(...) 282 href_proc_bin(obs, T, Acc, LTB); 283href_proc_port("['#CDVPort'"++T,Acc,LTB) -> 284 %% Port written by crashdump_viewer:parse_term(...) 285 {Port0,Rest} = split($],T), 286 PortStr= 287 case string:lexemes(Port0,",.|") of 288 [X,Y] -> 289 Port = "#Port<"++X++"."++Y++">", 290 href(Port,Port); 291 Ns -> 292 "#Port<" ++ lists:join($.,Ns) ++"...>" 293 end, 294 href_proc_port(Rest,[PortStr|Acc],LTB); 295href_proc_port("['#CDVPid'"++T,Acc,LTB) -> 296 %% Pid written by crashdump_viewer:parse_term(...) 297 {Pid0,Rest} = split($],T), 298 PidStr = 299 case string:lexemes(Pid0,",.|") of 300 [X,Y,Z] -> 301 Pid = "<"++X++"."++Y++"."++Z++">", 302 href(Pid,Pid); 303 Ns -> 304 "<" ++ lists:join($.,Ns) ++ "...>" 305 end, 306 href_proc_port(Rest,[PidStr|Acc],LTB); 307href_proc_port("'#CDVIncompleteHeap'"++T,Acc,LTB)-> 308 %% The heap is incomplete! Written by crashdump_viewer:deref_pts(...) 309 IH = lists:reverse( 310 lists:flatten( 311 "<FONT COLOR=\"#FF0000\">...(Incomplete Heap)</FONT>")), 312 href_proc_port(T,IH++Acc,LTB); 313href_proc_port("'#CDVTruncatedBinary'"++T,Acc,LTB)-> 314 %% A binary which is truncated! Written by 315 %% crashdump_viewer:parse_heap_term(...) 316 IH = lists:reverse( 317 lists:flatten( 318 "<FONT COLOR=\"#FF0000\"><<...(Truncated Binary)>>" 319 "</FONT>")), 320 href_proc_port(T,IH++Acc,LTB); 321href_proc_port("'#CDVNonexistingBinary'"++T,Acc,LTB)-> 322 %% A binary which could not be found in the dump! Written by 323 %% crashdump_viewer:parse_heap_term(...) 324 IH = lists:reverse( 325 lists:flatten( 326 "<FONT COLOR=\"#FF0000\"><<...(Nonexisting Binary)>>" 327 "</FONT>")), 328 href_proc_port(T,IH++Acc,LTB); 329href_proc_port("<"++T,Acc,LTB) -> 330 href_proc_port(T,["<"|Acc],LTB); 331href_proc_port(">"++T,Acc,LTB) -> 332 href_proc_port(T,[">"|Acc],LTB); 333href_proc_port([H|T],Acc,LTB) -> 334 href_proc_port(T,[H|Acc],LTB); 335href_proc_port([],Acc,_) -> 336 lists:reverse(Acc). 337 338href_proc_bin(From, T, Acc, LTB) -> 339 {OffsetSizePos,Rest} = split($],T), 340 BinStr = 341 case string:lexemes(OffsetSizePos,",.| \n") of 342 [Offset,SizeStr,Pos] when From =:= cdv -> 343 Size = list_to_integer(SizeStr), 344 PreviewSize = min(Size,10), 345 Id = {list_to_integer(Offset),PreviewSize,list_to_integer(Pos)}, 346 case crashdump_viewer:expand_binary(Id) of 347 {ok, '#CDVTruncatedBinary'} -> 348 lists:flatten( 349 "<FONT COLOR=\"#FF0000\">" 350 "<<...(Truncated Binary)>>" 351 "</FONT>"); 352 {ok, PreviewBin} -> 353 PreviewStr = preview_string(Size, PreviewBin), 354 if LTB -> 355 href("TARGET=\"expanded\"", 356 ["#Binary?offset="++Offset++ 357 "&size="++SizeStr++ 358 "&pos="++Pos], 359 PreviewStr); 360 true -> 361 PreviewStr 362 end 363 end; 364 [PreviewIntStr,PreviewBitSizeStr,SizeStr,Md5] when From =:= obs -> 365 Size = list_to_integer(SizeStr), 366 PreviewInt = list_to_integer(PreviewIntStr), 367 PreviewBitSize = list_to_integer(PreviewBitSizeStr), 368 PreviewStr = preview_string(Size,<<PreviewInt:PreviewBitSize>>), 369 if LTB -> 370 href("TARGET=\"expanded\"", 371 ["#OBSBinary?key1="++PreviewIntStr++ 372 "&key2="++SizeStr++ 373 "&key3="++Md5], 374 PreviewStr); 375 true -> 376 PreviewStr 377 end; 378 _ -> 379 "<< ... >>" 380 end, 381 href_proc_port(Rest,[BinStr|Acc],LTB). 382 383preview_string(Size, PreviewBin) when Size > 10 -> 384 ["<<", 385 remove_lgt(io_lib:format("~tp",[PreviewBin])), 386 "...(", 387 observer_lib:to_str({bytes,Size}), 388 ")", 389 ">>"]; 390preview_string(_, PreviewBin) -> 391 ["<<", 392 remove_lgt(io_lib:format("~tp",[PreviewBin])), 393 ">>"]. 394 395remove_lgt(Deep) -> 396 remove_lgt_1(lists:flatten(Deep)). 397 398remove_lgt_1([$<,$<|Rest]) -> 399 [$>,$>|BinStr] = lists:reverse(Rest), 400 replace_lgt(lists:reverse(BinStr)); 401remove_lgt_1(TruncBin) -> 402 TruncBin. 403 404replace_lgt([$<|R]) -> 405 ["<"|replace_lgt(R)]; 406replace_lgt([$>|R]) -> 407 [">"|replace_lgt(R)]; 408replace_lgt([L=[_|_]|R]) -> 409 [replace_lgt(L)|replace_lgt(R)]; 410replace_lgt([A|R]) -> 411 [A|replace_lgt(R)]; 412replace_lgt([]) -> []. 413 414split(Char,Str) -> 415 split(Char,Str,[]). 416split(Char,[Char|Str],Acc) -> % match Char 417 {lists:reverse(Acc),Str}; 418split(Char,[H|T],Acc) -> 419 split(Char,T,[H|Acc]). 420 421warn([]) -> 422 []; 423warn(Warning) -> 424 font("COLOR=\"#FF0000\"",p([Warning,br(),br()])). 425 426convert(#colors{fg={FR,FB,FG}, even={ER,EB,EG}, odd={OR,OG,OB}}) -> 427 #colors{fg = io_lib:format("\"#~2.16.0B~2.16.0B~2.16.0B\"", [FR,FB,FG]), 428 even = io_lib:format("\"#~2.16.0B~2.16.0B~2.16.0B\"", [ER,EB,EG]), 429 odd = io_lib:format("\"#~2.16.0B~2.16.0B~2.16.0B\"", [OR,OG,OB])}. 430