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