Trace abstraction as behaviour

Thomas Arts <>
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 <>
%%% Purpose : behaviour for trace analyses of a gen_server and gen_fsm
%%% Created : 29 Nov 2001 by Thomas Arts <>
%%% Modified: 01 Feb 2002 by Thomas Arts
%%% Last Mod: 08 Feb 2002 by Thomas Arts
%%%----------------------------------------------------------------------

-module(gen_trace).
-author('').

-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,{,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,{,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,{,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