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()