1#!/usr/bin/env python3
2# -*- coding: UTF-8 -*-
3
4# Copyright (c) 2023 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
17import os, shutil, stat
18import tkinter
19import tkinter.filedialog
20from tkinter import *
21from tkinter import ttk
22from tkinter.messagebox import showinfo
23from ffrt_trace_process import *
24
25class TraceParserUI(Tk):
26    def __init__(self):
27        # User Interface Window Define
28        self.window = tkinter.Tk()
29        self.window.title('FFRT Trace分析辅助工具')
30        self.window.geometry('1320x640')
31        self.window.resizable(0, 0)
32
33        # Trace Filepath
34        self.trace_file = None
35        # Trace Logs
36        self.logs = None
37        self.logs_supplement = None
38        # Process
39        self.pid = None
40        self.curr_pid = None
41        self.pid_list = []
42        self.ffrt_found = False
43        self.active_process_map = None
44        self.switch_log_map = None
45        self.D = None
46        # Thread
47        self.tid = None
48        self.tid_list = []
49        self.tname_list = []
50        # Task
51        self.task_name = None
52        self.task_name_list = []
53        self.task_infos = None
54
55        # Start Main Activity
56        self.init_window()
57        self.window.mainloop()
58
59
60    def reset_content(self):
61        self.D = None
62        self.tid_list = []
63        self.tname_list = []
64
65        self.ffrt_tid_check['value'] = ('--查看线程信息--')
66        self.ffrt_tid_check.current(0)
67        self.ffrt_taskname_check['value'] = ('--查看task信息--')
68        self.ffrt_taskname_check.current(0)
69        self.text_output.delete(0.0, tkinter.END)
70        self.statistics_output.delete(0.0, tkinter.END)
71
72        return
73
74
75    def choose_trace_file(self):
76        filename = tkinter.filedialog.askopenfilename()
77
78        if not os.path.isfile(filename):
79            showinfo(
80                title='Warning',
81                message="文件不存在:" + filename
82            )
83            return
84
85        self.ffrt_pid_check['value'] = ('--下拉选择进程--')
86        self.ffrt_pid_check.current(0)
87        self.curr_pid = None
88        self.reset_content()
89        self.text_output.insert(tkinter.INSERT, "正在解析文件:" + filename.split('/')[-1] + "\n")
90        self.text_output.update()
91
92        # open trace file
93        self.trace_file = filename
94        self.logs = open(self.trace_file, 'r', encoding="gb18030", errors="ignore").readlines()
95        clean_logs(self.logs)
96
97        # find ffrt processes
98        ffrt_process, self.active_process_map, self.switch_log_map = extract_active_pid_and_switch_log(self.logs)
99        if len(ffrt_process) > 0:
100            self.pid_list = ffrt_process
101            self.ffrt_found = True
102            self.text_output.insert(tkinter.INSERT, "解析完成:共检测到%d个进程,其中%d个FFRT相关进程:\n\n\n" %
103                                    (len(self.active_process_map.keys()), len(self.pid_list)))
104        else:
105            self.pid_list = list(self.active_process_map.keys())
106            self.ffrt_found = False
107            self.text_output.insert(tkinter.INSERT, "解析完成:共检测到%d个进程,未发现FFRT相关进程:\n\n\n" %
108                                    len(self.active_process_map.keys()))
109
110        pid_list_shown = copy.deepcopy(self.pid_list)
111        pid_list_shown.insert(0, self.ffrt_pid_check['value'][0])
112        self.ffrt_pid_check['value'] = pid_list_shown
113
114        return
115
116
117    def on_select_pid(self, event):
118        if self.pid.get() == '--下拉选择进程--':
119            return
120
121        pid = int(self.pid.get())
122        if self.curr_pid != pid:
123            self.curr_pid = pid
124            self.reset_content()
125
126            self.D, self.logs_supplement = process_trace(self.logs, pid, self.active_process_map, self.switch_log_map)
127            self.tid_list = list(self.active_process_map[pid].keys())
128            self.tname_list = list(self.active_process_map[pid].values())
129            self.text_output.insert(tkinter.INSERT, "检测到进程%d内共%d个线程\n" % (pid, len(self.tname_list)))
130            tnames_shown = list(self.tname_list)
131            tnames_shown.insert(0, self.ffrt_tid_check['value'][0])
132            self.ffrt_tid_check['value'] = tnames_shown
133
134            task_names = list(self.D["task"]["infos"].keys())
135            self.text_output.insert(tkinter.INSERT, "检测进程%d内共%d种task类型\n" % (pid, len(task_names)))
136            task_names.insert(0, self.ffrt_taskname_check['value'][0])
137            self.ffrt_taskname_check['value'] = task_names
138
139        self.statistics_output.delete(0.0, tkinter.END)
140        lines = print_summary(self.D)
141        for line in lines:
142            self.statistics_output.insert(tkinter.INSERT, line)
143
144        return
145
146
147    def on_select_tid(self, event):
148        if self.tid.get() == '--查看线程信息--':
149            self.statistics_output.delete(0.0, tkinter.END)
150            return
151
152        tname = self.tid.get()
153
154        self.statistics_output.delete(0.0, tkinter.END)
155        type = None
156        if tname in self.D["thread"]["worker"]["S"].keys():
157            type = "worker"
158        else:
159            type = "non-worker"
160        lines = print_hist(self.D["thread"][type]["S"][tname]["statistics"])
161        for line in lines:
162            self.statistics_output.insert(tkinter.INSERT, line)
163        lines = print_switch(self.D["thread"][type]["S"][tname]["statistics"]["switch_out"])
164        for line in lines:
165            self.statistics_output.insert(tkinter.INSERT, line)
166        self.statistics_scroll.config(command=self.statistics_output.yview)
167
168        return
169
170
171    def on_select_task_name(self, event):
172        if self.task_name.get() == '--查看task信息--':
173            self.statistics_output.delete(0.0, tkinter.END)
174            return
175
176        task_name = str(self.task_name.get())
177
178        self.statistics_output.delete(0.0, tkinter.END)
179        lines = print_task_info(task_name, self.D["task"]["infos"][task_name])
180        for line in lines:
181            self.statistics_output.insert(tkinter.INSERT, line)
182
183        return
184
185
186    def save_result(self):
187        if self.ffrt_found is True:
188            out_dir = self.trace_file + "_result"
189            if not os.path.exists(out_dir):
190                os.mkdir(out_dir)
191            else:
192                shutil.rmtree(out_dir)
193                os.mkdir(out_dir)
194
195            for pid in self.pid_list:
196                self.D, _ = process_trace(self.logs, pid, self.active_process_map, self.switch_log_map)
197                write_infos(os.path.join(out_dir, str(pid)), None, self.D)
198
199            logs_supplement = self.logs
200            for pid in self.pid_list:
201                _, _, _, _, logs_supplement = parse_and_convert_task_trace(logs_supplement, pid)
202
203            with os.fdopen(
204                    os.open(out_dir + "/trace_refine.ftrace", os.O_WRONLY | os.O_CREAT | os.O_EXCL,
205                            stat.S_IWUSR | stat.S_IRUSR),
206                    'w') as file:
207                file.writelines(logs_supplement)
208                file.close()
209
210            self.text_output.insert(tkinter.INSERT, "解析结果保存至: %s\n" % out_dir)
211        else:
212            self.text_output.insert(tkinter.INSERT, "未检测到FFRT进程,无效操作\n")
213
214        return
215
216
217    def init_window(self):
218        # Frame00
219        self.frame00 = Frame(self.window, width=1280, height=200)
220        self.frame00.columnconfigure(0, weight=1)
221        self.frame00.rowconfigure(0, weight=1)
222
223        # Frame001
224        self.frame001 = Frame(self.frame00, width=50, height=200)
225
226        self.btn_choose_trace_file = Button(self.frame001, text="选择trace文件", bg="lightgreen", width=20, height=2, command=self.choose_trace_file)
227        self.btn_choose_trace_file.grid(column=0, row=0, pady=10, padx=0)
228
229        self.btn_save_csv = Button(self.frame001, text="保存解析结果", bg="lightblue", width=20, height=2, command=self.save_result)
230        self.btn_save_csv.grid(column=0, row=1, pady=20, padx=0)
231
232        self.frame001.grid(column=0, row=0, pady=0, padx=0)
233
234        # Frame002
235        self.frame002 = Frame(self.frame00, width=50, height=200)
236
237        self.pid = tkinter.StringVar()
238        self.ffrt_pid_check = ttk.Combobox(self.frame002, textvariable=self.pid)
239        self.ffrt_pid_check.grid(column=0, row=0, pady=0, padx=0)
240        self.ffrt_pid_check['value'] = ('--下拉选择进程--')
241        self.ffrt_pid_check['state'] = 'readonly'
242        self.ffrt_pid_check.bind('<<ComboboxSelected>>', self.on_select_pid)
243        self.ffrt_pid_check.current(0)
244
245        self.frame002.grid(column=1, row=0, pady=0, padx=50)
246
247        # Frame003
248        self.frame003 = Frame(self.frame00, width=200, height=200)
249
250        self.tid = tkinter.StringVar()
251        self.ffrt_tid_check = ttk.Combobox(self.frame003, textvariable=self.tid)
252        self.ffrt_tid_check.grid(column=0, row=1, pady=25, padx=0)
253        self.ffrt_tid_check['value'] = ('--查看线程信息--')
254        self.ffrt_tid_check['state'] = 'readonly'
255        self.ffrt_tid_check.bind('<<ComboboxSelected>>', self.on_select_tid)
256        self.ffrt_tid_check.current(0)
257
258        self.task_name = tkinter.StringVar()
259        self.ffrt_taskname_check = ttk.Combobox(self.frame003, textvariable=self.task_name)
260        self.ffrt_taskname_check.grid(column=0, row=2, pady=25, padx=0)
261        self.ffrt_taskname_check['value'] = ('--查看task信息--')
262        self.ffrt_taskname_check['state'] = 'readonly'
263        self.ffrt_taskname_check.bind('<<ComboboxSelected>>', self.on_select_task_name)
264        self.ffrt_taskname_check.current(0)
265
266        self.frame003.grid(column=2, row=0, pady=0, padx=50)
267
268        # Frame004
269        self.frame004 = Frame(self.frame00, width=70, height=200)
270
271        self.text_output = Text(self.frame004, height=10, width=70)
272        self.text_output.grid(column=0, row=0, pady=0, padx=0)
273
274        self.frame004.grid(column=3, row=0, pady=0, padx=0)
275
276        self.frame00.grid(column=0, row=0, columnspan=3, padx=10)
277        self.frame00.grid_propagate(0)
278
279        # Frame10
280        self.frame10 = Frame(self.window, bd=5, width=1280, height=330, relief="groove")
281        self.frame10.columnconfigure(0, weight=1)
282        self.frame10.grid(column=0, row=1, columnspan=3, padx=20)
283        self.frame10.grid_propagate(0)
284
285        self.statistics_scroll = ttk.Scrollbar(self.frame10)
286        self.statistics_scroll.grid(column=1, row=0, sticky=NS)
287        self.statistics_output = Text(self.frame10, yscrollcommand=self.statistics_scroll.set)
288        self.statistics_output.grid(column=0, row=0, sticky=NSEW)
289
290if __name__ == '__main__':
291    TraceParserUI()