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(crashdump_viewer). 21 22%% 23%% This module is the main module in the crashdump viewer. It implements 24%% the server backend for the crashdump viewer tool. 25%% 26%% Tables 27%% ------ 28%% cdv_dump_index_table: This table holds all tags read from the 29%% crashdump, except the 'binary' tag. Each tag indicates where the 30%% information about a specific item starts. The table entry for a 31%% tag includes the start position for this item-information. In a 32%% crash dump file, all tags start with a "=" at the beginning of a 33%% line. 34%% 35%% cdv_binary_index_table: This table holds all 'binary' tags. The hex 36%% address for each binary is converted to its integer value before 37%% storing Address -> Start Position in this table. The hex value of 38%% the address is never used for lookup. 39%% 40%% cdv_reg_proc_table: This table holds mappings between pid and 41%% registered name. This is used for timers and monitors. 42%% 43%% cdv_heap_file_chars: For each 'proc_heap' and 'literals' tag, this 44%% table contains the number of characters to read from the crash dump 45%% file. This is used for giving an indication in percent of the 46%% progress when parsing this data. 47%% 48%% 49%% Process state 50%% ------------- 51%% file: The name of the crashdump currently viewed. 52%% dump_vsn: The version number of the crashdump 53%% wordsize: 4 | 8, the number of bytes in a word. 54%% 55 56%% User API 57-export([start/0,start/1,stop/0,script_start/0,script_start/1]). 58 59%% GUI API 60-export([start_link/0]). 61-export([read_file/1, 62 general_info/0, 63 processes/0, 64 proc_details/1, 65 port/1, 66 ports/0, 67 ets_tables/1, 68 internal_ets_tables/0, 69 timers/1, 70 funs/0, 71 atoms/0, 72 dist_info/0, 73 node_info/1, 74 loaded_modules/0, 75 loaded_mod_details/1, 76 memory/0, 77 persistent_terms/0, 78 allocated_areas/0, 79 allocator_info/0, 80 hash_tables/0, 81 index_tables/0, 82 schedulers/0, 83 expand_binary/1]). 84 85%% Library function 86-export([to_proplist/2, to_value_list/1]). 87 88%% gen_server callbacks 89-export([init/1, handle_call/3, handle_cast/2, handle_info/2, 90 terminate/2, code_change/3]). 91 92%% Test support 93-export([get_dump_versions/0]). 94 95%% Debug support 96-export([debug/1,stop_debug/0]). 97 98-include("crashdump_viewer.hrl"). 99-include_lib("kernel/include/file.hrl"). 100-include_lib("stdlib/include/ms_transform.hrl"). 101 102-define(SERVER, crashdump_viewer_server). 103-define(call_timeout,3600000). 104-define(chunk_size,1000). % number of bytes read from crashdump at a time 105-define(max_line_size,100). % max number of bytes (i.e. characters) the 106 % line_head/1 function can return 107-define(not_available,"N/A"). 108-define(binary_size_progress_limit,10000). 109-define(max_dump_version,[0,5]). 110 111%% The value of the next define must be divisible by 4. 112-define(base64_chunk_size, (4*256)). 113 114%% All possible tags - use macros in order to avoid misspelling in the code 115-define(abort,abort). 116-define(allocated_areas,allocated_areas). 117-define(allocator,allocator). 118-define(atoms,atoms). 119-define(binary,binary). 120-define(dirty_cpu_scheduler,dirty_cpu_scheduler). 121-define(dirty_cpu_run_queue,dirty_cpu_run_queue). 122-define(dirty_io_scheduler,dirty_io_scheduler). 123-define(dirty_io_run_queue,dirty_io_run_queue). 124-define(ende,ende). 125-define(erl_crash_dump,erl_crash_dump). 126-define(ets,ets). 127-define(fu,fu). 128-define(hash_table,hash_table). 129-define(hidden_node,hidden_node). 130-define(index_table,index_table). 131-define(instr_data,instr_data). 132-define(internal_ets,internal_ets). 133-define(literals,literals). 134-define(loaded_modules,loaded_modules). 135-define(memory,memory). 136-define(memory_map,memory_map). 137-define(memory_status,memory_status). 138-define(mod,mod). 139-define(no_distribution,no_distribution). 140-define(node,node). 141-define(not_connected,not_connected). 142-define(old_instr_data,old_instr_data). 143-define(persistent_terms,persistent_terms). 144-define(port,port). 145-define(proc,proc). 146-define(proc_dictionary,proc_dictionary). 147-define(proc_heap,proc_heap). 148-define(proc_messages,proc_messages). 149-define(proc_stack,proc_stack). 150-define(scheduler,scheduler). 151-define(timer,timer). 152-define(visible_node,visible_node). 153 154 155-record(state,{file,dump_vsn,wordsize=4,num_atoms="unknown"}). 156-record(dec_opts, {bin_addr_adj=0,base64=true}). 157 158%%%----------------------------------------------------------------- 159%%% Debugging 160%% Start tracing with 161%% debug(Functions). 162%% Functions = local | global | FunctionList 163%% FunctionList = [Function] 164%% Function = {FunctionName,Arity} | FunctionName 165debug(F) -> 166 ttb:tracer(all,[{file,"cdv"}]), % tracing all nodes 167 ttb:p(all,[call,timestamp]), 168 MS = [{'_',[],[{return_trace},{message,{caller}}]}], 169 tp(F,MS), 170 ttb:ctp(?MODULE,stop_debug), % don't want tracing of the stop_debug func 171 ok. 172tp([{M,F,A}|T],MS) -> % mod:func/arity 173 ttb:tpl(M,F,A,MS), 174 tp(T,MS); 175tp([{M,F}|T],MS) -> % mod:func 176 ttb:tpl(M,F,MS), 177 tp(T,MS); 178tp([M|T],MS) -> % mod 179 ttb:tp(M,MS), % only exported 180 tp(T,MS); 181tp([],_MS) -> 182 ok. 183stop_debug() -> 184 ttb:stop([format]). 185 186%%%----------------------------------------------------------------- 187%%% User API 188start() -> 189 start(undefined). 190start(File) -> 191 cdv_wx:start(File). 192 193stop() -> 194 case whereis(?SERVER) of 195 undefined -> 196 ok; 197 Pid -> 198 Ref = erlang:monitor(process,Pid), 199 cast(stop), 200 receive {'DOWN', Ref, process, Pid, _} -> ok end 201 end. 202 203%%%----------------------------------------------------------------- 204%%% Start crashdump_viewer via the cdv script located in 205%%% $OBSERVER_PRIV_DIR/bin 206script_start() -> 207 do_script_start(fun() -> start() end), 208 erlang:halt(). 209script_start([FileAtom]) -> 210 File = atom_to_list(FileAtom), 211 case filelib:is_regular(File) of 212 true -> 213 do_script_start(fun() -> start(File) end); 214 false -> 215 io:format("cdv error: the given file does not exist\n"), 216 usage() 217 end, 218 erlang:halt(); 219script_start(_) -> 220 usage(), 221 erlang:halt(). 222 223do_script_start(StartFun) -> 224 process_flag(trap_exit,true), 225 case StartFun() of 226 ok -> 227 case whereis(cdv_wx) of 228 Pid when is_pid(Pid) -> 229 link(Pid), 230 receive 231 {'EXIT', Pid, normal} -> 232 ok; 233 {'EXIT', Pid, Reason} -> 234 io:format("\ncdv crash: ~tp\n",[Reason]) 235 end; 236 _ -> 237 %io:format("\ncdv crash: ~p\n",[unknown_reason]) 238 ok 239 end; 240 Error -> 241 io:format("\ncdv start failed: ~tp\n",[Error]) 242 end. 243 244usage() -> 245 io:format( 246 "usage: cdv [file]\n" 247 "\tThe \'file\' must be an existing erlang crash dump.\n" 248 "\tIf omitted a file dialog will be opened.\n", 249 []). 250 251%%==================================================================== 252%% External functions 253%%==================================================================== 254%%%-------------------------------------------------------------------- 255%%% Start the server - called by cdv_wx 256start_link() -> 257 case whereis(?SERVER) of 258 undefined -> 259 gen_server:start_link({local, ?SERVER}, ?MODULE, [], []); 260 Pid -> 261 {ok,Pid} 262 end. 263 264%%%----------------------------------------------------------------- 265%%% Called by cdv_wx 266read_file(File) -> 267 cast({read_file,File}). 268 269%%%----------------------------------------------------------------- 270%%% The following functions are called when the different tabs are 271%%% created 272general_info() -> 273 call(general_info). 274processes() -> 275 call(procs_summary). 276ports() -> 277 call(ports). 278ets_tables(Owner) -> 279 call({ets_tables,Owner}). 280internal_ets_tables() -> 281 call(internal_ets_tables). 282timers(Owner) -> 283 call({timers,Owner}). 284funs() -> 285 call(funs). 286atoms() -> 287 call(atoms). 288dist_info() -> 289 call(dist_info). 290node_info(Channel) -> 291 call({node_info,Channel}). 292loaded_modules() -> 293 call(loaded_mods). 294loaded_mod_details(Mod) -> 295 call({loaded_mod_details,Mod}). 296memory() -> 297 call(memory). 298persistent_terms() -> 299 call(persistent_terms). 300allocated_areas() -> 301 call(allocated_areas). 302allocator_info() -> 303 call(allocator_info). 304hash_tables() -> 305 call(hash_tables). 306index_tables() -> 307 call(index_tables). 308schedulers() -> 309 call(schedulers). 310 311%%%----------------------------------------------------------------- 312%%% Called when a link to a process (Pid) is clicked. 313proc_details(Pid) -> 314 call({proc_details,Pid}). 315 316%%%----------------------------------------------------------------- 317%%% Called when a link to a port is clicked. 318port(Id) -> 319 call({port,Id}). 320 321%%%----------------------------------------------------------------- 322%%% Called when "<< xxx bytes>>" link is clicket to open a new window 323%%% displaying the whole binary. 324expand_binary(Pos) -> 325 call({expand_binary,Pos}). 326 327%%%----------------------------------------------------------------- 328%%% For testing only - called from crashdump_viewer_SUITE 329get_dump_versions() -> 330 call(get_dump_versions). 331 332%%==================================================================== 333%% Server functions 334%%==================================================================== 335 336%%-------------------------------------------------------------------- 337%% Function: init/1 338%% Description: Initiates the server 339%% Returns: {ok, State} | 340%% {ok, State, Timeout} | 341%% ignore | 342%% {stop, Reason} 343%%-------------------------------------------------------------------- 344init([]) -> 345 ets:new(cdv_dump_index_table,[ordered_set,named_table,public]), 346 ets:new(cdv_reg_proc_table,[ordered_set,named_table,public]), 347 ets:new(cdv_binary_index_table,[ordered_set,named_table,public]), 348 ets:new(cdv_heap_file_chars,[ordered_set,named_table,public]), 349 {ok, #state{}}. 350 351%%-------------------------------------------------------------------- 352%% Function: handle_call/3 353%% Description: Handling call messages 354%% Returns: {reply, Reply, State} | 355%% {reply, Reply, State, Timeout} | 356%% {noreply, State} | 357%% {noreply, State, Timeout} | 358%% {stop, Reason, Reply, State} | (terminate/2 is called) 359%% {stop, Reason, State} (terminate/2 is called) 360%%-------------------------------------------------------------------- 361handle_call(general_info,_From,State=#state{file=File}) -> 362 GenInfo = general_info(File), 363 NumAtoms = GenInfo#general_info.num_atoms, 364 WS = parse_vsn_str(GenInfo#general_info.system_vsn,4), 365 TW = case get(truncated) of 366 true -> 367 case get(truncated_reason) of 368 undefined -> 369 ["WARNING: The crash dump is truncated. " 370 "Some information might be missing."]; 371 Reason -> 372 ["WARNING: The crash dump is truncated " 373 "("++Reason++"). " 374 "Some information might be missing."] 375 end; 376 false -> [] 377 end, 378 ets:insert(cdv_reg_proc_table, 379 {cdv_dump_node_name,GenInfo#general_info.node_name}), 380 {reply,{ok,GenInfo,TW},State#state{wordsize=WS, num_atoms=NumAtoms}}; 381handle_call({expand_binary,{Offset,Size,Pos}},_From, 382 #state{file=File,dump_vsn=DumpVsn}=State) -> 383 Fd = open(File), 384 pos_bof(Fd,Pos), 385 DecodeOpts = get_decode_opts(DumpVsn), 386 {Bin,_Line} = get_binary(Offset,Size,bytes(Fd),DecodeOpts), 387 close(Fd), 388 {reply,{ok,Bin},State}; 389handle_call(procs_summary,_From,State=#state{file=File,wordsize=WS}) -> 390 TW = truncated_warning([?proc]), 391 Procs = procs_summary(File,WS), 392 {reply,{ok,Procs,TW},State}; 393handle_call({proc_details,Pid},_From, 394 State=#state{file=File,wordsize=WS,dump_vsn=DumpVsn})-> 395 Reply = 396 case get_proc_details(File,Pid,WS,DumpVsn) of 397 {ok,Proc,TW} -> 398 {ok,Proc,TW}; 399 Other -> 400 {error,Other} 401 end, 402 {reply, Reply, State}; 403handle_call({port,Id},_From,State=#state{file=File}) -> 404 Reply = 405 case get_port(File,Id) of 406 {ok,PortInfo} -> 407 TW = truncated_warning([{?port,Id}]), 408 {ok,PortInfo,TW}; 409 Other -> 410 {error,Other} 411 end, 412 {reply,Reply,State}; 413handle_call(ports,_From,State=#state{file=File}) -> 414 TW = truncated_warning([?port]), 415 Ports = get_ports(File), 416 {reply,{ok,Ports,TW},State}; 417handle_call({ets_tables,Pid0},_From,State=#state{file=File,wordsize=WS}) -> 418 Pid = 419 case Pid0 of 420 all -> '$2'; 421 _ -> Pid0 422 end, 423 TW = truncated_warning([?ets]), 424 Ets = get_ets_tables(File,Pid,WS), 425 {reply,{ok,Ets,TW},State}; 426handle_call(internal_ets_tables,_From,State=#state{file=File,wordsize=WS}) -> 427 InternalEts = get_internal_ets_tables(File,WS), 428 TW = truncated_warning([?internal_ets]), 429 {reply,{ok,InternalEts,TW},State}; 430handle_call({timers,Pid0},_From,State=#state{file=File}) -> 431 Pid = 432 case Pid0 of 433 all -> '$2'; 434 _ -> Pid0 435 end, 436 TW = truncated_warning([?timer]), 437 Timers = get_timers(File,Pid), 438 {reply,{ok,Timers,TW},State}; 439handle_call(dist_info,_From,State=#state{file=File}) -> 440 TW = truncated_warning([?visible_node,?hidden_node,?not_connected]), 441 Nods=nods(File), 442 {reply,{ok,Nods,TW},State}; 443handle_call({node_info,Channel},_From,State=#state{file=File}) -> 444 Reply = 445 case get_node(File,Channel) of 446 {ok,Nod} -> 447 TW = truncated_warning([?visible_node, 448 ?hidden_node, 449 ?not_connected]), 450 {ok,Nod,TW}; 451 {error,Other} -> 452 {error,Other} 453 end, 454 {reply,Reply,State}; 455handle_call(loaded_mods,_From,State=#state{file=File}) -> 456 TW = truncated_warning([?mod]), 457 {_CC,_OC,Mods} = loaded_mods(File), 458 {reply,{ok,Mods,TW},State}; 459handle_call({loaded_mod_details,Mod},_From, 460 #state{dump_vsn=DumpVsn,file=File}=State) -> 461 TW = truncated_warning([{?mod,Mod}]), 462 DecodeOpts = get_decode_opts(DumpVsn), 463 ModInfo = get_loaded_mod_details(File,Mod,DecodeOpts), 464 {reply,{ok,ModInfo,TW},State}; 465handle_call(funs,_From,State=#state{file=File}) -> 466 TW = truncated_warning([?fu]), 467 Funs = funs(File), 468 {reply,{ok,Funs,TW},State}; 469handle_call(atoms,_From,State=#state{file=File,num_atoms=NumAtoms0}) -> 470 TW = truncated_warning([?atoms]), 471 NumAtoms = try list_to_integer(NumAtoms0) catch error:badarg -> -1 end, 472 Atoms = atoms(File,NumAtoms), 473 {reply,{ok,Atoms,TW},State}; 474handle_call(memory,_From,State=#state{file=File}) -> 475 Memory=memory(File), 476 TW = truncated_warning([?memory]), 477 {reply,{ok,Memory,TW},State}; 478handle_call(persistent_terms,_From,State=#state{file=File,dump_vsn=DumpVsn}) -> 479 TW = truncated_warning([?persistent_terms,?literals]), 480 DecodeOpts = get_decode_opts(DumpVsn), 481 Terms = persistent_terms(File, DecodeOpts), 482 {reply,{ok,Terms,TW},State}; 483handle_call(allocated_areas,_From,State=#state{file=File}) -> 484 AllocatedAreas=allocated_areas(File), 485 TW = truncated_warning([?allocated_areas]), 486 {reply,{ok,AllocatedAreas,TW},State}; 487handle_call(allocator_info,_From,State=#state{file=File}) -> 488 SlAlloc=allocator_info(File), 489 TW = truncated_warning([?allocator]), 490 {reply,{ok,SlAlloc,TW},State}; 491handle_call(hash_tables,_From,State=#state{file=File}) -> 492 HashTables=hash_tables(File), 493 TW = truncated_warning([?hash_table,?index_table]), 494 {reply,{ok,HashTables,TW},State}; 495handle_call(index_tables,_From,State=#state{file=File}) -> 496 IndexTables=index_tables(File), 497 TW = truncated_warning([?hash_table,?index_table]), 498 {reply,{ok,IndexTables,TW},State}; 499handle_call(schedulers,_From,State=#state{file=File}) -> 500 Schedulers=schedulers(File), 501 TW = truncated_warning([?scheduler]), 502 {reply,{ok,Schedulers,TW},State}; 503handle_call(get_dump_versions,_From,State=#state{dump_vsn=DumpVsn}) -> 504 {reply,{ok,{?max_dump_version,DumpVsn}},State}. 505 506 507%%-------------------------------------------------------------------- 508%% Function: handle_cast/2 509%% Description: Handling cast messages 510%% Returns: {noreply, State} | 511%% {noreply, State, Timeout} | 512%% {stop, Reason, State} (terminate/2 is called) 513%%-------------------------------------------------------------------- 514handle_cast({read_file,File}, _State) -> 515 case do_read_file(File) of 516 {ok,DumpVsn} -> 517 observer_lib:report_progress({ok,done}), 518 {noreply, #state{file=File,dump_vsn=DumpVsn}}; 519 Error -> 520 end_progress(Error), 521 {noreply, #state{}} 522 end; 523handle_cast(stop,State) -> 524 {stop,normal,State}. 525 526 527%%-------------------------------------------------------------------- 528%% Function: handle_info/2 529%% Description: Handling all non call/cast messages 530%% Returns: {noreply, State} | 531%% {noreply, State, Timeout} | 532%% {stop, Reason, State} (terminate/2 is called) 533%%-------------------------------------------------------------------- 534handle_info(_Info, State) -> 535 {noreply, State}. 536 537%%-------------------------------------------------------------------- 538%% Function: terminate/2 539%% Description: Shutdown the server 540%% Returns: any (ignored by gen_server) 541%%-------------------------------------------------------------------- 542terminate(_Reason, _State) -> 543 ok. 544 545%%-------------------------------------------------------------------- 546%% Func: code_change/3 547%% Purpose: Convert process state when code is changed 548%% Returns: {ok, NewState} 549%%-------------------------------------------------------------------- 550code_change(_OldVsn, State, _Extra) -> 551 {ok, State}. 552 553%%-------------------------------------------------------------------- 554%%% Internal functions 555%%-------------------------------------------------------------------- 556call(Request) -> 557 gen_server:call(?SERVER,Request,?call_timeout). 558 559cast(Msg) -> 560 gen_server:cast(?SERVER,Msg). 561 562unexpected(_Fd,{eof,_LastLine},_Where) -> 563 ok; % truncated file 564unexpected(Fd,{part,What},Where) -> 565 skip_rest_of_line(Fd), 566 io:format("WARNING: Found unexpected line in ~ts:~n~ts ...~n",[Where,What]); 567unexpected(_Fd,What,Where) -> 568 io:format("WARNING: Found unexpected line in ~ts:~n~ts~n",[Where,What]). 569 570truncated_warning([]) -> 571 []; 572truncated_warning([Tag|Tags]) -> 573 case truncated_here(Tag) of 574 true -> truncated_warning(); 575 false -> truncated_warning(Tags) 576 end. 577truncated_warning() -> 578 case get(truncated_reason) of 579 undefined -> 580 ["WARNING: The crash dump is truncated here. " 581 "Some information might be missing."]; 582 Reason -> 583 ["WARNING: The crash dump is truncated here " 584 "("++Reason++"). " 585 "Some information might be missing."] 586 end. 587 588truncated_here(Tag) -> 589 case get(truncated) of 590 true -> 591 case get(last_tag) of 592 {Tag,_Pos} -> % Tag == {TagType,Id} 593 true; 594 {{Tag,_Id},_Pos} -> 595 true; 596 _LastTag -> 597 truncated_earlier(Tag) 598 end; 599 false -> 600 false 601 end. 602 603 604%% Check if the dump was truncated with the same tag, but earlier id. 605%% Eg if this is {?proc,"<0.30.0>"}, we should warn if the dump was 606%% truncated in {?proc,"<0.29.0>"} or earlier 607truncated_earlier({?proc,Pid}) -> 608 compare_pid(Pid,get(truncated_proc)); 609truncated_earlier(_Tag) -> 610 false. 611 612compare_pid("<"++Id,"<"++OtherId) -> 613 Id>=OtherId; 614compare_pid(_,_) -> 615 false. 616 617open(File) -> 618 {ok,Fd} = file:open(File,[read,read_ahead,raw,binary]), 619 Fd. 620close(Fd) -> 621 erase(chunk), 622 file:close(Fd). 623 624%% Set position relative to beginning of file 625%% If position is within the already read Chunk, then adjust 'chunk' 626%% and 'pos' in process dictionary. Else set position in file. 627pos_bof(Fd,Pos) -> 628 case get(pos) of 629 undefined -> 630 hard_pos_bof(Fd,Pos); 631 OldPos when Pos>=OldPos -> 632 case get(chunk) of 633 undefined -> 634 hard_pos_bof(Fd,Pos); 635 Chunk -> 636 ChunkSize = byte_size(Chunk), 637 ChunkEnd = OldPos+ChunkSize, 638 if Pos=<ChunkEnd -> 639 Diff = Pos-OldPos, 640 put(pos,Pos), 641 put(chunk,binary:part(Chunk,Diff,ChunkEnd-Pos)); 642 true -> 643 hard_pos_bof(Fd,Pos) 644 end 645 end; 646 _ -> 647 hard_pos_bof(Fd,Pos) 648 end. 649 650hard_pos_bof(Fd,Pos) -> 651 reset_chunk(), 652 file:position(Fd,{bof,Pos}). 653 654 655get_chunk(Fd) -> 656 case erase(chunk) of 657 undefined -> 658 case read(Fd) of 659 eof -> 660 put_pos(Fd), 661 eof; 662 Other -> 663 Other 664 end; 665 Bin -> 666 {ok,Bin} 667 end. 668 669%% Read and report progress 670progress_read(Fd) -> 671 {R,Bytes} = 672 case read(Fd) of 673 {ok,Bin} -> 674 {{ok,Bin},byte_size(Bin)}; 675 Other -> 676 {Other,0} 677 end, 678 update_progress(Bytes), 679 R. 680 681read(Fd) -> 682 file:read(Fd,?chunk_size). 683 684put_chunk(Fd,Bin) -> 685 {ok,Pos0} = file:position(Fd,cur), 686 Pos = Pos0 - byte_size(Bin), 687 put(chunk,Bin), 688 put(pos,Pos). 689 690put_pos(Fd) -> 691 {ok,Pos} = file:position(Fd,cur), 692 put(pos,Pos). 693 694reset_chunk() -> 695 erase(chunk), 696 erase(pos). 697 698line_head(Fd) -> 699 case get_chunk(Fd) of 700 {ok,Bin} -> line_head(Fd,Bin,[],0); 701 eof -> {eof,[]} 702 end. 703line_head(Fd,Bin,Acc,?max_line_size) -> 704 put_chunk(Fd,Bin), 705 {part,lists:reverse(Acc)}; 706line_head(Fd,<<$\n:8,Bin/binary>>,Acc,_N) -> 707 put_chunk(Fd,Bin), 708 lists:reverse(Acc); 709line_head(Fd,<<$::8,$\r:8,$\n:8,Bin/binary>>,Acc,_N) -> 710 put_chunk(Fd,Bin), 711 lists:reverse(Acc); 712line_head(Fd,<<$::8,$\r:8>>,Acc,N) -> 713 case get_chunk(Fd) of 714 {ok,Bin} -> line_head(Fd,<<$:,Bin/binary>>,Acc,N); 715 eof -> {eof,lists:reverse(Acc)} 716 end; 717line_head(Fd,<<$::8>>,Acc,N) -> 718 case get_chunk(Fd) of 719 {ok,Bin} -> line_head(Fd,<<$:,Bin/binary>>,Acc,N); 720 eof -> {eof,lists:reverse(Acc)} 721 end; 722line_head(Fd,<<$::8,Space:8,Bin/binary>>,Acc,_N) when Space=:=$ ;Space=:=$\n -> 723 put_chunk(Fd,Bin), 724 lists:reverse(Acc); 725line_head(Fd,<<$::8,Bin/binary>>,Acc,_N) -> 726 put_chunk(Fd,Bin), 727 lists:reverse(Acc); 728line_head(Fd,<<$\r:8,Bin/binary>>,Acc,N) -> 729 line_head(Fd,Bin,Acc,N+1); 730line_head(Fd,<<Char:8,Bin/binary>>,Acc,N) -> 731 line_head(Fd,Bin,[Char|Acc],N+1); 732line_head(Fd,<<>>,Acc,N) -> 733 case get_chunk(Fd) of 734 {ok,Bin} -> line_head(Fd,Bin,Acc,N); 735 eof -> {eof,lists:reverse(Acc)} 736 end. 737 738skip_rest_of_line(Fd) -> 739 case get_chunk(Fd) of 740 {ok,Bin} -> skip(Fd,Bin); 741 eof -> ok 742 end. 743skip(Fd,<<$\n:8,Bin/binary>>) -> 744 put_chunk(Fd,Bin), 745 ok; 746skip(Fd,<<_Char:8,Bin/binary>>) -> 747 skip(Fd,Bin); 748skip(Fd,<<>>) -> 749 case get_chunk(Fd) of 750 {ok,Bin} -> skip(Fd,Bin); 751 eof -> ok 752 end. 753 754 755string(Fd) -> 756 string(Fd, "-1"). 757string(Fd,NoExist) -> 758 case bytes(Fd,noexist) of 759 noexist -> NoExist; 760 Val -> byte_list_to_string(Val) 761 end. 762 763byte_list_to_string(ByteList) -> 764 Bin = list_to_binary(ByteList), 765 case unicode:characters_to_list(Bin) of 766 Str when is_list(Str) -> Str; 767 _ -> ByteList 768 end. 769 770bytes(Fd) -> 771 bytes(Fd, "-1"). 772bytes(Fd, NoExist) -> 773 case get_rest_of_line(Fd) of 774 {eof,[]} -> NoExist; 775 [] -> NoExist; 776 {eof,Val} -> Val; 777 "=abort:"++_ -> NoExist; 778 Val -> Val 779 end. 780 781get_rest_of_line(Fd) -> 782 case get_chunk(Fd) of 783 {ok,Bin} -> get_rest_of_line_1(Fd, Bin, []); 784 eof -> {eof,[]} 785 end. 786 787get_rest_of_line_1(Fd, <<$\n:8,Bin/binary>>, Acc) -> 788 put_chunk(Fd, Bin), 789 lists:reverse(Acc); 790get_rest_of_line_1(Fd, <<$\r:8,Rest/binary>>, Acc) -> 791 get_rest_of_line_1(Fd, Rest, Acc); 792get_rest_of_line_1(Fd, <<Char:8,Rest/binary>>, Acc) -> 793 get_rest_of_line_1(Fd, Rest, [Char|Acc]); 794get_rest_of_line_1(Fd, <<>>, Acc) -> 795 case get_chunk(Fd) of 796 {ok,Bin} -> get_rest_of_line_1(Fd, Bin, Acc); 797 eof -> {eof,lists:reverse(Acc)} 798 end. 799 800split(Str) -> 801 split($ ,Str,[]). 802split(Char,Str) -> 803 split(Char,Str,[]). 804split(Char,[Char|Str],Acc) -> % match Char 805 {lists:reverse(Acc),Str}; 806split(_Char,[$\r,$\n|Str],Acc) -> % new line 807 {lists:reverse(Acc),Str}; 808split(_Char,[$\n|Str],Acc) -> % new line 809 {lists:reverse(Acc),Str}; 810split(Char,[H|T],Acc) -> 811 split(Char,T,[H|Acc]); 812split(_Char,[],Acc) -> 813 {lists:reverse(Acc),[]}. 814 815%%%----------------------------------------------------------------- 816%%% 817parse_vsn_str([],WS) -> 818 WS; 819parse_vsn_str(Str,WS) -> 820 case Str of 821 "[64-bit]" ++ _Rest -> 822 8; 823 [_Char|Rest] -> 824 parse_vsn_str(Rest,WS) 825 end. 826 827 828%%%----------------------------------------------------------------- 829%%% Traverse crash dump and insert index in table for each heading. 830%%% Progress is reported during the time. 831do_read_file(File) -> 832 erase(?literals), %Clear literal cache. 833 put(truncated,false), %Not truncated (yet). 834 erase(truncated_reason), %Not truncated (yet). 835 case file:read_file_info(File) of 836 {ok,#file_info{type=regular, 837 access=FileA, 838 size=Size}} when FileA=:=read; FileA=:=read_write -> 839 Fd = open(File), 840 init_progress("Reading file",Size), 841 case progress_read(Fd) of 842 {ok,<<$=:8,TagAndRest/binary>>} -> 843 {Tag,Id,Rest,N1} = tag(Fd,TagAndRest,1), 844 case Tag of 845 ?erl_crash_dump -> 846 case check_dump_version(Id) of 847 {ok,DumpVsn} -> 848 reset_tables(), 849 insert_index(Tag,Id,Pos=N1+1), 850 put_last_tag(Tag,"",Pos), 851 DecodeOpts = get_decode_opts(DumpVsn), 852 indexify(Fd,DecodeOpts,Rest,N1), 853 end_progress(), 854 check_if_truncated(), 855 close(Fd), 856 {ok,DumpVsn}; 857 Error -> 858 close(Fd), 859 Error 860 end; 861 _Other -> 862 R = io_lib:format( 863 "~ts is not an Erlang crash dump~n", 864 [File]), 865 close(Fd), 866 {error,R} 867 end; 868 {ok,<<"<Erlang crash dump>",_Rest/binary>>} -> 869 %% old version - no longer supported 870 R = io_lib:format( 871 "The crashdump ~ts is in the pre-R10B format, " 872 "which is no longer supported.~n", 873 [File]), 874 close(Fd), 875 {error,R}; 876 _Other -> 877 R = io_lib:format( 878 "~ts is not an Erlang crash dump~n", 879 [File]), 880 close(Fd), 881 {error,R} 882 end; 883 _other -> 884 R = io_lib:format("~ts is not an Erlang crash dump~n",[File]), 885 {error,R} 886 end. 887 888check_dump_version(Vsn) -> 889 DumpVsn = [list_to_integer(L) || L<-string:lexemes(Vsn,".")], 890 if DumpVsn > ?max_dump_version -> 891 Info = 892 "This Crashdump Viewer is too old for the given " 893 "Erlang crash dump. Please use a newer version of " 894 "Crashdump Viewer.", 895 {error,Info}; 896 true -> 897 {ok,DumpVsn} 898 end. 899 900indexify(Fd,DecodeOpts,Bin,N) -> 901 case binary:match(Bin,<<"\n=">>) of 902 {Start,Len} -> 903 Pos = Start+Len, 904 <<_:Pos/binary,TagAndRest/binary>> = Bin, 905 {Tag,Id,Rest,N1} = tag(Fd,TagAndRest,N+Pos), 906 NewPos = N1+1, % +1 to get past newline 907 case Tag of 908 ?binary -> 909 %% Binaries are stored in a separate table in 910 %% order to minimize lookup time. Key is the 911 %% translated address. 912 {HexAddr,_} = get_hex(Id), 913 Addr = HexAddr bor DecodeOpts#dec_opts.bin_addr_adj, 914 insert_binary_index(Addr,NewPos); 915 _ -> 916 insert_index(Tag,Id,NewPos) 917 end, 918 case put_last_tag(Tag,Id,NewPos) of 919 {{?proc_heap,LastId},LastPos} -> 920 ets:insert(cdv_heap_file_chars,{LastId,N+Start+1-LastPos}); 921 {{?literals,[]},LastPos} -> 922 ets:insert(cdv_heap_file_chars,{literals,N+Start+1-LastPos}); 923 _ -> ok 924 end, 925 indexify(Fd,DecodeOpts,Rest,N1); 926 nomatch -> 927 case progress_read(Fd) of 928 {ok,Chunk0} when is_binary(Chunk0) -> 929 {Chunk,N1} = 930 case binary:last(Bin) of 931 $\n -> 932 {<<$\n,Chunk0/binary>>,N+byte_size(Bin)-1}; 933 _ -> 934 {Chunk0,N+byte_size(Bin)} 935 end, 936 indexify(Fd,DecodeOpts,Chunk,N1); 937 eof -> 938 eof 939 end 940 end. 941 942tag(Fd,Bin,N) -> 943 tag(Fd,Bin,N,[],[],tag). 944tag(_Fd,<<$\n:8,_/binary>>=Rest,N,Gat,Di,_Now) -> 945 {tag_to_atom(lists:reverse(Gat)),lists:reverse(Di),Rest,N}; 946tag(Fd,<<$\r:8,Rest/binary>>,N,Gat,Di,Now) -> 947 tag(Fd,Rest,N+1,Gat,Di,Now); 948tag(Fd,<<$::8,IdAndRest/binary>>,N,Gat,Di,tag) -> 949 tag(Fd,IdAndRest,N+1,Gat,Di,id); 950tag(Fd,<<Char:8,Rest/binary>>,N,Gat,Di,tag) -> 951 tag(Fd,Rest,N+1,[Char|Gat],Di,tag); 952tag(Fd,<<Char:8,Rest/binary>>,N,Gat,Di,id) -> 953 tag(Fd,Rest,N+1,Gat,[Char|Di],id); 954tag(Fd,<<>>,N,Gat,Di,Now) -> 955 case progress_read(Fd) of 956 {ok,Chunk} when is_binary(Chunk) -> 957 tag(Fd,Chunk,N,Gat,Di,Now); 958 eof -> 959 {tag_to_atom(lists:reverse(Gat)),lists:reverse(Di),<<>>,N} 960 end. 961 962check_if_truncated() -> 963 case get(last_tag) of 964 {{?ende,_},_} -> 965 put(truncated,false), 966 put(truncated_proc,false); 967 {{?literals,[]},_} -> 968 put(truncated,true), 969 put(truncated_proc,false), 970 %% Literals are truncated. Make sure we never 971 %% attempt to read in the literals. (Heaps that 972 %% references literals will show markers for 973 %% incomplete heaps, but will otherwise work.) 974 delete_index(?literals, []); 975 {TruncatedTag,_} -> 976 put(truncated,true), 977 find_truncated_proc(TruncatedTag) 978 end. 979 980find_truncated_proc({Tag,_Id}) when Tag==?atoms; 981 Tag==?binary; 982 Tag==?instr_data; 983 Tag==?memory_status; 984 Tag==?memory_map -> 985 put(truncated_proc,false); 986find_truncated_proc({Tag,Pid}) -> 987 case is_proc_tag(Tag) of 988 true -> 989 put(truncated_proc,Pid); 990 false -> 991 %% This means that the dump is truncated between ?proc and 992 %% ?proc_heap => memory info is missing for all procs. 993 put(truncated_proc,"<0.0.0>") 994 end. 995 996is_proc_tag(Tag) when Tag==?proc; 997 Tag==?proc_dictionary; 998 Tag==?proc_messages; 999 Tag==?proc_stack; 1000 Tag==?proc_heap -> 1001 true; 1002is_proc_tag(_) -> 1003 false. 1004 1005%%%----------------------------------------------------------------- 1006%%% Functions for reading information from the dump 1007general_info(File) -> 1008 [{_Id,Start}] = lookup_index(?erl_crash_dump), 1009 Fd = open(File), 1010 pos_bof(Fd,Start), 1011 Created = case get_rest_of_line(Fd) of 1012 {eof,SomeOfLine} -> SomeOfLine; 1013 WholeLine -> WholeLine 1014 end, 1015 1016 {Slogan,SysVsn} = get_slogan_and_sysvsn(Fd,[]), 1017 GI = get_general_info(Fd,#general_info{created=Created, 1018 slogan=Slogan, 1019 system_vsn=SysVsn}), 1020 1021 {MemTot,MemMax} = 1022 case lookup_index(?memory) of 1023 [{_,MemStart}] -> 1024 pos_bof(Fd,MemStart), 1025 Memory = get_meminfo(Fd,[]), 1026 Tot = case lists:keysearch(total,1,Memory) of 1027 {value,{_,T}} -> T; 1028 false -> "" 1029 end, 1030 Max = case lists:keysearch(maximum,1,Memory) of 1031 {value,{_,M}} -> M; 1032 false -> "" 1033 end, 1034 {Tot,Max}; 1035 _ -> 1036 {"",""} 1037 end, 1038 1039 close(Fd), 1040 {NumProcs,NumEts,NumFuns,NumTimers} = count(), 1041 NodeName = 1042 case lookup_index(?node) of 1043 [{N,_Start}] -> 1044 N; 1045 [] -> 1046 case lookup_index(?no_distribution) of 1047 [_] -> "'nonode@nohost'"; 1048 [] -> "unknown" 1049 end 1050 end, 1051 1052 InstrInfo = 1053 case lookup_index(?old_instr_data) of 1054 [] -> 1055 case lookup_index(?instr_data) of 1056 [] -> 1057 false; 1058 _ -> 1059 instr_data 1060 end; 1061 _ -> 1062 old_instr_data 1063 end, 1064 GI#general_info{node_name=NodeName, 1065 num_procs=integer_to_list(NumProcs), 1066 num_ets=integer_to_list(NumEts), 1067 num_timers=integer_to_list(NumTimers), 1068 num_fun=integer_to_list(NumFuns), 1069 mem_tot=MemTot, 1070 mem_max=MemMax, 1071 instr_info=InstrInfo}. 1072 1073get_slogan_and_sysvsn(Fd,Acc) -> 1074 case string(Fd,eof) of 1075 "Slogan: " ++ SloganPart when Acc==[] -> 1076 get_slogan_and_sysvsn(Fd,[SloganPart]); 1077 "System version: " ++ SystemVsn -> 1078 {lists:append(lists:reverse(Acc)),SystemVsn}; 1079 eof -> 1080 {lists:append(lists:reverse(Acc)),"-1"}; 1081 SloganPart -> 1082 get_slogan_and_sysvsn(Fd,[[$\n|SloganPart]|Acc]) 1083 end. 1084 1085get_general_info(Fd,GenInfo) -> 1086 case line_head(Fd) of 1087 "Compiled" -> 1088 get_general_info(Fd,GenInfo#general_info{compile_time=bytes(Fd)}); 1089 "Taints" -> 1090 Val = case string(Fd) of "-1" -> "(none)"; Line -> Line end, 1091 get_general_info(Fd,GenInfo#general_info{taints=Val}); 1092 "Atoms" -> 1093 get_general_info(Fd,GenInfo#general_info{num_atoms=bytes(Fd)}); 1094 "Calling Thread" -> 1095 get_general_info(Fd,GenInfo#general_info{thread=bytes(Fd)}); 1096 "=" ++ _next_tag -> 1097 GenInfo; 1098 Other -> 1099 unexpected(Fd,Other,"general information"), 1100 GenInfo 1101 end. 1102 1103count() -> 1104 {count_index(?proc),count_index(?ets),count_index(?fu),count_index(?timer)}. 1105 1106 1107%%----------------------------------------------------------------- 1108%% Page with all processes 1109procs_summary(File,WS) -> 1110 ParseFun = fun(Fd,Pid0) -> 1111 Pid = list_to_pid(Pid0), 1112 Proc = get_procinfo(Fd,fun main_procinfo/5, 1113 #proc{pid=Pid},WS), 1114 case Proc#proc.name of 1115 undefined -> 1116 true; 1117 Name -> 1118 %% Registered process - store to allow 1119 %% lookup for timers connected to 1120 %% registered name instead of pid. 1121 ets:insert(cdv_reg_proc_table,{Name,Pid}), 1122 ets:insert(cdv_reg_proc_table,{Pid0,Name}) 1123 end, 1124 case Proc#proc.memory of 1125 undefined -> Proc#proc{memory=Proc#proc.stack_heap}; 1126 _ -> Proc 1127 end 1128 end, 1129 lookup_and_parse_index(File,?proc,ParseFun,"processes"). 1130 1131%%----------------------------------------------------------------- 1132%% Page with one process 1133get_proc_details(File,Pid,WS,DumpVsn) -> 1134 case lookup_index(?proc,Pid) of 1135 [{_,Start}] -> 1136 Fd = open(File), 1137 {{Stack,MsgQ,Dict},TW} = 1138 case truncated_warning([{?proc,Pid}]) of 1139 [] -> 1140 expand_memory(Fd,Pid,DumpVsn); 1141 TW0 -> 1142 {{[],[],[]},TW0} 1143 end, 1144 pos_bof(Fd,Start), 1145 Proc0 = #proc{pid=Pid,stack_dump=Stack,msg_q=MsgQ,dict=Dict}, 1146 Proc = get_procinfo(Fd,fun all_procinfo/5,Proc0,WS), 1147 close(Fd), 1148 {ok,Proc,TW}; 1149 _ -> 1150 maybe_other_node(Pid) 1151 end. 1152 1153get_procinfo(Fd,Fun,Proc,WS) -> 1154 case line_head(Fd) of 1155 "State" -> 1156 State = case bytes(Fd) of 1157 "Garbing" -> "Garbing\n(limited info)"; 1158 State0 -> State0 1159 end, 1160 get_procinfo(Fd,Fun,Proc#proc{state=State},WS); 1161 "Name" -> 1162 get_procinfo(Fd,Fun,Proc#proc{name=string(Fd)},WS); 1163 "Spawned as" -> 1164 IF = string(Fd), 1165 case Proc#proc.name of 1166 undefined -> 1167 get_procinfo(Fd,Fun,Proc#proc{name=IF,init_func=IF},WS); 1168 _ -> 1169 get_procinfo(Fd,Fun,Proc#proc{init_func=IF},WS) 1170 end; 1171 "Message queue length" -> 1172 %% stored as integer so we can sort on it 1173 get_procinfo(Fd,Fun,Proc#proc{msg_q_len=list_to_integer(bytes(Fd))},WS); 1174 "Reductions" -> 1175 %% stored as integer so we can sort on it 1176 get_procinfo(Fd,Fun,Proc#proc{reds=list_to_integer(bytes(Fd))},WS); 1177 "Stack+heap" -> 1178 %% stored as integer so we can sort on it 1179 get_procinfo(Fd,Fun,Proc#proc{stack_heap= 1180 list_to_integer(bytes(Fd))*WS},WS); 1181 "Memory" -> 1182 %% stored as integer so we can sort on it 1183 get_procinfo(Fd,Fun,Proc#proc{memory=list_to_integer(bytes(Fd))},WS); 1184 {eof,_} -> 1185 Proc; % truncated file 1186 Other -> 1187 Fun(Fd,Fun,Proc,WS,Other) 1188 end. 1189 1190main_procinfo(Fd,Fun,Proc,WS,LineHead) -> 1191 case LineHead of 1192 "=" ++ _next_tag -> 1193 Proc; 1194 "arity = " ++ _ -> 1195 %%! Temporary workaround 1196 get_procinfo(Fd,Fun,Proc,WS); 1197 _Other -> 1198 skip_rest_of_line(Fd), 1199 get_procinfo(Fd,Fun,Proc,WS) 1200 end. 1201all_procinfo(Fd,Fun,Proc,WS,LineHead) -> 1202 case LineHead of 1203 %% - START - moved from get_procinfo - 1204 "Spawned by" -> 1205 case bytes(Fd) of 1206 "[]" -> 1207 get_procinfo(Fd,Fun,Proc,WS); 1208 Parent -> 1209 get_procinfo(Fd,Fun,Proc#proc{parent=Parent},WS) 1210 end; 1211 "Started" -> 1212 get_procinfo(Fd,Fun,Proc#proc{start_time=bytes(Fd)},WS); 1213 "Last scheduled in for" -> 1214 get_procinfo(Fd,Fun,Proc#proc{current_func= 1215 {"Last scheduled in for", 1216 string(Fd)}},WS); 1217 "Current call" -> 1218 get_procinfo(Fd,Fun,Proc#proc{current_func={"Current call", 1219 string(Fd)}},WS); 1220 "Number of heap fragments" -> 1221 get_procinfo(Fd,Fun,Proc#proc{num_heap_frag=bytes(Fd)},WS); 1222 "Heap fragment data" -> 1223 get_procinfo(Fd,Fun,Proc#proc{heap_frag_data=bytes(Fd)},WS); 1224 "OldHeap" -> 1225 Bytes = list_to_integer(bytes(Fd))*WS, 1226 get_procinfo(Fd,Fun,Proc#proc{old_heap=Bytes},WS); 1227 "Heap unused" -> 1228 Bytes = list_to_integer(bytes(Fd))*WS, 1229 get_procinfo(Fd,Fun,Proc#proc{heap_unused=Bytes},WS); 1230 "OldHeap unused" -> 1231 Bytes = list_to_integer(bytes(Fd))*WS, 1232 get_procinfo(Fd,Fun,Proc#proc{old_heap_unused=Bytes},WS); 1233 "BinVHeap" -> 1234 Bytes = list_to_integer(bytes(Fd))*WS, 1235 get_procinfo(Fd,Fun,Proc#proc{bin_vheap=Bytes},WS); 1236 "OldBinVHeap" -> 1237 Bytes = list_to_integer(bytes(Fd))*WS, 1238 get_procinfo(Fd,Fun,Proc#proc{old_bin_vheap=Bytes},WS); 1239 "BinVHeap unused" -> 1240 Bytes = list_to_integer(bytes(Fd))*WS, 1241 get_procinfo(Fd,Fun,Proc#proc{bin_vheap_unused=Bytes},WS); 1242 "OldBinVHeap unused" -> 1243 Bytes = case bytes(Fd) of 1244 "overflow" -> -1; 1245 Int -> list_to_integer(Int)*WS 1246 end, 1247 get_procinfo(Fd,Fun,Proc#proc{old_bin_vheap_unused=Bytes},WS); 1248 "New heap start" -> 1249 get_procinfo(Fd,Fun,Proc#proc{new_heap_start=bytes(Fd)},WS); 1250 "New heap top" -> 1251 get_procinfo(Fd,Fun,Proc#proc{new_heap_top=bytes(Fd)},WS); 1252 "Stack top" -> 1253 get_procinfo(Fd,Fun,Proc#proc{stack_top=bytes(Fd)},WS); 1254 "Stack end" -> 1255 get_procinfo(Fd,Fun,Proc#proc{stack_end=bytes(Fd)},WS); 1256 "Old heap start" -> 1257 get_procinfo(Fd,Fun,Proc#proc{old_heap_start=bytes(Fd)},WS); 1258 "Old heap top" -> 1259 get_procinfo(Fd,Fun,Proc#proc{old_heap_top=bytes(Fd)},WS); 1260 "Old heap end" -> 1261 get_procinfo(Fd,Fun,Proc#proc{old_heap_end=bytes(Fd)},WS); 1262 %% - END - moved from get_procinfo - 1263 "Last calls" -> 1264 get_procinfo(Fd,Fun,Proc#proc{last_calls=get_last_calls(Fd)},WS); 1265 "Link list" -> 1266 {Links,Monitors,MonitoredBy} = get_link_list(Fd), 1267 get_procinfo(Fd,Fun,Proc#proc{links=Links, 1268 monitors=Monitors, 1269 mon_by=MonitoredBy},WS); 1270 "Program counter" -> 1271 get_procinfo(Fd,Fun,Proc#proc{prog_count=string(Fd)},WS); 1272 "CP" -> 1273 get_procinfo(Fd,Fun,Proc#proc{cp=string(Fd)},WS); 1274 "arity = " ++ Arity -> 1275 %%! Temporary workaround 1276 get_procinfo(Fd,Fun,Proc#proc{arity=Arity--"\r\n"},WS); 1277 "Run queue" -> 1278 get_procinfo(Fd,Fun,Proc#proc{run_queue=string(Fd)},WS); 1279 "Internal State" -> 1280 get_procinfo(Fd,Fun,Proc#proc{int_state=string(Fd)},WS); 1281 "=" ++ _next_tag -> 1282 Proc; 1283 Other -> 1284 unexpected(Fd,Other,"process info"), 1285 get_procinfo(Fd,Fun,Proc,WS) 1286 end. 1287 1288%% The end of the 'Last calls' section is meant to be an empty line, 1289%% but in some cases this is not the case, so we also need to look for 1290%% the next heading which currently (OTP-20.1) can be "Link list: ", 1291%% "Dictionary: " or "Reductions: ". We do this by looking for ": " 1292%% and when found, pushing the heading back into the saved chunk. 1293%% 1294%% Note that the 'Last calls' section is only present if the 1295%% 'save_calls' process flag is set. 1296get_last_calls(Fd) -> 1297 case get_chunk(Fd) of 1298 {ok,Bin} -> 1299 get_last_calls(Fd,Bin,[],[]); 1300 eof -> 1301 [] 1302 end. 1303get_last_calls(Fd,<<$\n:8,Bin/binary>>,[],Lines) -> 1304 %% Empty line - we're done 1305 put_chunk(Fd,Bin), 1306 lists:reverse(Lines); 1307get_last_calls(Fd,<<$::8>>,Acc,Lines) -> 1308 case get_chunk(Fd) of 1309 {ok,Bin} -> 1310 %% Could be a colon followed by a space - see next function clause 1311 get_last_calls(Fd,<<$::8,Bin/binary>>,Acc,Lines); 1312 eof -> 1313 %% Truncated here - either we've got the next heading, or 1314 %% it was truncated in a last call function, in which case 1315 %% we note that it was truncated 1316 case byte_list_to_string(lists:reverse(Acc)) of 1317 NextHeading when NextHeading=="Link list"; 1318 NextHeading=="Dictionary"; 1319 NextHeading=="Reductions" -> 1320 put_chunk(Fd,list_to_binary(NextHeading++":")), 1321 lists:reverse(Lines); 1322 LastCallFunction-> 1323 lists:reverse(Lines,[LastCallFunction++":...(truncated)"]) 1324 end 1325 end; 1326get_last_calls(Fd,<<$\::8,$\s:8,Bin/binary>>,Acc,Lines) -> 1327 %% ": " - means we have the next heading in Acc - save it back 1328 %% into the chunk and return the lines we've found 1329 HeadingBin = list_to_binary(lists:reverse(Acc,[$:])), 1330 put_chunk(Fd,<<HeadingBin/binary,Bin/binary>>), 1331 lists:reverse(Lines); 1332get_last_calls(Fd,<<$\n:8,Bin/binary>>,Acc,Lines) -> 1333 get_last_calls(Fd,Bin,[],[byte_list_to_string(lists:reverse(Acc))|Lines]); 1334get_last_calls(Fd,<<$\r:8,Bin/binary>>,Acc,Lines) -> 1335 get_last_calls(Fd,Bin,Acc,Lines); 1336get_last_calls(Fd,<<$\s:8,Bin/binary>>,[],Lines) -> 1337 get_last_calls(Fd,Bin,[],Lines); 1338get_last_calls(Fd,<<Char:8,Bin/binary>>,Acc,Lines) -> 1339 get_last_calls(Fd,Bin,[Char|Acc],Lines); 1340get_last_calls(Fd,<<>>,Acc,Lines) -> 1341 case get_chunk(Fd) of 1342 {ok,Bin} -> 1343 get_last_calls(Fd,Bin,Acc,Lines); 1344 eof -> 1345 lists:reverse(Lines,[byte_list_to_string(lists:reverse(Acc))]) 1346 end. 1347 1348get_link_list(Fd) -> 1349 case string(Fd) of 1350 "[" ++ Rest -> 1351 #{links:=Links, 1352 mons:=Monitors, 1353 mon_by:=MonitoredBy} = 1354 get_link_list(Rest,#{links=>[],mons=>[],mon_by=>[]}), 1355 {lists:reverse(Links), 1356 lists:reverse(Monitors), 1357 lists:reverse(MonitoredBy)}; 1358 eof -> 1359 {[],[],[]} 1360 end. 1361 1362get_link_list(Bin,Acc) -> 1363 case string:split(Bin,", ") of 1364 [Link,Rest] -> 1365 get_link_list(Rest,get_link(Link,Acc)); 1366 [Link] -> 1367 get_link(string:trim(Link,trailing,"]"),Acc) 1368 end. 1369 1370get_link("#Port"++_=PortStr,#{links:=Links}=Acc) -> 1371 Acc#{links=>[{PortStr,PortStr}|Links]}; 1372get_link("<"++_=PidStr,#{links:=Links}=Acc) -> 1373 Acc#{links=>[{PidStr,PidStr}|Links]}; 1374get_link("{to," ++ Rest,#{mons:=Monitors}=Acc) -> 1375 Acc#{mons=>[parse_monitor(Rest)|Monitors]}; 1376get_link("{from," ++ Rest,#{mon_by:=MonitoredBy}=Acc) -> 1377 Acc#{mon_by=>[parse_monitor(Rest)|MonitoredBy]}; 1378get_link(Unexpected,Acc) -> 1379 io:format("WARNING: found unexpected data in link list:~n~ts~n",[Unexpected]), 1380 Acc. 1381 1382parse_monitor(Monitor) -> 1383 case string:lexemes(Monitor,",{}") of 1384 [Node,"[]"] -> 1385 {Node,Node++" node monitor"}; 1386 [Pid,Ref] -> 1387 {Pid,Pid++" ("++Ref++")"}; 1388 [Name,Node,Ref] -> 1389 %% Named process 1390 PidStr = get_pid_from_name(Name,Node), 1391 {PidStr,"{"++Name++","++Node++"} ("++Ref++")"} 1392 end. 1393 1394get_pid_from_name(Name,Node) -> 1395 case ets:lookup(cdv_reg_proc_table,cdv_dump_node_name) of 1396 [{_,Node}] -> 1397 case ets:lookup(cdv_reg_proc_table,Name) of 1398 [{_,Pid}] when is_pid(Pid) -> 1399 pid_to_list(Pid); 1400 _ -> 1401 "<unkonwn_pid>" 1402 end; 1403 _ -> 1404 "<unknown_pid_other_node>" 1405 end. 1406 1407maybe_other_node(Id) -> 1408 Channel = 1409 case split($.,Id) of 1410 {"<" ++ N, _Rest} -> 1411 N; 1412 {"#Port<" ++ N, _Rest} -> 1413 N; 1414 {_, []} -> 1415 not_found 1416 end, 1417 maybe_other_node2(Channel). 1418 1419maybe_other_node2(not_found) -> not_found; 1420maybe_other_node2(Channel) -> 1421 Ms = ets:fun2ms( 1422 fun({{Tag,Start},Ch}) when Tag=:=?visible_node, Ch=:=Channel -> 1423 {"Visible Node",Start}; 1424 ({{Tag,Start},Ch}) when Tag=:=?hidden_node, Ch=:=Channel -> 1425 {"Hidden Node",Start}; 1426 ({{Tag,Start},Ch}) when Tag=:=?not_connected, Ch=:=Channel -> 1427 {"Not Connected Node",Start} 1428 end), 1429 1430 case ets:select(cdv_dump_index_table,Ms) of 1431 [] -> 1432 not_found; 1433 [_] -> 1434 {other_node,Channel} 1435 end. 1436 1437 1438expand_memory(Fd,Pid,DumpVsn) -> 1439 DecodeOpts = get_decode_opts(DumpVsn), 1440 put(fd,Fd), 1441 Dict0 = get_literals(Fd,DecodeOpts), 1442 Dict = read_heap(Fd,Pid,DecodeOpts,Dict0), 1443 Expanded = {read_stack_dump(Fd,Pid,DecodeOpts,Dict), 1444 read_messages(Fd,Pid,DecodeOpts,Dict), 1445 read_dictionary(Fd,Pid,DecodeOpts,Dict)}, 1446 erase(fd), 1447 IncompleteWarning = 1448 case erase(incomplete_heap) of 1449 undefined -> 1450 []; 1451 true -> 1452 ["WARNING: This process has an incomplete heap. " 1453 "Some information might be missing."] 1454 end, 1455 {Expanded,IncompleteWarning}. 1456 1457get_literals(Fd,DecodeOpts) -> 1458 case get(?literals) of 1459 undefined -> 1460 OldFd = put(fd,Fd), 1461 Literals = read_literals(Fd,DecodeOpts), 1462 put(fd,OldFd), 1463 put(?literals,Literals), 1464 Literals; 1465 Literals -> 1466 Literals 1467 end. 1468 1469read_literals(Fd,DecodeOpts) -> 1470 case lookup_index(?literals,[]) of 1471 [{_,Start}] -> 1472 [{_,Chars}] = ets:lookup(cdv_heap_file_chars,literals), 1473 init_progress("Reading literals",Chars), 1474 pos_bof(Fd,Start), 1475 read_heap(DecodeOpts,gb_trees:empty()); 1476 [] -> 1477 gb_trees:empty() 1478 end. 1479 1480get_decode_opts(DumpVsn) -> 1481 BinAddrAdj = if 1482 DumpVsn < [0,3] -> 1483 %% This is a workaround for a bug in dump 1484 %% versions prior to 0.3: Addresses were 1485 %% truncated to 32 bits. This could cause 1486 %% binaries to get the same address as heap 1487 %% terms in the dump. To work around it we 1488 %% always store binaries on very high 1489 %% addresses in the gb_tree. 1490 16#f bsl 64; 1491 true -> 1492 0 1493 end, 1494 Base64 = DumpVsn >= [0,5], 1495 #dec_opts{bin_addr_adj=BinAddrAdj,base64=Base64}. 1496 1497%%% 1498%%% Read top level section. 1499%%% 1500 1501read_stack_dump(Fd,Pid,DecodeOpts,Dict) -> 1502 case lookup_index(?proc_stack,Pid) of 1503 [{_,Start}] -> 1504 pos_bof(Fd,Start), 1505 read_stack_dump1(Fd,DecodeOpts,Dict,[]); 1506 [] -> 1507 [] 1508 end. 1509read_stack_dump1(Fd,DecodeOpts,Dict,Acc) -> 1510 %% This function is never called if the dump is truncated in {?proc_heap,Pid} 1511 case bytes(Fd) of 1512 "=" ++ _next_tag -> 1513 lists:reverse(Acc); 1514 Line -> 1515 Stack = parse_top(Line,DecodeOpts,Dict), 1516 read_stack_dump1(Fd,DecodeOpts,Dict,[Stack|Acc]) 1517 end. 1518 1519parse_top(Line0, DecodeOpts, D) -> 1520 {Label,Line1} = get_label(Line0), 1521 {Term,Line,D} = parse_term(Line1, DecodeOpts, D), 1522 [] = skip_blanks(Line), 1523 {Label,Term}. 1524 1525%%% 1526%%% Read message queue. 1527%%% 1528 1529read_messages(Fd,Pid,DecodeOpts,Dict) -> 1530 case lookup_index(?proc_messages,Pid) of 1531 [{_,Start}] -> 1532 pos_bof(Fd,Start), 1533 read_messages1(Fd,DecodeOpts,Dict,[]); 1534 [] -> 1535 [] 1536 end. 1537read_messages1(Fd,DecodeOpts,Dict,Acc) -> 1538 %% This function is never called if the dump is truncated in {?proc_heap,Pid} 1539 case bytes(Fd) of 1540 "=" ++ _next_tag -> 1541 lists:reverse(Acc); 1542 Line -> 1543 Msg = parse_message(Line,DecodeOpts,Dict), 1544 read_messages1(Fd,DecodeOpts,Dict,[Msg|Acc]) 1545 end. 1546 1547parse_message(Line0, DecodeOpts, D) -> 1548 {Msg,":"++Line1,_} = parse_term(Line0, DecodeOpts, D), 1549 {Token,Line,_} = parse_term(Line1, DecodeOpts, D), 1550 [] = skip_blanks(Line), 1551 {Msg,Token}. 1552 1553%%% 1554%%% Read process dictionary 1555%%% 1556 1557read_dictionary(Fd,Pid,DecodeOpts,Dict) -> 1558 case lookup_index(?proc_dictionary,Pid) of 1559 [{_,Start}] -> 1560 pos_bof(Fd,Start), 1561 read_dictionary1(Fd,DecodeOpts,Dict,[]); 1562 [] -> 1563 [] 1564 end. 1565read_dictionary1(Fd,DecodeOpts,Dict,Acc) -> 1566 %% This function is never called if the dump is truncated in {?proc_heap,Pid} 1567 case bytes(Fd) of 1568 "=" ++ _next_tag -> 1569 lists:reverse(Acc); 1570 Line -> 1571 Msg = parse_dictionary(Line,DecodeOpts,Dict), 1572 read_dictionary1(Fd,DecodeOpts,Dict,[Msg|Acc]) 1573 end. 1574 1575parse_dictionary(Line0, DecodeOpts, D) -> 1576 {Entry,Line,_} = parse_term(Line0, DecodeOpts, D), 1577 [] = skip_blanks(Line), 1578 Entry. 1579 1580%%% 1581%%% Read heap data. 1582%%% 1583 1584read_heap(Fd,Pid,DecodeOpts,Dict0) -> 1585 case lookup_index(?proc_heap,Pid) of 1586 [{_,Pos}] -> 1587 [{_,Chars}] = ets:lookup(cdv_heap_file_chars,Pid), 1588 init_progress("Reading process heap",Chars), 1589 pos_bof(Fd,Pos), 1590 read_heap(DecodeOpts,Dict0); 1591 [] -> 1592 Dict0 1593 end. 1594 1595read_heap(DecodeOpts, Dict0) -> 1596 %% This function is never called if the dump is truncated in 1597 %% {?proc_heap,Pid}. 1598 %% 1599 %% It is not always possible to reconstruct the heap terms 1600 %% in a single pass, especially if maps are involved. 1601 %% See crashdump_helper:literal_map/0 for an example. 1602 %% 1603 %% Therefore, we need two passes. In the first pass 1604 %% we collect all lines without parsing them, and in the 1605 %% second pass we parse them. 1606 %% 1607 %% The first pass follows. 1608 1609 Lines0 = read_heap_lines(), 1610 1611 %% Save a map of all unprocessed lines so that deref_ptr() can 1612 %% access any line when there are references to terms not yet 1613 %% built. 1614 1615 LineMap = maps:from_list(Lines0), 1616 put(line_map, LineMap), 1617 1618 %% Refc binaries (tag "Yc") must be processed before any sub 1619 %% binaries (tag "Ys") referencing them, so we make sure to 1620 %% process all the refc binaries first. 1621 %% 1622 %% The other lines can be processed in any order, but processing 1623 %% them in the reverse order compared to how they are printed in 1624 %% the crash dump seems to minimize the number of references to 1625 %% terms that have not yet been built. That happens to be the 1626 %% order of the line list as returned by read_heap_lines/0. 1627 1628 RefcBins = [Refc || {_,<<"Yc",_/binary>>}=Refc <- Lines0], 1629 Lines = RefcBins ++ Lines0, 1630 1631 %% Second pass. 1632 1633 init_progress("Processing terms", map_size(LineMap)), 1634 Dict = parse_heap_terms(Lines, DecodeOpts, Dict0), 1635 erase(line_map), 1636 end_progress(), 1637 Dict. 1638 1639read_heap_lines() -> 1640 read_heap_lines_1(get(fd), []). 1641 1642read_heap_lines_1(Fd, Acc) -> 1643 case bytes(Fd) of 1644 "=" ++ _next_tag -> 1645 end_progress(), 1646 put(fd, end_of_heap), 1647 Acc; 1648 Line0 -> 1649 update_progress(length(Line0)+1), 1650 {Addr,":"++Line1} = get_hex(Line0), 1651 1652 %% Reduce the memory consumption by converting the 1653 %% line to a binary. Measurements show that it may also 1654 %% be benefical for performance, too, because it makes the 1655 %% garbage collections cheaper. 1656 1657 Line = list_to_binary(Line1), 1658 read_heap_lines_1(Fd, [{Addr,Line}|Acc]) 1659 end. 1660 1661parse_heap_terms([{Addr,Line0}|T], DecodeOpts, Dict0) -> 1662 case gb_trees:is_defined(Addr, Dict0) of 1663 true -> 1664 %% Already parsed (by a recursive call from do_deref_ptr() 1665 %% to parse_line()). Nothing to do. 1666 parse_heap_terms(T, DecodeOpts, Dict0); 1667 false -> 1668 %% Parse this previously unparsed term. 1669 Dict = parse_line(Addr, Line0, DecodeOpts, Dict0), 1670 parse_heap_terms(T, DecodeOpts, Dict) 1671 end; 1672parse_heap_terms([], _DecodeOpts, Dict) -> 1673 Dict. 1674 1675parse_line(Addr, Line0, DecodeOpts, Dict0) -> 1676 update_progress(1), 1677 Line1 = binary_to_list(Line0), 1678 {_Term,Line,Dict} = parse_heap_term(Line1, Addr, DecodeOpts, Dict0), 1679 [] = skip_blanks(Line), %Assertion. 1680 Dict. 1681 1682%%----------------------------------------------------------------- 1683%% Page with one port 1684get_port(File,Port) -> 1685 case lookup_index(?port,Port) of 1686 [{_,Start}] -> 1687 Fd = open(File), 1688 pos_bof(Fd,Start), 1689 R = get_portinfo(Fd,#port{id=Port}), 1690 close(Fd), 1691 {ok,R}; 1692 [] -> 1693 maybe_other_node(Port) 1694 end. 1695 1696%%----------------------------------------------------------------- 1697%% Page with all ports 1698get_ports(File) -> 1699 ParseFun = fun(Fd,Id) -> get_portinfo(Fd,#port{id=port_to_tuple(Id)}) end, 1700 lookup_and_parse_index(File,?port,ParseFun,"ports"). 1701 1702%% Converting port string to tuple to secure correct sorting. This is 1703%% converted back in cdv_port_cb:format/1. 1704port_to_tuple("#Port<"++Port) -> 1705 [I1,I2] = string:lexemes(Port,".>"), 1706 {list_to_integer(I1),list_to_integer(I2)}. 1707 1708get_portinfo(Fd,Port) -> 1709 case line_head(Fd) of 1710 "State" -> 1711 get_portinfo(Fd,Port#port{state=bytes(Fd)}); 1712 "Task Flags" -> 1713 get_portinfo(Fd,Port#port{task_flags=bytes(Fd)}); 1714 "Slot" -> 1715 %% stored as integer so we can sort on it 1716 get_portinfo(Fd,Port#port{slot=list_to_integer(bytes(Fd))}); 1717 "Connected" -> 1718 %% stored as pid so we can sort on it 1719 Connected0 = bytes(Fd), 1720 Connected = 1721 try list_to_pid(Connected0) 1722 catch error:badarg -> Connected0 1723 end, 1724 get_portinfo(Fd,Port#port{connected=Connected}); 1725 "Links" -> 1726 Pids = split_pid_list_no_space(bytes(Fd)), 1727 Links = [{Pid,Pid} || Pid <- Pids], 1728 get_portinfo(Fd,Port#port{links=Links}); 1729 "Registered as" -> 1730 get_portinfo(Fd,Port#port{name=string(Fd)}); 1731 "Monitors" -> 1732 Monitors0 = string:lexemes(bytes(Fd),"()"), 1733 Monitors = [begin 1734 [Pid,Ref] = string:lexemes(Mon,","), 1735 {Pid,Pid++" ("++Ref++")"} 1736 end || Mon <- Monitors0], 1737 get_portinfo(Fd,Port#port{monitors=Monitors}); 1738 "Suspended" -> 1739 Pids = split_pid_list_no_space(bytes(Fd)), 1740 Suspended = [{Pid,Pid} || Pid <- Pids], 1741 get_portinfo(Fd,Port#port{suspended=Suspended}); 1742 "Port controls linked-in driver" -> 1743 Str = lists:flatten(["Linked in driver: " | string(Fd)]), 1744 get_portinfo(Fd,Port#port{controls=Str}); 1745 "Port controls forker process" -> 1746 Str = lists:flatten(["Forker process: " | string(Fd)]), 1747 get_portinfo(Fd,Port#port{controls=Str}); 1748 "Port controls external process" -> 1749 Str = lists:flatten(["External proc: " | string(Fd)]), 1750 get_portinfo(Fd,Port#port{controls=Str}); 1751 "Port is a file" -> 1752 Str = lists:flatten(["File: "| string(Fd)]), 1753 get_portinfo(Fd,Port#port{controls=Str}); 1754 "Port is UNIX fd not opened by emulator" -> 1755 Str = lists:flatten(["UNIX fd not opened by emulator: "| string(Fd)]), 1756 get_portinfo(Fd,Port#port{controls=Str}); 1757 "Input" -> 1758 get_portinfo(Fd,Port#port{input=list_to_integer(bytes(Fd))}); 1759 "Output" -> 1760 get_portinfo(Fd,Port#port{output=list_to_integer(bytes(Fd))}); 1761 "Queue" -> 1762 get_portinfo(Fd,Port#port{queue=list_to_integer(bytes(Fd))}); 1763 "Port Data" -> 1764 get_portinfo(Fd,Port#port{port_data=string(Fd)}); 1765 1766 "=" ++ _next_tag -> 1767 Port; 1768 Other -> 1769 unexpected(Fd,Other,"port info"), 1770 Port 1771 end. 1772 1773split_pid_list_no_space(String) -> 1774 split_pid_list_no_space(String,[],[]). 1775split_pid_list_no_space([$>|Rest],Acc,Pids) -> 1776 split_pid_list_no_space(Rest,[],[lists:reverse(Acc,[$>])|Pids]); 1777split_pid_list_no_space([H|T],Acc,Pids) -> 1778 split_pid_list_no_space(T,[H|Acc],Pids); 1779split_pid_list_no_space([],[],Pids) -> 1780 lists:reverse(Pids). 1781 1782%%----------------------------------------------------------------- 1783%% Page with external ets tables 1784get_ets_tables(File,Pid,WS) -> 1785 ParseFun = fun(Fd,Id) -> 1786 ET = get_etsinfo(Fd,#ets_table{pid=list_to_pid(Id)},WS), 1787 ET#ets_table{is_named=tab_is_named(ET)} 1788 end, 1789 lookup_and_parse_index(File,{?ets,Pid},ParseFun,"ets"). 1790 1791tab_is_named(#ets_table{id=Name,name=Name}) -> "yes"; 1792tab_is_named(#ets_table{}) -> "no". 1793 1794get_etsinfo(Fd,EtsTable = #ets_table{details=Ds},WS) -> 1795 case line_head(Fd) of 1796 "Slot" -> 1797 get_etsinfo(Fd,EtsTable#ets_table{slot=list_to_integer(bytes(Fd))},WS); 1798 "Table" -> 1799 get_etsinfo(Fd,EtsTable#ets_table{id=string(Fd)},WS); 1800 "Name" -> 1801 get_etsinfo(Fd,EtsTable#ets_table{name=string(Fd)},WS); 1802 "Ordered set (AVL tree), Elements" -> 1803 skip_rest_of_line(Fd), 1804 get_etsinfo(Fd,EtsTable#ets_table{data_type="tree"},WS); 1805 "Buckets" -> 1806 %% A bug in erl_db_hash.c prints a space after the buckets 1807 %% - need to strip the string to make list_to_integer/1 happy. 1808 Buckets = list_to_integer(string:trim(bytes(Fd),both,"\s")), 1809 get_etsinfo(Fd,EtsTable#ets_table{buckets=Buckets},WS); 1810 "Objects" -> 1811 get_etsinfo(Fd,EtsTable#ets_table{size=list_to_integer(bytes(Fd))},WS); 1812 "Words" -> 1813 Words = list_to_integer(bytes(Fd)), 1814 Bytes = 1815 case Words of 1816 -1 -> -1; % probably truncated 1817 _ -> Words * WS 1818 end, 1819 get_etsinfo(Fd,EtsTable#ets_table{memory={bytes,Bytes}},WS); 1820 "=" ++ _next_tag -> 1821 EtsTable; 1822 "Chain Length Min" -> 1823 Val = bytes(Fd), 1824 get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{chain_min=>Val}},WS); 1825 "Chain Length Avg" -> 1826 Val = try list_to_float(string:trim(bytes(Fd),both,"\s")) 1827 catch _:_ -> "-" 1828 end, 1829 get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{chain_avg=>Val}},WS); 1830 "Chain Length Max" -> 1831 Val = bytes(Fd), 1832 get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{chain_max=>Val}},WS); 1833 "Chain Length Std Dev" -> 1834 Val = bytes(Fd), 1835 get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{chain_stddev=>Val}},WS); 1836 "Chain Length Expected Std Dev" -> 1837 Val = bytes(Fd), 1838 get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{chain_exp_stddev=>Val}},WS); 1839 "Fixed" -> 1840 Val = bytes(Fd), 1841 get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{fixed=>Val}},WS); 1842 "Type" -> 1843 Val = bytes(Fd), 1844 get_etsinfo(Fd,EtsTable#ets_table{data_type=Val},WS); 1845 "Protection" -> 1846 Val = bytes(Fd), 1847 get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{protection=>Val}},WS); 1848 "Compressed" -> 1849 Val = bytes(Fd), 1850 get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{compressed=>Val}},WS); 1851 "Write Concurrency" -> 1852 Val = bytes(Fd), 1853 get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{write_c=>Val}},WS); 1854 "Read Concurrency" -> 1855 Val = bytes(Fd), 1856 get_etsinfo(Fd,EtsTable#ets_table{details=Ds#{read_c=>Val}},WS); 1857 Other -> 1858 unexpected(Fd,Other,"ETS info"), 1859 EtsTable 1860 end. 1861 1862 1863%% Internal ets table page 1864get_internal_ets_tables(File,WS) -> 1865 InternalEts = lookup_index(?internal_ets), 1866 Fd = open(File), 1867 R = lists:map( 1868 fun({Descr,Start}) -> 1869 pos_bof(Fd,Start), 1870 {Descr,get_etsinfo(Fd,#ets_table{},WS)} 1871 end, 1872 InternalEts), 1873 close(Fd), 1874 R. 1875 1876%%----------------------------------------------------------------- 1877%% Page with list of all timers 1878get_timers(File,Pid) -> 1879 ParseFun = fun(Fd,Id) -> get_timerinfo(Fd,Id) end, 1880 T1 = lookup_and_parse_index(File,{?timer,Pid},ParseFun,"timers"), 1881 T2 = case ets:lookup(cdv_reg_proc_table,Pid) of 1882 [{_,Name}] -> 1883 lookup_and_parse_index(File,{?timer,Name},ParseFun,"timers"); 1884 _ -> 1885 [] 1886 end, 1887 T1 ++ T2. 1888 1889get_timerinfo(Fd,Id) -> 1890 case catch list_to_pid(Id) of 1891 Pid when is_pid(Pid) -> 1892 get_timerinfo_1(Fd,#timer{pid=Pid}); 1893 _ -> 1894 case ets:lookup(cdv_reg_proc_table,Id) of 1895 [{_,Pid}] when is_pid(Pid) -> 1896 get_timerinfo_1(Fd,#timer{pid=Pid,name=Id}); 1897 [] -> 1898 get_timerinfo_1(Fd,#timer{name=Id}) 1899 end 1900 end. 1901 1902get_timerinfo_1(Fd,Timer) -> 1903 case line_head(Fd) of 1904 "Message" -> 1905 get_timerinfo_1(Fd,Timer#timer{msg=string(Fd)}); 1906 "Time left" -> 1907 TimeLeft = list_to_integer(bytes(Fd) -- " ms"), 1908 get_timerinfo_1(Fd,Timer#timer{time=TimeLeft}); 1909 "=" ++ _next_tag -> 1910 Timer; 1911 Other -> 1912 unexpected(Fd,Other,"timer info"), 1913 Timer 1914 end. 1915 1916%%----------------------------------------------------------------- 1917%% Page with information about a node in the distribution 1918get_node(File,Channel) -> 1919 Ms = ets:fun2ms( 1920 fun({{Tag,Start},Ch}) when Tag=:=?visible_node, Ch=:=Channel -> 1921 {visible,Start}; 1922 ({{Tag,Start},Ch}) when Tag=:=?hidden_node, Ch=:=Channel -> 1923 {hidden,Start}; 1924 ({{Tag,Start},Ch}) when Tag=:=?not_connected, Ch=:=Channel -> 1925 {not_connected,Start} 1926 end), 1927 1928 case ets:select(cdv_dump_index_table,Ms) of 1929 [] -> 1930 {error,not_found}; 1931 [{Type,Pos}] -> 1932 Fd = open(File), 1933 NodeInfo = get_nodeinfo(Fd,Channel,Type,Pos), 1934 close(Fd), 1935 {ok,NodeInfo} 1936 end. 1937 1938%%----------------------------------------------------------------- 1939%% Page with information about the erlang distribution 1940nods(File) -> 1941 case lookup_index(?no_distribution) of 1942 [] -> 1943 V = lookup_index(?visible_node), 1944 H = lookup_index(?hidden_node), 1945 N = lookup_index(?not_connected), 1946 Fd = open(File), 1947 Visible = lists:map( 1948 fun({Channel,Start}) -> 1949 get_nodeinfo(Fd,Channel,visible,Start) 1950 end, 1951 V), 1952 Hidden = lists:map( 1953 fun({Channel,Start}) -> 1954 get_nodeinfo(Fd,Channel,hidden,Start) 1955 end, 1956 H), 1957 NotConnected = lists:map( 1958 fun({Channel,Start}) -> 1959 get_nodeinfo(Fd,Channel,not_connected,Start) 1960 end, 1961 N), 1962 close(Fd), 1963 Visible++Hidden++NotConnected; 1964 [_] -> 1965 %% no_distribution 1966 [] 1967 end. 1968 1969get_nodeinfo(Fd,Channel,Type,Start) -> 1970 pos_bof(Fd,Start), 1971 get_nodeinfo(Fd,#nod{channel=list_to_integer(Channel),conn_type=Type}). 1972 1973get_nodeinfo(Fd,Nod) -> 1974 case line_head(Fd) of 1975 "Name" -> 1976 get_nodeinfo(Fd,Nod#nod{name=bytes(Fd)}); 1977 "Controller" -> 1978 get_nodeinfo(Fd,Nod#nod{controller=bytes(Fd)}); 1979 "Creation" -> 1980 %% Throwing away elements like "(refc=1)", which might be 1981 %% printed from a debug compiled emulator. 1982 Creations = lists:flatmap(fun(C) -> try [list_to_integer(C)] 1983 catch error:badarg -> [] 1984 end 1985 end, string:lexemes(bytes(Fd)," ")), 1986 get_nodeinfo(Fd,Nod#nod{creation={creations,Creations}}); 1987 "Remote link" -> 1988 Procs = bytes(Fd), % e.g. "<0.31.0> <4322.54.0>" 1989 {Local,Remote} = split(Procs), 1990 Str = Local++" <-> "++Remote, 1991 NewRemLinks = [{Local,Str} | Nod#nod.remote_links], 1992 get_nodeinfo(Fd,Nod#nod{remote_links=NewRemLinks}); 1993 "Remote monitoring" -> 1994 Procs = bytes(Fd), % e.g. "<0.31.0> <4322.54.0>" 1995 {Local,Remote} = split(Procs), 1996 Str = Local++" -> "++Remote, 1997 NewRemMon = [{Local,Str} | Nod#nod.remote_mon], 1998 get_nodeinfo(Fd,Nod#nod{remote_mon=NewRemMon}); 1999 "Remotely monitored by" -> 2000 Procs = bytes(Fd), % e.g. "<0.31.0> <4322.54.0>" 2001 {Local,Remote} = split(Procs), 2002 Str = Local++" <- "++Remote, 2003 NewRemMonBy = [{Local,Str} | Nod#nod.remote_mon_by], 2004 get_nodeinfo(Fd,Nod#nod{remote_mon_by=NewRemMonBy}); 2005 "Error" -> 2006 get_nodeinfo(Fd,Nod#nod{error="ERROR: "++string(Fd)}); 2007 "=" ++ _next_tag -> 2008 Nod; 2009 Other -> 2010 unexpected(Fd,Other,"node info"), 2011 Nod 2012 end. 2013 2014%%----------------------------------------------------------------- 2015%% Page with details about one loaded modules 2016get_loaded_mod_details(File,Mod,DecodeOpts) -> 2017 [{_,Start}] = lookup_index(?mod,Mod), 2018 Fd = open(File), 2019 pos_bof(Fd,Start), 2020 InitLM = #loaded_mod{mod=Mod,old_size="No old code exists"}, 2021 Fun = fun(F, LM, LineHead) -> 2022 all_modinfo(F, LM, LineHead, DecodeOpts) 2023 end, 2024 ModInfo = get_loaded_mod_info(Fd,InitLM,Fun), 2025 close(Fd), 2026 ModInfo. 2027 2028%%----------------------------------------------------------------- 2029%% Page with list of all loaded modules 2030loaded_mods(File) -> 2031 ParseFun = 2032 fun(Fd,Id) -> 2033 get_loaded_mod_info(Fd, 2034 #loaded_mod{mod=get_atom(list_to_binary(Id))}, 2035 fun main_modinfo/3) 2036 end, 2037 {CC,OC} = 2038 case lookup_index(?loaded_modules) of 2039 [{_,StartTotal}] -> 2040 Fd = open(File), 2041 pos_bof(Fd,StartTotal), 2042 R = get_loaded_mod_totals(Fd,{"unknown","unknown"}), 2043 close(Fd), 2044 R; 2045 [] -> 2046 {"unknown","unknown"} 2047 end, 2048 {CC,OC,lookup_and_parse_index(File,?mod,ParseFun,"modules")}. 2049 2050get_loaded_mod_totals(Fd,{CC,OC}) -> 2051 case line_head(Fd) of 2052 "Current code" -> 2053 get_loaded_mod_totals(Fd,{bytes(Fd),OC}); 2054 "Old code" -> 2055 get_loaded_mod_totals(Fd,{CC,bytes(Fd)}); 2056 "=" ++ _next_tag -> 2057 {CC,OC}; 2058 Other -> 2059 unexpected(Fd,Other,"loaded modules info"), 2060 {CC,OC} % truncated file 2061 end. 2062 2063get_loaded_mod_info(Fd,LM,Fun) -> 2064 case line_head(Fd) of 2065 "Current size" -> 2066 CS = list_to_integer(bytes(Fd)), 2067 get_loaded_mod_info(Fd,LM#loaded_mod{current_size=CS},Fun); 2068 "Old size" -> 2069 OS = list_to_integer(bytes(Fd)), 2070 get_loaded_mod_info(Fd,LM#loaded_mod{old_size=OS},Fun); 2071 "=" ++ _next_tag -> 2072 LM; 2073 {eof,_} -> 2074 LM; % truncated file 2075 Other -> 2076 LM1 = Fun(Fd,LM,Other), 2077 get_loaded_mod_info(Fd,LM1,Fun) 2078 end. 2079 2080main_modinfo(_Fd,LM,_LineHead) -> 2081 LM. 2082all_modinfo(Fd,LM,LineHead,DecodeOpts) -> 2083 case LineHead of 2084 "Current attributes" -> 2085 Str = get_attribute(Fd, DecodeOpts), 2086 LM#loaded_mod{current_attrib=Str}; 2087 "Current compilation info" -> 2088 Str = get_attribute(Fd, DecodeOpts), 2089 LM#loaded_mod{current_comp_info=Str}; 2090 "Old attributes" -> 2091 Str = get_attribute(Fd, DecodeOpts), 2092 LM#loaded_mod{old_attrib=Str}; 2093 "Old compilation info" -> 2094 Str = get_attribute(Fd, DecodeOpts), 2095 LM#loaded_mod{old_comp_info=Str}; 2096 Other -> 2097 unexpected(Fd,Other,"loaded modules info"), 2098 LM 2099 end. 2100 2101get_attribute(Fd, DecodeOpts) -> 2102 Term = do_get_attribute(Fd, DecodeOpts), 2103 io_lib:format("~tp~n",[Term]). 2104 2105do_get_attribute(Fd, DecodeOpts) -> 2106 Bytes = bytes(Fd, ""), 2107 try get_binary(Bytes, DecodeOpts) of 2108 {Bin,_} -> 2109 try binary_to_term(Bin) of 2110 Term -> 2111 Term 2112 catch 2113 _:_ -> 2114 {"WARNING: The term is probably truncated!", 2115 "I cannot do binary_to_term/1.", 2116 Bin} 2117 end 2118 catch 2119 _:_ -> 2120 {"WARNING: The term is probably truncated!", 2121 "I cannot convert to binary.", 2122 Bytes} 2123 end. 2124 2125%%----------------------------------------------------------------- 2126%% Page with list of all funs 2127funs(File) -> 2128 ParseFun = fun(Fd,_Id) -> get_funinfo(Fd,#fu{}) end, 2129 lookup_and_parse_index(File,?fu,ParseFun,"funs"). 2130 2131get_funinfo(Fd,Fu) -> 2132 case line_head(Fd) of 2133 "Module" -> 2134 get_funinfo(Fd,Fu#fu{module=bytes(Fd)}); 2135 "Uniq" -> 2136 get_funinfo(Fd,Fu#fu{uniq=list_to_integer(bytes(Fd))}); 2137 "Index" -> 2138 get_funinfo(Fd,Fu#fu{index=list_to_integer(bytes(Fd))}); 2139 "Address" -> 2140 get_funinfo(Fd,Fu#fu{address=bytes(Fd)}); 2141 "Native_address" -> 2142 get_funinfo(Fd,Fu#fu{native_address=bytes(Fd)}); 2143 "Refc" -> 2144 get_funinfo(Fd,Fu#fu{refc=list_to_integer(bytes(Fd))}); 2145 "=" ++ _next_tag -> 2146 Fu; 2147 Other -> 2148 unexpected(Fd,Other,"fun info"), 2149 Fu 2150 end. 2151 2152%%----------------------------------------------------------------- 2153%% Page with list of all atoms 2154atoms(File,NumAtoms) -> 2155 case lookup_index(?atoms) of 2156 [{_Id,Start}] -> 2157 Fd = open(File), 2158 pos_bof(Fd,Start), 2159 get_atoms(Fd,NumAtoms); 2160 _ -> 2161 [] 2162 end. 2163 2164get_atoms(Fd,NumAtoms) -> 2165 case get_chunk(Fd) of 2166 {ok,Bin} -> 2167 init_progress("Processing atoms",NumAtoms), 2168 get_atoms(Fd,Bin,NumAtoms,[]); 2169 eof -> 2170 [] 2171 end. 2172 2173 2174%% Atoms are written one per line in the crash dump, in creation order 2175%% from last to first. 2176get_atoms(Fd,Bin,NumAtoms,Atoms) -> 2177 Bins = binary:split(Bin,<<"\n">>,[global]), 2178 get_atoms1(Fd,Bins,NumAtoms,Atoms). 2179 2180get_atoms1(_Fd,[<<"=",_/binary>>|_],_N,Atoms) -> 2181 end_progress(), 2182 Atoms; 2183get_atoms1(Fd,[LastBin],N,Atoms) -> 2184 case get_chunk(Fd) of 2185 {ok,Bin0} -> 2186 get_atoms(Fd,<<LastBin/binary,Bin0/binary>>,N,Atoms); 2187 eof -> 2188 end_progress(), 2189 [{N,get_atom(LastBin)}|Atoms] 2190 end; 2191get_atoms1(Fd,[Bin|Bins],N,Atoms) -> 2192 update_progress(), 2193 get_atoms1(Fd,Bins,N-1,[{N,get_atom(Bin)}|Atoms]). 2194 2195%% This ensures sorting according to first actual letter in the atom, 2196%% disregarding possible single quote. It is formatted back to correct 2197%% syntax in cdv_atom_cb:format/1 2198get_atom(<<"\'",Atom/binary>>) -> 2199 {Atom,q}; % quoted 2200get_atom(Atom) when is_binary(Atom) -> 2201 {Atom,nq}. % not quoted 2202 2203%%----------------------------------------------------------------- 2204%% Page with list of all persistent terms 2205persistent_terms(File, DecodeOpts) -> 2206 case lookup_index(?persistent_terms) of 2207 [{_Id,Start}] -> 2208 Fd = open(File), 2209 pos_bof(Fd,Start), 2210 Terms = get_persistent_terms(Fd), 2211 Dict = get_literals(Fd,DecodeOpts), 2212 parse_persistent_terms(Terms,DecodeOpts,Dict); 2213 _ -> 2214 [] 2215 end. 2216 2217parse_persistent_terms([[Name0,Val0]|Terms],DecodeOpts,Dict) -> 2218 {Name,_,_} = parse_term(binary_to_list(Name0),DecodeOpts,Dict), 2219 {Val,_,_} = parse_term(binary_to_list(Val0),DecodeOpts,Dict), 2220 [{Name,Val}|parse_persistent_terms(Terms,DecodeOpts,Dict)]; 2221parse_persistent_terms([],_,_) -> []. 2222 2223get_persistent_terms(Fd) -> 2224 case get_chunk(Fd) of 2225 {ok,Bin} -> 2226 get_persistent_terms(Fd,Bin,[]); 2227 eof -> 2228 [] 2229 end. 2230 2231 2232%% Persistent_Terms are written one per line in the crash dump. 2233get_persistent_terms(Fd,Bin,PersistentTerms) -> 2234 Bins = binary:split(Bin,<<"\n">>,[global]), 2235 get_persistent_terms1(Fd,Bins,PersistentTerms). 2236 2237get_persistent_terms1(_Fd,[<<"=",_/binary>>|_],PersistentTerms) -> 2238 PersistentTerms; 2239get_persistent_terms1(Fd,[LastBin],PersistentTerms) -> 2240 case get_chunk(Fd) of 2241 {ok,Bin0} -> 2242 get_persistent_terms(Fd,<<LastBin/binary,Bin0/binary>>,PersistentTerms); 2243 eof -> 2244 [get_persistent_term(LastBin)|PersistentTerms] 2245 end; 2246get_persistent_terms1(Fd,[Bin|Bins],Persistent_Terms) -> 2247 get_persistent_terms1(Fd,Bins,[get_persistent_term(Bin)|Persistent_Terms]). 2248 2249get_persistent_term(Bin) -> 2250 binary:split(Bin,<<"|">>). 2251 2252 2253%%----------------------------------------------------------------- 2254%% Page with memory information 2255memory(File) -> 2256 case lookup_index(?memory) of 2257 [{_,Start}] -> 2258 Fd = open(File), 2259 pos_bof(Fd,Start), 2260 R = get_meminfo(Fd,[]), 2261 close(Fd), 2262 R; 2263 _ -> 2264 [] 2265 end. 2266 2267get_meminfo(Fd,Acc) -> 2268 case line_head(Fd) of 2269 "=" ++ _next_tag -> 2270 lists:reverse(Acc); 2271 {eof,_last_line} -> 2272 lists:reverse(Acc); 2273 Key -> 2274 get_meminfo(Fd,[{list_to_atom(Key),bytes(Fd)}|Acc]) 2275 end. 2276 2277%%----------------------------------------------------------------- 2278%% Page with information about allocated areas 2279allocated_areas(File) -> 2280 case lookup_index(?allocated_areas) of 2281 [{_,Start}] -> 2282 Fd = open(File), 2283 pos_bof(Fd,Start), 2284 R = get_allocareainfo(Fd,[]), 2285 close(Fd), 2286 R; 2287 _ -> 2288 [] 2289 end. 2290 2291get_allocareainfo(Fd,Acc) -> 2292 case line_head(Fd) of 2293 "=" ++ _next_tag -> 2294 lists:reverse(Acc); 2295 {eof,_last_line} -> 2296 lists:reverse(Acc); 2297 Key -> 2298 Val = bytes(Fd), 2299 AllocInfo = 2300 case split(Val) of 2301 {Alloc,[]} -> 2302 {Key,Alloc,""}; 2303 {Alloc,Used} -> 2304 {Key,Alloc,Used} 2305 end, 2306 get_allocareainfo(Fd,[AllocInfo|Acc]) 2307 end. 2308 2309%%----------------------------------------------------------------- 2310%% Page with information about allocators 2311allocator_info(File) -> 2312 case lookup_index(?allocator) of 2313 [] -> 2314 []; 2315 AllAllocators -> 2316 Fd = open(File), 2317 R = lists:map(fun({Heading,Start}) -> 2318 {Heading,get_allocatorinfo(Fd,Start)} 2319 end, 2320 AllAllocators), 2321 close(Fd), 2322 [allocator_summary(R) | R] 2323 end. 2324 2325get_allocatorinfo(Fd,Start) -> 2326 pos_bof(Fd,Start), 2327 get_allocatorinfo1(Fd,[],0). 2328 2329get_allocatorinfo1(Fd,Acc,Max) -> 2330 case line_head(Fd) of 2331 "=" ++ _next_tag -> 2332 pad_and_reverse(Acc,Max,[]); 2333 {eof,_last_line} -> 2334 pad_and_reverse(Acc,Max,[]); 2335 Key -> 2336 Values = get_all_vals(bytes(Fd),[]), 2337 L = length(Values), 2338 Max1 = if L > Max -> L; true -> Max end, 2339 get_allocatorinfo1(Fd,[{Key,Values}|Acc],Max1) 2340 end. 2341 2342get_all_vals([$ |Rest],Acc) -> 2343 [lists:reverse(Acc)|get_all_vals(Rest,[])]; 2344get_all_vals([],Acc) -> 2345 [lists:reverse(Acc)]; 2346get_all_vals([Char|Rest],Acc) -> 2347 get_all_vals(Rest,[Char|Acc]). 2348 2349%% Make sure all V have the same length by padding with "". 2350pad_and_reverse([{K,V}|T],Len,Rev) -> 2351 VLen = length(V), 2352 V1 = if VLen == Len -> V; 2353 true -> V ++ lists:duplicate(Len-VLen,"") 2354 end, 2355 pad_and_reverse(T,Len,[{K,V1}|Rev]); 2356pad_and_reverse([],_,Rev) -> 2357 Rev. 2358 2359%% Calculate allocator summary: 2360%% 2361%% System totals: 2362%% blocks size = sum of mbcs, mbcs_pool and sbcs blocks size over 2363%% all allocator instances of all types 2364%% carriers size = sum of mbcs, mbcs_pool and sbcs carriers size over 2365%% all allocator instances of all types 2366%% 2367%% I any allocator except sbmbc_alloc has "option e: false" then don't 2368%% present system totals. 2369%% 2370%% For each allocator type: 2371%% blocks size = sum of sbmbcs, mbcs, mbcs_pool and sbcs blocks 2372%% size over all allocator instances of this type 2373%% carriers size = sum of sbmbcs, mbcs, mbcs_pool and sbcs carriers 2374%% size over all allocator instances of this type 2375%% mseg carriers size = sum of mbcs and sbcs mseg carriers size over all 2376%% allocator instances of this type 2377%% 2378 2379-define(sbmbcs_blocks_size,"sbmbcs blocks size"). 2380-define(mbcs_blocks_size,"mbcs blocks size"). 2381-define(sbcs_blocks_size,"sbcs blocks size"). 2382-define(sbmbcs_carriers_size,"sbmbcs carriers size"). 2383-define(mbcs_carriers_size,"mbcs carriers size"). 2384-define(sbcs_carriers_size,"sbcs carriers size"). 2385-define(mbcs_mseg_carriers_size,"mbcs mseg carriers size"). 2386-define(sbcs_mseg_carriers_size,"sbcs mseg carriers size"). 2387-define(segments_size,"segments_size"). 2388-define(mbcs_pool_blocks_size,"mbcs_pool blocks size"). 2389-define(mbcs_pool_carriers_size,"mbcs_pool carriers size"). 2390 2391-define(type_blocks_size,[?sbmbcs_blocks_size, 2392 ?mbcs_blocks_size, 2393 ?mbcs_pool_blocks_size, 2394 ?sbcs_blocks_size]). 2395-define(type_carriers_size,[?sbmbcs_carriers_size, 2396 ?mbcs_carriers_size, 2397 ?mbcs_pool_carriers_size, 2398 ?sbcs_carriers_size]). 2399-define(type_mseg_carriers_size,[?mbcs_mseg_carriers_size, 2400 ?sbcs_mseg_carriers_size]). 2401-define(total_blocks_size,[?mbcs_blocks_size, 2402 ?mbcs_pool_blocks_size, 2403 ?sbcs_blocks_size]). 2404-define(total_carriers_size,[?mbcs_carriers_size, 2405 ?mbcs_pool_carriers_size, 2406 ?sbcs_carriers_size]). 2407-define(total_mseg_carriers_size,[?mbcs_mseg_carriers_size, 2408 ?sbcs_mseg_carriers_size]). 2409-define(interesting_allocator_info, [?sbmbcs_blocks_size, 2410 ?mbcs_blocks_size, 2411 ?mbcs_pool_blocks_size, 2412 ?sbcs_blocks_size, 2413 ?sbmbcs_carriers_size, 2414 ?mbcs_carriers_size, 2415 ?sbcs_carriers_size, 2416 ?mbcs_mseg_carriers_size, 2417 ?mbcs_pool_carriers_size, 2418 ?sbcs_mseg_carriers_size, 2419 ?segments_size]). 2420-define(mseg_alloc,"mseg_alloc"). 2421-define(seg_size,"segments_size"). 2422-define(sbmbc_alloc,"sbmbc_alloc"). 2423-define(opt_e_false,{"option e","false"}). 2424 2425allocator_summary(Allocators) -> 2426 {Sorted,DoTotal} = sort_allocator_types(Allocators,[],true), 2427 {TypeTotals0,Totals} = sum_allocator_data(Sorted,DoTotal), 2428 {TotalMCS,TypeTotals} = 2429 case lists:keytake(?mseg_alloc,1,TypeTotals0) of 2430 {value,{_,[{?seg_size,SegSize}]},Rest} -> 2431 {integer_to_list(SegSize),Rest}; 2432 false -> 2433 {?not_available,TypeTotals0} 2434 end, 2435 {TotalBS,TotalCS} = 2436 case Totals of 2437 false -> 2438 {?not_available,?not_available}; 2439 {TBS,TCS} -> 2440 {integer_to_list(TBS),integer_to_list(TCS)} 2441 end, 2442 {"Allocator Summary", 2443 ["blocks size","carriers size","mseg carriers size"], 2444 [{"total",[TotalBS,TotalCS,TotalMCS]} | 2445 format_allocator_summary(lists:reverse(TypeTotals))]}. 2446 2447format_allocator_summary([{Type,Data}|Rest]) -> 2448 [format_allocator_summary(Type,Data) | format_allocator_summary(Rest)]; 2449format_allocator_summary([]) -> 2450 []. 2451 2452format_allocator_summary(Type,Data) -> 2453 BS = get_size_value(blocks_size,Data), 2454 CS = get_size_value(carriers_size,Data), 2455 MCS = get_size_value(mseg_carriers_size,Data), 2456 {Type,[BS,CS,MCS]}. 2457 2458get_size_value(Key,Data) -> 2459 case proplists:get_value(Key,Data) of 2460 undefined -> 2461 ?not_available; 2462 Int -> 2463 integer_to_list(Int) 2464 end. 2465 2466%% Sort allocator data per type 2467%% Input = [{Instance,[{Key,Data}]}] 2468%% Output = [{Type,[{Key,Value}]}] 2469%% where Key in Output is one of ?interesting_allocator_info 2470%% and Value is the sum over all allocator instances of each type. 2471sort_allocator_types([{Name,Data}|Allocators],Acc,DoTotal) -> 2472 Type = 2473 case string:lexemes(Name,"[]") of 2474 [T,_Id] -> T; 2475 [Name] -> Name; 2476 Other -> Other 2477 end, 2478 TypeData = proplists:get_value(Type,Acc,[]), 2479 {NewTypeData,NewDoTotal} = sort_type_data(Type,Data,TypeData,DoTotal), 2480 NewAcc = lists:keystore(Type,1,Acc,{Type,NewTypeData}), 2481 sort_allocator_types(Allocators,NewAcc,NewDoTotal); 2482sort_allocator_types([],Acc,DoTotal) -> 2483 {Acc,DoTotal}. 2484 2485sort_type_data(Type,[?opt_e_false|Data],Acc,_) when Type=/=?sbmbc_alloc-> 2486 sort_type_data(Type,Data,Acc,false); 2487sort_type_data(Type,[{Key,Val0}|Data],Acc,DoTotal) -> 2488 case lists:member(Key,?interesting_allocator_info) of 2489 true -> 2490 Val = list_to_integer(hd(Val0)), 2491 sort_type_data(Type,Data,update_value(Key,Val,Acc),DoTotal); 2492 false -> 2493 sort_type_data(Type,Data,Acc,DoTotal) 2494 end; 2495sort_type_data(_Type,[],Acc,DoTotal) -> 2496 {Acc,DoTotal}. 2497 2498%% Sum up allocator data in total blocks- and carriers size for all 2499%% allocators and per type of allocator. 2500%% Input = Output from sort_allocator_types/3 2501%% Output = {[{"mseg_alloc",[{"segments_size",Value}]}, 2502%% {Type,[{blocks_size,Value}, 2503%% {carriers_size,Value}, 2504%% {mseg_carriers_size,Value}]}, 2505%% ...], 2506%% {TotalBlocksSize,TotalCarriersSize}} 2507sum_allocator_data(AllocData,false) -> 2508 sum_allocator_data(AllocData,[],false); 2509sum_allocator_data(AllocData,true) -> 2510 sum_allocator_data(AllocData,[],{0,0}). 2511 2512sum_allocator_data([{_Type,[]}|AllocData],TypeAcc,Total) -> 2513 sum_allocator_data(AllocData,TypeAcc,Total); 2514sum_allocator_data([{Type,Data}|AllocData],TypeAcc,Total) -> 2515 {TypeSum,NewTotal} = sum_type_data(Data,[],Total), 2516 sum_allocator_data(AllocData,[{Type,TypeSum}|TypeAcc],NewTotal); 2517sum_allocator_data([],TypeAcc,Total) -> 2518 {TypeAcc,Total}. 2519 2520sum_type_data([{Key,Value}|Data],TypeAcc,Total) -> 2521 NewTotal = 2522 case Total of 2523 false -> 2524 false; 2525 {TotalBS,TotalCS} -> 2526 case lists:member(Key,?total_blocks_size) of 2527 true -> 2528 {TotalBS+Value,TotalCS}; 2529 false -> 2530 case lists:member(Key,?total_carriers_size) of 2531 true -> 2532 {TotalBS,TotalCS+Value}; 2533 false -> 2534 {TotalBS,TotalCS} 2535 end 2536 end 2537 end, 2538 NewTypeAcc = 2539 case lists:member(Key,?type_blocks_size) of 2540 true -> 2541 update_value(blocks_size,Value,TypeAcc); 2542 false -> 2543 case lists:member(Key,?type_carriers_size) of 2544 true -> 2545 update_value(carriers_size,Value,TypeAcc); 2546 false -> 2547 case lists:member(Key,?type_mseg_carriers_size) of 2548 true -> 2549 update_value(mseg_carriers_size,Value,TypeAcc); 2550 false -> 2551 %% "segments_size" for "mseg_alloc" 2552 update_value(Key,Value,TypeAcc) 2553 end 2554 end 2555 end, 2556 sum_type_data(Data,NewTypeAcc,NewTotal); 2557sum_type_data([],TypeAcc,Total) -> 2558 {TypeAcc,Total}. 2559 2560update_value(Key,Value,Acc) -> 2561 case lists:keytake(Key,1,Acc) of 2562 false -> 2563 [{Key,Value}|Acc]; 2564 {value,{Key,Old},Acc1} -> 2565 [{Key,Old+Value}|Acc1] 2566 end. 2567 2568%%----------------------------------------------------------------- 2569%% Page with hash table information 2570hash_tables(File) -> 2571 case lookup_index(?hash_table) of 2572 [] -> 2573 []; 2574 AllHashTables -> 2575 Fd = open(File), 2576 R = lists:map(fun({Name,Start}) -> 2577 get_hashtableinfo(Fd,Name,Start) 2578 end, 2579 AllHashTables), 2580 close(Fd), 2581 R 2582 end. 2583 2584get_hashtableinfo(Fd,Name,Start) -> 2585 pos_bof(Fd,Start), 2586 get_hashtableinfo1(Fd,#hash_table{name=Name}). 2587 2588get_hashtableinfo1(Fd,HashTable) -> 2589 case line_head(Fd) of 2590 "size" -> 2591 get_hashtableinfo1(Fd,HashTable#hash_table{size=bytes(Fd)}); 2592 "used" -> 2593 get_hashtableinfo1(Fd,HashTable#hash_table{used=bytes(Fd)}); 2594 "objs" -> 2595 get_hashtableinfo1(Fd,HashTable#hash_table{objs=bytes(Fd)}); 2596 "depth" -> 2597 get_hashtableinfo1(Fd,HashTable#hash_table{depth=bytes(Fd)}); 2598 "=" ++ _next_tag -> 2599 HashTable; 2600 Other -> 2601 unexpected(Fd,Other,"hash table information"), 2602 HashTable 2603 end. 2604 2605%%----------------------------------------------------------------- 2606%% Page with index table information 2607index_tables(File) -> 2608 case lookup_index(?index_table) of 2609 [] -> 2610 []; 2611 AllIndexTables -> 2612 Fd = open(File), 2613 R = lists:map(fun({Name,Start}) -> 2614 get_indextableinfo(Fd,Name,Start) 2615 end, 2616 AllIndexTables), 2617 close(Fd), 2618 R 2619 end. 2620 2621get_indextableinfo(Fd,Name,Start) -> 2622 pos_bof(Fd,Start), 2623 get_indextableinfo1(Fd,#index_table{name=Name}). 2624 2625get_indextableinfo1(Fd,IndexTable) -> 2626 case line_head(Fd) of 2627 "size" -> 2628 get_indextableinfo1(Fd,IndexTable#index_table{size=bytes(Fd)}); 2629 "used" -> 2630 get_indextableinfo1(Fd,IndexTable#index_table{used=bytes(Fd)}); 2631 "limit" -> 2632 get_indextableinfo1(Fd,IndexTable#index_table{limit=bytes(Fd)}); 2633 "rate" -> 2634 get_indextableinfo1(Fd,IndexTable#index_table{rate=bytes(Fd)}); 2635 "entries" -> 2636 get_indextableinfo1(Fd,IndexTable#index_table{entries=bytes(Fd)}); 2637 "=" ++ _next_tag -> 2638 IndexTable; 2639 Other -> 2640 unexpected(Fd,Other,"index table information"), 2641 IndexTable 2642 end. 2643 2644 2645%%----------------------------------------------------------------- 2646%% Page with scheduler table information 2647schedulers(File) -> 2648 Fd = open(File), 2649 2650 Schds0 = case lookup_index(?scheduler) of 2651 [] -> 2652 []; 2653 Normals -> 2654 [{Normals, #sched{type=normal}}] 2655 end, 2656 Schds1 = case lookup_index(?dirty_cpu_scheduler) of 2657 [] -> 2658 Schds0; 2659 DirtyCpus -> 2660 [{DirtyCpus, get_dirty_runqueue(Fd, ?dirty_cpu_run_queue)} 2661 | Schds0] 2662 end, 2663 Schds2 = case lookup_index(?dirty_io_scheduler) of 2664 [] -> 2665 Schds1; 2666 DirtyIos -> 2667 [{DirtyIos, get_dirty_runqueue(Fd, ?dirty_io_run_queue)} 2668 | Schds1] 2669 end, 2670 2671 R = schedulers1(Fd, Schds2, []), 2672 close(Fd), 2673 R. 2674 2675schedulers1(_Fd, [], Acc) -> 2676 Acc; 2677schedulers1(Fd, [{Scheds,Sched0} | Tail], Acc0) -> 2678 Acc1 = lists:foldl(fun({Name,Start}, AccIn) -> 2679 [get_schedulerinfo(Fd,Name,Start,Sched0) | AccIn] 2680 end, 2681 Acc0, 2682 Scheds), 2683 schedulers1(Fd, Tail, Acc1). 2684 2685get_schedulerinfo(Fd,Name,Start,Sched0) -> 2686 pos_bof(Fd,Start), 2687 get_schedulerinfo1(Fd,Sched0#sched{name=list_to_integer(Name)}). 2688 2689sched_type(?dirty_cpu_run_queue) -> dirty_cpu; 2690sched_type(?dirty_io_run_queue) -> dirty_io. 2691 2692get_schedulerinfo1(Fd, Sched) -> 2693 case get_schedulerinfo2(Fd, Sched) of 2694 {more, Sched2} -> 2695 get_schedulerinfo1(Fd, Sched2); 2696 {done, Sched2} -> 2697 Sched2 2698 end. 2699 2700get_schedulerinfo2(Fd, Sched=#sched{details=Ds}) -> 2701 case line_head(Fd) of 2702 "Current Process" -> 2703 {more, Sched#sched{process=bytes(Fd, "None")}}; 2704 "Current Port" -> 2705 {more, Sched#sched{port=bytes(Fd, "None")}}; 2706 2707 "Scheduler Sleep Info Flags" -> 2708 {more, Sched#sched{details=Ds#{sleep_info=>bytes(Fd, "None")}}}; 2709 "Scheduler Sleep Info Aux Work" -> 2710 {more, Sched#sched{details=Ds#{sleep_aux=>bytes(Fd, "None")}}}; 2711 2712 "Current Process State" -> 2713 {more, Sched#sched{details=Ds#{currp_state=>bytes(Fd)}}}; 2714 "Current Process Internal State" -> 2715 {more, Sched#sched{details=Ds#{currp_int_state=>bytes(Fd)}}}; 2716 "Current Process Program counter" -> 2717 {more, Sched#sched{details=Ds#{currp_prg_cnt=>string(Fd)}}}; 2718 "Current Process CP" -> 2719 {more, Sched#sched{details=Ds#{currp_cp=>string(Fd)}}}; 2720 "Current Process Limited Stack Trace" -> 2721 %% If there shall be last in scheduler information block 2722 {done, Sched#sched{details=get_limited_stack(Fd, 0, Ds)}}; 2723 2724 "=" ++ _next_tag -> 2725 {done, Sched}; 2726 2727 Other -> 2728 case Sched#sched.type of 2729 normal -> 2730 get_runqueue_info2(Fd, Other, Sched); 2731 _ -> 2732 unexpected(Fd,Other,"dirty scheduler information"), 2733 {done, Sched} 2734 end 2735 end. 2736 2737get_dirty_runqueue(Fd, Tag) -> 2738 case lookup_index(Tag) of 2739 [{_, Start}] -> 2740 pos_bof(Fd,Start), 2741 get_runqueue_info1(Fd,#sched{type=sched_type(Tag)}); 2742 [] -> 2743 #sched{} 2744 end. 2745 2746get_runqueue_info1(Fd, Sched) -> 2747 case get_runqueue_info2(Fd, line_head(Fd), Sched) of 2748 {more, Sched2} -> 2749 get_runqueue_info1(Fd, Sched2); 2750 {done, Sched2} -> 2751 Sched2 2752 end. 2753 2754get_runqueue_info2(Fd, LineHead, Sched=#sched{details=Ds}) -> 2755 case LineHead of 2756 "Run Queue Max Length" -> 2757 RQMax = list_to_integer(bytes(Fd)), 2758 RQ = RQMax + Sched#sched.run_q, 2759 {more, Sched#sched{run_q=RQ, details=Ds#{runq_max=>RQMax}}}; 2760 "Run Queue High Length" -> 2761 RQHigh = list_to_integer(bytes(Fd)), 2762 RQ = RQHigh + Sched#sched.run_q, 2763 {more, Sched#sched{run_q=RQ, details=Ds#{runq_high=>RQHigh}}}; 2764 "Run Queue Normal Length" -> 2765 RQNorm = list_to_integer(bytes(Fd)), 2766 RQ = RQNorm + Sched#sched.run_q, 2767 {more, Sched#sched{run_q=RQ, details=Ds#{runq_norm=>RQNorm}}}; 2768 "Run Queue Low Length" -> 2769 RQLow = list_to_integer(bytes(Fd)), 2770 RQ = RQLow + Sched#sched.run_q, 2771 {more, Sched#sched{run_q=RQ, details=Ds#{runq_low=>RQLow}}}; 2772 "Run Queue Port Length" -> 2773 RQ = list_to_integer(bytes(Fd)), 2774 {more, Sched#sched{port_q=RQ}}; 2775 2776 "Run Queue Flags" -> 2777 {more, Sched#sched{details=Ds#{runq_flags=>bytes(Fd, "None")}}}; 2778 2779 "=" ++ _next_tag -> 2780 {done, Sched}; 2781 Other -> 2782 unexpected(Fd,Other,"scheduler information"), 2783 {done, Sched} 2784 end. 2785 2786get_limited_stack(Fd, N, Ds) -> 2787 case string(Fd) of 2788 Addr = "0x" ++ _ -> 2789 get_limited_stack(Fd, N+1, Ds#{{currp_stack, N} => Addr}); 2790 "=" ++ _next_tag -> 2791 Ds; 2792 Line -> 2793 get_limited_stack(Fd, N+1, Ds#{{currp_stack, N} => Line}) 2794 end. 2795 2796%%%----------------------------------------------------------------- 2797%%% Parse memory in crashdump version 0.1 and newer 2798%%% 2799parse_heap_term([$l|Line0], Addr, DecodeOpts, D0) -> %Cons cell. 2800 {H,"|"++Line1,D1} = parse_term(Line0, DecodeOpts, D0), 2801 {T,Line,D2} = parse_term(Line1, DecodeOpts, D1), 2802 Term = [H|T], 2803 D = gb_trees:insert(Addr, Term, D2), 2804 {Term,Line,D}; 2805parse_heap_term([$t|Line0], Addr, DecodeOpts, D) -> %Tuple 2806 {N,":"++Line} = get_hex(Line0), 2807 parse_tuple(N, Line, Addr, DecodeOpts, D, []); 2808parse_heap_term([$F|Line0], Addr, _DecodeOpts, D0) -> %Float 2809 {N,":"++Line1} = get_hex(Line0), 2810 {Chars,Line} = get_chars(N, Line1), 2811 Term = list_to_float(Chars), 2812 D = gb_trees:insert(Addr, Term, D0), 2813 {Term,Line,D}; 2814parse_heap_term("B16#"++Line0, Addr, _DecodeOpts, D0) -> %Positive big number. 2815 {Term,Line} = get_hex(Line0), 2816 D = gb_trees:insert(Addr, Term, D0), 2817 {Term,Line,D}; 2818parse_heap_term("B-16#"++Line0, Addr, _DecodeOpts, D0) -> %Negative big number 2819 {Term0,Line} = get_hex(Line0), 2820 Term = -Term0, 2821 D = gb_trees:insert(Addr, Term, D0), 2822 {Term,Line,D}; 2823parse_heap_term("B"++Line0, Addr, _DecodeOpts, D0) -> %Decimal big num 2824 case string:to_integer(Line0) of 2825 {Int,Line} when is_integer(Int) -> 2826 D = gb_trees:insert(Addr, Int, D0), 2827 {Int,Line,D} 2828 end; 2829parse_heap_term([$P|Line0], Addr, _DecodeOpts, D0) -> % External Pid. 2830 {Pid0,Line} = get_id(Line0), 2831 Pid = ['#CDVPid'|Pid0], 2832 D = gb_trees:insert(Addr, Pid, D0), 2833 {Pid,Line,D}; 2834parse_heap_term([$p|Line0], Addr, _DecodeOpts, D0) -> % External Port. 2835 {Port0,Line} = get_id(Line0), 2836 Port = ['#CDVPort'|Port0], 2837 D = gb_trees:insert(Addr, Port, D0), 2838 {Port,Line,D}; 2839parse_heap_term("E"++Line0, Addr, DecodeOpts, D0) -> %Term encoded in external format. 2840 {Bin,Line} = get_binary(Line0, DecodeOpts), 2841 Term = binary_to_term(Bin), 2842 D = gb_trees:insert(Addr, Term, D0), 2843 {Term,Line,D}; 2844parse_heap_term("Yh"++Line0, Addr, DecodeOpts, D0) -> %Heap binary. 2845 {Term,Line} = get_binary(Line0, DecodeOpts), 2846 D = gb_trees:insert(Addr, Term, D0), 2847 {Term,Line,D}; 2848parse_heap_term("Yc"++Line0, Addr, DecodeOpts, D0) -> %Reference-counted binary. 2849 {Binp0,":"++Line1} = get_hex(Line0), 2850 {Offset,":"++Line2} = get_hex(Line1), 2851 {Sz,Line} = get_hex(Line2), 2852 Binp = Binp0 bor DecodeOpts#dec_opts.bin_addr_adj, 2853 case lookup_binary_index(Binp) of 2854 [{_,Start}] -> 2855 SymbolicBin = {'#CDVBin',Start}, 2856 Term = cdvbin(Offset, Sz, SymbolicBin), 2857 D1 = gb_trees:insert(Addr, Term, D0), 2858 D = gb_trees:enter(Binp, SymbolicBin, D1), 2859 {Term,Line,D}; 2860 [] -> 2861 Term = '#CDVNonexistingBinary', 2862 D1 = gb_trees:insert(Addr, Term, D0), 2863 D = gb_trees:enter(Binp, Term, D1), 2864 {Term,Line,D} 2865 end; 2866parse_heap_term("Ys"++Line0, Addr, DecodeOpts, D0) -> %Sub binary. 2867 {Binp0,":"++Line1} = get_hex(Line0), 2868 {Offset,":"++Line2} = get_hex(Line1), 2869 {Sz,Line3} = get_hex(Line2), 2870 {Term,Line,D1} = deref_bin(Binp0, Offset, Sz, Line3, DecodeOpts, D0), 2871 D = gb_trees:insert(Addr, Term, D1), 2872 {Term,Line,D}; 2873parse_heap_term("Mf"++Line0, Addr, DecodeOpts, D0) -> %Flatmap. 2874 {Size,":"++Line1} = get_hex(Line0), 2875 case parse_term(Line1, DecodeOpts, D0) of 2876 {Keys,":"++Line2,D1} when is_tuple(Keys) -> 2877 {Values,Line,D2} = parse_tuple(Size, Line2, Addr,DecodeOpts, D1, []), 2878 Pairs = zip_tuples(tuple_size(Keys), Keys, Values, []), 2879 Map = maps:from_list(Pairs), 2880 D = gb_trees:update(Addr, Map, D2), 2881 {Map,Line,D}; 2882 {Incomplete,_Line,D1} -> 2883 D = gb_trees:insert(Addr, Incomplete, D1), 2884 {Incomplete,"",D} 2885 end; 2886parse_heap_term("Mh"++Line0, Addr, DecodeOpts, D0) -> %Head node in a hashmap. 2887 {MapSize,":"++Line1} = get_hex(Line0), 2888 {N,":"++Line2} = get_hex(Line1), 2889 {Nodes,Line,D1} = parse_tuple(N, Line2, Addr, DecodeOpts, D0, []), 2890 Map = maps:from_list(flatten_hashmap_nodes(Nodes)), 2891 MapSize = maps:size(Map), %Assertion. 2892 D = gb_trees:update(Addr, Map, D1), 2893 {Map,Line,D}; 2894parse_heap_term("Mn"++Line0, Addr, DecodeOpts, D) -> %Interior node in a hashmap. 2895 {N,":"++Line} = get_hex(Line0), 2896 parse_tuple(N, Line, Addr, DecodeOpts, D, []). 2897 2898parse_tuple(0, Line, Addr, _, D0, Acc) -> 2899 Tuple = list_to_tuple(lists:reverse(Acc)), 2900 D = gb_trees:insert(Addr, Tuple, D0), 2901 {Tuple,Line,D}; 2902parse_tuple(N, Line0, Addr, DecodeOpts, D0, Acc) -> 2903 case parse_term(Line0, DecodeOpts, D0) of 2904 {Term,[$,|Line],D} when N > 1 -> 2905 parse_tuple(N-1, Line, Addr, DecodeOpts, D, [Term|Acc]); 2906 {Term,Line,D}-> 2907 parse_tuple(N-1, Line, Addr, DecodeOpts, D, [Term|Acc]) 2908 end. 2909 2910zip_tuples(0, _T1, _T2, Acc) -> 2911 Acc; 2912zip_tuples(N, T1, T2, Acc) when N =< tuple_size(T1) -> 2913 zip_tuples(N-1, T1, T2, [{element(N, T1),element(N, T2)}|Acc]). 2914 2915flatten_hashmap_nodes(Tuple) -> 2916 flatten_hashmap_nodes_1(tuple_size(Tuple), Tuple, []). 2917 2918flatten_hashmap_nodes_1(0, _Tuple, Acc) -> 2919 Acc; 2920flatten_hashmap_nodes_1(N, Tuple0, Acc0) -> 2921 case element(N, Tuple0) of 2922 [K|V] -> 2923 flatten_hashmap_nodes_1(N-1, Tuple0, [{K,V}|Acc0]); 2924 Tuple when is_tuple(Tuple) -> 2925 Acc = flatten_hashmap_nodes_1(N-1, Tuple0, Acc0), 2926 flatten_hashmap_nodes_1(tuple_size(Tuple), Tuple, Acc) 2927 end. 2928 2929parse_term([$H|Line0], DecodeOpts, D) -> %Pointer to heap term. 2930 {Ptr,Line} = get_hex(Line0), 2931 deref_ptr(Ptr, Line, DecodeOpts, D); 2932parse_term([$N|Line], _, D) -> %[] (nil). 2933 {[],Line,D}; 2934parse_term([$I|Line0], _, D) -> %Small. 2935 {Int,Line} = string:to_integer(Line0), 2936 {Int,Line,D}; 2937parse_term([$A|_]=Line, _, D) -> %Atom. 2938 parse_atom(Line, D); 2939parse_term([$P|Line0], _, D) -> %Pid. 2940 {Pid,Line} = get_id(Line0), 2941 {['#CDVPid'|Pid],Line,D}; 2942parse_term([$p|Line0], _, D) -> %Port. 2943 {Port,Line} = get_id(Line0), 2944 {['#CDVPort'|Port],Line,D}; 2945parse_term([$S|Str0], _, D) -> %Information string. 2946 Str1 = byte_list_to_string(Str0), 2947 Str = lists:reverse(skip_blanks(lists:reverse(Str1))), 2948 {Str,[],D}; 2949parse_term([$D|Line0], DecodeOpts, D) -> %DistExternal 2950 try 2951 {AttabSize,":"++Line1} = get_hex(Line0), 2952 {Attab, "E"++Line2} = parse_atom_translation_table(AttabSize, Line1, []), 2953 {Bin,Line3} = get_binary(Line2, DecodeOpts), 2954 {try 2955 erts_debug:dist_ext_to_term(Attab, Bin) 2956 catch 2957 error:_ -> '<invalid-distribution-message>' 2958 end, 2959 Line3, 2960 D} 2961 catch 2962 error:_ -> 2963 {'#CDVBadDistExt', skip_dist_ext(Line0), D} 2964 end. 2965 2966skip_dist_ext(Line) -> 2967 skip_dist_ext(lists:reverse(Line), []). 2968 2969skip_dist_ext([], SeqTraceToken) -> 2970 SeqTraceToken; 2971skip_dist_ext([$:| _], SeqTraceToken) -> 2972 [$:|SeqTraceToken]; 2973skip_dist_ext([C|Cs], KeptCs) -> 2974 skip_dist_ext(Cs, [C|KeptCs]). 2975 2976parse_atom([$A|Line0], D) -> 2977 {N,":"++Line1} = get_hex(Line0), 2978 {Chars, Line} = get_chars(N, Line1), 2979 {binary_to_atom(list_to_binary(Chars),utf8), Line, D}. 2980 2981parse_atom_translation_table(0, Line0, As) -> 2982 {list_to_tuple(lists:reverse(As)), Line0}; 2983parse_atom_translation_table(N, Line0, As) -> 2984 {A, Line1, _} = parse_atom(Line0, []), 2985 parse_atom_translation_table(N-1, Line1, [A|As]). 2986 2987 2988deref_ptr(Ptr, Line, DecodeOpts, D) -> 2989 Lookup0 = fun(D0) -> 2990 gb_trees:lookup(Ptr, D0) 2991 end, 2992 Lookup = wrap_line_map(Ptr, Lookup0), 2993 do_deref_ptr(Lookup, Line, DecodeOpts, D). 2994 2995deref_bin(Binp0, Offset, Sz, Line, DecodeOpts, D) -> 2996 Binp = Binp0 bor DecodeOpts#dec_opts.bin_addr_adj, 2997 Lookup0 = fun(D0) -> 2998 lookup_binary(Binp, Offset, Sz, D0) 2999 end, 3000 Lookup = wrap_line_map(Binp, Lookup0), 3001 do_deref_ptr(Lookup, Line, DecodeOpts, D). 3002 3003lookup_binary(Binp, Offset, Sz, D) -> 3004 case lookup_binary_index(Binp) of 3005 [{_,Start}] -> 3006 Term = cdvbin(Offset, Sz, {'#CDVBin',Start}), 3007 {value,Term}; 3008 [] -> 3009 case gb_trees:lookup(Binp, D) of 3010 {value,<<_:Offset/bytes,Sub:Sz/bytes,_/bytes>>} -> 3011 {value,Sub}; 3012 {value,SymbolicBin} -> 3013 {value,cdvbin(Offset, Sz, SymbolicBin)}; 3014 none -> 3015 none 3016 end 3017 end. 3018 3019wrap_line_map(Ptr, Lookup) -> 3020 wrap_line_map_1(get(line_map), Ptr, Lookup). 3021 3022wrap_line_map_1(#{}=LineMap, Ptr, Lookup) -> 3023 fun(D) -> 3024 case Lookup(D) of 3025 {value,_}=Res -> 3026 Res; 3027 none -> 3028 case LineMap of 3029 #{Ptr:=Line} -> 3030 {line,Ptr,Line}; 3031 #{} -> 3032 none 3033 end 3034 end 3035 end; 3036wrap_line_map_1(undefined, _Ptr, Lookup) -> 3037 Lookup. 3038 3039do_deref_ptr(Lookup, Line, DecodeOpts, D0) -> 3040 case Lookup(D0) of 3041 {value,Term} -> 3042 {Term,Line,D0}; 3043 none -> 3044 put(incomplete_heap, true), 3045 {'#CDVIncompleteHeap',Line,D0}; 3046 {line,Addr,NewLine} -> 3047 D = parse_line(Addr, NewLine, DecodeOpts, D0), 3048 do_deref_ptr(Lookup, Line, DecodeOpts, D) 3049 end. 3050 3051get_hex(L) -> 3052 get_hex_1(L, 0). 3053 3054get_hex_1([H|T]=L, Acc) -> 3055 case get_hex_digit(H) of 3056 none -> {Acc,L}; 3057 Digit -> get_hex_1(T, (Acc bsl 4) bor Digit) 3058 end; 3059get_hex_1([], Acc) -> {Acc,[]}. 3060 3061get_hex_digit(C) when $0 =< C, C =< $9 -> C-$0; 3062get_hex_digit(C) when $a =< C, C =< $f -> C-$a+10; 3063get_hex_digit(C) when $A =< C, C =< $F -> C-$A+10; 3064get_hex_digit(_) -> none. 3065 3066skip_blanks([$\s|T]) -> 3067 skip_blanks(T); 3068skip_blanks([$\r|T]) -> 3069 skip_blanks(T); 3070skip_blanks([$\n|T]) -> 3071 skip_blanks(T); 3072skip_blanks([$\t|T]) -> 3073 skip_blanks(T); 3074skip_blanks(T) -> T. 3075 3076get_chars(N, Line) -> 3077 get_chars(N, Line, []). 3078 3079get_chars(0, Line, Acc) -> 3080 {lists:reverse(Acc),Line}; 3081get_chars(N, [H|T], Acc) -> 3082 get_chars(N-1, T, [H|Acc]). 3083 3084get_id(Line0) -> 3085 [$<|Line] = lists:dropwhile(fun($<) -> false; (_) -> true end,Line0), 3086 get_id(Line, [], []). 3087 3088get_id([$>|Line], Acc, Id) -> 3089 {lists:reverse(Id,[list_to_integer(lists:reverse(Acc))]),Line}; 3090get_id([$.|Line], Acc, Id) -> 3091 get_id(Line,[],[list_to_integer(lists:reverse(Acc))|Id]); 3092get_id([H|T], Acc, Id) -> 3093 get_id(T, [H|Acc], Id). 3094 3095get_label(L) -> 3096 get_label(L, []). 3097 3098get_label([$:|Line], Acc) -> 3099 Label = lists:reverse(Acc), 3100 case get_hex(Label) of 3101 {Int,[]} -> 3102 {Int,Line}; 3103 _ -> 3104 {list_to_atom(Label),Line} 3105 end; 3106get_label([H|T], Acc) -> 3107 get_label(T, [H|Acc]). 3108 3109get_binary(Line0,DecodeOpts) -> 3110 case get_hex(Line0) of 3111 {N,":"++Line} -> 3112 get_binary_1(N, Line, DecodeOpts); 3113 _ -> 3114 {'#CDVTruncatedBinary',[]} 3115 end. 3116 3117get_binary_1(N,Line,#dec_opts{base64=false}) -> 3118 get_binary_hex(N, Line, [], false); 3119get_binary_1(N,Line0,#dec_opts{base64=true}) -> 3120 NumBytes = ((N+2) div 3) * 4, 3121 {Base64,Line} = lists:split(NumBytes, Line0), 3122 Bin = get_binary_base64(list_to_binary(Base64), <<>>, false), 3123 {Bin,Line}. 3124 3125get_binary(Offset,Size,Line0,DecodeOpts) -> 3126 case get_hex(Line0) of 3127 {_N,":"++Line} -> 3128 get_binary_1(Offset,Size,Line,DecodeOpts); 3129 _ -> 3130 {'#CDVTruncatedBinary',[]} 3131 end. 3132 3133get_binary_1(Offset,Size,Line,#dec_opts{base64=false}) -> 3134 Progress = Size > ?binary_size_progress_limit, 3135 Progress andalso init_progress("Reading binary",Size), 3136 get_binary_hex(Size, lists:sublist(Line,(Offset*2)+1,Size*2), [], 3137 Progress); 3138get_binary_1(StartOffset,Size,Line,#dec_opts{base64=true}) -> 3139 Progress = Size > ?binary_size_progress_limit, 3140 Progress andalso init_progress("Reading binary",Size), 3141 EndOffset = StartOffset + Size, 3142 StartByte = (StartOffset div 3) * 4, 3143 EndByte = ((EndOffset + 2) div 3) * 4, 3144 NumBytes = EndByte - StartByte, 3145 case list_to_binary(Line) of 3146 <<_:StartByte/bytes,Base64:NumBytes/bytes,_/bytes>> -> 3147 Bin0 = get_binary_base64(Base64, <<>>, Progress), 3148 Skip = StartOffset - (StartOffset div 3) * 3, 3149 <<_:Skip/bytes,Bin:Size/bytes,_/bytes>> = Bin0, 3150 {Bin,[]}; 3151 _ -> 3152 {'#CDVTruncatedBinary',[]} 3153 end. 3154 3155get_binary_hex(0, Line, Acc, Progress) -> 3156 Progress andalso end_progress(), 3157 {list_to_binary(lists:reverse(Acc)),Line}; 3158get_binary_hex(N, [A,B|Line], Acc, Progress) -> 3159 Byte = (get_hex_digit(A) bsl 4) bor get_hex_digit(B), 3160 Progress andalso update_progress(), 3161 get_binary_hex(N-1, Line, [Byte|Acc], Progress); 3162get_binary_hex(_N, [], _Acc, Progress) -> 3163 Progress andalso end_progress(), 3164 {'#CDVTruncatedBinary',[]}. 3165 3166get_binary_base64(<<Chunk0:?base64_chunk_size/bytes,T/bytes>>, 3167 Acc0, Progress) -> 3168 Chunk = base64:decode(Chunk0), 3169 Acc = <<Acc0/binary,Chunk/binary>>, 3170 Progress andalso update_progress(?base64_chunk_size * 3 div 4), 3171 get_binary_base64(T, Acc, Progress); 3172get_binary_base64(Chunk0, Acc, Progress) -> 3173 case Progress of 3174 true -> 3175 update_progress(?base64_chunk_size * 3 div 4), 3176 end_progress(); 3177 false -> 3178 ok 3179 end, 3180 Chunk = base64:decode(Chunk0), 3181 <<Acc/binary,Chunk/binary>>. 3182 3183cdvbin(Offset,Size,{'#CDVBin',Pos}) -> 3184 ['#CDVBin',Offset,Size,Pos]; 3185cdvbin(Offset,Size,['#CDVBin',_,_,Pos]) -> 3186 ['#CDVBin',Offset,Size,Pos]; 3187cdvbin(_,_,'#CDVTruncatedBinary') -> 3188 '#CDVTruncatedBinary'; 3189cdvbin(_,_,'#CDVNonexistingBinary') -> 3190 '#CDVNonexistingBinary'. 3191 3192%%----------------------------------------------------------------- 3193%% Functions for accessing tables 3194reset_tables() -> 3195 ets:delete_all_objects(cdv_dump_index_table), 3196 ets:delete_all_objects(cdv_reg_proc_table), 3197 ets:delete_all_objects(cdv_binary_index_table), 3198 ets:delete_all_objects(cdv_heap_file_chars). 3199 3200insert_index(Tag,Id,Pos) -> 3201 ets:insert(cdv_dump_index_table,{{Tag,Pos},Id}). 3202 3203delete_index(Tag,Id) -> 3204 Ms = [{{{Tag,'$1'},Id},[],[true]}], 3205 ets:select_delete(cdv_dump_index_table, Ms). 3206 3207lookup_index({Tag,Id}) -> 3208 lookup_index(Tag,Id); 3209lookup_index(Tag) -> 3210 lookup_index(Tag,'$2'). 3211lookup_index(Tag,Id) -> 3212 ets:select(cdv_dump_index_table,[{{{Tag,'$1'},Id},[],[{{Id,'$1'}}]}]). 3213 3214count_index(Tag) -> 3215 ets:select_count(cdv_dump_index_table,[{{{Tag,'_'},'_'},[],[true]}]). 3216 3217insert_binary_index(Addr,Pos) -> 3218 ets:insert(cdv_binary_index_table,{Addr,Pos}). 3219 3220lookup_binary_index(Addr) -> 3221 ets:lookup(cdv_binary_index_table,Addr). 3222 3223 3224%%----------------------------------------------------------------- 3225%% Convert tags read from crashdump to atoms used as first part of key 3226%% in cdv_dump_index_table 3227tag_to_atom("abort") -> ?abort; 3228tag_to_atom("allocated_areas") -> ?allocated_areas; 3229tag_to_atom("allocator") -> ?allocator; 3230tag_to_atom("atoms") -> ?atoms; 3231tag_to_atom("binary") -> ?binary; 3232tag_to_atom("dirty_cpu_scheduler") -> ?dirty_cpu_scheduler; 3233tag_to_atom("dirty_cpu_run_queue") -> ?dirty_cpu_run_queue; 3234tag_to_atom("dirty_io_scheduler") -> ?dirty_io_scheduler; 3235tag_to_atom("dirty_io_run_queue") -> ?dirty_io_run_queue; 3236tag_to_atom("end") -> ?ende; 3237tag_to_atom("erl_crash_dump") -> ?erl_crash_dump; 3238tag_to_atom("ets") -> ?ets; 3239tag_to_atom("fun") -> ?fu; 3240tag_to_atom("hash_table") -> ?hash_table; 3241tag_to_atom("hidden_node") -> ?hidden_node; 3242tag_to_atom("index_table") -> ?index_table; 3243tag_to_atom("instr_data") -> ?instr_data; 3244tag_to_atom("internal_ets") -> ?internal_ets; 3245tag_to_atom("literals") -> ?literals; 3246tag_to_atom("loaded_modules") -> ?loaded_modules; 3247tag_to_atom("memory") -> ?memory; 3248tag_to_atom("mod") -> ?mod; 3249tag_to_atom("persistent_terms") -> ?persistent_terms; 3250tag_to_atom("no_distribution") -> ?no_distribution; 3251tag_to_atom("node") -> ?node; 3252tag_to_atom("not_connected") -> ?not_connected; 3253tag_to_atom("old_instr_data") -> ?old_instr_data; 3254tag_to_atom("port") -> ?port; 3255tag_to_atom("proc") -> ?proc; 3256tag_to_atom("proc_dictionary") -> ?proc_dictionary; 3257tag_to_atom("proc_heap") -> ?proc_heap; 3258tag_to_atom("proc_messages") -> ?proc_messages; 3259tag_to_atom("proc_stack") -> ?proc_stack; 3260tag_to_atom("scheduler") -> ?scheduler; 3261tag_to_atom("timer") -> ?timer; 3262tag_to_atom("visible_node") -> ?visible_node; 3263tag_to_atom(UnknownTag) -> 3264 io:format("WARNING: Found unexpected tag:~ts~n",[UnknownTag]), 3265 list_to_atom(UnknownTag). 3266 3267%%%----------------------------------------------------------------- 3268%%% Store last tag for use when truncated, and reason if aborted 3269put_last_tag(?abort,Reason,_Pos) -> 3270 %% Don't overwrite the real last tag, and don't return it either, 3271 %% since that would make the caller of this function believe that 3272 %% the tag was complete. 3273 put(truncated_reason,Reason); 3274put_last_tag(Tag,Id,Pos) -> 3275 put(last_tag,{{Tag,Id},Pos}). 3276 3277%%%----------------------------------------------------------------- 3278%%% Fetch next chunk from crashdump file 3279lookup_and_parse_index(File,What,ParseFun,Str) when is_list(File) -> 3280 Indices = lookup_index(What), 3281 Fun = fun(Fd,{Id,Start}) -> 3282 pos_bof(Fd,Start), 3283 ParseFun(Fd,Id) 3284 end, 3285 Report = "Processing " ++ Str, 3286 progress_pmap(Report,File,Fun,Indices). 3287 3288%%%----------------------------------------------------------------- 3289%%% Convert a record to a proplist 3290to_proplist(Fields,Record) -> 3291 Values = to_value_list(Record), 3292 lists:zip(Fields,Values). 3293 3294%%%----------------------------------------------------------------- 3295%%% Convert a record to a simple list of field values 3296to_value_list(Record) -> 3297 [_RecordName|Values] = tuple_to_list(Record), 3298 Values. 3299 3300%%%----------------------------------------------------------------- 3301%%% Map over List and report progress in percent. 3302%%% Report is the text to be presented in the progress dialog. 3303%%% Distribute the load over a number of processes, and File is opened 3304%%% on each process and passed to the Fun as first argument. 3305%%% I.e. Fun = fun(Fd,Item) -> ItemResult end. 3306progress_pmap(Report,File,Fun,List) -> 3307 NTot = length(List), 3308 NProcs = erlang:system_info(schedulers) * 2, 3309 NPerProc = (NTot div NProcs) + 1, 3310 3311 %% Worker processes send message to collector for each ReportInterval. 3312 ReportInterval = (NTot div 100) + 1, 3313 3314 %% Progress reporter on collector process reports 1 percent for 3315 %% each message from worker process. 3316 init_progress(Report,99), 3317 3318 Collector = self(), 3319 {[],Pids} = 3320 lists:foldl( 3321 fun(_,{L,Ps}) -> 3322 {L1,L2} = if length(L)>=NPerProc -> lists:split(NPerProc,L); 3323 true -> {L,[]} % last chunk 3324 end, 3325 {P,_Ref} = 3326 spawn_monitor( 3327 fun() -> 3328 progress_map(Collector,ReportInterval,File,Fun,L1) 3329 end), 3330 {L2,[P|Ps]} 3331 end, 3332 {List,[]}, 3333 lists:seq(1,NProcs)), 3334 collect(Pids,[]). 3335 3336progress_map(Collector,ReportInterval,File,Fun,List) -> 3337 Fd = open(File), 3338 init_progress(ReportInterval, fun(_) -> Collector ! progress end, ok), 3339 progress_map(Fd,Fun,List,[]). 3340progress_map(Fd,Fun,[H|T],Acc) -> 3341 update_progress(), 3342 progress_map(Fd,Fun,T,[Fun(Fd,H)|Acc]); 3343progress_map(Fd,_Fun,[],Acc) -> 3344 close(Fd), 3345 exit({pmap_done,Acc}). 3346 3347collect([],Acc) -> 3348 end_progress(), 3349 lists:append(Acc); 3350collect(Pids,Acc) -> 3351 receive 3352 progress -> 3353 update_progress(), 3354 collect(Pids,Acc); 3355 {'DOWN', _Ref, process, Pid, {pmap_done,Result}} -> 3356 collect(lists:delete(Pid,Pids),[Result|Acc]); 3357 {'DOWN', _Ref, process, Pid, _Error} -> 3358 Warning = 3359 "WARNING: an error occured while parsing data.\n" ++ 3360 case get(truncated) of 3361 true -> "This might be because the dump is truncated.\n"; 3362 false -> "" 3363 end, 3364 io:format(Warning), 3365 collect(lists:delete(Pid,Pids),Acc) 3366 end. 3367 3368%%%----------------------------------------------------------------- 3369%%% Help functions for progress reporting 3370 3371%% Set text in progress dialog and initialize the progress counter 3372init_progress(Report,N) -> 3373 observer_lib:report_progress({ok,Report}), 3374 Interval = (N div 100) + 1, 3375 Fun = fun(P0) -> P=P0+1,observer_lib:report_progress({ok,P}),P end, 3376 init_progress(Interval,Fun,0). 3377init_progress(Interval,Fun,Acc) -> 3378 put(progress,{Interval,Interval,Fun,Acc}), 3379 ok. 3380 3381%% Count progress and report on given interval 3382update_progress() -> 3383 update_progress(1). 3384update_progress(Processed) -> 3385 do_update_progress(get(progress),Processed). 3386 3387do_update_progress({Count,Interval,Fun,Acc},Processed) when Processed>Count -> 3388 do_update_progress({Interval,Interval,Fun,Fun(Acc)},Processed-Count); 3389do_update_progress({Count,Interval,Fun,Acc},Processed) -> 3390 put(progress,{Count-Processed,Interval,Fun,Acc}), 3391 ok. 3392 3393%% End progress reporting for this item 3394end_progress() -> 3395 end_progress({ok,100}). 3396end_progress(Report) -> 3397 observer_lib:report_progress(Report), 3398 erase(progress), 3399 ok. 3400