1: %%
    2: %% %CopyrightBegin%
    3: %%
    4: %% Copyright Ericsson AB 2005-2011. All Rights Reserved.
    5: %%
    6: %% The contents of this file are subject to the Erlang Public License,
    7: %% Version 1.1, (the "License"); you may not use this file except in
    8: %% compliance with the License. You should have received a copy of the
    9: %% Erlang Public License along with this software. If not, it can be
   10: %% retrieved online at http://www.erlang.org/.
   11: %%
   12: %% Software distributed under the License is distributed on an "AS IS"
   13: %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
   14: %% the License for the specific language governing rights and limitations
   15: %% under the License.
   16: %%
   17: %% %CopyrightEnd%
   18: %%
   19: 
   20: 
   21: %%%-------------------------------------------------------------------
   22: %%% File    : system_info_SUITE.erl
   23: %%% Author  : Rickard Green <rickard.s.green@ericsson.com>
   24: %%% Description : Misc tests of erlang:system_info/1
   25: %%%
   26: %%% Created : 15 Jul 2005 by Rickard Green <rickard.s.green@ericsson.com>
   27: %%%-------------------------------------------------------------------
   28: -module(system_info_SUITE).
   29: -author('rickard.s.green@ericsson.com').
   30: 
   31: %-define(line_trace, 1).
   32: 
   33: -include_lib("test_server/include/test_server.hrl").
   34: 
   35: %-compile(export_all).
   36: -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, 
   37: 	 init_per_group/2,end_per_group/2, 
   38: 	 init_per_testcase/2, end_per_testcase/2]).
   39: 
   40: -export([process_count/1, system_version/1, misc_smoke_tests/1, heap_size/1, wordsize/1, memory/1,
   41:          ets_limit/1]).
   42: 
   43: -define(DEFAULT_TIMEOUT, ?t:minutes(2)).
   44: 
   45: suite() -> [{ct_hooks,[ts_install_cth]}].
   46: 
   47: all() -> 
   48:     [process_count, system_version, misc_smoke_tests,
   49:      heap_size, wordsize, memory, ets_limit].
   50: 
   51: groups() -> 
   52:     [].
   53: 
   54: init_per_suite(Config) ->
   55:     Config.
   56: 
   57: end_per_suite(_Config) ->
   58:     ok.
   59: 
   60: init_per_group(_GroupName, Config) ->
   61:     Config.
   62: 
   63: end_per_group(_GroupName, Config) ->
   64:     Config.
   65: 
   66: 
   67: init_per_testcase(_Case, Config) when is_list(Config) ->
   68:     Dog = ?t:timetrap(?DEFAULT_TIMEOUT),
   69:     [{watchdog, Dog}|Config].
   70: 
   71: end_per_testcase(_Case, Config) when is_list(Config) ->
   72:     Dog = ?config(watchdog, Config),
   73:     ?t:timetrap_cancel(Dog),
   74:     ok.
   75: 
   76: %%%
   77: %%% The test cases -------------------------------------------------------------
   78: %%%
   79: 
   80: process_count(doc) -> [];
   81: process_count(suite) -> [];
   82: process_count(Config) when is_list(Config) ->
   83:     case catch erlang:system_info(modified_timing_level) of
   84: 	Level when is_integer(Level) ->
   85: 	    {skipped,
   86: 	     "Modified timing (level " ++ integer_to_list(Level)
   87: 	     ++ ") is enabled. spawn() is too slow for this "
   88: 	     " test when modified timing is enabled."};
   89: 	_ ->
   90: 	    process_count_test()
   91:     end.
   92: 
   93: process_count_test() ->
   94:     ?line OldPrio = process_flag(priority, max),
   95:     ?line check_procs(10),
   96:     ?line check_procs(11234),
   97:     ?line check_procs(57),
   98:     ?line check_procs(1030),
   99:     ?line check_procs(687),
  100:     ?line check_procs(7923),
  101:     ?line check_procs(5302),
  102:     ?line check_procs(12456),
  103:     ?line check_procs(14),
  104:     ?line check_procs(1125),
  105:     ?line check_procs(236),
  106:     ?line check_procs(125),
  107:     ?line check_procs(2346),
  108:     ?line process_flag(priority, OldPrio),
  109:     ?line ok.
  110:     
  111: 
  112: check_procs(N) ->
  113:     ?line CP = length(processes()),
  114:     ?line Procs = start_procs(N),
  115:     ?line check_pc(CP+N),
  116:     ?line stop_procs(Procs),
  117:     ?line check_pc(CP).
  118: 
  119: check_pc(E) ->
  120:     ?line P = length(processes()),
  121:     ?line SI = erlang:system_info(process_count),
  122:     ?line ?t:format("E=~p; P=~p; SI=~p~n", [E, P, SI]),
  123:     ?line E = P,
  124:     ?line P = SI.
  125: 
  126: start_procs(N) ->
  127:     lists:map(fun (_) ->
  128: 		      P = spawn_opt(fun () ->
  129: 					    receive after infinity -> bye end
  130: 				    end,
  131: 				    [{priority, max}]),
  132: 		      {P, erlang:monitor(process, P)}
  133: 	      end,
  134: 	      lists:seq(1, N)).
  135: 
  136: stop_procs(PMs) ->
  137:     lists:foreach(fun ({P, _}) ->
  138: 			  exit(P, boom)
  139: 		  end, PMs),
  140:     lists:foreach(fun ({P, M}) ->
  141: 			  receive {'DOWN', M, process, P, boom} -> ok end
  142: 		  end, PMs).
  143: 
  144: 
  145: system_version(doc) -> [];
  146: system_version(suite) -> [];
  147: system_version(Config) when is_list(Config) ->
  148:     ?line {comment, erlang:system_info(system_version)}.
  149: 
  150: misc_smoke_tests(doc) -> [];
  151: misc_smoke_tests(suite) -> [];
  152: misc_smoke_tests(Config) when is_list(Config) ->
  153:     ?line true = is_binary(erlang:system_info(info)),
  154:     ?line true = is_binary(erlang:system_info(procs)),
  155:     ?line true = is_binary(erlang:system_info(loaded)),
  156:     ?line true = is_binary(erlang:system_info(dist)),
  157:     ?line ok = try erlang:system_info({cpu_topology,erts_get_cpu_topology_error_case}), fail catch error:badarg -> ok end,
  158:     ?line ok.
  159:     
  160: 
  161: heap_size(doc) -> [];
  162: heap_size(suite) -> [];
  163: heap_size(Config) when is_list(Config) ->
  164:    ?line {min_bin_vheap_size, VHmin} = erlang:system_info(min_bin_vheap_size),
  165:    ?line {min_heap_size, Hmin} =  erlang:system_info(min_heap_size),
  166:    ?line GCinf =  erlang:system_info(garbage_collection),
  167:    ?line VHmin = proplists:get_value(min_bin_vheap_size, GCinf),
  168:    ?line Hmin  = proplists:get_value(min_heap_size, GCinf),
  169:    ok.
  170: 
  171: wordsize(suite) ->
  172:     [];
  173: wordsize(doc) ->
  174:     ["Tests the various wordsize variants"];
  175: wordsize(Config) when is_list(Config) ->
  176:     ?line A = erlang:system_info(wordsize),
  177:     ?line true = is_integer(A),
  178:     ?line A = erlang:system_info({wordsize,internal}),
  179:     ?line B = erlang:system_info({wordsize,external}),
  180:     ?line true = A =< B,
  181:     case {B,A} of
  182: 	{4,4} ->
  183: 	    {comment, "True 32-bit emulator"};
  184: 	{8,8} ->
  185: 	    {comment, "True 64-bit emulator"};
  186: 	{8,4} ->
  187: 	    {comment, "Halfword 64-bit emulator"};
  188: 	Other ->
  189: 	    exit({unexpected_wordsizes,Other})
  190:     end.
  191: 
  192: memory(doc) -> ["Verify that erlang:memory/0 and memory results in crashdump produce are similar"];
  193: memory(Config) when is_list(Config) ->
  194:     %%
  195:     %% Verify that erlang:memory/0 and memory results in
  196:     %% crashdump produce are similar.
  197:     %%
  198:     %% erlang:memory/0 requests information from each scheduler
  199:     %% thread and puts the information together in erlang code
  200:     %% (erlang.erl).
  201:     %%
  202:     %% When a crash dump is written we cannot use the
  203:     %% erlang:memory/0 implementation. The crashdump implementation
  204:     %% is a pure C implementation inspecting all allocator instances
  205:     %% after the system has been blocked (erts_memory() in erl_alloc.c).
  206:     %%
  207:     %% Since we got two implementations, modifications can easily
  208:     %% cause them to produce different results.
  209:     %%
  210:     %% erts_debug:get_internal_state(memory) blocks the system and
  211:     %% execute the same code as the crash dump writing uses.
  212:     %%
  213: 
  214:     erts_debug:set_internal_state(available_internal_state, true),
  215:     %% Use a large heap size on the controling process in
  216:     %% order to avoid changes in its heap size during
  217:     %% comparisons.
  218:     MinHeapSize = process_flag(min_heap_size, 1024*1024), 
  219:     Prio = process_flag(priority, max),
  220:     try
  221: 	erlang:memory(), %% first call will init stat atoms
  222: 	garbage_collect(), %% blow up heap
  223: 	memory_test(Config)
  224:     catch
  225: 	error:notsup -> {skipped, "erlang:memory() not supported"}
  226:     after
  227: 	process_flag(min_heap_size, MinHeapSize),
  228: 	process_flag(priority, Prio),
  229: 	catch erts_debug:set_internal_state(available_internal_state, false)
  230:     end.
  231: 
  232: memory_test(_Config) ->
  233: 
  234:     MWs = spawn_mem_workers(),
  235: 
  236:     DPs = mem_workers_call(MWs,
  237: 			   fun () ->
  238: 				   mapn(fun (_) ->
  239: 						spawn(fun () ->
  240: 							      receive
  241: 							      after infinity ->
  242: 								      ok
  243: 							      end
  244: 						      end)
  245: 					end,
  246: 					1000 div erlang:system_info(schedulers_online))
  247: 			   end,
  248: 			   []),
  249:     cmp_memory(MWs, "spawn procs"),
  250: 
  251:     Ps = lists:flatten(DPs),
  252: 
  253:     mem_workers_call(MWs, 
  254: 		     fun () ->
  255: 			     lists:foreach(fun (P) -> link(P) end, Ps)
  256: 		     end,
  257: 		     []),
  258:     cmp_memory(MWs, "link procs"),
  259:     mem_workers_call(MWs,
  260: 		     fun () ->
  261: 			     lists:foreach(fun (P) -> unlink(P) end, Ps)
  262: 		     end,
  263: 		     []),
  264:     cmp_memory(MWs, "unlink procs"),
  265: 
  266:     DMs = mem_workers_call(MWs,
  267: 			   fun () ->
  268: 				   lists:map(fun (P) ->
  269: 						     monitor(process, P)
  270: 					     end, Ps)
  271: 			   end,
  272: 			   []),
  273:     cmp_memory(MWs, "monitor procs"),
  274:     Ms = lists:flatten(DMs),
  275:     mem_workers_call(MWs,
  276: 		     fun () ->
  277: 			     lists:foreach(fun (M) ->
  278: 						   demonitor(M)
  279: 					   end, Ms)
  280: 		     end,
  281: 		     []),
  282:     cmp_memory(MWs, "demonitor procs"),
  283: 
  284:     mem_workers_call(MWs,
  285: 		     fun () ->
  286: 			     lists:foreach(fun (P) ->
  287: 						   P ! {a, "message", make_ref()}
  288: 					   end, Ps)
  289: 		     end,
  290: 		     []),
  291:     cmp_memory(MWs, "message procs"),
  292: 
  293:     mem_workers_call(MWs,
  294: 		     fun () ->
  295: 			     Mons = lists:map(fun (P) ->
  296: 						      exit(P, kill),
  297: 						      monitor(process, P)
  298: 					      end,
  299: 					      Ps),
  300: 			     lists:foreach(fun (Mon) ->
  301: 						   receive
  302: 						       {'DOWN', Mon, _, _, _} -> ok
  303: 						   end
  304: 					   end,
  305: 					   Mons)
  306: 		     end, []),
  307:     cmp_memory(MWs, "kill procs"),
  308: 
  309:     mem_workers_call(MWs,
  310: 		     fun () ->
  311: 			     put(binary_data,
  312: 				 mapn(fun (_) -> list_to_binary(lists:duplicate(256,$?)) end, 100))
  313: 		     end,
  314: 		     []),
  315: 
  316:     cmp_memory(MWs, "store binary data"),
  317: 
  318:     mem_workers_call(MWs,
  319: 		     fun () ->
  320: 			     put(binary_data, false),
  321: 			     garbage_collect()
  322: 		     end,
  323: 		     []),
  324:     cmp_memory(MWs, "release binary data"),
  325: 
  326:     mem_workers_call(MWs,
  327: 		     fun () ->
  328: 			     list_to_atom("an ugly atom "++integer_to_list(erlang:system_info(scheduler_id))),
  329: 			     list_to_atom("another ugly atom "++integer_to_list(erlang:system_info(scheduler_id))),
  330: 			     list_to_atom("yet another ugly atom "++integer_to_list(erlang:system_info(scheduler_id)))
  331: 		     end,
  332: 		     []),
  333:     cmp_memory(MWs, "new atoms"),
  334: 
  335: 
  336:     mem_workers_call(MWs,
  337: 		     fun () ->
  338: 			     T = ets:new(?MODULE, []),
  339: 			     ets:insert(T, {gurka, lists:seq(1,10000)}),
  340: 			     ets:insert(T, {banan, lists:seq(1,1024)}),
  341: 			     ets:insert(T, {appelsin, make_ref()}),
  342: 			     put(ets_id, T)
  343: 		     end,
  344: 		     []),
  345:     cmp_memory(MWs, "store ets data"),
  346: 
  347:     mem_workers_call(MWs,
  348: 		     fun () ->
  349: 			     ets:delete(get(ets_id)),
  350: 			     put(ets_id, false)
  351: 		     end,
  352: 		     []),
  353:     cmp_memory(MWs, "remove ets data"),
  354: 
  355:     lists:foreach(fun (MW) ->
  356: 			  unlink(MW),
  357: 			  Mon = monitor(process, MW),
  358: 			  exit(MW, kill),
  359: 			  receive
  360: 			      {'DOWN', Mon, _, _, _} -> ok
  361: 			  end
  362: 		  end,
  363: 		  MWs),
  364:     ok.
  365: 
  366: mem_worker() ->
  367:     receive
  368: 	{call, From, Fun, Args} ->
  369: 	    From ! {reply, self(), apply(Fun, Args)},
  370: 	    mem_worker();
  371: 	{cast, _From, Fun, Args} ->
  372: 	    apply(Fun, Args),
  373: 	    mem_worker()
  374:     end.
  375: 
  376: mem_workers_call(MWs, Fun, Args) ->
  377:     lists:foreach(fun (MW) ->
  378: 			  MW ! {call, self(), Fun, Args}
  379: 		  end,
  380: 		  MWs),
  381:     lists:map(fun (MW) ->
  382: 		      receive
  383: 			  {reply, MW, Res} ->
  384: 			      Res
  385: 		      end
  386: 	      end,
  387: 	      MWs).
  388: 
  389: mem_workers_cast(MWs, Fun, Args) ->
  390:     lists:foreach(fun (MW) ->
  391: 			  MW ! {cast, self(), Fun, Args}
  392: 		  end,
  393: 		  MWs).
  394: 
  395: spawn_mem_workers() ->
  396:     spawn_mem_workers(erlang:system_info(schedulers_online)).
  397: 
  398: spawn_mem_workers(0) ->
  399:     [];
  400: spawn_mem_workers(N) ->
  401:     [spawn_opt(fun () -> mem_worker() end,
  402: 	       [{scheduler, N rem erlang:system_info(schedulers_online) + 1},
  403: 		link]) | spawn_mem_workers(N-1)].
  404: 
  405: 
  406: 
  407: mem_get(X, Mem) ->
  408:     case lists:keyfind(X, 1, Mem) of
  409: 	{X, Val} -> Val;
  410: 	false -> false
  411:     end.
  412: 
  413: cmp_memory(What, Mem1, Mem2, 1) ->
  414:     R1 = mem_get(What, Mem1),
  415:     R2 = mem_get(What, Mem2),
  416:     true = R1 == R2;
  417: cmp_memory(What, Mem1, Mem2, RelDiff) ->
  418:     %% We allow RealDiff diff
  419:     R1 = mem_get(What, Mem1),
  420:     R2 = mem_get(What, Mem2),
  421:     case R1 == R2 of
  422: 	true ->
  423: 	    ok;
  424: 	false ->
  425: 	    case R1 > R2 of
  426: 		true ->
  427: 		    true = R2*RelDiff > R1;
  428: 		false ->
  429: 		    true = R1*RelDiff > R2
  430: 	    end
  431:     end.
  432: 
  433: pos_int(Val) when Val >= 0 ->
  434:     Val;
  435: pos_int(Val) ->
  436:     exit({not_pos_int, Val}).
  437: 
  438: check_sane_memory(Mem) ->
  439:     Tot = pos_int(mem_get(total, Mem)),
  440:     Proc = pos_int(mem_get(processes, Mem)),
  441:     ProcUsed = pos_int(mem_get(processes_used, Mem)),
  442:     Sys = pos_int(mem_get(system, Mem)),
  443:     Atom = pos_int(mem_get(atom, Mem)),
  444:     AtomUsed = pos_int(mem_get(atom_used, Mem)),
  445:     Bin = pos_int(mem_get(binary, Mem)),
  446:     Code = pos_int(mem_get(code, Mem)),
  447:     Ets = pos_int(mem_get(ets, Mem)),
  448: 
  449:     Tot = Proc + Sys,
  450:     true = Sys > Atom + Bin + Code + Ets,
  451:     true = Proc >= ProcUsed,
  452:     true = Atom >= AtomUsed,
  453: 
  454:     case mem_get(maximum, Mem) of
  455: 	false -> ok;
  456: 	Max -> true = pos_int(Max) >= Tot
  457:     end,
  458:     ok.
  459: 
  460: cmp_memory(MWs, Str) ->
  461:     erlang:display(Str),
  462:     lists:foreach(fun (MW) -> garbage_collect(MW) end, MWs),
  463:     garbage_collect(),
  464:     erts_debug:set_internal_state(wait, deallocations),
  465: 
  466:     EDM = erts_debug:get_internal_state(memory),
  467:     EM = erlang:memory(),
  468: 
  469:     io:format("~s:~n"
  470: 	      "erlang:memory() = ~p~n"
  471: 	      "crash dump memory = ~p~n",
  472: 	      [Str, EM, EDM]),
  473: 
  474:     ?line check_sane_memory(EM),
  475:     ?line check_sane_memory(EDM),
  476: 
  477:     %% We expect these to always give us exactly the same result
  478: 
  479:     ?line cmp_memory(atom, EM, EDM, 1),
  480:     ?line cmp_memory(atom_used, EM, EDM, 1),
  481:     ?line cmp_memory(binary, EM, EDM, 1),
  482:     ?line cmp_memory(code, EM, EDM, 1),
  483:     ?line cmp_memory(ets, EM, EDM, 1),
  484: 
  485:     %% Total, processes, processes_used, and system will seldom
  486:     %% give us exactly the same result since the two readings
  487:     %% aren't taken atomically.
  488: 
  489:     ?line cmp_memory(total, EM, EDM, 1.05),
  490:     ?line cmp_memory(processes, EM, EDM, 1.05),
  491:     ?line cmp_memory(processes_used, EM, EDM, 1.05),
  492:     ?line cmp_memory(system, EM, EDM, 1.05),
  493: 
  494:     ok.
  495:     
  496: mapn(_Fun, 0) ->
  497:     [];
  498: mapn(Fun, N) ->
  499:     [Fun(N) | mapn(Fun, N-1)].
  500: 
  501: ets_limit(doc) ->
  502:     "Verify system_info(ets_limit) reflects max ETS table settings.";
  503: ets_limit(suite) -> [];
  504: ets_limit(Config0) when is_list(Config0) ->
  505:     Config = [{testcase,ets_limit}|Config0],
  506:     true = is_integer(get_ets_limit(Config)),
  507:     12345 = get_ets_limit(Config, 12345),
  508:     ok.
  509: 
  510: get_ets_limit(Config) ->
  511:     get_ets_limit(Config, 0).
  512: get_ets_limit(Config, EtsMax) ->
  513:     Envs = case EtsMax of
  514:                0 -> [];
  515:                _ -> [{"ERL_MAX_ETS_TABLES", integer_to_list(EtsMax)}]
  516:            end,
  517:     {ok, Node} = start_node(Config, Envs),
  518:     Me = self(),
  519:     Ref = make_ref(),
  520:     spawn_link(Node,
  521:                fun() ->
  522:                        Res = erlang:system_info(ets_limit),
  523:                        unlink(Me),
  524:                        Me ! {Ref, Res}
  525:                end),
  526:     receive
  527:         {Ref, Res} ->
  528:             Res
  529:     end,
  530:     stop_node(Node),
  531:     Res.
  532: 
  533: start_node(Config, Envs) when is_list(Config) ->
  534:     Pa = filename:dirname(code:which(?MODULE)),
  535:     {A, B, C} = now(),
  536:     Name = list_to_atom(atom_to_list(?MODULE)
  537:                         ++ "-"
  538:                         ++ atom_to_list(?config(testcase, Config))
  539:                         ++ "-"
  540:                         ++ integer_to_list(A)
  541:                         ++ "-"
  542:                         ++ integer_to_list(B)
  543:                         ++ "-"
  544:                         ++ integer_to_list(C)),
  545:     ?t:start_node(Name, peer, [{args, "-pa "++Pa}, {env, Envs}]).
  546: 
  547: stop_node(Node) ->
  548:     ?t:stop_node(Node).