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