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-2004 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%%% In addition, as a special exception, you have the permission to 20%%% link the code of this program with any library released under 21%%% the EPL license and distribute linked combinations including 22%%% the two; the MPL (Mozilla Public License), which EPL (Erlang 23%%% Public License) is based on, is included in this exception. 24 25 26%%---------------------------------------------------------------------- 27%% @copyright 2001 IDEALX 28%% @author Nicolas Niclausse <nicolas@niclux.org> 29%% @since 8 Feb 2001 30%% 31%% @doc monitor and log events: arrival and departure of users, new 32%% connections, os_mon and send/rcv message (when dump is set to true) 33%%---------------------------------------------------------------------- 34 35-module(ts_mon). 36-author('nicolas@niclux.org'). 37-vc('$Id$ '). 38 39-behaviour(gen_server). 40 41-include("ts_config.hrl"). 42 43%% External exports 44-export([start/1, stop/0, newclient/1, endclient/1, sendmes/1, 45 start_clients/1, abort/0, status/0, rcvmes/1, dumpstats/0, 46 dump/1, launcher_is_alive/0 47 ]). 48 49%% gen_server callbacks 50-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, 51 code_change/3]). 52 53-define(DUMP_FILENAME,"tsung.dump"). 54-define(FULLSTATS_FILENAME,"tsung-fullstats.log"). 55-define(DELAYED_WRITE_SIZE,524288). % 512KB 56-define(DELAYED_WRITE_DELAY,5000). % 5 sec 57 58%% one global ts_stats_mon procs + 4 dedicated stats procs to share the load 59-define(STATSPROCS, [request, connect, page, transaction, ts_stats_mon]). 60 61-record(state, {log, % log fd 62 backend, % type of backend: text|... 63 log_dir, % log directory 64 fullstats, % fullstats fd 65 dump_interval,% 66 dumpfile, % file used when dumptrafic is set light or full 67 client=0, % number of clients currently running 68 maxclient=0, % max of simultaneous clients 69 stats, % record keeping stats info 70 stop = false, % true if we should stop 71 laststats, % values of last printed stats 72 lastdate, % date of last printed stats 73 type, % type of logging (none, light, full) 74 launchers=0, % number of launchers started 75 timer_ref, % timer reference (for dumpstats) 76 wait_gui=false% wait gui before stopping 77 }). 78 79-record(stats, { 80 users_count = 0, 81 finish_users_count = 0, 82 os_mon, 83 session = [] 84 }). 85 86%%%---------------------------------------------------------------------- 87%%% API 88%%%---------------------------------------------------------------------- 89 90 91%%---------------------------------------------------------------------- 92%% @spec start(LogDir::string())-> {ok, Pid::pid()} | ignore | {error, Error::term()} 93%% @doc Start the monitoring process 94%% @end 95%%---------------------------------------------------------------------- 96start(LogDir) -> 97 ?LOG("starting monitor, global ~n",?NOTICE), 98 gen_server:start_link({global, ?MODULE}, ?MODULE, [LogDir], []). 99 100%% @spec start_clients({Machines::term(), Dump::string(), BackEnd::atom()}) -> ok 101start_clients({Machines, Dump, BackEnd}) -> 102 gen_server:call({global, ?MODULE}, {start_logger, Machines, Dump, BackEnd}, 103 infinity). 104stop() -> 105 gen_server:cast({global, ?MODULE}, {stop}). 106 107status() -> 108 gen_server:call({global, ?MODULE}, {status}). 109 110abort() -> 111 gen_server:cast({global, ?MODULE}, {abort}). 112 113dumpstats() -> 114 gen_server:cast({global, ?MODULE}, {dumpstats}). 115 116newclient({Who, When}) -> 117 gen_server:cast({global, ?MODULE}, {newclient, Who, When}). 118 119endclient({Who, When, Elapsed}) -> 120 gen_server:cast({global, ?MODULE}, {endclient, Who, When, Elapsed}). 121 122sendmes({none, _, _}) -> skip; 123sendmes({protocol, _, _}) -> skip; 124sendmes({protocol_local, _, _}) -> skip; 125sendmes({_Type, Who, What}) -> 126 gen_server:cast({global, ?MODULE}, {sendmsg, Who, ?TIMESTAMP, What}). 127 128rcvmes({none, _, _}) -> skip; 129rcvmes({protocol, _, _})-> skip; 130rcvmes({protocol_local, _, _})-> skip; 131rcvmes({_, _, closed}) -> skip; 132rcvmes({_Type, Who, What}) -> 133 gen_server:cast({global, ?MODULE}, {rcvmsg, Who, ?TIMESTAMP, What}). 134 135dump({none, _, _})-> skip; 136dump({cached, << >> })-> skip; 137dump({_Type, Who, What}) -> 138 gen_server:cast({global, ?MODULE}, {dump, Who, ?TIMESTAMP, What}); 139dump({cached, Data})-> 140 gen_server:cast({global, ?MODULE}, {dump, cached, Data}). 141 142launcher_is_alive() -> 143 gen_server:cast({global, ?MODULE}, {launcher_is_alive}). 144 145 146%%%---------------------------------------------------------------------- 147%%% Callback functions from gen_server 148%%%---------------------------------------------------------------------- 149 150%%---------------------------------------------------------------------- 151%% Func: init/1 152%% Returns: {ok, State} | 153%% {ok, State, Timeout} | 154%% ignore | 155%% {stop, Reason} 156%%---------------------------------------------------------------------- 157init([LogDir]) -> 158 ?LOGF("Init, log dir is ~p~n",[LogDir],?INFO), 159 Stats = #stats{os_mon = dict:new()}, 160 State=#state{ dump_interval = ?config(dumpstats_interval), 161 log_dir = LogDir, 162 stats = Stats, 163 lastdate = ?NOW, 164 laststats = Stats 165 }, 166 case ?config(mon_file) of 167 "-" -> 168 {ok, State#state{log=standard_io}}; 169 Name -> 170 Filename = filename:join(LogDir, Name), 171 case file:open(Filename,[append]) of 172 {ok, Stream} -> 173 ?LOG("starting monitor~n",?INFO), 174 {ok, State#state{log=Stream}}; 175 {error, Reason} -> 176 ?LOGF("Can't open mon log file! ~p~n",[Reason], ?ERR), 177 {stop, Reason} 178 end 179 end. 180 181%%---------------------------------------------------------------------- 182%% Func: handle_call/3 183%% Returns: {reply, Reply, State} | 184%% {reply, Reply, State, Timeout} | 185%% {noreply, State} | 186%% {noreply, State, Timeout} | 187%% {stop, Reason, Reply, State} | (terminate/2 is called) 188%% {stop, Reason, State} (terminate/2 is called) 189%%---------------------------------------------------------------------- 190handle_call({start_logger, Machines, DumpType, Backend}, From, State) -> 191 start_logger({Machines, DumpType, Backend}, From, State); 192 193%%% get status 194handle_call({status}, _From, State=#state{stats=Stats}) -> 195 {ok, Localhost} = ts_utils:node_to_hostname(node()), 196 CpuName = {{cpu,"tsung_controller@"++Localhost}, sample}, 197 CPU = case dict:find(CpuName,Stats#stats.os_mon) of 198 {ok, [ValCPU|_]} -> ValCPU ; 199 _ -> 0 200 end, 201 202 Request = ts_stats_mon:status(request), 203 Interval = ts_utils:elapsed(State#state.lastdate, ?NOW) / 1000, 204 Phase = ts_stats_mon:status(newphase,sum), 205 Connected = case ts_stats_mon:status(connected,sum) of 206 {ok, Val} -> Val; 207 _ -> 0 208 end, 209 Reply = { State#state.client, Request, Connected, Interval, Phase, CPU}, 210 {reply, Reply, State}; 211 212handle_call(Request, _From, State) -> 213 ?LOGF("Unknown call ~p !~n",[Request],?ERR), 214 Reply = ok, 215 {reply, Reply, State}. 216 217%%---------------------------------------------------------------------- 218%% Func: handle_cast/2 219%% Returns: {noreply, State} | 220%% {noreply, State, Timeout} | 221%% {stop, Reason, State} (terminate/2 is called) 222%%---------------------------------------------------------------------- 223handle_cast({add, _Data}, State=#state{wait_gui=true}) -> 224 {noreply,State}; 225handle_cast({add, Data}, State=#state{stats=Stats}) when is_list(Data) -> 226 case State#state.backend of 227 fullstats -> io:format(State#state.fullstats,"~p~n",[Data]); 228 _Other -> ok 229 end, 230 New = lists:foldl(fun ts_stats_mon:add_stats_data/2, Stats#stats.os_mon, Data), 231 NewStats = Stats#stats{os_mon=New}, 232 {noreply,State#state{stats=NewStats}}; 233 234handle_cast({add, Data}, State=#state{stats=Stats}) when is_tuple(Data) -> 235 case State#state.backend of 236 fullstats -> io:format(State#state.fullstats,"~p~n",[Data]); 237 _Other -> ok 238 end, 239 New = ts_stats_mon:add_stats_data(Data, Stats#stats.os_mon), 240 NewStats = Stats#stats{os_mon=New}, 241 {noreply,State#state{stats=NewStats}}; 242 243handle_cast({newclient, Who, When}, State=#state{stats=Stats}) -> 244 Clients = State#state.client+1, 245 OldCount = Stats#stats.users_count, 246 NewStats = Stats#stats{users_count=OldCount+1}, 247 248 case State#state.type of 249 none -> ok; 250 protocol -> ok; 251 protocol_local -> ok; 252 _ -> 253 io:format(State#state.dumpfile,"NewClient:~w:~p~n",[ts_utils:time2sec_hires(When), Who]), 254 io:format(State#state.dumpfile,"load:~w~n",[Clients]) 255 end, 256 case Clients > State#state.maxclient of 257 true -> 258 {noreply, State#state{client = Clients, maxclient=Clients, stats=NewStats}}; 259 false -> 260 {noreply, State#state{client = Clients, stats=NewStats}} 261 end; 262 263handle_cast({endclient, Who, When, Elapsed}, State=#state{stats=Stats}) -> 264 Clients = State#state.client-1, 265 OldSession = Stats#stats.session, 266 %% update session sample 267 NewSession = ts_stats_mon:update_stats(sample, OldSession, Elapsed), 268 OldCount = Stats#stats.finish_users_count, 269 NewStats = Stats#stats{finish_users_count=OldCount+1,session= NewSession}, 270 271 case State#state.type of 272 none -> 273 skip; 274 protocol -> 275 skip; 276 protocol_local -> 277 skip; 278 _Type -> 279 io:format(State#state.dumpfile,"EndClient:~w:~p~n",[ts_utils:time2sec_hires(When), Who]), 280 io:format(State#state.dumpfile,"load:~w~n",[Clients]) 281 end, 282 {noreply, State#state{client = Clients, stats=NewStats}}; 283 284handle_cast({dumpstats}, State=#state{stats=Stats}) -> 285 export_stats(State), 286 NewSessions = ts_stats_mon:reset_all_stats(Stats#stats.session), 287 NewOSmon = ts_stats_mon:reset_all_stats(Stats#stats.os_mon), 288 NewStats = Stats#stats{session=NewSessions, os_mon=NewOSmon}, 289 {noreply, State#state{laststats = Stats, stats=NewStats,lastdate=?NOW}}; 290 291 292handle_cast({sendmsg, _, _, _}, State = #state{type = none}) -> 293 {noreply, State}; 294 295handle_cast({sendmsg, Who, When, What}, State = #state{type=light,dumpfile=Log}) -> 296 io:format(Log,"Send:~w:~w:~-44s~n",[ts_utils:time2sec_hires(When),Who, binary_to_list(What)]), 297 {noreply, State}; 298 299handle_cast({sendmsg, Who, When, What}, State=#state{type=full,dumpfile=Log}) when is_binary(What)-> 300 io:format(Log,"Send:~w:~w:~s~n",[ts_utils:time2sec_hires(When),Who,binary_to_list(What)]), 301 {noreply, State}; 302 303handle_cast({sendmsg, Who, When, What}, State=#state{type=full,dumpfile=Log}) -> 304 io:format(Log,"Send:~w:~w:~p~n",[ts_utils:time2sec_hires(When),Who,What]), 305 {noreply, State}; 306 307handle_cast({dump, Who, When, What}, State=#state{type=protocol,dumpfile=Log}) -> 308 io:format(Log,"~w;~w;~s~n",[ts_utils:time2sec_hires(When),Who,What]), 309 {noreply, State}; 310 311handle_cast({dump, cached, Data}, State=#state{type=protocol,dumpfile=Log}) -> 312 file:write(Log,Data), 313 {noreply, State}; 314 315handle_cast({rcvmsg, _, _, _}, State = #state{type=none}) -> 316 {noreply, State}; 317 318handle_cast({rcvmsg, Who, When, What}, State = #state{type=light, dumpfile=Log}) when is_binary(What)-> 319 io:format(Log,"Recv:~w:~w:~-44s~n",[ts_utils:time2sec_hires(When),Who, binary_to_list(What)]), 320 {noreply, State}; 321handle_cast({rcvmsg, Who, When, What}, State = #state{type=light, dumpfile=Log}) -> 322 io:format(Log,"Recv:~w:~w:~-44p~n",[ts_utils:time2sec_hires(When),Who, What]), 323 {noreply, State}; 324 325handle_cast({rcvmsg, Who, When, What}, State=#state{type=full,dumpfile=Log}) when is_binary(What)-> 326 io:format(Log, "Recv:~w:~w:~s~n",[ts_utils:time2sec_hires(When),Who,binary_to_list(What)]), 327 {noreply, State}; 328 329handle_cast({rcvmsg, Who, When, What}, State=#state{type=full,dumpfile=Log}) -> 330 io:format(Log, "Recv:~w:~w:~p~n",[ts_utils:time2sec_hires(When),Who,What]), 331 {noreply, State}; 332 333handle_cast({stop}, State = #state{client=0, launchers=1, timer_ref=TRef}) -> 334 ?LOG("Stop asked, no more users, last launcher stopped, OK to stop~n", ?INFO), 335 case ?config(keep_web_gui) of 336 true -> 337 io:format(standard_io,"All slaves have stopped; keep controller and web dashboard alive. ~nHit CTRL-C or click Stop on the dashboard to stop.~n",[]), 338 timer:cancel(TRef), 339 close_stats(State), 340 {noreply, State#state{wait_gui=true}}; 341 _ -> 342 {stop, normal, State} 343 end; 344handle_cast({stop}, State=#state{launchers=L}) -> % we should stop, wait until no more clients are alive 345 ?LOG("A launcher has finished, but not all users have finished, wait before stopping~n", ?NOTICE), 346 {noreply, State#state{stop = true, launchers=L-1}}; 347 348handle_cast({launcher_is_alive}, State=#state{launchers=L}) -> 349 ?LOG("A launcher has started~n", ?INFO), 350 {noreply, State#state{launchers=L+1}}; 351 352 353handle_cast({abort}, State) -> % stop now ! 354 ?LOG("Aborting by request !~n", ?EMERG), 355 ts_stats_mon:add({ count, error_abort }), 356 {stop, abort, State}; 357 358handle_cast(Msg, State) -> 359 ?LOGF("Unknown msg ~p !~n",[Msg], ?WARN), 360 {noreply, State}. 361 362 363%%---------------------------------------------------------------------- 364%% Func: handle_info/2 365%% Returns: {noreply, State} | 366%% {noreply, State, Timeout} | 367%% {stop, Reason, State} (terminate/2 is called) 368%%---------------------------------------------------------------------- 369handle_info(_Info, State) -> 370 {noreply, State}. 371 372 373close_stats(State) -> 374 export_stats(State), 375 % blocking call to all ts_stats_mon; this way, we are 376 % sure the last call to dumpstats is finished 377 lists:foreach(fun(Name) -> ts_stats_mon:status(Name) end, ?STATSPROCS), 378 case State#state.backend of 379 json -> 380 io:format(State#state.log,"]}]}~n",[]); 381 _ -> 382 io:format(State#state.log,"EndMonitor:~w~n",[?TIMESTAMP]) 383 end, 384 case State#state.log of 385 standard_io -> ok; 386 Dev -> file:close(Dev) 387 end, 388 file:close(State#state.fullstats). 389 390%%---------------------------------------------------------------------- 391%% Func: terminate/2 392%% Purpose: Shutdown the server 393%% Returns: any (ignored by gen_server) 394%%---------------------------------------------------------------------- 395terminate(Reason, #state{wait_gui=true}) -> 396 ?LOGF("stopping monitor by gui (~p)~n",[Reason],?NOTICE); 397terminate(Reason, State) -> 398 ?LOGF("stopping monitor (~p)~n",[Reason],?NOTICE), 399 close_stats(State), 400 slave:stop(node()). 401 402%%-------------------------------------------------------------------- 403%% Func: code_change/3 404%% Purpose: Convert process state when code is changed 405%% Returns: {ok, NewState, NewStateData} 406%%-------------------------------------------------------------------- 407code_change(_OldVsn, StateData, _Extra) -> 408 {ok, StateData}. 409 410%%%---------------------------------------------------------------------- 411%%% Internal functions 412%%%---------------------------------------------------------------------- 413 414%%---------------------------------------------------------------------- 415%% Func: start_logger/3 416%% Purpose: open log files and start timer 417%% Returns: {reply, ok, State} | {stop, Reason, State} 418%%---------------------------------------------------------------------- 419%% fulltext backend: open log file with compression enable and delayed_write 420start_logger({Machines, DumpType, fullstats}, From, State=#state{fullstats=undefined}) -> 421 Filename = filename:join(State#state.log_dir,?FULLSTATS_FILENAME), 422 ?LOG("Open file with delayed_write for fullstats backend~n",?NOTICE), 423 case file:open(Filename,[write, {delayed_write, ?DELAYED_WRITE_SIZE, ?DELAYED_WRITE_DELAY}]) of 424 {ok, Stream} -> 425 start_logger({Machines, DumpType, fullstats}, From, State#state{fullstats=Stream}); 426 {error, Reason} -> 427 ?LOGF("Can't open mon log file ~p! ~p~n",[Filename,Reason], ?ERR), 428 {stop, Reason, State} 429 end; 430 431start_logger({Machines, DumpType, Backend}, _From, State=#state{log=Log,fullstats=FS}) -> 432 ?LOGF("Activate clients with ~p backend~n",[Backend],?NOTICE), 433 print_headline(Log,Backend), 434 start_launchers(Machines), 435 {ok, TRef} = timer:apply_interval(State#state.dump_interval, ?MODULE, dumpstats, [] ), 436 lists:foreach(fun(Name) -> ts_stats_mon:set_output(Backend,{Log,FS}, Name) end, ?STATSPROCS), 437 start_dump(State#state{type=DumpType, backend=Backend, timer_ref=TRef}). 438 439print_headline(Log,json)-> 440 DateStr = ts_utils:now_sec(), 441 io:format(Log,"{~n \"stats\": [~n {\"timestamp\": ~p, \"samples\": [",[DateStr]); 442print_headline(_Log,_Backend)-> 443 ok. 444 445%% @spec start_dump(State::record(state)) -> {reply, Reply, State} 446%% @doc open file for dumping traffic 447start_dump(State=#state{type=none}) -> 448 {reply, ok, State}; 449start_dump(State=#state{type=Type}) -> 450 Filename = filename:join(State#state.log_dir,?DUMP_FILENAME), 451 case file:open(Filename,[write, {delayed_write, ?DELAYED_WRITE_SIZE, ?DELAYED_WRITE_DELAY}]) of 452 {ok, Stream} -> 453 ?LOG("dump file opened, starting monitor~n",?INFO), 454 case Type of 455 protocol -> 456 io:format(Stream,"#date;pid;id;http method;host;URL;HTTP status;size;duration;transaction;match;error;tag~n",[]); 457 _ -> 458 ok 459 end, 460 {reply, ok, State#state{dumpfile=Stream}}; 461 {error, Reason} -> 462 ?LOGF("Can't open mon dump file! ~p~n",[Reason], ?ERR), 463 {reply, ok, State#state{type=none}} 464 end. 465 466%%---------------------------------------------------------------------- 467%% Func: export_stats/1 468%%---------------------------------------------------------------------- 469export_stats(State=#state{log=Log,stats=Stats,laststats=LastStats, backend=json}) -> 470 DateStr = ts_utils:now_sec(), 471 io:format(Log,"]},~n {\"timestamp\": ~w, \"samples\": [",[DateStr]), 472 %% print number of simultaneous users 473 io:format(Log," {\"name\": \"users\", \"value\": ~p, \"max\": ~p}",[State#state.client,State#state.maxclient]), 474 export_stats_common(json, Stats,LastStats,Log); 475 476export_stats(State=#state{log=Log,stats=Stats,laststats=LastStats, backend=BackEnd}) -> 477 DateStr = ts_utils:now_sec(), 478 io:format(Log,"# stats: dump at ~w~n",[DateStr]), 479 %% print number of simultaneous users 480 io:format(Log,"stats: ~p ~p ~p~n",[users,State#state.client,State#state.maxclient]), 481 export_stats_common(BackEnd, Stats,LastStats,Log). 482 483export_stats_common(BackEnd, Stats,LastStats,Log)-> 484 Param = {BackEnd,LastStats#stats.os_mon,Log}, 485 dict:fold(fun ts_stats_mon:print_stats/3, Param, Stats#stats.os_mon), 486 ts_stats_mon:print_stats({session, sample}, Stats#stats.session,{BackEnd,[],Log}), 487 ts_stats_mon:print_stats({users_count, count}, 488 Stats#stats.users_count, 489 {BackEnd,LastStats#stats.users_count,Log}), 490 ts_stats_mon:print_stats({finish_users_count, count}, 491 Stats#stats.finish_users_count, 492 {BackEnd,LastStats#stats.finish_users_count,Log}), 493 lists:foreach(fun(Name) -> ts_stats_mon:dumpstats(Name) end, ?STATSPROCS). 494 495%%---------------------------------------------------------------------- 496%% Func: start_launchers/2 497%% @doc start the launcher on clients nodes 498%%---------------------------------------------------------------------- 499start_launchers(Machines) -> 500 ?LOGF("Tsung clients setup: ~p~n",[Machines],?DEB), 501 GetHost = fun(A) -> list_to_atom(A#client.host) end, 502 HostList = lists:map(GetHost, Machines), 503 ?LOGF("Starting tsung clients on hosts: ~p~n",[HostList],?NOTICE), 504 %% starts beam on all client hosts 505 ts_config_server:newbeams(HostList). 506 507%% post_process_logs(FileName) -> 508%% {ok, Device} = file:open(FileName, [read]), 509%% post_process_line(io:get_line(Device, ""), Device, []). 510 511%% post_process_line(eof, Device, State) -> 512%% file:close(Device); 513%% post_process_line("End "++ _, Device, Logs) -> 514%% post_process_line(io:get_line(Device, ""),Device, Logs); 515%% post_process_line("# stats: dump at "++ TimeStamp, D, Logs=#logs{start_time=undefined}) -> 516%% {StartTime,_}=string:to_integer(TimeStamp), 517%% post_process_line(io:get_line(D, ""),D, #logs{start_time=StartTime}); 518%% post_process_line("# stats: dump at "++ TimeStamp, Dev, Logs) -> 519%% {Time,_}=string:to_integer(TimeStamp), 520%% Current=Time-Logs#logs.start_time, 521%% post_process_line(io:get_line(Dev, ""),Dev, Logs#logs{current_time=Current}); 522%% post_process_line("# stats: "++ Stats, Dev, Logs) -> 523%% case string:tokens(Stats," ") of 524%% {"users",Count, GlobalCount} -> 525%% todo; 526%% {Name, Count, Max} -> 527%% todo; 528%% {"tr_" ++ TrName, Count, Mean, StdDev, Max, Min, GMean,GCount} -> 529%% todo; 530%% {"{"++ Name, Count, Mean, StdDev, Max, Min, GMean,GCount} -> 531%% todo; 532%% {Name, Count, Mean, StdDev, Max, Min, GMean,GCount} -> 533%% todo 534%% end, 535%% post_process_line(io:get_line(Dev, ""),Dev, Logs). 536