openGauss源码解析(159)
openGauss源码解析:AI技术(6)
3. benchmark模块解析
benchmark的驱动脚本存放路径为X-Tuner的benchmark子目录。X-Tuner自带常用的benchmark驱动脚本,例如TPC-C、TPC-H等。X-Tuner通过调用benchmark/__init__.py文件中的get_benchmark_instance函数来加载不同的benchmark驱动脚本,获取benchmark驱动实例。其中,benchmark驱动脚本的格式如表8-3所示。
表8-3 benchmark驱动脚本的格式说明
脚本格式 | 说明 |
驱动脚本文件名 | 表示benchmark的名字,该名字用于表示驱动脚本的唯一性,可通过在X-Tuner的配置文件中的配置项benchmark_script来指定选择加载哪个benchmark驱动脚本 |
驱动脚本内容三要素 | path变量、cmd变量以及run函数 |
benchmark目录中的template.py文件是benchmark驱动脚本的模板,在该目录中,有TPC-C、TPC-H等预先写好的示例,都是基于该模板实现的。该模板定义了benchmark驱动脚本的基本结构,每个benchmark驱动脚本对调优程序来说,都可以认为是一个黑盒,只需要明确输入、输出的格式即可。下面来看看template.py中定义的benchmark驱动脚本格式。
# 提示:你需要先把数据导入到数据库中
# 调优程序会自动调用下述函数,该函数返回结果值就是benchmark测试结果
# path 变量表示实际benchmark所在路径
path = ''
# cmd变量定义了使用什么shell命令才可以启动benchmark
cmd = ''
# 函数定义了远端和本地的命令行接口,通过exec_command_sync()方法执行shell命令
def run(remote_server, local_host) -> float:
return 0
下面给出几个具体的例子,tpcc.py中给出的TPC-C测试脚本的例子如下:
from tuner.exceptions import ExecutionError
# 提示:你需要自己下载benchmark-sql测试工具,同时使用openGauss的JDBC驱动替换PostgreSQL目录下的JDBC驱动文件。你需要自己配置好TPC-C测试配置
# 测试程序通过下述命令运行
path = '/path/to/benchmarksql/run' # TPC-C测试脚本benchmark-sql的存放路径
cmd = "./runBenchmark.sh props.gs" # 自定义一个名为props.gs的benchmark-sql测试配置文件
def run(remote_server,local_host):
# 切换到TPC-C脚本目录下,清除历史错误日志,然后运行测试命令
# 此处最好等待几秒钟,因为benchmark-sql测试脚本生成最终测试报告是通过一个shell脚本实现的,整个过程会有延迟
# 为了保证能够获取到最终的tpmC数值报告,这里选择等待3秒钟
stdout, stderr = remote_server.exec_command_sync(['cd %s' % path, 'rm -rf benchmarksql-error.log', cmd, 'sleep 3'])
# 如果标准错误流中有数据,则报异常退出
if len(stderr) > 0:
raise ExecutionError(stderr)
# 寻找最终tpmC结果
tpmC = None
split_string = stdout.split() # 对标准输出流结果进行分词
for i, st in enumerate(split_string):
# 在benchmark-sql 5.0中,tpmC最终测试结果数值在‘(NewOrders)’关键字的后两位,正常情况下,找到该字段后直接返回即可
if "(NewOrders)" in st:
tpmC = split_string[i + 2]
break
stdout, stderr = remote_server.exec_command_sync(
"cat %s/benchmarksql-error.log" % path)
nb_err = stdout.count("ERROR:") # 判断整个benchmark运行过程中,是否有报错,记录报错的错误数
return float(tpmC) - 10 * nb_err # 这里将报错的错误数作为一个惩罚项,惩罚系数为10,越高的惩罚系数表示越看中报错的数量
其中,TPC-C配置文件props.gs的关键内容如下:
db=opengauss
driver=org.postgresql.Driver
// 配置连接信息
conn=jdbc:postgresql://192.168.1.100:5678/tpcc
…
// 定义数据量
warehouses=1
loadWorkers=4
// 定义并发量
terminals=100
//To run specified transactions per terminal- runMins must equal zero
runTxnsPerTerminal=10
//To run for specified minutes- runTxnsPerTerminal must equal zero
runMins=0
//Number of total transactions per minute
limitTxnsPerMin=300
…
有关TPC-C的测试脚本benchmark-sql的使用,网上公开的教程和资料非常多,此处不再赘述。openGauss的JDBC驱动可以在官方网站上进行下载,下载地址为:https://opengauss.org/zh/download.html。
下面再看一下TPC-H的例子。
import time
from tuner.exceptions import ExecutionError
# 提示:你需要先自行导入数据,然后准备sql测试文件
# 下述程序会自动采集整体运行时延
path = '/path/to/tpch/queries' # 存放TPC-H测试用的SQL脚本目录
cmd = "gsql -U {user} -W {password} -d {db} -p {port} -f {file}" # 需要运行TPC-H测试脚本的命令,一般使用'gsql -f 脚本文件'来运行
# 需要指出的是,由于可能会通过gsql连接数据库,因此可能会需要用户名、密码等信息,可以通过上述占位符,如{user}、{password}等进行占位,X-Tuner会自行渲染
def run(remote_server, local_host):
…
# 代价为全部测试用例的执行总时长
cost = time.time() - time_start
# 取相反数,适配run 函数的定义:返回结果越大表示性能越好。
return - cost
TPC-H脚本的全局变量cmd中存在占位符{user}、{password}等,这些会通过X-Tuner进行渲染,相关代码存在于benchmark/__init__.py中,如下所示:
def get_benchmark_instance(script, path, cmd, db_info):
…
# 验证benchmark 脚本有效性,如果没有指定path与cmd变量,会抛出异常
if (not getattr(bm, 'path', False)) or (not getattr(bm, 'cmd', False)) or (not getattr(bm, 'run', False)):
raise ConfigureError('The benchmark script %s is invalid. '
'For details, see the example template and description document.' % script)
# 检查run函数是否存在,且参数数量为2,即本地和远程两个shell接口
check_run_assertion = isinstance(bm.run, types.FunctionType) and bm.run.__code__.co_argcount == 2
if not check_run_assertion:
raise ConfigureError('The run function in the benchmark instance is not correctly defined. '
'Redefine the function by referring to the examples.')
# cmd与path变量,优先使用配置文件中的配置项,如果没有对应的配置项,则默认使用脚本中的内容
if path.strip() != '':
bm.path = path
if cmd.strip() != '':
bm.cmd = cmd
# 渲染cmd命令中的占位符
bm.cmd = bm.cmd.replace('{host}', db_info['host']) \
.replace('{port}', str(db_info['port'])) \
.replace('{user}', db_info['db_user']) \
.replace('{password}', db_info['db_user_pwd']) \
.replace('{db}', db_info['db_name'])
# 将数据库宿主机的shell接口包装起来
def wrapper(server_ssh):
return bm.run(server_ssh, local_ssh)
return wrapper