提交 54366299 编写于 作者: O openeuler-ci-bot 提交者: Gitee

!152 atune: refactor the code to generate yaml files and update csv and yaml files

Merge pull request !152 from hanxinke/master
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Copyright (c) 2020 Huawei Technologies Co., Ltd.
# A-Tune is licensed under the Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
# http://license.coscl.org.cn/MulanPSL2
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
# PURPOSE.
# See the Mulan PSL v2 for more details.
# Create: 2020-07-30
"""
The tool to translate .excel file to .yaml files
Usage: python3 translate_xlsx2yaml.py [-h] [-i] [-o] [-t] [-p]
"""
import argparse
import os
import subprocess
import string
from enum import Enum
from io import StringIO
import io
import openpyxl
import glob
import shlex
class CsvAttr(Enum):
"""Enumerate the headers"""
name = 0
desc = 1
geti = 2
seti = 3
needrestart = 4
typei = 5
options = 6
dtype = 7
scope_min = 8
scope_max = 9
step = 10
items = 11
select = 12
class TuningObject:
"""Tuning Object"""
def __init__(self, obj_list, block_dev, network_dev):
self.name = obj_list[CsvAttr.name.value].strip()
self.desc = obj_list[CsvAttr.desc.value].strip()
self.get = obj_list[CsvAttr.geti.value].strip()
self.set = obj_list[CsvAttr.seti.value].strip()
self.needrestart = obj_list[CsvAttr.needrestart.value].strip()
self.type = obj_list[CsvAttr.typei.value].strip()
self.options = obj_list[CsvAttr.options.value].strip()
self.dtype = obj_list[CsvAttr.dtype.value].strip()
self.scope_min = obj_list[CsvAttr.scope_min.value].strip()
self.scope_max = obj_list[CsvAttr.scope_max.value].strip()
self.step = obj_list[CsvAttr.step.value].strip()
self.items = obj_list[CsvAttr.items.value].strip()
self.block_device = block_dev
self.network_device = network_dev
if '@name' in self.get:
self.get = self.get.replace('@name', self.name)
if '@name' in self.set:
self.set = self.set.replace('@name', self.name)
if '@block' in self.get:
self.get = self.get.replace('@block', self.block_device)
if '@block' in self.set:
self.set = self.set.replace('@block', self.block_device)
if '@netdev' in self.get:
self.get = self.get.replace('@netdev', self.network_device)
if '@netdev' in self.set:
self.set = self.set.replace('@netdev', self.network_device)
self.needrestart = self.needrestart.lower()
def exec_cmd(self, cmd, value):
"""
Execute the input command
:param cmd: input command
:param value: the value used in the command
:return: True or False, the results of the execution
"""
if 'echo' in self.set:
cmd1, cmd2 = cmd.split('>')
try:
file = open(cmd2.strip(), "w")
except PermissionError:
return False
process = subprocess.Popen(shlex.split(cmd1), stdout=file, shell=False)
process.wait()
result = str(process.returncode)
elif 'ifconfig' in self.set:
process = subprocess.Popen(shlex.split(cmd), shell=False)
process.wait()
result = str(process.returncode)
elif 'sysctl' in self.set:
result = subprocess.check_output(shlex.split(cmd), stderr=subprocess.STDOUT, shell=False).decode().strip()
else:
process = subprocess.Popen(shlex.split(cmd), shell=False)
process.wait()
result = str(process.returncode)
cmd_fail = False
if 'sysctl' in self.set:
if 'Invalid argument' in result:
cmd_fail = True
elif 'ethtool' in self.set:
if result != '0' and result != '80':
cmd_fail = True
else:
if result != '0':
cmd_fail = True
if cmd_fail:
print('Invalid Value for ', self.name, ' :', value, '. Make sure the value is in valid range!(', cmd, ')')
return False
return True
def valid_test_command(self, value, orig_value):
"""
Test the set command in excel
:param value: the value used in the set command
:param orig_value: the result of get command
:return: True or False, the result of execution set command
"""
if value == orig_value:
return True
cmd = self.set.replace('$value', value)
result = self.exec_cmd(cmd, value)
return result
def valid_test_good(self):
"""
Main check, check the command and given values.
:return: True or False, check result
"""
if self.type == 'continuous':
return True
get_cmd = self.get
count = get_cmd.count('|')
if count > 0:
get_cmds = get_cmd.split('|')
i = 0
for cmds in get_cmds:
if i == 0:
child = subprocess.Popen(shlex.split(cmds), shell=False, stdout=subprocess.PIPE)
elif i == count:
process = subprocess.Popen(shlex.split(cmds), stdin=child.stdout,
stdout=subprocess.PIPE, shell=False)
result = process.communicate()
else:
child = subprocess.Popen(shlex.split(cmds), shell=False, stdin=child.stdout, stdout=subprocess.PIPE)
i += 1
else:
process = subprocess.Popen(shlex.split(get_cmd), shell=False, stdout=subprocess.PIPE)
result = process.communicate()
orig_value = str(result)
if self.dtype == 'int':
result_min = self.valid_test_command(self.scope_min, orig_value)
result_max = self.valid_test_command(self.scope_max, orig_value)
if not result_min or not result_max:
return False
elif self.dtype == 'string':
dis_options = self.options.split(';')
for option in dis_options:
option = '\"' + option.strip() + '\"'
result = self.valid_test_command(option, orig_value)
if not result:
return False
else:
print("Invalid dtype of ", self.name, ":", self.dtype, ". Only support int and string")
return False
return True
def __repr__(self):
fstr = StringIO()
fstr.write(' -\n')
fstr.write(' name : \"' + self.name + '\"\n')
fstr.write(' info :\n')
fstr.write(' desc : \"' + self.desc + '\"\n')
fstr.write(' get : \"' + self.get + '\"\n')
fstr.write(' set : \"' + self.set + '\"\n')
fstr.write(' needrestart : \"' + self.needrestart + '\"\n')
fstr.write(' type : \"' + self.type + '\"\n')
if self.type == 'continuous':
fstr.write(' scope :\n')
fstr.write(' - ' + self.scope_min + '\n')
fstr.write(' - ' + self.scope_max + '\n')
if self.dtype == 'string':
fstr.write(' dtype : \"string\"\n')
else:
fstr.write(' dtype : \"int\"\n')
elif self.type == 'discrete':
if self.dtype == 'string':
fstr.write(' options :\n')
dis_options = self.options.split(';')
for option in dis_options:
fstr.write(' - \"' + option.strip() + '\"\n')
fstr.write(' dtype : \"string\"\n')
elif self.dtype == 'int':
fstr.write(' scope :\n')
fstr.write(' - ' + self.scope_min + '\n')
fstr.write(' - ' + self.scope_max + '\n')
fstr.write(' step : ' + self.step + '\n')
fstr.write(' items : \n')
if self.items != '':
dis_items = self.items.split(';')
for dit in dis_items:
fstr.write(' - ' + dit + '\n')
fstr.write(' dtype : \"int\"\n')
else:
pass
else:
pass
return fstr.getvalue()
class XLSX2YAML:
"""
Get objects from excel files
"""
def __init__(self, in_file_name, out_file_name, project_name, iterations):
with open(in_file_name, 'rb') as file:
in_mem_file = io.BytesIO(file.read())
self.workbook = openpyxl.load_workbook(in_mem_file, read_only=True)
self.out_file = open(out_file_name, 'w')
self.project_name = project_name
self.iterations = iterations
def __del__(self):
self.out_file.close()
def get_head(self):
"""
Generate the header of yaml file.
:return: string, the header of yaml file.
"""
fstr = StringIO()
fstr.write('project:' + ' \"' + self.project_name + '\"' + '\n')
fstr.write('maxiterations: ' + str(self.iterations) + '\n')
fstr.write('startworkload: \"\"\n')
fstr.write('stopworkload: \"\"\n')
fstr.write('object : \n')
return fstr.getvalue()
def read_line_tuning_object(self, worksheet, line):
"""
Get a line of excel into list.
:param worksheet: excel sheet.
:param line: the number of line to get.
:return: list, the tuning object.
"""
obj_list = []
name_ = worksheet[str('B' + str(line))].value
if name_ is None or name_.strip() == "":
return obj_list
cols = string.ascii_uppercase[1:15]
for col in cols:
val = worksheet[str(col + str(line))].value
if val is None:
val = ''
obj_list.append(str(val))
return obj_list
def translate(self, block_dev, network_dev, test):
"""
Translate excel to yaml.
:param block_dev: the name of block device.
:param network_dev: the name of network device.
:param test: whether test the commands in file or not.
:return: True or False, translation result.
"""
self.out_file.write(self.get_head())
sheetnames = self.workbook.sheetnames
worksheet = self.workbook[sheetnames[0]]
line = 2
obj_list = self.read_line_tuning_object(worksheet, line)
if not obj_list:
print("Empty workbook, translation stops:", self.out_file)
return False
while obj_list:
is_select = obj_list[CsvAttr.select.value].strip()
if is_select == 'yes':
tun_obj = TuningObject(obj_list, block_dev, network_dev)
if test == 'True':
valid = tun_obj.valid_test_good()
if not valid:
return False
if tun_obj.name != '':
self.out_file.write(str(tun_obj))
line = line + 1
obj_list = self.read_line_tuning_object(worksheet, line)
return True
def main(in_dir, out_dir, iterations, project_name, block_dev, network_dev, test):
"""
Translate .xlsx files to .yaml files.
:param in_dir: the folder of input excel files.
:param out_dir: the folder of output yaml files.
:param iterations: iterations of the project (> 10).
:param project_name: the name of the configuration project.
:param block_dev: the name of block device.
:param network_dev: the name of network device.
:param test: whether test the commands in file or not.
:return: None
"""
if iterations <= 10:
print('-t iterations must be > 10, the input is ', iterations)
return False
if not os.path.exists(in_dir):
print("Failed: The input directory is not existed:", in_dir)
return False
if not os.path.exists(out_dir):
print("Warning: The output directory is not existed:", out_dir)
return False
in_file_list = glob.glob((str(in_dir) + "*.xlsx"))
if not in_file_list:
print("No .xlsx files exist in the directory")
return False
for file in in_file_list:
in_file_name = file
out_file_name = file.replace(".xlsx", ".yaml")
if os.path.exists(str(out_dir) + out_file_name):
print("Warning: The output yaml file is already exist, overwrite it!--", out_file_name)
xlsx2yaml = XLSX2YAML((in_dir + in_file_name), (out_dir + out_file_name), project_name, iterations)
result = xlsx2yaml.translate(block_dev, network_dev, test)
if result:
print('Translation of ' + str(file) + ' SUCCEEDED!\n')
else:
print('Translation of' + str(file) + ' FAILED!\n')
return result
if __name__ == '__main__':
ARG_PARSER = argparse.ArgumentParser(description="translate excel files to yaml files")
ARG_PARSER.add_argument('-i', '--in_dir', metavar='INPUT DIRECTORY',
default="./", help='The folder of input excel files')
ARG_PARSER.add_argument('-o', '--out_dir', metavar='OUTPUT DIRECTORY',
default="./", help='The folder of output yaml files')
ARG_PARSER.add_argument('-t', '--iteration', metavar='ITERATIONS', type=int,
default="100", help='Iterations of the project (> 10)')
ARG_PARSER.add_argument('-p', '--prj_name', metavar='NAME',
default="example", help='The name of the project')
ARG_PARSER.add_argument('-bd', '--block_device', metavar='BLOCK DEVICE',
default="sda", help='The name of block device')
ARG_PARSER.add_argument('-nd', '--network_device', metavar='NETWORK DEVICE',
default="enp189s0f0", help='The name of network device')
ARG_PARSER.add_argument('-f', '--test', metavar='TEST COMMAND',
default="True", help='Whether test the command or not')
ARGS = ARG_PARSER.parse_args()
main(ARGS.in_dir, ARGS.out_dir, ARGS.iteration, ARGS.prj_name, ARGS.block_device, ARGS.network_device, ARGS.test)
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Copyright (c) 2020 Huawei Technologies Co., Ltd.
# A-Tune is licensed under the Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
# http://license.coscl.org.cn/MulanPSL2
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
# PURPOSE.
# See the Mulan PSL v2 for more details.
# Create: 2020-08-14
......@@ -9,31 +9,26 @@
# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
# PURPOSE.
# See the Mulan PSL v2 for more details.
# Create: 2020-07-30
# Create: 2020-08-14
"""
The tool to translate .csv file to .yaml files
Usage: python3 translate_csv2yaml.py [-h] [-i] [-o] [-t] [-p]
Configure attribute in yaml file
"""
import csv
import argparse
import os
import subprocess
from enum import Enum
from io import StringIO
import glob
import shlex
class CsvAttr(Enum):
"""Enumerate the headers"""
class ConfigAttrName(Enum):
"""Enumerate config attribute"""
name = 0
desc = 1
geti = 2
seti = 3
needrestart = 4
typei = 5
get = 2
set = 3
need_restart = 4
type = 5
options = 6
dtype = 7
scope_min = 8
......@@ -43,40 +38,34 @@ class CsvAttr(Enum):
select = 12
class TuningObject:
"""Tuning Object"""
class ConfigAttribute:
"""config attribute in yaml"""
def __init__(self, obj_list, block_dev, network_dev):
self.name = obj_list[CsvAttr.name.value].strip()
self.desc = obj_list[CsvAttr.desc.value].strip()
self.get = obj_list[CsvAttr.geti.value].strip()
self.set = obj_list[CsvAttr.seti.value].strip()
self.needrestart = obj_list[CsvAttr.needrestart.value].strip()
self.type = obj_list[CsvAttr.typei.value].strip()
self.options = obj_list[CsvAttr.options.value].strip()
self.dtype = obj_list[CsvAttr.dtype.value].strip()
self.scope_min = obj_list[CsvAttr.scope_min.value].strip()
self.scope_max = obj_list[CsvAttr.scope_max.value].strip()
self.step = obj_list[CsvAttr.step.value].strip()
self.items = obj_list[CsvAttr.items.value].strip()
self.name = obj_list[ConfigAttrName.name.value].strip()
self.desc = obj_list[ConfigAttrName.desc.value].strip()
self.get = obj_list[ConfigAttrName.get.value].strip()
self.set = obj_list[ConfigAttrName.set.value].strip()
self.need_restart = obj_list[ConfigAttrName.need_restart.value].strip()
self.type = obj_list[ConfigAttrName.type.value].strip()
self.options = obj_list[ConfigAttrName.options.value].strip()
self.dtype = obj_list[ConfigAttrName.dtype.value].strip()
self.scope_min = obj_list[ConfigAttrName.scope_min.value].strip()
self.scope_max = obj_list[ConfigAttrName.scope_max.value].strip()
self.step = obj_list[ConfigAttrName.step.value].strip()
self.items = obj_list[ConfigAttrName.items.value].strip()
self.block_device = block_dev
self.network_device = network_dev
if '@name' in self.get:
self.get = self.get.replace('@name', self.name)
if '@name' in self.set:
self.set = self.set.replace('@name', self.name)
if '@block' in self.get:
self.get = self.get.replace('@block', self.block_device)
if '@block' in self.set:
self.set = self.set.replace('@block', self.block_device)
if '@netdev' in self.get:
self.get = self.get.replace('@netdev', self.network_device)
if '@netdev' in self.set:
self.set = self.set.replace('@netdev', self.network_device)
replace_map = {'@name': self.name, '@block': self.block_device,
'@netdev': self.network_device}
for key, value in replace_map.items():
if key in self.get:
self.get = self.get.replace(key, value)
if key in self.set:
self.set = self.set.replace(key, value)
self.needrestart = self.needrestart.lower()
self.need_restart = self.need_restart.lower()
def exec_cmd(self, cmd, value):
"""
......@@ -85,22 +74,19 @@ class TuningObject:
:param value: the value used in the command
:return: True or False, the results of the execution
"""
if 'echo' in self.set:
if 'echo ' in self.set and '>' in self.set:
cmd1, cmd2 = cmd.split('>')
try:
file = open(cmd2.strip(), "w")
except PermissionError:
return False
process = subprocess.Popen(shlex.split(cmd1), stdout=file, shell=False)
process.wait()
result = str(process.returncode)
elif 'ifconfig' in self.set:
with open(cmd2.strip(), 'w') as file:
process = subprocess.Popen(shlex.split(cmd1), stdout=file, shell=False)
process.wait()
result = str(process.returncode)
elif 'ifconfig ' in self.set:
process = subprocess.Popen(shlex.split(cmd), shell=False)
process.wait()
result = str(process.returncode)
elif 'sysctl' in self.set:
result = subprocess.check_output(shlex.split(cmd), stderr=subprocess.STDOUT, shell=False).decode().strip()
elif 'sysctl ' in self.set:
result = subprocess.check_output(shlex.split(cmd), stderr=subprocess.STDOUT,
shell=False).decode().strip()
else:
process = subprocess.Popen(shlex.split(cmd), shell=False)
process.wait()
......@@ -110,38 +96,19 @@ class TuningObject:
if 'sysctl' in self.set:
if 'Invalid argument' in result:
cmd_fail = True
elif 'ethtool' in self.set:
if result != '0' and result != '80':
cmd_fail = True
else:
if result != '0':
cmd_fail = True
if cmd_fail:
print('Invalid Value for ', self.name, ' :', value, '. Make sure the value is in valid range!(', cmd, ')')
print('Warning: %s invalid Value %s, make sure the value is in valid range.'
% (self.name, value))
return False
return True
def valid_test_command(self, value, orig_value):
def valid_test_attr(self):
"""
Test the set command in csv
:param value: the value used in the set command
:param orig_value: the result of get command
:return: True or False, the result of execution set command
"""
if value == orig_value:
return True
cmd = self.set.replace('$value', value)
result = self.exec_cmd(cmd, value)
return result
def valid_test_good(self):
"""
Main check, check the command and given values.
check the command and given attr.
:return: True or False, check result
"""
if self.type == 'continuous':
......@@ -149,42 +116,48 @@ class TuningObject:
get_cmd = self.get
count = get_cmd.count('|')
process = None
if count > 0:
get_cmds = get_cmd.split('|')
i = 0
for cmds in get_cmds:
if i == 0:
child = subprocess.Popen(shlex.split(cmds), shell=False, stdout=subprocess.PIPE)
elif i == count:
process = subprocess.Popen(shlex.split(cmds), stdin=child.stdout, stdout=subprocess.PIPE, shell=False)
result = process.communicate()
else:
child = subprocess.Popen(shlex.split(cmds), shell=False, stdin=child.stdout, stdout=subprocess.PIPE)
i += 1
for cmds in get_cmd.split('|'):
process = subprocess.Popen(shlex.split(cmds), shell=False, stdout=subprocess.PIPE,
stdin=None if process is None else process.stdout)
else:
process = subprocess.Popen(shlex.split(get_cmd), shell=False, stdout=subprocess.PIPE)
result = process.communicate()
orig_value = str(result)
result = process.communicate()
orig_value = bytes.decode(result[0]).replace('\n', ' ').strip()
if self.dtype == 'int':
result_min = self.valid_test_command(self.scope_min, orig_value)
result_max = self.valid_test_command(self.scope_max, orig_value)
if not result_min or not result_max:
return False
for value in [self.scope_min, self.scope_max]:
result = self.valid_test_command(value, orig_value)
if not result:
return False
elif self.dtype == 'string':
dis_options = self.options.split(';')
for option in dis_options:
option = '\"' + option.strip() + '\"'
result = self.valid_test_command(option, orig_value)
result = self.valid_test_command(option, '\"' + orig_value + '\"')
if not result:
return False
else:
print("Invalid dtype of ", self.name, ":", self.dtype, ". Only support int and string")
print("Warning: %s invalid dtype %s, only support int and string"
% (self.name, self.dtype))
return False
return True
def valid_test_command(self, value, orig_value):
"""
Test the set command
:param value: the value used in the set command
:param orig_value: the result of get command
:return: True or False, the result of execution set command
"""
if value == orig_value:
return True
cmd = self.set.replace('$value', value)
result = self.exec_cmd(cmd, value)
return result
def __repr__(self):
fstr = StringIO()
fstr.write(' -\n')
......@@ -193,7 +166,7 @@ class TuningObject:
fstr.write(' desc : \"' + self.desc + '\"\n')
fstr.write(' get : \"' + self.get + '\"\n')
fstr.write(' set : \"' + self.set + '\"\n')
fstr.write(' needrestart : \"' + self.needrestart + '\"\n')
fstr.write(' needrestart : \"' + self.need_restart + '\"\n')
fstr.write(' type : \"' + self.type + '\"\n')
if self.type == 'continuous':
......@@ -219,160 +192,14 @@ class TuningObject:
fstr.write(' items : \n')
if self.items != '':
dis_items = self.items.split(';')
for dit in dis_items:
fstr.write(' - ' + dit + '\n')
for item in dis_items:
fstr.write(' - ' + item + '\n')
fstr.write(' dtype : \"int\"\n')
else:
pass
print("Warning: %s invalid dtype %s, only support int and string"
% (self.name, self.dtype))
else:
pass
return fstr.getvalue()
print("Warning: %s invalid type %s, only support continuous and discrete"
% (self.name, self.type))
class XLSX2YAML:
"""
Get objects from excel files
"""
def __init__(self, in_file_name, out_file_name, project_name, iterations):
with open(in_file_name) as file:
reader = csv.reader(file)
self.rows = [row for row in reader]
self.out_file = open(out_file_name, 'w')
self.project_name = project_name
self.iterations = iterations
def __del__(self):
self.out_file.close()
def get_head(self):
"""
Generate the header of yaml file.
:return: string, the header of yaml file.
"""
fstr = StringIO()
fstr.write('project:' + ' \"' + self.project_name + '\"' + '\n')
fstr.write('maxiterations: ' + str(self.iterations) + '\n')
fstr.write('startworkload: \"\"\n')
fstr.write('stopworkload: \"\"\n')
fstr.write('object : \n')
return fstr.getvalue()
def read_line_tuning_object(self, line):
"""
Get a line of csv into list.
:param line: the number of line to get.
:return: list, the tuning object.
"""
obj_list = []
if line >= len(self.rows):
return obj_list
name_ = self.rows[line][1]
if name_ is None or name_.strip() == "":
return obj_list
for col in range(1, 14):
val = self.rows[line][col]
if val is None:
val = ''
obj_list.append(str(val))
return obj_list
def translate(self, block_dev, network_dev, test):
"""
Translate csv to yaml.
:param block_dev: block device name
:param network_dev: network device name
:param test: Whether test the commands or not
:return: True or False, the result of translation
"""
self.out_file.write(self.get_head())
line = 1
obj_list = self.read_line_tuning_object(line)
if not obj_list:
print("Empty workbook, translation stops:", self.out_file)
return False
while obj_list:
is_select = obj_list[CsvAttr.select.value].strip()
if is_select == 'yes':
tun_obj = TuningObject(obj_list, block_dev, network_dev)
if test == 'True':
valid = tun_obj.valid_test_good()
if not valid:
return False
if tun_obj.name != '':
self.out_file.write(str(tun_obj))
line = line + 1
obj_list = self.read_line_tuning_object(line)
return True
def main(in_dir, out_dir, iterations, project_name, block_dev, network_dev, test):
"""
Translate .csv files to .yaml files.
:param in_dir: the folder of input csv files.
:param out_dir: the folder of output yaml files.
:param iterations: iterations of the project (> 10).
:param project_name: the name of the configuration project.
:param block_dev: the name of block device.
:param network_dev: the name of network device.
:param test: Whether test the commands or not
:return: None
"""
if iterations <= 10:
print('-t iterations must be > 10, the input is ', iterations)
return False
if not os.path.exists(in_dir):
print("Failed: The input directory is not existed:", in_dir)
return False
if not os.path.exists(out_dir):
print("Warning: The output directory is not existed:", out_dir)
return False
in_file_list = glob.glob((str(in_dir) + "*.csv"))
if not in_file_list:
print("No .csv files exist in the directory")
return False
for file in in_file_list:
in_file_name = file
out_file_name = file.replace(".csv", ".yaml")
if os.path.exists(str(out_dir) + out_file_name):
print("Warning: The output yaml file is already exist, overwrite it!--", out_file_name)
xlsx2yaml = XLSX2YAML((in_dir + in_file_name), (out_dir + out_file_name),
project_name, iterations)
result = xlsx2yaml.translate(block_dev, network_dev, test)
if result:
print('Translation of ' + str(file) + ' SUCCEEDED!\n')
else:
print('Translation of' + str(file) + ' FAILED!\n')
return result
if __name__ == '__main__':
ARG_PARSER = argparse.ArgumentParser(description="translate excel files to yaml files")
ARG_PARSER.add_argument('-i', '--in_dir', metavar='INPUT DIRECTORY',
default="./", help='The folder of input excel files')
ARG_PARSER.add_argument('-o', '--out_dir', metavar='OUTPUT DIRECTORY',
default="./", help='The folder of output yaml files')
ARG_PARSER.add_argument('-t', '--iteration', metavar='ITERATIONS', type=int,
default="100", help='Iterations of the project (> 10)')
ARG_PARSER.add_argument('-p', '--prj_name', metavar='NAME',
default="example", help='The name of the project')
ARG_PARSER.add_argument('-bd', '--block_device', metavar='BLOCK DEVICE',
default="sda", help='The name of block device')
ARG_PARSER.add_argument('-nd', '--network_device', metavar='NETWORK DEVICE',
default="enp189s0f0", help='The name of network device')
ARG_PARSER.add_argument('-f', '--test', metavar='TEST COMMAND',
default="True", help='Whether test the command or not')
ARGS = ARG_PARSER.parse_args()
main(ARGS.in_dir, ARGS.out_dir, ARGS.iteration, ARGS.prj_name, ARGS.block_device, ARGS.network_device, ARGS.test)
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Copyright (c) 2020 Huawei Technologies Co., Ltd.
# A-Tune is licensed under the Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
# http://license.coscl.org.cn/MulanPSL2
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
# PURPOSE.
# See the Mulan PSL v2 for more details.
# Create: 2020-08-14
"""
The tool to translate file to .yaml files
Usage: python3 translate.py [-h] [-i] [-o] [-t] [-p] [-b] [-n] [-s] [-f]
"""
import argparse
import ast
import os
import glob
from translate_csv2yaml import TranslateCsv2Yaml
from translate_xlsx2yaml import TranslateXlsx2Yaml
def main(in_dir, out_dir, iterations, project_name, block_dev, network_dev, test, file_extension):
"""
Translate .xlsx or .csv files to .yaml files.
:param in_dir: the folder of input files.
:param out_dir: the folder of output yaml files.
:param iterations: iterations of the project (> 10).
:param project_name: the name of the configuration project.
:param block_dev: the name of block device.
:param network_dev: the name of network device.
:param test: whether test the commands in file or not.
:param file_extension: The file extension converted to yaml files.
:return: None
"""
if iterations <= 10:
print('Failed: Iterations must be > 10, the input is %s' % iterations)
return
if not os.path.exists(in_dir):
print("Failed: The input directory (%s) is not existed" % in_dir)
return
if not os.path.exists(out_dir):
print("Failed: The output directory (%s) is not existed" % out_dir)
return
if not file_extension in ("xlsx", "csv"):
print("Failed: The file extension must be be xlsx or csv")
return
in_file_list = glob.glob((str(in_dir) + "*." + file_extension))
if not in_file_list:
print("Warning: No %s files exist in the directory" % file_extension)
return
for file in in_file_list:
in_file_name = file
in_file_basename = os.path.basename(file)
out_file_name = in_file_basename.replace("." + file_extension, ".yaml")
if os.path.exists(str(out_dir) + out_file_name):
print("Warning: The output yaml file (%s) is already exist,"
" overwrite it!--" % out_file_name)
if file_extension == "xlsx":
translate_yaml = TranslateXlsx2Yaml(os.path.join(in_dir, in_file_name),
os.path.join(out_dir, out_file_name),
project_name, iterations,
block_dev, network_dev, test)
else:
translate_yaml = TranslateCsv2Yaml(os.path.join(in_dir, in_file_name),
os.path.join(out_dir, out_file_name),
project_name, iterations,
block_dev, network_dev, test)
if translate_yaml.translate():
print('Translating %s SUCCEEDED!' % str(file))
else:
print('Translating %s FAILED!' % str(file))
if __name__ == '__main__':
ARG_PARSER = argparse.ArgumentParser(description="translate excel or csv files to yaml files")
ARG_PARSER.add_argument('-i', '--in_dir', metavar='INPUT DIRECTORY',
default="./", help='The folder of input excel or csv files')
ARG_PARSER.add_argument('-o', '--out_dir', metavar='OUTPUT DIRECTORY',
default="./", help='The folder of output yaml files')
ARG_PARSER.add_argument('-t', '--iteration', metavar='ITERATIONS', type=int,
default="100", help='Iterations of the project (> 10)')
ARG_PARSER.add_argument('-p', '--prj_name', metavar='NAME',
default="example", help='The name of the project')
ARG_PARSER.add_argument('-b', '--block_device', metavar='BLOCK DEVICE',
default="sda", help='The name of block device')
ARG_PARSER.add_argument('-n', '--network_device', metavar='NETWORK DEVICE',
default="enp189s0f0", help='The name of network device')
ARG_PARSER.add_argument('-s', '--test', metavar='TEST COMMAND',
type=ast.literal_eval, default=False,
help='Whether test the command or not, True or False')
ARG_PARSER.add_argument('-f', '--file_extension', metavar='FILE EXTENSION',
default="csv", help='The file extension converted to yaml files '
'can be xlsx or csv')
ARGS = ARG_PARSER.parse_args()
main(ARGS.in_dir, ARGS.out_dir, ARGS.iteration, ARGS.prj_name,
ARGS.block_device, ARGS.network_device, ARGS.test, ARGS.file_extension)
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Copyright (c) 2020 Huawei Technologies Co., Ltd.
# A-Tune is licensed under the Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
# http://license.coscl.org.cn/MulanPSL2
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
# PURPOSE.
# See the Mulan PSL v2 for more details.
# Create: 2020-07-30
"""
translate .csv file to .yaml files
"""
import csv
from translate_yaml import TranslateYaml
class TranslateCsv2Yaml(TranslateYaml):
"""
The subclass for translating csv to .yaml files
"""
def __init__(self, in_file_name, out_file_name, project_name,
iterations, block_dev, network_dev, test):
super(TranslateCsv2Yaml, self).__init__(out_file_name, project_name,
iterations, block_dev, network_dev, test)
with open(in_file_name) as file:
reader = csv.reader(file)
self.rows = list(reader)
@staticmethod
def read_line(reader, line):
"""
Get a line from csv into list.
:param reader: the content of the csv to be read.
:param line: the number of line to get.
:return: list, the yaml object.
"""
obj_list = []
if line >= len(reader):
return obj_list
name_ = reader[line][1]
if name_ is None or name_.strip() == "":
return obj_list
for col in range(1, 14):
val = reader[line][col]
if val is None:
val = ''
obj_list.append(str(val))
return obj_list
def translate(self):
"""
Translate csv to yaml.
:return: True or False, translation result.
"""
line = 1
return self.write_yaml(self.rows, line)
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Copyright (c) 2020 Huawei Technologies Co., Ltd.
# A-Tune is licensed under the Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
# http://license.coscl.org.cn/MulanPSL2
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
# PURPOSE.
# See the Mulan PSL v2 for more details.
# Create: 2020-07-30
"""
translate .xlsx file to .yaml files
"""
import string
import io
import openpyxl
from translate_yaml import TranslateYaml
class TranslateXlsx2Yaml(TranslateYaml):
"""
The subclass for translating xlsx to .yaml files
"""
def __init__(self, in_file_name, out_file_name, project_name,
iterations, block_dev, network_dev, test):
super(TranslateXlsx2Yaml, self).__init__(out_file_name, project_name,
iterations, block_dev, network_dev, test)
with open(in_file_name, 'rb') as file:
in_mem_file = io.BytesIO(file.read())
self.workbook = openpyxl.load_workbook(in_mem_file, read_only=True)
@staticmethod
def read_line(reader, line):
"""
Get a line from xlsx into list.
:param reader: the content of the xlsx to be read.
:param line: the number of line to get.
:return: list, the yaml object.
"""
obj_list = []
name_ = reader[str('B' + str(line))].value
if name_ is None or name_.strip() == "":
return obj_list
cols = string.ascii_uppercase[1:15]
for col in cols:
val = reader[str(col + str(line))].value
if val is None:
val = ''
obj_list.append(str(val))
return obj_list
def translate(self):
"""
Translate xlsx to yaml.
:return: True or False, translation result.
"""
sheetnames = self.workbook.sheetnames
worksheet = self.workbook[sheetnames[0]]
line = 2
return self.write_yaml(worksheet, line)
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Copyright (c) 2020 Huawei Technologies Co., Ltd.
# A-Tune is licensed under the Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
# http://license.coscl.org.cn/MulanPSL2
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
# PURPOSE.
# See the Mulan PSL v2 for more details.
# Create: 2020-08-14
"""
translating file to .yaml files
"""
from io import StringIO
from config_attribute import ConfigAttrName, ConfigAttribute
class TranslateYaml:
"""The base class for translating file to .yaml files"""
def __init__(self, out_file_name, project_name, iterations, block_dev, network_dev, test):
"""
init TranslateYaml
:param out_file_name: the folder of output yaml files.
:param project_name: the name of the configuration project.
:param iterations: iterations of the project (> 10).
:param block_dev: the name of block device.
:param network_dev: the name of network device.
:param test: whether test the commands in file or not.
"""
self.out_file = open(out_file_name, 'w')
self.project_name = project_name
self.iterations = iterations
self.block_dev = block_dev
self.network_dev = network_dev
self.test = test
def __del__(self):
self.out_file.close()
def get_head(self):
"""
Generate the header of yaml file.
:return: string, the header of yaml file.
"""
fstr = StringIO()
fstr.write('project:' + ' \"' + self.project_name + '\"' + '\n')
fstr.write('maxiterations: ' + str(self.iterations) + '\n')
fstr.write('startworkload: \"\"\n')
fstr.write('stopworkload: \"\"\n')
fstr.write('object : \n')
return fstr.getvalue()
@staticmethod
def read_line(reader, line):
"""
Get a line into list.
:param reader: the content of the file to be read.
:param line: the number of line to get.
:return: list, the yaml object.
"""
print(reader, line)
return []
def translate(self):
"""
Translate file to yaml.
:return: True or False, translation result.
"""
print(self.project_name)
def write_yaml(self, content, line):
"""
write content to yaml.
:param content: the content written to yaml.
:param line: the line of content.
:return: True or False, translation result.
"""
self.out_file.write(self.get_head())
obj_list = self.read_line(content, line)
if not obj_list:
print("Empty workbook, stop translating %s" % self.out_file)
return False
while obj_list:
self.write_config_attr(obj_list)
line = line + 1
obj_list = self.read_line(content, line)
return True
def write_config_attr(self, obj_list):
"""
Write config attribute to yaml
:param obj_list: config attribute
:return: None
"""
is_select = obj_list[ConfigAttrName.select.value].strip()
if is_select == 'yes':
config_attr = ConfigAttribute(obj_list, self.block_dev, self.network_dev)
if self.test:
valid = config_attr.valid_test_attr()
if not valid:
return
if config_attr.name != '':
self.out_file.write(str(config_attr))
此差异已折叠。
......@@ -9,16 +9,16 @@ Category,"name
(Maximum Value of the parameter)","step
(Step of the parameter value)","items
(Enumerated values out of parameter values. Use "";"" to split different values)",select (whether to select the parameter),Remarks
mariadb,mariadb.key_buffer_size,Index parameters of the myisam storage engine,cat /etc/my.cnf | grep key_buffer_size | awk -F '=' '{print $2}' | awk '$1=$1',sed -i 's/key_buffer_size.*/key_buffer_size = $value/g' /etc/my.cnf,false,discrete,,int,1048576?,536870912,1048576?,,yes,
,mariadb.max_allowed_packet,Maximum number of received packets,cat /etc/my.cnf | grep max_allowed_packet | awk -F '=' '{print $2}' | awk '$1=$1',sed -i 's/max_allowed_packet.*/max_allowed_packet = $value/g' /etc/my.cnf,false,discrete,,int,1048576?,1048576?00,1048576?,,yes,
,mariadb.table_open_cache,Table cache for storing data,cat /etc/my.cnf | grep table_open_cache | awk -F '=' '{print $2}' | awk '$1=$1',sed -i 's/table_open_cache.*/table_open_cache = $value/g' /etc/my.cnf,false,discrete,,int,16,1000000,2,,yes,
,mariadb.back_log,The number of new requests stored in the stack,cat /etc/my.cnf | grep back_log | awk -F '=' '{print $2}' | awk '$1=$1',sed -i 's/back_log.*/back_log = $value/g' /etc/my.cnf,false,continuous,,int,16,65536,,,yes,
,mariadb.sort_buffer_size,Cache used for sorting,cat /etc/my.cnf | grep sort_buffer_size | awk -F '=' '{print $2}' | awk '$1=$1',sed -i 's/sort_buffer_size.*/sort_buffer_size = $value/g' /etc/my.cnf,false,discrete,,int,256,1048576?00,1024,,yes,
,mariadb.read_buffer_size,the buffer allocated to each thread during sequential table scanning.,cat /etc/my.cnf | grep read_buffer_size | awk -F '=' '{print $2}' | awk '$1=$1',sed -i 's/read_buffer_size.*/read_buffer_size = $value/g' /etc/my.cnf,false,discrete,,int,1024,1048576?00,1024,,yes,
,mariadb.read_rnd_buffer_size,the buffer allocated to each thread when the table is read randomly,cat /etc/my.cnf | grep read_rnd_buffer_size | awk -F '=' '{print $2}' | awk '$1=$1',sed -i 's/read_rnd_buffer_size.*/read_rnd_buffer_size = $value/g' /etc/my.cnf,false,discrete,,int,1024,1048576?00,1024,,yes,
,mariadb.myisam_sort_buffer_size,the buffer required for reordering when the MyISAM table changes,cat /etc/my.cnf | grep myisam_sort_buffer_size | awk -F '=' '{print $2}' | awk '$1=$1',sed -i 's/myisam_sort_buffer_size.*/myisam_sort_buffer_size = $value/g' /etc/my.cnf,false,discrete,,int,1024,1048576?00,1024,,yes,
,mariadb.thread_cache_size,Number of threads saved in the cache that are reused,cat /etc/my.cnf | grep thread_cache_size | awk -F '=' '{print $2}' | awk '$1=$1',sed -i 's/thread_cache_size.*/thread_cache_size = $value/g' /etc/my.cnf,false,continuous,,int,8,1000,,,yes,
,mariadb.max_connections,the max number of connections,cat /etc/my.cnf | grep max_connections | awk -F '=' '{print $2}' | awk '$1=$1',sed -i 's/max_connections.*/max_connections = $value/g' /etc/my.cnf,false,continuous,,int,10,65536,,,yes,
,mariadb.max_heap_table_size,size of a memory table that can be created,cat /etc/my.cnf | grep max_heap_table_size | awk -F '=' '{print $2}' | awk '$1=$1',sed -i 's/max_heap_table_size.*/max_heap_table_size = $value/g' /etc/my.cnf,false,discrete,,int,1024,1048576?00,1024,,yes,
,mariadb.innodb_buffer_pool_size,size of innodb buffer pool,cat /etc/my.cnf | grep innodb_buffer_pool_size | awk -F '=' '{print $2}' | awk '$1=$1',sed -i 's/innodb_buffer_pool_size.*/innodb_buffer_pool_size = $value/g' /etc/my.cnf,false,discrete,,int,1024,137438953472?,1024,,yes,
,mariadb.innodb_log_buffer_size,size of innodb log buffer,cat /etc/my.cnf | grep innodb_log_buffer_size | awk -F '=' '{print $2}' | awk '$1=$1',sed -i 's/innodb_log_buffer_size.*/innodb_log_buffer_size = $value/g' /etc/my.cnf,false,discrete,,int,1048576?,1048576?00,1048576?,,yes,
mariadb,mariadb.key_buffer_size,Index parameters of the myisam storage engine,cat /etc/my.cnf | grep key_buffer_size | awk -F '=' '{print $2}' | awk '$1=$1',sed -i 's/key_buffer_size.*/key_buffer_size = $value/g' /etc/my.cnf,FALSE,discrete,,int,1048576,536870912,1048576,,yes,
,mariadb.max_allowed_packet,Maximum number of received packets,cat /etc/my.cnf | grep max_allowed_packet | awk -F '=' '{print $2}' | awk '$1=$1',sed -i 's/max_allowed_packet.*/max_allowed_packet = $value/g' /etc/my.cnf,FALSE,discrete,,int,1048576,104857600,1048576,,yes,
,mariadb.table_open_cache,Table cache for storing data,cat /etc/my.cnf | grep table_open_cache | awk -F '=' '{print $2}' | awk '$1=$1',sed -i 's/table_open_cache.*/table_open_cache = $value/g' /etc/my.cnf,FALSE,discrete,,int,16,1000000,2,,yes,
,mariadb.back_log,The number of new requests stored in the stack,cat /etc/my.cnf | grep back_log | awk -F '=' '{print $2}' | awk '$1=$1',sed -i 's/back_log.*/back_log = $value/g' /etc/my.cnf,FALSE,continuous,,int,16,65536,,,yes,
,mariadb.sort_buffer_size,Cache used for sorting,cat /etc/my.cnf | grep sort_buffer_size | awk -F '=' '{print $2}' | awk '$1=$1',sed -i 's/sort_buffer_size.*/sort_buffer_size = $value/g' /etc/my.cnf,FALSE,discrete,,int,256,104857600,1024,,yes,
,mariadb.read_buffer_size,the buffer allocated to each thread during sequential table scanning.,cat /etc/my.cnf | grep read_buffer_size | awk -F '=' '{print $2}' | awk '$1=$1',sed -i 's/read_buffer_size.*/read_buffer_size = $value/g' /etc/my.cnf,FALSE,discrete,,int,1024,104857600,1024,,yes,
,mariadb.read_rnd_buffer_size,the buffer allocated to each thread when the table is read randomly,cat /etc/my.cnf | grep read_rnd_buffer_size | awk -F '=' '{print $2}' | awk '$1=$1',sed -i 's/read_rnd_buffer_size.*/read_rnd_buffer_size = $value/g' /etc/my.cnf,FALSE,discrete,,int,1024,104857600,1024,,yes,
,mariadb.myisam_sort_buffer_size,the buffer required for reordering when the MyISAM table changes,cat /etc/my.cnf | grep myisam_sort_buffer_size | awk -F '=' '{print $2}' | awk '$1=$1',sed -i 's/myisam_sort_buffer_size.*/myisam_sort_buffer_size = $value/g' /etc/my.cnf,FALSE,discrete,,int,1024,104857600,1024,,yes,
,mariadb.thread_cache_size,Number of threads saved in the cache that are reused,cat /etc/my.cnf | grep thread_cache_size | awk -F '=' '{print $2}' | awk '$1=$1',sed -i 's/thread_cache_size.*/thread_cache_size = $value/g' /etc/my.cnf,FALSE,continuous,,int,8,1000,,,yes,
,mariadb.max_connections,the max number of connections,cat /etc/my.cnf | grep max_connections | awk -F '=' '{print $2}' | awk '$1=$1',sed -i 's/max_connections.*/max_connections = $value/g' /etc/my.cnf,FALSE,continuous,,int,10,65536,,,yes,
,mariadb.max_heap_table_size,size of a memory table that can be created,cat /etc/my.cnf | grep max_heap_table_size | awk -F '=' '{print $2}' | awk '$1=$1',sed -i 's/max_heap_table_size.*/max_heap_table_size = $value/g' /etc/my.cnf,FALSE,discrete,,int,1024,104857600,1024,,yes,
,mariadb.innodb_buffer_pool_size,size of innodb buffer pool,cat /etc/my.cnf | grep innodb_buffer_pool_size | awk -F '=' '{print $2}' | awk '$1=$1',sed -i 's/innodb_buffer_pool_size.*/innodb_buffer_pool_size = $value/g' /etc/my.cnf,FALSE,discrete,,int,1024,1.37E+11,1024,,yes,
,mariadb.innodb_log_buffer_size,size of innodb log buffer,cat /etc/my.cnf | grep innodb_log_buffer_size | awk -F '=' '{print $2}' | awk '$1=$1',sed -i 's/innodb_log_buffer_size.*/innodb_log_buffer_size = $value/g' /etc/my.cnf,FALSE,discrete,,int,1048576,104857600,1048576,,yes,
此差异已折叠。
......@@ -12,9 +12,9 @@ object :
needrestart : "false"
type : "discrete"
scope :
- 1,048,576‬
- 1048576
- 536870912
step : 1,048,576‬
step : 1048576
items :
dtype : "int"
-
......@@ -26,9 +26,9 @@ object :
needrestart : "false"
type : "discrete"
scope :
- 1,048,576‬
- 1,048,576‬00
step : 1,048,576‬
- 1048576
- 104857600
step : 1048576
items :
dtype : "int"
-
......@@ -67,7 +67,7 @@ object :
type : "discrete"
scope :
- 256
- 1,048,576‬00
- 104857600
step : 1024
items :
dtype : "int"
......@@ -81,7 +81,7 @@ object :
type : "discrete"
scope :
- 1024
- 1,048,576‬00
- 104857600
step : 1024
items :
dtype : "int"
......@@ -95,7 +95,7 @@ object :
type : "discrete"
scope :
- 1024
- 1,048,576‬00
- 104857600
step : 1024
items :
dtype : "int"
......@@ -109,7 +109,7 @@ object :
type : "discrete"
scope :
- 1024
- 1,048,576‬00
- 104857600
step : 1024
items :
dtype : "int"
......@@ -147,7 +147,7 @@ object :
type : "discrete"
scope :
- 1024
- 1,048,576‬00
- 104857600
step : 1024
items :
dtype : "int"
......@@ -161,7 +161,7 @@ object :
type : "discrete"
scope :
- 1024
- 137,438,953,472‬
- 1.37E+11
step : 1024
items :
dtype : "int"
......@@ -174,8 +174,8 @@ object :
needrestart : "false"
type : "discrete"
scope :
- 1,048,576‬
- 1,048,576‬00
step : 1,048,576‬
- 1048576
- 104857600
step : 1048576
items :
dtype : "int"
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册