1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2005-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%%
22%%----------------------------------------------------------------------
23%% Purpose: mstone measurement
24%%
25%%----------------------------------------------------------------------
26
27-module(megaco_codec_mstone1).
28
29
30%% API
31-export([
32	 start/0,          start/1,          start/2,
33	 start_flex/0,     start_flex/1,     start_flex/2,
34	 start_no_drv/0,   start_no_drv/1,   start_no_drv/2,
35	 start_only_drv/0, start_only_drv/1, start_only_drv/2
36	]).
37
38%% Internal exports
39-export([mstone_runner_init/6]).
40
41
42-define(LIB, megaco_codec_mstone_lib).
43
44-ifndef(MSTONE_RUN_TIME).
45-define(MSTONE_RUN_TIME, 10). % minutes
46-endif.
47
48-ifndef(MSTONE_VERSION3).
49-define(MSTONE_VERSION3, v3).
50-endif.
51-define(VERSION3, ?MSTONE_VERSION3).
52
53-ifndef(MSTONE_CODECS).
54-define(MSTONE_CODECS, megaco_codec_transform:codecs()).
55-endif.
56
57-define(DEFAULT_MESSAGE_PACKAGE, megaco_codec_transform:default_message_package()).
58-define(DEFAULT_FACTOR,          1).
59-define(DEFAULT_DRV_INCLUDE,     ignore).
60
61%% -define(VERBOSE_STATS,true).
62
63-ifndef(MSTONE_RUNNER_MIN_HEAP_SZ).
64-define(MSTONE_RUNNER_MIN_HEAP_SZ,  16#ffff).
65-endif.
66-define(MSTONE_RUNNER_OPTS,
67        [link, {min_heap_size, ?MSTONE_RUNNER_MIN_HEAP_SZ}]).
68
69-record(mstone, {id, count, codec, econf, heap_size, reds}).
70
71
72start() ->
73    start(?DEFAULT_FACTOR).
74
75start([Factor]) ->
76    start(?DEFAULT_MESSAGE_PACKAGE, ?MSTONE_RUN_TIME, Factor);
77start([MessagePackage, Factor]) ->
78    start(MessagePackage, ?MSTONE_RUN_TIME, Factor);
79start([MessagePackage, RunTime, Factor]) ->
80    start(MessagePackage, RunTime, Factor);
81start(Factor) ->
82    start(?DEFAULT_MESSAGE_PACKAGE, ?MSTONE_RUN_TIME, Factor).
83
84start(MessagePackage, Factor) ->
85    start(MessagePackage, ?MSTONE_RUN_TIME, Factor).
86
87start(MessagePackage, RunTime, Factor) ->
88    do_start(MessagePackage, RunTime, Factor, ?DEFAULT_DRV_INCLUDE).
89
90
91start_flex() ->
92    start_flex(?DEFAULT_FACTOR).
93
94start_flex([Factor]) ->
95    start_flex(?DEFAULT_MESSAGE_PACKAGE, ?MSTONE_RUN_TIME, Factor);
96start_flex([MessagePackage, Factor]) ->
97    start_flex(MessagePackage, ?MSTONE_RUN_TIME, Factor);
98start_flex([MessagePackage, RunTime, Factor]) ->
99    start_flex(MessagePackage, RunTime, Factor);
100start_flex(Factor) ->
101    start_flex(?DEFAULT_MESSAGE_PACKAGE, ?MSTONE_RUN_TIME, Factor).
102
103start_flex(MessagePackage, Factor) ->
104    do_start(MessagePackage, ?MSTONE_RUN_TIME, Factor, flex).
105
106start_flex(MessagePackage, RunTime, Factor) ->
107    do_start(MessagePackage, RunTime, Factor, flex).
108
109
110start_only_drv() ->
111    start_only_drv(?DEFAULT_FACTOR).
112
113start_only_drv([Factor]) ->
114    start_only_drv(?DEFAULT_MESSAGE_PACKAGE, ?MSTONE_RUN_TIME, Factor);
115start_only_drv([MessagePackage, Factor]) ->
116    start_only_drv(MessagePackage, ?MSTONE_RUN_TIME, Factor);
117start_only_drv([MessagePackage, RunTime, Factor]) ->
118    start_only_drv(MessagePackage, RunTime, Factor);
119start_only_drv(Factor) ->
120    start_only_drv(?DEFAULT_MESSAGE_PACKAGE, ?MSTONE_RUN_TIME, Factor).
121
122start_only_drv(MessagePackage, Factor) ->
123    do_start(MessagePackage, ?MSTONE_RUN_TIME, Factor, only_drv).
124
125start_only_drv(MessagePackage, RunTime, Factor) ->
126    do_start(MessagePackage, RunTime, Factor, only_drv).
127
128
129start_no_drv() ->
130    start_no_drv(?DEFAULT_FACTOR).
131
132start_no_drv([Factor]) ->
133    start_no_drv(?DEFAULT_MESSAGE_PACKAGE, ?MSTONE_RUN_TIME, Factor);
134start_no_drv([MessagePackage, Factor]) ->
135    start_no_drv(MessagePackage, ?MSTONE_RUN_TIME, Factor);
136start_no_drv([MessagePackage, RunTime, Factor]) ->
137    start_no_drv(MessagePackage, RunTime, Factor);
138start_no_drv(Factor) ->
139    start_no_drv(?DEFAULT_MESSAGE_PACKAGE, ?MSTONE_RUN_TIME, Factor).
140
141start_no_drv(MessagePackage, Factor) ->
142    do_start(MessagePackage, ?MSTONE_RUN_TIME, Factor, no_drv).
143
144start_no_drv(MessagePackage, RunTime, Factor) ->
145    do_start(MessagePackage, RunTime, Factor, no_drv).
146
147
148do_start(MessagePackageRaw, RunTimeRaw, FactorRaw, DrvInclude) ->
149    RunTime        = parse_runtime(RunTimeRaw),
150    Factor         = parse_factor(FactorRaw),
151    MessagePackage = parse_message_package(MessagePackageRaw),
152    mstone_init(MessagePackage, RunTime, Factor, DrvInclude).
153
154
155parse_runtime(RunTimeAtom) when is_atom(RunTimeAtom) ->
156    parse_runtime_str(atom_to_list(RunTimeAtom));
157parse_runtime(RunTimeStr) when is_list(RunTimeStr) ->
158    parse_runtime_str(RunTimeStr);
159parse_runtime(RunTime) when is_integer(RunTime) andalso (RunTime > 0) ->
160    timer:minutes(RunTime);
161parse_runtime(BadRunTime) ->
162    throw({error, {bad_runtime, BadRunTime}}).
163
164parse_runtime_str(RuneTimeStr) ->
165    try
166        begin
167            case lists:reverse(RuneTimeStr) of
168                [$s|Rest] ->
169                    timer:seconds(list_to_integer(lists:reverse(Rest)));
170                [$m|Rest] ->
171                    timer:minutes(list_to_integer(lists:reverse(Rest)));
172                [$h|Rest] ->
173                    timer:hours(list_to_integer(lists:reverse(Rest)));
174                _ ->
175                    timer:minutes(list_to_integer(RuneTimeStr))
176            end
177        end
178    catch
179        _:_:_ ->
180            throw({error, {bad_runtime, RuneTimeStr}})
181    end.
182
183
184parse_factor(FactorAtom) when is_atom(FactorAtom) ->
185    case (catch list_to_integer(atom_to_list(FactorAtom))) of
186	Factor when is_integer(Factor) andalso (Factor > 0) ->
187	    Factor;
188	_ ->
189	    io:format("ERROR: Bad factor value: ~p~n", [FactorAtom]),
190	    throw({error, {bad_factor, FactorAtom}})
191    end;
192parse_factor(FactorRaw) when is_list(FactorRaw) ->
193    case (catch list_to_integer(FactorRaw)) of
194	Factor when is_integer(Factor) andalso (Factor > 0) ->
195	    Factor;
196	_ ->
197	    io:format("ERROR: Bad factor value: ~p~n", [FactorRaw]),
198	    throw({error, {bad_factor, FactorRaw}})
199    end;
200parse_factor(Factor) when is_integer(Factor) andalso (Factor > 0) ->
201    Factor;
202parse_factor(BadFactor) ->
203    throw({error, {bad_factor, BadFactor}}).
204
205
206parse_message_package(MessagePackageRaw) when is_list(MessagePackageRaw) ->
207    list_to_atom(MessagePackageRaw);
208parse_message_package(MessagePackage) when is_atom(MessagePackage) ->
209    MessagePackage;
210parse_message_package(BadMessagePackage) ->
211    throw({error, {bad_message_package, BadMessagePackage}}).
212
213
214%% Codecs is a list of megaco codec shortnames:
215%%
216%%    pretty | compact | ber | per | erlang
217%%
218
219mstone_init(MessagePackage, RunTime, Factor, DrvInclude) ->
220%%     io:format("mstone_init -> entry with"
221%% 	      "~n   MessagePackage: ~p"
222%% 	      "~n   RunTime:        ~p"
223%% 	      "~n   Factor:         ~p"
224%% 	      "~n   DrvInclude:     ~p"
225%% 	      "~n", [MessagePackage, RunTime, Factor, DrvInclude]),
226    Codecs = ?MSTONE_CODECS,
227    mstone_init(MessagePackage, RunTime, Factor, Codecs, DrvInclude).
228
229mstone_init(MessagePackage, RunTime, Factor, Codecs, DrvInclude) ->
230    Parent = self(),
231    Pid = spawn(
232	    fun() ->
233		    process_flag(trap_exit, true),
234		    do_mstone(MessagePackage,
235                              RunTime, Factor, Codecs, DrvInclude),
236		    Parent ! {done, self()}
237	    end),
238    receive
239	{done, Pid} ->
240	    ok
241    end.
242
243do_mstone(MessagePackage, RunTime, Factor, Codecs, DrvInclude) ->
244    io:format("~n", []),
245    ?LIB:display_os_info(),
246    ?LIB:display_system_info(),
247    ?LIB:display_app_info(),
248    io:format("~n", []),
249    (catch asn1rt_driver_handler:load_driver()),
250    {Pid, Conf} = ?LIB:start_flex_scanner(),
251    put(flex_scanner_conf, Conf),
252    EMessages = ?LIB:expanded_messages(MessagePackage, Codecs, DrvInclude),
253    EMsgs  = duplicate(Factor, EMessages),
254    MStone = t1(RunTime, EMsgs),
255    ?LIB:stop_flex_scanner(Pid),
256    io:format("~n", []),
257    io:format("MStone: ~p~n", [MStone]).
258
259duplicate(N, Elements) ->
260    duplicate(N, Elements, []).
261
262duplicate(_N, [], Acc) ->
263    lists:flatten(Acc);
264duplicate(N, [H|T], Acc) ->
265    duplicate(N, T, [lists:duplicate(N, H)|Acc]).
266
267t1(RunTime, EMsgs) ->
268    io:format(" * starting runners [~w] ", [length(EMsgs)]),
269    t1(RunTime, EMsgs, []).
270
271t1(_RunTime, [], Runners) ->
272    io:format(" done~n * await runners ready ", []),
273    await_runners_ready(Runners),
274    io:format(" done~n * now snooze", []),
275    receive after 5000 -> ok end,
276    io:format("~n * release them~n", []),
277    lists:foreach(fun(P) -> P ! {go, self()} end, Runners),
278    t2(1, [], Runners);
279t1(RunTime, [H|T], Runners) ->
280    Runner = init_runner(RunTime, H),
281    io:format(".", []),
282    t1(RunTime, T, [Runner|Runners]).
283
284await_runners_ready([]) ->
285    ok;
286await_runners_ready(Runners) ->
287    receive
288        {ready, Runner} ->
289            io:format(".", []),
290	    %% i("runner ~w ready", [Runner]),
291            await_runners_ready(lists:delete(Runner, Runners));
292	{'EXIT', Pid, Reason} ->
293	    case lists:member(Pid, Runners) of
294		true ->
295		    io:format("~nERROR: "
296			      "received (unexpected) exit signal "
297			      "from from runner ~p:"
298			      "~n~p~n", [Pid, Reason]),
299		    exit(Reason);
300		false ->
301		    await_runners_ready(Runners)
302	    end
303    end.
304
305-ifdef(VERBOSE_STATS).
306print_runner_stats(RunnerStats) ->
307    Sorted = lists:keysort(2, RunnerStats),
308    lists:foreach(fun(#mstone{id        = Id,
309			      count     = Num,
310                              codec     = Codec,
311                              econf     = EConf,
312                              heap_size = HeapSz,
313                              reds      = Reds}) ->
314			  i("runner: ~w"
315			    "~n   Count:           ~w"
316			    "~n   Codec:           ~w"
317			    "~n   Encoding config: ~p"
318			    "~n   Heap size:       ~p"
319			    "~n   Reductions:      ~p",
320			    [Id, Num, Codec, EConf, HeapSz, Reds]) end,
321                  Sorted),
322    ok.
323-else.
324print_runner_stats(_) ->
325    ok.
326-endif.
327
328t2(_, Acc, []) ->
329    i("~n~w runners", [length(Acc)]),
330    print_runner_stats(Acc),
331
332    HeapSzAcc = lists:sort([HS || #mstone{heap_size = HS} <- Acc]),
333    i("Runner heap size data:"
334      "~n   Min: ~w"
335      "~n   Max: ~w"
336      "~n   Avg: ~w",
337      [hd(HeapSzAcc),
338       hd(lists:reverse(HeapSzAcc)),
339       (lists:sum(HeapSzAcc) div length(HeapSzAcc))]),
340
341    RedsAcc   = lists:sort([R || #mstone{reds = R} <- Acc]),
342    i("Runner reductions data:"
343      "~n   Min: ~w"
344      "~n   Max: ~w"
345      "~n   Avg: ~w",
346      [hd(RedsAcc),
347       hd(lists:reverse(RedsAcc)),
348       (lists:sum(RedsAcc) div length(RedsAcc))]),
349
350    lists:sum([Num || #mstone{count = Num} <- Acc]);
351t2(N, Acc, Runners) ->
352    receive
353	{'EXIT', Pid, {runner_done, Codec, Conf, Num, Info}} ->
354            {value, {_, HeapSz}} = lists:keysearch(heap_size,  1, Info),
355            {value, {_, Reds}}   = lists:keysearch(reductions, 1, Info),
356            MStone = #mstone{id        = N,
357			     count     = Num,
358                             codec     = Codec,
359                             econf     = Conf,
360                             heap_size = HeapSz,
361                             reds      = Reds},
362	    t2(N + 1, [MStone|Acc], lists:delete(Pid, Runners))
363    end.
364
365init_runner(RunTime, {Codec, Mod, Conf, Msgs}) ->
366    Conf1 = runner_conf(Conf),
367    Conf2 = [{version3,?VERSION3}|Conf1],
368    Pid   = spawn_opt(?MODULE, mstone_runner_init,
369		      [RunTime, Codec, self(), Mod, Conf2, Msgs],
370		      ?MSTONE_RUNNER_OPTS),
371    Pid.
372
373runner_conf([flex_scanner]) ->
374    get(flex_scanner_conf);
375runner_conf(Conf) ->
376    Conf.
377
378
379
380detect_versions(Codec, _Conf, [], []) ->
381    exit({no_messages_found_for_codec, Codec});
382detect_versions(_Codec, _Conf, [], Acc) ->
383    lists:reverse(Acc);
384detect_versions(Codec, Conf, [{_Name, Bin}|Bins], Acc) ->
385    Data = ?LIB:detect_version(Codec, Conf, Bin),
386    detect_versions(Codec, Conf, Bins, [Data|Acc]).
387
388
389mstone_runner_init(RunTime, _Codec, Parent, Mod, Conf, Msgs0) ->
390    Msgs = detect_versions(Mod, Conf, Msgs0, []),
391    warmup(Mod, Conf, Msgs, []),
392    Parent ! {ready, self()},
393    receive
394        {go, Parent} ->
395            ok
396    end,
397    erlang:send_after(RunTime, self(), stop),
398    mstone_runner_loop(Parent, Mod, Conf, 0, Msgs).
399
400mstone_runner_loop(Parent, Mod, Conf, N, Msgs1) ->
401    receive
402        stop ->
403            exit({runner_done, Mod, Conf, N, mstone_runner_process_info()})
404    after 0 ->
405        {Inc, Msgs2} = mstone_all(Mod, Conf, Msgs1, []),
406	mstone_runner_loop(Parent, Mod, Conf, N+Inc, Msgs2)
407    end.
408
409mstone_runner_process_info() ->
410    PI = process_info(self()),
411    FL = [heap_size, stack_size, reductions],
412    lists:filter(fun({Key, _}) -> lists:member(Key, FL) end, PI).
413
414
415mstone_all(_Codec, _Conf, [], Acc) ->
416    {length(Acc), lists:reverse(Acc)};
417mstone_all(Codec, Conf, [{V, Bin}|Bins], Acc) when is_binary(Bin) ->
418    {ok, Msg} = apply(Codec, decode_message, [Conf, V, Bin]),
419    mstone_all(Codec, Conf, Bins, [{V, Msg}|Acc]);
420mstone_all(Codec, Conf, [{V, Msg}|Msgs], Acc) ->
421    {ok, Bin} = apply(Codec, encode_message, [Conf, V, Msg]),
422    mstone_all(Codec, Conf, Msgs, [{V, Bin}|Acc]).
423
424warmup(_Codec, _Conf, [], Acc) ->
425    lists:reverse(Acc);
426warmup(Codec, Conf, [{V, M}|Msgs], Acc) ->
427%%     io:format("~p warmup -> entry with"
428%% 	      "~n   Codec: ~p"
429%% 	      "~n   Conf:  ~p"
430%% 	      "~n", [self(), Codec, Conf]),
431    case (catch apply(Codec, decode_message, [Conf, V, M])) of
432        {ok, Msg} ->
433            case (catch apply(Codec, encode_message, [Conf, V, Msg])) of
434                {ok, Bin} ->
435                    warmup(Codec, Conf, Msgs, [Bin|Acc]);
436                EncodeError ->
437                    emsg("failed encoding message: ~n~p", [EncodeError])
438            end;
439        DecodeError ->
440            emsg("failed decoding message: "
441		 "~n   DecodeError: ~p"
442		 "~n   V:           ~p"
443		 "~n   M:           ~p", [DecodeError, V, M])
444    end.
445
446
447%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
448
449%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
450
451emsg(F, A) ->
452    error_logger:error_msg(F ++ "~n", A).
453
454%% i(F) ->
455%%     i(F, []).
456i(F, A) ->
457    io:format(F ++ "~n", A).
458
459