1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2002-2021. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20-module(observer_backend). 21 22%% General 23-export([vsn/0]). 24 25%% observer stuff 26-export([socket_info/0, 27 sys_info/0, get_port_list/0, get_socket_list/0, procs_info/1, 28 get_table/3, get_table_list/2, fetch_stats/2]). 29 30%% etop stuff 31-export([etop_collect/1]). 32-include("observer_backend.hrl"). 33 34%% ttb stuff 35-export([ttb_init_node/3, 36 ttb_write_trace_info/3, 37 ttb_write_binary/2, 38 ttb_stop/1, 39 ttb_fetch/2, 40 ttb_fetch/3, 41 ttb_resume_trace/0, 42 ttb_get_filenames/1]). 43 44-define(CHUNKSIZE,8191). % 8 kbytes - 1 byte 45 46-ifndef(ESOCK_DONT_SHOW_UNSUPPORTED_OPTS). 47-define(ESOCK_KEEP_UNSUPPORTED_OPT(_), true). 48-else. 49-define(ESOCK_KEEP_UNSUPPORTED_OPT(__SUPPORTED__), __SUPPORTED__). 50-endif. 51 52vsn() -> 53 case application:load(runtime_tools) of 54 R when R=:=ok; R=:={error,{already_loaded,runtime_tools}} -> 55 application:get_key(runtime_tools,vsn); 56 Error -> Error 57 end. 58 59%% 60%% observer backend 61%% 62 63socket_info() -> 64 Info0 = socket:info(), 65 {Counters, Info1} = maps:take(counters, Info0), 66 IovMax = maps:get(iov_max , Info1), 67 NumMons = socket:number_of_monitors(), 68 [{iov_max, IovMax}, {num_monitors, NumMons} | maps:to_list(Counters)]. 69 70 71 72sys_info() -> 73 MemInfo = try erlang:memory() of 74 Mem -> Mem 75 catch _:_ -> [] 76 end, 77 78 SchedulersOnline = erlang:system_info(schedulers_online), 79 SchedulersAvailable = case erlang:system_info(multi_scheduling) of 80 enabled -> SchedulersOnline; 81 _ -> 1 82 end, 83 84 {{_,Input},{_,Output}} = erlang:statistics(io), 85 [{uptime, element(1, erlang:statistics(wall_clock))}, 86 {run_queue, erlang:statistics(run_queue)}, 87 {io_input, Input}, 88 {io_output, Output}, 89 90 {logical_processors, erlang:system_info(logical_processors)}, 91 {logical_processors_online, erlang:system_info(logical_processors_online)}, 92 {logical_processors_available, erlang:system_info(logical_processors_available)}, 93 {schedulers, erlang:system_info(schedulers)}, 94 {schedulers_online, SchedulersOnline}, 95 {schedulers_available, SchedulersAvailable}, 96 97 {otp_release, erlang:system_info(otp_release)}, 98 {version, erlang:system_info(version)}, 99 {system_architecture, erlang:system_info(system_architecture)}, 100 {kernel_poll, erlang:system_info(kernel_poll)}, 101 {smp_support, erlang:system_info(smp_support)}, 102 {threads, erlang:system_info(threads)}, 103 {thread_pool_size, erlang:system_info(thread_pool_size)}, 104 {wordsize_internal, erlang:system_info({wordsize, internal})}, 105 {wordsize_external, erlang:system_info({wordsize, external})}, 106 {alloc_info, alloc_info()}, 107 {process_count, erlang:system_info(process_count)}, 108 {atom_limit, erlang:system_info(atom_limit)}, 109 {atom_count, erlang:system_info(atom_count)}, 110 {process_limit, erlang:system_info(process_limit)}, 111 {process_count, erlang:system_info(process_count)}, 112 {port_limit, erlang:system_info(port_limit)}, 113 {port_count, erlang:system_info(port_count)}, 114 {ets_limit, erlang:system_info(ets_limit)}, 115 {ets_count, erlang:system_info(ets_count)}, 116 {dist_buf_busy_limit, erlang:system_info(dist_buf_busy_limit)} 117 | MemInfo]. 118 119alloc_info() -> 120 AlcuAllocs = erlang:system_info(alloc_util_allocators), 121 try erlang:system_info({allocator_sizes, AlcuAllocs}) of 122 Allocators -> Allocators 123 catch _:_ -> [] 124 end. 125 126get_table(Parent, Table, Module) -> 127 spawn(fun() -> 128 link(Parent), 129 get_table2(Parent, Table, Module) 130 end). 131 132get_table2(Parent, Table, Type) -> 133 Size = case Type of 134 ets -> ets:info(Table, size); 135 mnesia -> mnesia:table_info(Table, size) 136 end, 137 case Size =/= undefined andalso Size > 0 of 138 false -> 139 Parent ! {self(), '$end_of_table'}, 140 normal; 141 true when Type =:= ets -> 142 Mem = ets:info(Table, memory), 143 Average = Mem div Size, 144 NoElements = max(10, 20000 div Average), 145 get_ets_loop(Parent, ets:match(Table, '$1', NoElements)); 146 true -> 147 Mem = mnesia:table_info(Table, memory), 148 Average = Mem div Size, 149 NoElements = max(10, 20000 div Average), 150 Ms = [{'$1', [], ['$1']}], 151 Get = fun() -> 152 get_mnesia_loop(Parent, mnesia:select(Table, Ms, NoElements, read)) 153 end, 154 %% Not a transaction, we don't want to grab locks when inspecting the table 155 mnesia:async_dirty(Get) 156 end. 157 158get_ets_loop(Parent, '$end_of_table') -> 159 Parent ! {self(), '$end_of_table'}; 160get_ets_loop(Parent, {Match, Cont}) -> 161 Parent ! {self(), Match}, 162 get_ets_loop(Parent, ets:match(Cont)). 163 164get_mnesia_loop(Parent, '$end_of_table') -> 165 Parent ! {self(), '$end_of_table'}; 166get_mnesia_loop(Parent, {Match, Cont}) -> 167 Parent ! {self(), Match}, 168 get_mnesia_loop(Parent, mnesia:select(Cont)). 169 170get_port_list() -> 171 ExtraItems = [monitors,monitored_by,parallelism,locking,queue_size,memory], 172 [begin 173 [{port_id,P}|erlang:port_info(P)] ++ 174 port_info(P,ExtraItems) ++ 175 inet_port_extra(erlang:port_info(P, name), P) 176 end || P <- erlang:ports()]. 177 178port_info(P,[Item|Items]) -> 179 case erlang:port_info(P,Item) of 180 undefined -> port_info(P,Items); 181 Value -> [Value|port_info(P,Items)] 182 end; 183port_info(_,[]) -> 184 []. 185 186inet_port_extra({_,Type},Port) when Type =:= "udp_inet"; 187 Type =:= "tcp_inet"; 188 Type =:= "sctp_inet" -> 189 Data = 190 case inet:getstat(Port) of 191 {ok, Stats} -> [{statistics, Stats}]; 192 _ -> [] 193 end ++ 194 case inet:peername(Port) of 195 {ok, {RAddr,RPort}} when is_tuple(RAddr), is_integer(RPort) -> 196 [{remote_address,RAddr},{remote_port,RPort}]; 197 {ok, RAddr} -> 198 [{remote_address,RAddr}]; 199 {error, _} -> [] 200 end ++ 201 case inet:sockname(Port) of 202 {ok, {LAddr,LPort}} when is_tuple(LAddr), is_integer(LPort) -> 203 [{local_address,LAddr},{local_port,LPort}]; 204 {ok, LAddr} -> 205 [{local_address,LAddr}]; 206 {error, _} -> [] 207 end ++ 208 case inet:getopts(Port, 209 [active, broadcast, buffer, bind_to_device, 210 delay_send, deliver, dontroute, exit_on_close, 211 header, high_msgq_watermark, high_watermark, 212 ipv6_v6only, keepalive, linger, low_msgq_watermark, 213 low_watermark, mode, netns, nodelay, packet, 214 packet_size, priority, read_packets, recbuf, 215 reuseaddr, send_timeout, send_timeout_close, 216 show_econnreset, sndbuf, tos, tclass]) of 217 {ok, Opts} -> [{options, Opts}]; 218 {error, _} -> [] 219 end, 220 [{inet,Data}]; 221inet_port_extra(_,_) -> 222 []. 223 224 225get_socket_list() -> 226 GetOpt = fun(_Sock, {Opt, false}) -> 227 {Opt, "Not Supported"}; 228 (Sock, {Opt, true}) -> 229 case socket:getopt(Sock, Opt) of 230 %% Convert to string? 231 {ok, Value0} -> 232 %% d("get_socket_list -> ok: " 233 %% "~n Option: ~p" 234 %% "~n Value: ~p", [Opt, Value]), 235 Value = 236 if 237 (Value0 =:= []) -> "-"; 238 true -> Value0 239 end, 240 {Opt, Value}; 241 %% We need to handle error cases and convert them 242 %% to something useful ("Not Supported") 243 {error, enotsup} = _ERROR -> 244 %% d("get_socket_list -> error: enotsup"), 245 {Opt, "Not Supported"}; 246 {error, enoprotoopt} = _ERROR -> 247 %% d("get_socket_list -> error: enoprotoopt"), 248 {Opt, "Not Supported"}; 249 {error, enotconn} = _ERROR -> 250 %% d("get_socket_list -> error: enotconn"), 251 {Opt, "Not Connected"}; 252 {error, {invalid, _}} = _ERROR -> 253 %% d("get_socket_list -> error: invalid"), 254 {Opt, "Not Implemented"}; 255 {error, Reason} -> 256 %% d("get_socket_list -> error: " 257 %% "~n Option: ~p" 258 %% "~n Reason: ~p", [Opt, _Reason]), 259 {Opt, f("error:~p", [Reason])} 260 end 261 end, 262 [begin 263 Kind = socket:which_socket_kind(S), 264 FD = case socket:getopt(S, otp, fd) of 265 {ok, FD0} -> 266 FD0; 267 _ -> 268 -1 269 end, 270 Info0 = socket:info(S), 271 IdStr0 = socket:to_list(S), 272 IdStr = case Info0 of 273 #{type := stream, 274 protocol := tcp} when (Kind =:= compat) -> 275 %% Faketi fake 276 "#Socket" ++ Id = IdStr0, 277 "#InetSocket" ++ Id; 278 _ -> 279 IdStr0 280 end, 281 {Counters0, Info1} = maps:take(counters, Info0), 282 Counters = maps:to_list(Counters0), 283 Info2 = maps:remove(ctype, Info1), 284 Info3 = maps:remove(num_acceptors, Info2), 285 Info4 = maps:remove(num_readers, Info3), 286 Info5 = maps:remove(num_writers, Info4), 287 Info6 = 288 case socket:peername(S) of 289 {ok, RAddr} -> 290 RAddrStr = sockaddr_to_list(RAddr), 291 maps:put(raddress, RAddrStr, Info5); 292 _ -> 293 Info5 294 end, 295 Info7 = 296 case socket:sockname(S) of 297 {ok, LAddr} -> 298 LAddrStr = sockaddr_to_list(LAddr), 299 maps:put(laddress, LAddrStr, Info6); 300 _ -> 301 Info6 302 end, 303 SockOpts = 304 [{{socket, Opt}, Supported} || 305 {Opt, Supported} <- 306 socket:supports(options, socket), 307 ?ESOCK_KEEP_UNSUPPORTED_OPT(Supported)], 308 DomainOpts = 309 case Info7 of 310 #{domain := inet6} -> 311 [{{ipv6, Opt}, Supported} || 312 {Opt, Supported} <- 313 socket:supports(options, ipv6), 314 ?ESOCK_KEEP_UNSUPPORTED_OPT(Supported)]; 315 _ -> 316 [{{ip, Opt}, Supported} || 317 {Opt, Supported} <- 318 socket:supports(options, ip), 319 ?ESOCK_KEEP_UNSUPPORTED_OPT(Supported)] 320 end, 321 ProtoOpts = 322 case Info7 of 323 #{domain := Domain, 324 type := stream, 325 protocol := tcp} when (Domain =:= inet) orelse 326 (Domain =:= inet6) -> 327 [{{tcp, Opt}, Supported} || 328 {Opt, Supported} <- 329 socket:supports(options, tcp), 330 ?ESOCK_KEEP_UNSUPPORTED_OPT(Supported)]; 331 #{domain := Domain, 332 type := dgram, 333 protocol := udp} when (Domain =:= inet) orelse 334 (Domain =:= inet6) -> 335 [{{udp, Opt}, Supported} || 336 {Opt, Supported} <- 337 socket:supports(options, udp), 338 ?ESOCK_KEEP_UNSUPPORTED_OPT(Supported)]; 339 #{domain := Domain, 340 type := seqpacket, 341 protocol := sctp} when (Domain =:= inet) orelse 342 (Domain =:= inet6) -> 343 [{{sctp, Opt}, Supported} || 344 {Opt, Supported} <- 345 socket:supports(options, sctp), 346 ?ESOCK_KEEP_UNSUPPORTED_OPT(Supported)]; 347 _ -> 348 [] 349 end, 350 Opts = SockOpts ++ DomainOpts ++ ProtoOpts, 351 Options = [GetOpt(S, Opt) || Opt <- Opts], 352 %% d("get_socket_list -> " 353 %% "~n Options: ~p", [Options]), 354 Info7#{id => S, 355 id_str => IdStr, 356 fd => FD, 357 kind => Kind, 358 monitored_by => socket:monitored_by(S), 359 statistics => Counters, 360 options => lists:sort(Options)} 361 end || S <- socket:which_sockets()]. 362 363sockaddr_to_list(#{family := local, path := PathBin}) when is_binary(PathBin) -> 364 erlang:binary_to_list(PathBin); 365sockaddr_to_list(#{family := local, path := Path}) when is_list(Path) -> 366 Path; 367sockaddr_to_list(#{family := inet, addr := Addr, port := Port}) -> 368 inet_parse:ntoa(Addr) ++ " : " ++ erlang:integer_to_list(Port); 369sockaddr_to_list(#{family := inet6, addr := Addr, port := Port, 370 flowinfo := FI, scope_id := SID}) -> 371 inet_parse:ntoa(Addr) ++ " : " ++ 372 erlang:integer_to_list(Port) ++ 373 " , " ++ erlang:integer_to_list(FI) ++ 374 " , " ++ erlang:integer_to_list(SID); 375sockaddr_to_list(Addr) -> 376 f("~p", [Addr]). 377 378 379get_table_list(ets, Opts) -> 380 HideUnread = proplists:get_value(unread_hidden, Opts, true), 381 HideSys = proplists:get_value(sys_hidden, Opts, true), 382 Info = fun(Id, Acc) -> 383 try 384 TabId = case ets:info(Id, named_table) of 385 true -> ignore; 386 false -> Id 387 end, 388 Name = ets:info(Id, name), 389 Protection = ets:info(Id, protection), 390 ignore(HideUnread andalso Protection == private, unreadable), 391 Owner = ets:info(Id, owner), 392 RegName = case catch process_info(Owner, registered_name) of 393 [] -> ignore; 394 {registered_name, ProcName} -> ProcName 395 end, 396 ignore(HideSys andalso ordsets:is_element(RegName, sys_processes()), system_tab), 397 ignore(HideSys andalso ordsets:is_element(Name, sys_tables()), system_tab), 398 ignore((RegName == mnesia_monitor) 399 andalso Name /= schema 400 andalso is_atom((catch mnesia:table_info(Name, where_to_read))), mnesia_tab), 401 Memory = ets:info(Id, memory) * erlang:system_info(wordsize), 402 Tab = [{name,Name}, 403 {id,TabId}, 404 {protection,Protection}, 405 {owner,Owner}, 406 {size,ets:info(Id, size)}, 407 {reg_name,RegName}, 408 {type,ets:info(Id, type)}, 409 {keypos,ets:info(Id, keypos)}, 410 {heir,ets:info(Id, heir)}, 411 {memory,Memory}, 412 {compressed,ets:info(Id, compressed)}, 413 {fixed,ets:info(Id, fixed)} 414 ], 415 [Tab|Acc] 416 catch _:_What -> 417 %% io:format("Skipped ~p: ~p ~n",[Id, _What]), 418 Acc 419 end 420 end, 421 lists:foldl(Info, [], ets:all()); 422 423get_table_list(mnesia, Opts) -> 424 HideSys = proplists:get_value(sys_hidden, Opts, true), 425 Owner = ets:info(schema, owner), 426 Owner /= undefined orelse 427 throw({error, "Mnesia is not running on: " ++ atom_to_list(node())}), 428 {registered_name, RegName} = process_info(Owner, registered_name), 429 Info = fun(Id, Acc) -> 430 try 431 Name = Id, 432 ignore(HideSys andalso ordsets:is_element(Name, mnesia_tables()), system_tab), 433 ignore(Name =:= schema, mnesia_tab), 434 Storage = mnesia:table_info(Id, storage_type), 435 Tab0 = [{name,Name}, 436 {owner,Owner}, 437 {size,mnesia:table_info(Id, size)}, 438 {reg_name,RegName}, 439 {type,mnesia:table_info(Id, type)}, 440 {keypos,2}, 441 {memory,mnesia:table_info(Id, memory) * erlang:system_info(wordsize)}, 442 {storage,Storage}, 443 {index,mnesia:table_info(Id, index)} 444 ], 445 Tab = if Storage == disc_only_copies -> 446 [{fixed, dets:info(Id, safe_fixed)}|Tab0]; 447 (Storage == ram_copies) orelse 448 (Storage == disc_copies) -> 449 [{fixed, ets:info(Id, fixed)}, 450 {compressed, ets:info(Id, compressed)}|Tab0]; 451 true -> Tab0 452 end, 453 [Tab|Acc] 454 catch _:_What -> 455 %% io:format("Skipped ~p: ~p ~p ~n",[Id, _What, Stacktrace]), 456 Acc 457 end 458 end, 459 lists:foldl(Info, [], mnesia:system_info(tables)). 460 461fetch_stats(Parent, Time) -> 462 process_flag(trap_exit, true), 463 fetch_stats_loop(Parent, Time). 464 465fetch_stats_loop(Parent, Time) -> 466 erlang:system_flag(scheduler_wall_time, true), 467 receive 468 _Msg -> 469 erlang:system_flag(scheduler_wall_time, false), 470 ok 471 after Time -> 472 _M = Parent ! {stats, 1, 473 erlang:statistics(scheduler_wall_time), 474 erlang:statistics(io), 475 try erlang:memory() catch _:_ -> [] end}, 476 fetch_stats_loop(Parent, Time) 477 end. 478 479%% 480%% Chunk sending process info to etop/observer 481%% 482procs_info(Collector) -> 483 All = processes(), 484 Send = fun Send (Pids) -> 485 try lists:split(10000, Pids) of 486 {First, Rest} -> 487 Collector ! {procs_info, self(), etop_collect(First, [])}, 488 Send(Rest) 489 catch _:_ -> 490 Collector ! {procs_info, self(), etop_collect(Pids, [])} 491 end 492 end, 493 Send(All). 494 495%% 496%% etop backend 497%% 498etop_collect(Collector) -> 499 %% If this is the first time and the scheduler_wall_time flag is 500 %% false, SchedulerWallTime will be 'undefined' (and show 0 cpu 501 %% utilization in etop). Next time the flag will be true and then 502 %% there will be a measurement. 503 SchedulerWallTime = erlang:statistics(scheduler_wall_time), 504 ProcInfo = etop_collect(processes(), []), 505 506 Collector ! {self(),#etop_info{now = erlang:timestamp(), 507 n_procs = length(ProcInfo), 508 run_queue = erlang:statistics(run_queue), 509 runtime = SchedulerWallTime, 510 memi = etop_memi(), 511 procinfo = ProcInfo 512 }}, 513 514 case SchedulerWallTime of 515 undefined -> 516 spawn(fun() -> flag_holder_proc(Collector) end), 517 ok; 518 _ -> 519 ok 520 end. 521 522flag_holder_proc(Collector) -> 523 erlang:system_flag(scheduler_wall_time,true), 524 Ref = erlang:monitor(process,Collector), 525 receive 526 {'DOWN',Ref,_,_,_} -> 527 erlang:system_flag(scheduler_wall_time,false), 528 ok 529 end. 530 531etop_memi() -> 532 try 533 [{total, c:memory(total)}, 534 {processes, c:memory(processes)}, 535 {ets, c:memory(ets)}, 536 {atom, c:memory(atom)}, 537 {code, c:memory(code)}, 538 {binary, c:memory(binary)}] 539 catch 540 error:notsup -> 541 undefined 542 end. 543 544etop_collect([P|Ps], Acc) when P =:= self() -> 545 etop_collect(Ps, Acc); 546etop_collect([P|Ps], Acc) -> 547 Fs = [registered_name,initial_call,memory,reductions,current_function,message_queue_len], 548 case process_info(P, Fs) of 549 undefined -> 550 etop_collect(Ps, Acc); 551 [{registered_name,Reg},{initial_call,Initial},{memory,Mem}, 552 {reductions,Reds},{current_function,Current},{message_queue_len,Qlen}] -> 553 Name = case Reg of 554 [] -> initial_call(Initial, P); 555 _ -> Reg 556 end, 557 Info = #etop_proc_info{pid=P,mem=Mem,reds=Reds,name=Name, 558 cf=Current,mq=Qlen}, 559 etop_collect(Ps, [Info|Acc]) 560 end; 561etop_collect([], Acc) -> Acc. 562 563initial_call({proc_lib, init_p, _}, Pid) -> 564 proc_lib:translate_initial_call(Pid); 565initial_call(Initial, _Pid) -> 566 Initial. 567 568%% 569%% ttb backend 570%% 571ttb_init_node(MetaFile_0,PI,Traci) -> 572 if 573 is_list(MetaFile_0); 574 is_atom(MetaFile_0) -> 575 {ok, Cwd} = file:get_cwd(), 576 MetaFile = filename:join(Cwd, MetaFile_0), 577 file:delete(MetaFile); 578 true -> % {local,_,_} 579 MetaFile = MetaFile_0 580 end, 581 case proplists:get_value(resume, Traci) of 582 {true, _} -> (autostart_module()):write_config(Traci); 583 _ -> ok 584 end, 585 Self = self(), 586 MetaPid = spawn(fun() -> ttb_meta_tracer(MetaFile,PI,Self,Traci) end), 587 receive {MetaPid,started} -> ok end, 588 MetaPid ! {metadata,Traci}, 589 case PI of 590 true -> 591 MetaPid ! {metadata,pnames()}, 592 ok; 593 false -> 594 ok 595 end, 596 {ok,MetaFile,MetaPid}. 597 598ttb_write_trace_info(MetaPid,Key,What) -> 599 MetaPid ! {metadata,Key,What}, 600 ok. 601 602ttb_meta_tracer(MetaFile,PI,Parent,SessionData) -> 603 erlang:monitor(process, proplists:get_value(ttb_control, SessionData)), 604 case PI of 605 true -> 606 ReturnMS = [{'_',[],[{return_trace}]}], 607 erlang:trace_pattern({erlang,spawn,3},ReturnMS,[meta]), 608 erlang:trace_pattern({erlang,spawn_link,3},ReturnMS,[meta]), 609 erlang:trace_pattern({erlang,spawn_opt,4},ReturnMS,[meta]), 610 erlang:trace_pattern({erts_internal,spawn_init,1},[],[meta]), 611 erlang:trace_pattern({erts_internal,dist_spawn_init,1},[],[meta]), 612 erlang:trace_pattern({erlang,register,2},[],[meta]), 613 erlang:trace_pattern({global,register_name,2},[],[meta]), 614 ok; 615 false -> 616 ok 617 end, 618 Parent ! {self(),started}, 619 case proplists:get_value(overload_check, SessionData) of 620 {Ms, M, F} -> 621 catch M:F(init), 622 erlang:send_after(Ms, self(), overload_check), 623 ok; 624 _ -> 625 ok 626 end, 627 ttb_meta_tracer_loop(MetaFile,PI,dict:new(),SessionData). 628 629ttb_meta_tracer_loop(MetaFile,PI,Acc,State) -> 630 receive 631 {trace_ts,_,call,{erlang,register,[Name,Pid]},_} -> 632 ok = ttb_store_meta({pid,{Pid,Name}},MetaFile), 633 ttb_meta_tracer_loop(MetaFile,PI,Acc,State); 634 {trace_ts,_,call,{global,register_name,[Name,Pid]},_} -> 635 ok = ttb_store_meta({pid,{Pid,{global,Name}}},MetaFile), 636 ttb_meta_tracer_loop(MetaFile,PI,Acc,State); 637 {trace_ts,CallingPid,call,{erlang,spawn_opt,[M,F,Args,_]},_} -> 638 MFA = {M,F,length(Args)}, 639 NewAcc = dict:update(CallingPid, 640 fun(Old) -> [MFA|Old] end, [MFA], 641 Acc), 642 ttb_meta_tracer_loop(MetaFile,PI,NewAcc,State); 643 {trace_ts,CallingPid,return_from,{erlang,spawn_opt,_Arity},Ret,_} -> 644 case Ret of 645 {NewPid,_Mref} when is_pid(NewPid) -> ok; 646 NewPid when is_pid(NewPid) -> ok 647 end, 648 NewAcc = 649 dict:update(CallingPid, 650 fun([H|T]) -> 651 ok = ttb_store_meta({pid,{NewPid,H}},MetaFile), 652 T 653 end, 654 Acc), 655 ttb_meta_tracer_loop(MetaFile,PI,NewAcc,State); 656 {trace_ts,CallingPid,call,{erlang,Spawn,[M,F,Args]},_} 657 when Spawn==spawn;Spawn==spawn_link -> 658 MFA = {M,F,length(Args)}, 659 NewAcc = dict:update(CallingPid, 660 fun(Old) -> [MFA|Old] end, [MFA], 661 Acc), 662 ttb_meta_tracer_loop(MetaFile,PI,NewAcc,State); 663 664 {trace_ts,CallingPid,return_from,{erlang,Spawn,_Arity},NewPid,_} 665 when Spawn==spawn;Spawn==spawn_link -> 666 NewAcc = 667 dict:update(CallingPid, 668 fun([H|T]) -> 669 ok = ttb_store_meta({pid,{NewPid,H}},MetaFile), 670 T 671 end, 672 Acc), 673 ttb_meta_tracer_loop(MetaFile,PI,NewAcc,State); 674 675 {trace_ts,CallingPid,call,{erts_internal,spawn_init,[{M,F,Args}]},_} -> 676 %% Local spawn_request()... 677 ok = ttb_store_meta({pid,{CallingPid,{M,F,length(Args)}}},MetaFile), 678 ttb_meta_tracer_loop(MetaFile,PI,Acc,State); 679 680 {trace_ts,CallingPid,call,{erts_internal, dist_spawn_init, [MFnoA]},_} -> 681 %% Distributed spawn_request()... 682 ok = ttb_store_meta({pid,{CallingPid,MFnoA}},MetaFile), 683 ttb_meta_tracer_loop(MetaFile,PI,Acc,State); 684 685 {metadata,Data} when is_list(Data) -> 686 ok = ttb_store_meta(Data,MetaFile), 687 ttb_meta_tracer_loop(MetaFile,PI,Acc,State); 688 689 {metadata,Key,Fun} when is_function(Fun) -> 690 ok = ttb_store_meta([{Key,Fun()}],MetaFile), 691 ttb_meta_tracer_loop(MetaFile,PI,Acc,State); 692 693 {metadata,Key,What} -> 694 ok = ttb_store_meta([{Key,What}],MetaFile), 695 ttb_meta_tracer_loop(MetaFile,PI,Acc,State); 696 overload_check -> 697 {Ms, M, F} = proplists:get_value(overload_check, State), 698 case catch M:F(check) of 699 true -> 700 erlang:trace(all, false, [all]), 701 ControlPid = proplists:get_value(ttb_control, State), 702 ControlPid ! {node_overloaded, node()}, 703 catch M:F(stop), 704 ttb_meta_tracer_loop(MetaFile,PI,Acc,lists:keydelete(overload_check, 1, State)); 705 _ -> 706 erlang:send_after(Ms, self(), overload_check), 707 ttb_meta_tracer_loop(MetaFile,PI,Acc, State) 708 end; 709 {'DOWN', _, _, _, _} -> 710 _ = stop_seq_trace(), 711 self() ! stop, 712 ttb_meta_tracer_loop(MetaFile,PI,Acc, State); 713 stop when PI=:=true -> 714 try_stop_resume(State), 715 try_stop_overload_check(State), 716 erlang:trace_pattern({erlang,spawn,3},false,[meta]), 717 erlang:trace_pattern({erlang,spawn_link,3},false,[meta]), 718 erlang:trace_pattern({erlang,spawn_opt,4},false,[meta]), 719 erlang:trace_pattern({erts_internal,spawn_init,1},false,[meta]), 720 erlang:trace_pattern({erts_internal,dist_spawn_init,1},false,[meta]), 721 erlang:trace_pattern({erlang,register,2},false,[meta]), 722 erlang:trace_pattern({global,register_name,2},false,[meta]); 723 stop -> 724 try_stop_resume(State), 725 try_stop_overload_check(State) 726 end. 727 728try_stop_overload_check(State) -> 729 case proplists:get_value(overload, State) of 730 undefined -> ok; 731 {_, M, F} -> catch M:F(stop) 732 end. 733 734pnames() -> 735 Processes = processes(), 736 Globals = lists:map(fun(G) -> {global:whereis_name(G),G} end, 737 global:registered_names()), 738 lists:flatten(lists:foldl(fun(Pid,Acc) -> [pinfo(Pid,Globals)|Acc] end, 739 [], Processes)). 740 741pinfo(P,Globals) -> 742 case process_info(P,registered_name) of 743 [] -> 744 case lists:keysearch(P,1,Globals) of 745 {value,{P,G}} -> {pid,{P,{global,G}}}; 746 false -> 747 case process_info(P,initial_call) of 748 {_,I} -> {pid,{P,I}}; 749 undefined -> [] % the process has terminated 750 end 751 end; 752 {_,R} -> {pid,{P,R}}; 753 undefined -> [] % the process has terminated 754 end. 755 756autostart_module() -> 757 element(2, application:get_env(runtime_tools, ttb_autostart_module)). 758 759try_stop_resume(State) -> 760 case proplists:get_value(resume, State) of 761 true -> (autostart_module()):delete_config(); 762 _ -> ok 763 end. 764 765ttb_resume_trace() -> 766 case (autostart_module()):read_config() of 767 {error, _} -> 768 ok; 769 {ok, Data} -> 770 Pid = proplists:get_value(ttb_control, Data), 771 {_, Timeout} = proplists:get_value(resume, Data), 772 case rpc:call(node(Pid), erlang, whereis, [ttb]) of 773 Pid -> 774 Pid ! {noderesumed, node(), self()}, 775 wait_for_fetch_ready(Timeout); 776 _ -> 777 ok 778 end, 779 (autostart_module()):delete_config(), 780 ok 781 end. 782 783wait_for_fetch_ready(Timeout) -> 784 receive 785 trace_resumed -> 786 ok 787 after Timeout -> 788 ok 789 end. 790 791ttb_store_meta(Data,{local,MetaFile,Port}) when is_list(Data) -> 792 ttb_send_to_port(Port,MetaFile,Data); 793ttb_store_meta(Data,MetaFile) when is_list(Data) -> 794 {ok,Fd} = file:open(MetaFile,[raw,append]), 795 ttb_write_binary(Fd,Data), 796 file:close(Fd); 797ttb_store_meta(Data,MetaFile) -> 798 ttb_store_meta([Data],MetaFile). 799 800ttb_write_binary(Fd,[H|T]) -> 801 ok = file:write(Fd,ttb_make_binary(H)), 802 ttb_write_binary(Fd,T); 803ttb_write_binary(_Fd,[]) -> 804 ok. 805 806ttb_send_to_port(Port,MetaFile,[H|T]) -> 807 B1 = ttb_make_binary(H), 808 B2 = term_to_binary({metadata,MetaFile,B1}), 809 erlang:port_command(Port,B2), 810 ttb_send_to_port(Port,MetaFile,T); 811ttb_send_to_port(_Port,_MetaFile,[]) -> 812 ok. 813 814ttb_make_binary(Term) -> 815 B = term_to_binary(Term), 816 SizeB = byte_size(B), 817 if SizeB > 255 -> 818 %% size is bigger than 8 bits, must therefore add an extra 819 %% size field 820 SB = term_to_binary({'$size',SizeB}), 821 <<(byte_size(SB)):8, SB/binary, B/binary>>; 822 true -> 823 <<SizeB:8, B/binary>> 824 end. 825 826 827%% Stop ttb 828ttb_stop(MetaPid) -> 829 Delivered = erlang:trace_delivered(all), 830 receive 831 {trace_delivered,all,Delivered} -> ok 832 end, 833 Ref = erlang:monitor(process,MetaPid), 834 MetaPid ! stop, 835 836 %% Must wait for the process to terminate there 837 %% because dbg will be stopped when this function 838 %% returns, and then the Port (in {local,MetaFile,Port}) 839 %% cannot be accessed any more. 840 receive {'DOWN', Ref, process, MetaPid, _Info} -> ok end, 841 stop_seq_trace(). 842 843stop_seq_trace() -> 844 seq_trace:reset_trace(), 845 seq_trace:set_system_tracer(false). 846 847%% Fetch ttb logs from remote node 848ttb_fetch(MetaFile,{Port,Host}) -> 849 ttb_fetch(MetaFile,{Port,Host},undefined). 850ttb_fetch(MetaFile,{Port,Host},MasterEnc) -> 851 erlang:process_flag(priority,low), 852 Files = ttb_get_filenames(MetaFile), 853 {ok, Sock} = gen_tcp:connect(Host, Port, [binary, {packet, 2}]), 854 send_files({Sock,Host},Files,MasterEnc,file:native_name_encoding()), 855 ok = gen_tcp:close(Sock). 856 857 858send_files({Sock,Host},[File|Files],MasterEnc,MyEnc) -> 859 {ok,Fd} = file:open(File,[raw,read,binary]), 860 Basename = filename:basename(File), 861 {Code,FilenameBin} = encode_filename(Basename,MasterEnc,MyEnc), 862 ok = gen_tcp:send(Sock,<<Code,FilenameBin/binary>>), 863 send_chunks(Sock,Fd), 864 ok = file:delete(File), 865 send_files({Sock,Host},Files,MasterEnc,MyEnc); 866send_files({_Sock,_Host},[],_MasterEnc,_MyEnc) -> 867 done. 868 869encode_filename(Basename,undefined,MyEnc) -> 870 %% Compatible with old version of ttb.erl, but no longer crashing 871 %% for code points > 255. 872 {1,unicode:characters_to_binary(Basename,MyEnc,MyEnc)}; 873encode_filename(Basename,MasterEnc,MyEnc) -> 874 case unicode:characters_to_binary(Basename,MyEnc,MasterEnc) of 875 Bin when is_binary(Bin) -> 876 %% Encoding succeeded 877 {2,Bin}; 878 _ -> 879 %% Can't convert Basename from my encoding to the master 880 %% node's encoding. Doing my best and hoping that master 881 %% node can fix it... 882 {3,unicode:characters_to_binary(Basename,MyEnc,MyEnc)} 883 end. 884 885send_chunks(Sock,Fd) -> 886 case file:read(Fd,?CHUNKSIZE) of 887 {ok,Bin} -> 888 ok = gen_tcp:send(Sock, <<0,Bin/binary>>), 889 send_chunks(Sock,Fd); 890 eof -> 891 ok; 892 {error,Reason} -> 893 ok = gen_tcp:send(Sock, <<2,(term_to_binary(Reason))/binary>>) 894 end. 895 896ttb_get_filenames(MetaFile) -> 897 Dir = filename:dirname(MetaFile), 898 Root = filename:rootname(filename:basename(MetaFile)), 899 {ok,List} = file:list_dir(Dir), 900 match_filenames(Dir,Root,List,[]). 901 902match_filenames(Dir,MetaFile,[H|T],Files) -> 903 case lists:prefix(MetaFile,H) of 904 true -> match_filenames(Dir,MetaFile,T,[filename:join(Dir,H)|Files]); 905 false -> match_filenames(Dir,MetaFile,T,Files) 906 end; 907match_filenames(_Dir,_MetaFile,[],Files) -> 908 Files. 909 910 911%%%%%%%%%%%%%%%%% 912 913sys_tables() -> 914 [ac_tab, asn1, 915 cdv_dump_index_table, cdv_menu_table, cdv_decode_heap_table, 916 cell_id, cell_pos, clist, 917 cover_internal_data_table, cover_collected_remote_data_table, cover_binary_code_table, 918 code, code_names, cookies, 919 corba_policy, corba_policy_associations, 920 dets, dets_owners, dets_registry, 921 disk_log_names, disk_log_pids, 922 eprof, erl_atom_cache, erl_epmd_nodes, 923 etop_accum_tab, etop_tr, 924 ets_coverage_data, 925 file_io_servers, 926 gs_mapping, gs_names, gstk_db, 927 gstk_grid_cellid, gstk_grid_cellpos, gstk_grid_id, 928 httpd, 929 id, 930 ign_req_index, ign_requests, 931 index, 932 inet_cache, inet_db, inet_hosts, 933 'InitialReferences', 934 int_db, 935 interpreter_includedirs_macros, 936 ir_WstringDef, 937 lmcounter, locks, 938 % mnesia_decision, 939 mnesia_gvar, mnesia_stats, 940 % mnesia_transient_decision, 941 pg2_table, 942 pg, 943 queue, 944 schema, 945 shell_records, 946 snmp_agent_table, snmp_local_db2, snmp_mib_data, snmp_note_store, snmp_symbolic_ets, 947 tkFun, tkLink, tkPriv, 948 ttb, ttb_history_table, 949 udp_fds, udp_pids 950 ]. 951 952sys_processes() -> 953 [auth, code_server, global_name_server, inet_db, 954 mnesia_recover, net_kernel, pg, timer_server, wxe_master]. 955 956mnesia_tables() -> 957 [ir_AliasDef, ir_ArrayDef, ir_AttributeDef, ir_ConstantDef, 958 ir_Contained, ir_Container, ir_EnumDef, ir_ExceptionDef, 959 ir_IDLType, ir_IRObject, ir_InterfaceDef, ir_ModuleDef, 960 ir_ORB, ir_OperationDef, ir_PrimitiveDef, ir_Repository, 961 ir_SequenceDef, ir_StringDef, ir_StructDef, ir_TypedefDef, 962 ir_UnionDef, logTable, logTransferTable, mesh_meas, 963 mesh_type, mnesia_clist, orber_CosNaming, 964 orber_objkeys, user 965 ]. 966 967ignore(true, Reason) -> throw(Reason); 968ignore(_,_ ) -> ok. 969 970f(F, A) -> 971 lists:flatten(io_lib:format(F, A)). 972 973%% d(F) -> 974%% d(F, []). 975 976%% d(Debug, F) when is_boolean(Debug) andalso is_list(F) -> 977%% d(Debug, F, []); 978%% d(F, A) when is_list(F) andalso is_list(A) -> 979%% d(get(debug), F, A). 980 981%% d(true, F, A) -> 982%% io:format("[ob] " ++ F ++ "~n", A); 983%% d(_, _, _) -> 984%% ok. 985 986 987