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