1%%% This code was developped by IDEALX (http://IDEALX.org/) and 2%%% contributors (their names can be found in the CONTRIBUTORS file). 3%%% Copyright (C) 2000-2001 IDEALX 4%%% 5%%% This program is free software; you can redistribute it and/or modify 6%%% it under the terms of the GNU General Public License as published by 7%%% the Free Software Foundation; either version 2 of the License, or 8%%% (at your option) any later version. 9%%% 10%%% This program is distributed in the hope that it will be useful, 11%%% but WITHOUT ANY WARRANTY; without even the implied warranty of 12%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13%%% GNU General Public License for more details. 14%%% 15%%% You should have received a copy of the GNU General Public License 16%%% along with this program; if not, write to the Free Software 17%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 18%%% 19 20%%% In addition, as a special exception, you have the permission to 21%%% link the code of this program with any library released under 22%%% the EPL license and distribute linked combinations including 23%%% the two. 24 25-module(ts_utils). 26-vc('$Id$ '). 27-author('nicolas.niclausse@niclux.org'). 28 29-include("ts_macros.hrl"). 30 31%% to get file_info record definition 32-include_lib("kernel/include/file.hrl"). 33 34%% user interface 35-export([debug/3, debug/4, get_val/1, init_seed/0, chop/1, elapsed/2, 36 now_sec/0, node_to_hostname/1, add_time/2, keyumerge/3, key1search/2, 37 level2int/1, mkey1search/2, datestr/0, datestr/1, size_or_length/1, 38 erl_system_args/0, erl_system_args/1, setsubdir/1, export_text/1, 39 foreach_parallel/2, spawn_par/3, inet_setopts/3, resolve/2, 40 stop_all/2, stop_all/3, stop_all/4, join/2, split/2, split2/2, split2/3, 41 make_dir/1, make_dir_raw/1, is_ip/1, from_https/1, to_https/1, keymax/2, 42 check_sum/3, check_sum/5, clean_str/1, file_to_list/1, term_to_list/1, 43 decode_base64/1, encode_base64/1, to_lower/1, 44 randomstr/1,urandomstr/1,urandomstr_noflat/1, eval/1, list_to_number/1, 45 time2sec/1, time2sec_hires/1, read_file_raw/1, init_seed/1, jsonpath/2, 46 concat_atoms/1, ceiling/1, accept_loop/3, append_to_filename/3, splitchar/2, 47 randombinstr/1,urandombinstr/1,log_transaction/1,conv_entities/1, wildcard/2, 48 ensure_all_started/2, pmap/2, pmap/3, get_node_id/0, filtermap/2, new_ets/2, 49 is_controller/0, spread_list/1, pack/1, random_alphanumstr/1]). 50 51level2int("debug") -> ?DEB; 52level2int("info") -> ?INFO; 53level2int("notice") -> ?NOTICE; 54level2int("warning") -> ?WARN; 55level2int("error") -> ?ERR; 56level2int("critical") -> ?CRIT; 57level2int("emergency") -> ?EMERG. 58 59-define(QUOT,"""). 60-define(APOS,"'"). 61-define(AMP,"&"). 62-define(GT,">"). 63-define(LT,"<"). 64-define(DUPSTR_SIZE,20). 65-define(DUPSTR,"qxvmvtglimieyhemzlxc"). 66-define(DUPBINSTR_SIZE,20). 67-define(DUPBINSTR,<<"qxvmvtglimieyhemzlxc">>). 68 69%%---------------------------------------------------------------------- 70%% Func: get_val/1 71%% Purpose: return environnement variable value for the current application 72%% Returns: Value | {undef_var, Var} 73%%---------------------------------------------------------------------- 74get_val(Var) -> 75 case application:get_env(Var) of 76 {ok, Val} -> 77 ensure_string(Var, Val); 78 undefined -> % undef, application not started, try to get var from stdlib 79 case application:get_env(stdlib,Var) of 80 undefined -> {undef_var, Var}; 81 {ok,Val} -> ensure_string(Var, Val) 82 end 83 end. 84 85 86 87%% ensure atom to string conversion of environnement variable 88%% This is intended to fix a problem making tsung run under Windows 89%% I convert parameter that are called from the command-line 90ensure_string(log_file, Atom) when is_atom(Atom) -> 91 atom_to_list(Atom); 92ensure_string(proxy_log_file, Atom) when is_atom(Atom) -> 93 atom_to_list(Atom); 94ensure_string(config_file, Atom) when is_atom(Atom) -> 95 atom_to_list(Atom); 96ensure_string(exclude_tag, Atom) when is_atom(Atom) -> 97 atom_to_list(Atom); 98ensure_string(_, Other) -> 99 Other. 100 101%%---------------------------------------------------------------------- 102%% Func: debug/3 103%% Purpose: print debug message if level is high enough 104%%---------------------------------------------------------------------- 105debug(From, Message, Level) -> 106 debug(From, Message, [], Level). 107 108debug(From, Message, Args, Level) -> 109 Debug_level = ?config(debug_level), 110 if 111 Level =< Debug_level -> 112 error_logger:info_msg("~20s:(~p:~p) "++ Message, 113 [From, Level, self()] ++ Args); 114 true -> 115 nodebug 116 end. 117 118%%---------------------------------------------------------------------- 119%% Func: elapsed/2 120%% Purpose: print elapsed time in milliseconds 121%% Returns: integer 122%%---------------------------------------------------------------------- 123elapsed({Before1, Before2, Before3}, {After1, After2, After3}) -> 124 After = After1 * 1000000000 + After2 * 1000 + After3/1000, 125 Before = Before1 * 1000000000 + Before2 * 1000 + Before3/1000, 126 case After - Before of 127 Neg when Neg < 0 -> % time duration must not be negative 128 0; 129 Val -> Val 130 end; 131elapsed(Before, After)-> 132 Elapsed=After-Before, 133 MicroSec = erlang:convert_time_unit(Elapsed, native, micro_seconds), 134 MicroSec / 1000. 135 136%%---------------------------------------------------------------------- 137%% Func: chop/1 138%% Purpose: remove trailing "\n" 139%%---------------------------------------------------------------------- 140chop(String) -> 141 string:strip(String, right, 10). 142 143%%---------------------------------------------------------------------- 144%% Func: clean_str/1 145%% Purpose: remove "\n" and space at the beginning and at that end of a string 146%%---------------------------------------------------------------------- 147clean_str(String) -> 148 Str1 = string:strip(String, both, 10), 149 Str2 = string:strip(Str1), 150 Str3 = string:strip(Str2, both, 10), 151 string:strip(Str3). 152 153 154%%---------------------------------------------------------------------- 155%% Func: init_seed/1 156%%---------------------------------------------------------------------- 157init_seed(now)-> 158 init_seed(); 159init_seed(A) when is_integer(A)-> 160 %% in case of a distributed test, we don't want each launcher to 161 %% have the same seed, therefore, we need to know the id of the 162 %% node to set a reproductible but different seed for each launcher. 163 Id=get_node_id(), 164 ?DebugF("Seeding with ~p on node ~p~n",[Id,node()]), 165 random:seed(1000*Id,-1000*A*Id,1000*A*A); 166init_seed({A,B}) when is_integer(A) and is_integer(B)-> 167 Id=get_node_id(), 168 ?DebugF("Seeding with ~p ~p ~p on node ~p~n",[A,B,Id,node()]), 169 %% init_seed with 2 args is called by ts_client, with increasing 170 %% values of A, and fixed B. If the seeds are too closed, the 171 %% initial pseudo random values will be quite closed to each 172 %% other. Trying to avoid this by using a multiplier big enough 173 %% (because the algorithm use mod 30XXX , see random.erl). 174 random:seed(4000*A*B*Id,-4000*B*A*Id,4000*Id*Id*A); 175init_seed({A,B,C}) -> 176 random:seed(A,B,C). 177 178get_node_id() -> 179 case string:tokens(atom_to_list(node()),"@") of 180 ["tsung_control"++_,_] -> 123456; 181 ["tsung"++Tail,_] -> 182 {match, [I]} = re:run(Tail, "\\d+$", [{capture, all, list}]), %" add comment for erlang-mode bug 183 list_to_integer(I); 184 _ -> 654321 185 end. 186 187%% @spec is_controller() -> true|false 188%% @doc return true if the caller is running on the controller node 189%% @end 190is_controller() -> 191 case string:tokens(atom_to_list(node()),"@") of 192 ["tsung_control"++_,_] -> true; 193 _ ->false 194 end. 195 196%%---------------------------------------------------------------------- 197%% Func: init_seed/0 198%%---------------------------------------------------------------------- 199init_seed()-> 200 init_seed(?TIMESTAMP). 201 202%%---------------------------------------------------------------------- 203%% Func: now_sec/0 204%% Purpose: returns unix like elapsed time in sec 205%% TODO: we should use erlang:system_time(seconds) when we drop < R18 compat 206%%---------------------------------------------------------------------- 207now_sec() -> 208 time2sec(?TIMESTAMP). 209 210time2sec({MSec, Seconds, _}) -> 211 Seconds+1000000*MSec. 212 213time2sec_hires({MSec, Seconds, MuSec}) -> 214 Seconds+1000000*MSec+MuSec/1000000. 215 216%%---------------------------------------------------------------------- 217%% Func: add_time/2 218%% Purpose: add given Seconds to given Time (same format as now()) 219%%---------------------------------------------------------------------- 220add_time({MSec, Seconds, MicroSec}, SecToAdd) when is_integer(SecToAdd)-> 221 NewSec = Seconds +SecToAdd, 222 case NewSec < 1000000 of 223 true -> {MSec, NewSec, MicroSec}; 224 false ->{MSec+ (NewSec div 1000000), NewSec-1000000, MicroSec} 225 end; 226add_time(Time, SecToAdd) when is_integer(SecToAdd)-> 227 MicroSec = erlang:convert_time_unit(Time, native, micro_seconds)+SecToAdd*1000000, 228 erlang:convert_time_unit(MicroSec, micro_seconds, native). 229 230node_to_hostname(Node) -> 231 [_Nodename, Hostname] = string:tokens( atom_to_list(Node), "@"), 232 {ok, Hostname}. 233 234to_lower(String)-> 235 string:to_lower(String). 236 237encode_base64(String)-> 238 base64:encode_to_string(String). 239 240decode_base64(Base64)-> 241 base64:decode_to_string(Base64). 242 243%%---------------------------------------------------------------------- 244%% Func: filtermap/2 245%% Purpose lists:zf is called lists:filtermap in erlang R16B1 and newer 246%% 247%%---------------------------------------------------------------------- 248filtermap(Fun, List)-> 249 case erlang:function_exported(lists, filtermap, 2) of 250 true -> 251 lists:filtermap(Fun,List); 252 false -> 253 lists:zf(Fun,List) 254 end. 255 256%%---------------------------------------------------------------------- 257%% Func: key1search/2 258%% Purpose: wrapper around httpd_utils module funs (maybe one day 259%% these functions will be added to the stdlib) 260%%---------------------------------------------------------------------- 261key1search(Tuple,String)-> 262 proplists:get_value(String,Tuple). 263 264%%---------------------------------------------------------------------- 265%% Func: mkey1search/2 266%% Purpose: multiple key1search: 267%% Take as input list of {Key, Value} tuples (length 2). 268%% Return the list of values corresponding to a given key 269%% It is assumed here that there might be several identical keys in the list 270%% unlike the lists:key... functions. 271%%---------------------------------------------------------------------- 272mkey1search(List, Key) -> 273 Results = lists:foldl( 274 fun({MatchKey, Value}, Acc) when MatchKey == Key -> 275 [Value | Acc]; 276 ({_OtherKey, _Value}, Acc) -> 277 Acc 278 end, 279 [], 280 List), 281 case Results of 282 [] -> undefined; 283 Results -> lists:reverse(Results) 284 end. 285 286%%---------------------------------------------------------------------- 287%% datestr/0 288%% Purpose: print date as a string 'YYYYMMDD-HHMM' 289%%---------------------------------------------------------------------- 290datestr()-> 291 datestr(erlang:localtime()). 292 293%%---------------------------------------------------------------------- 294%% datestr/1 295%%---------------------------------------------------------------------- 296datestr({{Y,M,D},{H,Min,_S}})-> 297 io_lib:format("~w~2.10.0b~2.10.0b-~2.10.0b~2.10.0b",[Y,M,D,H,Min]). 298 299%%---------------------------------------------------------------------- 300%% erl_system_args/0 301%%---------------------------------------------------------------------- 302erl_system_args()-> 303 erl_system_args(extended). 304erl_system_args(basic)-> 305 Rsh = case init:get_argument(rsh) of 306 {ok,[[Value]]} -> " -rsh " ++ Value; 307 _ -> " " 308 end, 309 lists:append([Rsh, " -detached -setcookie ", 310 atom_to_list(erlang:get_cookie()) ]); 311erl_system_args(extended)-> 312 BasicArgs = erl_system_args(basic), 313 SetArg = fun(A) -> case init:get_argument(A) of 314 error -> " "; 315 {ok,[[]]} -> " -" ++atom_to_list(A)++" "; 316 {ok,[[Val|_]]} when is_list(Val)-> " -" ++atom_to_list(A)++" "++Val++" " 317 end 318 end, 319 Shared = SetArg(shared), 320 Hybrid = SetArg(hybrid), 321 case {?config(smp_disable), erlang:system_info(otp_release)} of 322 {true,"R"++_} -> 323 Smp = " -smp disable "; 324 {true,V} when (V =:= "17" orelse V =:= "18" orelse V =:= "19") -> 325 Smp = " -smp disable "; 326 {true,_} -> 327 Smp = " +S 1 "; 328 _ -> 329 Smp = SetArg(smp) 330 end, 331 Inet = case init:get_argument(kernel) of 332 {ok,[["inetrc",InetRcFile]]} -> 333 ?LOGF("Get inetrc= ~p~n",[InetRcFile],?NOTICE), 334 " -kernel inetrc '"++ InetRcFile ++ "'" ; 335 _ -> " " 336 end, 337 Proto = case init:get_argument(proto_dist) of 338 {ok,[["inet6_tcp"]]}-> 339 ?LOG("IPv6 used for erlang distribution~n",?NOTICE), 340 " -proto_dist inet6_tcp " ; 341 _ -> " " 342 end, 343 ListenMin = case application:get_env(kernel,inet_dist_listen_min) of 344 undefined -> ""; 345 {ok, Min} -> " -kernel inet_dist_listen_min " ++ integer_to_list(Min)++ " " 346 end, 347 ListenMax = case application:get_env(kernel,inet_dist_listen_max) of 348 undefined -> ""; 349 {ok, Max} -> " -kernel inet_dist_listen_max " ++ integer_to_list(Max)++" " 350 end, 351 SSLCache = case application:get_env(ssl,session_cb) of 352 {ok, CB} when is_atom(CB) -> " -ssl session_cb " ++ atom_to_list(CB)++" "; 353 _ -> "" 354 end, 355 SSLLifetime = case application:get_env(ssl,session_lifetime) of 356 {ok, Time} when is_integer(Time) -> " -ssl session_lifetime " ++ integer_to_list(Time)++" "; 357 _ -> "" 358 end, 359 SSLCacheSize = case application:get_env(tsung,ssl_session_cache) of 360 {ok, Reuse} when is_integer(Reuse)-> " -tsung reuse_sessions " ++ integer_to_list(Reuse)++" "; 361 _ -> "" 362 end, 363 Threads= "+A "++integer_to_list(erlang:system_info(thread_pool_size))++" ", 364 ProcessMax="+P "++integer_to_list(erlang:system_info(process_limit))++" ", 365 Mea = case erlang:system_info(version) of 366 "5.3" ++ _Tail -> " +Mea r10b "; 367 _ -> " " 368 end, 369 lists:append([BasicArgs, Shared, Hybrid, Smp, Mea, Inet, Proto, Threads, 370 ProcessMax,ListenMin,ListenMax,SSLCache,SSLLifetime,SSLCacheSize]). 371 372%%---------------------------------------------------------------------- 373%% setsubdir/1 374%% Purpose: all log files are created in a directory whose name is the 375%% start date of the test. 376%% ---------------------------------------------------------------------- 377setsubdir(FileName) -> 378 Date = datestr(), 379 Path = filename:dirname(FileName), 380 Base = filename:basename(FileName), 381 Dir = filename:join(Path, Date), 382 case file:make_dir(Dir) of 383 ok -> 384 {ok, {Dir, Base}}; 385 {error, eexist} -> 386 ?DebugF("Directory ~s already exist~n",[Dir]), 387 {ok, {Dir, Base}}; 388 Err -> 389 ?LOGF("Can't create directory ~s (~p)!~n",[Dir, Err],?EMERG), 390 {error, Err} 391 end. 392 393%%---------------------------------------------------------------------- 394%% export_text/1 395%% Purpose: Escape special characters `<', `&', `'' and `"' flattening 396%% the text. 397%%---------------------------------------------------------------------- 398export_text(T) -> 399 export_text(T, []). 400 401export_text(Bin, Cont) when is_binary(Bin) -> 402 export_text(binary_to_list(Bin), Cont); 403export_text([], Exported) -> 404 lists:flatten(lists:reverse(Exported)); 405export_text([$< | T], Cont) -> 406 export_text(T, [?LT | Cont]); 407export_text([$> | T], Cont) -> 408 export_text(T, [?GT | Cont]); 409export_text([$& | T], Cont) -> 410 export_text(T, [?AMP | Cont]); 411export_text([$' | T], Cont) -> %' 412 export_text(T, [?APOS | Cont]); 413export_text([$" | T], Cont) -> %" 414 export_text(T, [?QUOT | Cont]); 415export_text([C | T], Cont) -> 416 export_text(T, [C | Cont]). 417 418%%---------------------------------------------------------------------- 419%% stop_all/2 420%%---------------------------------------------------------------------- 421stop_all(Host, Name) -> 422 stop_all(Host, Name, "Tsung"). 423 424stop_all([Host],Name,MsgName) -> 425 VoidFun = fun(_A)-> ok end, 426 stop_all([Host],Name,MsgName, VoidFun). 427 428stop_all([Host],Name,MsgName,Fun) when is_atom(Host) -> 429 _List= net_adm:world_list([Host]), 430 global:sync(), 431 case global:whereis_name(Name) of 432 undefined -> 433 Msg = MsgName ++" is not running on " ++ atom_to_list(Host), 434 erlang:display(Msg); 435 Pid -> 436 Controller_Node = node(Pid), 437 Fun(Controller_Node), 438 slave:stop(Controller_Node) 439 end; 440stop_all(_,_,_,_)-> 441 erlang:display("Bad Hostname"). 442 443%%---------------------------------------------------------------------- 444%% make_dir/1 445%% Purpose: create directory. Missing parent directories ARE created 446%%---------------------------------------------------------------------- 447make_dir(DirName) -> 448 make_dir_rec(DirName,file). 449 450make_dir_raw(DirName) -> 451 make_dir_rec(DirName,prim_file). 452 453make_dir_rec(DirName,FileMod) when is_list(DirName) -> 454 case FileMod:read_file_info(DirName) of 455 {ok, #file_info{type=directory}} -> 456 ok; 457 {error,enoent} -> 458 make_dir_rec("", FileMod,filename:split(DirName)); 459 {error, Reason} -> 460 {error,Reason} 461 end. 462 463make_dir_rec(_Path, _FileMod, []) -> 464 ok; 465make_dir_rec(Path, FileMod,[Parent|Childs]) -> 466 CurrentDir=filename:join([Path,Parent]), 467 case FileMod:read_file_info(CurrentDir) of 468 {ok, #file_info{type=directory}} -> 469 make_dir_rec(CurrentDir, FileMod,Childs); 470 {error,enoent} -> 471 case FileMod:make_dir(CurrentDir) of 472 ok -> 473 make_dir_rec(CurrentDir, FileMod, Childs); 474 {error, eexist} -> 475 make_dir_rec(CurrentDir, FileMod, Childs); 476 Error -> 477 Error 478 end; 479 {error, Reason} -> 480 {error,Reason} 481 end. 482 483%% check if a string is an IPv4 address (as "192.168.0.1") 484is_ip(String) when is_list(String) -> 485 EightBit="(2[0-4][0-9]|25[0-5]|1[0-9][0-9]|[0-9][0-9]|[0-9])", 486 RegExp = lists:append(["^",EightBit,"\.",EightBit,"\.",EightBit,"\.",EightBit,"$"]), %" 487 case re:run(String, RegExp) of 488 {match,_} -> true; 489 _ -> false 490 end; 491is_ip(_) -> false. 492 493%%---------------------------------------------------------------------- 494%% to_https/1 495%% Purpose: rewrite https URL, to act as a pure non ssl proxy 496%%---------------------------------------------------------------------- 497to_https({url, "http://-"++Rest})-> "https://" ++ Rest; 498to_https({url, URL})-> URL; 499to_https({request, {body,Data}}) when is_list(Data) -> 500 %% body request, no headers 501 {ok, re:replace(Data,"http://-","https://",[global])}; 502to_https({request, S="CONNECT"++_Rest}) -> {ok,S}; 503to_https({request, []}) -> {ok, []}; 504to_https({request, String}) when is_list(String) -> 505 EndOfHeader = string:str(String, "\r\n\r\n"), 506 Header = string:substr(String, 1, EndOfHeader - 1) ++ "\r\n", 507 Body = string:substr(String, EndOfHeader + 4), 508 ReOpts=[global,{return,list}], 509 TmpHeader = re:replace(Header,"http://-","https://",ReOpts), 510 TmpHeader2 = re:replace(TmpHeader,"Accept-Encoding: [0-9,a-z_ ]+\r\n","",ReOpts++[caseless]), 511 RealHeader = re:replace(TmpHeader2,"Host: -","Host: ",ReOpts++[caseless]), 512 RealBody = re:replace(Body,"http://-","https://",ReOpts), 513 RealString = RealHeader++ "\r\n" ++ RealBody, 514 {ok, RealString}. 515 516 517%% @spec from_https(string()) -> {ok, string() | iodata()} 518%% @doc replace https links with 'http://-' 519%% @end 520from_https(String) when is_list(String)-> 521 ReOpts=[{newline,crlf},multiline,global,caseless], 522 %% remove Secure from Set-Cookie (TSUN-120) 523 TmpData = re:replace(String,"(.*set-cookie:.*); *secure(.*$.*$)","\\1\\2",ReOpts), 524 Data=re:replace(TmpData,"https://","http://-",[global]), 525 {ok, Data}. 526 527%% concatenate a list of atoms 528concat_atoms(Atoms) when is_list(Atoms) -> 529 String =lists:foldl(fun(A,Acc) -> 530 Acc++atom_to_list(A) end, "", Atoms), 531 list_to_atom(String). 532 533%% A Perl-style join --- concatenates all strings in Strings, 534%% separated by Sep. 535join(_Sep, []) -> []; 536join(Sep, List) when is_list(List)-> 537 ToStr = fun(A) when is_integer(A) -> integer_to_list(A); 538 (A) when is_list(A) -> A; 539 (A) when is_float(A) -> io_lib:format("~.3f",[A]); 540 (A) when is_atom(A) -> atom_to_list(A); 541 (A) when is_binary(A) -> binary_to_list(A) 542 end, 543 string:join(lists:map(ToStr,List), Sep). 544 545%% split a string given a string (at first occurence of char) 546split(String,Chr) when is_list(String), is_list(Chr) -> 547 re:split(String,Chr,[{return,list}]); 548 549split(String,Chr) when is_binary(String), is_binary(Chr) -> 550 binary:split(String,[Chr],[global]). 551 552 553%% split a string given a char (faster) 554splitchar(String,Chr) -> 555 splitchar2(String,Chr,[],[]). 556splitchar2([],_,[],Acc) -> 557 lists:reverse(Acc); 558splitchar2([],_,AccChr,Acc) -> 559 lists:reverse([lists:reverse(AccChr)|Acc]); 560splitchar2([Chr|String],Chr,AccChr,Acc) -> 561 splitchar2(String,Chr,[],[lists:reverse(AccChr)|Acc]); 562splitchar2([Other|String],Chr,AccChr,Acc) -> 563 splitchar2(String,Chr,[Other|AccChr],Acc). 564 565 566%% split a string in 2 (at first occurence of char) 567split2(String,Chr) -> 568 split2(String,Chr,nostrip). 569 570split2(String,Chr,strip) -> % split and strip blanks 571 {A, B} = split2(String,Chr,nostrip), 572 {string:strip(A), string:strip(B)}; 573split2(String,Chr,nostrip) -> 574 case string:chr(String, Chr) of 575 0 -> {String,[]}; 576 Pos -> {string:substr(String,1,Pos-1), string:substr(String,Pos+1)} 577 end. 578 579 580foreach_parallel(Fun, List)-> 581 SpawnFun = fun(A) -> spawn(?MODULE, spawn_par, lists:append([[Fun,self()], [A]])) end, 582 lists:foreach(SpawnFun, List), 583 wait_pids(length(List)). 584 585wait_pids(0) -> done; 586wait_pids(N) -> 587 receive 588 {ok, _Pid, _Res } -> 589 wait_pids(N-1) 590 after ?TIMEOUT_PARALLEL_SPAWN -> 591 {error, {timout, N}} % N missing answer 592 end. 593 594spawn_par(Fun, PidFrom, Args) -> 595 Res = Fun(Args), 596 PidFrom ! {ok, self(), Res}. 597 598%%---------------------------------------------------------------------- 599%% Func: inet_setopts/3 600%% Purpose: set inet options depending on the protocol (gen_tcp, gen_udp, 601%% ssl) 602%%---------------------------------------------------------------------- 603inet_setopts(_, none, _) -> %socket was closed before 604 none; 605inet_setopts(ssl6, Socket, Opts) -> 606 inet_setopts(ssl, Socket, Opts); 607inet_setopts(ssl, Socket, Opts) -> 608 case ssl:setopts(Socket, Opts) of 609 ok -> 610 Socket; 611 {error, closed} -> 612 none; 613 Error -> 614 ?LOGF("Error while setting ssl options ~p ~p ~n", [Opts, Error], ?ERR), 615 none 616 end; 617inet_setopts(gen_tcp6, Socket, Opts)-> 618 inet_setopts(gen_tcp, Socket, Opts); 619inet_setopts(gen_udp6, Socket, Opts)-> 620 inet_setopts(gen_udp, Socket, Opts); 621inet_setopts(_Type, Socket, Opts)-> 622 case inet:setopts(Socket, Opts) of 623 ok -> 624 Socket; 625 {error, closed} -> 626 none; 627 Error -> 628 ?LOGF("Error while setting inet options ~p ~p ~n", [Opts, Error], ?ERR), 629 none 630 end. 631 632%%---------------------------------------------------------------------- 633%% Func: check_sum/3 634%% Purpose: check sum of int equals 100. 635%% Args: List of tuples, index of int in tuple, Error msg 636%% Returns ok | {error, {bad_sum, Msg}} 637%%---------------------------------------------------------------------- 638check_sum(RecList, Index, ErrorMsg) -> 639 %% popularity may be a float number. 5.10-2 precision 640 check_sum(RecList, Index, 100, 0.05, ErrorMsg). 641check_sum(RecList, Index, Total, Epsilon, ErrorMsg) -> 642 %% we use the tuple representation of a record ! 643 Sum = lists:foldl(fun(X, Sum) -> element(Index,X)+Sum end, 0, RecList), 644 Delta = abs(Sum - Total), 645 case Delta < Epsilon of 646 true -> ok; 647 false -> {error, {bad_sum, Sum ,ErrorMsg}} 648 end. 649 650%%---------------------------------------------------------------------- 651%% Func: file_to_list/1 652%% Purpose: read a file line by line and put them in a list 653%% Args: filename 654%% Returns {ok, List} | {error, Reason} 655%%---------------------------------------------------------------------- 656file_to_list(FileName) -> 657 case file:open(FileName, [read]) of 658 {error, Reason} -> 659 {error, Reason}; 660 {ok , File} -> 661 Lines = read_lines(File), 662 file:close(File), 663 {ok, Lines} 664 end. 665 666read_lines(FD) ->read_lines(FD,io:get_line(FD,""),[]). 667 668read_lines(_FD, eof, L) -> 669 lists:reverse(L); 670read_lines(FD, Line, L) -> 671 read_lines(FD, io:get_line(FD,""),[chop(Line)|L]). 672 673%%---------------------------------------------------------------------- 674%% Func: keyumerge/3 675%% Purpose: Same as lists:keymerge, but remove duplicates (use items from A) 676%% Returns: List 677%%---------------------------------------------------------------------- 678keyumerge(_N,[],B)->B; 679keyumerge(N,[A|Rest],B)-> 680 Key = element(N,A), 681 % remove old values if it exists 682 NewB = lists:keydelete(Key, N, B), 683 keyumerge(N,Rest, [A|NewB]). 684 685%%---------------------------------------------------------------------- 686%% Func: keymax/2 687%% Purpose: Return Max of Nth element of a list of tuples 688%% Returns: Number 689%%---------------------------------------------------------------------- 690keymax(_N,[])-> 0; 691keymax(N,[L])-> element(N,L); 692keymax(N,[E|Tail])-> 693 keymax(N,Tail,element(N,E)). 694 695keymax(_N,[],Max)-> Max; 696keymax(N,[E|Tail],Max)-> 697 keymax(N,Tail,lists:max([Max,element(N,E)])). 698 699%%-------------------------------------------------------------------- 700%% Function: resolve/2 701%% Description: return cached hostname or gethostbyaddr for given ip 702%%-------------------------------------------------------------------- 703resolve(Ip, Cache) -> 704 case lists:keysearch(Ip, 1, Cache) of 705 {value, {Ip, ReverseHostname}} -> 706 {ReverseHostname, Cache}; 707 false -> 708 case inet:gethostbyaddr(Ip) of 709 {ok, {hostent,ReverseHostname,_,inet,_,_}} -> 710 %% cache dns result and return it 711 ?LOGF("Add ~p -> ~p to DNS cache ~n", [Ip, ReverseHostname],?DEB), 712 {ReverseHostname, [{Ip, ReverseHostname} | Cache]}; 713 {error, Reason} -> 714 ?LOGF("DNS resolution error on ~p: ~p~n", [Ip, Reason],?WARN), 715 %% cache dns name as IP : {ip, ip} and return Ip 716 NewCache = lists:keymerge(1, Cache, [{Ip, Ip}]), 717 {Ip, NewCache} 718 end 719 end. 720 721%%---------------------------------------------------------------------- 722%% @spec urandomstr_noflat(Size::integer()) ->string() 723%% @doc generate pseudo-random list of given size. Implemented by 724%% duplicating list of fixed size to be faster. unflatten version 725%% @end 726%%---------------------------------------------------------------------- 727urandomstr_noflat(Size) when is_integer(Size) , Size >= ?DUPSTR_SIZE -> 728 Msg= lists:duplicate(Size div ?DUPSTR_SIZE,?DUPSTR), 729 case Size rem ?DUPSTR_SIZE of 730 0-> 731 Msg; 732 Rest -> 733 lists:append(Msg,urandomstr_noflat(Rest)) 734 end; 735urandomstr_noflat(Size) when is_integer(Size), Size >= 0 -> 736 lists:nthtail(?DUPSTR_SIZE-Size, ?DUPSTR). 737 738%%---------------------------------------------------------------------- 739%% @spec urandombinstr(Size::integer()) ->binary() 740%% @doc same as urandomstr/1, but returns a binary. 741%% @end 742%%---------------------------------------------------------------------- 743urandombinstr(Size) when is_integer(Size) , Size >= ?DUPBINSTR_SIZE -> 744 Loop = Size div ?DUPBINSTR_SIZE, 745 Rest = Size rem ?DUPBINSTR_SIZE, 746 Res=lists:foldl(fun(_X,Acc)-> <<Acc/binary, ?DUPBINSTR/binary>> end, << >>,lists:seq(1,Loop)), 747 << Res/binary, ?DUPBINSTR:Rest/binary>>; 748urandombinstr(Size) when is_integer(Size), Size >= 0 -> 749 <<?DUPBINSTR:Size/binary>> . 750 751%%---------------------------------------------------------------------- 752%% @spec urandomstr(Size::integer()) ->string() 753%% @doc same as urandomstr_noflat/1, but returns a flat list. 754%% @end 755%%---------------------------------------------------------------------- 756urandomstr(Size) when is_integer(Size), Size >= 0 -> 757 lists:flatten(urandomstr_noflat(Size)). 758 759%%---------------------------------------------------------------------- 760%% @spec randomstr(Size::integer()) ->string() 761%% @doc returns a random string. slow if Size is high. 762%% @end 763%%---------------------------------------------------------------------- 764randomstr(Size) when is_integer(Size), Size >= 0 -> 765 lists:map(fun (_) -> random:uniform(25) + $a end, lists:seq(1,Size)). 766 767random_alphanumstr(Size) when is_integer(Size), Size >= 0 -> 768 AllowedChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 769 S = length(AllowedChars), 770 lists:map(fun (_) -> lists:nth(random:uniform(S), AllowedChars) end, lists:seq(1,Size)). 771 772%%---------------------------------------------------------------------- 773%% @spec randombinstr(Size::integer()) ->binary() 774%% @doc returns a random binary string. slow if Size is high. 775%% @end 776%%---------------------------------------------------------------------- 777randombinstr(0) -> <<>>; 778randombinstr(Size) when is_integer(Size), Size > 0 -> 779 randombinstr(Size,<<>>). 780randombinstr(0,Bin) -> Bin; 781randombinstr(Size,Bin) -> 782 C=random:uniform(25)+$a, 783 randombinstr(Size-1, << Bin/binary, C >>). 784 785 786%%---------------------------------------------------------------------- 787%% @spec eval(string()) -> term() 788%% @doc evaluate strings as Erlang code at runtime 789%% @end 790%%---------------------------------------------------------------------- 791eval(Code) -> 792 {ok, Scanned, _} = erl_scan:string(lists:flatten(Code)), 793 {ok, Parsed} = erl_parse:parse_exprs(Scanned), 794 {value, Result, _} = erl_eval:exprs(Parsed, erl_eval:new_bindings()), 795 Result. 796 797%%---------------------------------------------------------------------- 798%% @spec list_to_number(string()) -> integer() | float() 799%% @doc convert a 'number' to either int or float 800%% @end 801%%---------------------------------------------------------------------- 802list_to_number(Number) -> 803 try list_to_integer(Number) of 804 Int -> Int 805 catch 806 error:_Reason -> 807 list_to_float(Number) 808 end. 809 810term_to_list(I) when is_integer(I)-> 811 integer_to_list(I); 812term_to_list(I) when is_atom(I)-> 813 atom_to_list(I); 814term_to_list(I) when is_list(I)-> 815 I; 816term_to_list(I) when is_float(I)-> 817 float_to_list(I); 818term_to_list(B) when is_binary(B)-> 819 binary_to_list(B). 820 821read_file_raw(File) when is_list(File) -> 822 case {file:open(File,[read,raw,binary]), file:read_file_info(File)} of 823 { {ok,IODev}, {ok,#file_info{size=Size} } } -> 824 case file:pread(IODev,0,Size) of 825 {ok, Res} -> 826 file:close(IODev), 827 {ok, Res, Size}; 828 Else -> 829 ?LOGF("pread file ~p of size ~p: ~p~n",[File,Size,Else],?NOTICE), 830 file:close(IODev), 831 Else 832 end; 833 {{ok,IODev}, {error, Reason} } -> 834 file:close(IODev), 835 {error,Reason}; 836 {{error,Reason},_} -> 837 {error, Reason} 838 end. 839 840 841%%---------------------------------------------------------------------- 842%% @spec jsonpath(JSONPath::string(),JSON::iolist()) -> term() 843%% @doc very limited implementation of JSONPath from JSON struct. 844%% @end 845%%---------------------------------------------------------------------- 846jsonpath("$."++JSONPath,JSON) -> 847 jsonpath(JSONPath,JSON); 848jsonpath(JSONPath,JSON) -> 849 Fun= fun(A) -> 850 case catch list_to_integer(A) of 851 I when is_integer(I) -> 852 I+1; 853 _Error -> 854 list_to_binary(A) 855 end 856 end, 857 Str=re:replace(JSONPath,"\\[(.*?)\\]","\.\\1",[{return,list},global]), 858 Keys=lists:map(Fun, string:tokens(Str,".")), 859 json_get_bin(Keys,JSON). 860json_get_bin([],Val) -> 861 Val; 862json_get_bin([_Key|_Keys],undefined) -> 863 undefined; 864json_get_bin([N|Keys],L) when is_integer(N), N =< length(L) -> 865 Val = lists:nth(N,L), 866 json_get_bin(Keys,Val); 867json_get_bin([N|Keys], L) when N =:= <<"*">>, is_list(L) -> 868 lists:map(fun(A) -> json_get_bin(Keys,A) end, L); 869json_get_bin([N|Keys],Val) when N =:= <<"*">> -> 870 json_get_bin(Keys,Val); 871json_get_bin([<<"?",Expr/binary>> | Keys],L) when is_list(L) -> 872 case string:tokens(binary_to_list(Expr),"=") of 873 [Key,Val] -> 874 Fun = fun(S) -> case json_get_bin([list_to_binary(Key)],S) of 875 Int when is_integer(Int) -> 876 integer_to_list(Int) =:= Val; 877 Other when is_binary(Other)-> 878 binary_to_list(Other) =:= Val 879 end 880 end, 881 ?LOG("ok~n",?ERR), 882 case lists:filter(Fun,L) of 883 [] -> 884 undefined; 885 [Res] -> 886 json_get_bin(Keys,Res); 887 Res -> 888 lists:map(fun(A) -> json_get_bin(Keys,A) end, Res) 889 end; 890 _ -> 891 undefined 892 end; 893json_get_bin([Key|Keys],{struct,JSON}) when is_list(JSON) -> 894 Val = proplists:get_value(Key,JSON), 895 json_get_bin(Keys,Val); 896json_get_bin(_,_) -> 897 undefined. 898 899%% Map function F over list L in parallel. 900pmap(F, L) -> 901 Parent = self(), 902 [receive {Pid, Result} -> Result end || Pid <- [spawn(fun() -> Parent ! {self(), F(X)} end) || X <- L]]. 903 904 905%% Map function F over list L in parallel, with maximum NProcs in parallel 906%% FIXME: handle timeout 907pmap(F, L, NProcs) -> 908 pmap(F, L, NProcs,""). 909 910pmap(F, L, NProcs, Res) when length(L) > NProcs-> 911 {Head, Tail} = lists:split(NProcs,L), 912 Parent = self(), 913 lists:foldl(fun(X, Acc) -> spawn(fun() -> Parent ! {pmap, self(), F(X), Acc} end), Acc+1 end, 0, Head), 914 NewRes = wait_result(NProcs,[]), 915 pmap(F,Tail, NProcs, Res ++ NewRes); 916 917pmap(F, L, _NProcs, Acc) -> 918 Acc ++ pmap(F,L). 919 920wait_result(0, Res)-> 921 {_Ids, RealRes} = lists:unzip(lists:keysort(1, Res)), 922 RealRes; 923wait_result(Nprocs, Res)-> 924 receive 925 {pmap, _Pid, Result, Id} -> 926 NewRes = Res ++ [{Id, Result}], 927 wait_result(Nprocs-1, NewRes) 928 end. 929 930%% 931ceiling(X) -> 932 T = erlang:trunc(X), 933 case (X - T) of 934 Neg when Neg < 0 -> 935 T; 936 Pos when Pos > 0 -> T + 1; 937 _ -> T 938 end. 939 940%%-------------------------------------------------------------------- 941%% Func: accept_loop/3 942%% Purpose: infinite listen/accept loop, delegating handling of accepts 943%% to the gen_server proper. 944%% Returns: only returns by throwing an exception 945%%-------------------------------------------------------------------- 946accept_loop(PPid, Tag, ServerSock)-> 947 case 948 case gen_tcp:accept(ServerSock) of 949 {ok, ClientSock} -> 950 ok = gen_tcp:controlling_process(ClientSock, PPid), 951 gen_server:call(PPid, {accepted, Tag, ClientSock}); 952 Error -> 953 gen_server:call(PPid, {accept_error, Tag, Error}) 954 end 955 of 956 continue -> 957 accept_loop(PPid, Tag, ServerSock); 958 _-> 959 normal 960 end. 961 962 963append_to_filename(Filename, From, To) -> 964 case re:replace(Filename,From,To, [{return,list},global] ) of 965 Filename -> Filename ++"." ++ To; 966 RealName -> RealName 967 end. 968 969 970log_transaction([]) -> 971 "-"; 972log_transaction([{TransactionName,_}| _Tail]) -> 973 TransactionName. 974 975%%-------------------------------------------------------------------- 976%% Func: conv_entities/1 977%% Purpose: Convert html entities to string 978%%-------------------------------------------------------------------- 979conv_entities(Binary)-> 980 conv_entities(Binary,[]). 981conv_entities(<< >>,Acc) -> 982 list_to_binary(Acc); 983conv_entities(<< "&", T/binary >> ,Acc) -> 984 conv_entities(T,[ Acc, << "&">>]); 985conv_entities(<< "<", T/binary >>,Acc) -> 986 conv_entities(T,[ Acc, << "<">>]); 987conv_entities(<< ">", T/binary >>,Acc) -> 988 conv_entities(T,[ Acc, << ">">>]); 989conv_entities(<<""", T/binary >>,Acc) -> 990 conv_entities(T,[ Acc, << "\"">>]); 991conv_entities(<<"'", T/binary >>,Acc) -> 992 conv_entities(T,[ Acc, << "'">>]); 993conv_entities(<<H:1/binary, T/binary >>,Acc) -> 994 conv_entities(T,[ Acc, H]). 995 996%% start an application and it's dependencies recursively 997%% does the same as application:ensure_all_started (only in R16B2) 998ensure_all_started(App, Type) -> 999 start_ok(App, Type, application:start(App, Type)). 1000 1001start_ok(_App, _Type, ok) -> ok; 1002start_ok(_App, _Type, {error, {already_started, _App}}) -> ok; 1003start_ok(App, Type, {error, {not_started, Dep}}) -> 1004 ok = ensure_all_started(Dep, Type), 1005 ensure_all_started(App, Type); 1006start_ok(App, _Type, {error, Reason}) -> 1007 erlang:error({app_start_failed, App, Reason}). 1008 1009wildcard(Wildcard,Names) -> 1010 PatternTmp = re:replace("^"++Wildcard,"\\*",".*",[{return,list}]), 1011 Pattern = re:replace(PatternTmp,"\\?",".{1}",[{return,list}]) ++ "$" , 1012 lists:filter(fun(N) -> re:run(N, Pattern) =/= nomatch end, Names). 1013 1014%% dummy comment with a " "to circumvent an erlang-mode bug in emacs" 1015 1016%%-------------------------------------------------------------------- 1017%% Func: new_ets/1 1018%% Purpose: Wrapper for ets:new/1 used in external modules 1019%% @spec new_ets(Prefix::binary(), UserId::integer()) -> string() 1020%% @doc init an ets:table 1021%% @end 1022%%-------------------------------------------------------------------- 1023new_ets(Prefix, UserId)-> 1024 EtsName = binary_to_list(Prefix) ++ "_" ++ integer_to_list(UserId), 1025 ?LOGF("create ets:table ~p ~n", [EtsName], ?INFO), 1026 ets:new(list_to_atom(EtsName), []). 1027 1028 1029size_or_length(Data) when is_binary(Data) -> 1030 size(Data); 1031size_or_length(Data) when is_list(Data) -> 1032 length(Data). 1033 1034%% given a list with successives duplicates, try to spread duplicates 1035%% all over the list. e.g. [a,a,a,b,b,c,c] -> [a,b,c,a,b,c,a] 1036spread_list(List) -> 1037 spread_list2(pack(List),[]). 1038 1039spread_list2([], Res) -> 1040 Res; 1041spread_list2(PackedList, OldRes) -> 1042 Fun = fun([A], {Res, ResTail}) -> {[A|Res], ResTail}; 1043 ([A|ATail], {Res, ResTail}) -> {[A|Res], [ATail|ResTail]} 1044 end, 1045 {Res, Tail} = lists:foldl(Fun, {[],[]}, PackedList), 1046 spread_list2(lists:reverse(Tail), OldRes ++ lists:reverse(Res)). 1047 1048%% pack duplicates into sublists 1049%% http://lambdafoo.com/blog/2008/02/26/99-erlang-problems-1-15/ 1050pack([]) -> 1051 []; 1052pack([H|[]]) -> 1053 [[H]]; 1054pack([H,H|C]) -> 1055 [Head|Tail] = pack([H|C]), 1056 X = lists:append([H],Head), 1057 [X|Tail]; 1058pack([H,H2|C]) -> 1059 if H =/= H2 -> 1060 [[H]|pack([H2|C])] 1061 end. 1062