• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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()