blob: 5e11ad7e7548cd76c6b92810403b0c9d5feb4c51 [file] [edit]
#!/usr/bin/env python3
#
# Copyright (C) 2024 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Utility function to create a visualization graph using dot language."""
import argparse
import hashlib
import json
import logging
import pathlib
import sys
def _create_graphviz(
adjacency_list: dict,
output: pathlib.Path,
colors: bool,
):
"Creates a diagram to display a graph using DOT language."
content = ["digraph {"]
content.extend([
"\tgraph [rankdir=LR, splines=ortho];",
"\tnode [color=steelblue, shape=plaintext];",
"\tedge [arrowhead=odot, color=olive];",
])
leaves = []
for node in adjacency_list.values():
# vmlinux is dependency for most of the nodes so skip it.
if node["name"] == "vmlinux":
continue
# Skip nodes without dependents.
if not node["dependents"]:
leaves.append(node["name"])
continue
edges = []
for neighbor in node["dependents"]:
edges.append(f'"{adjacency_list[neighbor]["name"]}"')
edge_str = ",".join(edges)
# Customize edge colors.
edge_color = ""
if colors:
h = hashlib.shake_256(edge_str.encode())
edge_color = f' [color=" # {h.hexdigest(3)}"]'
content.append(f'\t"{node["name"]}" -> {edge_str}{edge_color};')
logging.warning("Leaf nodes: [%s]", leaves)
content.append("}")
output.write_text("\n".join(content), encoding="utf-8")
def _read_graph(
adjacency_list_file: str,
):
try:
with pathlib.Path(adjacency_list_file).open() as adjacency_list:
return json.load(adjacency_list)
except Exception as exc:
raise argparse.ArgumentError(
f"{adjacency_list_file}", "Failed to load."
) from exc
def main():
"""Creates two maps of dependencies for a directory full of kernel modules."""
parser = argparse.ArgumentParser()
parser.add_argument(
"adjacency_list",
type=_read_graph,
help="File with a graph represented as an adjacency list.",
)
parser.add_argument(
"output", type=pathlib.Path, help="Where to store the output"
)
parser.add_argument(
"--colors",
action="store_true",
help=(
"Edges to dependents of a module share the same color. This is"
" useful to differentiate dependencies of a module."
),
)
args = parser.parse_args()
logging.basicConfig(level=logging.INFO,
format="%(levelname)s: %(message)s")
# Create graph visualization.
_create_graphviz(args.adjacency_list, args.output, args.colors)
if __name__ == "__main__":
sys.exit(main())