This will be a short one as there is not much to say however I want to keep my findings somewhere so here it is.
TLDR
#!/usr/bin/env escript
% -*- erlang -*-
main(_) ->
xref:start(server),
xref:add_directory(server, "_build/default/lib", [{recurse, true}]),
{ok, Deps} = xref:q(server, "E ||| V"),
xref:stop(server),
FromTo = lists:map(
fun(X) ->
{{From, _, _}, {To, _, _}} = X,
{From, To}
end,
Deps
),
FromToDifferent = lists:filter(
fun(X) ->
{From, To} = X,
From =/= To
end,
FromTo
),
FromToUnique = lists:uniq(FromToDifferent),
LinksText = lists:foldl(
fun(X, Acc) ->
{From, To} = X,
FromString = atom_to_list(From),
ToString = atom_to_list(To),
case string:prefix(ToString, "$") of
nomatch ->
string:join(
[Acc, "\t", FromString, " -> ", ToString, "\n"], ""
);
_ -> Acc
end
end,
"",
FromToUnique
),
Output = string:join(["strict digraph {\n", LinksText, "}\n"], ""),
ok = file:write_file("./graph.dot", Output),
os:cmd("dot -x -Goverlap=scale -Tpng ./graph.dot -o ./graph.png"),
os:cmd("firefox ./graph.png").
Find dependencies
To identify dependencies, we will use the xref
module that comes with the Erlang standard library.
First, create an xref
server by typing the following:
xref:start(server).
Next, provide a directory for analysis.
In this example, I will use the default build directory, but you can specify the directory of a specific version.
xref:add_directory(server, "_build/default/lib", [{recurse, true}]).
Finally, query the dependencies.
In this case my query is E ||| V
to find the subset of calls to and from any of the vertices. For more information, refer to the xref documentation
{ok, Deps} = xref:q(server, "E ||| V").
Filter and transform the dependency list
First, transform the tuples given by xref
to a simpler {from, to}
tuple.
FromTo = lists:map(
fun(X) ->
{{From, _, _}, {To, _, _}} = X,
{From, To}
end,
Deps
).
Next, remove the modules that point to themselves, as it is not interesting in this case. Keep only unique tuples to avoid repetition.
FromToDifferent = lists:filter(
fun(X) ->
{From, To} = X,
From =/= To
end,
FromTo
),
FromToUnique = lists:uniq(FromToDifferent).
Create the output text
Transform the list into a string that adheres to the graphviz
format. This format allows you to later create a png
or pdf
of the graph.
LinksText = lists:foldl(
fun(X, Acc) ->
{From, To} = X,
FromString = atom_to_list(From),
ToString = atom_to_list(To),
% '$' breaks xgraphviz
case string:prefix(ToString, "$") of
nomatch ->
string:join(
[Acc, "\t", FromString, " -> ", ToString, "\n"], ""
);
_ -> Acc
end
end,
"",
FromToUnique
),
Output = string:join(["strict digraph {\n", LinksText, "}\n"], "").
Finally, write the Output
variable to a file, launch the graphviz
command, and use firefox
to visualize the generated image.
ok = file:write_file("./graph.dot", Output),
os:cmd("dot -x -Goverlap=scale -Tpng ./graph.dot -o ./graph.png"),
os:cmd("firefox ./graph.png").
The x -Goverlap=scale
flags are used to improve the output file by giving more space between nodes. However, as you will see below, the readability is still not perfect.
Limitations
While writing this script, I discovered that some elements are missing. At the beginning, I thought I had made a mistake, but it turned out to be a limitation. This limitation is that it is unable to extract information from unresolved calls such as spawn
or apply
.