• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python3
2"""A script for parsing and filtering component dot file.
3Adapted from vendor/google_clockwork/packages/SystemUI/daggervis/parser.py
4
5Input: input_dot_file output_dot_file [beginning_nodes_filter]
6Output: create a new dot file with styles applied. The output dot file will only contain nodes
7reachable from the beginning_nodes_filter if it's specified.
8"""
9import sys
10import os
11import random
12try:
13    import pydot
14except ImportError as e:
15    print("Error: python3-pydot is not installed. Please run \"sudo apt install python3-pydot\" first.", file=sys.stderr)
16    sys.exit(1)
17
18def main():
19    # Parse args
20    if len(sys.argv) < 2:
21        print("Error: please specify an input dot file", file=sys.stderr)
22        sys.exit(1)
23    if len(sys.argv) < 3:
24        print("Error: please specify an output dot file", file=sys.stderr)
25        sys.exit(1)
26    input_path = sys.argv[1]
27    output_path = sys.argv[2]
28    if len(sys.argv) > 3:
29        beginning_nodes_filter= sys.argv[3]
30    else:
31        beginning_nodes_filter= None
32
33    # Load graph
34    try:
35        graph = pydot.graph_from_dot_file(input_path)[0]
36    except Exception as e:
37        print("Error: unable to load dot file \"" + input_path + "\"", file=sys.stderr)
38        sys.exit(1)
39    print("Loaded dot file from " + input_path)
40
41    # Trim graph
42    if beginning_nodes_filter!= None:
43        trim_graph(graph, beginning_nodes_filter)
44
45    # Add styles
46    style_graph(graph)
47
48    with open(output_path, "w") as f:
49        f.write(str(graph))
50        print("Saved output dot file " + output_path)
51
52"""
53Trim a graph by only keeping nodes/edges reachable from beginning nodes.
54"""
55def trim_graph(graph, beginning_nodes_filter):
56    beginning_node_names = set()
57    all_nodes = graph.get_nodes()
58    for n in all_nodes:
59        if beginning_nodes_filter in get_label(n):
60            beginning_node_names.add(n.get_name())
61    if len(beginning_node_names) == 0:
62        print("Error: unable to find nodes matching \"" + beginning_nodes_filter + "\"", file=sys.stderr)
63        sys.exit(1)
64    filtered_node_names = set()
65    all_edges = graph.get_edges()
66    for node_name in beginning_node_names:
67        dfs(all_edges, node_name, filtered_node_names)
68    cnt_trimmed_nodes = 0
69    for node in all_nodes:
70        if not node.get_name() in filtered_node_names:
71            graph.del_node(node.get_name())
72            cnt_trimmed_nodes += 1
73    cnt_trimmed_edges = 0
74    for edge in all_edges:
75        if not edge.get_source() in filtered_node_names:
76            graph.del_edge(edge.get_source(), edge.get_destination())
77            cnt_trimmed_edges += 1
78    print("Trimed " + str(cnt_trimmed_nodes) + " nodes and " + str(cnt_trimmed_edges) + " edges")
79
80def dfs(all_edges, node_name, filtered_node_names):
81    if node_name in filtered_node_names:
82        return
83    filtered_node_names.add(node_name)
84    for edge in all_edges:
85        if edge.get_source() == node_name:
86            dfs(all_edges, edge.get_destination(), filtered_node_names)
87
88"""
89Apply styles to the dot graph.
90"""
91def style_graph(graph):
92    for n in graph.get_nodes():
93        label = get_label(n)
94        # Contains additional classes that are outside the typical CarSystemUI package path/naming
95        additional_car_systemui_classes = [
96            "com.android.systemui.wm.BarControlPolicy",
97            "com.android.systemui.wm.DisplaySystemBarsController",
98            "com.android.systemui.wm.DisplaySystemBarsInsetsControllerHost"
99            ]
100        # Style SystemUI nodes
101        if ("com.android.systemui" in label):
102            if ("com.android.systemui.car" in label or "Car" in label or label in additional_car_systemui_classes):
103                n.obj_dict["attributes"]["color"] = "darkolivegreen1"
104                n.add_style("filled")
105            else:
106                n.obj_dict["attributes"]["color"] = "burlywood"
107                n.obj_dict["attributes"]["shape"] = "box"
108                n.add_style("filled")
109
110        # Trim common labels
111        trim_replacements = [("java.util.", ""), ("javax.inject.", "") , ("com.", "c."),
112                             ("google.", "g."), ("android.", "a."),
113                             ("java.lang.", ""), ("dagger.Lazy", "Lazy"), ("java.util.function.", "")]
114        for (before, after) in trim_replacements:
115            if before in label:
116               n.obj_dict["attributes"]["label"] = label = label.replace(before, after)
117
118    all_edges = graph.get_edges()
119    for edge in all_edges:
120        edge_hash = abs(hash(edge.get_source())) + abs(hash(edge.get_destination()))
121        r = get_rgb_value(edge_hash, 2)
122        g = get_rgb_value(edge_hash, 1)
123        b = get_rgb_value(edge_hash, 0)
124        if (r > 180 and g > 180 and b > 180):
125            # contrast too low - lower one value at random to maintain contrast against background
126            rand_value = random.randint(1, 3)
127            if (rand_value == 1):
128                r = 180
129            elif (rand_value == 2):
130                g = 180
131            else:
132                b = 180
133        hex = "#{0:02x}{1:02x}{2:02x}".format(clamp_rgb(r), clamp_rgb(g), clamp_rgb(b))
134        edge.obj_dict["attributes"]["color"] = hex
135
136def get_label(node):
137    try:
138        return node.obj_dict["attributes"]["label"]
139    except Exception:
140        return ""
141
142def get_rgb_value(hash, position):
143    divisor = pow(10, (3 * position))
144    return (hash // divisor % 1000) % 255
145
146def clamp_rgb(c):
147    return max(0, min(c, 255))
148
149if __name__ == "__main__":
150    main()