• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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