1%%% Test suite for the recon module. Because many of the tests in 2%%% here depend on runtime properties and this is *not* transparent, 3%%% the tests are rather weak and more or less check for interface 4%%% conformance and obvious changes more than anything. 5-module(recon_SUITE). 6-include_lib("common_test/include/ct.hrl"). 7-include_lib("eunit/include/eunit.hrl"). 8-compile(export_all). 9 10-ifdef(OTP_RELEASE). 11-define(FILES_IMPL, nif). 12-define(ERROR_LOGGER_MATCH(_), ). 13-define(REDUCTIONS_MATCH(X), X). 14-else. 15-define(FILES_IMPL, port). 16-define(ERROR_LOGGER_MATCH(X), X,). 17-define(REDUCTIONS_MATCH(_), []). 18-endif. 19 20all() -> [{group,info}, proc_count, proc_window, bin_leak, 21 node_stats_list, get_state, source, tcp, udp, files, port_types, 22 inet_count, inet_window, binary_memory, scheduler_usage]. 23 24groups() -> [{info, [], [info3, info4, info1, info2, 25 port_info1, port_info2]}]. 26 27init_per_group(info, Config) -> 28 Self = self(), 29 Pid = spawn(fun() -> 30 {ok, TCP} = gen_tcp:listen(0, []), 31 {ok, UDP} = gen_udp:open(0), 32 Self ! {TCP, UDP}, 33 timer:sleep(infinity) 34 end), 35 receive 36 {TCP, UDP} -> [{pid, Pid}, {tcp, TCP}, {udp, UDP} | Config] 37 end. 38 39end_per_group(info, Config) -> 40 exit(?config(pid, Config), kill). 41 42init_per_testcase(files, Config) -> 43 case ?FILES_IMPL of 44 nif -> {skip, "files can no longer be listed in OTP-21 and above"}; 45 port -> Config 46 end; 47init_per_testcase(_, Config) -> 48 Config. 49 50end_per_testcase(_, Config) -> 51 Config. 52 53%%%%%%%%%%%%% 54%%% TESTS %%% 55%%%%%%%%%%%%% 56 57info3(Config) -> 58 Pid = ?config(pid, Config), 59 {A,B,C} = pid_to_triple(Pid), 60 Info1 = recon:info(Pid), 61 Info2 = recon:info(A,B,C), 62 %% Reduction count is unreliable 63 ?assertMatch(?REDUCTIONS_MATCH( 64 [{work, [{reductions,_}]}, {work, [{reductions,_}]}] 65 ), (Info1 -- Info2) ++ (Info2 -- Info1)). 66 67info4(Config) -> 68 Pid = ?config(pid, Config), 69 Keys = [meta, signals, location, memory_used, 70 links, monitors, messages, 71 [links, monitors, messages]], 72 {A,B,C} = pid_to_triple(Pid), 73 lists:map(fun(Key) -> 74 Info = recon:info(Pid, Key), 75 Info = recon:info(A,B,C, Key) 76 end, 77 Keys). 78 79info1(Config) -> 80 Pid = ?config(pid, Config), 81 Categories = [{meta, [registered_name, dictionary, group_leader, status]}, 82 {signals, [links, monitors, monitored_by, trap_exit]}, 83 {location, [initial_call, current_stacktrace]}, 84 {memory_used, [memory, message_queue_len, heap_size, 85 total_heap_size, garbage_collection]}, 86 {work, [reductions]}], 87 [] = lists:flatten( 88 [K || {Cat,List} <- Categories, 89 K <- List, 90 Info <- [recon:info(Pid)], 91 undefined == proplists:get_value(K, proplists:get_value(Cat,Info)) 92 ]), 93 register(info1, Pid), 94 Res1 = recon:info(info1), 95 Res2 = recon:info(whereis(info1)), 96 Res3 = recon:info(pid_to_triple(whereis(info1))), 97 Res4 = recon:info(lists:flatten(io_lib:format("~p",[Pid]))), 98 unregister(info1), 99 L = lists:usort(Res1 ++ Res2 ++ Res3 ++ Res4), 100 ?assertMatch(?REDUCTIONS_MATCH([{work,[{reductions,_}]}, 101 {work,[{reductions,_}]}, 102 {work,[{reductions,_}]}]), L -- Res1), 103 ?assertMatch(?REDUCTIONS_MATCH([{work,[{reductions,_}]}, 104 {work,[{reductions,_}]}, 105 {work,[{reductions,_}]}]), L -- Res2), 106 ?assertMatch(?REDUCTIONS_MATCH([{work,[{reductions,_}]}, 107 {work,[{reductions,_}]}, 108 {work,[{reductions,_}]}]), L -- Res3), 109 ?assertMatch(?REDUCTIONS_MATCH([{work,[{reductions,_}]}, 110 {work,[{reductions,_}]}, 111 {work,[{reductions,_}]}]), L -- Res4), 112 ok. 113 114info2(Config) -> 115 Pid = ?config(pid, Config), 116 Categories = [{meta, [registered_name, dictionary, group_leader, status]}, 117 {signals, [links, monitors, monitored_by, trap_exit]}, 118 {location, [initial_call, current_stacktrace]}, 119 {memory_used, [memory, message_queue_len, heap_size, 120 total_heap_size, garbage_collection]}, 121 {work, [reductions]}], 122 %% registered_name is special -- only returns 123 %% [] when passed through by info/2. Because we pass terms through 124 %% according to the docs, we have to respect that 125 [] = recon:info(Pid, registered_name), 126 %% Register to get the expected tuple 127 register(info2, Pid), 128 Keys = lists:flatten([K || {_,L} <- Categories, K <- L]), 129 %% check that individual category call works for all terms 130 [] = lists:flatten( 131 [K || {Cat, List} <- Categories, 132 K <- List, 133 {GetCat,Info} <- [recon:info(Pid, Cat)], 134 Cat =:= GetCat, 135 undefined =:= proplists:get_value(K, Info)] 136 ), 137 %% Can get a list of arguments 138 true = lists:sort(Keys) 139 =:= 140 lists:sort(proplists:get_keys(recon:info(Pid, Keys))), 141 true = length(Keys) 142 =:= 143 length([1 || K1 <- Keys, {K2,_} <- [recon:info(Pid, K1)], 144 K1 == K2]), 145 unregister(info2). 146 147proc_count(_Config) -> 148 Res = recon:proc_count(memory, 10), 149 true = proc_attrs(Res), 150 %% greatest to smallest 151 true = lists:usort(fun({P1,V1,_},{P2,V2,_}) -> {V1,P1} >= {V2,P2} end, 152 Res) =:= Res, 153 10 = length(Res), 154 15 = length(recon:proc_count(reductions, 15)). 155 156proc_window(_Config) -> 157 Res = recon:proc_window(reductions, 10, 100), 158 true = proc_attrs(Res), 159 %% we can't check order easily because stuff doesn't move 160 %% fast enough on a test node to show up here 161 10 = length(Res), 162 15 = length(recon:proc_window(memory, 15, 100)). 163 164bin_leak(_Config) -> 165 Res = recon:bin_leak(5), 166 5 = length(Res), 167 true = proc_attrs(Res), 168 true = lists:any(fun({_,Val,_})-> Val =/= 0 end, Res), 169 %% all results are =< 0, and ordered from smallest to biggest 170 lists:foldl(fun({_,Current,_}, Next) when Current =< Next -> Current end, 171 0, 172 lists:reverse(Res)). 173 174%% This function implicitly tests node_stats/4 175node_stats_list(_Config) -> 176 Res = recon:node_stats_list(2,100), 177 2 = length([1 || {[{process_count,_}, 178 {run_queue,_}, 179 ?ERROR_LOGGER_MATCH({error_logger_queue_len,_}) 180 {memory_total,_}, 181 {memory_procs,_}, 182 {memory_atoms,_}, 183 {memory_bin,_}, 184 {memory_ets,_}|_], 185 [{bytes_in,_}, 186 {bytes_out,_}, 187 {gc_count,_}, 188 {gc_words_reclaimed,_}, 189 {reductions,_}, 190 {scheduler_usage,[_|_]}|_]} <- Res]). 191 192get_state(_Config) -> 193 Res = recon:get_state(kernel_sup), 194 Res = recon:get_state(whereis(kernel_sup)), 195 Res = recon:get_state(pid_to_triple(whereis(kernel_sup))), 196 state = element(1,Res). 197 198%% Skip on remote-loading, too hard 199 200source(_Config) -> 201 Pat = <<"find this sentence in this file's source">>, 202 {_,_} = binary:match(iolist_to_binary(recon:source(?MODULE)), Pat). 203 204tcp(_Config) -> 205 {ok, Listen} = gen_tcp:listen(0, []), 206 true = lists:member(Listen, recon:tcp()). 207 208udp(_Config) -> 209 {ok, Port} = gen_udp:open(0), 210 true = lists:member(Port, recon:udp()). 211 212%% SCTP not supported everywhere, skipped. 213 214files(Config) -> 215 Len = length(recon:files()), 216 {ok, _IoDevice} = file:open(filename:join(?config(priv_dir, Config), "a"), 217 [write]), 218 true = Len + 1 =:= length(recon:files()). 219 220port_types(_Config) -> 221 lists:all(fun({[_|_],N}) when is_integer(N), N > 0 -> true end, 222 recon:port_types()). 223 224inet_count(_Config) -> 225 [gen_tcp:listen(0,[]) || _ <- lists:seq(1,100)], 226 Res = recon:inet_count(oct, 10), 227 %% all results are =< 0, and ordered from biggest to smaller 228 lists:foldl(fun({_,Current,_}, Next) when Current >= Next -> Current end, 229 0, 230 lists:reverse(Res)), 231 true = inet_attrs(Res), 232 %% greatest to smallest 233 10 = length(Res), 234 15 = length(recon:inet_count(cnt, 15)). 235 236inet_window(_Config) -> 237 [gen_tcp:listen(0,[]) || _ <- lists:seq(1,100)], 238 Res = recon:inet_window(oct, 10, 100), 239 %% all results are =< 0, and ordered from biggest to smaller 240 lists:foldl(fun({_,Current,_}, Next) when Current >= Next -> Current end, 241 0, 242 lists:reverse(Res)), 243 true = inet_attrs(Res), 244 %% greatest to smallest 245 10 = length(Res), 246 15 = length(recon:inet_window(cnt, 15, 100)). 247 248%% skip RPC 249 250port_info1(Config) -> 251 TCP = ?config(tcp, Config), 252 UDP = ?config(tcp, Config), 253 TCPInfo = recon:port_info(TCP), 254 UDPInfo = recon:port_info(UDP), 255 %% type is the only specific value supported for now 256 Cats = lists:sort([meta, signals, io, memory_used, type]), 257 %% Too many options for inet stuff. 258 Cats = lists:sort(proplists:get_keys(TCPInfo)), 259 Cats = lists:sort(proplists:get_keys(UDPInfo)). 260 261port_info2(Config) -> 262 TCP = ?config(tcp, Config), 263 UDP = ?config(tcp, Config), 264 %% Not testing the whole set, but heeeh. Good enough. 265 {io, [{input,_},{output,_}]} = recon:port_info(TCP, io), 266 {io, [{input,_},{output,_}]} = recon:port_info(UDP, io). 267 268%% binary_memory is a created attribute that counts the amount 269%% of memory held by refc binaries, usable in info/2-4 and 270%% in proc_count/proc_window. 271binary_memory(_Config) -> 272 %% we just don't want them to crash like it happens with 273 %% non-existing attributes. 274 ?assertError(_, recon:proc_count(fake_attribute, 10)), 275 ?assertError(_, recon:proc_window(fake_attribute, 10, 100)), 276 recon:proc_count(binary_memory, 10), 277 recon:proc_window(binary_memory, 10, 100), 278 %% And now for info, it should work in lists, which can contain 279 %% duplicates, or in single element calls. 280 %% Note: we allocate the binary before spawning the process but 281 %% use it in a closure to avoid race conditions on allocation for 282 %% the test. 283 Bin = <<1:999999>>, 284 Pid1 = spawn_link(fun() -> timer:sleep(100000) end), 285 Pid2 = spawn_link(fun() -> timer:sleep(100000), Bin end), 286 {binary_memory, 0} = recon:info(Pid1, binary_memory), 287 {binary_memory, N} = recon:info(Pid2, binary_memory), 288 true = N > 0, 289 Res1 = recon:info(Pid1, [binary, binary_memory, binary]), 290 Res2 = recon:info(Pid2, [binary_memory, binary, binary_memory]), 291 %% we expect everything to look as a single call to process_info/2 292 [{binary,X}, {binary_memory,_}, {binary,X}] = Res1, 293 [{binary_memory,Y}, {binary,_}, {binary_memory,Y}] = Res2. 294 295%% Just check that we get many schedulers and that the usage values are all 296%% between 0 and 1 inclusively. We don't care for edge cases like a 297%% scheduler disappearing halfway through a run. 298scheduler_usage(_Config) -> 299 List = recon:scheduler_usage(100), 300 ?assertEqual(length(List), length( 301 [1 || {Id,Rate} <- List, 302 is_integer(Id), Id > 0, 303 Rate >= 0, Rate =< 1]) 304 ). 305 306%%%%%%%%%%%%%%% 307%%% HELPERS %%% 308%%%%%%%%%%%%%%% 309 310pid_to_triple(Pid) when is_pid(Pid) -> 311 "<0."++Rest = lists:flatten(io_lib:format("~p",[Pid])), 312 {B,C} = lists:foldl(fun($>, Acc) -> Acc; 313 ($., B) -> {B,0}; 314 (N, {B,C}) -> {B,(C*10)+(N-$0)}; 315 (N, B) -> (B*10)+(N-$0) 316 end, 317 0, 318 Rest), 319 {0,B,C}. 320 321proc_attrs(L) -> 322 lists:all(fun({Pid,_Val,List}) -> is_pid(Pid) andalso is_list(List) end, 323 L). 324 325inet_attrs(L) -> 326 lists:all(fun({Port,_Val,List}) -> is_port(Port) andalso is_list(List) end, 327 L). 328