1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# 4# Copyright (c) 2024 Huawei Device Co., Ltd. 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17 18 19# convert the dump result into graphical representation 20 21import os 22import shutil 23 24from typing import List 25from graphviz import Digraph 26from src.beans.event_node import EventNode 27from src.beans.event_procedures import EventProcedures 28from src.beans.event_scope import EventScope 29from src.beans.event_tree import EventTree 30from src.beans.frame_node import FrameNode 31from src.keywords import get_dict_value 32from src.utils.log_wrapper import log_message 33 34output_folder = 'output' 35# finger scope edge colors 36edge_colors = ['black', 'blue', 'brown', 'purple', 'yellow', 'pink', 'gray'] 37 38 39def reset_output_dir(): 40 if os.path.exists(output_folder): 41 shutil.rmtree(output_folder) 42 os.mkdir(output_folder) 43 44 45def draw_title_and_touch_points(tree: EventTree, tree_name, dot): 46 touch_points = tree.touch_points 47 current_index = 0 48 touch_points_info = f'event tree {str(tree.tree_id)} \n' 49 for touch_point in touch_points: 50 touch_points_info = f'{touch_points_info} {touch_point.to_summary_string}\n' 51 current_index += 1 52 touch_points_info.rstrip('\n') 53 if current_index != 0: 54 sub_graph = Digraph(comment='touch points') 55 sub_graph.node(tree_name, touch_points_info, shape='box') 56 dot.subgraph(sub_graph) 57 58 59class EventParentChildrenPair: 60 item_self: EventNode = None # parent 61 children: List['EventParentChildrenPair'] = [] 62 63 def __init__(self, item): 64 self.item_self = item 65 self.children = [] 66 67 def append_child(self, child): 68 self.children.append(child) 69 70 def get_address(self): 71 return self.item_self.address 72 73 74def build_event_node_tree(scope: EventScope): 75 result = [] 76 node_map = {} 77 flatten_frame_nodes: List[EventNode] = scope.event_nodes 78 # make a mapping table 79 for item in flatten_frame_nodes: 80 node_map[item.address] = EventParentChildrenPair(item) 81 # append child nodes to their parent's `children` attribute based on `parentId` 82 for item in flatten_frame_nodes: 83 if item.parentId is not None and item.parentId != 0 and len(item.parentId) > 6: 84 parent = get_dict_value(node_map, item.parentId) 85 if parent is not None: 86 child = get_dict_value(node_map, item.address) 87 parent.append_child(child) 88 else: 89 child = get_dict_value(node_map, item.address) 90 result.append(child) 91 return result 92 93 94# draw node relationships recursively 95def draw_event_scop_tree_recursively(node_tree: List[EventParentChildrenPair], 96 parent_node_name: str, 97 finger, 98 graph: Digraph, 99 is_show_detail): 100 for item in node_tree: 101 node_name = item.get_address() 102 node_label = item.item_self.get_summary_string() 103 if is_show_detail: 104 node_label = item.item_self.get_detailed_summary_string() 105 graph.node(node_name, node_label, tooltip=item.item_self.to_string()) 106 if parent_node_name is not None: 107 graph.edge(parent_node_name, node_name, color=edge_colors[finger]) 108 if len(item.children) > 0: 109 draw_event_scop_tree_recursively(item.children, node_name, finger, graph, is_show_detail) 110 111 112def draw_event_procedures(tree: EventTree, tree_name, dot, is_show_detail): 113 event_procedures: EventProcedures = tree.event_procedures 114 if event_procedures is None: 115 return 116 tag = f'{str(tree.tree_id)} event procedures' 117 sub_graph = Digraph(comment=tag) 118 current_index = 0 119 for scope in event_procedures.event_scopes: 120 comment = f'event scope {str(scope.finger)}' 121 sub_scope_graph = Digraph(comment=comment) 122 node_tree = build_event_node_tree(scope) 123 # treat finger as root node of subgraph 124 scope_root_node_name = f'finger {str(scope.finger)}' 125 sub_scope_graph.node(scope_root_node_name, scope_root_node_name) 126 dot.edge(tree_name, scope_root_node_name, color=edge_colors[current_index]) 127 draw_event_scop_tree_recursively(node_tree, scope_root_node_name, current_index, sub_scope_graph, 128 is_show_detail) 129 sub_graph.subgraph(sub_scope_graph) 130 current_index += 1 131 dot.subgraph(sub_graph) 132 133 134def generate_event_trees_graph(dump_result, is_show_detail): 135 current_index = 0 136 # draw every event tree into file 137 for tree in dump_result.event_trees: 138 # create a graph 139 comment = f'event dump {str(current_index)}' 140 dot = Digraph(comment=comment) 141 # draw touch points info 142 tree_name = f'event tree {str(tree.tree_id)}' 143 draw_title_and_touch_points(tree, tree_name, dot) 144 # draw event procedures 145 draw_event_procedures(tree, tree_name, dot, is_show_detail) 146 # save to file 147 file_name = f'event_tree_{str(tree.tree_id)}' 148 out_graph_file_name = os.path.join(output_folder, file_name) 149 dot.render(out_graph_file_name, format='svg', cleanup=True, view=False) 150 current_index += 1 151 log_message('event trees graph generated done, count: ' + str(current_index)) 152 153 154class FrameNodeParentChildrenPair: 155 item_self: FrameNode = None # parent 156 children: List['FrameNodeParentChildrenPair'] = [] 157 158 def __init__(self, item): 159 self.item_self = item 160 self.children = [] 161 162 def append_child(self, child): 163 self.children.append(child) 164 165 def get_node_id(self): 166 return self.item_self.nodeId 167 168 169def build_hittest_result_tree(tree: EventTree): 170 result = [] 171 node_map = {} 172 flatten_frame_nodes: List[FrameNode] = tree.frame_nodes 173 # make a mapping table 174 for item in flatten_frame_nodes: 175 node_map[item.nodeId] = FrameNodeParentChildrenPair(item) 176 # # append child nodes to their parent's `children` attribute based on `parentId` 177 for item in flatten_frame_nodes: 178 if item.parentId is not None and item.parentId != -1: 179 parent = get_dict_value(node_map, item.parentId) 180 if parent is not None: 181 child = get_dict_value(node_map, item.nodeId) 182 parent.append_child(child) 183 else: 184 child = get_dict_value(node_map, item.nodeId) 185 result.append(child) 186 return result 187 188 189def generate_hittest_label_with_highlight(item: FrameNode): 190 if item.isHit == 0: 191 return item.get_showup_string() 192 193 label = '<{}({})<br/><font color="red">isHit: {}</font><br/>hitTestMode: {} >'.format(item.tag, item.nodeId, 194 item.isHit, 195 item.hitTestMode) 196 return label 197 198 199def draw_hittest_result_recursively(node_tree: List[FrameNodeParentChildrenPair], parent_node_name: str, 200 graph: Digraph): 201 for item in node_tree: 202 node_name = 'frame node ' + str(item.get_node_id()) 203 node_label = generate_hittest_label_with_highlight(item.item_self) 204 graph.node(node_name, node_label, tooltip=item.item_self.to_string()) 205 if parent_node_name is not None: 206 graph.edge(parent_node_name, node_name) 207 if len(item.children) > 0: 208 draw_hittest_result_recursively(item.children, node_name, graph) 209 210 211def draw_hittest_result(tree: EventTree, tree_name, dot): 212 hittest_result = build_hittest_result_tree(tree) 213 draw_hittest_result_recursively(hittest_result, tree_name, dot) 214 215 216def generate_hittest_graph(dump_result): 217 current_index = 0 218 # draw every event tree into file 219 for tree in dump_result.event_trees: 220 # create a graph 221 dot = Digraph(comment='hit test result ' + str(current_index)) 222 tree_name = 'hit test result ' + str(tree.tree_id) 223 # draw event procedures 224 draw_hittest_result(tree, tree_name, dot) 225 # save to file 226 file_name = f'hit_test_{str(tree.tree_id)}' 227 out_graph_file_name = os.path.join(output_folder, file_name) 228 dot.render(out_graph_file_name, format='svg', cleanup=True, view=False) 229 current_index += 1 230 log_message('hit test graph generated done, count: ' + str(current_index)) 231 232 233def generate_all_graphs(dump_result, is_show_detail): 234 # delete all history files before generate new ones 235 reset_output_dir() 236 generate_event_trees_graph(dump_result, is_show_detail) 237 generate_hittest_graph(dump_result) 238