%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2003-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. %% %% %CopyrightEnd% %% -module(crashdump_viewer_SUITE). %% Test functions -export([all/0, suite/0,groups/0,init_per_group/2,end_per_group/2, translate/1,start/1,fini/1,load_file/1, non_existing/1,not_a_crashdump/1,old_crashdump/1]). -export([init_per_suite/1, end_per_suite/1]). -export([init_per_testcase/2, end_per_testcase/2]). -include_lib("common_test/include/ct.hrl"). -include("test_server_line.hrl"). -include_lib("kernel/include/file.hrl"). -include_lib("stdlib/include/ms_transform.hrl"). -define(default_timeout, ?t:minutes(30)). -define(sl_alloc_vsns,[r9b]). init_per_testcase(_Case, Config) -> DataDir = ?config(data_dir,Config), Fs = filelib:wildcard(filename:join(DataDir,"*translated*")), lists:foreach(fun(F) -> file:delete(F) end,Fs), catch crashdump_viewer:stop(), Dog = ?t:timetrap(?default_timeout), [{watchdog, Dog}|Config]. end_per_testcase(_Case, Config) -> Dog=?config(watchdog, Config), ?t:timetrap_cancel(Dog), ok. suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [translate, load_file, non_existing, not_a_crashdump, old_crashdump]. groups() -> []. init_per_group(_GroupName, Config) -> Config. end_per_group(_GroupName, Config) -> Config. init_per_suite(doc) -> ["Create a lot of crashdumps which can be used in the testcases below"]; init_per_suite(Config) when is_list(Config) -> Dog = ?t:timetrap(?default_timeout), application:start(inets), % will be using the http client later http:set_options([{ipv6,disabled}]), DataDir = ?config(data_dir,Config), Rels = [R || R <- [r12b,r13b], ?t:is_release_available(R)] ++ [current], io:format("Creating crash dumps for the following releases: ~p", [Rels]), AllDumps = create_dumps(DataDir,Rels), ?t:timetrap_cancel(Dog), [{dumps,AllDumps}|Config]. translate(suite) -> []; translate(doc) -> ["Test that crash dumps from OTP R9B can be translated"]; translate(Config) when is_list(Config) -> DataDir = ?config(data_dir,Config), OutFile = filename:join(DataDir,"translated"), R9BFiles = filelib:wildcard(filename:join(DataDir,"r9b_dump.*")), AllFiles = R9BFiles, lists:foreach( fun(File) -> io:format("Translating file: ~s~n",[File]), ok = crashdump_translate:old2new(File,OutFile), check_result(File,OutFile) end, AllFiles), ok. start(suite) -> []; start(doc) -> ["Test start and stop of the Crashdump Viewer"]; start(Config) when is_list(Config) -> %% Set a much shorter timeout here... We don't have all the time in world. AngryDog = ?t:timetrap(?t:seconds(30)), Port = start_cdv(), true = is_pid(whereis(crashdump_viewer_server)), true = is_pid(whereis(web_tool)), Html = contents(Port,"start_page"), "Welcome to the Web BasedErlang Crash Dump Analyser" = strip(Html), ok = crashdump_viewer:stop(), timer:sleep(10), % give some time to stop undefined = whereis(crashdump_viewer_server), undefined = whereis(web_tool), Url = cdv_url(Port,"start_page"), {error,_} = http:request(get,{Url,[]},[],[]), % exit(whereis(httpc_manager),kill), ?t:timetrap_cancel(AngryDog), ok. fini(Config) when is_list(Config) -> ok. load_file(suite) -> []; load_file(doc) -> ["Load files into the tool and view all pages"]; load_file(Config) when is_list(Config) -> case ?t:is_debug() of true -> {skip,"Debug-compiled emulator -- far too slow"}; false -> load_file_1(Config) end. load_file_1(Config) -> DataDir = ?config(data_dir,Config), Port = start_cdv(), AllFiles = filelib:wildcard(filename:join(DataDir,"r*_dump.*")), lists:foreach( fun(File) -> browse_file(Port,File), special(Port,File) end, AllFiles), ok = crashdump_viewer:stop(). non_existing(suite) -> []; non_existing(doc) -> ["Try to load nonexisting file"]; non_existing(Config) when is_list(Config) -> Port = start_cdv(), Url = "http://localhost:"++Port++"/cdv_erl/crashdump_viewer/read_file", Html = request_sync(post,{Url,[],[],"path=nonexistingfile"}), "Please wait..." = title(Html), "An error occured:nonexistingfile is not an Erlang crash dump" = strip(wait(10,Port,"redirect")), ok = crashdump_viewer:stop(). old_crashdump(doc) -> ["Try to load nonexisting file"]; old_crashdump(Config) when is_list(Config) -> Port = start_cdv(), DataDir = ?config(data_dir, Config), OldCrashDump = filename:join(DataDir, "old_format.dump"), Url = "http://localhost:"++Port++"/cdv_erl/crashdump_viewer/read_file", Html = request_sync(post,{Url,[],[],"path="++OldCrashDump}), "Please wait..." = title(Html), Str = "An error occured:The crashdump "++OldCrashDump++ " is in the pre-R10B format, which is no longer supported.", Str = strip(wait(10,Port,"redirect")), ok = crashdump_viewer:stop(). not_a_crashdump(suite) -> []; not_a_crashdump(doc) -> ["Try to load a file which is not an erlang crashdump"]; not_a_crashdump(Config) when is_list(Config) -> Port = start_cdv(), NoCrashdump = code:which(?MODULE), Url = "http://localhost:"++Port++"/cdv_erl/crashdump_viewer/read_file", Html = request_sync(post,{Url,[],[],"path="++NoCrashdump}), "Please wait..." = title(Html), Str = "An error occured:"++NoCrashdump++" is not an Erlang crash dump", Str = strip(wait(10,Port,"redirect")), ok = crashdump_viewer:stop(), % exit(whereis(httpc_manager),kill), ok. end_per_suite(doc) -> ["Remove generated crashdumps"]; end_per_suite(Config) when is_list(Config) -> Dumps = ?config(dumps,Config), lists:foreach(fun(CD) -> ok = file:delete(CD) end,Dumps), lists:keydelete(dumps,1,Config). %%%----------------------------------------------------------------- %%% Internal start_cdv() -> ?t:capture_start(), ok = crashdump_viewer:start(), "WebTool is available at http://localhost:" ++ Where = lists:flatten(?t:capture_get()), ?t:capture_stop(), [Port|_] = string:tokens(Where,"/"), Port. check_result(File,OutFile) -> {ok,#file_info{size=FS}} = file:read_file_info(File), {ok,#file_info{size=OFS}} = file:read_file_info(OutFile), Rel = if OFS > 0 -> FS/OFS; true -> 1.25 end, if Rel>0.75, Rel<1.25 -> ok; true -> ?t:fail({unreasonable_size,File,FS,OFS}) end, {ok,Fd} = file:open(OutFile,[read]), "=erl_crash_dump:0.0\n" = io:get_line(Fd,''), case is_truncated(File) of true -> ok; false -> {ok,_} = file:position(Fd,{eof,-5}), case io:get_line(Fd,'') of "=end\n" -> ok; Other -> ?t:fail({truncated,File,Other}) end end, ok = file:close(Fd). %% Read a page and check that the page title matches Title contents(Port,Link) -> Url = cdv_url(Port,Link), request_sync(get,{Url,[]}). cdv_url(Port,Link) -> "http://localhost:" ++ Port ++ "/cdv_erl/crashdump_viewer/" ++ Link. request_sync(Method,HTTPReqCont) -> case http:request(Method, HTTPReqCont, [{timeout,30000}], [{full_result, false}]) of {ok,{200,Html}} -> Html; {ok,{Code,Html}} -> io:format("~s\n", [Html]), io:format("Received ~w from http:request(...) with\nMethod=~w\n" "HTTPReqCont=~p\n", [Code,Method,HTTPReqCont]), ?t:fail(); Other -> io:format( "Received ~w from http:request(...) with\nMethod=~w\n" "HTTPReqCont=~p\n", [Other,Method,HTTPReqCont]), ?t:fail() end. strip([$<|Html]) -> strip(drop_tag(Html)); strip([$\n|Html]) -> strip(Html); strip([X|Html]) -> [X|strip(Html)]; strip([]) -> []. drop_tag([$>|Html]) -> Html; drop_tag([_|Html]) -> drop_tag(Html). title(Port,Link,Title) -> Html = contents(Port,Link), Title = title(Html). wait(0,_Port,Link) -> ?t:fail({wait,Link,timeout}); wait(Time,Port,Link) -> Html = contents(Port,Link), case title(Html) of "Please wait..." -> timer:sleep(1000), wait(Time-1,Port,Link); _Title -> Html end. title([$<,$T,$I,$T,$L,$E,$>|Html]) -> title_end(Html); title([_|Html]) -> title(Html); title([]) -> []. title_end([$<,$/,$T,$I,$T,$L,$E,$>|_]) -> []; title_end([X|Html]) -> [X|title_end(Html)]. %%%----------------------------------------------------------------- %%% General check of what is displayed for a dump browse_file(Port,File) -> io:format("Browsing file: ~s~n",[File]), %% The page where a filename can be entered title(Port,"read_file_frame","Read File"), %% Load a file Url = "http://localhost:"++Port++"/cdv_erl/crashdump_viewer/read_file", Html = request_sync(post,{Url,[],[],"path="++File}), "Please wait..." = title(Html), "Crashdump Viewer Start Page" = title(wait(10,Port,"start_page")), %% The frame with the initial information for a dump title(Port,"initial_info_frame","General Information"), %% Topmost frame of the page FilenameFrame = contents(Port,"filename_frame"), Match = "FilenameCrashdump currently viewed:" ++ File, true = lists:prefix(Match,strip(FilenameFrame)), %% Toggle a menu item and check that it explodes/collapses title(Port,"menu_frame","Menu"), exploded = toggle_menu(Port), collapsed = toggle_menu(Port), %% Open each page in menu and check that correct title is shown title(Port,"general_info","General Information"), title(Port,"processes","Process Information"), title(Port,"sort_procs?sort=state","Process Information"), title(Port,"sort_procs?sort=state","Process Information"), title(Port,"sort_procs?sort=pid","Process Information"), title(Port,"sort_procs?sort=pid","Process Information"), title(Port,"sort_procs?sort=msg_q_len","Process Information"), title(Port,"sort_procs?sort=msg_q_len","Process Information"), title(Port,"sort_procs?sort=reds","Process Information"), title(Port,"sort_procs?sort=reds","Process Information"), title(Port,"sort_procs?sort=mem","Process Information"), title(Port,"sort_procs?sort=mem","Process Information"), title(Port,"sort_procs?sort=name","Process Information"), title(Port,"sort_procs?sort=name","Process Information"), title(Port,"sort_procs?sort=init_func","Process Information"), title(Port,"sort_procs?sort=init_func","Process Information"), title(Port,"ports","Port Information"), title(Port,"ets_tables","ETS Table Information"), title(Port,"timers","Timer Information"), title(Port,"fun_table","Fun Information"), title(Port,"atoms","Atoms"), title(Port,"dist_info","Distribution Information"), title(Port,"loaded_modules","Loaded Modules Information"), title(Port,"hash_tables","Hash Table Information"), title(Port,"index_tables","Index Table Information"), title(Port,"memory","Memory Information"), title(Port,"allocated_areas","Information about allocated areas"), title(Port,"allocator_info","Allocator Information"), case is_truncated(File) of true -> ok; _ -> proc_details(Port), port_details(Port), title(Port,"loaded_mod_details?mod=kernel","kernel") end, ok. special(Port,File) -> case filename:extension(File) of ".full_dist" -> contents(Port,"processes"), AllProcs = contents(Port,"sort_procs?sort=name"), %% I registered a process as aaaaaaaa in the full_dist dumps %% to make sure it will be the first in the list when sorted %% on names. There are some special data here, så I'll thoroughly %% read the process details for this process. Other processes %% are just briefly traversed. {Pid,Rest1} = get_first_process(AllProcs), ProcDetails = contents(Port,"proc_details?pid=" ++ Pid), ProcTitle = "Process " ++ Pid, ProcTitle = title(ProcDetails), title(Port,"ets_tables?pid="++Pid,"ETS Tables for Process "++Pid), title(Port,"timers?pid="++Pid,"Timers for Process "++Pid), case filename:basename(File) of "r10b_dump.full_dist" -> [MsgQueueLink,DictLink,StackDumpLink] = expand_memory_links(ProcDetails), MsgQueue = contents(Port,MsgQueueLink), "MsgQueue" = title(MsgQueue), title(Port,DictLink,"Dictionary"), title(Port,StackDumpLink,"StackDump"), ExpandBinaryLink = expand_binary_link(MsgQueue), title(Port,ExpandBinaryLink,"Expanded binary"), lookat_all_pids(Port,Rest1); _ -> ok end; %%! No longer needed - all atoms are shown on one page!! %% ".250atoms" -> %% Html1 = contents(Port,"atoms"), %% NextLink1 = next_link(Html1), %% "Atoms" = title(Html1), %% Html2 = contents(Port,NextLink1), %% NextLink2 = next_link(Html2), %% "Atoms" = title(Html2), %% Html3 = contents(Port,NextLink2), %% "" = next_link(Html3), %% "Atoms" = title(Html3); _ -> ok end, case filename:basename(File) of "r10b_dump." ++ _ -> lookat_all_pids(Port,contents(Port,"processes")); "r11b_dump." ++ _ -> lookat_all_pids(Port,contents(Port,"processes")); _ -> ok end, ok. lookat_all_pids(Port,Pids) -> case get_first_process(Pids) of {Pid,Rest} -> ProcDetails = contents(Port,"proc_details?pid=" ++ Pid), ProcTitle = "Process " ++ Pid, ProcTitle = title(ProcDetails), title(Port,"ets_tables?pid="++Pid,"ETS Tables for Process "++Pid), title(Port,"timers?pid="++Pid,"Timers for Process "++Pid), MemoryLinks = expand_memory_links(ProcDetails), lists:foreach( fun(Link) -> Cont = contents(Port,Link), true = lists:member(title(Cont), ["MsgQueue", "Dictionary", "StackDump"]) end, MemoryLinks), lookat_all_pids(Port,Rest); false -> ok end. get_first_process([]) -> false; get_first_process(Html) -> case Html of "