Trace abstraction as behaviour
Thomas Arts
thomas@REDACTED
Mon Feb 11 16:36:24 CET 2002
Hi all
If you want to visualize several traces of the same generic
servers interacting which each other, you might like the attached
new behaviour "gen_trace.erl".
You can define one abstraction over a trace and use it for
several traces. This might give you a better view on what the
program is actually doing.
The best part of it, as far as I see it, is that you can map
states in the trace that are equivalent under the abstraction to
the same node in a graph. As such, you get a very compact
picture of complex behaviour.
I attached some documentation with an example in the attached
postscript file.
Have fun
Thomas
---
Thomas Arts
Ericsson
Computer Science Laboratory
-------------- next part --------------
%%%----------------------------------------------------------------------
%%% File : gen_trace.erl
%%% Author : Thomas Arts <thomas@REDACTED>
%%% Purpose : behaviour for trace analyses of a gen_server and gen_fsm
%%% Created : 29 Nov 2001 by Thomas Arts <thomas@REDACTED>
%%% Modified: 01 Feb 2002 by Thomas Arts
%%% Last Mod: 08 Feb 2002 by Thomas Arts
%%%----------------------------------------------------------------------
-module(gen_trace).
-author('thomas@REDACTED').
-version("1.0").
-export([dump/3,
abstract/3,
abstract0/3,
proc_info/1,
proc_select/2,
show/1,
behaviour_info/1,
init/0, % default callbacks for gen_trace
abstract_state/2,
abstract_event/2,
terminate/1
]).
-export([davinci/2,
dot/2,
svg/2,
postscript/2,
dictionaries/2,
print_states/2,
cadp/2]).
-import(lists,[foldl/3,foldr/3,member/2,foreach/2,map/2,keysearch/3]).
%-define(DEBUG(S,A),io:format(S,A)).
-define(DEBUG(S,A),ok).
-define(ERROR(To,S,A),To!{error,S,A}).
-define(LEN,500). % defaultlength of trace
-define(MAXLEN,5000000). % maximum length of trace
-define(MAXWAIT,20). % default attempts to get initialized
-define(STATE_COLOUR,"yellow").
-define(EVENT_COLOUR,none).
-record(trace, {vertex_dict = % vk -> {abstract state,CB,real state}
dict:store(0,{init,?MODULE,init},dict:new()),
edge_dict = dict:new(), % ek -> {abstract event,CB,real event}
state_dict = % dict(sk,orddict(pid(),vk))
dict:store(0,orddict:new(),dict:new()),
states = 0, % sk :: integer()
graph = digraph:new(), % digraph({integer(),colour()})
actions = dict:new(), % dict(pid(),[ek :: integer()])
localstates = dict:new(), % dict(pid(),term())
sendpoints = dict:new(), % dict(pid(),{term(),pid(),vk,ek})
trace_procdict = false, % trace process dictionary
procdict = dict:new() , % dict(pid(),dict(Key,Value))
connect = trace,
collect = false, % group equal states together
mapping = [], % {pid(),name(),colour()}
callback = ?MODULE, % default callback
callbacks = dict:new() % specified callbacks
}).
behaviour_info(callbacks) ->
[{init,0},{abstract_state,2},{abstract_event,2}].
%%%%%%%%%%%%%%% Default CallBack part %%%%%%%%%%%%%%%%%%%%%%%%%%%
init() ->
init.
abstract_state(Term,LS) ->
{Term,LS}.
abstract_event(Event,LS) ->
{Event,LS}.
terminate(LS) ->
ok.
%%%%%%%%%%%%%%% End CallBack part %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%+type dump([pid()],filename(),[option()]) -> trace_process :: pid().
% where option() is
% {len,int()} | {timeout, seconds::int() | infinity}
%
% the processes in the given list are located and their actions are traced
dump(Processes,File,Options) ->
{Local,Remote} =
foldl(fun({N,P},{Ls,Rs}) ->
case N==node() of
true ->
{[P|Ls],Rs};
false ->
{Ls,appendnode({N,P},Rs)}
end;
(P,{Ls,Rs}) ->
{[P|Ls],Rs}
end,{[],[]},Processes),
spawn(fun() -> tracestart([{node(),Local}|Remote],File,Options) end).
tracestart(NodeProcesses,File,Options) ->
process_flag(trap_exit,true),
Collector = self(),
ProcDict = options(process_dictionary,false,Options),
foreach(fun({Node,[]}) ->
ok;
({Node,Procs}) ->
spawn_link(Node,fun() -> tracer(Collector,Procs,ProcDict) end)
end,NodeProcesses),
{Length,Timer} =
case keysearch(timeout,1,Options) of
{value,{timeout,Seconds}} when integer(Seconds) ->
{options(len,?MAXLEN,Options),
spawn_link(fun() ->
receive
after Seconds*1000 ->
exit(timeout)
end
end)};
_ ->
{options(len,?LEN,Options),self()} % wait until length reached
end,
{ok,_} =
disk_log:open([{name,tracelog},{file,filename:absname(File)}]),
disk_log:log(tracelog,{trace,self(),processes,
foldl(fun({N,Ps},Total) ->
length(Ps)+Total
end,0,NodeProcesses)}),
disk_log:log(tracelog,{trace,self(),trace_process_dictionary,ProcDict}),
traceloop(Timer,Length).
traceloop(Timer,0) ->
disk_log:close(tracelog),
io:format("~nTracing has finished~n"),
exit(tracer_finished); % kills all remote tracing processes
traceloop(Timer,Length) ->
receive
stoptracing ->
traceloop(Timer,0);
{'EXIT',Timer,_} ->
traceloop(Timer,0);
X ->
ok = disk_log:log(tracelog,X),
io:format("."),
traceloop(Timer,Length-1)
end.
% switch all local function tracing off. Thus, only exported functions
% are traced
%
tracer(Collector,Procs,ProcDict) ->
?DEBUG("tracer started node ~p pid ~p~n",[node(),self()]),
erlang:trace_pattern({'_','_','_'},false,[local]), % recover pman bug
erlang:trace_pattern({gen_server,loop,'_'},true,[local]),
erlang:trace_pattern({gen_fsm,loop,'_'},true,[local]),
erlang:trace_pattern({erlang,put,'_'},ProcDict,[local]),
tracemodules(Collector,Procs,[],[],sets:new()).
tracemodules(Collector,SearchPids,PidsToTrace,Mapping,Events) ->
{NewSearch,Pids,NewMap} =
foldl(fun(P,{NSs,NPs,M}) when pid(P) ->
Collector!{trace,P,process_dict,process_info(P,dictionary)},
erlang:monitor(process,P),
erlang:trace(P,true,[call,send,{tracer,self()}]),
{NSs,[P|NPs],M};
(P,{NSs,NPs,M}) ->
?DEBUG("Searching process activity for ~p~n",[P]),
case whereis(P) of
Pid when pid(Pid) ->
Collector!{trace,Pid,mapped,{node(),P}},
Collector!{trace,Pid,process_dict,
process_info(Pid,dictionary)},
erlang:monitor(process,Pid),
erlang:trace(Pid,true,[call,send,{tracer,self()}]),
{NSs,[Pid|NPs],[{Pid,P}|M]};
_ ->
{[P|NSs],NPs,M}
end
end, {[],PidsToTrace,Mapping}, SearchPids),
receive
Message ->
case Message of
{trace,Pid,call,{M,F,As}} ->
case member(Pid,Pids) of
true ->
case {M,F,As} of
{gen_server,loop,[_,_,State,Mod,_,_]} ->
NewEvents =
sets:union(Events,
tracepattern(gen_server,Mod)),
tracemodules(Collector,NewSearch,Pids--[Pid],
NewMap,NewEvents);
{gen_fsm,loop,[_,_,State,Data,Mod,_,_]} ->
Collector!{trace,Pid,gen_fsm_state,State},
NewEvents =
sets:add_element({Mod,State,2},
sets:add_element({Mod,State,3},
sets:union(Events,
tracepattern(gen_fsm,Mod)))),
tracemodules(Collector,NewSearch,Pids--[Pid],
NewMap,NewEvents);
{erlang,put,Args} ->
Collector!Message,
tracemodules(Collector,NewSearch,Pids,
NewMap,Events);
Other ->
?DEBUG("tracer ignored: ~p~n",[Other]),
tracemodules(Collector,NewSearch,Pids,
NewMap,Events)
end;
false ->
NewEvents =
tracehandle(Collector,Events,Message),
tracemodules(Collector,NewSearch,Pids,
NewMap,NewEvents)
end;
{'DOWN', _, process, Pid, Info} ->
Collector!{trace,Pid,'DOWN',Info},
case keysearch(Pid,1,NewMap) of
{value,{_,Proc}} ->
tracemodules(Collector,[Proc|NewSearch],Pids--[Pid],
NewMap--[{Pid,Proc}],Events);
_ ->
tracemodules(Collector,NewSearch,Pids--[Pid],
NewMap,Events)
end;
{trace,Pid,send,Msg,To} ->
Collector!{trace,Pid,send,Msg,To},
tracemodules(Collector,NewSearch,Pids,NewMap,Events);
Other ->
?DEBUG("tracer ignored message: ~p~n",[Other]),
tracemodules(Collector,NewSearch,Pids,NewMap,Events)
end
after 20 ->
tracemodules(Collector,NewSearch,Pids,NewMap,Events)
end.
tracehandle(Collector,Events,Message) ->
case Message of
{trace,Pid,call,{erlang,put,Args}} ->
Collector!Message,
Events;
{trace,Pid,call,{gen_server,loop,_}} ->
Events;
{trace,Pid,call,{gen_fsm,loop,[_,_,State,Data,Mod,_,_]}} ->
case sets:is_element({Mod,State,2},Events) of
true ->
Events;
false ->
Collector!{trace,Pid,gen_fsm_state,State},
sets:add_element({Mod,State,2},
sets:add_element({Mod,State,3},Events))
end;
{trace,Pid,call,{Mod,Func,Args}} ->
ShouldTrace =
sets:fold(fun({M,F,A},B) ->
B or ((M==Mod) and (F==Func))
end,false,Events),
case ShouldTrace of
true ->
case sets:is_element({Mod,Func,length(Args)},Events) of
true ->
Collector!{trace,Pid,call,{Mod,Func,Args}};
false ->
?DEBUG("tracer ignored message: ~p~n",
[{trace,Pid,call,{Mod,Func,Args}}])
end;
false ->
?DEBUG("drop tracing ~p~n",[{Mod,Func,Args}]),
erlang:trace_pattern({Mod,Func,'_'},false,[global])
end,
Events;
Other ->
?DEBUG("tracer ignored call: ~p~n",[Other]),
Events
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%+type proc_info(filename()) -> {[pid() | {node(),pid()}],length::integer()}.
%
proc_info(InFile) ->
{Procs,Mapping,Nr} = proc_info2(InFile),
{map(fun(P) ->
case dict:find(P,Mapping) of
error ->
P;
{_,Name} ->
Name
end
end,sets:to_list(Procs)),Nr}.
proc_info2(InFile) ->
{ok,_} = disk_log:open([{name,tracelog},{file,InFile}]),
{Procs,Mapping,Nr} = scan_proc_info(start,dict:new(),sets:new(),0),
disk_log:close(tracelog),
{Procs,Mapping,Nr}.
scan_proc_info(Continuation,Mapping,Procs,Nr) ->
case disk_log:chunk(tracelog,Continuation,1) of
eof ->
{Procs,Mapping,Nr};
{NewContinuation,[Term]} ->
case Term of
{trace,Pid,processes,OriginalNr} ->
%io:format("original attempt to trace ~p processes~n",
% [OriginalNr]),
scan_proc_info(NewContinuation,Mapping,Procs,Nr);
{trace,Pid,mapped,{nonode@REDACTED,Name}} ->
scan_proc_info(NewContinuation,
dict:store(Pid,Name,Mapping),Procs,Nr);
{trace,Pid,mapped,{Node,Name}} ->
scan_proc_info(NewContinuation,
dict:store(Pid,{Node,Name},Mapping),Procs,Nr);
Other when tuple(Other) ->
Pid = element(2,Other),
scan_proc_info(NewContinuation,Mapping,
sets:add_element(Pid,Procs),Nr+1)
end
end.
%+type proc_select(filename(), [pid() | {node(),pid()}]) -> ok.
%
proc_select(InFile,Procs) ->
{ok,_} = disk_log:open([{name,tracelog},{file,InFile}]),
{ok,_} = disk_log:open([{name,selectlog},
{file,filename:rootname(InFile)++"-sel"++
filename:extension(InFile)}]),
disk_log:log(selectlog,{trace,self(),processes,length(Procs)}),
scan_proc_select(start,Procs),
disk_log:close(selectlog),
disk_log:close(tracelog).
scan_proc_select(Continuation,Procs) ->
case disk_log:chunk(tracelog,Continuation,1) of
eof ->
ok;
{NewContinuation,[Term]} ->
case Term of
{trace,Pid,mapped,{nonode@REDACTED,Proc}} ->
case member(Proc,Procs) of
true ->
disk_log:log(selectlog,Term),
scan_proc_select(NewContinuation,[Pid|Procs]);
false ->
scan_proc_select(NewContinuation,Procs)
end;
{trace,Pid,mapped,Proc} ->
case member(Proc,Procs) of
true ->
disk_log:log(selectlog,Term),
scan_proc_select(NewContinuation,[Pid|Procs]);
false ->
scan_proc_select(NewContinuation,Procs)
end;
Other when tuple(Other) ->
case member(element(2,Other),Procs) of
true ->
disk_log:log(selectlog,Term),
scan_proc_select(NewContinuation,Procs);
false ->
scan_proc_select(NewContinuation,Procs)
end
end
end.
%+type show(filename()) -> ok.
%
show(InFile) ->
{ok,_} = disk_log:open([{name,tracelog},{file,InFile}]),
scan_show(start),
disk_log:close(tracelog).
scan_show(Continuation) ->
case disk_log:chunk(tracelog,Continuation,1) of
eof ->
ok;
{NewContinuation,[Term]} ->
io:format("~p~n",[Term]),
scan_show(NewContinuation)
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%+type abstract(callback :: module(), in::filename(), [option()]) -> ok
% where option() =
% {tool, toolname(), file::atom()} |
% {connect, graph | changegraph |
% trace | changetrace | singletrace | msc}
%+type abstract([{pid()|{node(),pid()},callback :: module()}], in::filename(),
% [option()]) -> ok
% where option() =
% {tool, toolname(), file::atom()} |
% {connect, graph | changegraph |
% trace | changetrace | singletrace | msc}
abstract(C,F,O) ->
Abstractor = spawn_link(?MODULE,abstract0,[C,F,O]).
abstract0(CallBack,InFile,Options) when atom(CallBack) ->
Trace =
#trace{callback=CallBack},
abstract1(Trace,InFile,Options);
abstract0(CallBacks,InFile,Options) ->
Trace =
#trace{callbacks=dict:from_list(CallBacks)},
abstract1(Trace,InFile,Options).
abstract1(Trace,InFile,Options) ->
{ok,_} = disk_log:open([{name,tracelog},{file,InFile}]),
Connect = options(connect,trace,Options),
Collect = member(Connect,[graph,changegraph]),
digraph:add_vertex(Trace#trace.graph,0,none),
ResultTrace =
readlog(Trace#trace{connect=Connect, collect=Collect},start),
disk_log:close(tracelog),
%%%
% Here the digraph is assembled further
%%%
%io:format("sendpoints ~p~n",[dict:to_list(ResultTrace#trace.sendpoints)]),
DefaultCB = ResultTrace#trace.callback,
case keysearch(tool,1,Options) of
{value,{tool,Tool,OutFile}} ->
case catch ?MODULE:Tool(filename:absname(OutFile),ResultTrace) of
{'EXIT',Reason} ->
io:format("failed using tool ~p: ~w~n",[Tool,Reason]);
Info ->
map(fun({FN,Cont}) -> file:write_file(FN,Cont) end,Info)
end;
_ ->
io:format("no tool specified~n")
end,
terminate(DefaultCB,ResultTrace#trace.localstates),
digraph:delete(Trace#trace.graph),
io:format("finished trace abstraction~n").
readlog(Trace,Continuation) ->
case disk_log:chunk(tracelog,Continuation,1) of
eof ->
Procs =
orddict:fetch_keys(
dict:fetch(Trace#trace.states,Trace#trace.state_dict)),
remainsend(Trace,Procs);
{NewContinuation,[Term]} ->
readlog(abs_event(Trace,Term),NewContinuation)
end.
%+type abs_event(trace(),term()) -> trace().
%
abs_event(Trace,Term) ->
case Term of
{trace,Pid,process_dict,{dictionary,Dict}} ->
Trace#trace{procdict =
dict:store(Pid,dict:from_list(Dict),
Trace#trace.procdict)};
{trace,Pid,call,{erlang,put,[Field,Value]}} ->
ProcDict =
case dict:find(Pid,Trace#trace.procdict) of
error ->
dict:store(Field,Value,dict:new());
{_,Dict} ->
dict:store(Field,Value,Dict)
end,
Trace#trace{procdict =
dict:store(Pid,ProcDict,Trace#trace.procdict)};
{trace,Pid,call,{Mod,handle_call,[Msg,From,State]}} ->
statechange(Pid,Trace,handle_call,State,{Msg,From});
{trace,Pid,call,{Mod,handle_cast,[Msg,State]}} ->
statechange(Pid,Trace,handle_cast,State,Msg);
{trace,Pid,call,{Mod,handle_info,[Msg,State]}} ->
statechange(Pid, Trace,handle_info,State,Msg);
{trace,Pid,send,Msg,To} ->
CallBack =
callback(Trace#trace.callbacks,Trace#trace.callback,Pid),
LocalState =
localstate(Pid,CallBack,Trace#trace.localstates),
{AbsEvent,LState} =
CallBack:abstract_event({send,To,Msg},LocalState),
{Trace3,EventKey} =
findabsevent(Pid,Trace,AbsEvent,{send,To,Msg}),
Trace3#trace{actions =
dict:append(Pid,EventKey,Trace3#trace.actions),
localstates=
dict:store(Pid,LState,Trace3#trace.localstates)};
{trace,Pid,'DOWN',Reason} ->
AbsEvent =
{node_down,Pid,Reason},
{Trace2,EventKey} =
findabsevent(Pid,Trace,node_down,{node_down,Pid,Reason}),
Trace2#trace{actions =
dict:append(Pid,EventKey,Trace2#trace.actions)};
{trace,Pid,mapped,{nonode@REDACTED,Name}} ->
CallBack =
callback(Trace#trace.callbacks,Trace#trace.callback,Name),
io:format("CallBack ~p used for Pid ~w~n",[CallBack,Pid]),
Colour = 100*(length(Trace#trace.mapping)+1),
Trace#trace{mapping =
[{Pid,Name,Colour}|Trace#trace.mapping],
callbacks =
dict:store(Pid,CallBack,Trace#trace.callbacks)};
{trace,Pid,mapped,Proc} ->
CallBack =
callback(Trace#trace.callbacks,Trace#trace.callback,Proc),
?DEBUG("CallBack ~p used for Pid ~w~n",[CallBack,Pid]),
Colour = 100*(length(Trace#trace.mapping)+1),
Trace#trace{mapping =
[{Pid,Proc,Colour}|Trace#trace.mapping],
callbacks =
dict:store(Pid,CallBack,Trace#trace.callbacks)};
{trace,_,processes,Nr} ->
Trace;
X ->
?DEBUG("uninterpreted ~p~n",[X]),
Trace
end.
statechange(Pid,Trace,Action,State,Event) ->
CallBack =
callback(Trace#trace.callbacks,Trace#trace.callback,Pid),
LocalState =
localstate(Pid,CallBack,Trace#trace.localstates),
{AbsState,LState1} =
CallBack:abstract_state(State,LocalState),
{AbsEvent,LState2} =
CallBack:abstract_event({Action,Event},LState1),
{Trace1,EventKey} =
findabsevent(Pid,Trace,AbsEvent,{Action,Event}),
{AbsActions,Actions} =
case dict:find(Pid,Trace1#trace.actions) of
error ->
{[],[]};
{_,Values} ->
foldr(fun(V,{AAs,As}) ->
{AA,_,A} = dict:fetch(V,Trace#trace.edge_dict),
{[AA|AAs],[A|As]}
end,{[],[]},Values)
end,
{AbsEvents,LState3} =
case catch CallBack:abstract_eventseq(AbsActions,LState2) of
{'EXIT',Reason} ->
case Reason of
{undef,[{CallBack,abstract_eventseq,_}|_]} ->
{AbsActions, LState2};
_ ->
io:format("~p~n",[Reason]),
{AbsActions, LState2}
end;
Result ->
Result
end,
OldStateKey =
Trace1#trace.states,
Trace2 =
Trace1#trace{actions =
dict:store(Pid,[EventKey],Trace1#trace.actions),
localstates =
dict:store(Pid,LState3,Trace1#trace.localstates)},
Trace3 =
addstate(Pid,Trace2,AbsState,State),
{Trace4,ActionsNr} =
findabsevent(Pid,Trace3,AbsEvents, Actions),
digraph:add_edge(Trace4#trace.graph,
{OldStateKey,Trace4#trace.states,ActionsNr},
OldStateKey,Trace4#trace.states,ActionsNr),
?DEBUG("edge: ~p -> ~p nr ~p~n",[OldStateKey,Trace4#trace.states,ActionsNr]),
Trace4.
callback(CallBacks,DefaultCallBack,Pid) ->
case dict:find(Pid,CallBacks) of
error ->
DefaultCallBack;
{_,Value} ->
Value
end.
localstate(Pid,CallBack,StateDict) ->
case dict:find(Pid,StateDict) of
error ->
CallBack:init();
{_,LState} ->
LState
end.
% event is encountered and now it should be added in the way
% 'collect' prescribes
%
% update vertex_dict with {AbsState,CallBack,State}
% if new state encountered
% update states
% update state_dict
% update graph with extra node
%
% thus, guaranteeing that for every key in state_dict there is a
% node in the graph and vice versa
%
addstate(Pid,Trace,AbsState,State) ->
StateVector =
dict:fetch(Trace#trace.states,Trace#trace.state_dict),
{States,VertexKey,NewStateVector,NewStateKey} =
case Trace#trace.collect of
true ->
{Value,VK} =
case find_2_3(AbsState,Trace#trace.vertex_dict) of
error ->
{[State],dict:size(Trace#trace.vertex_dict)};
{_,Key} ->
{_,_,Ss} = dict:fetch(Key,Trace#trace.vertex_dict),
{[State|Ss],Key}
end,
NewSV =
orddict:store(Pid,VK,StateVector),
case find_2(NewSV,Trace#trace.state_dict) of
error ->
{Value,VK,NewSV,dict:size(Trace#trace.state_dict)};
{_,SKey} ->
{Value,VK,NewSV,SKey}
end;
false ->
VK = dict:size(Trace#trace.vertex_dict),
NewSV = orddict:store(Pid,VK,StateVector),
{State,VK,NewSV,dict:size(Trace#trace.state_dict)}
end,
% could add colour here (of Pid that is in action)
digraph:add_vertex(Trace#trace.graph,NewStateKey,none),
?DEBUG("added state vector ~w = ~p~n",[NewStateKey,NewStateVector]),
CB =
callback(Trace#trace.callbacks,Trace#trace.callback,Pid),
Trace#trace{vertex_dict =
dict:store(VertexKey,{AbsState,CB,States},
Trace#trace.vertex_dict),
states =
NewStateKey,
state_dict =
dict:store(NewStateKey,NewStateVector,
Trace#trace.state_dict)
}.
% event is encountered and now it should be added to edge_dict
% in the way 'collect' prescribes
%
findabsevent(Pid,Trace,AbsEvent,Event) ->
{Value,EdgeKey} =
case Trace#trace.collect of
true ->
case find_2_3(AbsEvent,Trace#trace.edge_dict) of
error ->
{[Event],dict:size(Trace#trace.edge_dict)};
{_,Key} ->
{_,_,Events} = dict:fetch(Key,Trace#trace.edge_dict),
{[Event|Events],Key}
end;
false ->
{Event,dict:size(Trace#trace.edge_dict)}
end,
CB =
callback(Trace#trace.callbacks,Trace#trace.callback,Pid),
{Trace#trace{edge_dict =
dict:store(EdgeKey,{AbsEvent,CB,Value},Trace#trace.edge_dict)},
EdgeKey}.
%
% is this abstract state/event already stored?
% note that we do not check the pid() here, thus in graph mode
% pids can be messed up
%
find_2_3(SndKey,Dict) ->
dict:fold(fun(Key1,{Key2,_,_},Result) ->
case (Key2==SndKey) of
true ->
{ok,Key1};
false ->
Result
end
end,error,Dict).
find_2(Value,Dict) ->
dict:fold(fun(Key,V,Result) ->
case V==Value of
true ->
{ok,Key};
false ->
Result
end
end,error,Dict).
% some messages are sent, but not yet received
% (those are in sendpoints, edge_dict, but not in graph)
% add them to destination pid() where
% 'extern' is used for pids that are not traced
%
% depends on value 'collect' how this is done in particular
%
remainsend(Trace,DrawnProcs) ->
NewSendPoints =
dict:fold(fun(Key,Value,NewDict) ->
case member(Key,DrawnProcs) of
true ->
dict:store(Key,
map(fun({M,N,P,A}) ->
case member(P,DrawnProcs) of
true -> {M,N,P,A};
false -> {M,N,extern,A}
end
end,Value),NewDict);
false ->
foldl(fun(V,D) ->
dict:append(extern,V,D)
end,NewDict,Value)
end
end,dict:new(),Trace#trace.sendpoints),
Trace#trace{sendpoints = NewSendPoints}.
%%%%%%%%%%%%%%%% format functions %%%%%%%%%%%%%%%%%%%%
%+type graph_to_dicts(trace()) ->
% {[{node(),{string(),term(),colour()}}],
% [{{node(),node()},{string(),term(),colour()}}]}.
%
graph_to_dicts(Trace) ->
VDict =
expand_state_dict(Trace),
EDict =
expand_event_dict(Trace),
{VDict,EDict}.
%+type noedgelabel([{node(),{string(),term(),colour()}}],
% [{{node(),node()},{string(),term(),colour()}}]) ->
% {dict(node(),{string(),term(),colour()}),
% dict(node(),[{node(),none|arrow}])}.
%
noedgelabel({VDict,EDict}) ->
{NVDict,NEDict,_} =
foldl(fun({{N1,N2},Label},{VD,ED,Max}) ->
{dict:store(Max,Label,VD),
dict:append(Max,{N2,arrow},dict:append(N1,{Max,none},ED)),
Max+1}
end,{dict:from_list(VDict),dict:new(),length(VDict)},EDict),
{NVDict,NEDict}.
expand_state_dict(Trace) ->
Colour =
case Trace#trace.connect of
_ ->
?STATE_COLOUR
end,
case member(Trace#trace.connect,[changetrace,changegraph]) of
true ->
StateKeys = lists:seq(0,dict:size(Trace#trace.state_dict)-1),
{Dict,_} =
foldl(fun(StateKey,{D,OldVector}) ->
StateVector = dict:fetch(StateKey,Trace#trace.state_dict),
ExpandVector =
foldl(fun({Pid,VK},EV) ->
case member({Pid,VK},OldVector) of
true -> % no change
EV;
false ->
{AbsState,CB,State} =
dict:fetch(VK,
Trace#trace.vertex_dict),
case Trace#trace.connect of
singletrace ->
[{typeset_state(CB,AbsState),
State}|EV];
_ ->
[{pid_to_list(Pid)++"\n"++
typeset_state(CB,AbsState),
State}|EV]
end
end
end,[],StateVector),
{[{StateKey,zvector(ExpandVector,Colour)}|D],StateVector}
end,{[],orddict:new()},StateKeys),
Dict;
false ->
dict:fold(fun(StateKey,StateVector,D) ->
ExpandVector =
map(fun({Pid,VK}) ->
{AbsState,CB,State} =
dict:fetch(VK,Trace#trace.vertex_dict),
case Trace#trace.connect of
singletrace ->
{typeset_state(CB,AbsState),State};
_ ->
{pid_to_list(Pid)++"\n"++
typeset_state(CB,AbsState),State}
end
end,StateVector),
[{StateKey,zvector(ExpandVector,Colour)}|D]
end,[],Trace#trace.state_dict)
end.
zvector(PairList,Colour) ->
{AbsStates,StateLists} = unzip(PairList),
{concat(AbsStates,"\n"),StateLists,Colour}.
expand_event_dict(Trace) ->
Colour = ?EVENT_COLOUR,
foldl(fun(E,Es) ->
{_,N1,N2,Label} = digraph:edge(Trace#trace.graph,E),
{AbsEvents,CB,Events} =
dict:fetch(Label,Trace#trace.edge_dict),
[{{N1,N2},{typeset_events(CB,AbsEvents),Events,Colour}}|Es]
end,[],digraph:edges(Trace#trace.graph)).
%%%%%%%%%%%%%%%% DOT %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
dot(FileName,Trace) ->
{VDict,EDict} =
graph_to_dicts(Trace),
Dot =
["digraph G {\n",dot_build(VDict,EDict),"}\n"],
[{FileName++".dot",Dot}].
%+type dot_build(dict(node(),{string(),term(),colour()}),
% dict({node(),node()},{string(),term(),colour()})) ->
% deeplist().
%
dot_build(VDict,EDict) ->
Vertices =
map(fun({Nr,{ShortForm,LongForm,Colour}}) ->
integer_to_list(Nr)++
" [" ++
case Colour of
none -> "";
_ -> "style=filled, color=\""++Colour++"\",\n"
end ++
" shape=box,\n label=\""++
dot_quote(ShortForm)++
"\"];\n"
end,VDict),
Edges =
map(fun({{From,To},{ShortForm,LongForm,Colour}}) ->
integer_to_list(From)++
" -> "++
integer_to_list(To)++
" [label=\""++dot_quote(ShortForm)++"\"];\n"
end,EDict),
Vertices++Edges.
dot_quote(String) ->
foldr(fun(C,S) ->
case C of
10 ->
"\\n"++S;
13 ->
"\\n"++S;
$< ->
"< "++S;
$> ->
"> "++S;
$& ->
"& "++S;
_ ->
[C|S]
end
end, "", String).
%%%%%%%%%%%%%%%% POSTSCRIPT via dot tool %%%%%%%%%%%%%%%%%%%%%%%%%%%
postscript(FileName,Trace) ->
[{DotFileName,DotData}] =
dot(FileName,Trace),
% should use a port here, but have to learn how to do that
%Dot = open_port({spawn, "dot -Tps "}, []),
%Dot ! {self(), {command, DotData}},
%receive
% {Dot, {data, Data}} ->
% [{FileName++".ps",Data}]
%end.
file:write_file(DotFileName,DotData),
PSData = os:cmd("dot -Tps "++DotFileName),
[{FileName++".ps",PSData}].
%%%%%%%%%%%%%%%% SVG output via dot tool %%%%%%%%%%%%%%%%%%%%%%%%%%%
svg(FileName,Trace) ->
[{DotFileName,DotData}] =
dot(FileName,Trace),
% should use a port here, but have to learn how to do that
% Dot = open_port({spawn, "dot -Tsvg "}, []), % call dot here with -svg
% Dot ! {self(), {command, DotData}},
% receive
% {Dot, {data, Data}} ->
% [{FileName++".svg",add_scrollbars(Data)}]
% end.
file:write_file(DotFileName,DotData),
SVGData = os:cmd("dot -Tsvg "++DotFileName),
[{FileName++".svg",add_scrollbars(SVGData)}].
add_scrollbars(SVGData) ->
ReplaceString =
case os:getenv("SVGScript") of
false ->
scrollbars();
FileName ->
"<script xlink:href=\""++FileName++"\"/>"
end,
case regexp:sub(SVGData,"<svg width=[^\\n]*","&\n"++ReplaceString) of
{ok, NewString, 1} ->
NewString;
_ ->
io:format("Incompatible version of Dot SVG output detected~n",[]),
exit(dot_version)
end.
scrollbars() ->
["<script>\n",
"<![CDATA[\n",
"var sbLight = \"rgb(200 200 200)\"\n",
"var sbDark = \"rgb(135 135 135)\"\n",
"var sbShadow = \"rgb(72 72 72)\"\n",
"\n",
"var sbYScale, sbXScale\n",
"var sbYG, sbXG, sbYBg, sbXBg\n",
"var sbYTop, sbYBottom, sbYSlider\n",
"var sbXLeft, sbXSlider, sbXRight\n",
"\n",
"var sbYDrag = false, sbXDrag\n",
"var sbLastY, sbLastX\n",
"var sbScrollY, sbScrollX\n",
"var sbCover\n",
"var sbTimeout\n",
"\n",
"function sbButtonSetSize(b, w, h)\n",
"{\n",
" b.bg.setAttribute(\"width\", w)\n",
" b.bg.setAttribute(\"height\", h)\n",
" b.bl.setAttribute(\"d\", \"M1.5 \"+(h-1.5)+ \"v-\"+(h-3)+\"h\"+(w-3))\n",
" b.bd.setAttribute(\"d\", \"M1.5 \"+(h-0.5)+\"h\"+(w-2)+\"v-\"+(h-2))\n",
" b.bs.setAttribute(\"d\", \"M0.5 \"+(h+0.5)+\"h\"+w+\"v-\"+h)\n",
"}\n",
"\n",
"\n",
"function sbButtonMake(kind)\n",
"{\n",
" var b = new Object()\n",
" var t = document.createElement(\"g\")\n",
" b.g = t\n",
" t.setAttribute(\"fill\", \"none\")\n",
" t.setAttribute(\"stroke-linecap\", \"square\")\n",
" t.setAttribute(\"shape-rendering\", \"optimizeSpeed\")\n",
" var r = document.createElement(\"rect\")\n",
" b.bg = r\n",
" r.setAttribute(\"fill\", sbLight)\n",
" t.appendChild(r)\n",
" var p = document.createElement(\"path\")\n",
" b.bl = p\n",
" p.setAttribute(\"stroke\", \"white\")\n",
" t.appendChild(p)\n",
" p = document.createElement(\"path\")\n",
" b.bd = p\n",
" p.setAttribute(\"stroke\", sbDark)\n",
" t.appendChild(p)\n",
" p = document.createElement(\"path\")\n",
" b.bs = p\n",
" p.setAttribute(\"stroke\", sbShadow)\n",
" t.appendChild(p)\n",
" if(kind)\n",
" {\n",
" p = document.createElement(\"path\")\n",
" p.setAttribute(\"fill\", \"black\")\n",
" if(kind == '^')\n",
" p.setAttribute(\"d\", \"M4.5 9.5l3-3l3 3z\")\n",
" else if(kind == 'v')\n",
" p.setAttribute(\"d\", \"M4.5 6.5l3 3l3-3z\")\n",
" else if(kind == '>')\n",
" p.setAttribute(\"d\", \"M6.5 9.5l3-3l-3-3z\")\n",
" else if(kind == '<')\n",
" p.setAttribute(\"d\", \"M9.5 9.5l-3-3l3-3z\")\n",
" t.appendChild(p)\n",
" }\n",
" sbButtonSetSize(b, 15, 15)\n",
" return b\n",
"}\n",
"\n",
"function sbNewRect(x, y, w, h, fill)\n",
"{\n",
" var r = document.createElement(\"rect\")\n",
" r.setAttribute(\"x\", x)\n",
" r.setAttribute(\"y\", y)\n",
" r.setAttribute(\"width\", h)\n",
" r.setAttribute(\"height\", w)\n",
" r.setAttribute(\"fill\", fill)\n",
" return r\n",
"}\n",
"\n",
"function sbMake()\n",
"{\n",
" sbYG = document.createElement(\"g\")\n",
" sbYG.setAttribute(\"class\", \"sbNoPrint\")\n",
" document.rootElement.appendChild(sbYG)\n",
"\n",
" var ss = document.createElement(\"style\")\n",
" ss.appendChild(document.createTextNode(\"@media print{.sbNoPrint{display:none;}}\"))\n",
" ss.setAttribute(\"type\", \"text/css\")\n",
" sbYG.appendChild(ss)\n",
"\n",
" if(document.getElementById(\"sbBgPatt\") == null)\n",
" {\n",
" var p = document.createElement(\"pattern\")\n",
" p.setAttribute(\"id\", \"sbBgPatt\")\n",
" sbYG.appendChild(p)\n",
" p.setAttribute(\"width\", \"2\")\n",
" p.setAttribute(\"height\", \"2\")\n",
" p.setAttribute( \"patternUnits\", \"userSpaceOnUse\")\n",
" p.setAttribute(\"shape-rendering\", \"optimizeSpeed\")\n",
" p.appendChild(sbNewRect(0, 0, 2, 2, \"white\"))\n",
" p.appendChild(sbNewRect(0, 0, 1, 1, sbLight))\n",
" p.appendChild(sbNewRect(1, 1, 1, 1, sbLight))\n",
" }\n",
"\n",
" sbYBg = document.createElement(\"rect\")\n",
" sbYBg.setAttribute(\"fill\",\"url(#sbBgPatt)\")\n",
" sbYBg.setAttribute(\"width\",\"16\")\n",
" sbYG.appendChild(sbYBg)\n",
"\n",
" sbYTop = sbButtonMake(\"^\")\n",
" sbYG.appendChild(sbYTop.g)\n",
" sbYSlider = sbButtonMake()\n",
" sbYG.appendChild(sbYSlider.g)\n",
" sbYBottom = sbButtonMake(\"v\")\n",
" sbYG.appendChild(sbYBottom.g)\n",
"\n",
" sbXG = document.createElement(\"g\")\n",
" sbXG.setAttribute(\"class\", \"sbNoPrint\")\n",
" document.rootElement.appendChild(sbXG)\n",
"\n",
" sbXBg = document.createElement(\"rect\")\n",
" sbXBg.setAttribute(\"fill\",\"url(#sbBgPatt)\")\n",
" sbXBg.setAttribute(\"height\",\"16\")\n",
" sbXG.appendChild(sbXBg)\n",
"\n",
" sbXLeft = sbButtonMake(\"<\")\n",
" sbXG.appendChild(sbXLeft.g)\n",
" sbXSlider = sbButtonMake()\n",
" sbXG.appendChild(sbXSlider.g)\n",
" sbXRight = sbButtonMake(\">\")\n",
" sbXG.appendChild(sbXRight.g)\n",
"}\n",
"\n",
"function toCoord(coordstr)\n",
"{\n",
" /* The following conversion works for dot generated svg files\n",
" but not for general graphs. To be fixed. */\n",
" var index = coordstr.indexOf(\"pt\")\n",
" if(index>=0)\n",
" return coordstr.substr(0,index)\n",
" else\n",
" return coordstr\n",
"}\n",
"\n",
"\n",
"function sbSync()\n",
"{\n",
" var r = document.rootElement\n",
" var s = 1/r.currentScale\n",
" var ct = r.currentTranslate\n",
" var w = getInnerWidth()\n",
" var h = getInnerHeight()\n",
"\n",
" var effH = h - 2*16 - 1\n",
" var sh = toCoord(r.getAttribute(\"height\"))\n",
" var effW = w - 2*16 - 1\n",
" var sw = toCoord(r.getAttribute(\"width\"))\n",
"\n",
" var tfY = \"matrix(\" + s + \" 0 0 \" + s + \" \" + (w-ct.x)*s + \" \" + (-ct.y)*s + \")\"\n",
" sbYG.setAttribute(\"transform\", tfY)\n",
" sbYBg.setAttribute(\"height\", h)\n",
" sbYBg.setAttribute(\"transform\", \"translate(-16 0)\")\n",
" sbYTop.g.setAttribute(\"transform\", \"translate(-16 0)\")\n",
" sbYBottom.g.setAttribute(\"transform\", \"translate(-16 \" + (h-16) + \")\")\n",
"\n",
" var tfX = \"matrix(\" + s + \" 0 0 \" + s + \" \" + (-ct.x)*s + \" \" + (h-ct.y)*s + \")\"\n",
" sbXG.setAttribute(\"transform\", tfX)\n",
" sbXBg.setAttribute(\"width\", w-16)\n",
" sbXBg.setAttribute(\"transform\", \"translate(0 -16)\")\n",
" sbXLeft.g.setAttribute(\"transform\", \"translate(0 -16)\")\n",
" sbXRight.g.setAttribute(\"transform\", \"translate(\"+ (w-32) + \" -16)\")\n",
"\n",
" sbYScale = effH*s/sh\n",
" var cy = -Math.round(ct.y*sbYScale)\n",
" var ch = h*sbYScale\n",
"\n",
" if(ch > effH)\n",
" ch = effH\n",
" if(cy + ch > effH)\n",
" cy = effH - ch\n",
" if(cy < 0)\n",
" cy = 0\n",
" if(ch == effH)\n",
" sbYG.setAttribute(\"display\", \"none\")\n",
" else if(sbYG.getAttribute(\"display\") != \"\")\n",
" sbYG.removeAttribute(\"display\")\n",
"\n",
" sbYSlider.g.setAttribute(\"transform\", \"translate(-16 \"+(16+cy)+\")\")\n",
" sbButtonSetSize(sbYSlider, 15, ch)\n",
"\n",
" sbXScale = effW*s/sw\n",
" var cx = -Math.round(ct.x*sbXScale)\n",
" var cw = w*sbXScale\n",
"\n",
" if(cw > effW)\n",
" cw = effW\n",
" if(cx + cw > effW)\n",
" cx = effW - cw\n",
" if(cx < 0)\n",
" cx = 0\n",
" if(cw == effW)\n",
" sbXG.setAttribute(\"display\", \"none\")\n",
" else if(sbXG.getAttribute(\"display\") != \"\")\n",
" sbXG.removeAttribute(\"display\")\n",
"\n",
" sbXSlider.g.setAttribute(\"transform\", \"translate(\"+(16+cx)+\" -16)\")\n",
" sbButtonSetSize(sbXSlider, cw, 15)\n",
"}\n",
"\n",
"function sbSetScrollY(y)\n",
"{\n",
" var h = getInnerHeight()\n",
" var my = (h-2*16-1)/sbYScale - h\n",
" if(y < 0)\n",
" y = 0\n",
" else if(y > my)\n",
" y = my\n",
" sbScrollY = y\n",
" document.rootElement.currentTranslate.y = -sbScrollY\n",
"}\n",
"\n",
"function sbSetScrollX(x)\n",
"{\n",
" var h = getInnerWidth()\n",
" var mx = (h-2*16-1)/sbXScale - h\n",
" if(x < 0)\n",
" x = 0\n",
" else if(x > mx)\n",
" x = mx\n",
" sbScrollX = x\n",
" document.rootElement.currentTranslate.x = -sbScrollX\n",
"}\n",
"\n",
"function sbStartYDrag(evt)\n",
"{\n",
" sbYDrag = true\n",
" sbLastY = evt.clientY\n",
" sbScrollY = -document.rootElement.currentTranslate.y\n",
" sbCover = sbNewRect(-10000, -10000, 30000, 30000, \"white\")\n",
" sbCover.setAttribute(\"fill\", \"none\")\n",
" sbCover.setAttribute(\"pointer-events\", \"all\")\n",
" sbYG.appendChild(sbCover)\n",
"}\n",
"\n",
"function sbStartXDrag(evt)\n",
"{\n",
" sbXDrag = true\n",
" sbLastX = evt.clientX\n",
" sbScrollX = -document.rootElement.currentTranslate.x\n",
" sbCover = sbNewRect(-10000, -10000, 30000, 30000, \"white\")\n",
" sbCover.setAttribute(\"fill\", \"none\")\n",
" sbCover.setAttribute(\"pointer-events\", \"all\")\n",
" sbXG.appendChild(sbCover)\n",
"}\n",
"\n",
"function sbEndDrag(evt)\n",
"{\n",
" if (sbYDrag)\n",
" sbYG.removeChild(sbCover) \n",
" if (sbXDrag)\n",
" sbXG.removeChild(sbCover) \n",
" sbYDrag = false\n",
" sbXDrag = false\n",
" sbCover = null\n",
"}\n",
"\n",
"function sbDoDrag(evt)\n",
"{\n",
" if(sbYDrag) {\n",
" var y = evt.clientY\n",
" var ry = Math.round((y - sbLastY)/sbYScale)\n",
" if(ry == 0)\n",
" return\n",
" sbLastY += ry*sbYScale\n",
" sbSetScrollY(sbScrollY + ry)\n",
" }\n",
" else if(sbXDrag) {\n",
" var x = evt.clientX\n",
" var rx = Math.round((x - sbLastX)/sbXScale)\n",
" if(rx == 0)\n",
" return\n",
"\n",
" sbLastX += rx*sbXScale\n",
" sbSetScrollX(sbScrollX + rx)\n",
" }\n",
" else\n",
" return\n",
"}\n",
"\n",
"function sbUnitDown(evt)\n",
"{\n",
" sbSetScrollY(-document.rootElement.currentTranslate.y+Math.round((evt?1:5)/sbYScale))\n",
" if(sbTimeout)\n",
" clearTimeout(sbTimeout)\n",
" sbTimeout = setTimeout(\"sbUnitDown()\", (evt?500:50))\n",
"}\n",
"\n",
"function sbUnitUp(evt)\n",
"{\n",
" sbSetScrollY(-document.rootElement.currentTranslate.y-Math.round((evt?1:5)/sbYScale))\n",
" if(sbTimeout)\n",
" clearTimeout(sbTimeout)\n",
" sbTimeout = setTimeout(\"sbUnitUp()\", (evt?500:50))\n",
"}\n",
"\n",
"function sbUnitLeft(evt)\n",
"{\n",
" sbSetScrollX(-document.rootElement.currentTranslate.x-Math.round((evt?1:5)/sbXScale))\n",
" if(sbTimeout)\n",
" clearTimeout(sbTimeout)\n",
" sbTimeout = setTimeout(\"sbUnitLeft()\", (evt?500:50))\n",
"}\n",
"\n",
"function sbUnitRight(evt)\n",
"{\n",
" sbSetScrollX(-document.rootElement.currentTranslate.x+Math.round((evt?1:5)/sbXScale))\n",
" if(sbTimeout)\n",
" clearTimeout(sbTimeout)\n",
" sbTimeout = setTimeout(\"sbUnitRight()\", (evt?500:50))\n",
"}\n",
"\n",
"function sbScrollYPage(evt)\n",
"{\n",
" var y = evt.clientY\n",
" var ty = document.rootElement.currentTranslate.y\n",
" var sy = 16-Math.round(ty*sbYScale)\n",
" var dy = getInnerHeight()\n",
" if(y <= sy)\n",
" dy = -dy\n",
" sbSetScrollY(-ty+dy)\n",
"}\n",
"\n",
"function sbScrollXPage(evt)\n",
"{\n",
" var x = evt.clientX\n",
" var tx = document.rootElement.currentTranslate.x\n",
" var sx = 16-Math.round(tx*sbXScale)\n",
" var dx = getInnerHeight()\n",
" if(x <= sx)\n",
" dx = -dx\n",
" sbSetScrollX(-tx+dx)\n",
"}\n",
"\n",
"function sbStopScroll(evt)\n",
"{\n",
" sbEndDrag(evt) \n",
" if(sbTimeout)\n",
" clearTimeout(sbTimeout)\n",
" sbTimeout = null\n",
"}\n",
"\n",
"function sbInit()\n",
"{\n",
" sbMake()\n",
" sbSync()\n",
" var re = document.rootElement\n",
" re.addEventListener(\"SVGScroll\", sbSync, false)\n",
" re.addEventListener(\"SVGResize\", sbSync, false)\n",
" re.addEventListener(\"SVGZoom\", sbSync, false)\n",
"\n",
" sbYTop.g.addEventListener(\"mousedown\", sbUnitUp, false)\n",
" sbYBottom.g.addEventListener(\"mousedown\", sbUnitDown, false)\n",
" sbYSlider.g.addEventListener(\"mousedown\", sbStartYDrag, false)\n",
"\n",
" re.addEventListener(\"mouseup\", sbStopScroll, false)\n",
"\n",
" sbYBg.addEventListener(\"click\", sbScrollYPage, false)\n",
" sbXBg.addEventListener(\"click\", sbScrollXPage, false)\n",
"\n",
" re.addEventListener(\"mousemove\", sbDoDrag, false)\n",
" re.addEventListener(\"mouseup\", sbEndDrag, false)\n",
"\n",
" sbXLeft.g.addEventListener(\"mousedown\", sbUnitLeft, false)\n",
" sbXRight.g.addEventListener(\"mousedown\", sbUnitRight, false)\n",
" sbXSlider.g.addEventListener(\"mousedown\", sbStartXDrag, false)\n",
"}\n",
"\n",
"sbInit()]]>\n",
"</script>\n"].
%%%%%%%%%%%%%%%% DAVINCI %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
davinci(FileName,Trace) ->
{VDict,EDict} =
noedgelabel(graph_to_dicts(Trace)),
DaVinci =
["[",davinci_build(VDict,EDict),"]"],
[{FileName++".daVinci",DaVinci}].
%+type davinci_build(dict(node(),{string(),term(),colour()}),
% dict(node(),[{node(),none|arrow}])) ->
% deeplist().
%
davinci_build(VDict,EDict) ->
sep(fun(Nr) ->
{ShortForm,LongForm,Colour} = dict:fetch(Nr,VDict),
["l(\"",
integer_to_list(Nr),
"\",n(\"\",[a(\"OBJECT\",\"",
davinci_quote(ShortForm),
"\")",
case Colour of
none ->
"";
_ ->
[",a(\"COLOR\",\"",Colour,"\")"]
end,
"],[",
sep(fun(To) ->
davinci_edge(Nr,To)
end,case dict:find(Nr,EDict) of
{ok,Tos} -> Tos;
_ -> []
end),
"]))"]
end,lists:seq(0,dict:size(VDict)-1)).
davinci_quote(String) ->
concat(string:tokens(String,[10,13]),"\\n").
davinci_edge(From,{To,none}) ->
["e(\"\",[a(\"_DIR\",\"none\")],r(\"",
integer_to_list(To),
"\"))"];
davinci_edge(From,{To,arrow}) ->
["e(\"\",[],r(\"",
integer_to_list(To),
"\"))"].
concat([],Sep) ->
"";
concat([Token],Sep) ->
Token;
concat([Token|Tokens],Sep) ->
Token++Sep++concat(Tokens,Sep).
sep(F,[]) ->
[];
sep(F,[E]) ->
[F(E)];
sep(F,[E|Es]) ->
[F(E),",\n"|sep(F,Es)].
%%%%%%%%%%%%%%%% NILSSON %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
dictionaries(FileName,Trace) ->
{VDict,EDict} = graph_to_dicts(Trace),
[{FileName++".dicts",term_to_binary({trace,VDict,EDict})}].
%%%%%%%%%%%%%%%%% CADP %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
cadp(FileName,Trace) ->
{VDict,EDict} = graph_to_dicts(Trace),
Length = length(EDict),
Max = length(VDict),
Labels =
[io_lib:format("des (0,~w,~w)~n",[Length,Max])|
map(fun({{N1,N2},{ShortForm,LongForm,Colour}}) ->
["(",integer_to_list(N1),
",\""++ShortForm++"\",",integer_to_list(N2),")\n"]
end,lists:sort(EDict))],
[{FileName++".aut",Labels},{FileName++".map",term_to_binary(VDict)}].
%%%%%%%%%%%%%%%% Print States %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
print_states(FileName,Trace) ->
Dict = expand_state_dict(Trace),
[{FileName++".ascii",map(fun({N,{ShortForm,LongForm,Colour}}) ->
io_lib:format("~p~n",[ShortForm])
end, Dict)}].
%%%%%%%%%%%%%%%% local functions %%%%%%%%%%%%%%%%%%%%%
terminate(CallBack,States) ->
dict:fold(fun(Key,Value,Acc) ->
case catch CallBack:terminate(Value) of
{'EXIT',{undef,[{CallBack,terminate,_}|_]}} ->
Acc;
{'EXIT',Reason} ->
io:format("error ~p:terminate/1 ~p~n",[CallBack,Reason]),
error;
_ ->
Acc
end
end,ok,States).
%+type appendnode({Key,Value},[{Key,[Value]}]) -> [{Key,[Value]}].
%
appendnode({N,P},[]) ->
[{N,[P]}];
appendnode({N,P},[{N,Ps}|Rest]) ->
[{N,[P|Ps]}|Rest];
appendnode({N,P},[{M,Ps}|Rest]) ->
[{M,Ps}|appendnode({N,P},Rest)].
%
%
% in R8 and later, we can use behaviour_info in this function, i.e.
% trace all functions in behaviour_info(callbacks)
%
tracepattern(gen_server,Module) ->
erlang:trace_pattern({Module,handle_call,'_'},true,[global]),
erlang:trace_pattern({Module,handle_cast,'_'},true,[global]),
erlang:trace_pattern({Module,handle_info,'_'},true,[global]),
erlang:trace_pattern({Module,terminate,'_'},true,[global]),
erlang:trace_pattern({Module,code_change,'_'},true,[global]),
behaviour(gen_server,Module);
tracepattern(gen_fsm,Module) ->
erlang:trace_pattern({Module,'_','_'},true,[global]),
behaviour(gen_fsm,Module).
%+type behaviour(behaviour :: atom()) -> set({fun::atom(),arity::integer()}).
%
behaviour(gen_server,Module) ->
sets:from_list([{Module,handle_call,3},
{Module,handle_cast,2},
{Module,handle_info,2},
{Module,terminate,2},
{Module,code_change,3}]);
behaviour(gen_fsm,Module) ->
sets:from_list([{Module,handle_event,3},
{Module,handle_sync_event,4},
{Module,handle_info,3},
{Module,terminate,3},
{Module,code_change,4}]).
options(KeyWord,Default,List) ->
case keysearch(KeyWord,1,List) of
{value,{_,V}} ->
V;
_ ->
Default
end.
typeset_state(CallBack,AbsState) ->
case catch CallBack:typeset_state(AbsState) of
{'EXIT',Reason} ->
case Reason of
{undef,[{CallBack,typeset_state,_}|_]} ->
lists:flatten(io_lib:format("~w",[AbsState]));
_ ->
io:format("~p~n",[Reason]),
lists:flatten(io_lib:format("~w",[AbsState]))
end;
Result ->
lists:flatten(Result)
end.
typeset_events(CallBack,AbsEvents) ->
TypesetEvents =
map(fun(A) ->
case catch CallBack:typeset_event(A) of
{'EXIT',Reason} ->
case Reason of
{undef,[{CallBack,typeset_event,_}|_]} ->
ok;
_ ->
io:format("typset error: ~p~n",[Reason])
end,
lists:flatten(io_lib:format("~w",[A]));
Result ->
lists:flatten(Result)
end
end,AbsEvents),
case catch CallBack:typeset_eventseq(TypesetEvents) of
{'EXIT',Reason} ->
case Reason of
{undef,[{CallBack,typeset_eventseq,_}|_]} ->
ok;
_ ->
io:format("typset error: ~p~n",[Reason])
end,
lists:flatten(["[",concat(TypesetEvents,",\n"),"]"]);
Result ->
lists:flatten(Result)
end.
unzip([]) ->
{[],[]};
unzip([{X,Y}|Rest]) ->
{Xs,Ys} = unzip(Rest),
{[X|Xs],[Y|Ys]}.
-------------- next part --------------
%
% Thomas Arts
% February 2002
%
% abstraction for
% code_server process
%
-module(abs_code_server).
-behaviour(gen_trace).
-export([init/0,
abstract_state/2, abstract_event/2, abstract_eventseq/2]).
% store is a reflection of the state, i.e., the code path
%
%+type init() -> store().
%
init() ->
[].
%+type abstract_state(term(),store()) -> {term(),store()}.
%
abstract_state(Term,Store) ->
CodePath = element(2,Term),
case Store of
[] ->
{initial_code_path,CodePath};
_ ->
case CodePath -- Store of
[] ->
{code_path,CodePath};
Added ->
{extended_code_path,CodePath}
end
end.
%+type abstract_event(term(),store()) -> {term(),store()}.
%
abstract_event({handle_call,{{load_file,F},Client}},Store) ->
{try_to_load_file,Store};
abstract_event({handle_call,{{load_abs,F},Client}},Store) ->
{try_to_load_file_without_search,Store};
abstract_event({handle_call,{{purge,F},Client}},Store) ->
{try_to_purge_file,Store};
abstract_event({send,Pid,{'$gen_call',_,Cmd}},Store) ->
{{gen_server_call,Cmd},Store};
abstract_event({send,Pid,{_,{get_file,FileName}}},Store) ->
case filename:basename(FileName) of
FileName ->
{get_file_from_currect_dir,Store};
_ ->
{get_file_from_path_dir,Store}
end;
abstract_event({send,Pid,{_,{error,_,_}}},Store) ->
{{send,error},Store};
abstract_event({send,Pid,{Ref,Msg}},Store) ->
{{send,Msg},Store};
abstract_event(Event,Store) ->
{Event,Store}.
%+type abstract_eventseq([term()],store()) -> {[term()],store()}.
%
abstract_eventseq(Events,Store) ->
{Events,Store}.
-------------- next part --------------
%
% Thomas Arts
% November 2001
%
% no abstraction
% for reading the raw trace
%
-module(abs0).
-behaviour(gen_trace).
-export([init/0,
abstract_state/2, abstract_event/2, abstract_eventseq/2,
terminate/1]).
%+type init() -> store().
%
init() ->
0.
%+type abstract_state(term(),store()) -> {term(),store()}.
%
abstract_state(Term,Store) ->
{Term,Store+1}.
%+type abstract_event(term(),store()) -> {term(),store()}.
%
abstract_event(Event,Store) ->
{Event,Store}.
%+type abstract_eventseq([term()],store()) -> {[term()],store()}.
%
abstract_eventseq(Events,Store) ->
{Events,Store}.
%+type terminate(store()) -> ok.
%
terminate(Store) ->
io:format("trace of ~p states~n",[Store]).
-------------- next part --------------
A non-text attachment was scrubbed...
Name: release.ps
Type: application/postscript
Size: 97037 bytes
Desc: not available
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20020211/18483798/attachment.ps>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20020211/18483798/attachment.html>
More information about the erlang-questions
mailing list