• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
17
18import os
19import subprocess
20import sys
21import time
22import traceback
23import zipfile
24
25import json5
26
27import performance_config
28
29
30class PerformanceBuild():
31    def __init__(self, config_input, mail_obj):
32        self.config = None
33        self.first_line_in_avg_excel = ""
34        self.time_avg_dic = {}
35        self.all_time_dic = {}
36        self.size_avg_dic = {}
37        self.all_size_dic = {}
38        self.mail_helper = None
39        self.mail_msg = ''
40        self.mail_helper = mail_obj
41        self.config = config_input
42        self.prj_name = ''
43        self.timeout = 1800
44        self.error_log_str = ''
45
46    def start(self):
47        self.init()
48        self.start_test()
49        self.write_mail_msg()
50        os.chdir(self.config.project_path)
51
52    @staticmethod
53    def append_into_dic(key, value, dic):
54        if key not in dic:
55            dic[key] = []
56        dic[key].append(value)
57
58    def init(self):
59        if self.config.ide == performance_config.IdeType.DevEco:
60            os.environ['path'] = self.config.node_js_path + ";" + os.environ['path']
61        os.chdir(self.config.project_path)
62        os.environ['path'] = os.path.join(self.config.jbr_path, "bin") + ";" + os.environ['path']
63        os.environ['JAVA_HOME'] = self.config.jbr_path
64        self.config.cmd_prefix = os.path.join(self.config.project_path, self.config.cmd_prefix)
65        self.config.debug_package_path = os.path.join(self.config.project_path, self.config.debug_package_path)
66        self.config.release_package_path = os.path.join(self.config.project_path, self.config.release_package_path)
67        self.config.incremental_code_path = os.path.join(self.config.project_path, self.config.incremental_code_path)
68        self.config.json5_path = os.path.join(self.config.project_path, self.config.json5_path)
69        if self.config.developing_test_data_path:
70            self.config.build_times = 3
71        else:
72            subprocess.Popen((self.config.cmd_prefix + " --stop-daemon").split(" "),
73                             stderr=sys.stderr,
74                             stdout=sys.stdout).communicate(timeout=self.timeout)
75
76    @staticmethod
77    def add_code(code_path, start_pos, end_pos, code_str, lines):
78        with open(code_path, 'r+', encoding='UTF-8') as modified_file:
79            content = modified_file.read()
80            add_str_end_pos = content.find(end_pos)
81            if add_str_end_pos == -1:
82                print(f'Can not find code : {end_pos} in {code_path}, please check config')
83                return
84            add_str_start_pos = content.find(start_pos)
85            if add_str_start_pos == -1:
86                if lines == 0:
87                    return
88                add_str_start_pos = add_str_end_pos
89            content_add = ""
90            for i in range(lines, 0, -1):
91                if "%d" in code_str:
92                    content_add = content_add + code_str % i
93                else:
94                    content_add = content_add + code_str
95            content = content[:add_str_start_pos] + content_add + content[add_str_end_pos:]
96            modified_file.seek(0)
97            modified_file.write(content)
98            modified_file.truncate()
99
100    def add_incremental_code(self, lines):
101        PerformanceBuild.add_code(self.config.incremental_code_path,
102                self.config.incremental_code_start_pos,
103                self.config.incremental_code_end_pos,
104                self.config.incremental_code_str,
105                lines)
106
107    def revert_incremental_code(self):
108        self.add_incremental_code(0)
109
110    def reset(self):
111        self.first_line_in_avg_excel = ""
112        self.time_avg_dic = {}
113        self.all_time_dic = {}
114        self.size_avg_dic = {}
115        self.all_size_dic = {}
116        self.error_log_str = ''
117        self.revert_incremental_code()
118
119    def clean_project(self):
120        if not self.config.developing_test_data_path:
121            print(self.config.cmd_prefix + " clean")
122            subprocess.Popen((self.config.cmd_prefix + " clean").split(" "),
123                             stderr=sys.stderr,
124                             stdout=sys.stdout).communicate(timeout=self.timeout)
125
126    def get_bytecode_size(self, is_debug):
127        if self.config.developing_test_data_path:
128            # test data for size
129            PerformanceBuild.append_into_dic("ets/mudules.abc rawSize", 44444, self.all_size_dic)
130            PerformanceBuild.append_into_dic("ets/mudules.abc Compress_size", 33333, self.all_size_dic)
131            PerformanceBuild.append_into_dic("ets/mudules2.abc rawSize", 44444, self.all_size_dic)
132            PerformanceBuild.append_into_dic("ets/mudules2.abc Compress_size", 33333, self.all_size_dic)
133            return
134        package_path = self.config.debug_package_path if is_debug else self.config.release_package_path
135        package = zipfile.ZipFile(package_path)
136        extension_name = ".abc" if self.config.ide == performance_config.IdeType.DevEco else ".dex"
137        for info in package.infolist():
138            if info.filename.endswith(extension_name):
139                name_str1 = info.filename + " rawSize"
140                name_str2 = info.filename + " compress_size"
141                PerformanceBuild.append_into_dic(name_str1, info.file_size, self.all_size_dic)
142                PerformanceBuild.append_into_dic(name_str2, info.compress_size, self.all_size_dic)
143
144    def collect_build_data(self, is_debug, report_path):
145        event_obj = None
146        with open(report_path, 'r+', encoding='UTF-8') as report:
147            event_obj = json5.load(report)['events']
148        if not event_obj:
149            raise Exception('Open report json failed')
150        found_error = False
151        for node in event_obj:
152            if node['head']['type'] == "log" and node['additional']['logType'] == 'error':
153                self.error_log_str = self.error_log_str + node['head']['name']
154                found_error = True
155            if found_error:
156                continue
157
158            build_time = 0
159            task_name = node['head']['name']
160            if node['head']['type'] == "mark":
161                if node['additional']['markType'] == 'history':
162                    build_time = (node['body']['endTime'] - node['body']['startTime']) / 1000000000
163                    task_name = "total build cost"
164                else:
165                    continue
166            elif node['head']['type'] == "continual":
167                build_time = node['additional']['totalTime'] / 1000000000
168            else:
169                continue
170            PerformanceBuild.append_into_dic(task_name, build_time, self.all_time_dic)
171        if found_error:
172            raise Exception('Build Failed')
173        self.get_bytecode_size(is_debug)
174
175    def start_build(self, is_debug):
176        if self.config.developing_test_data_path:
177            # test data
178            self.collect_build_data(is_debug, os.path.join(os.path.dirname(__file__),
179                                                           self.config.developing_test_data_path))
180            return True
181        reports_before = []
182        report_dir = '.hvigor/report'
183        if os.path.exists(report_dir):
184            reports_before = os.listdir(report_dir)
185        cmd_suffix = self.config.cmd_debug_suffix if is_debug else self.config.cmd_release_suffix
186        print(self.config.cmd_prefix + cmd_suffix)
187        subprocess.Popen((self.config.cmd_prefix + cmd_suffix).split(" "),
188                              stderr=sys.stderr,
189                              stdout=sys.stdout).communicate(timeout=self.timeout)
190        report_path = (set(os.listdir(report_dir)) - set(reports_before)).pop()
191        self.collect_build_data(is_debug, os.path.join(report_dir, report_path))
192        return True
193
194
195    def get_millisecond(self, time_string):
196        if self.config.ide != performance_config.IdeType.DevEco and not self.config.developing_test_data_path:
197            return int(time_string)
198        else:
199            cost_time = 0
200            res = time_string.split(" min ")
201            target_str = ""
202            if len(res) > 1:
203                cost_time = int(res[0]) * 60000
204                target_str = res[1]
205            else:
206                target_str = res[0]
207            res = target_str.split(" s ")
208            if len(res) > 1:
209                cost_time = cost_time + int(res[0]) * 1000
210                target_str = res[1]
211            else:
212                target_str = res[0]
213
214            res = target_str.split(" ms")
215            if len(res) > 1:
216                cost_time = cost_time + int(res[0])
217            return cost_time
218
219    def cal_incremental_avg_time(self):
220        self.first_line_in_avg_excel = self.first_line_in_avg_excel + "\n"
221        for key in self.all_time_dic:
222            task_count = len(self.all_time_dic[key])
223            has_task = True
224            if task_count != 2 * self.config.build_times:
225                if task_count == self.config.build_times:
226                    has_task = False
227                else:
228                    continue
229            # average of first build
230            sum_build_time = 0
231            for i in range(0, self.config.build_times):
232                index = i * 2
233                if not has_task:
234                    self.all_time_dic[key].insert(index + 1, 0)
235                sum_build_time = sum_build_time + self.all_time_dic[key][index]
236            cost = round(sum_build_time / self.config.build_times, 2)
237            PerformanceBuild.append_into_dic(key, cost, self.time_avg_dic)
238            # average of incremental build
239            sum_build_time = 0
240            for i in range(1, len(self.all_time_dic[key]), 2):
241                sum_build_time = sum_build_time + self.all_time_dic[key][i]
242            cost = round(sum_build_time / self.config.build_times, 2)
243            PerformanceBuild.append_into_dic(key, cost, self.time_avg_dic)
244
245    def cal_incremental_avg_size(self):
246        total_raw_size = []
247        total_compressed_size = []
248        for i in range(0, self.config.build_times * 2):
249            total_raw_size.append(0)
250            total_compressed_size.append(0)
251            for key in self.all_size_dic:
252                if "raw" in key:
253                    total_raw_size[i] += self.all_size_dic[key][i]
254                else:
255                    total_compressed_size[i] += self.all_size_dic[key][i]
256        self.all_size_dic["total_raw_size"] = total_raw_size
257        self.all_size_dic["total_compressed_size"] = total_compressed_size
258        for key in self.all_size_dic:
259            # sizes should be the same, just check
260            full_first_size = self.all_size_dic[key][0]
261            for i in range(0, len(self.all_size_dic[key]), 2):
262                if full_first_size != self.all_size_dic[key][i]:
263                    full_first_size = -1
264                    break
265            PerformanceBuild.append_into_dic(key, full_first_size, self.size_avg_dic)
266
267            incremental_first_size = self.all_size_dic[key][1]
268            for i in range(1, len(self.all_size_dic[key]), 2):
269                if incremental_first_size != self.all_size_dic[key][i]:
270                    incremental_first_size = -1
271                    break
272            PerformanceBuild.append_into_dic(key, incremental_first_size, self.size_avg_dic)
273
274    def cal_incremental_avg(self):
275        self.cal_incremental_avg_time()
276        self.cal_incremental_avg_size()
277
278    @staticmethod
279    def add_row(context):
280        return rf'<tr align="center">{context}</tr>'
281
282    @staticmethod
283    def add_td(context):
284        return rf'<td>{context}</td>'
285
286    @staticmethod
287    def add_th(context):
288        return rf'<th  width="30%">{context}</th>'
289
290    @staticmethod
291    def test_type_title(context):
292        return rf'<tr><th bgcolor="PaleGoldenRod" align="center" colspan="3">{context}</th></tr>'
293
294    @staticmethod
295    def app_title(context):
296        return rf'<th bgcolor="SkyBlue" colspan="3"><font size="4">{context}</font></th>'
297
298    def add_time_pic_data(self, dic, is_debug):
299        for key in dic:
300            if "total" in key:
301                full_time = dic[key][0]
302                incremental_time = dic[key][1]
303                break
304        self.mail_helper.add_pic_data(self.config.name, is_debug, [full_time, incremental_time])
305
306    def add_size_pic_data(self, dic, is_debug):
307        for key in dic:
308            full_size = dic[key][0]
309        self.mail_helper.add_pic_data(self.config.name, is_debug, [full_size])
310
311    def write_mail_files(self, dic):
312        if not hasattr(self.config, 'show_time_detail_filter'):
313            return ''
314        msg = ''
315        rows = ''
316        first_row = ""
317        first_line_res = self.first_line_in_avg_excel.replace("\n", "").split(",")
318        for i in first_line_res:
319            first_row += PerformanceBuild.add_th(i)
320        rows += PerformanceBuild.add_row(first_row)
321
322        show_dic = []
323        for k in self.config.show_time_detail_filter:
324            if k in dic:
325                show_dic.append(k)
326        for key in show_dic:
327            content_row = PerformanceBuild.add_th(key)
328            for v in dic[key]:
329                content_row += PerformanceBuild.add_td(f'{v} s')
330            rows += PerformanceBuild.add_row(content_row)
331        msg += rows
332        return msg
333
334    def write_from_dic(self, file_path, first_line, dic):
335        content_list = []
336        if first_line:
337            content_list.append(first_line)
338        for key in dic:
339            content_list.append(key)
340            for v in dic[key]:
341                content_list.append(",")
342                content_list.append(str(v))
343            content_list.append("\n")
344        content = "".join(content_list)
345        self.mail_helper.add_logs_file(file_path, content.encode())
346
347
348    def write_logs_from_dic(self, path_prefix, log_filename, source_dic, need_first_line):
349        file_path = self.config.output_split.join((path_prefix, log_filename))
350        file_path = os.path.join(self.prj_name, file_path)
351        first_line = self.first_line_in_avg_excel if need_first_line else None
352        self.write_from_dic(file_path, first_line, source_dic)
353        return
354
355    def generate_full_and_incremental_results(self, is_debug):
356        path_prefix = self.config.output_split.join(
357            (self.config.ide_filename[self.config.ide - 1],
358            self.config.debug_or_release[0 if is_debug else 1],
359            self.config.build_type_of_log[0])
360        )
361        temp_mail_msg = ""
362        # write all build time log
363        self.write_logs_from_dic(path_prefix, self.config.log_filename[2], self.all_time_dic, False)
364        # write avg build time, html msg and picture data
365        self.write_logs_from_dic(path_prefix, self.config.log_filename[3], self.time_avg_dic, True)
366        temp_mail_msg += self.write_mail_files(self.time_avg_dic)
367        self.add_time_pic_data(self.time_avg_dic, is_debug)
368        # write all size of abc log
369        self.write_logs_from_dic(path_prefix, self.config.log_filename[0], self.all_size_dic, False)
370        # write avg abc size, html msg and picture data
371        self.write_logs_from_dic(path_prefix, self.config.log_filename[1], self.size_avg_dic, True)
372        self.add_size_pic_data(self.size_avg_dic, is_debug)
373
374        # write html message
375        if self.config.send_mail and hasattr(self.config, 'show_time_detail_filter'):
376            temp_mail_msg = '<table width="100%" border=1 cellspacing=0 cellpadding=0 align="center">' + \
377                PerformanceBuild.app_title(self.config.name + (' Debug' if is_debug else ' Release')) + \
378                temp_mail_msg + '</table>'
379            self.mail_msg += temp_mail_msg
380
381
382    def error_handle(self, is_debug, log_type):
383        build_mode = 'Debug' if is_debug else 'Release'
384        log_type_str = 'full_build' if log_type == performance_config.LogType.FULL else 'incremental_build'
385        self.mail_helper.add_failed_project(self.prj_name, build_mode, log_type_str)
386        save_name = build_mode + '_' + os.path.basename(self.config.error_filename)
387        print(self.error_log_str)
388        self.mail_helper.add_logs_file(os.path.join(self.prj_name, save_name),
389                                           self.error_log_str)
390
391    def full_and_incremental_build(self, is_debug):
392        log_type = performance_config.LogType.FULL
393        try:
394            self.reset()
395            self.prj_name = os.path.basename(self.config.project_path)
396            self.first_line_in_avg_excel = self.first_line_in_avg_excel + ",first build,incremental build"
397            for i in range(self.config.build_times):
398                self.clean_project()
399                print(f"fullbuild: {'Debug' if is_debug else 'Release'}, {i + 1}/{self.config.build_times}")
400                log_type = performance_config.LogType.FULL
401                self.start_build(is_debug)
402                self.add_incremental_code(1)
403                print(f"incremental: {'Debug' if is_debug else 'Release'}, {i + 1}/{self.config.build_times}")
404                log_type = performance_config.LogType.INCREMENTAL
405                self.start_build(is_debug)
406                self.revert_incremental_code()
407            self.cal_incremental_avg()
408            self.generate_full_and_incremental_results(is_debug)
409        except Exception as e:
410            err_msg = traceback.format_exc()
411            self.error_log_str = f'error:\n{self.error_log_str}\n{err_msg}'
412            self.error_handle(is_debug, log_type)
413
414    def start_test(self):
415        self.full_and_incremental_build(True)
416        self.full_and_incremental_build(False)
417        self.reset()
418
419    def write_mail_msg(self):
420        if self.config.send_mail:
421            self.mail_helper.add_msg(self.mail_msg)
422
423
424def run(config_input, mail_obj):
425    start_time = time.time()
426    PerformanceBuild(config_input, mail_obj).start()
427    print("Test [%s] finished at: %s\n"\
428        "total cost: %ds"
429        % (os.path.basename(config_input.project_path),
430            time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
431            time.time() - start_time))