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))