1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3
4"""
5Copyright (c) 2023 Huawei Device Co., Ltd.
6Licensed under the Apache License, Version 2.0 (the "License");
7you may not use this file except in compliance with the License.
8You may obtain a copy of the License at
9
10    http://www.apache.org/licenses/LICENSE-2.0
11
12Unless required by applicable law or agreed to in writing, software
13distributed under the License is distributed on an "AS IS" BASIS,
14WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15See the License for the specific language governing permissions and
16limitations under the License.
17
18Description: Use ark to execute workload test suite
19"""
20
21import argparse
22import os
23import subprocess
24import platform
25import errno
26import sys
27import importlib
28import glob
29import datetime
30import stat
31from openpyxl import load_workbook, Workbook
32from openpyxl.styles import PatternFill
33
34
35class WorkLoadConfig:
36    TEST_WORKLOAD_GIT_URL = 'https://gitee.com/xliu-huanwei/ark-workload.git'
37    START_INDEX = -19
38    END_INDEX = -5
39
40
41def str_to_bool(value):
42    if isinstance(value, bool):
43        return value
44    if value.lower() in ('true', '1'):
45        return True
46    elif value.lower() in ('false', '0'):
47        return False
48    else:
49        raise argparse.ArgumentTypeError('Invalid boolean value: {}'.format(value))
50
51
52def parse_args():
53    parser = argparse.ArgumentParser()
54    parser.add_argument('--code-path', metavar='DIR',
55                        help='code root path')
56    parser.add_argument('--run-aot', default=False, nargs='?', type=str_to_bool,
57                        help='Default Run run_pgo.sh, run-aot is true run run_aot.sh')
58    parser.add_argument('--report', default=False, nargs='?', type=str_to_bool,
59                        help='Support daily care performance results and script '
60                             'preliminary analysis of performance data.')
61    parser.add_argument('--run-interpreter', default=False, nargs='?', type=str_to_bool,
62                    help='Run the interpreter if set to true')
63    parser.add_argument('--tools-type', default='dev', nargs='?', help='tools type')
64    parser.add_argument('--boundary-value', default=-10, nargs='?',
65                        help='inferior boundary value')
66    parser.add_argument('--run-count', default='10', nargs='?',
67                        help='Compile all cases, execute the case count')
68    parser.add_argument('--code-v', default='', nargs='?', help='Compile weekly_workload')
69    parser.add_argument('--swift-tools-path', default='', nargs='?', help='swift tools path')
70    parser.add_argument('--Ninja-ReleaseAssert', default='', nargs='?', help='Ninja ReleaseAssert')
71    return parser.parse_args()
72
73
74def execute_shell_command(command: str):
75    process = subprocess.Popen(command, shell=False)
76    process.wait()
77
78
79def execute_shell_command_add(command: list):
80    for file in glob.glob(command[-1]):
81        command[-1] = file
82        process = subprocess.Popen(command, shell=False)
83        process.wait()
84
85
86def git_clone(repository_url: str, destination_path: str):
87    command = ['git', 'clone', repository_url, destination_path]
88    if platform.system() == "Windows":
89        subprocess.run(command, check=True, shell=False)
90    else:
91        subprocess.run(command, check=True)
92
93
94def git_checkout(commit_hash: str, destination_path: str):
95    command = ['git', 'checkout', commit_hash]
96    subprocess.run(command, cwd=destination_path)
97
98
99def git_pull(check_out_dir=os.getcwd()):
100    cmds = ['git', 'pull', '--rebase']
101    with subprocess.Popen(cmds, cwd=check_out_dir) as proc:
102        proc.wait()
103
104
105def git_clean(clean_dir=os.getcwd()):
106    cmds = ['git', 'checkout', '--', '.']
107    with subprocess.Popen(cmds, cwd=clean_dir) as proc:
108        proc.wait()
109
110
111def execute_shell_script(script_path, args):
112    command = ['bash', script_path] + args
113    process = subprocess.Popen(command, stdout=subprocess.PIPE,
114                               stderr=subprocess.PIPE, universal_newlines=True)
115    while True:
116        try:
117            text_data = process.stdout.readline()
118            sys.stdout.flush()
119            if len(text_data.strip()) != 0:
120                print(text_data.strip())
121        except OSError as error:
122            if error == errno.ENOENT:
123                print("no such file")
124            elif error == errno.EPERM:
125                print("permission denied")
126            break
127        if not text_data:
128            break
129    process.wait()
130    return_code = process.returncode
131    if return_code != 0:
132        error_output = process.stderr.read().strip()
133        if error_output:
134            print(error_output)
135
136
137def configure_environment(args, code_v, tools_type):
138    swift_tools_path = '~/tools/swift-5.7.3-RELEASE-ubuntu22.04/usr/bin'
139    if args.swift_tools_path:
140        swift_tools_path = args.swift_tools_path
141    ninja_releaseAssert = '~/apple/build/Ninja-ReleaseAssert'
142    if args.Ninja_ReleaseAssert:
143        ninja_releaseAssert = args.Ninja_ReleaseAssert
144    text = f"--case-path {code_v}\n" \
145           f"--ts-tools-path {args.code_path}\n" \
146           f"--tools-type {tools_type}\n" \
147           f"--swift-tools-path {swift_tools_path}\n" \
148           "--android-ndk ~/apple/android-ndk-r25c\n" \
149           f"--Ninja-ReleaseAssert {ninja_releaseAssert}\n" \
150           "end"
151    args = os.O_RDWR | os.O_CREAT
152    file_descriptor = os.open('toolspath.txt', args, stat.S_IRUSR | stat.S_IWUSR)
153    file_object = os.fdopen(file_descriptor, "w+")
154    file_object.write(text)
155
156
157def write_to_txt(file_path, text):
158    args = os.O_RDWR | os.O_CREAT | os.O_APPEND
159    file_descriptor = os.open(file_path, args, stat.S_IRUSR | stat.S_IWUSR)
160    file_object = os.fdopen(file_descriptor, "w+")
161    file_object.write(text)
162
163
164def prepare_workload_code(path):
165    data_dir = os.path.join("arkcompiler/ets_runtime/test/workloadtest/", "data")
166    if path:
167        data_dir = os.path.join(path, data_dir)
168    if not os.path.isdir(os.path.join(data_dir, '.git')):
169        git_clone(WorkLoadConfig.TEST_WORKLOAD_GIT_URL, data_dir)
170        os.chdir(data_dir)
171    else:
172        os.chdir(data_dir)
173        git_clean(data_dir)
174        git_pull(data_dir)
175    execute_shell_command_add(['chmod', '+x', '*.sh'])
176    execute_shell_command_add(['chmod', '+x', '*.py'])
177    try:
178        importlib.import_module('openpyxl')
179    except ImportError:
180        execute_shell_command(['pip', 'install', 'openpyxl'])
181
182
183def report(boundary_value: int):
184    del_out_file()
185    file_paths = glob.glob(os.path.join("./", "pgo_data_*.xlsx"))
186    red_fill = PatternFill(start_color='FF0000', end_color='FF0000', fill_type='solid')
187    file_paths.sort(key=lambda x: datetime.datetime.strptime(
188        x[WorkLoadConfig.START_INDEX:WorkLoadConfig.END_INDEX],
189        "%Y%m%d%H%M%S"), reverse=True)
190    max_two_files = file_paths[:2]
191    boundary_num = 0
192    if len(max_two_files) == 2:
193        wb_one = load_workbook(max_two_files[0])
194        sheet_one = wb_one.active
195        wb_two = load_workbook(max_two_files[1])
196        sheet_two = wb_two.active
197        data_one = []
198        data_two = []
199        for row in sheet_one.iter_rows(min_row=2, values_only=True):
200            data_one.append(row)
201        for row in sheet_two.iter_rows(min_row=2, values_only=True):
202            data_two.append(row)
203        print('generate report dependent files:', max_two_files)
204        result_data = []
205        write_to_txt('../out/pgo_daily.txt', 'case:percentage\n')
206        build_data(data_one, data_two, result_data)
207        result_wb = Workbook()
208        result_sheet = result_wb.active
209        result_sheet.append(['case', 'percentage'])
210        for row in result_data:
211            result_sheet.append(row)
212            cell = result_sheet.cell(row=result_sheet.max_row, column=2)
213            if cell.value and float(cell.value.strip('%')) < boundary_value:
214                cell.fill = red_fill
215                boundary_num += 1
216        now = datetime.datetime.now()
217        name = "".join([now.strftime("%Y%m%d%H%M%S") + "_pgo_daily.xlsx"])
218        result_sheet.append(['Total_Case', str(len(result_data))])
219        result_sheet.append(['Boundary_Total_Case', str(boundary_num)])
220        write_to_txt('../out/pgo_daily.txt', ''.join(["Total_Case", ":", str(len(result_data)), '\n']))
221        write_to_txt('../out/pgo_daily.txt', ''.join(["Boundary_Total_Case", ":", str(boundary_num), '\n']))
222        result_wb.save(os.path.join("../out", name))
223        print('generate report {} success.'.format(name))
224
225
226def build_data(data_one, data_two, result_data):
227    for row_one in data_one:
228        for row_two in data_two:
229            if row_one[0] == row_two[0]:
230                append_data(row_one, row_two, result_data)
231
232
233def append_data(row_one, row_two, result_data):
234    case = row_one[0]
235    average_one = convert_to_num(row_one[-1])
236    average_two = convert_to_num(row_two[-1])
237    if average_one != 0 and average_one != -1 and average_two != -1:
238        difference = (average_one - average_two) / average_one * 100
239        percentage = "{:.2f}%".format(difference)
240        result_data.append([case, percentage])
241        write_to_txt('../out/pgo_daily.txt', ''.join([case, ":", percentage, '\n']))
242
243
244def convert_to_num(str_num):
245    try:
246        double_num = float(str_num)
247        return double_num
248    except ValueError:
249        return -1
250
251
252def del_out_file():
253    destination_dir = '../out/'
254    os.makedirs(destination_dir, exist_ok=True)
255    file_list = os.listdir(destination_dir)
256    for file_name in file_list:
257        file_path = os.path.join(destination_dir, file_name)
258        if os.path.isfile(file_path):
259            os.remove(file_path)
260
261
262def main(args):
263    print("\nWait a moment..........\n")
264    start_time = datetime.datetime.now()
265    prepare_workload_code(args.code_path)
266    print("run_interpreter: " + str(args.run_interpreter))
267    tools_type = 'dev'
268    if args.tools_type:
269        tools_type = args.tools_type
270    boundary_value = -10
271    if args.boundary_value:
272        boundary_value = args.boundary_value
273    run_count = '10'
274    if args.run_count:
275        run_count = args.run_count
276    code_v = 'weekly_workload'
277    if args.code_v:
278        code_v = args.code_v
279    if args.run_aot:
280        print('execute run_aot.sh is currently not supported')
281    else:
282        configure_environment(args, code_v, tools_type)
283        execute_args = ['--build', '--excel']
284        if code_v:
285            execute_args.append('--code-v')
286            execute_args.append(code_v)
287        if args.run_interpreter:
288            execute_args.append('--run-interpreter')
289        execute_args.append('--run')
290        execute_args.append('--run-count')
291        execute_args.append(run_count)
292        execute_shell_script("run_pgo.sh", execute_args)
293    end_time = datetime.datetime.now()
294    print(f"used time is: {str(end_time - start_time)}")
295    if args.report:
296        try:
297            report(int(args.boundary_value))
298        except ValueError:
299            print('args.boundary_value value should be a number')
300
301
302if __name__ == "__main__":
303    sys.exit(main(parse_args()))
304