%% ``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 via the world wide web 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.
%%
%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
%% AB. All Rights Reserved.''
%%
%% $Id$
%%
%% Author: Lennart �hman, [email protected]
%%
%% INVISO LogFileMerger TracePort File READER.
%%
%% This module implements a reader process capable of reading traceport files
%% and feeding them according to the logfile merger process message protocoll
%% to the logfile merger process.
%% This module can also serve as example for writing file readers for other
%% file formats.
%%
%% A reader process must:
%% Support the reader-receiver protocoll.
%% receive next_entry message: {get_next_entry,ReceiverPid}
%% recieve stop message should the receiver wish to quit: {stop,ReceiverPid}.
%% send next_entry message, either with entry or fault-code.
%% next_entry message contains:{next_entry,self(),PidMappings,Timestamp,Term}
%% {next_entry,self(),Error}
%% recognize receiver termination (EXIT-signal).
%% Understand logfile structure, both filename structure and content.
%% Understand content (log-entry) details to extract the entry and entry
%% components as timestamp and originating pid (to make pid-mappings).
%% Understand any trace information files (ti).
%%
%% The logfile structure written by inviso_rt_meta is:
%% {Pid,Alias,Op,TimeStamp} where:
%% Pid=pid(), if Alias==unalias: pid()|other_than_pid()
%% Op=alias|unalias,
%% TimeStamp=now()
%% -----------------------------------------------------------------------------
-module(inviso_lfm_tpfreader).
-export([init/2]).
%% -----------------------------------------------------------------------------
-export([handle_logfile_sort_wrapset/1]). % Exported as a service to other readers.
%% -----------------------------------------------------------------------------
%% init(RecPid,FileStruct)=N/A
%% RecPid=pid(), the process id of the log file merger.
%% FileStruct=LogFiles | [LogFiles,...]
%% LogFiles=[{trace_log,[File,...]} [,{ti_log,[File]}] ]
%% File=string()
%% Spawn on this function to start a reader process for trace-port generated
%% logfiles, possibly with inviso-generated ti-files.
init(RecPid,LogFiles=[Tuple|_]) when is_tuple(Tuple) -> % Only one LogFiles.
init(RecPid,[LogFiles]);
init(RecPid,FileStruct) when is_list(FileStruct) ->
logfiles_loop(RecPid,FileStruct).
%% -----------------------------------------------------------------------------
logfiles_loop(RecPid,[LogFiles|Rest]) ->
{TIalias,TIunalias}=handle_ti_file(LogFiles),% If there is a ti-file, read it.
Files=handle_logfiles(LogFiles), % Returns a sorted list of logfiles.
case open_next_file(Files) of
{ok,FileName,FD,NewFiles} ->
case loop(RecPid,FileName,NewFiles,TIalias,TIunalias,FD) of
next ->
logfiles_loop(RecPid,Rest);
stop ->
true % Terminate normally.
end;
done -> % Hmm, already out of files.
true; % Then lets terminate normally.
{error,Reason} -> % Couldn't even open the first file.
exit(Reason)
end;
logfiles_loop(_RecPid,[]) -> % No more files in LogFiles.
true. % Terminate normally.
%% This workloop reads an entry from the input file upon request from the merger
%% process and sends it back to the merger process (Parent). If the file ends
%% there are more files to open and read in Files, the next file will be opened.
loop(RecPid,FileName,Files,TIalias,TIunalias,FD) ->
receive
{get_next_entry,RecPid} -> % The receiver request the next entry.
case fetch_next(FileName,FD,Files) of
{ok,Term,NewCurrFile,NewFiles,NewFD} ->
TS=find_timestamp_in_term(Term),
PidMappings=make_pid_mappings(Term,TIalias,TIunalias,TS),
RecPid ! {next_entry,self(),PidMappings,TS,Term},
loop(RecPid,NewCurrFile,NewFiles,TIalias,TIunalias,NewFD);
{error,Reason} -> % Not a properly formatted entry.
RecPid ! {next_entry,self(),{error,Reason}},
loop(RecPid,FileName,Files,TIalias,TIunalias,FD);
done -> % No more files to read in this LogFiles.
next % Are there more Files in FileStruct?
end;
{stop,RecPid} -> % The receiver process is done.
file:close(FD), % Close file and terminate normally.
stop
end.
%% -----------------------------------------------------------------------------
%% Function which reads the next trace-entry from the file handled by FD, or if
%% that file reaches EOF opens the next file in Files. Files must be sorted in
%% the correct order.
%% Returns {ok,Term,NewFileName,NewFiles,NewFD}, {error,Reason} or 'done'.
fetch_next(FileName,FD,Files) ->
case read_traceport_file(FileName,FD) of
{ok,Term} -> % There were more terms in the file.
{ok,Term,FileName,Files,FD}; % No changes necessary then.
eof -> % This file is empty, try next file!
file:close(FD),
case open_next_file(Files) of
{ok,NewFileName,NewFD,NewFiles} -> % A new file has been opened.
fetch_next(NewFileName,NewFD,NewFiles); % Try again.
done -> % No more files.
done;
{error,Reason} -> % Problems opening files.
{error,Reason}
end;
{error,Reason} -> % Problems reading the file.
{error,Reason}
end.
read_traceport_file(FileName,FD) ->
case file:read(FD,5) of % Trace-port file entries start with 5 bytes.
{ok,<<0,Size:32>>} -> % Each entry in a traceport file begins.
case file:read(FD,Size) of
{ok,Bin} when is_binary(Bin),size(Bin)=:=Size ->
try binary_to_term(Bin) of
Term -> % Bin was a properly formatted term!
{ok,Term}
catch
error:_Reason -> % Not a properly formatted term!
{error,{binary_to_term,[FileName,Bin]}}
end;
{ok,Bin} -> % Incorrect length.
{error,{faulty_length,[FileName,Size,Bin]}};
eof -> % This is premature end of file!
{error,{premature_eof,FileName}}
end;
{ok,<<1,DroppedMsgs:32>>} ->
{ok,{drop,DroppedMsgs}};
{ok,JunkBin} -> % Don't understand, report it as error.
{error,{junk,[FileName,JunkBin]}};
eof -> % A correct end of file!
eof
end.
%% Help function which opens a file in raw binary mode and returns
%% {ok,FileName,FD,Rest} or {error,Reason}.
open_next_file([]) -> % There are no more files to open.
done;
open_next_file([FileName|Rest]) ->
case file:open(FileName,[read,raw,binary]) of
{ok,FD} ->
{ok,FileName,FD,Rest};
{error,Reason} ->
{error,{open,[FileName,Reason]}}
end.
%% ------------------------------------------------------------------------------
%% ==============================================================================
%% Help functions.
%% ==============================================================================
%% Help function which extract the originating process id from the log entry
%% term and returns a list of all associations to the PID found in TIalias.
make_pid_mappings(_,void,_,_) -> % Trace Information is not used.
[]; % Simply no pid mappings then!
make_pid_mappings(Term,TIalias,TIunalias,TS)
when element(1,Term)==trace;element(1,Term)==trace_ts ->
Pid=element(2,Term), % The pid.
TempAliases=find_aliases(ets:lookup(TIalias,Pid),TS),
remove_expired_aliases(TempAliases,TIalias,TIunalias,TS),
lists:map(fun({_,_,Alias})->Alias end,
find_aliases(ets:lookup(TIalias,Pid),TS));
make_pid_mappings(_Term,_TIalias,_TIunalias,_TS) -> % Don't understand Term.
[]. % Simply no translations then!
%% Help function traversing a list of ets-alias-table entries and returning a
%% list of those old enough to have happend before TS.
%% Note that it is possible to have an Offset in microseconds. This because an
%% association may end up in the ti-file a short time after logentries starts
%% to appear in the log file for the process in question. We therefore like to
%% allow some slack,
find_aliases(List,TS) ->
lists:filter(fun({_,Now,_}) when Now<TS -> true;
(_) -> false
end,
List).
%% ------------------------------------------------------------------------------
%% Help function which removes aliases that are no longer valid from the
%% ETS table. It uses unalias entries which are older than TS but younger than
%% the alias association.
%% Returns nothing significant.
remove_expired_aliases([{Pid,Now1,Alias}|Rest],TIalias,TIunalias,TS) ->
Candidates=ets:lookup(TIunalias,Alias),
lists:foreach(fun({_,Now2,P})
when (Now2>Now1) and
(Now2<TS) and
((P==Pid) or (not(is_pid(P)))) ->
ets:delete_object(TIalias,{Pid,Now1,Alias}),
true; % This alias is infact no longer.
(_) ->
false
end,
Candidates),
remove_expired_aliases(Rest,TIalias,TIunalias,TS);
remove_expired_aliases([],_,_,_) ->
true.
%% ------------------------------------------------------------------------------
find_timestamp_in_term({trace_ts,_,_,_,TS}) ->
TS;
find_timestamp_in_term({trace_ts,_,_,_,_,TS}) ->
TS;
find_timestamp_in_term(_) -> % Don't know if there is a timestamp.
false.
%% -----------------------------------------------------------------------------
%% -----------------------------------------------------------------------------
%% Help function handling a trace-information file and building the TIstruct storage.
%% -----------------------------------------------------------------------------
%% Help function which opens a standard ti-file, reads its content and
%% builds two ETS-table where PID is primary index in the one for aliases, and
%% the alias is primary index in the one for unalias.
%% Returns a handle to the two ETS tables.
%%
%% This function currently handles:
%% (1) plain straight raw binary files.
handle_ti_file(FileStruct) ->
case lists:keysearch(ti_log,1,FileStruct) of
{value,{_,[FileName]}} when is_list(FileName) -> % There is one ti-file in this set.
case file:open(FileName,[read,raw,binary]) of
{ok,FD} ->
TIdAlias=ets:new(list_to_atom("inviso_ti_atab_"++pid_to_list(self())),
[bag]),
TIdUnalias=ets:new(list_to_atom("inviso_ti_utab_"++pid_to_list(self())),
[bag]),
handle_ti_file_2(FD,TIdAlias,TIdUnalias), % Fill the table.
file:close(FD),
{TIdAlias,TIdUnalias};
{error,_Reason} -> % Hmm, unable to open the file.
{void,void} % Treat it as no ti-file.
end;
{value,_} -> % Some other file-set.
{void,void}; % Pretend we don't understand.
false -> % No ti-file in this set.
{void,void}
end.
handle_ti_file_2(FD,TIdAlias,TIdUnalias) ->
case file:read(FD,5) of % First read the header.
{ok,<<_,Size:32>>} ->
case file:read(FD,Size) of % Read the actual term.
{ok,Bin} when size(Bin)=:=Size ->
try binary_to_term(Bin) of
{Pid,Alias,alias,NowStamp} -> % Save this association.
ets:insert(TIdAlias,{Pid,NowStamp,Alias}),
handle_ti_file_2(FD,TIdAlias,TIdUnalias);
{Pid,Alias,unalias,NowStamp} ->
ets:insert(TIdUnalias,{Alias,NowStamp,Pid}),
handle_ti_file_2(FD,TIdAlias,TIdUnalias);
_Term -> % Don't understand!
handle_ti_file_2(FD,TIdAlias,TIdUnalias)
catch
error:_Reason -> % Badly formatted term
handle_ti_file_2(FD,TIdAlias,TIdUnalias)
end;
{ok,_JunkBin} -> % To short probably.
handle_ti_file_2(FD,TIdAlias,TIdUnalias); % Just drop it.
eof -> % Should not come here, but
{TIdAlias,TIdUnalias} % not much we can do, drop it and stop.
end;
{ok,_} -> % Also an error.
handle_ti_file_2(FD,TIdAlias,TIdUnalias);
eof -> % This is the normal eof point.
{TIdAlias,TIdUnalias}
end.
%% -----------------------------------------------------------------------------
%% -----------------------------------------------------------------------------
%% Help functions sorting out what kind of logfiles we have to deal with.
%% -----------------------------------------------------------------------------
%% Help function which takes the filestruct argument and retrieves the names
%% of all log-files mentioned there. If there are several logfiles, this function
%% sorts them beginning with the oldest. That means that this function must
%% have knowledge of how wrap-sets and so on works.
%% Today known set-types:
%% (1) file: One plain file.
%% (2) wrap_set: List of files belonging to a wrap-set. Must be sorted.
handle_logfiles(FileStruct) ->
handle_logfiles_2(lists:keysearch(trace_log,1,FileStruct)).
handle_logfiles_2({value,{_,[FileName]}}) when is_list(FileName)-> % One single plain file.
[FileName];
handle_logfiles_2({value,{_,Files}}) when is_list(Files) -> % A wrap-set.
handle_logfile_sort_wrapset(Files);
handle_logfiles_2(_) ->
[]. % Pretend there were no files otherwise.
%% Help function which sorts the files in WrapSet beginning with the oldest.
%% It assumes that a logfile is Name++SeqNo++Suffix.
%% First the Name and Suffix must be established. We look at all files to find
%% that out.
%% Returns a list of sorted filenames.
%% This function is exported since it might turn useful in own implemented
%% readers.
handle_logfile_sort_wrapset(Set=[_FileName]) -> % Only one file! Done then :-)
Set;
handle_logfile_sort_wrapset([]) -> % Also pretty simple :-)
[];
handle_logfile_sort_wrapset(FileSet) ->
Prefix=find_common_prefix(FileSet),
Suffix=find_common_prefix(lists:map(fun(Str)->lists:reverse(Str) end,FileSet)),
find_hole_in_wrapset(FileSet,length(Prefix),length(Suffix)).
%% Help function which finds the longest common prefix of all strings in the
%% argument-list. Returns that string.
find_common_prefix(Files=[[FirstChar|_]|_]) ->
find_common_prefix_2(Files,FirstChar,[],[]);
find_common_prefix([_|_]) -> % Means that prefix is "".
"".
find_common_prefix_2([[CurrChar|RestString]|Rest],CurrChar,Files,RevPrefix) ->
find_common_prefix_2(Rest,CurrChar,[RestString|Files],RevPrefix);
find_common_prefix_2([_String|_],_CurrChar,_Files,RevPrefix) ->
lists:reverse(RevPrefix); % Found a difference.
find_common_prefix_2([],CurrChar,Files=[[FirstChar|_]|_],RevPrefix) ->
find_common_prefix_2(Files,FirstChar,[],[CurrChar|RevPrefix]);
find_common_prefix_2([],CurrChar,_,RevPrefix) ->
lists:reverse([CurrChar|RevPrefix]). % Actually, prefix was entire string!
%% Help function which returns a sorted list of FileSet with the oldest first.
find_hole_in_wrapset(FileSet,PreLen,SufLen) ->
NumberedFiles=find_hole_in_wrapset_2(FileSet,PreLen,SufLen),
find_hole_in_wrapset_3(lists:sort(NumberedFiles),0,[]). % Wrap-sets start at 0.
find_hole_in_wrapset_2([FileName|Rest],PreLen,SufLen) ->
[{list_to_integer(lists:sublist(FileName,PreLen+1,length(FileName)-PreLen-SufLen)),
FileName}|
find_hole_in_wrapset_2(Rest,PreLen,SufLen)];
find_hole_in_wrapset_2([],_,_) ->
[].
find_hole_in_wrapset_3([{N,FileName}|Rest],N,Acc) ->
find_hole_in_wrapset_3(Rest,N+1,[FileName|Acc]);
find_hole_in_wrapset_3([{_,FileName}|Rest],_N,Acc) -> % FileName is the oldest one.
[FileName|lists:map(fun({_,FN})->FN end,Rest)]++lists:reverse(Acc);
find_hole_in_wrapset_3([],_,Acc) -> % Means all were in order.
lists:reverse(Acc).
%% -----------------------------------------------------------------------------