aboutsummaryrefslogtreecommitdiffstats
path: root/lib/kernel/test/global_SUITE_data
diff options
context:
space:
mode:
Diffstat (limited to 'lib/kernel/test/global_SUITE_data')
-rw-r--r--lib/kernel/test/global_SUITE_data/global_trace.erl1023
1 files changed, 1023 insertions, 0 deletions
diff --git a/lib/kernel/test/global_SUITE_data/global_trace.erl b/lib/kernel/test/global_SUITE_data/global_trace.erl
new file mode 100644
index 0000000000..4f253baac4
--- /dev/null
+++ b/lib/kernel/test/global_SUITE_data/global_trace.erl
@@ -0,0 +1,1023 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(global_trace).
+
+%%%
+%%% Inspection of High Level Trace created by global.erl.
+%%%
+
+%%% A few handy functions when running the test_server
+%%%
+
+d() ->
+ lists:foreach(fun(F) -> dd(F, []) end, last()).
+
+d(Testcase) ->
+ d(Testcase, []).
+
+%% Skip "global_" from T.
+d(Testcase, Options) ->
+ [Filename] = tmp_files(Testcase),
+ dd(Filename, Options).
+
+dd(Filename, Options) ->
+ io:format("\n======= ~s \n", [Filename]),
+ t(Filename, Options).
+
+last() ->
+ tmp_files("*").
+
+%% global_groups_change: one node is restarted
+%% global_mass_death: nodes are stopped
+%% global_lock_die: two spurious (trying to remove locks taken by other pid)
+%% global_otp_5640: 4 spurious (names registered again &c)
+tmp_files(A) when is_atom(A) ->
+ tmp_files(atom_to_list(A));
+tmp_files(T) when is_list(T) ->
+ Logs = logdir(),
+ Dir = lists:last(filelib:wildcard(filename:join(Logs, "*"))),
+ filelib:wildcard(filename:join([Dir, log_private, "global_" ++ T])).
+
+%logdir() ->
+% "/net/yoshi/ldisk/daily_build/otp_norel_linux_r11b.2007-02-18_19/"
+% "test/test_server/global_SUITE.logs";
+%logdir() ->
+% "/ldisk/daily_build/otp_norel_linux_suse_r11b.2007-02-07_19/test/"
+% "test_server/global_SUITE.logs";
+logdir() ->
+ "/tmp/tests/test_server/global_SUITE.logs".
+
+
+
+%%% The contents of this file is by no means fixed; the printouts are
+%%% updated depending on the problems at hand. Not everything has been
+%%% designed very carefully :)
+%%%
+%%% For one thing, the trace from all nodes are written onto the file
+%%% as one single term. One term per node would be better. &c.
+
+-compile(export_all).
+
+-record(state, {connect_all, known = [], synced = [],
+ resolvers = [], syncers = [], node_name = node(),
+ the_locker, the_deleter, the_registrar, trace = [],
+ global_lock_down
+ }).
+
+%% Compatible with different versions.
+state(#state{}=S) ->
+ S;
+state({state, ConnectAll, Known, Synced, LockersResolvers, Syncers,
+ NodeName, TheLocker, TheDeleter}) ->
+ %% r10b: Lockers, r10b_patched, r11b: Resolvers
+ #state{connect_all = ConnectAll, known = Known, synced = Synced,
+ resolvers = LockersResolvers, syncers = Syncers,
+ node_name = NodeName, the_locker = TheLocker,
+ the_deleter = TheDeleter, the_registrar = undefined, trace = []};
+state({state, ConnectAll, Known, Synced, Resolvers, Syncers,
+ NodeName, TheLocker, TheDeleter, Trace}) ->
+ %% r11b, some time before r11b-3
+ #state{connect_all = ConnectAll, known = Known, synced = Synced,
+ resolvers = Resolvers, syncers = Syncers,
+ node_name = NodeName, the_locker = TheLocker,
+ the_deleter = TheDeleter, the_registrar = undefined,
+ trace = Trace};
+state({state, ConnectAll, Known, Synced, Resolvers, Syncers,
+ NodeName, TheLocker, TheDeleter, TheRegistrar, Trace}) ->
+ %% r11b, some time after r11b-3
+ #state{connect_all = ConnectAll, known = Known, synced = Synced,
+ resolvers = Resolvers, syncers = Syncers,
+ node_name = NodeName, the_locker = TheLocker,
+ the_deleter = TheDeleter, the_registrar = TheRegistrar,
+ trace = Trace, global_lock_down = false};
+state(Else) ->
+ Else.
+
+%%% Trace tuples look like {Node, Now, Message, Nodes, Extra}.
+%%% Nodes is the list as returned by nodes().
+%%% Extra is [] most of the time.
+%%%
+%%% init
+%%% {nodedown,DownNode}
+%%% {extra_nodedown,DownNode}
+%%% {nodeup, UpNode}
+%%% {added,AddedNodes}, Extra = [{new_nodes, NewNodes},
+%%% {abcast, Known},
+%%% {ops,Ops}]
+%%% NewKnown = Known ++ AddedNodes
+%%% AddedNodes = NewNodes -- Known
+%%% NewNodes �r h�r den man f�rhandlat med plus de noder den k�nner till.
+%%% {added, AddedNodes}, Extra = [{ops,Ops}]
+%%% NewKnown = Known ++ AddedNodes
+%%% Den (passiva) noden f�r Nodes som �r NewNodes
+%%% hos den f�rhandlande. Sedan: AddedNodes = (Nodes -- Known) -- [node()].
+%%% Det �r som hos f�rhandlaren.
+%%% {nodes_changed, {New,Old}}
+%%% Every now and then the list [node() | nodes()] is checked for updates.
+%%% New are the nodes that global does not know of (yet).
+%%% {new_node_name, NewNode}
+%%% Ignored. Someone changes the nodename dynamically.
+%%% {ins_name, Node}, Extra = [Name, Pid]
+%%% Node = node(Pid)
+%%% {ins_name_ext, Node}, Extra = [Name, Pid]
+%%% Node = node(Pid)
+%%% {del_name, Node}, Extra = [Name, Pid]
+%%% Node = node(Pid)
+%%% {ins_lock, Node}, Extra = [Id, Pid]
+%%% Node = node(Pid)
+%%% {rem_lock, Node}, Extra = [Id, Pid]
+%%% Node = node(Pid)
+%%% {locker_succeeded, node()}, Extra = {First, Known}
+%%% {locker_failed, node()}, Extra = {Tried, SoFar}
+%%% The nodes in SoFar have been locked, could not lock Tried.
+%%%
+%%% Also trace of the creation and deletion of resolvers
+%%% (this kind of resolvers are created when nodeup arrives from
+%%% unknown nodes (there are also name resolvers...)).
+%%% {new_resolver, Node}, Extra = [Tag, ResolverPid]
+%%% {kill_resolver, Node}, Extra = [Tag, ResolverPid]
+%%% {exit_resolver, Node}, Extra = [Tag]
+
+-record(node, {
+ node,
+ known = [], % #state.known (derived)
+ nodes = [], % nodes()
+ locks = [], % [{Id, [Pid, node(Pid)]}] (derived)
+ names = [], % [{Name, Pid, node(Pid)}] (derived)
+ resolvers = [], % [{Node, Tag, ResolverPid}]
+ n_locks = {0, % OK
+ 0, % Tried to lock the boss
+ 0, % Tried to lock other boss
+ 0}, % Tried to lock known
+ rejected = 0 % Lock OK, but later rejected
+ }).
+
+-record(w, {nodes = [], % [#node{}]
+ n = []}).
+
+t(File) ->
+ t(File, []).
+
+%%% What to search for in the output of t/2?
+%%% - 'NEGOTIATIONS': this is a list of the name negotiations
+%%% (the big picture);
+%%% - '###' signals a possibly strange event;
+%%% - 'spurious' is used for "tagging" such events;
+%%% - 'resol ' could mean that some resolver process has not been removed;
+%%% ...
+
+%% Options:
+%% {show_state, From, To}
+%% From = To = integer() | {integer(), integer()}
+%% Examples: {7, 8} (show states between seconds 7.0 and 8.0);
+%% {{1,431234},{2,432}} (between 1.431234 and 2.000432)
+%% The state of a node includes locks, names, nodes, known, ...
+%% Default is {{0,0}, {0,0}}, that is, do not show state.
+%% show_state
+%% same as {show_state, 0, 1 bsl 28}, that is, show every state
+%% {show_trace, bool()
+%% Show the complete trace as one list and per node pair.
+%% Default is true.
+t(File, Options) ->
+ {StateFun, ShowTrace} =
+ case options(Options, [show_state, show_trace]) of
+ [{From,To}, ST] ->
+ {fun(T, S) ->
+ Time = element(2, T),
+ if
+ Time >= From, Time =< To ->
+ io:format("===> ~p~n", [T]),
+ display_nodes("After", Time, S#w.nodes, T);
+ true ->
+ ok
+ end
+ end, ST};
+ _ ->
+ erlang:error(badarg, [File, Options])
+ end,
+ D1 = try
+ %% All nodes' trace is put on the file as one binary.
+ %% This could (and should?) be improved upon.
+ {ok, Bin} = file:read_file(File),
+ io:format("Size of trace file is ~p bytes~n", [size(Bin)]),
+ binary_to_term(Bin)
+ catch _:_ ->
+ {ok, [D0]} = file:consult(File),
+ D0
+ end,
+ {D2, End} = case D1 of
+ {high_level_trace, ET, D3} ->
+ {D3, ET};
+ _ ->
+ {D1, now()}
+ end,
+ D = adjust_nodes(D2),
+ {NodeNodeTrace, _NodeTrace, Trace, Base} = get_trace(D, End),
+ messages(D, Base, End),
+
+ %io:format("NET~n ~p~n", [net_kernel_nodes(NodeTrace)]),
+
+ io:format("NEGOTIATIONS:~n ~p~n", [negotiations(Trace)]),
+
+ io:format("*** Complete trace ***~n"),
+ if
+ ShowTrace ->
+ show_trace(Trace),
+ io:format("--- End of complete trace ---~n"),
+ lists:map(fun({{Node,ActionNode},Ts}) ->
+ io:format("*** Trace for ~p on node ~p ***~n",
+ [ActionNode, Node]),
+ show_trace(lists:keysort(2, Ts)),
+ io:format("--- End of trace for ~p on node ~p ---~n",
+ [ActionNode, Node])
+ end, NodeNodeTrace);
+ true -> ok
+ end,
+ io:format("*** Evaluation ***~n"),
+ {Fini, Spurious} = eval(Trace, StateFun),
+ io:format("*** End of evaluation ***~n"),
+ show_spurious(NodeNodeTrace, Spurious),
+ display_nodes("FINI", '', Fini),
+ ok.
+
+% show_trace(Trace) ->
+% lists:foreach(fun({Node, {S,Mu}, Message, Nodes, Extra}) ->
+% io:format("~2w.~6..0w ~w~n", [S, Mu, Node]),
+% io:format(" ~p~n", [Message]),
+% io:format(" Nodes: ~p~n", [Nodes]),
+% case Extra of
+% [] -> ok;
+% _ -> io:format(" Extra: ~p~n", [Extra])
+% end
+% end, Trace);
+show_trace(Trace) ->
+ lists:map(fun(T) -> io:format("~p~n", [T]) end, Trace).
+
+get_trace(D, EndTime0) ->
+ NodeTrace0 = [{Node,lists:keysort(2, (state(State))#state.trace)} ||
+ {Node,{info,State}} <- D,
+ case state(State) of
+ #state{trace = no_trace} ->
+ io:format("No trace for ~p~n", [Node]),
+ false;
+ #state{} ->
+ true;
+ Else ->
+ io:format("Bad state for ~p: ~p~n",
+ [Node, Else]),
+ false
+ end],
+ Trace0 = lists:keysort(2, lists:append([T || {_Node, T} <- NodeTrace0])),
+ Trace1 = sort_nodes(Trace0),
+ {Base, Trace2} = adjust_times(Trace1),
+ EndTime = adjust_time(EndTime0, Base),
+ io:format("The trace was generated at ~p~n", [EndTime]),
+ Trace = [T || T <- Trace2, element(2, T) < EndTime],
+ NodeTrace = [{Node, adjust_times(Ts, Base)} ||
+ {Node, Ts} <- NodeTrace0],
+ NodeNodeTrace =
+ [{{Node,ActionNode}, T} || {Node, Ts} <- NodeTrace,
+ T <- Ts,
+ ActionNode <- action_nodes(T)],
+ {family(NodeNodeTrace), NodeTrace, Trace, Base}.
+
+adjust_nodes([E | Es]) ->
+ [adjust_nodes(E) | adjust_nodes(Es)];
+adjust_nodes(T) when is_tuple(T) ->
+ list_to_tuple(adjust_nodes(tuple_to_list(T)));
+adjust_nodes(A) when is_atom(A) ->
+ adjust_node(A);
+adjust_nodes(E) ->
+ E.
+
+sort_nodes(Ts) ->
+ [setelement(4, T, lists:sort(element(4, T))) || T <- Ts].
+
+adjust_times([]) ->
+ {0, []};
+adjust_times([T1 | _]=Ts) ->
+ Base = element(2, T1),
+ {Base, adjust_times(Ts, Base)}.
+
+adjust_times(Ts, Base) ->
+ [setelement(2, adj_tag(T, Base), adjust_time(element(2, T), Base)) ||
+ T <- Ts].
+
+adj_tag({Node, Time, {M, Node2}, Nodes, Extra}=T, Base) ->
+ if
+ M =:= new_resolver;
+ M =:= kill_resolver;
+ M =:= exit_resolver ->
+ {Node, Time, {M, Node2}, Nodes,
+ [adjust_time(hd(Extra), Base) | tl(Extra)]};
+ true ->
+ T
+ end.
+
+adjust_time(Time, Base) ->
+ musec2sec(timer:now_diff(Time, Base)).
+
+action_nodes({_Node, _Time, {_, Nodes}, _, _}) when is_list(Nodes) ->
+ Nodes;
+action_nodes({_Node, _Time, {_, Node}, _, _}) ->
+ [Node].
+
+%% Some of the names in global_SUITE.erl are recognized.
+adjust_node(Node) ->
+ case atom_to_list(Node) of
+ "cp" ++ L ->
+ list_to_atom([$c, $p | lists:takewhile(fun is_digit/1, L)]);
+ "test_server" ++ _ ->
+ test_server;
+ "a_2" ++ _ ->
+ a_2;
+ "n_1" ++ _ ->
+ n_1;
+ "n_2" ++ _ ->
+ n_2;
+ "z_2" ++ _ ->
+ z_2;
+ "z_" ++ _ ->
+ z;
+ "b_" ++ _ ->
+ b;
+ "c_external_nodes" ++ _ ->
+ c_external_nodes;
+ _ ->
+ Node
+ end.
+
+is_digit(C) ->
+ (C >= $0) and (C =< $9).
+
+eval(Trace, Fun) ->
+ eval(Trace, {0, 0}, #w{}, Fun).
+
+eval([T | Ts], Time0, S0, Fun) ->
+ Time1 = element(2, T),
+ case is_fresh(S0#w.nodes) of
+ true ->
+ io:format("~p ***************** FRESH *****************~n",
+ [Time1]);
+ false ->
+ ok
+ end,
+ case time_diff(Time1, Time0) > 0 of
+ true ->
+ display_nodes("PAUS", Time1, S0#w.nodes, T);
+ false ->
+ ok
+ end,
+ S = eval_trace(T, S0),
+ Fun(T, S),
+ eval(Ts, Time1, S, Fun);
+eval([], _, S, _Fun) ->
+ {S#w.nodes, lists:usort(S#w.n)}.
+
+%% Old.
+eval_trace({Node, Time, {added,Added}, _Nodes, [_NewNodes,_Abc]}, S0) ->
+ added(Node, Added, Time, S0);
+eval_trace({Node, Time, {added,Added}, _Nodes, []}, S0) ->
+ added(Node, Added, Time, S0);
+
+
+eval_trace({Node, Time, {init, Node}, Nodes, []}, S0) ->
+ init(Node, Nodes, Time, S0);
+eval_trace({Node, Time, {nodedown, DownNode}, Nodes, []}, S0) ->
+ node_down(Node, DownNode, Nodes, Time, S0);
+eval_trace({Node, Time, {extra_nodedown, DownNode}, Nodes, []}, S0) ->
+ node_down(Node, DownNode, Nodes, Time, S0);
+eval_trace({Node, Time, {nodeup, UpNode}, Nodes, []}, S0) ->
+ node_up(Node, UpNode, Nodes, Time, S0);
+eval_trace({Node, Time, {added,Added}, _Nodes, [_NewNodes,_Abc,_Ops]}, S0) ->
+ added(Node, Added, Time, S0);
+eval_trace({Node, Time, {added,Added}, _Nodes, [_Ops]}, S0) ->
+ added(Node, Added, Time, S0);
+eval_trace({Node, Time, {nodes_changed, {New,Old}}, _Nodes, []}, S0) ->
+ nodes_changed(Node, New, Old, Time, S0);
+eval_trace({Node, Time, {ins_name, PNode}, _Nodes, [Name, Pid]}, S0) ->
+ insert_name(Node, PNode, Time, Name, Pid, S0);
+eval_trace({Node, Time, {del_name, PNode}, _Nodes, [Name, Pid]}, S0) ->
+ delete_name(Node, PNode, Time, Name, Pid, S0);
+eval_trace({Node, Time, {ins_name_ext, PNode}, _Nodes, [Name, Pid]}, S0) ->
+ insert_external_name(Node, PNode, Time, Name, Pid, S0);
+eval_trace({Node, Time, {ins_lock, PNode}, _Nodes, [Id, Pid]}, S0) ->
+ insert_lock(Node, PNode, Time, Id, Pid, S0);
+eval_trace({Node, Time, {rem_lock, PNode}, _Nodes, [Id, Pid]}, S0) ->
+ remove_lock(Node, PNode, Time, Id, Pid, S0);
+eval_trace({Node, Time, {locker_succeeded, _}, _Nodes,{_First,_Known}}, S0) ->
+ locker_succeeded(Node, Time, S0);
+eval_trace({Node, Time, {lock_rejected, _}, _Nodes, Known}, S0) ->
+ lock_rejected(Node, Time, Known, S0);
+eval_trace({Node, Time, {locker_failed, _}, _Nodes, {Tried,SoFar}}, S0) ->
+ locker_failed(Node, Time, Tried, SoFar, S0);
+eval_trace({Node, Time, {new_resolver, RNode}, _Nodes, [Tag, ResPid]}, S0) ->
+ new_resolver(Node, Time, RNode, Tag, ResPid, S0);
+eval_trace({Node, Time, {kill_resolver, RNode}, _Nodes, [Tag,_ResPid]}, S0) ->
+ stop_resolver(Node, Time, RNode, Tag, kill, S0);
+eval_trace({Node, Time, {exit_resolver, RNode}, _Nodes, [Tag]}, S0) ->
+ stop_resolver(Node, Time, RNode, Tag, exit, S0);
+eval_trace(_Ignored, S) ->
+io:format("ignored ~p~n", [_Ignored]),
+ S.
+
+init(_Node, [], _Time, S) ->
+ S;
+init(Node, NodesList, Time, S) ->
+ io:format("### ~p ~p: already in nodes(): ~p~n", [Node, Time, NodesList]),
+ S.
+
+node_down(Node, DownNode, NodesList, Time, S0) ->
+ case get_node(Node, S0) of
+ {ok, #node{known = Known, nodes = Nodes}=N} ->
+ case lists:member(DownNode, Nodes) of
+ true ->
+ S1 = case lists:member(DownNode, Known) of
+ true ->
+ S0;
+ false ->
+ io:format("### ~p ~p:~n "
+ "nodedown but unknown ~p~n",
+ [Node, Time, DownNode]),
+ case lists:member(DownNode, Nodes) of
+ true ->
+ io:format("(but note that ~p"
+ " is member of nodes())~n",
+ [DownNode]);
+ false ->
+ ok
+ end,
+ add_spurious(Node, DownNode, S0, Time)
+ end,
+ NewKnown = lists:delete(DownNode, Known),
+ NewNodes = lists:delete(DownNode, Nodes),
+ put_node(N#node{known = NewKnown, nodes = NewNodes}, S1);
+ false ->
+ io:format("### ~p ~p:~n spurious nodedown from ~p~n "
+ "~p~n", [Node, Time, DownNode, NodesList]),
+ NewKnown = lists:delete(DownNode, Known),
+ S1 = put_node(N#node{known = NewKnown,nodes = Nodes}, S0),
+ add_spurious(Node, DownNode, S1, Time)
+ end;
+ not_ok ->
+ io:format("### ~p ~p:~n unknown node got nodedown from ~p~n",
+ [Node, Time, DownNode]),
+ add_spurious(Node, DownNode, S0, Time)
+ end.
+
+node_up(Node, UpNode, NodesList, Time, S) ->
+ case get_node(Node, S) of
+ {ok, #node{nodes = Nodes}=N} ->
+ case lists:member(UpNode, Nodes) of
+ true ->
+ io:format("### ~p ~p:~n spurious nodeup from ~p~n "
+ "~p~n", [Node, Time, UpNode, NodesList]),
+ add_spurious(Node, UpNode, S, Time);
+ false ->
+ put_node(N#node{nodes = lists:sort([UpNode | Nodes])}, S)
+ end;
+ not_ok ->
+ S#w{nodes = [#node{node = Node, nodes = [UpNode]} | S#w.nodes]}
+ end.
+
+added(Node, Added, Time, S0) ->
+ case get_node(Node, S0) of
+ {ok, #node{known = Known, nodes = Nodes}=N} ->
+ case Known -- (Known -- Added) of
+ [] ->
+ S1 = put_node(N#node{known = lists:sort(Added ++ Known),
+ nodes = Nodes}, S0),
+ case lists:member(Node, Added) of
+ true ->
+ io:format("### ~p ~p:~n adding node()"
+ " to known (~p)~n", [Node, Time,Added]),
+ add_spurious(Node, Added, S1, Time);
+ false ->
+ S1
+ end;
+ AK ->
+ io:format("### ~p ~p:~n added already known ~p~n",
+ [Node, Time, AK]),
+ S1 = put_node(N#node{known = lists:usort(Added ++ Known),
+ nodes = Nodes}, S0),
+ add_spurious(Node, AK, S1, Time)
+ end;
+ not_ok ->
+ io:format("### ~p ~p:~n unknown node got added ~p~n",
+ [Node, Time, Added]),
+ S1 = S0#w{nodes = [#node{node = Node, known = Added} |
+ S0#w.nodes]},
+ add_spurious(Node, Added, S1, Time)
+ end.
+
+nodes_changed(Node, New, Old, Time, S) ->
+ io:format("### ~p ~p:~n nodes changed, new are ~p, old are ~p~n",
+ [Node, Time, New, Old]),
+ S.
+
+insert_external_name(Node, PNode, Time, Name, Pid, S) ->
+ insert_name(Node, PNode, Time, Name, Pid, S).
+
+insert_name(Node, PNode, Time, Name, Pid, S0) ->
+ RegName = {Name, Pid, PNode},
+ case get_node(Node, S0) of
+ {ok, #node{names = Names}=N} ->
+ case lists:keysearch(Name, 1, Names) of
+ {value, {Name, OldPid, OldPNode}} ->
+ io:format("### ~p ~p:~n name ~p already registered "
+ "for ~p on ~p~n",
+ [Node, Time, Name, OldPid, OldPNode]),
+ add_spurious(Node, [PNode], S0, Time);
+ false ->
+ case lists:keysearch(Pid, 2, Names) of
+ {value, {OldName, Pid, OldPNode}} ->
+ io:format("### ~p ~p:~n pid ~p already "
+ "registered as ~p on ~p~n",
+ [Node, Time, Pid, OldName, OldPNode]),
+ add_spurious(Node, [PNode], S0, Time);
+ false ->
+ put_node(N#node{names = [RegName | Names]}, S0)
+ end
+ end;
+ not_ok ->
+ io:format("### ~p ~p:~n unknown node registered ~p for ~p "
+ "on ~p~n", [Node, Time, Name, Pid, PNode]),
+ Known = add_to_known(Node, PNode, []),
+ N = #node{node = Node, known = Known, names = [RegName]},
+ S1 = S0#w{nodes = [N | S0#w.nodes]},
+ add_spurious(Node, [PNode], S1, Time)
+ end.
+
+delete_name(Node, PNode, Time, Name, Pid, S0) ->
+ case get_node(Node, S0) of
+ {ok, #node{names = Names}=N} ->
+ case lists:keysearch(Name, 1, Names) of
+ {value, {Name, Pid, PNode}} ->
+ NewNames = lists:keydelete(Name, 1, Names),
+ put_node(N#node{names = NewNames}, S0);
+ {value, {Name, Pid2, PNode2}} -> % bad log
+ io:format("### ~p ~p:~n name ~p not registered "
+ "for ~p on ~p but for ~p on ~p~n",
+ [Node, Time, Name, Pid, PNode, Pid2, PNode2]),
+ add_spurious(Node, [PNode], S0, Time);
+ false ->
+ io:format("### ~p ~p:~n name ~p not registered "
+ "for ~p on ~p~n",
+ [Node, Time, Name, Pid, PNode]),
+ add_spurious(Node, [PNode], S0, Time)
+ end;
+ not_ok ->
+ io:format("### ~p ~p:~n unknown node deleted ~p for ~p on ~p~n",
+ [Node, Time, Name, Pid, PNode]),
+ Known = add_to_known(Node, PNode, []),
+ N = #node{node = Node, known = Known},
+ S1 = S0#w{nodes = [N | S0#w.nodes]},
+ add_spurious(Node, [PNode], S1, Time)
+ end.
+
+insert_lock(Node, PNode, Time, Id, Pid, S0) ->
+ Lock = {Pid, PNode},
+ case get_node(Node, S0) of
+ {ok, #node{locks = NLocks}=N} ->
+ case lists:keysearch(Id, 1, NLocks) of
+ {value, {Id, OldLocks}} ->
+ case lists:member(Lock, OldLocks) of
+ true ->
+ io:format("### ~p ~p:~n lock ~p already set "
+ "for ~p on ~p~n",
+ [Node, Time, Id, Pid, PNode]),
+ %% This is not so strange, actually.
+ add_spurious(Node, [PNode], S0, Time);
+ false ->
+ NewLocks = {Id, [Lock | OldLocks]},
+ Ls = lists:keyreplace(Id, 1, NLocks, NewLocks),
+ put_node(N#node{locks = Ls}, S0)
+ end;
+ false ->
+ put_node(N#node{locks = [{Id,[Lock]}|N#node.locks]}, S0)
+ end;
+ not_ok ->
+ Known = add_to_known(Node, PNode, []),
+ N = #node{node = Node, known = Known, locks = [{Id, [Lock]}]},
+ S1 = S0#w{nodes = [N | S0#w.nodes]},
+ if
+ Node =/= PNode ->
+ io:format("### ~p ~p:~n unknown pid ~p locked ~p on "
+ "~p~n", [Node, Time, Pid, Id, PNode]),
+ add_spurious(Node, [PNode], S1, Time);
+ true ->
+ S1
+ end
+ end.
+
+remove_lock(Node, PNode, Time, Id, Pid, S0) ->
+ Lock = {Pid, PNode},
+ case get_node(Node, S0) of
+ {ok, #node{locks = NLocks}=N} ->
+ case lists:keysearch(Id, 1, NLocks) of
+ {value, {Id, OldLocks}} ->
+ case lists:member(Lock, OldLocks) of
+ true ->
+ NewLocks = lists:delete(Lock, OldLocks),
+ Ls = case NewLocks of
+ [] ->
+ lists:keydelete(Id, 1, NLocks);
+ _ ->
+ lists:keyreplace(Id, 1, NLocks,
+ {Id, NewLocks})
+ end,
+ put_node(N#node{locks = Ls}, S0);
+ false ->
+ io:format("### ~p ~p:~n lock ~p not set "
+ "by ~p on ~p~n",
+ [Node, Time, Id, Pid, PNode]),
+ add_spurious(Node, [PNode], S0, Time)
+ end;
+ false ->
+ io:format("### ~p ~p:~n lock ~p not set "
+ "by ~p on ~p~n",
+ [Node, Time, Id, Pid, PNode]),
+ add_spurious(Node, [PNode], S0, Time)
+ end;
+ not_ok ->
+ io:format("### ~p ~p:~n ~p unlocked ~p on unknown node ~p~n",
+ [Node, Time, Pid, Id, PNode]),
+ Known = add_to_known(Node, PNode, []),
+ N = #node{node = Node, known = Known},
+ S1 = S0#w{nodes = [N | S0#w.nodes]},
+ add_spurious(Node, [PNode], S1, Time)
+ end.
+
+%% This is just statistics...
+locker_succeeded(Node, Time, S0) ->
+ case get_node(Node, S0) of
+ {ok, #node{n_locks = {Ok,Boss,NodeX,Bad}}=N} ->
+ put_node(N#node{n_locks = {Ok+1,Boss,NodeX,Bad}}, S0);
+ not_ok ->
+ io:format("### ~p ~p:~n unknown node's locker succeeded~n",
+ [Node, Time]),
+ add_spurious(Node, [Node], S0, Time)
+ end.
+
+lock_rejected(Node, Time, _Known, S0) ->
+ case get_node(Node, S0) of
+ {ok, #node{rejected = Rej}=N} ->
+ put_node(N#node{rejected = Rej+1}, S0);
+ not_ok ->
+ io:format("### ~p ~p:~n unknown node's lock rejected~n",
+ [Node, Time]),
+ add_spurious(Node, [Node], S0, Time)
+ end.
+
+locker_failed(Node, Time, Tried, SoFar, S0) ->
+ case get_node(Node, S0) of
+ {ok, #node{known = Known, n_locks = {Ok,Boss,NodeX,Bad}}=N} ->
+ TheBoss = lists:max([Node | Known]),
+ Cheap = (Tried =:= [TheBoss]),
+ RatherCheap = ((SoFar -- [Node, TheBoss]) =:= []) and
+ ((Tried -- [Node, TheBoss]) =/= []),
+ if
+ Cheap ->
+ put_node(N#node{n_locks = {Ok,Boss+1,NodeX,Bad}}, S0);
+ RatherCheap ->
+ put_node(N#node{n_locks = {Ok,Boss,NodeX+1,Bad}}, S0);
+ true ->
+ put_node(N#node{n_locks = {Ok,Boss,NodeX,Bad+1}}, S0)
+ end;
+ not_ok ->
+ io:format("### ~p ~p:~n unknown node's locker failed~n",
+ [Node, Time]),
+ add_spurious(Node, [Node], S0, Time)
+ end.
+
+new_resolver(Node, Time, ResNode, Tag, ResPid, S0) ->
+ case get_node(Node, S0) of
+ {ok, #node{resolvers = Rs}=N} ->
+ put_node(N#node{resolvers = [{ResNode, Tag, ResPid} | Rs]}, S0);
+ not_ok ->
+ io:format("### ~p ~p:~n resolver created for unknown node~n",
+ [Node, Time]),
+ add_spurious(Node, [Node], S0, Time)
+ end.
+
+stop_resolver(Node, Time, ResNode, Tag, How, S0) ->
+ case get_node(Node, S0) of
+ {ok, #node{resolvers = Rs}=N} ->
+ case lists:keysearch(Tag, 2, Rs) of
+ {value, {ResNode, Tag, _ResPid}} ->
+ NewRs = lists:keydelete(Tag, 2, Rs),
+ put_node(N#node{resolvers = NewRs}, S0);
+ false ->
+ case lists:keysearch(ResNode, 1, Rs) of
+ {value, {ResNode, _Tag2, _ResPid2}} ->
+ NewRs = lists:keydelete(ResNode, 1, Rs),
+ put_node(N#node{resolvers = NewRs}, S0);
+ false when How =:= exit ->
+ io:format("### ~p ~p:~n there is no resolver "
+ "with tag ~p on node ~p~n",
+ [Node, Time, Tag, ResNode]),
+ add_spurious(Node, [ResNode], S0, Time);
+ false when How =:= kill ->
+ S0
+ end
+ end;
+ not_ok ->
+ io:format("### ~p ~p:~n resolver stopped for unknown node~n",
+ [Node, Time]),
+ add_spurious(Node, [Node], S0, Time)
+ end.
+
+add_to_known(Node, NodeToAdd, Known) ->
+ if
+ Node =:= NodeToAdd ->
+ Known;
+ true ->
+ lists:sort([NodeToAdd | Known])
+ end.
+
+get_node(Node, S) ->
+ case lists:keysearch(Node, #node.node, S#w.nodes) of
+ {value, N} ->
+ {ok, N};
+ false ->
+ not_ok
+ end.
+
+put_node(#node{node = Node, known = [], nodes = [], locks = [], names = [],
+ n_locks = {0,0,0,0}},
+ S) ->
+ S#w{nodes = lists:keydelete(Node, #node.node, S#w.nodes)};
+put_node(N, S) ->
+ S#w{nodes = lists:keyreplace(N#node.node, #node.node, S#w.nodes, N)}.
+
+is_fresh(#node{known = [], nodes = [], locks = [], names = []}) ->
+ true;
+is_fresh(#node{}) ->
+ false;
+is_fresh([]) ->
+ true;
+is_fresh([N | Ns]) ->
+ is_fresh(N) andalso is_fresh(Ns).
+
+add_spurious(Node, ActionNodes, S, Time) when is_list(ActionNodes) ->
+ S#w{n = [{{Node,N},Time}|| N <- ActionNodes] ++ S#w.n};
+add_spurious(Node, ActionNode, S, Time) ->
+ add_spurious(Node, [ActionNode], S, Time).
+
+messages(D, Base, End) ->
+ messages1(no_info(D), no_info),
+ messages1(resolvers(D, Base, End), resolvers),
+ messages1(syncers(D), syncers).
+
+messages1(M, ST) ->
+ [foo || {Node, T} <- M,
+ ok =:= io:format(ms(ST), [Node, T])].
+
+ms(no_info) ->
+ "~p: ~p~n";
+ms(resolvers) ->
+ "~p: resolvers ~p~n";
+ms(syncers) ->
+ "~p: syncers ~p~n".
+
+no_info(D) ->
+ [{Node,no_info} || {Node, no_info} <- D].
+
+resolvers(D, Base, End) ->
+ [{Node,
+ [{N,adjust_time(T, Base),P} || {N, T, P} <- Rs, T < End]} ||
+ {Node, {info,State}} <- D,
+ is_record(State, state),
+ [] =/= (Rs = (state(State))#state.resolvers)].
+
+syncers(D) ->
+ [{Node,Ss} || {Node, {info,State}} <- D,
+ is_record(State, state),
+ [] =/= (Ss = (state(State))#state.syncers)].
+
+net_kernel_nodes(NodeTrace) ->
+ [{Node, nkn(Trace, [])} || {Node, Trace} <- NodeTrace].
+
+nkn([], _Nodes) ->
+ [];
+nkn([{Node, Time, _Message, Ns, _X} | Ts], Nodes) ->
+ {NewS, _, OldS} = sofs:symmetric_partition(sofs:set(Ns), sofs:set(Nodes)),
+ New = sofs:to_external(NewS),
+ Old = sofs:to_external(OldS),
+ [{Node, Time, {newnode, N}, []} || N <- New] ++
+ [{Node, Time, {oldnode, N}, []} || N <- Old] ++
+ nkn(Ts, (Nodes -- Old) ++ New).
+
+negotiations(Trace) ->
+ Ns = [{Node,T,Added,X} ||
+ {Node,T,{added,Added},_Nodes,X} <- Trace],
+ Pass = [{passive,Node,T,Added} ||
+ {Node,T,Added,[_Ops]} <- Ns],
+ Act = [{active,Node,T,Other,Added,NewNodes} ||
+ {Node,T,Added,[{new_nodes,[Other|_]=NewNodes},_Abcast,_Ops]} <- Ns],
+ Act ++ Pass.
+
+show_spurious(NodeTrace, Spurious) ->
+ Pairs = [{Node,ActionNode} || {{Node,ActionNode}, _Time} <- Spurious],
+ S = sofs:restriction(sofs:relation(NodeTrace), sofs:set(Pairs)),
+ [foo ||
+ {{{Node,ANode},Times},
+ {{Node,ANode},Ts}} <- lists:zip(family(Spurious),
+ sofs:to_external(S)),
+ show_spurious(Node, ANode, Times, lists:keysort(2, Ts))].
+
+show_spurious(Node, ActionNode, Times, Ts) ->
+ io:format("** Actions for ~p on node ~p **~n", [ActionNode, Node]),
+ lists:map(fun(T) -> spurious(Node, T, Times) end, Ts),
+ io:format("-- End of actions for ~p on node ~p --~n", [ActionNode, Node]),
+ true.
+
+spurious(Node, Trace, Times) ->
+ As = case Trace of
+ {Node, _T0, {init, Node}, _Nodes, _} ->
+ init; % should not happen, I guess
+ {Node, _T0, {nodedown, _ActionNode}, _Nodes, _} ->
+ nodedown;
+ {Node, _T0, {extra_nodedown, _ActionNode}, _Nodes, _} ->
+ extra_nodedown;
+ {Node, _T0, {nodeup, _ActionNode}, _Nodes, _} ->
+ nodeup;
+ {Node, _T0, {added, Added}, _Nodes, [_Ops]} ->
+ {passive, Added};
+ {Node, _T0, {added, Added}, _Nodes, [_NewNodes,_AbCast,_Ops]} ->
+ {negotiator, Added};
+ {Node, _T0, {ins_lock, PNode}, _Nodes, [Id, Pid]} ->
+ {insert_lock, [Id, Pid, PNode]};
+ {Node, _T0, {rem_lock, PNode}, _Nodes, [Id, Pid]} ->
+ {remove_lock, [Id, Pid, PNode]};
+ {Node, _T0, {ins_name, PNode}, _Nodes, [Name, Pid]} ->
+ {insert_name, [Name, Pid, PNode]};
+ {Node, _T0, {del_name, PNode}, _Nodes, [Name, Pid]} ->
+ {insert_name, [Name, Pid, PNode]};
+ {Node, _T0, {nodes_changed, CNode}, _Nodes, []} ->
+ {nodes_changed, [CNode]};
+ {Node, _T0, {Any, Some}, _Nodes, X} ->
+ {Any, [Some | X]}
+ end,
+ T = element(2, Trace),
+ _Nodes2 = element(4, Trace),
+ TS = ["(spurious)" || lists:member(T, Times)],
+ io:format("~p: ~p ~s~n", [T, As, TS]),
+% io:format(" ~w~n", [_Nodes2]),
+ ok.
+
+display_nodes(Why, Time, Nodes) ->
+ display_nodes(Why, Time, Nodes, none).
+
+display_nodes(Why, Time, Nodes, LastTrace) ->
+ io:format("~p **** ~s ****~n", [Time, Why]),
+ {OkL, BossL, NodeXL, BadL} = unzip4([L || #node{n_locks = L} <- Nodes]),
+ [NOk, NBoss, NNodeX, NBad] =
+ [lists:sum(L) || L <- [OkL, BossL, NodeXL, BadL]],
+ Rejected = lists:sum([Rej || #node{rejected = Rej} <- Nodes]),
+ io:format("Locks: (~w+~w+~w=~w)/~w, ~w of ~w rejected~n",
+ [NOk, NBoss, NNodeX, NOk+NBoss+NNodeX, NOk+NBoss+NNodeX+NBad,
+ Rejected, NOk]),
+ lists:foreach(fun(#node{node = Node, known = Known, nodes = Ns,
+ locks = Locks, names = Names,
+ n_locks = {Ok, Boss, NodeX, Bad},
+ resolvers = Resolvers0,
+ rejected = Rej}) ->
+ NodeL = io_lib:format("~p: ",[Node]),
+ io:format("~sknown ~p~n", [NodeL, Known]),
+ Sp = spaces(NodeL),
+ case Ns =:= Known of
+ true -> ok;
+ false -> display_list(Sp, nodes, Ns)
+ end,
+ display_list(Sp, locks, Locks),
+ display_list(Sp, names, lists:sort(Names)),
+ Resolvers = lists:sort(Resolvers0),
+ _ResNs = [R || {R,_,_} <- Resolvers],
+ %% Should check trace on this node (Node) only:
+ New = [N || {_,_,{nodeup,N},_,_} <- [LastTrace]],
+ _ResAllowed = (Ns -- New) -- Known,
+%% Displays too much junk.
+% case ResAllowed =:= ResNs of
+% true -> ok;
+% false -> display_list(Sp, resol, Resolvers)
+% end,
+ %% This is less bulky:
+ case Known =:= Ns of
+ true -> display_list(Sp, resol, Resolvers);
+ false -> ok
+ end,
+ case {Ok, Boss, NodeX, Bad} of
+ {0, 0, 0, 0} -> ok;
+ _ -> io:format("~slocks (~w+~w+~w=~w)/~w, "
+ "~w of ~w rejected~n",
+ [Sp, Ok, Boss, NodeX,
+ Ok+Boss+NodeX,Ok+Boss+NodeX+Bad,
+ Rej, Ok])
+ end
+ end, lists:keysort(#node.node, Nodes)),
+ io:format("\n").
+
+display_list(_S, _What, []) ->
+ ok;
+display_list(S, What, L) ->
+ io:format("~s~p ~p~n", [S, What, L]).
+
+spaces(Iolist) ->
+ lists:duplicate(iolist_size(Iolist), $\s).
+
+family(R) ->
+ sofs:to_external(sofs:relation_to_family(sofs:relation(R))).
+
+time_diff({S1,MyS1}, {S0,MyS0}) ->
+ ((S1*1000000+MyS1) - (S0*1000000+MyS0)) div 1000000.
+
+musec2sec(T) ->
+ S = T div 1000000,
+ M = (T - S * 1000000),
+ {S, M}.
+
+%%% Options
+
+options(Options, Keys) when is_list(Options) ->
+ options(Options, Keys, []);
+options(Option, Keys) ->
+ options([Option], Keys, []).
+
+options(Options0, [Key | Keys], L) when is_list(Options0) ->
+ Options = case lists:member(Key, Options0) of
+ true ->
+ [atom_option(Key) | lists:delete(Key, Options0)];
+ false ->
+ Options0
+ end,
+ V = case lists:keysearch(Key, 1, Options) of
+ {value, {show_state, From, To}} when is_integer(From), From >= 0,
+ is_integer(To), To >= From ->
+ {ok, {{From,0}, {To,0}}};
+ {value, {show_state, {From, FromMusec},
+ {To, ToMusec}}} when is_integer(From),
+ From >= 0,
+ is_integer(To),
+ To >= From,
+ FromMusec >= 0,
+ FromMusec =< 999999,
+ ToMusec >= 0,
+ ToMusec =< 999999 ->
+ {ok, {{From,FromMusec}, {To,ToMusec}}};
+ {value, {show_state, false}} ->
+ {value, default_option(show_state)};
+ {value, {show_trace, Bool}} when Bool; not Bool ->
+ {ok, Bool};
+ {value, {Key, _}} ->
+ badarg;
+ false ->
+ Default = default_option(Key),
+ {ok, Default}
+ end,
+ case V of
+ badarg ->
+ badarg;
+ {ok, Value} ->
+ NewOptions = lists:keydelete(Key, 1, Options),
+ options(NewOptions, Keys, [Value | L])
+ end;
+options([], [], L) ->
+ lists:reverse(L);
+options(_Options, _, _L) ->
+ badarg.
+
+default_option(show_state) -> {{0,0}, {0,0}};
+default_option(show_trace) -> true.
+
+atom_option(show_state) ->
+ {show_state, 0, 1 bsl 28};
+atom_option(show_trace) ->
+ {show_trace, true};
+atom_option(_) ->
+ erlang:error(program_error, []).
+
+unzip4(Ts) -> unzip4(Ts, [], [], [], []).
+
+unzip4([{X, Y, Z, W} | Ts], Xs, Ys, Zs, Ws) ->
+ unzip4(Ts, [X | Xs], [Y | Ys], [Z | Zs], [W | Ws]);
+unzip4([], Xs, Ys, Zs, Ws) ->
+ {lists:reverse(Xs), lists:reverse(Ys),
+ lists:reverse(Zs), lists:reverse(Ws)}.
+