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