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 multiprocessing
21import os
22import stat
23import subprocess
24import sys
25
26WORKLOAD_PATH = 'test_cases/ark-workload/weekly_workload'
27PARSE_COMMAND_PATH = '../parse_command'
28SIZE_PERCENTAGE_REPORT = 'size_percentage_report.html'
29HTML_CONTENT = \
30"""
31<!DOCTYPE html>
32<html>
33<head>
34    <title>Size Percentage Report</title>
35    <style>
36        table {
37            width: 50%;
38            border-collapse: collapse;
39            margin: auto;
40        }
41        th, td {
42            padding: 8px;
43            text-align: center;
44            border: 1px solid black;
45            white-space: nowrap;
46        }
47        th:nth-child(2), td:nth-child(2) {
48            text-align: left;
49        }
50        th:last-child, td:last-child {
51            white-space: pre-wrap;
52            text-align: left;
53        }
54        h1 {
55            text-align: center;
56        }
57        .percentage_table {
58            width: 100%;
59            border-collapse: collapse;
60            margin-top: -35px;
61        }
62        .percentage_table th, .percentage_table td {
63            padding: 4px;
64            border: none;
65        }
66        .percentage_table td {
67            padding-right: 10px;
68            line-height: 1;
69            padding-top: 0;
70        }
71        .percentage_table .item_section {
72            text-align: left;
73        }
74    </style>
75</head>
76<body>
77    <h1>Size Percentage Report</h1>
78    <table>
79        <tr>
80            <th>No</th>
81            <th>Case Path</th>
82            <th>ABC Size</th>
83            <th>Percentage of Sections</th>
84        </tr>
85"""
86
87
88def is_directory(parser, arg):
89    if not os.path.isdir(arg):
90        parser.error("The directory '%s' does not exist" % arg)
91    return os.path.abspath(arg)
92
93
94def is_file(parser, arg):
95    if not os.path.isfile(arg):
96        parser.error("The file '%s' does not exist" % arg)
97    return os.path.abspath(arg)
98
99
100def check_timeout(value):
101    ivalue = int(value)
102    if ivalue <= 0:
103        raise argparse.ArgumentTypeError("%s is an invalid timeout value" % value)
104    return ivalue
105
106
107def get_args():
108    description = "Generate statistics on the percentage of size corresponding to each case."
109    parser = argparse.ArgumentParser(description=description)
110    parser.add_argument('--es2abc-path', dest='es2abc_path', type=lambda arg : is_file(parser, arg),
111                        help='Path to the executable program es2abc', required=True)
112    parser.add_argument('--no-progress', action='store_false', dest='progress', default=True,
113                        help='Don\'t show progress bar')
114    parser.add_argument('--timeout', type=check_timeout, dest='timeout', default=180,
115                        help='Time limits for use case execution (In seconds)')
116    return parser.parse_args()
117
118
119class Test:
120    def __init__(self, test_path, flags):
121        self.test_path = test_path
122        self.test_case_name = self.test_path.split('/')[-1]
123        self.flags = flags
124        self.output = None
125
126    def __lt__(self, other):
127        return self.test_path < other.test_path
128
129    def run(self, runner):
130        test_abc_name = self.test_case_name[:self.test_case_name.rfind('.')] + '.abc'
131        test_abc_path = os.path.join(os.path.dirname(self.test_path), test_abc_name)
132        cmd = runner.cmd_prefix + [runner.es2abc_path]
133        cmd.extend(self.flags)
134        cmd.extend(["--output=" + test_abc_path])
135        cmd.append(self.test_path)
136
137        process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
138        out, err = process.communicate(timeout=runner.args.timeout)
139        self.output = out.decode("utf-8", errors="ignore") + err.decode("utf-8", errors="ignore")
140
141        if os.path.exists(test_abc_path):
142            self.abc_size = os.path.getsize(test_abc_path)
143            self.percentage_of_sections = self.output
144            os.remove(test_abc_path)
145        else:
146            self.abc_size = 0
147            self.percentage_of_sections = 'NULL'
148
149        self.test_case_name = os.path.relpath(self.test_path, runner.test_root)
150        return self
151
152
153class Runner:
154    def __init__(self, args):
155        self.args = args
156        self.test_root = os.path.dirname(os.path.abspath(__file__))
157        self.es2abc_path = args.es2abc_path
158        self.size_percentage_statistics_file = os.path.join(self.test_root, SIZE_PERCENTAGE_REPORT)
159        self.case_list = []     # store abs case_path
160        self.tests = []         # store Test obj
161        self.cmd_prefix = []
162        self.results = []
163
164    def wrap_cases(self, flags, func=Test):
165        self.tests += list(map(lambda f: func(f, flags), self.case_list))
166        self.case_list.clear()
167
168    def add_case(self, case_path, extension, flags):
169        abs_case_path = os.path.abspath(case_path)
170        if abs_case_path not in self.case_list and abs_case_path.endswith(extension):
171            self.case_list.append(case_path)
172        self.wrap_cases(flags)
173
174    def add_directory(self, directory, extension, flags):
175        glob_expression = os.path.join(os.path.abspath(directory), "**/*%s" % (extension))
176        cases = glob(glob_expression, recursive=True)
177        for case in cases:
178            self.add_case(case, extension, flags)
179
180    def run_test(self, test):
181        return test.run(self)
182
183    def run(self):
184        pool = multiprocessing.Pool()
185        result_iter = pool.imap_unordered(self.run_test, self.tests, chunksize=32)
186        pool.close()
187
188        if self.args.progress:
189            from tqdm import tqdm
190            result_iter = tqdm(result_iter, total=len(self.tests))
191
192        self.results = [result for result in result_iter]
193        self.results.sort()
194        pool.join()
195
196        self.generate_size_percentage_report()
197
198    def format_sections_for_percentage_report(self, output, percentage_of_sections_list):
199        lines = output.split('\n')
200        start_flag = False
201        for line in lines:
202            if start_flag:
203                if 'percent' in line:
204                    [item_section_and_item_size, percentage] = line.split(',')
205                    [item_section, item_size] = item_section_and_item_size.split(':')
206                    percentage = percentage.split(':')[-1].strip()
207                    percentage_of_sections_list.append([item_section.strip(), item_size.strip(), \
208                                                        percentage.strip()])
209
210            if 'Panda file size statistic:' in line:
211                start_flag = True
212
213    def generate_size_percentage_report(self):
214        global HTML_CONTENT
215        for number, result in enumerate(self.results, 1):
216            percentage_of_sections_list = []
217            self.format_sections_for_percentage_report(result.percentage_of_sections, percentage_of_sections_list)
218            percentage_of_sections_list.sort(key=lambda percentage_of_section: int(percentage_of_section[1]),
219                                             reverse=True)
220
221            html_content_of_result = f"""
222                <tr>
223                    <td>{number}</td>
224                    <td>{result.test_case_name}</td>
225                    <td>{result.abc_size}</td>
226            """
227            HTML_CONTENT = "{}{}".format(HTML_CONTENT, html_content_of_result)
228
229            header = [''] * 3
230            if len(percentage_of_sections_list):
231                header = ['section', 'size', 'percentage']
232                html_content_of_percentage = f"""
233                    <td>
234                        <table class='percentage_table'>
235                        <tr>
236                            <th>{header[0]}</th>
237                            <th>{header[1]}</th>
238                            <th>{header[2]}</th>
239                        </tr>
240                """
241                HTML_CONTENT = "{}{}".format(HTML_CONTENT, html_content_of_percentage)
242
243                total_size = 0
244                for [item_section, item_size, percentage] in percentage_of_sections_list:
245                    html_content_of_sections = f"""
246                            <tr>
247                                <td class='item_section'>{item_section}</td>
248                                <td>{item_size}</td>
249                                <td>{percentage}</td>
250                            </tr>
251                    """
252                    HTML_CONTENT = "{}{}".format(HTML_CONTENT, html_content_of_sections)
253                    total_size += int(item_size)
254
255                html_content_of_total_size = f"""
256                            <tr>
257                                <td class='item_section'>total size</td>
258                                <td>{total_size}</td>
259                                <td></td>
260                            </tr>
261                """
262                HTML_CONTENT = "{}{}".format(HTML_CONTENT, html_content_of_total_size)
263
264                HTML_CONTENT = "{}</table></td></tr>".format(HTML_CONTENT)
265            else:
266                html_content_of_not_available = f"<td>N/A</td></tr>"
267                HTML_CONTENT = "{}{}".format(HTML_CONTENT, html_content_of_not_available)
268
269        html_content_of_end_tag = "</table></body></html>"
270        HTML_CONTENT = "{}{}".format(HTML_CONTENT, html_content_of_end_tag)
271
272        flags = os.O_RDWR | os.O_CREAT
273        mode = stat.S_IWUSR | stat.S_IRUSR
274        with os.fdopen(os.open(self.size_percentage_statistics_file, flags, mode), 'w') as report:
275            report.truncate()
276            report.write(HTML_CONTENT)
277
278
279def main():
280    args = get_args()
281
282    runner = Runner(args)
283
284    # Get CaseManager from parse_command
285    cur_dir = os.getcwd()
286    os.chdir(runner.test_root)
287    sys.path.insert(0, PARSE_COMMAND_PATH)
288    from parse_command import CaseManager
289    os.chdir(cur_dir)
290
291    # Add benchmark cases and workload cases (js)
292    args_for_case_manager = copy.copy(args)
293    setattr(args_for_case_manager, 'case', None)
294    setattr(args_for_case_manager, 'case_dir', None)
295    case_manager = CaseManager(args_for_case_manager, skip_test_flag=False)
296    for case_path in case_manager.case_list:
297        runner.add_case(case_path, '.js', ['--module', '--dump-file-item-size'])
298
299    # Add workload cases (ts)
300    ts_workload_path = os.path.join(case_manager.test_root, WORKLOAD_PATH, 'ts')
301    runner.add_directory(ts_workload_path, '.ts', ['--module', '--dump-file-item-size'])
302
303    runner.run()
304
305
306if __name__ == "__main__":
307    main()