Files
2023-12-18 20:06:15 +08:00

315 lines
10 KiB
Python

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
#############################################################################
# Copyright (c) 2023 Huawei Technologies Co.,Ltd.
#
# openGauss is licensed under 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.
# ----------------------------------------------------------------------------
# Description : preset management. Including reading and resolving preset.
#############################################################################
import os
import re
import json
from enum import Enum
from impl.perf_config.basic.project import Project
"""
preset is a set of information about some business attributes.
By writing a preset file, the preset configuration can be directly read during
the tool running, thus skipping the stage of business investigation.
preset contains Preset information about the service survey, and the configuration
parameters are related to the service survey.
The preset file is actually a json file. This section describes how to set
configuration items in key-value pairs.
In json, you can write in strings. However, different parameters have different
parsing schemes, such as enumeration, ordinary string, etc., and some parsing rules
are needed to parse and determine whether the preset file is valid.
The built-in preset is stored in the tool code directory.
Also, preset written by the user can be stored in the $GAUSSLOG/om/pg_perfconfig/preset.
The difference between the two is that built-in files are automatically replaced when
the database is upgrade.
The preset name is a file name and needs to end with '.json'.
If files with the same name exist in two directories, the internal directory takes
precedence.
"""
class PsOptionType(Enum):
INT = 'integer'
STR = 'string'
ENUM_VALUE = 'enum value'
ENUM_LIST = 'list of enum'
PATH = 'path'
class PsOptionRule(object):
@staticmethod
def transformIntValue(value, check_range):
"""
transform an int value.
:param value: origin value.
:param check_range: function to check range. return T/F.
:return: NA
"""
if type(value) != int:
value = int(value)
if check_range is not None and not check_range(value):
raise ValueError(f'value {value} not in range.')
return value
@staticmethod
def transformStrValue(value, check_range):
"""
transform a string value.
:param value: origin value.
:param check_range: function to check range. return T/F.
:return: NA
"""
if type(value) != str:
value = str(value)
if check_range is not None and not check_range(value):
raise ValueError(f'value {value} not in range.')
return value
@staticmethod
def transformEnumValue(value, value_list):
"""
transform an enum value.
:param value: origin value.
:param value_list: enumrate list.
:return: NA
"""
if type(value) != str:
value = str(value)
value = value.strip()
if value not in value_list:
raise ValueError(f'value {value} not in range.')
return value
@staticmethod
def transformEnumValueList(value, value_list):
"""
transform an enum value list.
:param value: origin value. The value can be a string separated
by commas or Spaces. It also could be a list.
:param value_list: enumrate list.
:return: NA
"""
opts = value if type(value) == list else re.split(r'[, ]+', str(value))
res = set()
for opt in opts:
if opt not in value_list:
raise ValueError(f'option {opt} not in range.')
res.add(opt)
return list(res)
@staticmethod
def transformPath(value, check_exist):
"""
transform a path.
:param value: origin value.
:param check_exist: Checks whether the path exists and is accessible.
:return: NA
"""
path = value if type(value) == str else str(value)
if check_exist and not os.access(path, os.F_OK):
raise ValueError(f'Could not access path Option path {path}.')
return path
def __init__(self, desc, option_type, default, range_info):
self.desc = desc
self.option_type = option_type
self.default = default
self.range_info = range_info
def regulate(self, value):
if value is None:
return self.default
try:
if self.option_type == PsOptionType.INT:
return PsOptionRule.transformIntValue(value, self.range_info)
if self.option_type == PsOptionType.STR:
return PsOptionRule.transformStrValue(value, self.range_info)
elif self.option_type == PsOptionType.ENUM_VALUE:
return PsOptionRule.transformEnumValue(value, self.range_info)
elif self.option_type == PsOptionType.ENUM_LIST:
return PsOptionRule.transformEnumValueList(value, self.range_info)
elif self.option_type == PsOptionType.PATH:
return PsOptionRule.transformPath(value, self.range_info)
else:
assert False
except ValueError as e:
Project.fatal('Preset error, ' + str(e))
class Preset(object):
# options list.
options = {
'desc': PsOptionRule(
'The description of preset',
PsOptionType.STR,
'no description',
None
),
'scenario': PsOptionRule(
'Business scenario.',
PsOptionType.ENUM_VALUE,
'OLTP-performance',
['OLTP-produce', 'OLTP-performance']
),
'rel_count': PsOptionRule(
'How many tables do you have?',
PsOptionType.INT,
300,
lambda x:x > 0
),
'index_count': PsOptionRule(
'How many indexs do you have?',
PsOptionType.INT,
600,
lambda x:x > 0
),
'rel_kind': PsOptionRule(
'What kind of table do you used?',
PsOptionType.ENUM_LIST,
['heap-table', 'partition-table'],
['heap-table', 'partition-table', 'column-table', 'column-partition-table']
),
'part_count': PsOptionRule(
'How many partitions do you have?',
PsOptionType.INT,
200,
lambda x:x > 0
),
'data_size': PsOptionRule(
'How much data is there? unit is MB.',
PsOptionType.INT,
8192,
lambda x:x > 0
),
'parallel': PsOptionRule(
'How much concurrency is there?',
PsOptionType.INT,
400,
lambda x:x > 0
),
'isolated_xlog': PsOptionRule(
'Storing wal on a separate disk.',
PsOptionType.PATH,
None,
True
)
}
@staticmethod
def usage():
"""
how to write preset.
"""
res = 'The preset configure is a file in JSON format that contains the following parameters:\n\n'
for opt in Preset.options:
res += f'Name: {opt}\n'
res += f' Description: {Preset.options[opt].desc}\n'
res += f' Type: {Preset.options[opt].option_type.value}\n'
if Preset.options[opt].option_type in [PsOptionType.ENUM_VALUE, PsOptionType.ENUM_LIST]:
res += f' Range: {str(Preset.options[opt].range_info)}\n'
res += '\n'
return res
@staticmethod
def get_preset_dir():
dir1 = os.path.join(Project.environ.workspace1, 'preset')
dir2 = os.path.join(Project.environ.workspace2, 'preset')
return dir1, dir2
@staticmethod
def get_all_presets():
def _read_preset_dir(_dir):
"""
read all '.json' file in _dir.
"""
if _dir is None:
return []
if not os.access(_dir, os.F_OK):
Project.warning('Could not access ' + _dir)
return []
files = os.listdir(_dir)
res = []
for file in files:
if file.endswith('.json'):
res.append(file[:-5])
return res
dir1, dir2 = Preset.get_preset_dir()
builtins = _read_preset_dir(dir1)
tmp_usersets = _read_preset_dir(dir2)
# when duplicate name, builtin first.
usersets = [x for x in tmp_usersets if x not in builtins]
return builtins, usersets
def __init__(self, preset_name):
builtins, usersets = Preset.get_all_presets()
dir1, dir2 = Preset.get_preset_dir()
file = ''
if preset_name in builtins:
file = os.path.join(dir1, f'{preset_name}.json')
elif preset_name in usersets:
file = os.path.join(dir2, f'{preset_name}.json')
else:
Project.fatal('Could not find preset: ' + preset_name)
self.name = preset_name
with open(file, 'r') as f:
config = json.load(f)
for key in config:
if not self.options.__contains__(key):
Project.warning(f'skip unknown option {key} in preset.')
self.content = {}
for key in self.options:
val = config.get(key)
self.content[key] = self.options[key].regulate(val)
def __str__(self):
res = f'Preset name: {self.name}\n'
desc = self.content.get('desc')
res += ' {}\n'.format(desc if desc is not None else 'no description')
res += 'Detail:\n'
for k in self.content:
if k == 'desc':
continue
res += ' {0}: {1}\n'.format(k, self.content[k])
return res
def __getitem__(self, item):
return self.content.get(item)