Current section
Files
Jump to
Current section
Files
src/ides_static.erl
-module(ides_static).
-export([
supervisor_tree/1,
kill_graph/2,
ancestors/2,
siblings/2,
intensity_info/2,
format/2,
print/2,
find_process_by_name/2
]).
-export_type([
t/0,
worker_process/0,
supervisor_process/0,
static_process/0,
intensity_info/0,
kill_source/0,
static_error/0,
static_warning/0
]).
-type worker_process() :: #{
name := string(),
module := module(),
parent := module() | undefined,
type := worker,
restart_type := ides:child_restart_type()
}.
-type supervisor_process() :: #{
name := string(),
module := module(),
parent := module() | undefined,
type := supervisor,
strategy := ides:supervisor_strategy(),
restart_type => ides:child_restart_type(),
children := [static_process()],
intensity := intensity_info()
}.
-type static_process() :: supervisor_process() | worker_process().
-type t() :: #{
tree := [static_process()],
warnings := [static_warning()]
}.
-type intensity_info() :: #{
max_restarts := non_neg_integer(),
max_period := non_neg_integer()
}.
-type kill_source() :: {ancestor, module()} | {sibling, module()}.
-type static_error() ::
{missing_beam, module()}
| {no_debug_info, module()}
| {not_a_supervisor, module()}
| {dynamic_child_spec, module()}
| {unresolvable_module, module()}.
-type static_warning() ::
{unresolvable_module, module()}
| {dynamic_child_spec, module()}
| {missing_behaviour, module()}.
-doc "Build the static supervision tree from BEAM files."
"Returns tree roots and any warnings (unresolvable modules, dynamic children).".
-spec supervisor_tree([file:filename()]) ->
{ok, #{tree := [static_process()], warnings := [static_warning()]}} | {error, static_error()}.
supervisor_tree(BeamPaths) ->
{ok, BeamMap} = ides_static_beam:load_beams(BeamPaths),
Supervisors = maps:filter(fun(_M, Info) -> ides_static_beam:is_supervisor(Info) end, BeamMap),
{RootTrees, Warnings} = build_trees(Supervisors, BeamMap),
{ok, #{tree => RootTrees, warnings => Warnings}}.
build_trees(Supervisors, BeamMap) ->
{AllTrees, AllWarnings} = maps:fold(
fun(Module, Info, {TreesAcc, WarnAcc}) ->
case ides_static_parse:parse_init(Info) of
{ok, SupFlags, ChildSpecs} ->
{Children, ChildWarnings} = build_children(ChildSpecs, BeamMap, Module),
Tree = #{
name => atom_to_list(Module),
module => Module,
parent => undefined,
type => supervisor,
strategy => maps:get(strategy, SupFlags),
children => Children,
intensity => #{
max_restarts => maps:get(intensity, SupFlags),
max_period => maps:get(period, SupFlags)
}
},
{[Tree | TreesAcc], WarnAcc ++ ChildWarnings};
{error, _} ->
{TreesAcc, WarnAcc}
end
end,
{[], []},
Supervisors
),
%% Only return root supervisors (not referenced as children of others)
AllChildModules = collect_child_modules(AllTrees),
RootTrees = [T || T <- AllTrees, not lists:member(maps:get(module, T), AllChildModules)],
{RootTrees, AllWarnings}.
collect_child_modules(Trees) ->
lists:flatmap(fun collect_modules/1, Trees).
collect_modules(#{children := Children}) ->
[maps:get(module, C) || C <- Children] ++
lists:flatmap(fun collect_modules/1, Children);
collect_modules(_) ->
[].
build_children(ChildSpecs, BeamMap, ParentModule) ->
lists:foldl(
fun(Child, {KidsAcc, WarnAcc}) ->
{Kid, Warns} = build_child(Child, BeamMap, ParentModule),
{KidsAcc ++ [Kid], WarnAcc ++ Warns}
end,
{[], []},
ChildSpecs
).
build_child(Child, BeamMap, ParentModule) ->
{M, _F, _A} = maps:get(start, Child),
case maps:get(type, Child) of
supervisor ->
case maps:find(M, BeamMap) of
{ok, Info} ->
case ides_static_parse:parse_init(Info) of
{ok, SupFlags, ChildSpecs} ->
{Children, ChildWarnings} = build_children(ChildSpecs, BeamMap, M),
Warns = child_warnings(SupFlags, M, ChildWarnings),
Result = #{
name => atom_to_list(maps:get(id, Child)),
module => M,
parent => ParentModule,
type => supervisor,
strategy => maps:get(strategy, SupFlags),
restart_type => maps:get(restart, Child),
children => Children,
intensity => #{
max_restarts => maps:get(intensity, SupFlags),
max_period => maps:get(period, SupFlags)
}
},
{Result, Warns};
{error, _} ->
{
#{
name => atom_to_list(maps:get(id, Child)),
module => M,
parent => ParentModule,
type => worker,
restart_type => maps:get(restart, Child)
},
[]
}
end;
error ->
{
#{
name => atom_to_list(maps:get(id, Child)),
module => M,
parent => ParentModule,
type => worker,
restart_type => maps:get(restart, Child)
},
[{unresolvable_module, M}]
}
end;
worker ->
Warns =
case maps:find(M, BeamMap) of
{ok, _} -> [];
error -> [{unresolvable_module, M}]
end,
{
#{
name => atom_to_list(maps:get(id, Child)),
module => M,
parent => ParentModule,
type => worker,
restart_type => maps:get(restart, Child)
},
Warns
}
end.
child_warnings(SupFlags, M, ChildWarnings) ->
case maps:get(strategy, SupFlags) of
simple_one_for_one -> [{dynamic_child_spec, M} | ChildWarnings];
_ -> ChildWarnings
end.
-doc "Return restart intensity policy for a supervisor module.".
-spec intensity_info(module(), t()) ->
{ok, intensity_info()} | {error, not_found}.
intensity_info(Module, #{tree := Trees}) ->
case find_node(Module, Trees) of
{ok, #{type := supervisor, intensity := Intensity}} ->
{ok, Intensity};
_ ->
{error, not_found}
end.
-doc "Return all modules that could cause the target module's process to be killed.".
-spec kill_graph(module(), t()) ->
{ok, [module()]} | {error, not_found}.
kill_graph(Module, #{tree := Trees}) ->
case find_node(Module, Trees) of
{ok, _} ->
Killers = find_killers(Module, Trees),
{ok, Killers};
error ->
{error, not_found}
end.
find_killers(Module, Trees) ->
lists:usort(find_killers_in_trees(Module, Trees)).
find_killers_in_trees(Module, Trees) ->
lists:flatmap(fun(Tree) -> find_killers_in_node(Module, Tree, []) end, Trees).
find_killers_in_node(Module, #{type := supervisor, children := Children} = Sup, Ancestors) ->
SupModule = maps:get(module, Sup),
case has_child(Module, Children) of
true ->
AncestorModules = [maps:get(module, A) || A <- Ancestors],
Strategy = maps:get(strategy, Sup),
SiblingKillers = killer_siblings_for(Strategy, Module, Children),
[SupModule | AncestorModules] ++ SiblingKillers ++
child_killers(Module, Children, [Sup | Ancestors]);
false ->
child_killers(Module, Children, [Sup | Ancestors])
end;
find_killers_in_node(_Module, _Node, _Ancestors) ->
[].
child_killers(Module, Children, Ancestors) ->
lists:flatmap(
fun(C) -> find_killers_in_node(Module, C, Ancestors) end,
Children
).
killer_siblings_for(one_for_all, Module, Children) ->
child_modules_except(Module, Children);
killer_siblings_for(rest_for_one, Module, Children) ->
{Before, _} = lists:splitwith(
fun(C) -> maps:get(module, C) =/= Module end,
Children
),
[maps:get(module, C) || C <- Before];
killer_siblings_for(_, _Module, _Children) ->
[].
has_child(Module, Children) ->
lists:any(fun(C) -> maps:get(module, C) =:= Module end, Children).
child_modules_except(Module, Children) ->
[maps:get(module, C) || C <- Children, maps:get(module, C) =/= Module].
-doc "Return the ancestor supervisor chain for a module, from root to direct parent.".
-spec ancestors(module(), t()) ->
{ok, [module()]} | {error, not_found}.
ancestors(Module, #{tree := Trees}) ->
case find_ancestors(Module, Trees) of
[] -> {error, not_found};
Path -> {ok, Path}
end.
find_ancestors(Module, Trees) ->
lists:flatmap(fun(T) -> find_ancestors_path(Module, T) end, Trees).
find_ancestors_path(Module, #{type := supervisor, module := Mod, children := Children}) ->
case has_child(Module, Children) of
true ->
[Mod];
false ->
case lists:flatmap(fun(C) -> find_ancestors_path(Module, C) end, Children) of
[] -> [];
[_ | _] = InnerPath -> [Mod | InnerPath]
end
end;
find_ancestors_path(_Module, _Node) ->
[].
-doc "Return sibling modules (other children of the same parent supervisor).".
-spec siblings(module(), t()) ->
{ok, [module()]} | {error, not_found}.
siblings(Module, #{tree := Trees}) ->
case find_node(Module, Trees) of
{ok, #{parent := ParentMod}} when ParentMod =/= undefined ->
{ok, ParentNode} = find_node(ParentMod, Trees),
Siblings = child_modules_except(Module, maps:get(children, ParentNode)),
{ok, Siblings};
{ok, _} ->
{ok, []};
error ->
{error, not_found}
end.
-doc "Render the supervision tree as indented ASCII, marking the target with `*`.".
-spec format(module(), t()) -> iolist().
format(Module, #{tree := Trees}) ->
[format_node(Module, T, 0) || T <- Trees].
format_node(
Module,
#{type := supervisor, strategy := Strategy, children := Children} = Node,
Depth
) ->
Name = maps:get(name, Node, atom_to_list(maps:get(module, Node))),
Anno =
case maps:find(restart_type, Node) of
{ok, Restart} -> [" (", atom_to_list(Strategy), ", ", atom_to_list(Restart), ")"];
error -> [" (", atom_to_list(Strategy), ")"]
end,
Prefix = format_prefix(Module, Node, Depth),
[
Prefix,
Name,
Anno,
"\n"
| [format_node(Module, C, Depth + 1) || C <- Children]
];
format_node(Module, #{type := worker, restart_type := Restart} = Node, Depth) ->
Name = maps:get(name, Node, atom_to_list(maps:get(module, Node))),
Prefix = format_prefix(Module, Node, Depth),
[Prefix, Name, " (", atom_to_list(Restart), ")\n"].
format_prefix(_Module, _Node, 0) ->
"";
format_prefix(Module, #{module := Module}, Depth) ->
lists:duplicate(Depth * 4 - 2, $\s) ++ "* ";
format_prefix(_Module, _Node, Depth) ->
lists:duplicate(Depth * 4 - 2, $\s) ++ " ".
-doc "Like `format/2` but writes to stdout.".
-spec print(module(), t()) -> ok.
print(Module, Trees) ->
io:format("~s", [format(Module, Trees)]).
-doc "Look up a module by its registered process name in the tree.".
-spec find_process_by_name(string(), t()) -> {ok, module()} | {error, not_found}.
find_process_by_name(Name, #{tree := Trees}) ->
find_by_name(Name, Trees).
find_by_name(Name, [#{name := Name, module := Mod} | _]) ->
{ok, Mod};
find_by_name(Name, [#{children := Children} | Rest]) ->
case find_by_name(Name, Children) of
{ok, Mod} -> {ok, Mod};
{error, not_found} -> find_by_name(Name, Rest)
end;
find_by_name(Name, [_ | Rest]) ->
find_by_name(Name, Rest);
find_by_name(_, []) ->
{error, not_found}.
find_node(_Module, []) ->
error;
find_node(Module, [#{module := Module} = Node | _]) ->
{ok, Node};
find_node(Module, [#{type := supervisor, children := Children} | Rest]) ->
case find_node(Module, Children) of
{ok, _} = Found -> Found;
error -> find_node(Module, Rest)
end;
find_node(Module, [_ | Rest]) ->
find_node(Module, Rest).