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