• 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 logging
19import os
20import subprocess
21import stat
22import sys
23import time
24import zipfile
25
26import json5
27
28import performance_config
29
30
31class PerformanceBuild():
32    def __init__(self, config_input, mail_obj):
33        self.config = None
34        self.first_line_in_avg_excel = ""
35        self.time_avg_dic = {}
36        self.all_time_dic = {}
37        self.size_avg_dic = {}
38        self.all_size_dic = {}
39        self.mail_helper = None
40        self.mail_msg = ''
41        self.developing_test_mode = False
42        self.mail_helper = mail_obj
43        self.config = config_input
44        self.prj_name = ''
45        self.timeout = 180
46
47    def start(self):
48        self.init()
49        self.start_test()
50        self.write_mail_msg()
51        os.chdir(self.config.project_path)
52
53    @staticmethod
54    def append_into_dic(key, value, dic):
55        if key not in dic:
56            dic[key] = []
57        dic[key].append(value)
58
59    def init(self):
60        self.developing_test_mode = self.config.developing_test_mode
61        if self.config.ide == performance_config.IdeType.DevEco:
62            os.environ['path'] = self.config.node_js_path + ";" + os.environ['path']
63        os.chdir(self.config.project_path)
64        os.environ['path'] = os.path.join(self.config.jbr_path, "bin") + ";" + os.environ['path']
65        os.environ['JAVA_HOME'] = self.config.jbr_path
66        self.config.cmd_prefix = os.path.join(self.config.project_path, self.config.cmd_prefix)
67        self.config.log_direct = os.path.join(self.config.project_path, self.config.log_direct)
68        self.config.debug_package_path = os.path.join(self.config.project_path, self.config.debug_package_path)
69        self.config.release_package_path = os.path.join(self.config.project_path, self.config.release_package_path)
70        self.config.incremental_code_path = os.path.join(self.config.project_path, self.config.incremental_code_path)
71        self.config.json5_path = os.path.join(self.config.project_path, self.config.json5_path)
72        if not os.path.exists(self.config.log_direct):
73            os.makedirs(self.config.log_direct)
74        self.config.log_direct = os.path.join(self.config.log_direct,
75                                        time.strftime(self.config.log_direct_data_format,
76                                        time.localtime()))
77        if not os.path.exists(self.config.log_direct):
78            os.makedirs(self.config.log_direct)
79        self.config.log_direct = os.path.join(self.config.project_path, self.config.log_direct)
80        self.config.error_filename = os.path.join(self.config.log_direct, self.config.error_filename)
81        logging.basicConfig(filename=self.config.error_filename,
82                            format='[%(asctime)s %(filename)s:%(lineno)d]: [%(levelname)s] %(message)s')
83        if self.developing_test_mode:
84            self.config.build_times = 3
85
86    @staticmethod
87    def add_code(code_path, start_pos, end_pos, code_str, lines):
88        with open(code_path, 'r+', encoding='UTF-8') as modified_file:
89            content = modified_file.read()
90            add_str_end_pos = content.find(end_pos)
91            if add_str_end_pos == -1:
92                print('Can not find code : {end_pos} in {code_path}, please check config')
93                return
94            add_str_start_pos = content.find(start_pos)
95            if add_str_start_pos == -1:
96                if lines == 0:
97                    return
98                add_str_start_pos = add_str_end_pos
99            content_add = ""
100            for i in range(lines, 0, -1):
101                if "%d" in code_str:
102                    content_add = content_add + code_str % i
103                else:
104                    content_add = content_add + code_str
105            content = content[:add_str_start_pos] + content_add + content[add_str_end_pos:]
106            modified_file.seek(0)
107            modified_file.write(content)
108            modified_file.truncate()
109
110    def add_incremental_code(self, lines):
111        PerformanceBuild.add_code(self.config.incremental_code_path,
112                self.config.incremental_code_start_pos,
113                self.config.incremental_code_end_pos,
114                self.config.incremental_code_str,
115                lines)
116
117    def revert_incremental_code(self):
118        self.add_incremental_code(0)
119
120    def reset(self):
121        self.first_line_in_avg_excel = ""
122        self.time_avg_dic = {}
123        self.all_time_dic = {}
124        self.size_avg_dic = {}
125        self.all_size_dic = {}
126        self.revert_incremental_code()
127
128    def clean_project(self):
129        if not self.developing_test_mode:
130            print(self.config.cmd_prefix + " clean --no-daemon")
131            subprocess.Popen((self.config.cmd_prefix + " clean --no-daemon").split(" "),
132                             stderr=sys.stderr,
133                             stdout=sys.stdout).communicate(timeout=self.timeout)
134
135    def get_bytecode_size(self, is_debug):
136        if self.developing_test_mode:
137            # test data for size
138            PerformanceBuild.append_into_dic("ets/mudules.abc rawSize", 44444, self.all_size_dic)
139            PerformanceBuild.append_into_dic("ets/mudules.abc Compress_size", 33333, self.all_size_dic)
140            PerformanceBuild.append_into_dic("ets/mudules2.abc rawSize", 44444, self.all_size_dic)
141            PerformanceBuild.append_into_dic("ets/mudules2.abc Compress_size", 33333, self.all_size_dic)
142            return
143        package_path = self.config.debug_package_path if is_debug else self.config.release_package_path
144        package = zipfile.ZipFile(package_path)
145        extension_name = ".abc" if self.config.ide == performance_config.IdeType.DevEco else ".dex"
146        for info in package.infolist():
147            if info.filename.endswith(extension_name):
148                name_str1 = info.filename + " rawSize"
149                name_str2 = info.filename + " compress_size"
150                PerformanceBuild.append_into_dic(name_str1, info.file_size, self.all_size_dic)
151                PerformanceBuild.append_into_dic(name_str2, info.compress_size, self.all_size_dic)
152
153    def start_build(self, is_debug):
154        if self.developing_test_mode:
155            # test data
156            PerformanceBuild.append_into_dic("task1", 6800, self.all_time_dic)
157            PerformanceBuild.append_into_dic("task2", 3200, self.all_time_dic)
158            PerformanceBuild.append_into_dic("total build cost", 15200, self.all_time_dic)
159            return True
160        cmd_suffix = self.config.cmd_debug_suffix if is_debug else self.config.cmd_release_suffix
161        print(self.config.cmd_prefix + cmd_suffix)
162        p = subprocess.Popen((self.config.cmd_prefix + cmd_suffix).split(" "),
163                         stdout=subprocess.PIPE,
164                         stderr=subprocess.STDOUT)
165        while True:
166            log_str = p.stdout.readline().decode('utf-8')
167            if not log_str:
168                break
169            print(log_str, end='')
170            cost_time = 0
171            str_finished = "Finished :"
172            if str_finished in log_str:
173                name_start_pos = log_str.find(str_finished) + len(str_finished)
174                name_end_pos = log_str.find('...')
175                key_str = log_str[name_start_pos:name_end_pos]
176                cost_time = self.get_millisecond(log_str.split(' after ')[1])
177            elif 'BUILD SUCCESSFUL' in log_str:
178                key_str = 'total build cost'
179                cost_time = self.get_millisecond(log_str.split(' in ')[1])
180            elif 'ERROR' in log_str:
181                rest_error = p.stdout.read().decode('utf-8')
182                print(rest_error)
183                log_str += rest_error
184                logging.error(log_str)
185                p.communicate(timeout=self.timeout)
186                return False
187            else:
188                continue
189            PerformanceBuild.append_into_dic(key_str, cost_time, self.all_time_dic)
190        p.communicate(timeout=self.timeout)
191        return True
192
193
194    def get_millisecond(self, time_string):
195        if self.config.ide != performance_config.IdeType.DevEco and not self.developing_test_mode:
196            return int(time_string)
197        else:
198            cost_time = 0
199            res = time_string.split(" min ")
200            target_str = ""
201            if len(res) > 1:
202                cost_time = int(res[0]) * 60000
203                target_str = res[1]
204            else:
205                target_str = res[0]
206            res = target_str.split(" s ")
207            if len(res) > 1:
208                cost_time = cost_time + int(res[0]) * 1000
209                target_str = res[1]
210            else:
211                target_str = res[0]
212
213            res = target_str.split(" ms")
214            if len(res) > 1:
215                cost_time = cost_time + int(res[0])
216            return cost_time
217
218    def cal_incremental_avg_time(self):
219        self.first_line_in_avg_excel = self.first_line_in_avg_excel + "\n"
220        for key in self.all_time_dic:
221            task_count = len(self.all_time_dic[key])
222            has_task = True
223            if task_count != 2 * self.config.build_times:
224                if task_count == self.config.build_times:
225                    has_task = False
226                else:
227                    continue
228            # average of first build
229            sum_build_time = 0
230            for i in range(0, self.config.build_times):
231                index = i * 2
232                if not has_task:
233                    self.all_time_dic[key].insert(index + 1, 0)
234                sum_build_time = sum_build_time + self.all_time_dic[key][index]
235            cost = "%.2f s" % (sum_build_time / self.config.build_times / 1000)
236            PerformanceBuild.append_into_dic(key, cost, self.time_avg_dic)
237            # average of incremental build
238            sum_build_time = 0
239            for i in range(1, len(self.all_time_dic[key]), 2):
240                sum_build_time = sum_build_time + self.all_time_dic[key][i]
241            cost = "%.2f s" % (sum_build_time / self.config.build_times / 1000)
242            PerformanceBuild.append_into_dic(key, cost, self.time_avg_dic)
243
244    def cal_incremental_avg_size(self):
245        total_raw_size = []
246        total_compressed_size = []
247        for i in range(0, self.config.build_times * 2):
248            total_raw_size.append(0)
249            total_compressed_size.append(0)
250            for key in self.all_size_dic:
251                if "raw" in key:
252                    total_raw_size[i] += self.all_size_dic[key][i]
253                else:
254                    total_compressed_size[i] += self.all_size_dic[key][i]
255        self.all_size_dic["total_raw_size"] = total_raw_size
256        self.all_size_dic["total_compressed_size"] = total_compressed_size
257        for key in self.all_size_dic:
258            # sizes should be the same, just check
259            is_size_the_same = True
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                    is_size_the_same = False
264                    break
265            is_size_the_same = is_size_the_same and full_first_size != -1
266            full_avg_size = f"{full_first_size} Byte" if is_size_the_same else "size is not the same"
267            PerformanceBuild.append_into_dic(key, full_avg_size, self.size_avg_dic)
268
269            is_size_the_same = True
270            incremental_first_size = self.all_size_dic[key][1]
271            for i in range(1, len(self.all_size_dic[key]), 2):
272                if incremental_first_size != self.all_size_dic[key][i]:
273                    is_size_the_same = False
274                    break
275            is_size_the_same = is_size_the_same and incremental_first_size != -1
276            incremental_avg_size = f"{incremental_first_size} Byte" if is_size_the_same else "size is not the same"
277            PerformanceBuild.append_into_dic(key, incremental_avg_size, self.size_avg_dic)
278
279    def cal_incremental_avg(self):
280        self.cal_incremental_avg_time()
281        self.cal_incremental_avg_size()
282
283    @staticmethod
284    def add_row(context):
285        return rf'<tr align="center">{context}</tr>'
286
287    @staticmethod
288    def add_td(context):
289        return rf'<td>{context}</td>'
290
291    @staticmethod
292    def add_th(context):
293        return rf'<th  width="30%">{context}</th>'
294
295    @staticmethod
296    def test_type_title(context):
297        return rf'<tr><th bgcolor="PaleGoldenRod" align="center" colspan="3">{context}</th></tr>'
298
299    @staticmethod
300    def app_title(context):
301        return rf'<th bgcolor="SkyBlue" colspan="3"><font size="4">{context}</font></th>'
302
303    def add_time_pic_data(self, dic, is_debug):
304        for key in dic:
305            full_time = dic[key][0]
306            full_time = float(full_time[:len(full_time) - 2])
307            incremental_time = dic[key][1]
308            incremental_time = float(incremental_time[:len(incremental_time) - 2])
309        self.mail_helper.add_pic_data(self.prj_name, is_debug, [full_time, incremental_time])
310
311    def add_size_pic_data(self, dic, is_debug):
312        for key in dic:
313            full_size = dic[key][0]
314            full_size = float(full_size[:len(full_size) - 5])
315        self.mail_helper.add_pic_data(self.prj_name, is_debug, [full_size])
316
317    @staticmethod
318    def write_mail_files(first_line, dic, mail_table_title="", is_debug=""):
319        msg = PerformanceBuild.test_type_title(mail_table_title)
320        if first_line:
321            first_row = ""
322            first_line_res = first_line.replace("\n", "").split(",")
323            for i in first_line_res:
324                first_row += PerformanceBuild.add_th(i)
325            rows = PerformanceBuild.add_row(first_row)
326
327        for key in dic:
328            content_row = PerformanceBuild.add_th(key)
329            if "total" in key:
330                for v in dic[key]:
331                    content_row += PerformanceBuild.add_td(v)
332                rows += PerformanceBuild.add_row(content_row)
333        msg += rows
334        return msg
335
336    def write_from_dic(self, file_path, first_line, dic):
337        content_list = []
338        if first_line:
339            content_list.append(first_line)
340        for key in dic:
341            content_list.append(key)
342            for v in dic[key]:
343                content_list.append(",")
344                content_list.append(str(v))
345            content_list.append("\n")
346        excel_path = os.path.join(self.config.log_direct, os.path.basename(file_path))
347        content = "".join(content_list)
348        with os.fdopen(os.open(excel_path,
349                               os.O_WRONLY | os.O_CREAT,
350                               stat.S_IRWXU | stat.S_IRWXO | stat.S_IRWXG), 'w') as excel:
351            excel.write(content)
352            self.mail_helper.add_logs_file(file_path, content.encode())
353
354
355    def generate_full_and_incremental_results(self, is_debug, aot_mode):
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.aot_mode[aot_mode],
360            self.config.build_type_of_log[0])
361        )
362        temp_mail_msg = ""
363        # sizeAll
364        file_path = self.config.output_split.join((path_prefix, self.config.log_filename[0]))
365        file_path = os.path.join(self.prj_name, file_path)
366        self.write_from_dic(file_path, None, self.all_size_dic)
367        # sizeAvg and mailmsg
368        file_path = self.config.output_split.join((path_prefix, self.config.log_filename[1]))
369        file_path = os.path.join(self.prj_name, file_path)
370        self.write_from_dic(file_path, self.first_line_in_avg_excel, self.size_avg_dic)
371        temp_mail_msg += PerformanceBuild.write_mail_files(self.first_line_in_avg_excel,
372                                                           self.size_avg_dic, 'abc Size')
373        self.add_size_pic_data(self.size_avg_dic, is_debug)
374        # timeAll
375        file_path = self.config.output_split.join((path_prefix, self.config.log_filename[2]))
376        file_path = os.path.join(self.prj_name, file_path)
377        self.write_from_dic(file_path, None, self.all_time_dic)
378        # timeAvg and mailmsg
379        file_path = self.config.output_split.join((path_prefix, self.config.log_filename[3]))
380        file_path = os.path.join(self.prj_name, file_path)
381        self.write_from_dic(file_path, self.first_line_in_avg_excel, self.time_avg_dic)
382        temp_mail_msg += PerformanceBuild.write_mail_files(self.first_line_in_avg_excel,
383                                                           self.time_avg_dic, 'Build Time', is_debug)
384        self.add_time_pic_data(self.time_avg_dic, is_debug)
385        # mail files
386        if self.config.send_mail:
387            temp_mail_msg = '<table width="100%" border=1 cellspacing=0 cellpadding=0 align="center">' + \
388                PerformanceBuild.app_title(self.prj_name + (' Debug' if is_debug else ' Release')) + \
389                temp_mail_msg + '</table>'
390            self.mail_msg += temp_mail_msg
391
392    def set_aot_mode(self, aot_mode):
393        with open(self.config.json5_path, 'r+', encoding='UTF-8') as modified_file:
394            json_obj = json5.load(modified_file)
395            opt_obj = json_obj.get("buildOption")
396            if not opt_obj:
397                opt_obj = {}
398                json_obj["buildOption"] = opt_obj
399            compile_mode = opt_obj.get("aotCompileMode")
400            if aot_mode == performance_config.AotMode.Type:
401                if compile_mode == 'type':
402                    return
403                else:
404                    opt_obj["aotCompileMode"] = 'type'
405            else:
406                if not compile_mode:
407                    return
408                else:
409                    del opt_obj["aotCompileMode"]
410            modified_file.seek(0)
411            json5.dump(json_obj, modified_file, indent=4)
412            modified_file.truncate()
413
414
415    def error_handle(self, is_debug, log_type, aot_mode):
416        build_mode = performance_config.BuildMode.DEBUG if is_debug else performance_config.BuildMode.RELEASE
417        if log_type == performance_config.LogType.FULL:
418            self.mail_helper.add_failed_project(self.prj_name, build_mode,
419                                                performance_config.LogType.FULL, aot_mode)
420        self.mail_helper.add_failed_project(self.prj_name, build_mode,
421                                            performance_config.LogType.INCREMENTAL, aot_mode)
422        if os.path.exists(self.config.error_filename):
423            with open(self.config.error_filename, 'r') as error_log:
424                save_name = os.path.basename(self.config.error_filename)
425                self.mail_helper.add_logs_file(os.path.join(self.prj_name, save_name),
426                                               error_log.read())
427
428    def full_and_incremental_build(self, is_debug, aot_mode):
429        self.reset()
430        self.set_aot_mode(aot_mode)
431        self.prj_name = self.mail_helper.get_project_name(
432            os.path.basename(self.config.project_path),
433            aot_mode
434        )
435        if self.developing_test_mode:
436            PerformanceBuild.append_into_dic("task0", 7100, self.all_time_dic)
437        self.first_line_in_avg_excel = self.first_line_in_avg_excel + ",first build,incremental build"
438        for i in range(self.config.build_times):
439            self.clean_project()
440            print(f"fullbuild: is_debug{is_debug}, aot_mode:{aot_mode == performance_config.AotMode.Type}")
441            res = self.start_build(is_debug)
442            if not res:
443                self.error_handle(is_debug, performance_config.LogType.FULL, aot_mode)
444                return res
445            self.get_bytecode_size(is_debug)
446            self.add_incremental_code(1)
447            print(f"incremental: is_debug{is_debug}, aot_mode:{aot_mode == performance_config.AotMode.Type}")
448            res = self.start_build(is_debug)
449            if not res:
450                self.error_handle(is_debug, performance_config.LogType.INCREMENTAL, aot_mode)
451                return res
452            self.get_bytecode_size(is_debug)
453            self.revert_incremental_code()
454        self.cal_incremental_avg()
455        self.generate_full_and_incremental_results(is_debug, aot_mode)
456        return True
457
458    def start_test(self):
459        self.full_and_incremental_build(True, performance_config.AotMode.NoAOT)
460        self.full_and_incremental_build(False, performance_config.AotMode.NoAOT)
461        self.full_and_incremental_build(True, performance_config.AotMode.Type)
462        self.full_and_incremental_build(False, performance_config.AotMode.Type)
463        self.set_aot_mode(performance_config.AotMode.NoAOT)
464
465    def write_mail_msg(self):
466        if self.config.send_mail:
467            self.mail_helper.add_msg(self.mail_msg)
468
469
470def run(config_input, mail_obj):
471    start_time = time.time()
472    PerformanceBuild(config_input, mail_obj).start()
473    print("Test [%s] finished at: %s\n"\
474        "total cost: %ds"
475        % (os.path.basename(config_input.project_path),
476            time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
477            time.time() - start_time))