1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2002-2020. 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
21-module(snmp_test_lib).
22
23-include_lib("kernel/include/file.hrl").
24
25
26-export([tc_try/2, tc_try/3,
27         tc_try/4, tc_try/5]).
28-export([proxy_call/3]).
29-export([hostname/0, hostname/1, localhost/0, localhost/1, os_type/0, sz/1,
30	 display_suite_info/1]).
31-export([non_pc_tc_maybe_skip/4,
32         os_based_skip/1,
33         has_support_ipv6/0
34        ]).
35-export([init_per_suite/1, end_per_suite/1,
36         init_suite_top_dir/2, init_group_top_dir/2, init_testcase_top_dir/2,
37	 fix_data_dir/1,
38	 lookup/2,
39	 replace_config/3, set_config/3, get_config/2, get_config/3]).
40-export([fail/3, skip/3]).
41-export([hours/1, minutes/1, seconds/1, sleep/1]).
42-export([flush_mqueue/0, mqueue/0, mqueue/1, trap_exit/0, trap_exit/1]).
43-export([ping/1, local_nodes/0, nodes_on/1]).
44-export([start_node/2, stop_node/1]).
45-export([is_app_running/1,
46	 is_crypto_running/0, is_mnesia_running/0, is_snmp_running/0,
47         ensure_not_running/3]).
48-export([crypto_start/0, crypto_support/0]).
49-export([watchdog/3, watchdog_start/1, watchdog_start/2, watchdog_stop/1]).
50-export([del_dir/1]).
51-export([f/2, formated_timestamp/0]).
52-export([p/2, print1/2, print2/2, print/5]).
53-export([eprint/2, wprint/2, nprint/2, iprint/2]).
54-export([explicit_inet_backend/0, test_inet_backends/0]).
55-export([which_host_ip/2]).
56
57
58-define(SKIP(R), skip(R, ?MODULE, ?LINE)).
59
60
61%% ----------------------------------------------------------------------
62%% Run test-case
63%%
64
65%% *** tc_try/2,3,4,5 ***
66%% Case:   Basically the test case name
67%% TCCond: A fun that is evaluated before the actual test case
68%%         The point of this is that it can performs checks to
69%%         see if we shall run the test case at all.
70%%         For instance, the test case may only work in specific
71%%         conditions.
72%% Pre:    A fun that is nominally part of the test case
73%%         but is an initiation that must be "undone". This is
74%%         done by the Post fun (regardless if the TC is successfull
75%%         or not). Example: Starts a couple of nodes,
76%% TC:     The test case fun
77%% Post:   A fun that undo what was done by the Pre fun.
78%%         Example: Stops the nodes created by the Pre function.
79tc_try(Case, TC) ->
80    tc_try(Case, fun() -> ok end, TC).
81
82tc_try(Case, TCCond, TC0) when is_function(TC0, 0) ->
83    Pre  = fun()  -> undefined end,
84    TC   = fun(_) -> TC0() end,
85    Post = fun(_) -> ok end,
86    tc_try(Case, TCCond, Pre, TC, Post).
87
88tc_try(Case, Pre, TC, Post)
89  when is_atom(Case) andalso
90       is_function(Pre, 0) andalso
91       is_function(TC, 1) andalso
92       is_function(Post, 1) ->
93    TCCond = fun() -> ok end,
94    tc_try(Case, TCCond, Pre, TC, Post).
95
96tc_try(Case, TCCond, Pre, TC, Post)
97  when is_atom(Case) andalso
98       is_function(TCCond, 0) andalso
99       is_function(Pre, 0) andalso
100       is_function(TC, 1) andalso
101       is_function(Post, 1) ->
102    tc_begin(Case),
103    try TCCond() of
104        ok ->
105            tc_print("starting: try pre"),
106            try Pre() of
107                State ->
108                    tc_print("pre done: try test case"),
109                    try
110                        begin
111                            TC(State),
112                            sleep(seconds(1)),
113                            tc_print("test case done: try post"),
114                            (catch Post(State)),
115                            tc_end("ok")
116                        end
117                    catch
118                        C:{skip, _} = SKIP when (C =:= throw) orelse
119                                                (C =:= exit) ->
120                            tc_print("test case (~w) skip: try post", [C]),
121                            (catch Post(State)),
122                            tc_end( f("skipping(catched,~w,tc)", [C]) ),
123                            SKIP;
124                        C:E:S ->
125                            %% We always check the system events
126                            %% before we accept a failure.
127                            %% We do *not* run the Post here because it might
128                            %% generate sys events itself...
129                            case snmp_test_global_sys_monitor:events() of
130                                [] ->
131                                    tc_print("test case failed: try post"),
132                                    (catch Post(State)),
133                                    tc_end( f("failed(catched,~w,tc)", [C]) ),
134                                    erlang:raise(C, E, S);
135                                SysEvs ->
136                                    tc_print("System Events received during tc: "
137                                             "~n   ~p"
138                                             "~nwhen tc failed:"
139                                             "~n   C: ~p"
140                                             "~n   E: ~p"
141                                             "~n   S: ~p",
142                                             [SysEvs, C, E, S]),
143                                    (catch Post(State)),
144                                    tc_end( f("skipping(catched-sysevs,~w,tc)",
145                                              [C]) ),
146                                    SKIP = {skip, "TC failure with system events"},
147                                    SKIP
148                            end
149                    end
150            catch
151                C:{skip, _} = SKIP when (C =:= throw) orelse
152                                        (C =:= exit) ->
153                    tc_end( f("skipping(catched,~w,tc-pre)", [C]) ),
154                    SKIP;
155                C:E:S ->
156                    %% We always check the system events
157                    %% before we accept a failure
158                    case snmp_test_global_sys_monitor:events() of
159                        [] ->
160                            tc_print("tc-pre failed: auto-skip"
161                                     "~n   C: ~p"
162                                     "~n   E: ~p"
163                                     "~n   S: ~p",
164                                     [C, E, S]),
165                            tc_end( f("auto-skip(catched,~w,tc-pre)", [C]) ),
166                            SKIP = {skip, f("TC-Pre failure (~w)", [C])},
167                            SKIP;
168                        SysEvs ->
169                                   tc_print("System Events received: "
170                                            "~n   ~p"
171                                            "~nwhen tc-pre failed:"
172                                            "~n   C: ~p"
173                                            "~n   E: ~p"
174                                            "~n   S: ~p",
175                                            [SysEvs, C, E, S], "", ""),
176                            tc_end( f("skipping(catched-sysevs,~w,tc-pre)", [C]) ),
177                            SKIP = {skip, "TC-Pre failure with system events"},
178                            SKIP
179                    end
180            end;
181        {skip, _} = SKIP ->
182            tc_end("skipping(cond)"),
183            SKIP;
184        {error, Reason} ->
185            tc_end("failed(cond)"),
186            exit({tc_cond_failed, Reason})
187    catch
188        C:{skip, _} = SKIP when ((C =:= throw) orelse (C =:= exit)) ->
189            tc_end( f("skipping(catched,~w,cond)", [C]) ),
190            SKIP;
191        C:E:S ->
192            %% We always check the system events before we accept a failure
193            case snmp_test_global_sys_monitor:events() of
194                [] ->
195                    tc_end( f("failed(catched,~w,cond)", [C]) ),
196                    erlang:raise(C, E, S);
197                SysEvs ->
198                    tc_print("System Events received: "
199                             "~n   ~p", [SysEvs], "", ""),
200                    tc_end( f("skipping(catched-sysevs,~w,cond)", [C]) ),
201                    SKIP = {skip, "TC cond failure with system events"},
202                    SKIP
203            end
204    end.
205
206
207tc_set_name(N) when is_atom(N) ->
208    tc_set_name(atom_to_list(N));
209tc_set_name(N) when is_list(N) ->
210    put(tc_name, N).
211
212tc_get_name() ->
213    get(tc_name).
214
215tc_begin(TC) ->
216    OldVal = process_flag(trap_exit, true),
217    put(old_trap_exit, OldVal),
218    tc_set_name(TC),
219    tc_print("begin ***",
220             "~n----------------------------------------------------~n", "").
221
222tc_end(Result) when is_list(Result) ->
223    OldVal = erase(old_trap_exit),
224    process_flag(trap_exit, OldVal),
225    tc_print("done: ~s", [Result],
226             "", "----------------------------------------------------~n~n"),
227    ok.
228
229tc_print(F) ->
230    tc_print(F, [], "", "").
231
232tc_print(F, A) ->
233    tc_print(F, A, "", "").
234
235tc_print(F, Before, After) ->
236    tc_print(F, [], Before, After).
237
238tc_print(F, A, Before, After) ->
239    Name = tc_which_name(),
240    FStr = f("*** [~s][~s][~p] " ++ F ++ "~n",
241             [formated_timestamp(), Name, self() | A]),
242    io:format(user, Before ++ FStr ++ After, []),
243    io:format(standard_io, Before ++ FStr ++ After, []).
244
245tc_which_name() ->
246    case tc_get_name() of
247        undefined ->
248            case get(sname) of
249                undefined ->
250                    "";
251                SName when is_list(SName) ->
252                    SName
253            end;
254        Name when is_list(Name) ->
255            Name
256    end.
257
258
259%% ----------------------------------------------------------------------
260%% Misc functions
261%%
262
263explicit_inet_backend() ->
264    %% This is intentional!
265    %% This is a kernel flag, which if set disables
266    %% our own special handling of the inet_backend
267    %% in our test suites.
268    case application:get_all_env(kernel) of
269        Env when is_list(Env) ->
270            case lists:keysearch(inet_backend, 1, Env) of
271                {value, {inet_backend, _}} ->
272                    true;
273                _ ->
274                    false
275            end;
276        _ ->
277            false
278    end.
279
280test_inet_backends() ->
281    case init:get_argument(snmp) of
282        {ok, SnmpArgs} when is_list(SnmpArgs) ->
283            test_inet_backends(SnmpArgs, atom_to_list(?FUNCTION_NAME));
284        error ->
285            false
286    end.
287
288test_inet_backends([], _) ->
289    false;
290test_inet_backends([[Key, Val] | _], Key) ->
291    case list_to_atom(string:to_lower(Val)) of
292        Bool when is_boolean(Bool) ->
293            Bool;
294        _ ->
295            false
296    end;
297test_inet_backends([_|Args], Key) ->
298    test_inet_backends(Args, Key).
299
300
301
302proxy_call(F, Timeout, Default)
303  when is_function(F, 0) andalso is_integer(Timeout) andalso (Timeout > 0) ->
304    {P, M} = erlang:spawn_monitor(fun() -> exit(F()) end),
305    receive
306        {'DOWN', M, process, P, Reply} ->
307            Reply
308    after Timeout ->
309            erlang:demonitor(M, [flush]),
310            exit(P, kill),
311            Default
312    end.
313
314
315hostname() ->
316    hostname(node()).
317
318hostname(Node) ->
319    case string:tokens(atom_to_list(Node), [$@]) of
320        [_, Host] ->
321            Host;
322        _ ->
323            []
324    end.
325
326
327which_host_ip(Hostname, Family) ->
328    case snmp_misc:ip(Hostname, Family) of
329        {ok, {A, _, _, _}} = OK
330          when (A =/= 127) ->
331            OK;
332        {ok, {A, _, _, _, _, _, _, _}} = OK
333          when (A =/= 0) andalso
334               (A =/= 16#fe80) ->
335            OK;
336        {ok, _} ->
337            try localhost(Family) of
338                Addr ->
339                    {ok, Addr}
340            catch
341                C:E:S ->
342                    {error, {C, E, S}}
343            end;
344        {error, _} = ERROR ->
345            ERROR
346    end.
347
348
349localhost() ->
350    localhost(inet).
351
352localhost(Family) ->
353    case inet:getaddr(net_adm:localhost(), Family) of
354        {ok, {127, _, _, _}} when (Family =:= inet) ->
355            %% Ouch, we need to use something else
356            case inet:getifaddrs() of
357                {ok, IfList} ->
358                    which_addr(Family, IfList);
359                {error, Reason1} ->
360                    fail({getifaddrs, Reason1}, ?MODULE, ?LINE)
361            end;
362        {ok, {A1, _, _, _, _, _, _, _}} when (Family =:= inet6) andalso
363                                             ((A1 =:= 0) orelse
364                                              (A1 =:= 16#fe80)) ->
365            %% Ouch, we need to use something else
366            case inet:getifaddrs() of
367                {ok, IfList} ->
368                    which_addr(Family, IfList);
369                {error, Reason1} ->
370                    fail({getifaddrs, Reason1}, ?MODULE, ?LINE)
371            end;
372        {ok, Addr} ->
373            Addr;
374        {error, Reason2} ->
375            fail({getaddr, Reason2}, ?MODULE, ?LINE)
376    end.
377
378which_addr(_Family, []) ->
379    fail(no_valid_addr, ?MODULE, ?LINE);
380which_addr(Family, [{"lo", _} | IfList]) ->
381    which_addr(Family, IfList);
382which_addr(Family, [{"tun" ++ _, _} | IfList]) ->
383    which_addr(Family, IfList);
384which_addr(Family, [{"docker" ++ _, _} | IfList]) ->
385    which_addr(Family, IfList);
386which_addr(Family, [{"br-" ++ _, _} | IfList]) ->
387    which_addr(Family, IfList);
388which_addr(Family, [{_Name, IfOpts} | IfList]) ->
389    case which_addr2(Family, IfOpts) of
390        {ok, Addr} ->
391            Addr;
392        {error, _} ->
393            which_addr(Family, IfList)
394    end.
395
396which_addr2(_Family, []) ->
397    {error, not_found};
398which_addr2(Family, [{addr, Addr}|_])
399  when (Family =:= inet) andalso (size(Addr) =:= 4) ->
400    {ok, Addr};
401which_addr2(Family, [{addr, Addr}|_])
402  when (Family =:= inet6) andalso (size(Addr) =:= 8) ->
403    {ok, Addr};
404which_addr2(Family, [_|IfOpts]) ->
405    which_addr2(Family, IfOpts).
406
407
408sz(L) when is_list(L) ->
409    length(L);
410sz(B) when is_binary(B) ->
411    size(B);
412sz(O) ->
413    {unknown_size,O}.
414
415
416os_type() ->
417    case (catch test_server:os_type()) of
418	{'EXIT', _} ->
419	    %% Pre-R10 test server does not have this function
420	    os:type();
421	OsType ->
422	    OsType
423    end.
424
425display_suite_info(SUITE) when is_atom(SUITE) ->
426    (catch do_display_suite_info(SUITE)).
427
428do_display_suite_info(SUITE) ->
429    MI = SUITE:module_info(),
430    case (catch display_version(MI)) of
431	ok ->
432	    ok;
433	_ ->
434	    case (catch display_app_version(MI)) of
435		ok ->
436		    ok;
437		_ ->
438		    io:format("No version info available for test suite ~p~n",
439			      [?MODULE])
440	    end
441    end.
442
443display_version(MI) ->
444    {value, {compile, CI}} = lists:keysearch(compile, 1, MI),
445    {value, {options, CO}} = lists:keysearch(options, 1, CI),
446    Version = version_of_compiler_options(CO),
447    io:format("~p version info: "
448	      "~n   Version: ~p"
449	      "~n", [?MODULE, Version]),
450    ok.
451
452version_of_compiler_options([{d, version, Version} | _]) ->
453    Version;
454version_of_compiler_options([_ | T]) ->
455    version_of_compiler_options(T).
456
457display_app_version(MI) ->
458    {value, {attributes, Attrs}} = lists:keysearch(attributes, 1, MI),
459    {value, {vsn, Vsn}}          = lists:keysearch(vsn, 1, Attrs),
460    {value, {app_vsn, AppVsn}}   = lists:keysearch(app_vsn, 1, Attrs),
461    io:format("~p version info: "
462	      "~n   VSN:     ~p"
463	      "~n   App vsn: ~s"
464	      "~n", [?MODULE, Vsn, AppVsn]),
465    ok.
466
467
468%% ----------------------------------------------------------------
469%% Conditional skip of testcases
470%%
471
472non_pc_tc_maybe_skip(Config, Condition, File, Line)
473  when is_list(Config) andalso is_function(Condition) ->
474    %% Check if we shall skip the skip
475    case os:getenv("TS_OS_BASED_SKIP") of
476        "false" ->
477            ok;
478        _ ->
479	    case lists:keysearch(ts, 1, Config) of
480		{value, {ts, snmp}} ->
481		    %% Always run the testcase if we are using our own
482		    %% test-server...
483		    ok;
484		_ ->
485		    try Condition() of
486			true ->
487			    skip(non_pc_testcase, File, Line);
488			false ->
489			    ok
490                    catch
491                        C:E:S ->
492                            skip({condition, C, E, S}, File, Line)
493		    end
494	    end
495    end.
496
497
498%% The type and spec'ing is just to increase readability
499-type os_family()  :: win32 | unix.
500-type os_name()    :: atom().
501-type os_version() :: string() | {non_neg_integer(),
502				  non_neg_integer(),
503				  non_neg_integer()}.
504-type os_skip_check() :: fun(() -> boolean()) |
505			    fun((os_version()) -> boolean()).
506-type skippable() :: any | [os_family() |
507			    {os_family(), os_name() |
508			                  [os_name() | {os_name(),
509							os_skip_check()}]}].
510
511-spec os_based_skip(skippable()) -> boolean().
512
513os_based_skip(any) ->
514    true;
515os_based_skip(Skippable) when is_list(Skippable) ->
516    os_base_skip(Skippable, os:type());
517os_based_skip(_Crap) ->
518    false.
519
520os_base_skip(Skippable, {OsFam, OsName}) ->
521    os_base_skip(Skippable, OsFam, OsName);
522os_base_skip(Skippable, OsFam) ->
523    os_base_skip(Skippable, OsFam, undefined).
524
525os_base_skip(Skippable, OsFam, OsName) ->
526    %% Check if the entire family is to be skipped
527    %% Example: [win32, unix]
528    case lists:member(OsFam, Skippable) of
529        true ->
530            true;
531        false ->
532	    %% Example: [{unix, freebsd}] | [{unix, [freebsd, darwin]}]
533	    case lists:keysearch(OsFam, 1, Skippable) of
534		{value, {OsFam, OsName}} ->
535		    true;
536		{value, {OsFam, Check}} when is_function(Check, 0) ->
537		    Check();
538		{value, {OsFam, Check}} when is_function(Check, 1) ->
539		    Check(os:version());
540		{value, {OsFam, OsNames}} when is_list(OsNames) ->
541		    %% OsNames is a list of:
542		    %%    [atom()|{atom(), function/0 | function/1}]
543                    case lists:member(OsName, OsNames) of
544			true ->
545			    true;
546			false ->
547			    os_based_skip_check(OsName, OsNames)
548		    end;
549		_ ->
550		    false
551	    end
552    end.
553
554%% Performs a check via a provided fun with arity 0 or 1.
555%% The argument is the result of os:version().
556os_based_skip_check(OsName, OsNames) ->
557    case lists:keysearch(OsName, 1, OsNames) of
558	{value, {OsName, Check}} when is_function(Check, 0) ->
559	    Check();
560	{value, {OsName, Check}} when is_function(Check, 1) ->
561	    Check(os:version());
562	_ ->
563	    false
564    end.
565
566
567%% A modern take on the "Check if our host handle IPv6" question.
568%%
569has_support_ipv6() ->
570    case os:type() of
571        {win32, _} ->
572            %% We do not yet have support for windows in the socket nif,
573            %% so for windows we need to use the old style...
574            old_has_support_ipv6();
575        _ ->
576            socket:is_supported(ipv6) andalso has_valid_ipv6_address()
577    end.
578
579has_valid_ipv6_address() ->
580    case net:getifaddrs(fun(#{addr  := #{family := inet6},
581                              flags := Flags}) ->
582                                not lists:member(loopback, Flags);
583                           (_) ->
584                                false
585                        end) of
586        {ok, [#{addr := #{addr := LocalAddr}}|_]} ->
587            %% At least one valid address, we pick the first...
588            try validate_ipv6_address(LocalAddr)
589            catch
590                _:_:_ ->
591                    false
592            end;
593        {ok, _} ->
594            false;
595        {error, _} ->
596            false
597    end.
598
599validate_ipv6_address(LocalAddr) ->
600    Domain = inet6,
601    ServerSock =
602        case socket:open(Domain, dgram, udp) of
603            {ok, SS} ->
604                SS;
605            {error, R2} ->
606                ?SKIP(f("(server) socket open failed: ~p", [R2]))
607        end,
608    LocalSA = #{family => Domain, addr => LocalAddr},
609    ServerPort =
610        case socket:bind(ServerSock, LocalSA) of
611            ok ->
612                {ok, #{port := P1}} = socket:sockname(ServerSock),
613                P1;
614            {error, R3} ->
615                socket:close(ServerSock),
616                ?SKIP(f("(server) socket bind failed: ~p", [R3]))
617        end,
618    ServerSA = LocalSA#{port => ServerPort},
619    ClientSock =
620        case socket:open(Domain, dgram, udp) of
621            {ok, CS} ->
622                CS;
623            {error, R4} ->
624                ?SKIP(f("(client) socket open failed: ~p", [R4]))
625        end,
626    case socket:bind(ClientSock, LocalSA) of
627        ok ->
628            ok;
629        {error, R5} ->
630            socket:close(ServerSock),
631            socket:close(ClientSock),
632            ?SKIP(f("(client) socket bind failed: ~p", [R5]))
633    end,
634    case socket:sendto(ClientSock, <<"hejsan">>, ServerSA) of
635        ok ->
636            ok;
637        {error, R6} ->
638            socket:close(ServerSock),
639            socket:close(ClientSock),
640            ?SKIP(f("failed socket sendto test: ~p", [R6]))
641    end,
642    case socket:recvfrom(ServerSock) of
643        {ok, {_, <<"hejsan">>}} ->
644            socket:close(ServerSock),
645            socket:close(ClientSock),
646            true;
647        {error, R7} ->
648            socket:close(ServerSock),
649            socket:close(ClientSock),
650            ?SKIP(f("failed socket recvfrom test: ~p", [R7]))
651   end.
652
653
654
655
656old_has_support_ipv6() ->
657    case inet:gethostname() of
658        {ok, Hostname} ->
659            old_has_support_ipv6(Hostname) andalso old_is_ipv6_host(Hostname);
660        _ ->
661            false
662    end.
663
664old_has_support_ipv6(Hostname) ->
665    case inet:getaddr(Hostname, inet6) of
666        {ok, Addr} when (size(Addr) =:= 8) andalso
667                        (element(1, Addr) =/= 0) andalso
668                        (element(1, Addr) =/= 16#fe80) ->
669            true;
670        _ ->
671            false
672    end.
673
674old_is_ipv6_host(Hostname) ->
675    case ct:require(ipv6_hosts) of
676        ok ->
677            lists:member(list_to_atom(Hostname), ct:get_config(ipv6_hosts));
678        _ ->
679            false
680    end.
681
682
683
684%% ----------------------------------------------------------------
685%% Test suite utility functions
686%%
687
688%% Common suite init function
689%% This should be used by "all" suite init functions.
690
691init_per_suite(Config) ->
692
693    iprint("snmp environment: "
694           "~n   (snmp) app:  ~p"
695           "~n   (all)  init: ~p"
696           "~n   (snmp) init: ~p",
697           [application:get_all_env(snmp),
698            init:get_arguments(),
699            case init:get_argument(snmp) of
700                {ok, Args} -> Args;
701                error -> undefined
702            end]),
703
704    ct:timetrap(minutes(2)),
705
706    try analyze_and_print_host_info() of
707        {Factor, HostInfo} when is_integer(Factor) ->
708            try maybe_skip(HostInfo) of
709                true ->
710                    {skip, "Unstable host and/or os (or combo thererof)"};
711                false ->
712                    snmp_test_global_sys_monitor:start(),
713                    [{snmp_factor, Factor} | Config]
714            catch
715                throw:{skip, _} = SKIP ->
716                    SKIP
717            end
718    catch
719        throw:{skip, _} = SKIP ->
720            SKIP
721    end.
722
723maybe_skip(_HostInfo) ->
724
725    %% We have some crap machines that causes random test case failures
726    %% for no obvious reason. So, attempt to identify those without actually
727    %% checking for the host name...
728
729    LinuxVersionVerify =
730        fun(V) when (V > {3,6,11}) ->
731                false; % OK - No skip
732           (V) when (V =:= {3,6,11}) ->
733                case string:trim(os:cmd("cat /etc/issue")) of
734                    "Fedora release 16 " ++ _ -> % Stone age Fedora => Skip
735                        true;
736                    _ ->
737                        false
738                end;
739           (V) when (V =:= {3,4,20}) ->
740                case string:trim(os:cmd("cat /etc/issue")) of
741                    "Wind River Linux 5.0.1.0" ++ _ -> % *Old* Wind River => skip
742                        true;
743                    _ ->
744                        false
745                end;
746           (V) when (V =:= {2,6,32}) ->
747                case string:trim(os:cmd("cat /etc/issue")) of
748                    "Debian GNU/Linux 6.0 " ++ _ -> % Stone age Debian => Skip
749                        true;
750                    _ ->
751                        false
752                end;
753           (V) when (V > {2,6,24}) ->
754                false; % OK - No skip
755           (V) when (V =:= {2,6,10}) ->
756                case string:trim(os:cmd("cat /etc/issue")) of
757                    "MontaVista" ++ _ -> % Stone age MontaVista => Skip
758                        %% The real problem is that the machine is *very* slow
759                        true;
760                    _ ->
761                        false
762                end;
763           (_) ->
764                %% We are specifically checking for
765                %% a *really* old gento...
766                case string:find(string:strip(os:cmd("uname -a")), "gentoo") of
767                    nomatch ->
768                        false;
769                    _ -> % Stone age gentoo => Skip
770                        true
771                end
772        end,
773    DarwinVersionVerify =
774        fun(V) when (V > {9, 8, 0}) ->
775                %% This version is OK: No Skip
776                false;
777           (_V) ->
778                %% This version is *not* ok: Skip
779                true
780        end,
781    SkipWindowsOnVirtual =
782        %% fun() ->
783        %%         SysMan = win_sys_info_lookup(system_manufacturer, HostInfo),
784        %%         case string:to_lower(SysMan) of
785        %%             "vmware" ++ _ ->
786        %%                 true;
787        %%             _ ->
788        %%                 false
789        %%         end
790        %% end,
791        fun() ->
792                %% The host has been replaced and the VM has been reinstalled
793                %% so for now we give it a chance...
794                false
795        end,
796    COND = [{unix,  [{linux, LinuxVersionVerify},
797                     {darwin, DarwinVersionVerify}]},
798            {win32, SkipWindowsOnVirtual}],
799    os_based_skip(COND).
800
801
802end_per_suite(Config) when is_list(Config) ->
803
804    snmp_test_global_sys_monitor:stop(),
805
806    Config.
807
808
809
810fix_data_dir(Config) ->
811    DataDir0     = lookup(data_dir, Config),
812    DataDir1     = filename:split(filename:absname(DataDir0)),
813    [_|DataDir2] = lists:reverse(DataDir1),
814    DataDir      = filename:join(lists:reverse(DataDir2) ++ [?snmp_test_data]),
815    Config1      = lists:keydelete(data_dir, 1, Config),
816    [{data_dir, DataDir ++ "/"} | Config1].
817
818
819init_suite_top_dir(Suite, Config0) ->
820    io:format("~w:init_suite_top_dir -> entry with"
821	      "~n   Suite:   ~p"
822	      "~n   Config0: ~p"
823	      "~n", [?MODULE, Suite, Config0]),
824    Dir         = lookup(priv_dir, Config0),
825    SuiteTopDir = filename:join(Dir, Suite),
826    case file:make_dir(SuiteTopDir) of
827        ok ->
828            ok;
829        {error, eexist} ->
830            ok;
831        {error, Reason} ->
832            fail({failed_creating_suite_top_dir, SuiteTopDir, Reason},
833		 ?MODULE, ?LINE)
834    end,
835
836    %% This is just in case...
837    Config1 = lists:keydelete(snmp_group_top_dir, 1, Config0),
838    Config2 = lists:keydelete(snmp_suite_top_dir, 1, Config1),
839    [{snmp_suite_top_dir, SuiteTopDir} | Config2].
840
841
842init_group_top_dir(GroupName, Config) ->
843    io:format("~w:init_group_top_dir -> entry with"
844	      "~n   GroupName: ~p"
845	      "~n   Config:    ~p"
846	      "~n", [?MODULE, GroupName, Config]),
847    case lists:keysearch(snmp_group_top_dir, 1, Config) of
848	{value, {_Key, Dir}} ->
849	    %% This is a sub-group, so create our dir within Dir
850	    GroupTopDir = filename:join(Dir, GroupName),
851	    case file:make_dir(GroupTopDir) of
852		ok ->
853		    ok;
854		{error, Reason} ->
855		    fail({failed_creating_group_top_dir, GroupTopDir, Reason},
856			 ?MODULE, ?LINE)
857	    end,
858	    [{snmp_group_top_dir, GroupTopDir} | Config];
859
860	_ ->
861            %% This is a "top level" group, that is, there is only the suite
862	    case lists:keysearch(snmp_suite_top_dir, 1, Config) of
863		{value, {_Key, Dir}} ->
864		    GroupTopDir = filename:join(Dir, GroupName),
865		    case file:make_dir(GroupTopDir) of
866			ok ->
867			    ok;
868			{error, Reason} ->
869			    fail({failed_creating_group_top_dir,
870				  GroupTopDir, Reason},
871				 ?MODULE, ?LINE)
872		    end,
873		    [{snmp_group_top_dir, GroupTopDir} | Config];
874		_ ->
875		    fail(could_not_find_suite_top_dir, ?MODULE, ?LINE)
876	    end
877    end.
878
879
880init_testcase_top_dir(Case, Config) ->
881    io:format("~w:init_testcase_top_dir -> entry with"
882	      "~n   Case:   ~p"
883	      "~n   Config: ~p"
884	      "~n", [?MODULE, Case, Config]),
885    case lists:keysearch(snmp_group_top_dir, 1, Config) of
886	{value, {_Key, Dir}} ->
887	    CaseTopDir = filename:join(Dir, Case),
888	    ok = file:make_dir(CaseTopDir),
889	    CaseTopDir;
890	false ->
891	    case lists:keysearch(snmp_suite_top_dir, 1, Config) of
892		{value, {_Key, Dir}} ->
893		    CaseTopDir = filename:join(Dir, Case),
894		    ok = file:make_dir(CaseTopDir),
895		    CaseTopDir;
896		false ->
897		    fail(failed_creating_case_top_dir, ?MODULE, ?LINE)
898	    end
899    end.
900
901
902replace_config(Key, Config, NewValue) ->
903    lists:keyreplace(Key, 1, Config, {Key, NewValue}).
904
905set_config(Key, Def, Config) ->
906    case get_config(Key, Config) of
907        undefined ->
908            [{Key, Def}|Config];
909        _ ->
910            Config
911    end.
912
913get_config(Key,C) ->
914    get_config(Key,C,undefined).
915
916get_config(Key,C,Default) ->
917    case lists:keysearch(Key,1,C) of
918        {value,{Key,Val}} ->
919            Val;
920        _ ->
921            Default
922    end.
923
924lookup(Key, Config) ->
925    {value, {Key, Value}} = lists:keysearch(Key, 1, Config),
926    Value.
927
928fail(Reason, Mod, Line) ->
929    exit({suite_failed, Reason, Mod, Line}).
930
931skip(Reason, Module, Line) ->
932    String = lists:flatten(io_lib:format("Skipping ~p(~p): ~p~n",
933					 [Module, Line, Reason])),
934    exit({skip, String}).
935
936
937%% This function prints various host info, which might be usefull
938%% when analyzing the test suite (results).
939%% It also returns a "factor" that can be used when deciding
940%% the load for some test cases. Such as run time or number of
941%% iterations. This only works for some OSes.
942%%
943%% We make some calculations on Linux, OpenBSD and FreeBSD.
944%% On SunOS we always set the factor to 2 (just to be on the safe side)
945%% On all other os:es (mostly windows) we check the number of schedulers,
946%% but at least the factor will be 2.
947analyze_and_print_host_info() ->
948    {OsFam, OsName} = os:type(),
949    Version         =
950        case os:version() of
951            {Maj, Min, Rel} ->
952                f("~w.~w.~w", [Maj, Min, Rel]);
953            VStr ->
954                VStr
955        end,
956    case {OsFam, OsName} of
957        {unix, linux} ->
958            analyze_and_print_linux_host_info(Version);
959        {unix, openbsd} ->
960            analyze_and_print_openbsd_host_info(Version);
961        {unix, freebsd} ->
962            analyze_and_print_freebsd_host_info(Version);
963        {unix, netbsd} ->
964            analyze_and_print_netbsd_host_info(Version);
965        {unix, darwin} ->
966            analyze_and_print_darwin_host_info(Version);
967        {unix, sunos} ->
968            analyze_and_print_solaris_host_info(Version);
969        {win32, nt} ->
970            analyze_and_print_win_host_info(Version);
971        _ ->
972            io:format("OS Family: ~p"
973                      "~n   OS Type:               ~p"
974                      "~n   Version:               ~p"
975                      "~n   Num Online Schedulers: ~s"
976                      "~n", [OsFam, OsName, Version, str_num_schedulers()]),
977            {num_schedulers_to_factor(), []}
978    end.
979
980linux_which_distro(Version) ->
981    case file:read_file_info("/etc/issue") of
982        {ok, _} ->
983            case [string:trim(S) ||
984                     S <- string:tokens(os:cmd("cat /etc/issue"), [$\n])] of
985                [DistroStr|_] ->
986                    io:format("Linux: ~s"
987                              "~n   ~s"
988                              "~n",
989                              [Version, DistroStr]),
990                    case DistroStr of
991                        "Wind River Linux" ++ _ ->
992                            wind_river;
993                        "MontaVista" ++ _ ->
994                            montavista;
995                        "Yellow Dog" ++ _ ->
996                            yellow_dog;
997                        _ ->
998                            other
999                    end;
1000                X ->
1001                    io:format("Linux: ~s"
1002                              "~n   ~p"
1003                              "~n",
1004                              [Version, X]),
1005                    other
1006            end;
1007        _ ->
1008            io:format("Linux: ~s"
1009                      "~n", [Version]),
1010            other
1011    end.
1012
1013analyze_and_print_linux_host_info(Version) ->
1014    Distro =
1015        case file:read_file_info("/etc/issue") of
1016            {ok, _} ->
1017                linux_which_distro(Version);
1018            _ ->
1019                io:format("Linux: ~s"
1020                          "~n", [Version]),
1021                other
1022        end,
1023    Factor =
1024        case (catch linux_which_cpuinfo(Distro)) of
1025            {ok, {CPU, BogoMIPS}} ->
1026                io:format("CPU: "
1027                          "~n   Model:                 ~s"
1028                          "~n   BogoMIPS:              ~w"
1029                          "~n   Num Online Schedulers: ~s"
1030                          "~n", [CPU, BogoMIPS, str_num_schedulers()]),
1031                if
1032                    (BogoMIPS > 20000) ->
1033                        1;
1034                    (BogoMIPS > 10000) ->
1035                        2;
1036                    (BogoMIPS > 5000) ->
1037                        3;
1038                    (BogoMIPS > 2000) ->
1039                        5;
1040                    (BogoMIPS > 1000) ->
1041                        8;
1042                    true ->
1043                        10
1044                end;
1045            {ok, CPU} ->
1046                io:format("CPU: "
1047                          "~n   Model:                 ~s"
1048                          "~n   Num Online Schedulers: ~s"
1049                          "~n", [CPU, str_num_schedulers()]),
1050                NumChed = erlang:system_info(schedulers),
1051                if
1052                    (NumChed > 2) ->
1053                        2;
1054                    true ->
1055                        5
1056                end;
1057            _ ->
1058                5
1059        end,
1060    %% Check if we need to adjust the factor because of the memory
1061    try linux_which_meminfo() of
1062        AddFactor ->
1063            io:format("TS Scale Factor: ~w (~w + ~w)~n",
1064                      [timetrap_scale_factor(), Factor, AddFactor]),
1065            {Factor + AddFactor, []}
1066    catch
1067        _:_:_ ->
1068            io:format("TS Scale Factor: ~w (~w)~n",
1069                      [timetrap_scale_factor(), Factor]),
1070            {Factor, []}
1071    end.
1072
1073
1074
1075linux_cpuinfo_lookup(Key) when is_list(Key) ->
1076    linux_info_lookup(Key, "/proc/cpuinfo").
1077
1078linux_cpuinfo_cpu() ->
1079    case linux_cpuinfo_lookup("cpu") of
1080        [Model] ->
1081            Model;
1082        _ ->
1083            "-"
1084    end.
1085
1086linux_cpuinfo_motherboard() ->
1087    case linux_cpuinfo_lookup("motherboard") of
1088        [MB] ->
1089            MB;
1090        _ ->
1091            "-"
1092    end.
1093
1094linux_cpuinfo_bogomips() ->
1095    case linux_cpuinfo_lookup("bogomips") of
1096        BMips when is_list(BMips) ->
1097            try lists:sum([bogomips_to_int(BM) || BM <- BMips])
1098            catch
1099                _:_:_ ->
1100                    "-"
1101            end;
1102        _ ->
1103            "-"
1104    end.
1105
1106linux_cpuinfo_BogoMIPS() ->
1107    case linux_cpuinfo_lookup("BogoMIPS") of
1108        BMips when is_list(BMips) ->
1109            try lists:sum([bogomips_to_int(BM) || BM <- BMips])
1110            catch
1111                _:_:_ ->
1112                    "-"
1113            end;
1114        _ ->
1115            "-"
1116    end.
1117
1118linux_cpuinfo_total_bogomips() ->
1119    case linux_cpuinfo_lookup("total bogomips") of
1120        [TBM] ->
1121            try bogomips_to_int(TBM)
1122            catch
1123                _:_:_ ->
1124                    "-"
1125            end;
1126        _ ->
1127            "-"
1128    end.
1129
1130bogomips_to_int(BM) ->
1131    try list_to_float(BM) of
1132        F ->
1133            floor(F)
1134    catch
1135        _:_:_ ->
1136            try list_to_integer(BM) of
1137                I ->
1138                    I
1139            catch
1140                _:_:_ ->
1141                    throw(noinfo)
1142            end
1143    end.
1144
1145linux_cpuinfo_model() ->
1146    case linux_cpuinfo_lookup("model") of
1147        [M|_] ->
1148            M;
1149        _X ->
1150            "-"
1151    end.
1152
1153linux_cpuinfo_platform() ->
1154    case linux_cpuinfo_lookup("platform") of
1155        [P] ->
1156            P;
1157        _ ->
1158            "-"
1159    end.
1160
1161linux_cpuinfo_model_name() ->
1162    case linux_cpuinfo_lookup("model name") of
1163        [M|_] ->
1164            M;
1165        _ ->
1166            "-"
1167    end.
1168
1169linux_cpuinfo_processor() ->
1170    case linux_cpuinfo_lookup("Processor") of
1171        [P] ->
1172            P;
1173        _ ->
1174            "-"
1175    end.
1176
1177
1178linux_which_cpuinfo(montavista) ->
1179    CPU =
1180        case linux_cpuinfo_cpu() of
1181            "-" ->
1182                throw(noinfo);
1183            Model ->
1184                case linux_cpuinfo_motherboard() of
1185                    "-" ->
1186                        Model;
1187                    MB ->
1188                        Model ++ " (" ++ MB ++ ")"
1189                end
1190        end,
1191    case linux_cpuinfo_bogomips() of
1192        "-" ->
1193            {ok, CPU};
1194        BMips ->
1195            {ok, {CPU, BMips}}
1196    end;
1197
1198linux_which_cpuinfo(yellow_dog) ->
1199    CPU =
1200        case linux_cpuinfo_cpu() of
1201            "-" ->
1202                throw(noinfo);
1203            Model ->
1204                case linux_cpuinfo_motherboard() of
1205                    "-" ->
1206                        Model;
1207                    MB ->
1208                        Model ++ " (" ++ MB ++ ")"
1209                end
1210        end,
1211    {ok, CPU};
1212
1213linux_which_cpuinfo(wind_river) ->
1214    Model =
1215        case linux_cpuinfo_model() of
1216            "-" ->
1217                %% Try 'model name'
1218                case linux_cpuinfo_model_name() of
1219                    "-" ->
1220                        throw(noinfo);
1221                    MN ->
1222                        MN
1223                end;
1224            M ->
1225                M
1226        end,
1227    CPU =
1228        case linux_cpuinfo_platform() of
1229            "-" ->
1230                Model;
1231            Platform ->
1232                Model ++ " (" ++ Platform ++ ")"
1233        end,
1234    case linux_cpuinfo_total_bogomips() of
1235        "-" ->
1236            case linux_cpuinfo_BogoMIPS() of
1237                "-" ->
1238                    {ok, CPU};
1239                BMips ->
1240                    {ok, {CPU, BMips}}
1241            end;
1242        BMips ->
1243            {ok, {CPU, BMips}}
1244    end;
1245
1246%% Check for x86 (Intel or AMD)
1247linux_which_cpuinfo(other) ->
1248    CPU =
1249        case linux_cpuinfo_model_name() of
1250            "-" ->
1251                %% ARM (at least some distros...)
1252                case linux_cpuinfo_processor() of
1253                    "-" ->
1254                        %% Ok, we give up
1255                        throw(noinfo);
1256                    Proc ->
1257                        Proc
1258                end;
1259            ModelName ->
1260                ModelName
1261        end,
1262    case linux_cpuinfo_bogomips() of
1263        "-" ->
1264            {ok, CPU};
1265        BMips ->
1266            {ok, {CPU, BMips}}
1267    end.
1268
1269linux_meminfo_lookup(Key) when is_list(Key) ->
1270    linux_info_lookup(Key, "/proc/meminfo").
1271
1272linux_meminfo_memtotal() ->
1273    case linux_meminfo_lookup("MemTotal") of
1274        [X] ->
1275            X;
1276        _ ->
1277            "-"
1278    end.
1279
1280%% We *add* the value this return to the Factor.
1281linux_which_meminfo() ->
1282    case linux_meminfo_memtotal() of
1283        "-" ->
1284            0;
1285        MemTotal ->
1286            io:format("Memory:"
1287                      "~n   ~s"
1288                      "~n", [MemTotal]),
1289            case string:tokens(MemTotal, [$ ]) of
1290                [MemSzStr, MemUnit] ->
1291                    MemSz2 = list_to_integer(MemSzStr),
1292                    MemSz3 =
1293                        case string:to_lower(MemUnit) of
1294                            "kb" ->
1295                                MemSz2;
1296                            "mb" ->
1297                                MemSz2*1024;
1298                            "gb" ->
1299                                MemSz2*1024*1024;
1300                            _ ->
1301                                throw(noinfo)
1302                        end,
1303                    if
1304                        (MemSz3 >= 8388608) ->
1305                            0;
1306                        (MemSz3 >= 4194304) ->
1307                            1;
1308                        (MemSz3 >= 2097152) ->
1309                            3;
1310                        true ->
1311                            5
1312                    end;
1313                _X ->
1314                    0
1315            end
1316    end.
1317
1318
1319%% Just to be clear: This is ***not*** scientific...
1320analyze_and_print_openbsd_host_info(Version) ->
1321    io:format("OpenBSD:"
1322              "~n   Version: ~p"
1323              "~n", [Version]),
1324    Extract =
1325        fun(Key) ->
1326                string:tokens(string:trim(os:cmd("sysctl " ++ Key)), [$=])
1327        end,
1328    try
1329        begin
1330            CPU =
1331                case Extract("hw.model") of
1332                    ["hw.model", Model] ->
1333                        string:trim(Model);
1334                    _ ->
1335                        "-"
1336                end,
1337            CPUSpeed =
1338                case Extract("hw.cpuspeed") of
1339                    ["hw.cpuspeed", Speed] ->
1340                        list_to_integer(Speed);
1341                    _ ->
1342                        -1
1343                end,
1344            NCPU =
1345                case Extract("hw.ncpufound") of
1346                    ["hw.ncpufound", N] ->
1347                        list_to_integer(N);
1348                    _ ->
1349                        -1
1350                end,
1351            Memory =
1352                case Extract("hw.physmem") of
1353                    ["hw.physmem", PhysMem] ->
1354                        list_to_integer(PhysMem) div 1024;
1355                    _ ->
1356                        -1
1357                end,
1358            io:format("CPU:"
1359                      "~n   Model: ~s"
1360                      "~n   Speed: ~w"
1361                      "~n   N:     ~w"
1362                      "~nMemory:"
1363                      "~n   ~w KB"
1364                      "~n", [CPU, CPUSpeed, NCPU, Memory]),
1365            io:format("TS Scale Factor: ~w~n", [timetrap_scale_factor()]),
1366            CPUFactor =
1367                if
1368                    (CPUSpeed =:= -1) ->
1369                        1;
1370                    (CPUSpeed >= 2000) ->
1371                        if
1372                            (NCPU >= 4) ->
1373                                1;
1374                            (NCPU >= 2) ->
1375                                2;
1376                            true ->
1377                                3
1378                        end;
1379                    true ->
1380                        if
1381                            (NCPU >= 4) ->
1382                                2;
1383                            (NCPU >= 2) ->
1384                                3;
1385                            true ->
1386                                4
1387                        end
1388                end,
1389            MemAddFactor =
1390                if
1391                    (Memory =:= -1) ->
1392                        0;
1393                    (Memory >= 8388608) ->
1394                        0;
1395                    (Memory >= 4194304) ->
1396                        1;
1397                    (Memory >= 2097152) ->
1398                        2;
1399                    true ->
1400                        3
1401                end,
1402            {CPUFactor + MemAddFactor, []}
1403        end
1404    catch
1405        _:_:_ ->
1406            io:format("TS Scale Factor: ~w~n", [timetrap_scale_factor()]),
1407            {2, []}
1408    end.
1409
1410
1411analyze_and_print_freebsd_host_info(Version) ->
1412    io:format("FreeBSD:"
1413              "~n   Version: ~p"
1414              "~n", [Version]),
1415    %% This test require that the program 'sysctl' is in the path.
1416    %% First test with 'which sysctl', if that does not work
1417    %% try with 'which /sbin/sysctl'. If that does not work either,
1418    %% we skip the test...
1419    try
1420        begin
1421            SysCtl =
1422                case string:trim(os:cmd("which sysctl")) of
1423                    [] ->
1424                        case string:trim(os:cmd("which /sbin/sysctl")) of
1425                            [] ->
1426                                throw(sysctl);
1427                            SC2 ->
1428                                SC2
1429                        end;
1430                    SC1 ->
1431                        SC1
1432                end,
1433            Extract =
1434                fun(Key) ->
1435                        string:tokens(string:trim(os:cmd(SysCtl ++ " " ++ Key)),
1436                                      [$:])
1437                end,
1438            CPU      = analyze_freebsd_cpu(Extract),
1439            CPUSpeed = analyze_freebsd_cpu_speed(Extract),
1440            NCPU     = analyze_freebsd_ncpu(Extract),
1441            Memory   = analyze_freebsd_memory(Extract),
1442            io:format("CPU:"
1443                      "~n   Model:                 ~s"
1444                      "~n   Speed:                 ~w"
1445                      "~n   N:                     ~w"
1446                      "~n   Num Online Schedulers: ~s"
1447                      "~nMemory:"
1448                      "~n   ~w KB"
1449                      "~n",
1450                      [CPU, CPUSpeed, NCPU, str_num_schedulers(), Memory]),
1451            io:format("TS Scale Factor: ~w~n", [timetrap_scale_factor()]),
1452            CPUFactor =
1453                if
1454                    (CPUSpeed =:= -1) ->
1455                        1;
1456                    (CPUSpeed >= 2000) ->
1457                        if
1458                            (NCPU >= 4) ->
1459                                1;
1460                            (NCPU >= 2) ->
1461                                2;
1462                            true ->
1463                                3
1464                        end;
1465                    true ->
1466                        if
1467                            (NCPU =:= -1) ->
1468                                1;
1469                            (NCPU >= 4) ->
1470                                2;
1471                            (NCPU >= 2) ->
1472                                3;
1473                            true ->
1474                                4
1475                        end
1476                end,
1477            MemAddFactor =
1478                if
1479                    (Memory =:= -1) ->
1480                        0;
1481                    (Memory >= 8388608) ->
1482                        0;
1483                    (Memory >= 4194304) ->
1484                        1;
1485                    (Memory >= 2097152) ->
1486                        2;
1487                    true ->
1488                        3
1489                end,
1490            {CPUFactor + MemAddFactor, []}
1491        end
1492    catch
1493        _:_:_ ->
1494            io:format("CPU:"
1495                      "~n   Num Online Schedulers: ~s"
1496                      "~n", [str_num_schedulers()]),
1497            io:format("TS Scale Factor: ~w~n", [timetrap_scale_factor()]),
1498            Factor = case erlang:system_info(schedulers) of
1499                         1 ->
1500                             10;
1501                         2 ->
1502                             5;
1503                         _ ->
1504                             2
1505                     end,
1506            {Factor, []}
1507    end.
1508
1509
1510analyze_freebsd_cpu(Extract) ->
1511    analyze_freebsd_item(Extract, "hw.model", fun(X) -> X end, "-").
1512
1513analyze_freebsd_cpu_speed(Extract) ->
1514    analyze_freebsd_item(Extract,
1515                         "hw.clockrate",
1516                         fun(X) -> list_to_integer(X) end,
1517                         -1).
1518
1519analyze_freebsd_ncpu(Extract) ->
1520    analyze_freebsd_item(Extract,
1521                         "hw.ncpu",
1522                         fun(X) -> list_to_integer(X) end,
1523                         -1).
1524
1525analyze_freebsd_memory(Extract) ->
1526    analyze_freebsd_item(Extract,
1527                         "hw.physmem",
1528                         fun(X) -> list_to_integer(X) div 1024 end,
1529                         -1).
1530
1531analyze_freebsd_item(Extract, Key, Process, Default) ->
1532    try
1533        begin
1534            case Extract(Key) of
1535                [Key, Model] ->
1536                    Process(string:trim(Model));
1537                _ ->
1538                    Default
1539            end
1540        end
1541    catch
1542        _:_:_ ->
1543            Default
1544    end.
1545
1546
1547analyze_and_print_netbsd_host_info(Version) ->
1548    io:format("NetBSD:"
1549              "~n   Version: ~p"
1550              "~n", [Version]),
1551    %% This test require that the program 'sysctl' is in the path.
1552    %% First test with 'which sysctl', if that does not work
1553    %% try with 'which /sbin/sysctl'. If that does not work either,
1554    %% we skip the test...
1555    try
1556        begin
1557            SysCtl =
1558                case string:trim(os:cmd("which sysctl")) of
1559                    [] ->
1560                        case string:trim(os:cmd("which /sbin/sysctl")) of
1561                            [] ->
1562                                throw(sysctl);
1563                            SC2 ->
1564                                SC2
1565                        end;
1566                    SC1 ->
1567                        SC1
1568                end,
1569            Extract =
1570                fun(Key) ->
1571                        [string:trim(S) ||
1572                            S <-
1573                                string:tokens(string:trim(os:cmd(SysCtl ++ " " ++ Key)),
1574                                              [$=])]
1575                end,
1576            CPU      = analyze_netbsd_cpu(Extract),
1577            Machine  = analyze_netbsd_machine(Extract),
1578            Arch     = analyze_netbsd_machine_arch(Extract),
1579            CPUSpeed = analyze_netbsd_cpu_speed(Extract),
1580            NCPU     = analyze_netbsd_ncpu(Extract),
1581            Memory   = analyze_netbsd_memory(Extract),
1582            io:format("CPU:"
1583                      "~n   Model:          ~s (~s, ~s)"
1584                      "~n   Speed:          ~w MHz"
1585                      "~n   N:              ~w"
1586                      "~n   Num Schedulers: ~w"
1587                      "~nMemory:"
1588                      "~n   ~w KB"
1589                      "~n",
1590                      [CPU, Machine, Arch, CPUSpeed, NCPU,
1591                       erlang:system_info(schedulers), Memory]),
1592            CPUFactor =
1593                if
1594                    (CPUSpeed =:= -1) ->
1595                        1;
1596                    (CPUSpeed >= 2000) ->
1597                        if
1598                            (NCPU >= 4) ->
1599                                1;
1600                            (NCPU >= 2) ->
1601                                2;
1602                            true ->
1603                                3
1604                        end;
1605                    true ->
1606                        if
1607                            (NCPU =:= -1) ->
1608                                1;
1609                            (NCPU >= 4) ->
1610                                2;
1611                            (NCPU >= 2) ->
1612                                3;
1613                            true ->
1614                                4
1615                        end
1616                end,
1617            MemAddFactor =
1618                if
1619                    (Memory =:= -1) ->
1620                        0;
1621                    (Memory >= 8388608) ->
1622                        0;
1623                    (Memory >= 4194304) ->
1624                        1;
1625                    (Memory >= 2097152) ->
1626                        2;
1627                    true ->
1628                        3
1629                end,
1630            {CPUFactor + MemAddFactor, []}
1631        end
1632    catch
1633        _:_:_ ->
1634            io:format("CPU:"
1635                      "~n   Num Schedulers: ~w"
1636                      "~n", [erlang:system_info(schedulers)]),
1637            Factor = case erlang:system_info(schedulers) of
1638                         1 ->
1639                             10;
1640                         2 ->
1641                             5;
1642                         _ ->
1643                             2
1644                     end,
1645            {Factor, []}
1646    end.
1647
1648analyze_netbsd_cpu(Extract) ->
1649    analyze_netbsd_item(Extract, "hw.model", fun(X) -> X end, "-").
1650
1651analyze_netbsd_machine(Extract) ->
1652    analyze_netbsd_item(Extract, "hw.machine", fun(X) -> X end, "-").
1653
1654analyze_netbsd_machine_arch(Extract) ->
1655    analyze_netbsd_item(Extract, "hw.machine_arch", fun(X) -> X end, "-").
1656
1657analyze_netbsd_cpu_speed(Extract) ->
1658    analyze_netbsd_item(Extract, "machdep.dmi.processor-frequency",
1659                        fun(X) -> case string:tokens(X, [$\ ]) of
1660                                      [MHz, "MHz"] ->
1661                                          list_to_integer(MHz);
1662                                      _ ->
1663                                          -1
1664                                  end
1665                        end, "-").
1666
1667analyze_netbsd_ncpu(Extract) ->
1668    analyze_netbsd_item(Extract,
1669                        "hw.ncpu",
1670                        fun(X) -> list_to_integer(X) end,
1671                        -1).
1672
1673analyze_netbsd_memory(Extract) ->
1674    analyze_netbsd_item(Extract,
1675                        "hw.physmem64",
1676                        fun(X) -> list_to_integer(X) div 1024 end,
1677                        -1).
1678
1679analyze_netbsd_item(Extract, Key, Process, Default) ->
1680    analyze_freebsd_item(Extract, Key, Process, Default).
1681
1682
1683
1684%% Model Identifier: Macmini7,1
1685%%       Processor Name: Intel Core i5
1686%%       Processor Speed: 2,6 GHz
1687%%       Number of Processors: 1
1688%%       Total Number of Cores: 2
1689%%       L2 Cache (per Core): 256 KB
1690%%       L3 Cache: 3 MB
1691%%       Hyper-Threading Technology: Enabled
1692%%       Memory: 16 GB
1693
1694analyze_and_print_darwin_host_info(Version) ->
1695    %% This stuff is for macOS.
1696    %% If we ever tested on a pure darwin machine,
1697    %% we need to find some other way to find some info...
1698    %% Also, I suppose its possible that we for some other
1699    %% reason *fail* to get the info...
1700    case analyze_darwin_software_info() of
1701        [] ->
1702            io:format("Darwin:"
1703                      "~n   Version:               ~s"
1704                      "~n   Num Online Schedulers: ~s"
1705                      "~n", [Version, str_num_schedulers()]),
1706            {num_schedulers_to_factor(), []};
1707        SwInfo when  is_list(SwInfo) ->
1708            SystemVersion = analyze_darwin_sw_system_version(SwInfo),
1709            KernelVersion = analyze_darwin_sw_kernel_version(SwInfo),
1710            HwInfo        = analyze_darwin_hardware_info(),
1711            ModelName     = analyze_darwin_hw_model_name(HwInfo),
1712            ModelId       = analyze_darwin_hw_model_identifier(HwInfo),
1713            ProcName      = analyze_darwin_hw_processor_name(HwInfo),
1714            ProcSpeed     = analyze_darwin_hw_processor_speed(HwInfo),
1715            NumProc       = analyze_darwin_hw_number_of_processors(HwInfo),
1716            NumCores      = analyze_darwin_hw_total_number_of_cores(HwInfo),
1717            Memory        = analyze_darwin_hw_memory(HwInfo),
1718            io:format("Darwin:"
1719                      "~n   System Version:        ~s"
1720                      "~n   Kernel Version:        ~s"
1721                      "~n   Model:                 ~s (~s)"
1722                      "~n   Processor:             ~s (~s, ~s, ~s)"
1723                      "~n   Memory:                ~s"
1724                      "~n   Num Online Schedulers: ~s"
1725                      "~n", [SystemVersion, KernelVersion,
1726                             ModelName, ModelId,
1727                             ProcName, ProcSpeed, NumProc, NumCores,
1728                             Memory,
1729                             str_num_schedulers()]),
1730            CPUFactor = analyze_darwin_cpu_to_factor(ProcName,
1731                                                     ProcSpeed,
1732                                                     NumProc,
1733                                                     NumCores),
1734            MemFactor = analyze_darwin_memory_to_factor(Memory),
1735            if (MemFactor =:= 1) ->
1736                    {CPUFactor, []};
1737               true ->
1738                    {CPUFactor + MemFactor, []}
1739            end
1740    end.
1741
1742analyze_darwin_sw_system_version(SwInfo) ->
1743    proplists:get_value("system version", SwInfo, "-").
1744
1745analyze_darwin_sw_kernel_version(SwInfo) ->
1746    proplists:get_value("kernel version", SwInfo, "-").
1747
1748analyze_darwin_software_info() ->
1749    analyze_darwin_system_profiler("SPSoftwareDataType").
1750
1751analyze_darwin_hw_model_name(HwInfo) ->
1752    proplists:get_value("model name", HwInfo, "-").
1753
1754analyze_darwin_hw_model_identifier(HwInfo) ->
1755    proplists:get_value("model identifier", HwInfo, "-").
1756
1757analyze_darwin_hw_processor_name(HwInfo) ->
1758    proplists:get_value("processor name", HwInfo, "-").
1759
1760analyze_darwin_hw_processor_speed(HwInfo) ->
1761    proplists:get_value("processor speed", HwInfo, "-").
1762
1763analyze_darwin_hw_number_of_processors(HwInfo) ->
1764    proplists:get_value("number of processors", HwInfo, "-").
1765
1766analyze_darwin_hw_total_number_of_cores(HwInfo) ->
1767    proplists:get_value("total number of cores", HwInfo, "-").
1768
1769analyze_darwin_hw_memory(HwInfo) ->
1770    proplists:get_value("memory", HwInfo, "-").
1771
1772analyze_darwin_hardware_info() ->
1773    analyze_darwin_system_profiler("SPHardwareDataType").
1774
1775%% This basically has the structure: "Key: Value"
1776%% But could also be (for example):
1777%%    "Something:" (which we ignore)
1778%%    "Key: Value1:Value2"
1779analyze_darwin_system_profiler(DataType) ->
1780    %% First, make sure the program actually exist:
1781    case os:cmd("which system_profiler") of
1782        [] ->
1783            [];
1784        _ ->
1785            D0 = os:cmd("system_profiler " ++ DataType),
1786            D1 = string:tokens(D0, [$\n]),
1787            D2 = [string:trim(S1) || S1 <- D1],
1788            D3 = [string:tokens(S2, [$:]) || S2 <- D2],
1789            analyze_darwin_system_profiler2(D3)
1790    end.
1791
1792analyze_darwin_system_profiler2(L) ->
1793    analyze_darwin_system_profiler2(L, []).
1794
1795analyze_darwin_system_profiler2([], Acc) ->
1796    [{string:to_lower(K), V} || {K, V} <- lists:reverse(Acc)];
1797analyze_darwin_system_profiler2([[_]|T], Acc) ->
1798    analyze_darwin_system_profiler2(T, Acc);
1799analyze_darwin_system_profiler2([[H1,H2]|T], Acc) ->
1800    analyze_darwin_system_profiler2(T, [{H1, string:trim(H2)}|Acc]);
1801analyze_darwin_system_profiler2([[H|TH0]|T], Acc) ->
1802    %% Some value parts has ':' in them, so put them together
1803    TH1 = colonize(TH0),
1804    analyze_darwin_system_profiler2(T, [{H, string:trim(TH1)}|Acc]).
1805
1806%% This is only called if the length is at least 2
1807colonize([L1, L2]) ->
1808    L1 ++ ":" ++ L2;
1809colonize([H|T]) ->
1810    H ++ ":" ++ colonize(T).
1811
1812
1813%% The memory looks like this "<size> <unit>". Example: "2 GB"
1814analyze_darwin_memory_to_factor(Mem) ->
1815    case [string:to_lower(S) || S <- string:tokens(Mem, [$\ ])] of
1816        [_SzStr, "tb"] ->
1817            1;
1818        [SzStr, "gb"] ->
1819            try list_to_integer(SzStr) of
1820                Sz when Sz < 2 ->
1821                    20;
1822                Sz when Sz < 4 ->
1823                    10;
1824                Sz when Sz < 8 ->
1825                    5;
1826                Sz when Sz < 16 ->
1827                    2;
1828                _ ->
1829                    1
1830            catch
1831                _:_:_ ->
1832                    20
1833            end;
1834        [_SzStr, "mb"] ->
1835            20;
1836        _ ->
1837            20
1838    end.
1839
1840
1841%% The speed is a string: "<speed> <unit>"
1842%% the speed may be a float, which we transforms into an integer of MHz.
1843%% To calculate a factor based on processor speed, number of procs
1844%% and number of cores is ... not an exact ... science ...
1845analyze_darwin_cpu_to_factor(_ProcName,
1846                             ProcSpeedStr, NumProcStr, NumCoresStr) ->
1847    Speed =
1848        case [string:to_lower(S) || S <- string:tokens(ProcSpeedStr, [$\ ])] of
1849            [SpeedStr, "mhz"] ->
1850                try list_to_integer(SpeedStr) of
1851                    SpeedI ->
1852                        SpeedI
1853                catch
1854                    _:_:_ ->
1855                        try list_to_float(SpeedStr) of
1856                            SpeedF ->
1857                                trunc(SpeedF)
1858                        catch
1859                            _:_:_ ->
1860                                -1
1861                        end
1862                end;
1863            [SpeedStr, "ghz"] ->
1864                try list_to_float(SpeedStr) of
1865                    SpeedF ->
1866                        trunc(1000*SpeedF)
1867                catch
1868                    _:_:_ ->
1869                        try list_to_integer(SpeedStr) of
1870                            SpeedI ->
1871                                1000*SpeedI
1872                        catch
1873                            _:_:_ ->
1874                                -1
1875                        end
1876                end;
1877            _ ->
1878                -1
1879        end,
1880    NumProc = try list_to_integer(NumProcStr) of
1881                  NumProcI ->
1882                      NumProcI
1883              catch
1884                  _:_:_ ->
1885                      1
1886              end,
1887    NumCores = try list_to_integer(NumCoresStr) of
1888                   NumCoresI ->
1889                       NumCoresI
1890               catch
1891                   _:_:_ ->
1892                       1
1893               end,
1894    if
1895        (Speed > 3000) ->
1896            if
1897                (NumProc =:= 1) ->
1898                    if
1899                        (NumCores < 2) ->
1900                            5;
1901                        (NumCores < 4) ->
1902                            3;
1903                        (NumCores < 6) ->
1904                            2;
1905                        true ->
1906                            1
1907                    end;
1908                true ->
1909                    if
1910                        (NumCores < 4) ->
1911                            2;
1912                        true ->
1913                            1
1914                    end
1915            end;
1916        (Speed > 2000) ->
1917            if
1918                (NumProc =:= 1) ->
1919                    if
1920                        (NumCores < 2) ->
1921                            8;
1922                        (NumCores < 4) ->
1923                            5;
1924                        (NumCores < 6) ->
1925                            3;
1926                        true ->
1927                            1
1928                    end;
1929                true ->
1930                    if
1931                        (NumCores < 4) ->
1932                            5;
1933                        (NumCores < 8) ->
1934                            2;
1935                        true ->
1936                            1
1937                    end
1938            end;
1939        true ->
1940            if
1941                (NumProc =:= 1) ->
1942                    if
1943                        (NumCores < 2) ->
1944                            10;
1945                        (NumCores < 4) ->
1946                            7;
1947                        (NumCores < 6) ->
1948                            5;
1949                        (NumCores < 8) ->
1950                            3;
1951                        true ->
1952                            1
1953                    end;
1954                true ->
1955                    if
1956                        (NumCores < 4) ->
1957                            8;
1958                        (NumCores < 8) ->
1959                            4;
1960                        true ->
1961                            1
1962                    end
1963            end
1964    end.
1965
1966
1967analyze_and_print_solaris_host_info(Version) ->
1968    Release =
1969        case file:read_file_info("/etc/release") of
1970            {ok, _} ->
1971                case [string:trim(S) || S <- string:tokens(os:cmd("cat /etc/release"), [$\n])] of
1972                    [Rel | _] ->
1973                        Rel;
1974                    _ ->
1975                        "-"
1976                end;
1977            _ ->
1978                "-"
1979        end,
1980    %% Display the firmware device tree root properties (prtconf -b)
1981    Props = [list_to_tuple([string:trim(PS) || PS <- Prop]) ||
1982                Prop <- [string:tokens(S, [$:]) ||
1983                            S <- string:tokens(os:cmd("prtconf -b"), [$\n])]],
1984    BannerName = case lists:keysearch("banner-name", 1, Props) of
1985                     {value, {_, BN}} ->
1986                         string:trim(BN);
1987                     _ ->
1988                         "-"
1989                 end,
1990    InstructionSet =
1991        case string:trim(os:cmd("isainfo -k")) of
1992            "Pseudo-terminal will not" ++ _ ->
1993                "-";
1994            IS ->
1995                IS
1996        end,
1997    PtrConf = [list_to_tuple([string:trim(S) || S <- Items]) || Items <- [string:tokens(S, [$:]) || S <- string:tokens(os:cmd("prtconf"), [$\n])], length(Items) > 1],
1998    SysConf =
1999        case lists:keysearch("System Configuration", 1, PtrConf) of
2000            {value, {_, SC}} ->
2001                SC;
2002            _ ->
2003                "-"
2004        end,
2005    %% Because we count the lines of the output (which may contain
2006    %% any number of extra crap lines) we need to ensure we only
2007    %% count the "proper" stdout. So send it to a tmp file first
2008    %% and then count its number of lines...
2009    NumPhysCPU =
2010       try
2011            begin
2012                File1 = f("/tmp/psrinfo_p.~s.~w", [os:getpid(), os:system_time()]),
2013                os:cmd("psrinfo -p > " ++ File1),
2014                string:trim(os:cmd("cat " ++ File1))
2015            end
2016        catch
2017                _:_:_ ->
2018                    "-"
2019        end,
2020    %% Because we count the lines of the output (which may contain
2021    %% any number of extra crap lines) we need to ensure we only
2022    %% count the "proper" stdout. So send it to a tmp file first
2023    %% and then count its number of lines...
2024    NumVCPU =
2025        try
2026            begin
2027                File2 = f("/tmp/psrinfo.~s.~w", [os:getpid(), os:system_time()]),
2028                os:cmd("psrinfo > " ++ File2),
2029                [NumVCPUStr | _] = string:tokens(os:cmd("wc -l " ++ File2), [$\ ]),
2030                NumVCPUStr
2031            end
2032        catch
2033            _:_:_ ->
2034                "-"
2035        end,
2036    MemSz =
2037        case lists:keysearch("Memory size", 1, PtrConf) of
2038            {value, {_, MS}} ->
2039                MS;
2040            _ ->
2041                "-"
2042        end,
2043    io:format("Solaris: ~s"
2044              "~n   Release:               ~s"
2045              "~n   Banner Name:           ~s"
2046              "~n   Instruction Set:       ~s"
2047              "~n   CPUs:                  ~s (~s)"
2048              "~n   System Config:         ~s"
2049              "~n   Memory Size:           ~s"
2050              "~n   Num Online Schedulers: ~s"
2051              "~n~n", [Version, Release, BannerName, InstructionSet,
2052                       NumPhysCPU, NumVCPU,
2053                       SysConf, MemSz,
2054                       str_num_schedulers()]),
2055    io:format("TS Scale Factor: ~w~n", [timetrap_scale_factor()]),
2056    MemFactor =
2057        try string:tokens(MemSz, [$ ]) of
2058            [SzStr, "Mega" ++ _] ->
2059                try list_to_integer(SzStr) of
2060                    Sz when Sz > 8192 ->
2061                        0;
2062                    Sz when Sz > 4096 ->
2063                        1;
2064                    Sz when Sz > 2048 ->
2065                        2;
2066                    _ ->
2067                        5
2068                catch
2069                    _:_:_ ->
2070                        10
2071                end;
2072            [SzStr, "Giga" ++ _] ->
2073                try list_to_integer(SzStr) of
2074                    Sz when Sz > 8 ->
2075                        0;
2076                    Sz when Sz > 4 ->
2077                        1;
2078                    Sz when Sz > 2 ->
2079                        2;
2080                    _ ->
2081                        5
2082                catch
2083                    _:_:_ ->
2084                        10
2085                end;
2086            _ ->
2087                10
2088        catch
2089            _:_:_ ->
2090                10
2091        end,
2092    {try erlang:system_info(schedulers) of
2093         1 ->
2094             10;
2095         2 ->
2096             5;
2097         N when (N =< 6) ->
2098             2;
2099         _ ->
2100             1
2101     catch
2102         _:_:_ ->
2103             10
2104     end + MemFactor, []}.
2105
2106
2107
2108analyze_and_print_win_host_info(Version) ->
2109    SysInfo    = which_win_system_info(),
2110    OsName     = win_sys_info_lookup(os_name,             SysInfo),
2111    OsVersion  = win_sys_info_lookup(os_version,          SysInfo),
2112    SysMan     = win_sys_info_lookup(system_manufacturer, SysInfo),
2113    SysMod     = win_sys_info_lookup(system_model,        SysInfo),
2114    NumProcs   = win_sys_info_lookup(num_processors,      SysInfo),
2115    TotPhysMem = win_sys_info_lookup(total_phys_memory,   SysInfo),
2116    io:format("Windows: ~s"
2117              "~n   OS Version:             ~s (~p)"
2118              "~n   System Manufacturer:    ~s"
2119              "~n   System Model:           ~s"
2120              "~n   Number of Processor(s): ~s"
2121              "~n   Total Physical Memory:  ~s"
2122              "~n   Num Online Schedulers:  ~s"
2123              "~n~n", [OsName, OsVersion, Version,
2124                       SysMan, SysMod, NumProcs, TotPhysMem,
2125                       str_num_schedulers()]),
2126    io:format("TS Scale Factor: ~w~n", [timetrap_scale_factor()]),
2127    MemFactor =
2128        try
2129            begin
2130                [MStr, MUnit|_] =
2131                    string:tokens(lists:delete($,, TotPhysMem), [$\ ]),
2132                case string:to_lower(MUnit) of
2133                    "gb" ->
2134                        try list_to_integer(MStr) of
2135                            M when M > 8 ->
2136                                0;
2137                            M when M > 4 ->
2138                                1;
2139                            M when M > 2 ->
2140                                2;
2141                            _ ->
2142                                5
2143                        catch
2144                            _:_:_ ->
2145                                10
2146                        end;
2147                    "mb" ->
2148                        try list_to_integer(MStr) of
2149                            M when M > 8192 ->
2150                                0;
2151                            M when M > 4096 ->
2152                                1;
2153                            M when M > 2048 ->
2154                                2;
2155                            _ ->
2156                                5
2157                        catch
2158                            _:_:_ ->
2159                                10
2160                        end;
2161                    _ ->
2162                        10
2163                end
2164            end
2165        catch
2166            _:_:_ ->
2167                10
2168        end,
2169    CPUFactor =
2170        case erlang:system_info(schedulers) of
2171            1 ->
2172                10;
2173            2 ->
2174                5;
2175            _ ->
2176                2
2177        end,
2178    {CPUFactor + MemFactor, SysInfo}.
2179
2180win_sys_info_lookup(Key, SysInfo) ->
2181    win_sys_info_lookup(Key, SysInfo, "-").
2182
2183win_sys_info_lookup(Key, SysInfo, Def) ->
2184    case lists:keysearch(Key, 1, SysInfo) of
2185        {value, {Key, Value}} ->
2186            Value;
2187        false ->
2188            Def
2189    end.
2190
2191%% This function only extracts the prop we actually care about!
2192which_win_system_info() ->
2193    F = fun() ->
2194                try
2195                    begin
2196                        SysInfo = os:cmd("systeminfo"),
2197                        process_win_system_info(
2198                          string:tokens(SysInfo, [$\r, $\n]), [])
2199                    end
2200                catch
2201                    C:E:S ->
2202                        io:format("Failed get or process System info: "
2203                                  "   Error Class: ~p"
2204                                  "   Error:       ~p"
2205                                  "   Stack:       ~p"
2206                                  "~n", [C, E, S]),
2207                        []
2208                end
2209        end,
2210    proxy_call(F, minutes(1), []).
2211
2212process_win_system_info([], Acc) ->
2213    Acc;
2214process_win_system_info([H|T], Acc) ->
2215    case string:tokens(H, [$:]) of
2216        [Key, Value] ->
2217            case string:to_lower(Key) of
2218                "os name" ->
2219                    process_win_system_info(T,
2220                                            [{os_name, string:trim(Value)}|Acc]);
2221                "os version" ->
2222                    process_win_system_info(T,
2223                                            [{os_version, string:trim(Value)}|Acc]);
2224                "system manufacturer" ->
2225                    process_win_system_info(T,
2226                                            [{system_manufacturer, string:trim(Value)}|Acc]);
2227                "system model" ->
2228                    process_win_system_info(T,
2229                                            [{system_model, string:trim(Value)}|Acc]);
2230                "processor(s)" ->
2231                    [NumProcStr|_] = string:tokens(Value, [$\ ]),
2232                    T2 = lists:nthtail(list_to_integer(NumProcStr), T),
2233                    process_win_system_info(T2,
2234                                            [{num_processors, NumProcStr}|Acc]);
2235                "total physical memory" ->
2236                    process_win_system_info(T,
2237                                            [{total_phys_memory, string:trim(Value)}|Acc]);
2238                _ ->
2239                    process_win_system_info(T, Acc)
2240            end;
2241        _ ->
2242            process_win_system_info(T, Acc)
2243    end.
2244
2245
2246
2247str_num_schedulers() ->
2248    try erlang:system_info(schedulers_online) of
2249        N -> f("~w", [N])
2250    catch
2251        _:_:_ -> "-"
2252    end.
2253
2254num_schedulers_to_factor() ->
2255    try erlang:system_info(schedulers_online) of
2256        1 ->
2257            10;
2258        2 ->
2259            5;
2260        N when (N =< 6) ->
2261            2;
2262        _ ->
2263            1
2264    catch
2265        _:_:_ ->
2266            10
2267    end.
2268
2269
2270linux_info_lookup(Key, File) ->
2271    %% try
2272    %%     begin
2273    %%         GREP   = os:cmd("grep " ++ "\"" ++ Key ++ "\"" ++ " " ++ File),
2274    %%         io:format("linux_info_lookup() -> GREP:   ~p~n", [GREP]),
2275    %%         TOKENS = string:tokens(GREP, [$:,$\n]),
2276    %%         io:format("linux_info_lookup() -> TOKENS: ~p~n", [TOKENS]),
2277    %%         INFO   = [string:trim(S) || S <- TOKENS],
2278    %%         io:format("linux_info_lookup() -> INFO:   ~p~n", [INFO]),
2279    %%         linux_info_lookup_collect(Key, INFO, [])
2280    %%     end
2281    %% catch
2282    %%     _:_:_ ->
2283    %%         "-"
2284    %% end.
2285    try [string:trim(S) || S <- string:tokens(os:cmd("grep " ++ "\"" ++ Key ++ "\"" ++ " " ++ File), [$:,$\n])] of
2286        Info ->
2287            linux_info_lookup_collect(Key, Info, [])
2288    catch
2289        _:_:_ ->
2290            "-"
2291    end.
2292
2293linux_info_lookup_collect(_Key, [], Values) ->
2294    lists:reverse(Values);
2295linux_info_lookup_collect(Key, [Key, Value|Rest], Values) ->
2296    linux_info_lookup_collect(Key, Rest, [Value|Values]);
2297linux_info_lookup_collect(_, _, Values) ->
2298    lists:reverse(Values).
2299
2300
2301
2302%% ----------------------------------------------------------------
2303%% Time related function
2304%%
2305
2306hours(N)   -> trunc(N * 1000 * 60 * 60).
2307minutes(N) -> trunc(N * 1000 * 60).
2308seconds(N) -> trunc(N * 1000).
2309
2310
2311sleep(infinity) ->
2312    receive
2313    after infinity ->
2314            ok
2315    end;
2316sleep(MSecs) ->
2317    receive
2318    after trunc(MSecs) ->
2319            ok
2320    end,
2321    ok.
2322
2323
2324%% ----------------------------------------------------------------
2325%% Process utility function
2326%%
2327
2328mqueue() ->
2329    mqueue(self()).
2330mqueue(Pid) when is_pid(Pid) ->
2331    Key = messages,
2332    case process_info(Pid, Key) of
2333        {Key, Msgs} ->
2334            Msgs;
2335        _ ->
2336            []
2337    end.
2338
2339flush_mqueue() ->
2340    io:format("~p~n", [lists:reverse(flush_mqueue([]))]).
2341
2342flush_mqueue(MQ) ->
2343    receive
2344        Any ->
2345            flush_mqueue([Any|MQ])
2346    after 0 ->
2347            MQ
2348    end.
2349
2350
2351trap_exit() ->
2352    {trap_exit, Flag} = process_info(self(),trap_exit), Flag.
2353
2354trap_exit(Flag) ->
2355    process_flag(trap_exit, Flag).
2356
2357
2358
2359%% ----------------------------------------------------------------
2360%% Node utility functions
2361%%
2362
2363ping(N) ->
2364    case net_adm:ping(N) of
2365 	pang ->
2366 	    error;
2367 	pong ->
2368 	    ok
2369     end.
2370
2371local_nodes() ->
2372    nodes_on(net_adm:localhost()).
2373
2374nodes_on(Host) when is_list(Host) ->
2375    net_adm:world_list([list_to_atom(Host)]).
2376
2377
2378start_node(Name, Args) ->
2379    Opts = [{cleanup, false}, {args, Args}],
2380    test_server:start_node(Name, slave, Opts).
2381
2382
2383stop_node(Node) ->
2384    test_server:stop_node(Node).
2385
2386
2387%% ----------------------------------------------------------------
2388%% Application and Crypto utility functions
2389%%
2390
2391is_app_running(App) when is_atom(App) ->
2392    Apps = application:which_applications(),
2393    lists:keymember(App,1,Apps).
2394
2395is_crypto_running() ->
2396    is_app_running(crypto).
2397
2398is_mnesia_running() ->
2399    is_app_running(mnesia).
2400
2401is_snmp_running() ->
2402    is_app_running(snmp).
2403
2404crypto_start() ->
2405    try crypto:start() of
2406        ok ->
2407            ok;
2408        {error, {already_started,crypto}} ->
2409            ok
2410    catch
2411        exit:{undef, [{crypto, start, [], []} | _]}:_ ->
2412            {error, no_crypto};
2413        C:E:S ->
2414            {error, {C, E, S}}
2415    end.
2416
2417crypto_support() ->
2418    crypto_support([md5, sha], []).
2419
2420crypto_support([], []) ->
2421    yes;
2422crypto_support([], Acc) ->
2423    {no, Acc};
2424crypto_support([Func|Funcs], Acc) ->
2425    case is_crypto_supported(Func) of
2426        true ->
2427            crypto_support(Funcs, Acc);
2428        false ->
2429            crypto_support(Funcs, [Func|Acc])
2430    end.
2431
2432is_crypto_supported(Func) ->
2433    snmp_misc:is_crypto_supported(Func).
2434
2435
2436%% This function ensures that a *named* process on the local node is not running.
2437%% It does so by:
2438%%   1) Wait for 'Timeout' msec
2439%%   2) If 1 did not work, issue 'stop' and then wait 'Timeout' msec
2440%%   3) And finally, if 2 did not work, issue exit(kill).
2441ensure_not_running(Name, Stopper, Timeout)
2442  when is_atom(Name) andalso
2443       is_function(Stopper, 0) andalso
2444       is_integer(Timeout) ->
2445    ensure_not_running(whereis(Name), Name, Stopper, Timeout).
2446
2447ensure_not_running(Pid, Name, Stopper, Timeout) when is_pid(Pid) ->
2448    MRef = erlang:monitor(process, Pid),
2449    try
2450        begin
2451            ensure_not_running_wait(Pid, MRef, Timeout),
2452            ensure_not_running_stop(Pid, MRef, Stopper, Timeout),
2453            ensure_not_running_kill(Pid, MRef, Timeout),
2454            exit({failed_ensure_not_running, Name})
2455        end
2456    catch
2457        throw:ok ->
2458            sleep(1000),
2459            ok
2460    end;
2461ensure_not_running(_, _, _, _) ->
2462    iprint("ensure_not_running -> not running", []),
2463    sleep(1000), % This should not actually be necessary!
2464    ok.
2465
2466
2467ensure_not_running_wait(Pid, MRef, Timeout) ->
2468    receive
2469        {'DOWN', MRef, process, Pid, _Info} ->
2470            iprint("ensure_not_running_wait -> died peacefully", []),
2471            throw(ok)
2472    after Timeout ->
2473            wprint("ensure_not_running_wait -> giving up", []),
2474            ok
2475    end.
2476
2477ensure_not_running_stop(Pid, MRef, Stopper, Timeout) ->
2478    %% Spawn a stop'er process
2479    StopPid = spawn(Stopper),
2480    receive
2481        {'DOWN', MRef, process, Pid, _Info} ->
2482            nprint("ensure_not_running_stop -> dead (stopped)", []),
2483            throw(ok)
2484    after Timeout ->
2485            wprint("ensure_not_running_stop -> giving up", []),
2486            exit(StopPid, kill),
2487            ok
2488    end.
2489
2490ensure_not_running_kill(Pid, MRef, Timeout) ->
2491    exit(Pid, kill),
2492    receive
2493        {'DOWN', MRef, process, Pid, _Info} ->
2494            nprint("ensure_not_running_kill -> dead (killed)", []),
2495            throw(ok)
2496    after Timeout ->
2497            wprint("ensure_not_running_kill -> giving up", []),
2498            ok
2499    end.
2500
2501
2502%% ----------------------------------------------------------------
2503%% Watchdog functions
2504%%
2505
2506watchdog_start(Timeout) ->
2507    watchdog_start(unknown, Timeout).
2508
2509watchdog_start(Case, Timeout) ->
2510    spawn_link(?MODULE, watchdog, [Case, Timeout, self()]).
2511
2512watchdog_stop(Pid) ->
2513    unlink(Pid),
2514    exit(Pid, kill),
2515    ok.
2516
2517watchdog(Case, Timeout0, Pid) ->
2518    process_flag(priority, max),
2519    Timeout = timeout(Timeout0),
2520    receive
2521    after Timeout ->
2522	    Mon = erlang:monitor(process, Pid),
2523	    case erlang:process_info(Pid) of
2524		undefined ->
2525		    ok;
2526		ProcInfo ->
2527		    Line =
2528			case lists:keysearch(dictionary, 1, ProcInfo) of
2529			    {value, {_, Dict}} when is_list(Dict) ->
2530				case lists:keysearch(test_server_loc, 1, Dict) of
2531				    {value, {_, {_Mod, L}}} when is_integer(L) ->
2532					L;
2533				    _ ->
2534					0
2535				end;
2536			    _ -> % This borders on paranoia, but...
2537				0
2538			end,
2539		    Trap = {timetrap_timeout, Timeout, Line},
2540		    exit(Pid, Trap),
2541		    receive
2542			{'DOWN', Mon, process, Pid, _} ->
2543			    ok
2544		    after 10000 ->
2545			    warning_msg("Failed stopping "
2546					"test case ~p process ~p "
2547					"[~w] after ~w: killing instead",
2548				[Case, Pid, Line, Timeout]),
2549			    exit(Pid, kill)
2550		    end
2551	    end
2552    end.
2553
2554warning_msg(F, A) ->
2555    (catch error_logger:warning_msg(F ++ "~n", A)).
2556
2557timeout(T) ->
2558    trunc(timeout(T, os:type())).
2559
2560timeout(T, _) ->
2561    T * timetrap_scale_factor().
2562
2563timetrap_scale_factor() ->
2564    case (catch test_server:timetrap_scale_factor()) of
2565	{'EXIT', _} ->
2566	    1;
2567	N ->
2568	    N
2569    end.
2570
2571
2572%% ----------------------------------------------------------------------
2573%% file & dir functions
2574%%
2575
2576del_dir(Dir) when is_list(Dir) ->
2577    (catch do_del_dir(Dir)).
2578
2579do_del_dir(Dir) ->
2580    io:format("delete directory ~s~n", [Dir]),
2581    case file:list_dir(Dir) of
2582	{ok, Files} ->
2583	    Files2 = [filename:join(Dir, File) || File <- Files],
2584	    del_dir2(Files2),
2585	    case file:del_dir(Dir) of
2586		ok ->
2587		    io:format("directory ~s deleted~n", [Dir]),
2588		    ok;
2589		{error, eexist} = Error1 ->
2590		    io:format("directory not empty: ~n", []),
2591		    {ok, Files3} = file:list_dir(Dir),
2592		    io:format("found additional files: ~n~p~n",
2593			      [Files3]),
2594		    throw(Error1);
2595		{error, Reason2} = Error2 ->
2596		    io:format("failed deleting directory: ~w~n", [Reason2]),
2597		    throw(Error2)
2598	    end;
2599	Else ->
2600	    Else
2601    end.
2602
2603del_dir2([]) ->
2604    ok;
2605del_dir2([File|Files]) ->
2606    del_file_or_dir(File),
2607    del_dir2(Files).
2608
2609del_file_or_dir(FileOrDir) ->
2610    case file:read_file_info(FileOrDir) of
2611	{ok, #file_info{type = directory}} ->
2612	    do_del_dir(FileOrDir);
2613	{ok, _} ->
2614	    io:format("   delete file ~s~n", [FileOrDir]),
2615	    case file:delete(FileOrDir) of
2616		ok ->
2617		    io:format("   => deleted~n", []),
2618		    ok;
2619		{error, Reason} = Error ->
2620		    io:format("   => failed - ~w~n", [Reason]),
2621		    throw(Error)
2622	    end;
2623
2624	_ ->
2625	    ok
2626    end.
2627
2628
2629%% ----------------------------------------------------------------------
2630%% (debug) Print functions
2631%%
2632
2633f(F, A) ->
2634    lists:flatten(io_lib:format(F, A)).
2635
2636p(Mod, Case) when is_atom(Mod) andalso is_atom(Case) ->
2637    case get(test_case) of
2638	undefined ->
2639	    put(test_case, Case),
2640	    p("~n~n************ ~w:~w ************", [Mod, Case]);
2641	_ ->
2642	    ok
2643    end;
2644
2645p(F, A) when is_list(F) andalso is_list(A) ->
2646    io:format(user, F ++ "~n", A).
2647
2648%% This is just a bog standard printout, with a (formatted) timestamp
2649%% prefix and a newline after.
2650%% print1 - prints to both standard_io and user.
2651%% print2 - prints to just standard_io.
2652
2653print_format(F, A) ->
2654    FTS = snmp_test_lib:formated_timestamp(),
2655    io_lib:format("[~s] " ++ F ++ "~n", [FTS | A]).
2656
2657print1(F, A) ->
2658    S = print_format(F, A),
2659    io:format("~s", [S]),
2660    io:format(user, "~s", [S]).
2661
2662print2(F, A) ->
2663    S = print_format(F, A),
2664    io:format("~s", [S]).
2665
2666
2667print(Prefix, Module, Line, Format, Args) ->
2668    io:format("*** [~s] ~s ~p ~p ~p:~p *** " ++ Format ++ "~n",
2669	      [formated_timestamp(),
2670	       Prefix, node(), self(), Module, Line|Args]).
2671
2672formated_timestamp() ->
2673    snmp_misc:formated_timestamp().
2674
2675
2676%% ----------------------------------------------------------------------
2677%%
2678%% General purpose print functions
2679%% ERROR, WARNING and NOTICE are written both to 'user' and 'standard_io'.
2680%% INFO only to 'standard_io'.
2681%%
2682%% Should we also allow for (optional) a "short name" (sname)?
2683%%
2684
2685%% ERROR print (both to user and standard_io)
2686eprint(F, A) ->
2687    Str = format_print("ERROR", F, A),
2688    io:format(user,        "~s~n", [Str]),
2689    io:format(standard_io, "~s~n", [Str]).
2690
2691%% WARNING print (both to user and standard_io)
2692wprint(F, A) ->
2693    Str = format_print("WARNING", F, A),
2694    io:format(user,        "~s~n", [Str]),
2695    io:format(standard_io, "~s~n", [Str]).
2696
2697%% NOTICE print (both to user and standard_io)
2698nprint(F, A) ->
2699    Str = format_print("NOTICE", F, A),
2700    io:format(user,        "~s~n", [Str]),
2701    io:format(standard_io, "~s~n", [Str]).
2702
2703%% INFO print (only to user)
2704iprint(F, A) ->
2705    Str = format_print("INFO", F, A),
2706    io:format(standard_io, "~s~n", [Str]).
2707
2708format_print(Prefix, F, A) ->
2709    format_print(get(tname), Prefix, F, A).
2710
2711format_print(undefined, Prefix, F, A) ->
2712    f("*** [~s] ~s ~p ~p *** ~n" ++ F ++ "~n",
2713      [formated_timestamp(), Prefix, node(), self() | A]);
2714format_print(TName, Prefix, F, A) when is_atom(TName) ->
2715    format_print(atom_to_list(TName), Prefix, F, A);
2716format_print(TName, Prefix, F, A) when is_list(TName) ->
2717    f("*** [~s] ~s ~s ~p ~p *** ~n" ++ F ++ "~n",
2718      [formated_timestamp(), Prefix, TName, node(), self() | A]).
2719
2720
2721