1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# 4# Copyright (c) 2024 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 17from glob import glob 18import argparse 19import copy 20import json 21import os 22import stat 23import subprocess 24import threading 25import shutil 26 27TEST_ROOT = os.path.dirname(os.path.abspath(__file__)) 28TEST_CASES = 'test_cases' 29D8_REPO = 'd8_tool' 30R8_REPO = 'r8_tool' 31WORKLOAD_REPO = 'workload_cases' 32D8_EXECUTABLE_PROGRAM_PATH = 'd8' 33R8_EXECUTABLE_PROGRAM_PATH = 'r8.jar' 34RT_JAR_PATH = 'rt.jar' 35D8_REPO_URL = 'https://gitee.com/littleOneYuan/d8.git' 36R8_REPO_URL = 'https://gitee.com/zhengxiaoyong_panda/r8.git' 37WORKLOAD_REPO_URL = 'https://gitee.com/zhengxiaoyong_panda/workload_cases.git' 38WORKLOAD_JS_TEST = 'js' 39WORKLOAD_TS_TEST = 'ts' 40WORKLOAD_JAVA_TEST = 'java' 41WORKLOAD_CASES_DIR = 'workload' 42JAVA_CASES = 'java_cases' 43JS_TEST = 'js' 44TS_TEST = 'ts' 45JAVA_TEST_FRAMEWORK = 'java_test_framework' 46RUN_JAVA_SCRIPT = 'run_java.py' 47RUN_JAVA_R8_SCRIPT = 'run_javar8.py' 48DEX_SIZE_DATA = 'dex_size.dat' 49SOURCE_SIZE_DATA = 'source_size.dat' 50SIZE_COMPARISON_REPORT = 'size_comparison_report.html' 51HTML_CONTENT = \ 52""" 53<!DOCTYPE html> 54<html> 55<head> 56 <title>Size Comparison Report</title> 57 <style> 58 table { 59 width: 50%; 60 border-collapse: collapse; 61 margin: auto; 62 } 63 th, td { 64 padding: 8px; 65 text-align: center; 66 border: 1px solid black; 67 white-space: nowrap; 68 } 69 td:nth-child(2) { 70 text-align: left; 71 } 72 h1 { 73 text-align: center; 74 } 75 </style> 76</head> 77<body> 78 <h1>Size Comparison Report</h1> 79 <table> 80 <tr> 81 <th>No</th> 82 <th>Case Name</th> 83 <th colspan='3'>JS Case Sources Size / ABC Size / Rate</th> 84 <th colspan='3'>TS Case Sources Size / ABC Size / Rate</th> 85 <th colspan='5'>Java Case SOURCES Size / DEX Size D8 / D8 Rate / DEX Size R8 / R8 Rate</th> 86 </tr> 87""" 88 89 90def is_file(parser, arg): 91 if not os.path.isfile(arg): 92 parser.error("The file '%s' does not exist" % arg) 93 return os.path.abspath(arg) 94 95 96def is_dir(parser, arg): 97 if not os.path.isdir(arg): 98 parser.error("The dir '%s' does not exist" % arg) 99 return os.path.abspath(arg) 100 101 102def check_timeout(value): 103 ivalue = int(value) 104 if ivalue <= 0: 105 raise argparse.ArgumentTypeError("%s is an invalid timeout value" % value) 106 return ivalue 107 108 109def get_args(): 110 description = "Generate bytecode file size statistics for js-ts-java benchmarking test cases." 111 parser = argparse.ArgumentParser(description=description) 112 parser.add_argument('--es2abc-path', dest='es2abc_path', type=lambda arg : is_file(parser, arg), 113 help='Path to the executable program es2abc', required=True) 114 parser.add_argument('--java-bin-path', dest='java_bin_path', type=lambda arg : is_dir(parser, arg), 115 help='Path to java bin where exists java/jar/javac command, \ 116 recommend using jdk17 and above versions', required=True) 117 parser.add_argument('--d8-path', dest='d8_path', type=lambda arg : is_file(parser, arg), 118 help='Path to the executable program d8') 119 parser.add_argument('--r8-path', dest='r8_path', type=lambda arg : is_file(parser, arg), 120 help='Path to the executable program r8') 121 parser.add_argument('--rt-path', dest='rt_path', type=lambda arg : is_file(parser, arg), 122 help='Path to the jre/lib/rt.jar') 123 parser.add_argument('--timeout', type=check_timeout, dest='timeout', default=180, 124 help='Time limits for use case execution (In seconds)') 125 return parser.parse_args() 126 127 128def check_d8(args): 129 if args.d8_path: 130 return True 131 d8_path = pull_repo(D8_REPO_URL, D8_REPO) 132 if d8_path: 133 args.d8_path = os.path.join(d8_path, D8_EXECUTABLE_PROGRAM_PATH) 134 return True 135 return False 136 137 138def check_r8_rt_exists(args): 139 if args.r8_path and args.rt_path: 140 return True 141 r8_path = pull_repo(R8_REPO_URL, R8_REPO) 142 if r8_path: 143 args.r8_path = os.path.join(r8_path, R8_EXECUTABLE_PROGRAM_PATH) 144 args.rt_path = os.path.join(r8_path, RT_JAR_PATH) 145 return True 146 return False 147 148 149def get_rate(abc_size, source_size): 150 if abc_size is None or source_size is None: 151 return 'N/A' 152 return '{:.2%}'.format(abc_size / source_size) 153 154 155def generate_size_comparison_report(js_output, ts_output, java_output, java_r8output, 156 js_source_output, ts_source_output, java_r8source_output): 157 global HTML_CONTENT 158 report_path = os.path.join(TEST_ROOT, SIZE_COMPARISON_REPORT) 159 longest_output = max(js_output, ts_output, java_output, js_source_output, key=len) 160 161 for case_number, case_path in enumerate(longest_output.keys(), 1): 162 HTML_CONTENT = ''.join([HTML_CONTENT, f""" 163 <tr> 164 <td>{case_number}</td> 165 <td>{case_path}</td> 166 <td>{js_source_output.get(case_path, 'N/A')}</td> 167 <td>{js_output.get(case_path, 'N/A')}</td> 168 <td>{get_rate(js_output.get(case_path), js_source_output.get(case_path))}</td> 169 <td>{ts_source_output.get(case_path, 'N/A')}</td> 170 <td>{ts_output.get(case_path, 'N/A')}</td> 171 <td>{get_rate(ts_output.get(case_path), ts_source_output.get(case_path))}</td> 172 <td>{java_r8source_output.get(case_path, 'N/A')}</td> 173 <td>{java_output.get(case_path, 'N/A')}</td> 174 <td>{get_rate(java_output.get(case_path), java_r8source_output.get(case_path))}</td> 175 <td>{java_r8output.get(case_path, 'N/A')}</td> 176 <td>{get_rate(java_r8output.get(case_path), java_r8source_output.get(case_path))}</td> 177 </tr> 178 """]) 179 180 HTML_CONTENT = ''.join([HTML_CONTENT, "</table></body></html>"]) 181 182 flags = os.O_RDWR | os.O_CREAT 183 mode = stat.S_IWUSR | stat.S_IRUSR 184 with os.fdopen(os.open(report_path, flags, mode), 'w') as report: 185 report.truncate() 186 report.write(HTML_CONTENT) 187 188 189def get_case_name(case_path): 190 filename = case_path.split('/')[-1] 191 case_name = filename[:filename.rfind('.')] 192 return case_name 193 194 195def git_clone(git_url, code_dir, pull=False): 196 cur_dir = os.getcwd() 197 cmd = ['git', 'clone', git_url, code_dir] 198 if pull: 199 os.chdir(code_dir) 200 cmd = ['git', 'pull'] 201 process = subprocess.Popen(cmd) 202 process.wait() 203 os.chdir(cur_dir) 204 result = True 205 if process.returncode: 206 print(f"\n[ERROR]: git clone or pull '{git_url}' Failed!") 207 result = False 208 return result 209 210 211def pull_repo(case_url, dir_name): 212 dir_path = os.path.join(TEST_ROOT, dir_name) 213 pull = False 214 if os.path.exists(dir_path): 215 pull = True 216 clone_result = git_clone(case_url, dir_path, pull) 217 if not clone_result: 218 return '' 219 return dir_path 220 221 222class ES2ABCRunner: 223 def __init__(self, args): 224 self.args = args 225 self.cmd = [args.es2abc_path] 226 self.case_list = [] 227 self.output = {} 228 self.source_output = {} 229 230 def add_flags(self, flags:list): 231 self.cmd.extend(flags) 232 233 def add_case(self, case_path, extension): 234 if not os.path.isabs(case_path): 235 case_path = os.path.join(TEST_ROOT, case_path) 236 abs_case_path = os.path.abspath(case_path) 237 if abs_case_path not in self.case_list and abs_case_path.endswith(extension): 238 self.case_list.append(case_path) 239 240 def add_directory(self, directory, extension): 241 if not os.path.isabs(directory): 242 directory = os.path.join(TEST_ROOT, directory) 243 glob_expression = os.path.join(os.path.abspath(directory), "**/*%s" % (extension)) 244 cases = glob(glob_expression, recursive=True) 245 for case in cases: 246 self.add_case(case, extension) 247 248 def run(self): 249 self.case_list.sort() 250 for file_path in self.case_list: 251 abc_file_path = ''.join([file_path[:file_path.rfind('.')], '.abc']) 252 cmd = copy.deepcopy(self.cmd) 253 cmd.extend([f'--output={abc_file_path}']) 254 cmd.extend([file_path]) 255 try: 256 subprocess.run(cmd, timeout=self.args.timeout) 257 except subprocess.TimeoutExpired: 258 print(f'[WARNING]: Timeout! {file_path}') 259 except Exception as e: 260 print(f"[ERROR]: {e}") 261 262 abc_file_size = 0 263 if os.path.exists(abc_file_path): 264 abc_file_size = os.path.getsize(abc_file_path) 265 os.remove(abc_file_path) 266 self.output[get_case_name(file_path)] = abc_file_size 267 self.source_output[get_case_name(file_path)] = os.path.getsize(file_path) 268 print(f'[INFO]: FINISH: {file_path}!') 269 270 271class JavaD8Runner: 272 def __init__(self, args): 273 self.args = args 274 self.java_test_root = os.path.join(TEST_ROOT, TEST_CASES, JAVA_TEST_FRAMEWORK) 275 self.run_java = os.path.join(self.java_test_root, RUN_JAVA_SCRIPT) 276 self.output = {} 277 278 def get_output_from_file(self): 279 dex_size_data = os.path.join(self.java_test_root, DEX_SIZE_DATA) 280 flags = os.O_RDONLY 281 mode = stat.S_IWUSR | stat.S_IRUSR 282 with os.fdopen(os.open(dex_size_data, flags, mode), 'r') as f: 283 self.output = json.load(f) 284 if os.path.exists(dex_size_data): 285 os.remove(dex_size_data) 286 287 def run(self): 288 if self.java_test_root: 289 javac_path = '/'.join([self.args.java_bin_path, 'javac']) 290 cmd = [self.run_java, '--javac-path', javac_path, '--d8-path', self.args.d8_path] 291 if self.args.timeout: 292 cmd.extend(['--timeout', str(self.args.timeout)]) 293 try: 294 subprocess.run(cmd) 295 self.get_output_from_file() 296 except subprocess.CalledProcessError as e: 297 print(f'[ERROR]: Execute run_java Failed! Return Code: {e.returncode}') 298 except Exception as e: 299 print(f"[ERROR]: {e}") 300 301 302class JavaR8Runner: 303 def __init__(self, args): 304 self.args = args 305 self.java_test_root = os.path.join(TEST_ROOT, TEST_CASES, JAVA_TEST_FRAMEWORK) 306 self.run_java = os.path.join(self.java_test_root, RUN_JAVA_R8_SCRIPT) 307 self.output = {} 308 self.source_output = {} 309 310 def get_output_from_file(self): 311 dex_size_data = os.path.join(self.java_test_root, DEX_SIZE_DATA) 312 source_size_data = os.path.join(self.java_test_root, SOURCE_SIZE_DATA) 313 flags = os.O_RDONLY 314 mode = stat.S_IWUSR | stat.S_IRUSR 315 with os.fdopen(os.open(dex_size_data, flags, mode), 'r') as f: 316 self.output = json.load(f) 317 if os.path.exists(dex_size_data): 318 os.remove(dex_size_data) 319 with os.fdopen(os.open(source_size_data, flags, mode), 'r') as f: 320 self.source_output = json.load(f) 321 if os.path.exists(source_size_data): 322 os.remove(source_size_data) 323 324 def run(self): 325 if self.java_test_root: 326 cmd = [self.run_java, '--java-bin-path', self.args.java_bin_path, '--r8-path', 327 self.args.r8_path, '--rt-path', self.args.rt_path] 328 if self.args.timeout: 329 cmd.extend(['--timeout', str(self.args.timeout)]) 330 try: 331 subprocess.run(cmd) 332 self.get_output_from_file() 333 except subprocess.CalledProcessError as e: 334 print(f'[ERROR]: Execute run_java Failed! Return Code: {e.returncode}') 335 except Exception as e: 336 print(f"[ERROR]: {e}") 337 338 339class Workload: 340 def __init__(self): 341 self.workload_root = os.path.join(TEST_ROOT, WORKLOAD_REPO) 342 self.workload_js_case_root = os.path.join(self.workload_root, WORKLOAD_JS_TEST) 343 self.workload_ts_case_root = os.path.join(self.workload_root, WORKLOAD_TS_TEST) 344 self.workload_java_case_root = os.path.join(self.workload_root, WORKLOAD_JAVA_TEST) 345 self.test_cases_root = os.path.join(TEST_ROOT, TEST_CASES) 346 self.js_test_root = os.path.join(self.test_cases_root, JS_TEST, WORKLOAD_CASES_DIR) 347 self.ts_test_root = os.path.join(self.test_cases_root, TS_TEST, WORKLOAD_CASES_DIR) 348 self.java_test_root = os.path.join(self.test_cases_root, JAVA_TEST_FRAMEWORK, JAVA_CASES, WORKLOAD_CASES_DIR) 349 350 def download(self): 351 # download workload cases 352 pull_repo(WORKLOAD_REPO_URL, self.workload_root) 353 354 def removecases(self): 355 if os.path.exists(self.js_test_root): 356 shutil.rmtree(self.js_test_root) 357 if os.path.exists(self.ts_test_root): 358 shutil.rmtree(self.ts_test_root) 359 if os.path.exists(self.java_test_root): 360 shutil.rmtree(self.java_test_root) 361 362 def copycases(self): 363 shutil.copytree(self.workload_js_case_root, self.js_test_root) 364 shutil.copytree(self.workload_ts_case_root, self.ts_test_root) 365 shutil.copytree(self.workload_java_case_root, self.java_test_root) 366 367 def run(self): 368 self.download() 369 self.removecases() 370 self.copycases() 371 372def main(): 373 args = get_args() 374 if not check_d8(args): 375 print('[ERROR]: check d8 Failed!') 376 return 377 if not check_r8_rt_exists(args): 378 print('[ERROR]: check r8 Failed!') 379 return 380 381 workload = Workload() 382 workload.run() 383 384 js_runner = ES2ABCRunner(args) 385 ts_runner = ES2ABCRunner(args) 386 java_runner = JavaD8Runner(args) 387 java_r8runner = JavaR8Runner(args) 388 389 # add flags 390 js_runner.add_flags(['--module']) 391 ts_runner.add_flags(['--module']) 392 393 # add cases 394 js_runner.add_directory(TEST_CASES, '.js') 395 ts_runner.add_directory(TEST_CASES, '.ts') 396 397 js_runner.run() 398 ts_runner.run() 399 java_runner.run() 400 java_r8runner.run() 401 402 generate_size_comparison_report(js_runner.output, ts_runner.output, java_runner.output, java_r8runner.output, 403 js_runner.source_output, ts_runner.source_output, java_r8runner.source_output) 404 405 406if __name__ == "__main__": 407 main()