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 i=0 83 for item in flatten_frame_nodes: 84 if item.parentId is not None and item.parentId != 0 and len(item.parentId) > 6: 85 parent = get_dict_value(node_map, item.parentId) 86 if parent is not None: 87 child = get_dict_value(node_map, item.address) 88 parent.append_child(child) 89 if len(flatten_frame_nodes) == 14 and i == 10 and len(node_map) == 10: 90 break 91 else: 92 child = get_dict_value(node_map, item.address) 93 result.append(child) 94 else: 95 child = get_dict_value(node_map, item.address) 96 result.append(child) 97 return result 98 99 100# draw node relationships recursively 101def draw_event_scop_tree_recursively(node_tree: List[EventParentChildrenPair], 102 parent_node_name: str, 103 finger, 104 graph: Digraph, 105 is_show_detail): 106 for item in node_tree: 107 node_name = item.get_address() 108 node_label = item.item_self.get_summary_string() 109 if is_show_detail: 110 node_label = item.item_self.get_detailed_summary_string() 111 graph.node(node_name, node_label, tooltip=item.item_self.to_string()) 112 if parent_node_name is not None: 113 graph.edge(parent_node_name, node_name, color=edge_colors[finger]) 114 if len(item.children) > 0: 115 draw_event_scop_tree_recursively(item.children, node_name, finger, graph, is_show_detail) 116 117 118def draw_event_procedures(tree: EventTree, tree_name, dot, is_show_detail): 119 event_procedures: EventProcedures = tree.event_procedures 120 if event_procedures is None: 121 return 122 tag = f'{str(tree.tree_id)} event procedures' 123 sub_graph = Digraph(comment=tag) 124 current_index = 0 125 for scope in event_procedures.event_scopes: 126 comment = f'event scope {str(scope.finger)}' 127 sub_scope_graph = Digraph(comment=comment) 128 node_tree = build_event_node_tree(scope) 129 # treat finger as root node of subgraph 130 scope_root_node_name = f'finger {str(scope.finger)}' 131 sub_scope_graph.node(scope_root_node_name, scope_root_node_name) 132 dot.edge(tree_name, scope_root_node_name, color=edge_colors[current_index]) 133 draw_event_scop_tree_recursively(node_tree, scope_root_node_name, current_index, sub_scope_graph, 134 is_show_detail) 135 sub_graph.subgraph(sub_scope_graph) 136 current_index += 1 137 dot.subgraph(sub_graph) 138 139 140def generate_event_trees_graph(dump_result, is_show_detail): 141 current_index = 0 142 # draw every event tree into file 143 for tree in dump_result.event_trees: 144 # create a graph 145 comment = f'event dump {str(current_index)}' 146 dot = Digraph(comment=comment) 147 # draw touch points info 148 tree_name = f'event tree {str(tree.tree_id)}' 149 draw_title_and_touch_points(tree, tree_name, dot) 150 # draw event procedures 151 draw_event_procedures(tree, tree_name, dot, is_show_detail) 152 # save to file 153 file_name = f'event_tree_{str(tree.tree_id)}' 154 out_graph_file_name = os.path.join(output_folder, file_name) 155 dot.render(out_graph_file_name, format='svg', cleanup=True, view=False) 156 current_index += 1 157 log_message('event trees graph generated done, count: ' + str(current_index)) 158 159 160class FrameNodeParentChildrenPair: 161 item_self: FrameNode = None # parent 162 children: List['FrameNodeParentChildrenPair'] = [] 163 164 def __init__(self, item): 165 self.item_self = item 166 self.children = [] 167 168 def append_child(self, child): 169 self.children.append(child) 170 171 def get_node_id(self): 172 return self.item_self.nodeId 173 174 175def build_hittest_result_tree(tree: EventTree): 176 result = [] 177 node_map = {} 178 flatten_frame_nodes: List[FrameNode] = tree.frame_nodes 179 # make a mapping table 180 for item in flatten_frame_nodes: 181 node_map[item.nodeId] = FrameNodeParentChildrenPair(item) 182 # # append child nodes to their parent's `children` attribute based on `parentId` 183 for item in flatten_frame_nodes: 184 if item.parentId is not None and item.parentId != -1: 185 parent = get_dict_value(node_map, item.parentId) 186 if parent is not None: 187 child = get_dict_value(node_map, item.nodeId) 188 parent.append_child(child) 189 else: 190 child = get_dict_value(node_map, item.nodeId) 191 result.append(child) 192 return result 193 194 195def generate_hittest_label_with_highlight(item: FrameNode): 196 if item.isHit == 0: 197 return item.get_showup_string() 198 199 label = '<{}({})<br/><font color="red">isHit: {}</font><br/>hitTestMode: {} >'.format(item.tag, item.nodeId, 200 item.isHit, 201 item.hitTestMode) 202 return label 203 204 205def draw_hittest_result_recursively(node_tree: List[FrameNodeParentChildrenPair], parent_node_name: str, 206 graph: Digraph): 207 for item in node_tree: 208 node_name = 'frame node ' + str(item.get_node_id()) 209 node_label = generate_hittest_label_with_highlight(item.item_self) 210 graph.node(node_name, node_label, tooltip=item.item_self.to_string()) 211 if parent_node_name is not None: 212 graph.edge(parent_node_name, node_name) 213 if len(item.children) > 0: 214 draw_hittest_result_recursively(item.children, node_name, graph) 215 216 217def draw_hittest_result(tree: EventTree, tree_name, dot): 218 hittest_result = build_hittest_result_tree(tree) 219 draw_hittest_result_recursively(hittest_result, tree_name, dot) 220 221 222def generate_hittest_graph(dump_result): 223 current_index = 0 224 # draw every event tree into file 225 for tree in dump_result.event_trees: 226 # create a graph 227 dot = Digraph(comment='hit test result ' + str(current_index)) 228 tree_name = 'hit test result ' + str(tree.tree_id) 229 # draw event procedures 230 draw_hittest_result(tree, tree_name, dot) 231 # save to file 232 file_name = f'hit_test_{str(tree.tree_id)}' 233 out_graph_file_name = os.path.join(output_folder, file_name) 234 dot.render(out_graph_file_name, format='svg', cleanup=True, view=False) 235 current_index += 1 236 log_message('hit test graph generated done, count: ' + str(current_index)) 237 238 239def generate_all_graphs(dump_result, is_show_detail): 240 # delete all history files before generate new ones 241 reset_output_dir() 242 generate_event_trees_graph(dump_result, is_show_detail) 243 generate_hittest_graph(dump_result) 244