diff options
Diffstat (limited to 'lib/megaco/examples/meas/megaco_codec_meas.erl')
-rw-r--r-- | lib/megaco/examples/meas/megaco_codec_meas.erl | 655 |
1 files changed, 655 insertions, 0 deletions
diff --git a/lib/megaco/examples/meas/megaco_codec_meas.erl b/lib/megaco/examples/meas/megaco_codec_meas.erl new file mode 100644 index 0000000000..88b34105ac --- /dev/null +++ b/lib/megaco/examples/meas/megaco_codec_meas.erl @@ -0,0 +1,655 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2002-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%---------------------------------------------------------------------- +%% Purpose: Measure megaco codec's encoding & decoding time's +%% +%% Measurement process consists of: +%% For each message in a directory: +%% Pre: Read message from the file, close the file +%% Measurement: 1) measure decode +%% 2) measure encode (of the previously decoded message) +%% Post: Print average +%% For each directory: +%% A summery is written, both to the console and to a file, +%% in an excel compatible format. +%% +%% megaco_codec_meas:t(). +%% megaco_codec_meas:t([pretty, compact]). +%% megaco_codec_meas:t([per, pretty, compact]). +%% +%%---------------------------------------------------------------------- + +-module(megaco_codec_meas). + +%% -compile(export_all). + + +%% API +-export([start/0, start/1]). +-export([start1/0]). + +%% Internal exports +-export([do_measure_codec/7, do_measure_codec_loop/7]). +-export([flex_scanner_handler/1]). + + +-include_lib("kernel/include/file.hrl"). + +-define(V3, v3). + +-define(MEASURE_TIMEOUT, 100000). % 100 sec + +-ifndef(MEASURE_COUNT_TIME). +-define(MEASURE_COUNT_TIME, 1*1000*1000). % 1 seconds +-endif. + +-ifndef(MEASURE_TIME). +-define(MEASURE_TIME, 10000). +-endif. + +-ifndef(MEASURE_CODECS). +-define(MEASURE_CODECS, megaco_codec_transform:codecs()). +-endif. + +-define(DEFAULT_MESSAGE_PACKAGE, megaco_codec_transform:default_message_package()). + +-record(stat, {name, ecount, etime, dcount, dtime, size}). + + +%% Runs the measurement on all "official" codecs + +start1() -> + put(everbose,true), + start(). + +start() -> + meas_init(?DEFAULT_MESSAGE_PACKAGE, ?MEASURE_CODECS). + +start([MessagePackage]) -> + do_start(MessagePackage, ?MEASURE_CODECS); +start(MessagePackage) -> + do_start(MessagePackage, ?MEASURE_CODECS). + +do_start(MessagePackageRaw, Codecs) -> + MessagePackage = parse_message_package(MessagePackageRaw), + meas_init(MessagePackage, Codecs). + +parse_message_package(MessagePackageRaw) when is_list(MessagePackageRaw) -> + list_to_atom(MessagePackageRaw); +parse_message_package(MessagePackage) when is_atom(MessagePackage) -> + MessagePackage; +parse_message_package(BadMessagePackage) -> + throw({error, {bad_message_package, BadMessagePackage}}). + + +%% Dirs is a list of directories containing files, +%% each with a single megaco message. +%% +%% Note that it is a requirement that each dir has +%% the name of the codec with which the messages has +%% been encoded: +%% +%% pretty | compact | ber | per | erlang +%% + +meas_init(MessagePackage, Codecs) -> + %% process_flag(trap_exit, true), + io:format("~nRun meas on message package: ~p~n~n", [MessagePackage]), + display_os_info(), + display_system_info(), + display_app_info(), + io:format("~n", []), + Started = now(), + case megaco_codec_transform:messages(MessagePackage) of + Messages when is_list(Messages) -> + ExpandedMessages = expand_messages(Codecs, Messages), + Results = t1(ExpandedMessages, []), + display_time(Started, now()), + store_results(Results); + Error -> + Error + end. + +display_os_info() -> + V = case os:version() of + {Major, Minor, Release} -> + lists:flatten( + io_lib:format("~w.~w.~w", [Major, Minor, Release])); + Str -> + Str + end, + case os:type() of + {OsFam, OsName} -> + io:format("OS: ~p-~p: ~s~n", [OsFam, OsName, V]); + OsFam -> + io:format("OS: ~p: ~s~n", [OsFam, V]) + end. + +display_system_info() -> + SysArch = string:strip(erlang:system_info(system_architecture),right,$\n), + SysVer = string:strip(erlang:system_info(system_version),right,$\n), + io:format("System architecture: ~s~n", [SysArch]), + io:format("System version: ~s~n", [SysVer]), + ok. + + +display_app_info() -> + display_megaco_info(), + display_asn1_info(). + +display_megaco_info() -> + MI = megaco:module_info(), + {value, {attributes, Attr}} = lists:keysearch(attributes, 1, MI), + {value, {app_vsn, Ver}} = lists:keysearch(app_vsn, 1, Attr), + FlexStr = + case megaco_flex_scanner:is_enabled() of + true -> + case megaco_flex_scanner:is_reentrant_enabled() of + true -> + "reentrant flex"; + false -> + "non-reentrant flex" + end; + false -> + "no flex" + end, + io:format("Megaco version: ~s (~s)~n", [Ver, FlexStr]). + +display_asn1_info() -> + AI = megaco_ber_bin_drv_media_gateway_control_v1:info(), + Vsn = + case lists:keysearch(vsn, 1, AI) of + {value, {vsn, V}} when is_atom(V) -> + atom_to_list(V); + {value, {vsn, V}} when is_list(V) -> + V; + _ -> + "unknown" + end, + io:format("ASN.1 version: ~s~n", [Vsn]). + + +%% {MegaSec, Sec, MicroSec} +display_time(Start, Fin) -> + FormatDate1 = format_timestamp(Start), + FormatDate2 = format_timestamp(Fin), + FormatDiff = format_diff(Start, Fin), + io:format("Started: ~s~n", [FormatDate1]), + io:format("Finished: ~s~n", [FormatDate2]), + io:format(" ~s~n~n~n", [FormatDiff]), + ok. + +format_timestamp({_N1, _N2, N3} = Now) -> + {Date, Time} = calendar:now_to_datetime(Now), + {YYYY,MM,DD} = Date, + {Hour,Min,Sec} = Time, + FormatDate = + io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", + [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), + lists:flatten(FormatDate). + +format_diff(Start, Fin) -> + DateTime1 = calendar:now_to_universal_time(Start), + DateTime2 = calendar:now_to_universal_time(Fin), + T1 = calendar:datetime_to_gregorian_seconds(DateTime1), + T2 = calendar:datetime_to_gregorian_seconds(DateTime2), + {_, Diff} = calendar:gregorian_seconds_to_datetime(T2 - T1), + Tmp = + case Diff of + {0, 0, S} -> + io_lib:format("~.2.0w sec", [S]); + {0, M, S} -> + io_lib:format("~w min ~.2.0w sec", [M,S]); + {H, M, S} -> + io_lib:format("~w hour ~w min ~.2.0w sec", [H,M,S]) + end, + lists:flatten(Tmp). + + + +t1([], Results) -> + lists:reverse(Results); +t1([{Id, Codec, Conf, _, _} = ECodec|EMsgs], Results) -> + case (catch measure(ECodec)) of + {'EXIT', Reason} -> + error("measure of codec ~p exited: ~n~p", [Codec, Reason]), + t1(EMsgs, Results); + {error, Reason} -> + error("skipping codec ~p: ~n~p", [Codec, Reason]), + t1(EMsgs, Results); + {ok, Res} -> + t1(EMsgs, [{Id, Conf, Res}| Results]) + end. + + +measure({Id, Codec, Conf, Count, Msgs}) -> + io:format("measure using codec ~p ~p~n ", [Codec, Conf]), + {Init, Conf1} = measure_init(Conf), + Conf2 = [{version3,?V3}|Conf1], + Res = measure(Id, Codec, Conf2, Msgs, [], Count), + measure_fin(Init), + Res. + + +expand_messages(Codecs, Messages) -> + ECodecs = expand_codecs(Codecs, []), + expand_messages(ECodecs, Messages, []). + +expand_messages([], _, EMessages) -> + lists:reverse(EMessages); +expand_messages([{Id, Codec, Conf, Count} | ECodecs], Messages, EMessages) -> + case lists:keysearch(Id, 1, Messages) of + {value, {Id, Msgs}} -> + expand_messages(ECodecs, Messages, + [{Id, Codec, Conf, Count, Msgs}|EMessages]); + false -> + exit({error, {no_such_codec_data, Id}}) + end. + +expand_codecs([], ECodecs) -> + lists:reverse(lists:flatten(ECodecs)); +expand_codecs([Codec|Codecs], ECodecs) when is_atom(Codec) -> + ECodec = expand_codec(Codec), + expand_codecs(Codecs, [ECodec|ECodecs]). + +expand_codec(Codec) -> + case Codec of + pretty -> + [{Codec, megaco_pretty_text_encoder, [flex_scanner], 2000}, + {Codec, megaco_pretty_text_encoder, [], 1000}]; + compact -> + [{Codec, megaco_compact_text_encoder, [flex_scanner], 3000}, + {Codec, megaco_compact_text_encoder, [], 1500}]; + ber -> + [{Codec, megaco_ber_bin_encoder, [driver,native], 4000}, + {Codec, megaco_ber_bin_encoder, [native], 3000}, + {Codec, megaco_ber_bin_encoder, [driver], 3000}, + {Codec, megaco_ber_bin_encoder, [], 1000}]; + per -> + [{Codec, megaco_per_bin_encoder, [driver,native], 4000}, + {Codec, megaco_per_bin_encoder, [native], 3000}, + {Codec, megaco_per_bin_encoder, [driver], 3000}, + {Codec, megaco_per_bin_encoder, [], 1000}]; + erlang -> + [ + {Codec, megaco_erl_dist_encoder, [megaco_compressed,compressed], 500}, + {Codec, megaco_erl_dist_encoder, [compressed], 400}, + {Codec, megaco_erl_dist_encoder, [megaco_compressed], 10000}, + {Codec, megaco_erl_dist_encoder, [], 10000} + ]; + Else -> + exit({error, {invalid_codec, Else}}) + end. + + +measure_init([flex_scanner]) -> + start_flex_scanner(); +measure_init(Conf) -> + {undefined, Conf}. + + +measure_fin(Pid) when is_pid(Pid) -> + stop_flex_scanner(Pid), + ok; +measure_fin(_) -> + ok. + + +measure(_Dir, _Codec, _Conf, [], [], _MCount) -> + {error, no_messages}; + +measure(_Dir, _Codec, _Conf, [], Res, _MCount) -> + + Eavg = avg([Etime/Ecnt || #stat{ecount = Ecnt, etime = Etime} <- Res]), + Davg = avg([Dtime/Dcnt || #stat{dcount = Dcnt, dtime = Dtime} <- Res]), + Savg = avg([Size || #stat{size = Size} <- Res]), + + io:format("~n Measurment on ~p messages:" + "~n Average size: ~w bytes, " + "~n encode: ~w microsec, " + "~n decode: ~w microsec~n~n", + [length(Res), Savg, Eavg, Davg]), + + {ok, lists:reverse(Res)}; + +measure(Dir, Codec, Conf, [{Name, Bin}|Msgs], Results, MCount) -> + io:format(" ~p", [Name]), + case (catch do_measure(Dir, Codec, Conf, Name, Bin, MCount)) of + {ok, Stat} -> + measure(Dir, Codec, Conf, Msgs, [Stat | Results], MCount); + + {error, S} -> + io:format("~n~s failed: ~n", [Name]), + error(S,[]), + measure(Dir, Codec, Conf, Msgs, Results, MCount); + + {info, S} -> + case get(verbose) of + true -> + io:format("~n", []), + info(S,[]); + _ -> + io:format("~n~s skipped~n", [Name]) + end, + measure(Dir, Codec, Conf, Msgs, Results, MCount) + + end. + + +do_measure(_Id, Codec, Conf, Name, BinMsg, MCount) -> + %% io:format("~n~s~n", [binary_to_list(BinMsg)]), + {Version, NewBin} = detect_version(Codec, Conf, BinMsg), + {Msg, Dcnt, Dtime} = measure_decode(Codec, Conf, Version, NewBin, MCount), + {_, Ecnt, Etime} = measure_encode(Codec, Conf, Version, Msg, MCount), + + {ok, #stat{name = Name, + ecount = Ecnt, etime = Etime, + dcount = Dcnt, dtime = Dtime, + size = size(NewBin)}}. + +detect_version(Codec, Conf, Bin) -> + case (catch Codec:version_of(Conf, Bin)) of + {ok, V} -> + io:format("[~w]", [V]), + {ok, M} = Codec:decode_message(Conf, V, Bin), + {ok, NewBin} = Codec:encode_message(Conf, V, M), + io:format("[~w]", [size(NewBin)]), + {V, NewBin}; + Error -> + io:format("~nversion detection failed:~n~p", [Error]), + Error + end. + + +measure_decode(Codec, Conf, Version, Bin, MCount) -> + case measure_codec(Codec, decode_message, Conf, Version, Bin, MCount) of + {ok, Res} -> + Res; + {error, Reason} -> + S = format("decode failed for ~p:~n~p", [Codec, Reason]), + throw({error, S}) + end. + +measure_encode(Codec, Conf, Version, Bin, MCount) -> + case measure_codec(Codec, encode_message, Conf, Version, Bin, MCount) of + {ok, Res} -> + Res; + {error, Reason} -> + S = format("encode failed for ~p:~n~p", [Codec, Reason]), + throw({error, S}) + end. + + +measure_codec(Codec, Func, Conf, Version, Bin, MCount) -> + Pid = spawn_link(?MODULE, do_measure_codec, + [self(), Codec, Func, Conf, Version, Bin, MCount]), + receive + {measure_result, Pid, Func, Res} -> + {ok, Res}; + {error, Pid, Error} -> + {error, Error}; + Else -> + {error, {unexpected_result, Else}} + after ?MEASURE_TIMEOUT -> + Info = + case (catch process_info(Pid)) of + I when is_list(I) -> + exit(Pid, kill), + I; + _ -> + undefined + end, + {error, {timeout, MCount, Info}} + end. + + +do_measure_codec(Parent, Codec, Func, Conf, Version, Bin, MCount) -> + {ok, Count} = measure_warmup(Codec, Func, Conf, Version, Bin, MCount), + Res = timer:tc(?MODULE, do_measure_codec_loop, + [Codec, Func, Conf, Version, Bin, Count, dummy]), + case Res of + {Time, {ok, M}} -> + %% io:format("~w ", [Time]), + Parent ! {measure_result, self(), Func, {M, Count, Time}}; + {_Time, Error} -> + Parent ! {error, self(), Error} + end, + unlink(Parent). % Make sure Parent don't get our exit signal + + +%% This function does more mor less what the real measure function +%% above does. But with the diff: +%% 1) Warmup to ensure that all used code are loaded +%% 2) To aproximate the encoding time, to ensure that +%% the real encode is done with enough iterations. +measure_warmup(Codec, Func, Conf, Version, M, MCount) -> + Res = timer:tc(?MODULE, do_measure_codec_loop, + [Codec, Func, Conf, Version, M, MCount, dummy]), + case Res of + {Time, {ok, _}} -> + %% OK so far, now calculate the count: + Count = round(?MEASURE_COUNT_TIME/(Time/MCount)), + %% io:format("~w ", [Count]), + {ok, Count}; + {_Time, Error} -> + {error, {warmup_failed, Error}} + end. + + +do_measure_codec_loop(_Codec, _Func, _Conf, _Version, _Bin, 0, M) -> + {ok, M}; +do_measure_codec_loop(Codec, Func, Conf, Version, Bin, Count, _) -> + {ok, M} = apply(Codec, Func, [Conf, Version, Bin]), + do_measure_codec_loop(Codec, Func, Conf, Version, Bin, Count - 1, M). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +store_results(Results) -> + io:format("storing: ~n", []), + store_excel_message_size(Results), + store_excel_decode_time(Results), + store_excel_encode_time(Results), + store_excel_total_time(Results), + io:format("~n", []), + ok. + + +store_excel_message_size(Res) -> + Filename = "message_size.xls", + io:format(" creating ~s~n", [Filename]), + {ok, Fd} = file:open(Filename,[write]), + Sizes = message_sizes(Res, []), + store_excel_tab(Fd, Sizes), + ok. + +store_excel_decode_time(Res) -> + Filename = "decode_time.xls", + io:format(" creating ~s~n", [Filename]), + {ok, Fd} = file:open(Filename,[write]), + Decodes = dec_times(Res, []), + store_excel_tab(Fd, Decodes), + ok. + +store_excel_encode_time(Res) -> + Filename = "encode_time.xls", + io:format(" creating ~s~n", [Filename]), + {ok, Fd} = file:open(Filename,[write]), + Encodes = enc_times(Res, []), + store_excel_tab(Fd, Encodes), + ok. + +store_excel_total_time(Res) -> + Filename = "total_time.xls", + io:format(" creating ~s~n", [Filename]), + {ok, Fd} = file:open(Filename,[write]), + Totals = tot_times(Res, []), + store_excel_tab(Fd, Totals), + ok. + + +message_sizes([], Sizes) -> + lists:reverse(Sizes); +message_sizes([{Dir, Conf, Res}|T], Acc) -> + Sizes = [Size || #stat{size = Size} <- Res], + Avg = avg(Sizes), + message_sizes(T, [{Dir, Conf, Avg, Sizes}|Acc]). + +dec_times([], Times) -> + lists:reverse(Times); +dec_times([{Dir, Conf, Res}|T], Acc) -> + Times = [Time/Count || #stat{dcount = Count, dtime = Time} <- Res], + Avg = avg(Times), + dec_times(T, [{Dir, Conf, Avg, Times}|Acc]). + +enc_times([], Times) -> + lists:reverse(Times); +enc_times([{Dir, Conf, Res}|T], Acc) -> + Times = [Time/Count || #stat{ecount = Count, etime = Time} <- Res], + Avg = avg(Times), + enc_times(T, [{Dir, Conf, Avg, Times}|Acc]). + +tot_times([], Times) -> + lists:reverse(Times); +tot_times([{Dir, Conf, Res}|T], Acc) -> + Times = [(Etime/Ecnt)+(Dtime/Dcnt) || #stat{ecount = Ecnt, + etime = Etime, + dcount = Dcnt, + dtime = Dtime} <- Res], + Avg = avg(Times), + tot_times(T, [{Dir, Conf, Avg, Times}|Acc]). + + +avg(Vals) -> + round(lists:sum(Vals)/length(Vals)). + + +store_excel_tab(_Fd, []) -> + ok; % Just in case there was something wrong with the test +store_excel_tab(Fd, Res) -> + %% For all elements of this list, the Values is of the same length... + [{_, _, _, Values}|_] = Res, + store_excel_tab_header(Fd, length(Values), 1), + store_excel_tab1(Fd, Res). + +store_excel_tab1(Fd, []) -> + io:format(Fd, "~n", []); +store_excel_tab1(Fd, [{Dir, Conf, Avg, Values}|T]) when is_list(Conf) -> + io:format(Fd, "~s~s (~w)", + [filename:basename(Dir), config_to_string(Conf), Avg]), + store_excel_tab_row(Fd, Values), + store_excel_tab1(Fd, T). + +config_to_string([]) -> + ""; +config_to_string([C]) when is_atom(C) -> + io_lib:format("_~w", [C]); +config_to_string([C|Cs]) when is_atom(C) -> + lists:flatten(io_lib:format("_~w", [C]) ++ config_to_string(Cs)). + +store_excel_tab_header(Fd, 0, _) -> + io:format(Fd, "~n", []); +store_excel_tab_header(Fd, N, M) -> + io:format(Fd, "\t~w", [M]), + store_excel_tab_header(Fd, N-1, M+1). + +store_excel_tab_row(Fd, []) -> + io:format(Fd, "~n", []); +store_excel_tab_row(Fd, [Value|Values]) -> + io:format(Fd, "\t~w", [round(Value)]), + store_excel_tab_row(Fd, Values). + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start_flex_scanner() -> + Pid = proc_lib:spawn(?MODULE, flex_scanner_handler, [self()]), + receive + {flex_scanner_started, Pid, Conf} -> + {Pid, [Conf]}; + {flex_scanner_error, {failed_loading_flex_scanner_driver, Reason}} -> + throw({error, {failed_loading_flex_scanner_driver, Reason}}); + {flex_scanner_error, Reason} -> + throw({error, {failed_loading_flex_scanner_driver, Reason}}) + after 10000 -> + exit(Pid, kill), + throw({error, {failed_starting_flex_scanner, timeout}}) + end. + +stop_flex_scanner(Pid) -> + Pid ! stop_flex_scanner. + +flex_scanner_handler(Pid) -> + case (catch megaco_flex_scanner:start()) of + {ok, Port} when is_port(Port) -> + Pid ! {flex_scanner_started, self(), {flex, Port}}, + flex_scanner_handler(Pid, Port); + {ok, Ports} when is_tuple(Ports) -> + Pid ! {flex_scanner_started, self(), {flex, Ports}}, + flex_scanner_handler(Pid, Ports); + {error, {load_driver, {open_error, Reason}}} -> + Error = {failed_loading_flex_scanner_driver, Reason}, + Pid ! {flex_scanner_error, Error}, + exit(Error); + Else -> + Error = {unknown_result_from_start_flex_scanner, Else}, + Pid ! {flex_scanner_error, Error}, + exit(Error) + end. + +flex_scanner_handler(Pid, PortOrPorts) -> + receive + {ping, Pinger} -> + Pinger ! {pong, self()}, + flex_scanner_handler(Pid, PortOrPorts); + {'EXIT', Port, Reason} -> + case megaco_flex_scanner:is_scanner_port(Port, PortOrPorts) of + true -> + Pid ! {flex_scanner_exit, Reason}, + exit({flex_scanner_exit, Reason}); + false -> + info("exit signal from unknown port ~p" + "~n Reason: ~p", [Port, Reason]), + flex_scanner_handler(Pid, PortOrPorts) + end; + stop_flex_scanner -> + megaco_flex_scanner:stop(PortOrPorts), + exit(normal); + Other -> + info("flex scanner handler got something:~n~p", [Other]), + flex_scanner_handler(Pid, PortOrPorts) + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +info(F, A) -> + io:format(F ++ "~n", A). + + +error(F, A) -> + io:format("ERROR: " ++ F ++ "~n", A). + + +format(F, A) -> + lists:flatten(io_lib:format(F, A)). |